Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@
}
},
{
"name": "[sig-operator][Jira:OLM] OLMvo on hypershift mgmt ROSA-OSD_CCS-HyperShiftMGMT-ConnectedOnly-Author:kuiwang-Medium-45381-Support custom catalogs in hypershift",
"name": "[sig-operator][Jira:OLM] OLMv0 on hypershift mgmt PolarionID:45381-[Skipped:Disconnected]Support custom catalogs in hypershift",
"labels": {
"Extended": {},
"NonHyperShiftHOST": {}
},
"resources": {
"isolation": {}
},
"source": "openshift:payload:olmv0",
"lifecycle": "informing",
"environmentSelector": {
"exclude": "topology==\"External\""
}
},
{
"name": "[sig-operator][Jira:OLM] OLMv0 on hypershift mgmt PolarionID:45408-[Skipped:Disconnected]Eliminate use of imagestreams in catalog management",
"labels": {
"Extended": {},
"NonHyperShiftHOST": {}
Expand Down
51 changes: 49 additions & 2 deletions tests-extension/test/qe/specs/olmv0_hypershiftmgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package specs
import (
"context"
"path/filepath"
"strings"

g "github.com/onsi/ginkgo/v2"
o "github.com/onsi/gomega"
Expand All @@ -13,7 +14,7 @@ import (
)

// it is mapping to the Describe "OLM on hypershift" in olm.go
var _ = g.Describe("[sig-operator][Jira:OLM] OLMvo on hypershift mgmt", g.Label("NonHyperShiftHOST"), func() {
var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 on hypershift mgmt", g.Label("NonHyperShiftHOST"), func() {
defer g.GinkgoRecover()

var (
Expand All @@ -25,19 +26,29 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMvo on hypershift mgmt", g.Label(

g.BeforeEach(func() {
exutil.SkipMicroshift(oc)
if !exutil.IsHypershiftMgmtCluster(oc) {
g.Skip("this is not a hypershift management cluster, skip test run")
}

isAKS, errIsAKS = exutil.IsAKSCluster(context.TODO(), oc)
if errIsAKS != nil {
g.Skip("can not determine if it is openshift cluster or aks cluster")
}
if !isAKS {
exutil.SkipNoOLMCore(oc)
}

err := exutil.EnsureHypershiftBinary(oc)
if err != nil {
g.Skip("Failed to setup hypershift binary: " + err.Error())
}

guestClusterName, guestClusterKube, hostedClusterNS = exutil.ValidHypershiftAndGetGuestKubeConf(oc)
e2e.Logf("%s, %s, %s", guestClusterName, guestClusterKube, hostedClusterNS)
oc.SetGuestKubeconf(guestClusterKube)
})

g.It("ROSA-OSD_CCS-HyperShiftMGMT-ConnectedOnly-Author:kuiwang-Medium-45381-Support custom catalogs in hypershift", func() {
g.It("PolarionID:45381-[Skipped:Disconnected]Support custom catalogs in hypershift", func() {
var (
itName = g.CurrentSpecReport().FullText()
buildPruningBaseDir = exutil.FixturePath("testdata", "olm")
Expand Down Expand Up @@ -106,4 +117,40 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMvo on hypershift mgmt", g.Label(

})

g.It("PolarionID:45408-[Skipped:Disconnected]Eliminate use of imagestreams in catalog management", func() {
controlProject := hostedClusterNS + "-" + guestClusterName
if !isAKS {
exutil.SkipBaselineCaps(oc, "None")
g.By("1) check if uses the ImageStream resource")
isOutput, err := oc.AsAdmin().Run("get").Args("is", "catalogs", "-n", controlProject, "-o", "yaml").Output()
if err != nil {
e2e.Failf("Fail to get cronjob in project: %s, error:%v", controlProject, err)
}
is := []string{"certified-operators", "community-operators", "redhat-marketplace", "redhat-operators"}
for _, imageStream := range is {
if !strings.Contains(isOutput, imageStream) {
e2e.Failf("find ImageStream:%s in project:%v", imageStream, controlProject)
}
}
}

g.By("2) check if Deployment uses the ImageStream")
deploys := []string{"certified-operators-catalog", "community-operators-catalog", "redhat-marketplace-catalog", "redhat-operators-catalog"}
for _, deploy := range deploys {
annotations, err := oc.AsAdmin().Run("get").Args("deployment", "-n", controlProject, deploy, "-o=jsonpath={.metadata.annotations}").Output()
if err != nil {
e2e.Failf("Fail to get deploy:%s in project: %s, error:%v", deploy, controlProject, err)
}
if !isAKS {
if !strings.Contains(strings.ToLower(annotations), "imagestream") {
e2e.Failf("The deploy does not use ImageStream: %v", annotations)
}
} else {
if strings.Contains(strings.ToLower(annotations), "imagestream") {
e2e.Failf("The deploy does not use ImageStream: %v", annotations)
}
}
}
})

})
181 changes: 170 additions & 11 deletions tests-extension/test/qe/util/hypeshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"

"github.com/blang/semver/v4"
Expand Down Expand Up @@ -40,21 +44,172 @@ const (
PowerVSPlatform HostedClusterPlatformType = "PowerVS"
)

var (
hypershiftBinaryOnce sync.Once
hypershiftBinarySetup error
)

// EnsureHypershiftBinary ensures hypershift binary is available with cross-process synchronization
func EnsureHypershiftBinary(oc *CLI) error {
hypershiftBinaryOnce.Do(func() {
hypershiftBinarySetup = ensureHypershiftBinaryWithLock(oc)
})
return hypershiftBinarySetup
}

// isArchitectureSupported checks if the current architecture supports hypershift binary
func isArchitectureSupported() error {
arch := runtime.GOARCH
os := runtime.GOOS

e2e.Logf("Current runtime: OS=%s, ARCH=%s", os, arch)

// Hypershift binary is x86-64 Linux ELF executable
if os != "linux" {
return fmt.Errorf("hypershift binary only supports Linux, current OS: %s", os)
}

if arch != "amd64" {
return fmt.Errorf("hypershift binary only supports x86-64 architecture, current architecture: %s", arch)
}

return nil
}

func ensureHypershiftBinaryWithLock(oc *CLI) error {
e2e.Logf("Setting up hypershift binary...")

_, err := exec.LookPath("hypershift")
if err == nil {
e2e.Logf("hypershift command is found in PATH")
return nil
}

// Check architecture compatibility first
if err := isArchitectureSupported(); err != nil {
return err
}

cwd, err := os.Getwd()
if err != nil {
return err
}

var hypershiftPath string
var hypershiftDir string
var lockPath string

if cwd == "/tmp" {
hypershiftPath = filepath.Join(cwd, "hypershift")
hypershiftDir = cwd
lockPath = filepath.Join(cwd, "hypershift.lock")
} else {
hypershiftPath = "/tmp/hypershift"
hypershiftDir = "/tmp"
lockPath = "/tmp/hypershift.lock"
}

lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer func() {
if closeErr := lockFile.Close(); closeErr != nil {
e2e.Logf("Failed to close lock file: %v", closeErr)
}
}()

e2e.Logf("Acquiring file lock for hypershift binary installation...")
maxRetries := 90
for i := 0; i < maxRetries; i++ {
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err == nil {
break
}
if err != syscall.EWOULDBLOCK {
return err
}
e2e.Logf("Lock is held by another process, retrying in 1 second... (%d/%d)", i+1, maxRetries)
time.Sleep(1 * time.Second)
}
if err != nil {
return err
}
defer func() {
if unlockErr := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); unlockErr != nil {
e2e.Logf("Failed to unlock file: %v", unlockErr)
}
}()

e2e.Logf("File lock acquired, checking if hypershift binary exists...")

if _, err := os.Stat(hypershiftPath); err == nil {
e2e.Logf("Hypershift binary already exists at %s", hypershiftPath)
return setupHypershiftEnv(hypershiftDir)
}

e2e.Logf("Extracting hypershift binary from container image...")
err = oc.WithoutNamespace().Run("image").Args("extract", "quay.io/hypershift/hypershift-operator:latest", "--file=/usr/bin/hypershift").Execute()
if err != nil {
return err
}

if hypershiftDir != cwd {
// Move hypershift binary to target directory
err = exec.Command("mv", "hypershift", hypershiftPath).Run()
if err != nil {
return fmt.Errorf("failed to move hypershift binary: %v", err)
}
// Set executable permissions
err = exec.Command("chmod", "755", hypershiftPath).Run()
if err != nil {
return fmt.Errorf("failed to set permissions on hypershift binary: %v", err)
}
} else {
// Set executable permissions in current directory
err = exec.Command("chmod", "755", "hypershift").Run()
if err != nil {
return fmt.Errorf("failed to set permissions on hypershift binary: %v", err)
}
}

e2e.Logf("Hypershift binary installed at %s", hypershiftPath)
return setupHypershiftEnv(hypershiftDir)
}

func setupHypershiftEnv(hypershiftDir string) error {
currentPath := os.Getenv("PATH")
if !strings.Contains(currentPath, hypershiftDir) {
newPath := hypershiftDir + ":" + currentPath
err := os.Setenv("PATH", newPath)
if err != nil {
return err
}
e2e.Logf("Added %s to PATH: %s", hypershiftDir, newPath)
}
return nil
}

// IsHypershiftMgmtCluster checks if the current cluster is a hypershift management cluster
// Returns true if both hypershift operator and hosted cluster namespace exist
func IsHypershiftMgmtCluster(oc *CLI) bool {
operatorNS := GetHyperShiftOperatorNameSpace(oc)
hostedclusterNS := GetHyperShiftHostedClusterNameSpace(oc)
return len(operatorNS) > 0 && len(hostedclusterNS) > 0
}

// ValidHypershiftAndGetGuestKubeConf check if it is hypershift env and get kubeconf of the hosted cluster
// the first return is hosted cluster name
// the second return is the file of kubeconfig of the hosted cluster
// the third return is the hostedcluster namespace in mgmt cluster which contains the generated resources
// if it is not hypershift env, it will skip test.
func ValidHypershiftAndGetGuestKubeConf(oc *CLI) (string, string, string) {
operatorNS := GetHyperShiftOperatorNameSpace(oc)
if len(operatorNS) <= 0 {
g.Skip("there is no hypershift operator on host cluster, so it is not hypershift mgmt cluster and skip test run")
if !IsHypershiftMgmtCluster(oc) {
g.Skip("this is not a hypershift management cluster, skip test run")
}

operatorNS := GetHyperShiftOperatorNameSpace(oc)
hostedclusterNS := GetHyperShiftHostedClusterNameSpace(oc)
if len(hostedclusterNS) <= 0 {
g.Skip("there is no hosted cluster NS in mgmt cluster, so it is not hypershift mgmt cluster and skip test run")
}

clusterNames, err := oc.AsAdmin().WithoutNamespace().Run("get").Args(
"-n", hostedclusterNS, "hostedclusters", "-o=jsonpath={.items[*].metadata.name}").Output()
Expand Down Expand Up @@ -102,15 +257,12 @@ func ValidHypershiftAndGetGuestKubeConf(oc *CLI) (string, string, string) {
// the third return is the hostedcluster namespace in mgmt cluster which contains the generated resources
// if it is not hypershift env, it will not skip the testcase and return null string.
func ValidHypershiftAndGetGuestKubeConfWithNoSkip(oc *CLI) (string, string, string) {
operatorNS := GetHyperShiftOperatorNameSpace(oc)
if len(operatorNS) <= 0 {
if !IsHypershiftMgmtCluster(oc) {
return "", "", ""
}

operatorNS := GetHyperShiftOperatorNameSpace(oc)
hostedclusterNS := GetHyperShiftHostedClusterNameSpace(oc)
if len(hostedclusterNS) <= 0 {
return "", "", ""
}

clusterNames, err := oc.AsAdmin().WithoutNamespace().Run("get").Args(
"-n", hostedclusterNS, "hostedclusters", "-o=jsonpath={.items[*].metadata.name}").Output()
Expand Down Expand Up @@ -170,6 +322,13 @@ func GetHyperShiftOperatorNameSpace(oc *CLI) string {
// GetHyperShiftHostedClusterNameSpace get hypershift hostedcluster namespace
// if not exist, it will return empty string. If more than one exists, it will return the first one.
func GetHyperShiftHostedClusterNameSpace(oc *CLI) string {
// First check if HostedCluster CRD exists
_, crdErr := oc.AsAdmin().WithoutNamespace().Run("get").Args("crd", "hostedclusters.hypershift.openshift.io", "--ignore-not-found").Output()
if crdErr != nil {
e2e.Logf("HostedCluster CRD not found, this is not a hypershift management cluster: %v", crdErr)
return ""
}

namespace, err := oc.AsAdmin().WithoutNamespace().Run("get").Args(
"hostedcluster", "-A", "--ignore-not-found", "-ojsonpath={.items[*].metadata.namespace}").Output()

Expand Down