Skip to content
Draft
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
8 changes: 8 additions & 0 deletions cmd/cluster-config-operator-tests-ext/dependencymagnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

// This file imports test packages to ensure they are registered with the OTE framework.
// The blank import causes the test's init() functions to run, which registers Ginkgo specs.

import (
_ "github.com/openshift/cluster-config-operator/test/e2e"
)
26 changes: 23 additions & 3 deletions cmd/cluster-config-operator-tests-ext/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (

"github.com/spf13/cobra"
"k8s.io/component-base/cli"
"k8s.io/klog/v2"

otecmd "github.com/openshift-eng/openshift-tests-extension/pkg/cmd"
oteextension "github.com/openshift-eng/openshift-tests-extension/pkg/extension"
oteginkgo "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo"
"github.com/openshift/cluster-config-operator/pkg/version"

"k8s.io/klog/v2"
)

func main() {
Expand Down Expand Up @@ -65,9 +65,29 @@ func newOperatorTestCommand() (*cobra.Command, error) {
//
// This method must be called before adding the registry to the OTE framework.
func prepareOperatorTestsRegistry() (*oteextension.Registry, error) {
registry := oteextension.NewRegistry()
// Build test specs from Ginkgo
specs, err := oteginkgo.BuildExtensionTestSpecsFromOpenShiftGinkgoSuite()
if err != nil {
return nil, fmt.Errorf("failed to build test specs from ginkgo: %w", err)
}

// Create extension
extension := oteextension.NewExtension("openshift", "payload", "cluster-config-operator")

// Add test suites
extension.AddSuite(oteextension.Suite{
Name: "openshift/cluster-config-operator/operator/parallel",
Qualifiers: []string{
`name.contains("[Operator]") && name.contains("[Parallel]")`,
},
})

// Add specs to extension
extension.AddSpecs(specs)

// Register extension with registry
registry := oteextension.NewRegistry()
registry.Register(extension)

return registry, nil
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ toolchain go1.24.5
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.38.0
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250804142706-7b3ab438a292
github.com/openshift/api v0.0.0-20250806102053-6a7223edb2fc
github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee
Expand Down Expand Up @@ -45,6 +47,7 @@ require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
Expand All @@ -64,8 +67,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
github.com/onsi/gomega v1.38.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down Expand Up @@ -102,6 +103,7 @@ require (
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/grpc v1.68.1 // indirect
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
Expand Down
61 changes: 61 additions & 0 deletions test/e2e/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package e2e

import (
"context"
"time"

configclient "github.com/openshift/client-go/config/clientset/versioned"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

const (
operatorNamespace = "openshift-config-operator"
operatorName = "openshift-config-operator"
clusterOperatorName = "config-operator"
pollTimeout = 2 * time.Minute
pollInterval = 5 * time.Second
)

// getKubernetesClient returns a Kubernetes client for interacting with the cluster.
func getKubernetesClient() (kubernetes.Interface, error) {
config, err := getRestConfig()
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}

// getRestConfig returns a REST config for the cluster.
func getRestConfig() (*rest.Config, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
return kubeConfig.ClientConfig()
}

// testContext returns a context for test operations.
func testContext() context.Context {
return context.Background()
}

// int64Ptr returns a pointer to an int64 value.
func int64Ptr(i int64) *int64 {
return &i
}

// getConfigClient returns a config client for interacting with OpenShift config resources.
func getConfigClient() (configclient.Interface, error) {
config, err := getRestConfig()
if err != nil {
return nil, err
}
return configclient.NewForConfig(config)
}

// getDynamicClient returns a dynamic client for interacting with arbitrary resources.
func getDynamicClient(config *rest.Config) (dynamic.Interface, error) {
return dynamic.NewForConfig(config)
}
144 changes: 144 additions & 0 deletions test/e2e/operator_health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package e2e

import (
g "github.com/onsi/ginkgo/v2"
o "github.com/onsi/gomega"
configv1 "github.com/openshift/api/config/v1"
operatorv1 "github.com/openshift/api/operator/v1"
configclient "github.com/openshift/client-go/config/clientset/versioned"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// NOTE: Basic ClusterOperator status checks (Available/Degraded/Progressing) are covered by
// origin monitor tests (pkg/monitortests/clusterversionoperator/legacycvomonitortests).
// These tests focus on operator-specific metadata and configuration verification.

var _ = g.Describe("[Operator][Parallel] ClusterOperator Verification", func() {
var (
ctx = testContext()
configClient configclient.Interface
)

g.BeforeEach(func() {
var err error
configClient, err = getConfigClient()
o.Expect(err).NotTo(o.HaveOccurred(), "failed to create config client")
})

g.Context("Version Information", func() {
g.It("should report version information", func() {
co, err := configClient.ConfigV1().ClusterOperators().Get(ctx, clusterOperatorName, metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred())

g.By("checking operator version is reported")
operatorVersion := findVersion(co.Status.Versions, "operator")
o.Expect(operatorVersion).NotTo(o.BeNil(), "operator version should be reported")
o.Expect(operatorVersion.Version).NotTo(o.BeEmpty(), "operator version should not be empty")

g.By("checking feature-gates version is reported")
featureGatesVersion := findVersion(co.Status.Versions, "feature-gates")
o.Expect(featureGatesVersion).NotTo(o.BeNil(), "feature-gates version should be reported")
// Note: feature-gates version can be empty string initially
})
})

g.Context("RelatedObjects", func() {
g.It("should track all required related objects", func() {
co, err := configClient.ConfigV1().ClusterOperators().Get(ctx, clusterOperatorName, metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred())

g.By("checking operator.openshift.io/configs resource is tracked")
found := findRelatedObject(co.Status.RelatedObjects, configv1.ObjectReference{
Group: "operator.openshift.io",
Resource: "configs",
Name: "cluster",
})
o.Expect(found).To(o.BeTrue(), "should track operator.openshift.io/configs/cluster in relatedObjects")

g.By("checking openshift-config namespace is tracked")
found = findRelatedObject(co.Status.RelatedObjects, configv1.ObjectReference{
Resource: "namespaces",
Name: "openshift-config",
})
o.Expect(found).To(o.BeTrue(), "should track openshift-config namespace in relatedObjects")

g.By("checking openshift-config-operator namespace is tracked")
found = findRelatedObject(co.Status.RelatedObjects, configv1.ObjectReference{
Resource: "namespaces",
Name: "openshift-config-operator",
})
o.Expect(found).To(o.BeTrue(), "should track openshift-config-operator namespace in relatedObjects")
})
})

g.Context("Required Namespaces", func() {
g.It("should have all required namespaces", func() {
k8sClient, err := getKubernetesClient()
o.Expect(err).NotTo(o.HaveOccurred())

g.By("checking openshift-config namespace exists")
ns, err := k8sClient.CoreV1().Namespaces().Get(ctx, "openshift-config", metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), "openshift-config namespace should exist")
o.Expect(ns.Name).To(o.Equal("openshift-config"))

g.By("checking openshift-config-managed namespace exists")
ns, err = k8sClient.CoreV1().Namespaces().Get(ctx, "openshift-config-managed", metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), "openshift-config-managed namespace should exist")
o.Expect(ns.Name).To(o.Equal("openshift-config-managed"))

g.By("checking openshift-config-operator namespace exists")
ns, err = k8sClient.CoreV1().Namespaces().Get(ctx, operatorNamespace, metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), "openshift-config-operator namespace should exist")
o.Expect(ns.Name).To(o.Equal(operatorNamespace))
})
})

g.Context("Operator Config CR", func() {
g.It("should exist", func() {
config, err := getRestConfig()
o.Expect(err).NotTo(o.HaveOccurred())

// Use dynamic client to get the operator config CR
dynamicClient, err := getDynamicClient(config)
o.Expect(err).NotTo(o.HaveOccurred())

gvr := operatorv1.GroupVersion.WithResource("configs")
obj, err := dynamicClient.Resource(gvr).Get(ctx, "cluster", metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), "operator.openshift.io/configs/cluster CR should exist")
o.Expect(obj).NotTo(o.BeNil())
o.Expect(obj.GetName()).To(o.Equal("cluster"))
})
})
})

// findCondition finds a condition by type in the conditions slice
func findCondition(conditions []configv1.ClusterOperatorStatusCondition, conditionType configv1.ClusterStatusConditionType) *configv1.ClusterOperatorStatusCondition {
for i := range conditions {
if conditions[i].Type == conditionType {
return &conditions[i]
}
}
return nil
}

// findVersion finds a version by name in the versions slice
func findVersion(versions []configv1.OperandVersion, name string) *configv1.OperandVersion {
for i := range versions {
if versions[i].Name == name {
return &versions[i]
}
}
return nil
}

// findRelatedObject checks if a related object exists in the relatedObjects slice
func findRelatedObject(relatedObjects []configv1.ObjectReference, target configv1.ObjectReference) bool {
for _, obj := range relatedObjects {
if obj.Group == target.Group &&
obj.Resource == target.Resource &&
obj.Name == target.Name {
return true
}
}
return false
}
14 changes: 14 additions & 0 deletions vendor/github.com/go-task/slim-sprig/v3/.editorconfig

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/go-task/slim-sprig/v3/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions vendor/github.com/go-task/slim-sprig/v3/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading