diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index cd96fe95..0a800ede 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -28,6 +28,12 @@ const ( // operational and it can be used by OpenStack Lightspeed operator. OpenShiftLightspeedOperatorReadyCondition condition.Type = "OpenShiftLightspeedOperatorReady" + // OpenStackLightspeedMCPServerReadyCondition is set to True when the reconciliation of the MCP server succeeds. + // This can indicate either that no OpenStack deployment was detected (thus, no MCP server was needed), or that an + // MCP server was successfully deployed because an OpenStack deployment was present. If set to False, it means that + // there was a failure during the MCP server deployment process. + OpenStackLightspeedMCPServerReadyCondition condition.Type = "OpenStackLightspeedMCPServerReady" + // OCPRAGCondition Status=True condition which indicates the OCP RAG version resolution status OCPRAGCondition condition.Type = "OCPRAGReady" ) @@ -63,4 +69,13 @@ const ( // OCPRAGOverrideInvalidMessage OCPRAGOverrideInvalidMessage = "Invalid OCP RAG version override" + + // OpenStackLightspeedMCPServerInitMessage + OpenStackLightspeedMCPServerInitMessage = "MCP server deployment has not resolved" + + // OpenStackLightspeedMCPServerInitNoDeployment + OpenStackLightspeedMCPServerNoDeployment = "MCP server not deployed (no OpenStack instance detected)" + + // OpenStackLightspeedMCPServerDeployed + OpenStackLightspeedMCPServerDeployed = "MCP server is ready" ) diff --git a/api/v1beta1/openstacklightspeed_types.go b/api/v1beta1/openstacklightspeed_types.go index 17237357..e331802c 100644 --- a/api/v1beta1/openstacklightspeed_types.go +++ b/api/v1beta1/openstacklightspeed_types.go @@ -27,6 +27,7 @@ const ( // OpenStackLightspeedContainerImage is the fall-back container image for OpenStackLightspeed OpenStackLightspeedContainerImage = "quay.io/openstack-lightspeed/rag-content:os-docs-2025.2" + MCPServerContainerImage = "quay.io/openstack-lightspeed/rhos-mcps:latest" MaxTokensForResponseDefault = 2048 ) @@ -165,6 +166,7 @@ func (instance OpenStackLightspeed) IsReady() bool { type OpenStackLightspeedDefaults struct { RAGImageURL string + MCPServerImageURL string MaxTokensForResponse int } @@ -176,6 +178,9 @@ func SetupDefaults() { openStackLightspeedDefaults := OpenStackLightspeedDefaults{ RAGImageURL: util.GetEnvVar( "RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT", OpenStackLightspeedContainerImage), + MCPServerImageURL: util.GetEnvVar( + "RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT", MCPServerContainerImage, + ), MaxTokensForResponse: MaxTokensForResponseDefault, } diff --git a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml index 9b2b7299..70070cfc 100644 --- a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml @@ -98,6 +98,27 @@ spec: spec: clusterPermissions: - rules: + - apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: @@ -106,6 +127,14 @@ spec: - get - list - watch + - apiGroups: + - core.openstack.org + resources: + - openstackcontrolplanes + verbs: + - get + - list + - watch - apiGroups: - lightspeed.openstack.org resources: @@ -214,6 +243,8 @@ spec: fieldPath: metadata.annotations['olm.targetNamespaces'] - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + - name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT + value: quay.io/openstack-lightspeed/rhos-mcps:latest image: quay.io/openstack-lightspeed/operator:latest livenessProbe: httpGet: @@ -341,4 +372,6 @@ spec: relatedImages: - image: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 name: openstack-lightspeed-image-url-default + - image: quay.io/openstack-lightspeed/rhos-mcps:latest + name: mcp-server-image-url-default version: 0.0.1 diff --git a/cmd/main.go b/cmd/main.go index 1f71bf5e..905d6e27 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,12 +22,15 @@ import ( "fmt" "os" "strings" + "sync/atomic" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + openstackv1beta1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -39,6 +42,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" "github.com/openstack-lightspeed/operator/internal/controller" @@ -56,6 +60,10 @@ func init() { utilruntime.Must(operatorsv1alpha1.AddToScheme(scheme)) utilruntime.Must(apiv1beta1.AddToScheme(scheme)) + + utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) + + utilruntime.Must(openstackv1beta1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -170,9 +178,17 @@ func main() { // Defaults for OpenStackLightspeed apiv1beta1.SetupDefaults() + dynamicWatchCRDs, err := getDynamicWatchCRDs() + if err != nil { + setupLog.Error(err, "unable to retrieve DynamicWatchCRDs") + os.Exit(1) + } + if err = (&controller.OpenStackLightspeedReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Cache: mgr.GetCache(), + DynamicWatchCRD: dynamicWatchCRDs, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed") os.Exit(1) @@ -210,3 +226,22 @@ func getWatchNamespaces() ([]string, error) { return strings.Split(ns, ","), nil } + +// getDynamicWatchCRDs returns a map of GroupVersionKind to *atomic.Bool +// representing resources that should be watched dynamically. The watch starts +// once they appear in the cluster for the first time (not required at operator +// start time). +func getDynamicWatchCRDs() (map[schema.GroupVersionKind]*atomic.Bool, error) { + var openStackControlPlane openstackv1beta1.OpenStackControlPlane + openStackControlPlaneGVKs, _, err := scheme.ObjectKinds(&openStackControlPlane) + if err != nil { + return nil, err + } + + dynamicWatchCRDs := make(map[schema.GroupVersionKind]*atomic.Bool) + for _, gvk := range openStackControlPlaneGVKs { + dynamicWatchCRDs[gvk] = new(atomic.Bool) + } + + return dynamicWatchCRDs, nil +} diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 788d464a..f093f835 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -74,6 +74,8 @@ spec: fieldPath: metadata.namespace - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + - name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT + value: quay.io/openstack-lightspeed/rhos-mcps:latest securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0c785742..5e0d007e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,27 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: @@ -12,6 +33,14 @@ rules: - get - list - watch +- apiGroups: + - core.openstack.org + resources: + - openstackcontrolplanes + verbs: + - get + - list + - watch - apiGroups: - lightspeed.openstack.org resources: diff --git a/go.mod b/go.mod index 2877c719..f0ba1e40 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,24 @@ go 1.24.6 require ( github.com/go-logr/logr v1.4.3 - github.com/onsi/ginkgo/v2 v2.27.5 - github.com/onsi/gomega v1.39.0 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0 - github.com/operator-framework/api v0.37.0 - k8s.io/apimachinery v0.34.3 - k8s.io/client-go v0.34.2 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.4 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad + github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6 + github.com/operator-framework/api v0.27.0 + k8s.io/api v0.33.2 + k8s.io/apiextensions-apiserver v0.33.2 + k8s.io/apimachinery v0.33.2 + k8s.io/client-go v0.33.2 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.19.7 ) require ( cel.dev/expr v0.24.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect @@ -35,13 +39,17 @@ require ( github.com/go-openapi/swag v0.23.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.1.3 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -49,12 +57,37 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715 // indirect + github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f // indirect + github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d // indirect + github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44 // indirect + github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a // indirect + github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1 // indirect + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c // indirect + github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869 // indirect + github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef // indirect + github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad // indirect + github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84 // indirect + github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede // indirect + github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da // indirect + github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188 // indirect + github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450 // indirect + github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08 // indirect + github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe // indirect + github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4 // indirect + github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d // indirect + github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect + github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1 // indirect + github.com/rhobs/observability-operator v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect @@ -70,36 +103,53 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.36.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + golang.org/x/tools v0.41.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/apiserver v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/apiserver v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +// Pin k8s.io versions to match controller-runtime v0.19.7 and lib-common requirements +replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging + +replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging + +replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging + +replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging + +replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging + +// pin to support rabbitmq 2.16.0 rebase +replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) +replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec diff --git a/go.sum b/go.sum index 264df5b3..0ade5bce 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -19,8 +21,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -54,25 +56,30 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo= +github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -105,14 +112,62 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE= -github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0 h1:2TD4hi+MLt67jKxJUs2tuBKFMxibrLJQqKqhsTMsHeQ= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0/go.mod h1:rgpcv2tLD+/vudXx/gpIQSTuRpk4GOxHx84xwfvQalM= -github.com/operator-framework/api v0.37.0 h1:2XCMWitBnumtJTqzip6LQKUwpM2pXVlt3gkpdlkbaCE= -github.com/operator-framework/api v0.37.0/go.mod h1:NZs4vB+Jiamyv3pdPDjZtuC4U7KX0eq4z2r5hKY5fUA= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715 h1:hNROsRDZp3AEiuybQce8pfeTow1fXUE+2U/s5OaY86g= +github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715/go.mod h1:b02h7tVlbdTcXyMrk8HrJuC0TDN+l4halOffKKSSovc= +github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f h1:HC8zeL4goEVhZ40qEpostFgARLIQWd1/sTaMwME4kIo= +github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f/go.mod h1:FATSK+sHg210WxxPnZKHHckqd52z30fpOeldzvpaR0M= +github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d h1:CpW6pBVLG+rWfG7SuWELI5UG1LO29TcD0H6N3kEIbsE= +github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d/go.mod h1:mkTeZcZh2iCVt94RiSLr79dscYlt1mDveArM+Ko4LqU= +github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44 h1:p+26/PQupwaGs8NHF5TrVDmdcjCik2Nar+panWK5i2U= +github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44/go.mod h1:mdM4lgeEeNIzKv5zDSpz1ZQYwVyTc5zLahzHonoy4x8= +github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a h1:FZzn4xAr7Gyg08j9DnRL4fzDxMqPPphHQAsc5GafU7U= +github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a/go.mod h1:oCU2YPu91BPMRN8a+gKcct3GRXGaIuBj9JAN73nO4dY= +github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1 h1:P+yf2BZz5dR5BW8eUHcNK1aQmnHviFZwa0A3D9WPM3g= +github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1/go.mod h1:rbJiGqYQIGdbfCLienv6qrzC2ve8pl8zhim7Atwe6wE= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c h1:JokgOl3nOi+GxYDHsw/Y1zKYFHFOfzLlhMtm39Jig4E= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c/go.mod h1:5hS/cVzc/HURwsbp4MMNwgqAl5bUqKvJdK+4irXeymE= +github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869 h1:YEBKRN4/vaCzg+jm/7MQwF1R7vsssbgGZIlU4GseF10= +github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869/go.mod h1:rwSopRg6m3xK0KVu8UZ7w3UXhInil/5W4qJMOoAH3VE= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f h1:kww9uX/ai4jkYhH4zzVNqSp4ea5rt1YHJfNmZVUeeOo= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f/go.mod h1:3HPuplIwUZOqIKQ1Cbv8Ciufunof87NO1/3RZOOxINo= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad h1:CCUNIUzCukBYhMJ0aZbQ2/6eF7Lskzzf/CuXLLtEz5k= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef h1:IXN2GwCJLirHciDp/sq8oql4F/ScBCKwklTtUYdptS4= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef/go.mod h1:zOX7Y05keiSppIvLabuyh42QHBMhCcoskAtxFRbwXKo= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad h1:PAQBnG86cQlbgJ8oTaP0glg61BboxcSEHHoTbfLKXyw= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= +github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84 h1:ZMr9smVn/0dbum/HUpNWiASBsecFpZO+WCHGz9l7NNY= +github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84/go.mod h1:jxZ5NdCT6rZdrfUVDDZDLmQxYcNBEwjuT+wivZSqerY= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede h1:EZogjKFvg5SC0ju1c42jccjzLTzdBEUwQBk5loVdsOc= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede/go.mod h1:bBzAf1Wn8fMkNHRp/9lOQCaw717Wgdvl3aUhLnrPuig= +github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da h1:24F/dMYdk5sqBSzFWI9m7x8lEVS2I5tWLWWqLhfuJDQ= +github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da/go.mod h1:wppdogZcqFpqePqElJji7k4gFEMYZvfFvS0qRttHN58= +github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188 h1:ms9ecAqsGVyNTN/6ShW6lI9aUHy5aSSPyh4IHMCO53o= +github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188/go.mod h1:jc2c9B9vPB8eNC64y+wbp1NWfHGf3kaUpqDuolejVDM= +github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450 h1:dy+wWCVAUmrhYqekvlOVA8Nwk6fnTcioLbwZOMGMEXk= +github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450/go.mod h1:yLFm9wOqXvtXSXRh2S0OQfC1sBKR1duQhxmZVKMwhxA= +github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6 h1:uxVBo9bCcCmSH7Ky/jQ3XZAQhMSazs80gB0Dh40YSL4= +github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6/go.mod h1:cByl4/iuZ/93GiXPldeen38HtrsbwQ4rDm8uWShjZAs= +github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08 h1:c0qe8sRmZJZuX/q3Xx1R2I3tKa4+Sjx7MtGa8EDsdvo= +github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08/go.mod h1:WHY9jJVYxJJljm0JwO5EwX8u33mg+mZdfkyRxNz0/XI= +github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe h1:TOJX0PTCqrNrH0jizyu765kJZOCLgPc90E23kA5auzg= +github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe/go.mod h1:FzIV8wfuxxvPHwsxVSiL5t7wV9hu7uuQ2VJQwCvcG5I= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= +github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4 h1:3LXEzcK6Am1kTTGeCATWbhgVAKHEyKx9shKIEjn1n90= +github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4/go.mod h1:Kpak2VcVhlzhyYaExQlsDG5SKRfulZTTWXNXxWlyND0= +github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d h1:kReh7Ix3lKnV+/qqG1dZ2oEhjecZO4cX+RBrAz4t2wQ= +github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d/go.mod h1:Zeoa1UFw7qDDKZ8az7XTeuUSjK+2uDsbn+L2KT9SRsk= +github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6 h1:I/Qnyl0xZI3I/VZtDfZEfCsqzgNTih80WJ9x4g7d4Gg= +github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6/go.mod h1:XEJp64OcVDbT9G1gHowBBruWcZngWN4C5Z8UgpOoqvk= +github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/6VpifiXI= +github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -126,6 +181,12 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1 h1:uahWdfwYHqAb28Y1WsltZsiFlIgjxI8HHQxgUdBgPYQ= +github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1/go.mod h1:I9jGubP/TOORi53RNCK7yPH5cP0TAyxzrNirBhHJtoM= +github.com/rhobs/observability-operator v1.0.0 h1:BqV9ZaIt7/52yJTw9xeSiQ1uo7O1A272AzgQms/uo7M= +github.com/rhobs/observability-operator v1.0.0/go.mod h1:OheVnMXdnGj+GPoVA3UDgsJfC2pXIKluLFe+5LJzxC0= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -140,8 +201,6 @@ github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -185,8 +244,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -198,47 +257,47 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= @@ -254,36 +313,40 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= +k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= +k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= +k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= +k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= +k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.14 h1:DORopWIu2qg7gmVyA9UUGGGmO1Rmnq5Oe+GmsKen3yo= +k8s.io/apiserver v0.31.14/go.mod h1:q81QJuh85u/HN74pdw5Ci4EnrRmCOonZj9FvLwf8DWc= +k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= +k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= +k8s.io/component-base v0.31.14 h1:VNjBuEMmvlwL4twRlMmlaVmsodIRaNivXcZoAx1/x7Q= +k8s.io/component-base v0.31.14/go.mod h1:9ogYcJBUdB4VQ/OMgInYVRScC9bguXxSEEZPsInY+uM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= +sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/controller/funcs.go b/internal/controller/funcs.go index 2be67ed9..5bc00851 100644 --- a/internal/controller/funcs.go +++ b/internal/controller/funcs.go @@ -18,9 +18,19 @@ package controller import ( "context" + "errors" "fmt" "math/rand" "strconv" + "strings" + + corev1 "k8s.io/api/core/v1" + + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,11 +38,16 @@ import ( _ "embed" + appsv1 "k8s.io/api/apps/v1" + + common_cm "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" - k8s_errors "k8s.io/apimachinery/pkg/api/errors" + common_secret "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -41,6 +56,9 @@ const ( // by openstack-operator. OpenStackLightspeedDefaultProvider = "openstack-lightspeed-provider" + // OpenStackLightspeedChecksumAnnotation - is the annotation key used to store the checksum of resources + OpenStackLightspeedChecksumAnnotation = "openstack.org/checksum" + // OpenStackLightspeedOwnerIDLabel - name of a label that contains ID of OpenStackLightspeed instance // that manages the OLSConfig. OpenStackLightspeedOwnerIDLabel = "openstack.org/lightspeed-owner-id" @@ -169,9 +187,12 @@ func BuildRAGConfigs(instance *apiv1beta1.OpenStackLightspeed, ocpVersion string // PatchOLSConfig patches OLSConfig with information from OpenStackLightspeed instance. func PatchOLSConfig( + ctx context.Context, helper *common_helper.Helper, + scheme *runtime.Scheme, instance *apiv1beta1.OpenStackLightspeed, olsConfig *uns.Unstructured, + dynamicWatchCRD *DynamicWatchCRD, ) error { // Patch the Providers section providersPatch := []interface{}{ @@ -282,6 +303,55 @@ func PatchOLSConfig( return err } + available, err := IsDynamicCRDReady( + helper, + *dynamicWatchCRD, + &openstackv1.OpenStackControlPlane{}, + ) + if err != nil { + return err + } + + if available { + OpenShiftMCPServerConfig := map[string]interface{}{ + "name": "openshift-lightspeed-mcp", + "streamableHTTP": map[string]interface{}{ + "url": fmt.Sprintf("%s/openshift", GetMCPServerURL()), + "headers": map[string]interface{}{ + "OCP_TOKEN": "kubernetes", + }, + }, + } + + OpenStackMCPServerConfig := map[string]interface{}{ + "name": "openstack-lightspeed-mcp", + "streamableHTTP": map[string]interface{}{ + "url": fmt.Sprintf("%s/openstack", GetMCPServerURL()), + }, + } + + MCPServersConfig := []interface{}{OpenShiftMCPServerConfig, OpenStackMCPServerConfig} + err = uns.SetNestedSlice(olsConfig.Object, MCPServersConfig, "spec", "mcpServers") + if err != nil { + return err + } + + // Add featureGates to enable "MCPServe" + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{"MCPServer"}, "spec", "featureGates") + if err != nil { + return err + } + } else { + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{}, "spec", "mcpServers") + if err != nil { + return err + } + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{}, "spec", "featureGates") + if err != nil { + return err + } + } + // Add OpenStack finalizers if !controllerutil.AddFinalizer(olsConfig, helper.GetFinalizer()) && instance.Status.Conditions == nil { return fmt.Errorf("cannot add finalizer") @@ -362,3 +432,221 @@ func OLSConfigPing(ctx context.Context, helper *common_helper.Helper) error { } return nil } + +// GetCRDName returns the name of the CustomResourceDefinition (CRD) for a given +// GroupVersionKind (GVK). The CRD name is constructed as "s." string. +// +// NOTE(lpiwowar): This approach is NOT perfect but it is sufficient for +// OpenStackControlPlane use cases and potentially other CRDs. For broader use, +// we should consider implementing a more robust transformation from GroupVersionKind +// to CRD name. +func GetCRDName(gvk schema.GroupVersionKind) string { + return fmt.Sprintf("%ss.%s", strings.ToLower(gvk.Kind), gvk.Group) +} + +// IsCRDEstablished checks if a CRD exists and is in "Established" state (ready for use) +// It returns the following values: +// - (true, nil) if the CRD exists and is established +// - (false, nil) if the CRD doesn't exist +// - (false, error) for other errors +func IsCRDEstablished(ctx context.Context, helper *common_helper.Helper, gvk schema.GroupVersionKind) (bool, error) { + crdName := GetCRDName(gvk) + crd := &apiextensionsv1.CustomResourceDefinition{} + err := helper.GetClient().Get(ctx, client.ObjectKey{Name: crdName}, crd) + if err != nil && k8s_errors.IsNotFound(err) { + return false, nil + } else if err != nil { + return false, err + } + + for _, condition := range crd.Status.Conditions { + if condition.Type == apiextensionsv1.Established && condition.Status == apiextensionsv1.ConditionTrue { + return true, nil + } + } + + return false, nil +} + +// CreateOwnerReference creates an owner reference for the given OpenStackLightspeed instance. +func CreateOwnerReference(instance *apiv1beta1.OpenStackLightspeed) metav1.OwnerReference { + return metav1.OwnerReference{ + APIVersion: instance.APIVersion, + Kind: instance.Kind, + Name: instance.Name, + UID: instance.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + } +} + +// CopyResource copies a resources (supported: Secret, ConfigMap) from one namespace +// to another, applying the specified owner references and computing checksums. +// It performs the following steps: +// +// 1. Fetches the source object identified by its namespace and name. +// 2. Creates or patches the target object in the target namespace with the same data and relevant metadata. +// 3. Sets the given owner references on the copied object. +// 4. Recomputes and sets checksum annotation using the lib-common Hash methods. +// +// Returns the copied object if successful, or an error if the operation fails +// or the type is unsupported. +func CopyResource( + ctx context.Context, + helper *common_helper.Helper, + sourceObject client.Object, + targetObject client.Object, + ownerReference []metav1.OwnerReference, +) (client.Object, error) { + objectKey := types.NamespacedName{ + Namespace: sourceObject.GetNamespace(), + Name: sourceObject.GetName(), + } + + err := helper.GetClient().Get(ctx, objectKey, sourceObject) + if err != nil { + return nil, err + } + + var copyObject client.Object + + switch object := sourceObject.(type) { + case *corev1.Secret: + copySecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetObject.GetName(), + Namespace: targetObject.GetNamespace(), + }, + } + + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), copySecret, func() error { + copySecret.Data = object.Data + copySecret.StringData = object.StringData + copySecret.Type = object.Type + copySecret.SetOwnerReferences(ownerReference) + + checksum, err := common_secret.Hash(copySecret) + if err != nil { + return err + } + + SetChecksumAnnotation(copySecret, checksum) + + return nil + }) + + copyObject = copySecret + case *corev1.ConfigMap: + copyConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetObject.GetName(), + Namespace: targetObject.GetNamespace(), + }, + } + + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), copyConfigMap, func() error { + copyConfigMap.Data = object.Data + copyConfigMap.BinaryData = object.BinaryData + copyConfigMap.SetOwnerReferences(ownerReference) + + checksum, err := common_cm.Hash(copyConfigMap) + if err != nil { + return err + } + + SetChecksumAnnotation(copyConfigMap, checksum) + + return nil + }) + + copyObject = copyConfigMap + default: + return nil, errors.New("cannot copy resource (invalid type)") + } + + if err != nil { + return nil, err + } + + return copyObject, nil +} + +// SetChecksumAnnotation sets or updates the checksum annotation on the provided +// object.This function adds or overwrites only the OpenStackLightspeedChecksumAnnotation +// key, preserving any existing annotations. +func SetChecksumAnnotation(object client.Object, checksum string) { + annotations := object.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + + annotations[OpenStackLightspeedChecksumAnnotation] = checksum + object.SetAnnotations(annotations) +} + +// GetChecksumAnnotation retrieves the checksum annotation from the given object. +// If the annotation is not found, it returns an empty string. +func GetChecksumAnnotation(object client.Object) string { + annotations := object.GetAnnotations() + if annotations == nil { + return "" + } + + checksum, ok := annotations[OpenStackLightspeedChecksumAnnotation] + if !ok { + return "" + } + + return checksum +} + +// GetDeploymentVolumeSection returns a pointer to the Volume in the Deployment's PodSpec +// whose name matches the given volumeSectionName. If no such volume is found, it returns nil. +// This is useful for patching or inspecting a named volume within a Deployment's specification. +func GetDeploymentVolumeSection(deployment appsv1.Deployment, volumeSectionName string) *corev1.Volume { + for i, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Name == volumeSectionName { + return &deployment.Spec.Template.Spec.Volumes[i] + } + } + + return nil +} + +// GetObjectGVKs retrieves the GroupVersionKinds for an clientObject using the +// provided runtime.Scheme. It returns the list of GVKs and any error encountered. +func GetObjectGVKs(helper *common_helper.Helper, object client.Object) ([]schema.GroupVersionKind, error) { + gvks, _, err := helper.GetScheme().ObjectKinds(object) + if err != nil { + return nil, err + } + + return gvks, nil +} + +// IsDynamicCRDReady checks whether all GroupVersionKinds (GVKs) associated with +// the given object are being watched and have been observed as ready by the +// dynamic watch. It returns true only if all relevant GVKs are present and marked as seen. +func IsDynamicCRDReady( + helper *common_helper.Helper, + dynamicWatchCRD DynamicWatchCRD, + object client.Object, +) (bool, error) { + gvks, err := GetObjectGVKs(helper, object) + if err != nil { + return false, err + } + + for _, gvk := range gvks { + seen, exists := dynamicWatchCRD[gvk] + if !exists { + return false, fmt.Errorf("GVK %v not found in DynamicWatchCRD map", gvk) + } + + if !seen.Load() { + return false, nil + } + } + + return true, nil +} diff --git a/internal/controller/mcp_server.go b/internal/controller/mcp_server.go new file mode 100644 index 00000000..88615b5d --- /dev/null +++ b/internal/controller/mcp_server.go @@ -0,0 +1,607 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package controller + +import ( + "context" + _ "embed" + "errors" + "fmt" + + common_deployment "github.com/openstack-k8s-operators/lib-common/modules/common/deployment" + common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" + apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" +) + +// --------------------------------------------------------------------------- +// Builders: MCP Server Deployment +// --------------------------------------------------------------------------- + +const ( + // CloudsYAMLConfigMapName is the name of the ConfigMap, located in the operator's namespace, + // that contains the clouds.yaml configuration file. + CloudsYAMLConfigMapName string = "openstack-config" + + // SecureYAMLSecretName is the name of the Secret, located in the operator's namespace, + // that contains the secrets.yaml configuration file for the MCP server. + SecureYAMLSecretName string = "openstack-config-secret" + + // CombinedCABundleSecretName is the name of the Secret, located in the operator's namespace, + // that contains the combined TLS CA bundle required by the MCP server. + CombinedCABundleSecretName string = "combined-ca-bundle" + + // MCPConfigYAMLConfigMapName is the name of the ConfigMap, located in the operator's namespace, + // that contains the config.yaml file for the MCP server. + MCPConfigYAMLConfigMapName string = "mcp-config" + + // MCPServerPort - Port on which the OpenStack Lightspeed MCP server listens + MCPServerPort = 8080 + + // MCPServiceName specifies the Service name for the OpenStack Lightspeed MCP server + MCPServiceName = "mcp-server-service" +) + +// MCPServerConfig - stores the config file for the MCP server +// +//go:embed mcp_server_config.yaml +var MCPServerConfig string + +// MCPDeploymentLabels are the labels applied to the MCP server deployment. +var MCPDeploymentLabels = map[string]string{ + "app": "openstack-lightspeed-mcp-server", +} + +// BuildMCPServerDeployment creates a Kubernetes Deployment resource for the MCP server. +// The Deployment expects that the following resources exists: +// - ConfigMap with `CloudsYAMLConfigMapName` name containing clouds.yaml +// - ConfigMap with `MCPConfigYAMLConfigMapName` name containing config.yaml +// - Secret with `SecretsYAMLSecretName` name containing secure.yaml +// - Secret with `CombinedCABundleSecretName` name containing tls-ca-bundle.pem +func BuildMCPServerDeployment( + instance *apiv1beta1.OpenStackLightspeed, +) appsv1.Deployment { + deployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mcp-server", + Namespace: instance.Namespace, + Labels: MCPDeploymentLabels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: MCPDeploymentLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: MCPDeploymentLabels, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: SecureYAMLSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: SecureYAMLSecretName, + Items: []corev1.KeyToPath{ + { + Key: "secure.yaml", + Path: "secure.yaml", + }, + }, + }, + }, + }, + { + Name: CloudsYAMLConfigMapName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: CloudsYAMLConfigMapName, + }, + Items: []corev1.KeyToPath{ + { + Key: "clouds.yaml", + Path: "clouds.yaml", + }, + }, + }, + }, + }, + { + Name: CombinedCABundleSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: CombinedCABundleSecretName, + Items: []corev1.KeyToPath{ + { + Key: "tls-ca-bundle.pem", + Path: "tls-ca-bundle.pem", + }, + }, + }, + }, + }, + { + Name: MCPConfigYAMLConfigMapName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: MCPConfigYAMLConfigMapName, + }, + Items: []corev1.KeyToPath{ + { + Key: "config.yaml", + Path: "config.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{{ + Name: "mcp-server-container", + Image: apiv1beta1.OpenStackLightspeedDefaultValues.MCPServerImageURL, + VolumeMounts: []corev1.VolumeMount{ + { + Name: SecureYAMLSecretName, + MountPath: "/app/secure.yaml", + SubPath: "secure.yaml", + }, + { + Name: CloudsYAMLConfigMapName, + MountPath: "/app/clouds.yaml", + SubPath: "clouds.yaml", + }, + { + Name: CombinedCABundleSecretName, + MountPath: "/app/tls-ca-bundle.pem", + SubPath: "tls-ca-bundle.pem", + ReadOnly: true, + }, + { + Name: MCPConfigYAMLConfigMapName, + MountPath: "/app/config.yaml", + SubPath: "config.yaml", + }, + }, + }}, + }, + }, + }, + } + + deployment.SetOwnerReferences([]metav1.OwnerReference{ + CreateOwnerReference(instance), + }) + + return deployment +} + +func BuildMCPServerService( + instance *apiv1beta1.OpenStackLightspeed, +) corev1.Service { + service := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: MCPServiceName, + Namespace: instance.Namespace, + Labels: MCPDeploymentLabels, + }, + Spec: corev1.ServiceSpec{ + Selector: MCPDeploymentLabels, + Ports: []corev1.ServicePort{ + { + Name: "http", + Protocol: corev1.ProtocolTCP, + Port: MCPServerPort, + TargetPort: intstr.FromInt(MCPServerPort), + }, + }, + Type: corev1.ServiceTypeClusterIP, + }, + } + + return service +} + +func BuildMCPServerConfigMap( + instance *apiv1beta1.OpenStackLightspeed, +) corev1.ConfigMap { + configMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: MCPConfigYAMLConfigMapName, + Namespace: instance.Namespace, + }, + Data: map[string]string{ + "config.yaml": MCPServerConfig, + }, + } + + configMap.SetOwnerReferences([]metav1.OwnerReference{ + CreateOwnerReference(instance), + }) + + return configMap +} + +func GetMCPServerURL() string { + return fmt.Sprintf("http://%s:%d", MCPServiceName, MCPServerPort) +} + +// --------------------------------------------------------------------------- +// End Builders +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Reconcile: MCP deployment +// --------------------------------------------------------------------------- + +// ReconcileMCPServer performs the reconciliation of the OpenStack Lightspeed +// MCP server. +func (r *OpenStackLightspeedReconciler) ReconcileMCPServer( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (ctrl.Result, error) { + var openStackControlPlaneInstance openstackv1.OpenStackControlPlane + ready, err := IsDynamicCRDReady(helper, r.DynamicWatchCRD, &openStackControlPlaneInstance) + if err != nil { + return ctrl.Result{}, err + } else if !ready { + helper.GetLogger().Info("OpenStackControlPlane CRD not available, deleting MCP server") + return r.ReconcileMCPServerDelete(ctx, helper, instance) + } + + openStackControlPlaneList := &openstackv1.OpenStackControlPlaneList{} + err = r.List(ctx, openStackControlPlaneList) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + switch openStackControlPlaneListLen := len(openStackControlPlaneList.Items); openStackControlPlaneListLen { + + case 0: + helper.GetLogger().Info("No OpenStackControlPlane found, deleting MCP server") + return r.ReconcileMCPServerDelete(ctx, helper, instance) + + case 1: + openStackControlPlaneInstance = openStackControlPlaneList.Items[0] + return r.ReconcileMCPServerCreate(ctx, helper, instance, &openStackControlPlaneInstance) + + default: + err = errors.New("more than one OpenStackControlPlane found") + return ctrl.Result{}, err + } +} + +// ReconcileMCPServerCreate ensures the MCP Server is deployed and configured. +// It copies required resources from the openstack namespace, creates +// the MCP Server ConfigMap, and makes sure the deployment uses the latest resources. +// The OpenStackLightspeed instance status is updated on success; on error, +// the caller must update status. +func (r *OpenStackLightspeedReconciler) ReconcileMCPServerCreate( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (ctrl.Result, error) { + ok, err := CheckRequiredOpenStackControlPlaneFields(helper, openStackControlPlaneInstance) + if err != nil { + return ctrl.Result{}, err + } else if !ok { + return ctrl.Result{}, nil + } + + copiedObjects, err := CopyObjectsToOpenStackLightspeedNamespace( + ctx, + helper, + instance, + openStackControlPlaneInstance, + ) + if err != nil && k8s_errors.IsNotFound(err) { + return ctrl.Result{}, nil + } else if err != nil { + return ctrl.Result{}, err + } + + ConfigYAMLConfigMap := BuildMCPServerConfigMap(instance) + err = helper.GetClient().Create(ctx, &ConfigYAMLConfigMap) + if err != nil && !k8s_errors.IsAlreadyExists(err) { + return ctrl.Result{}, err + } + + err = DeployMCPServer(ctx, helper, instance, openStackControlPlaneInstance, copiedObjects) + if err != nil { + return ctrl.Result{}, err + } + + // A set of resources we want to be owned by the MCP Server deployment. + resourcesToOwn := []client.Object{ + copiedObjects[CloudsYAMLConfigMapName], + copiedObjects[SecureYAMLSecretName], + copiedObjects[CombinedCABundleSecretName], + &ConfigYAMLConfigMap, + } + err = MCPServerDeploymentOwnResources(ctx, helper, instance, resourcesToOwn...) + if err != nil { + return ctrl.Result{}, err + } + + mcpServerDeploymentReady, err := IsMCPServerDeploymentReady(ctx, helper, instance) + if err != nil { + return ctrl.Result{}, err + } + + if mcpServerDeploymentReady { + instance.Status.Conditions.MarkTrue( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + apiv1beta1.OpenStackLightspeedMCPServerDeployed, + ) + } + + return ctrl.Result{}, nil +} + +func (r *OpenStackLightspeedReconciler) ReconcileMCPServerDelete( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (ctrl.Result, error) { + deployment := BuildMCPServerDeployment(instance) + err := helper.GetClient().Delete(ctx, &deployment) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + instance.Status.Conditions.MarkTrue( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + apiv1beta1.OpenStackLightspeedMCPServerNoDeployment, + ) + + return ctrl.Result{}, nil +} + +// CheckRequiredOpenStackControlPlaneFields checks whether the required fields are present +// in the OpenStackControlPlane instance for the MCP server. The following fields are validated: +// - .Spec.OpenStackClient.Template.OpenStackConfigSecret: +// The name of the Secret containing secrets.yaml. This field is required. +// - .Spec.OpenStackClient.Template.OpenStackConfigMap: +// The name of the ConfigMap containing clouds.yaml. This field is required. +// - .Status.TLS.CaBundleSecretName: +// The name of the Secret containing CA certificates. This gets populated during +// the deployment. +func CheckRequiredOpenStackControlPlaneFields( + helper *common_helper.Helper, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (bool, error) { + if openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigSecret == nil { + err := errors.New("OpenStackClient.Template.OpenStackConfigSecret is missing value") + return false, err + } else if openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigMap == nil { + err := errors.New("OpenStackControlPlane.OpenStackClient.Template.OpenStackConfigMap is missing value") + return false, err + } else if openStackControlPlaneInstance.Status.TLS.CaBundleSecretName == "" { + helper.GetLogger().Info("Waiting for OpenStackControlPlaneInstance.Status.TLS.CaBundleSecretName value") + return false, nil + } + + return true, nil +} + +// CopyObjectsToOpenStackLightspeedNamespace copies the required ConfigMaps and +// Secrets from the OpenStackControlPlane's namespace to the OpenStack Lightspeed +// deployment namespace, making these resources available to the MCP server. +// All necessary fields in the OpenStackControlPlane instance must be non-nil +// and valid. It is recommended to call CheckRequiredOpenStackControlPlaneFields +// to ensure this. +func CopyObjectsToOpenStackLightspeedNamespace( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (map[string]client.Object, error) { + objectsToCopy := map[string]client.Object{ + SecureYAMLSecretName: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: *openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigSecret, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + CloudsYAMLConfigMapName: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: *openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigMap, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + CombinedCABundleSecretName: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: openStackControlPlaneInstance.Status.TLS.CaBundleSecretName, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + } + + ownerReference := []metav1.OwnerReference{CreateOwnerReference(instance)} + copiedObjects := make(map[string]client.Object) + var err error + + for resourceName, sourceObject := range objectsToCopy { + targetObject := sourceObject.DeepCopyObject().(client.Object) + targetObject.SetNamespace(instance.Namespace) + targetObject.SetName(resourceName) + + copiedObjects[resourceName], err = CopyResource(ctx, helper, sourceObject, targetObject, ownerReference) + if err != nil && k8s_errors.IsNotFound(err) { + helper.GetLogger().Info( + "Resource %s not found in namespace %s, waiting for it to be created", + sourceObject.GetName(), + sourceObject.GetNamespace(), + ) + return nil, err + } else if err != nil { + return nil, err + } else if copiedObjects[resourceName] == nil { + return nil, errors.New("the internal representatnion of the copied object is nil") + } + } + + return copiedObjects, nil +} + +// DeployMCPServer ensures that the MCP Server Deployment is up-to-date and +// properly configured. It creates or patches the Deployment by injecting the +// names of the necessary copied resources (ConfigMaps and Secrets) from the +// OpenStack related namespace. Additionally, it sets annotations on +// the Deployment with the checksums of these resources. Any change in resource +// content will trigger a restart of the pods, ensuring consistent configuration. +func DeployMCPServer( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, + requiredResources map[string]client.Object, +) error { + deployment := BuildMCPServerDeployment(instance) + _, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), &deployment, func() error { + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = make(map[string]string) + } + + var volumeSections []*corev1.Volume + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, CloudsYAMLConfigMapName)) + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, SecureYAMLSecretName)) + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, CombinedCABundleSecretName)) + + var deploymentChecksum string + const checksumPrefixLen = 10 + for _, volumeSection := range volumeSections { + if volumeSection == nil { + return errors.New("missing volume section in MCP Server deployment") + } + + checksum := GetChecksumAnnotation(requiredResources[volumeSection.Name]) + if len(checksum) == 0 { + return fmt.Errorf("missing checksum annotation for: %s", volumeSection.Name) + } + + deploymentChecksum += checksum[:checksumPrefixLen] + } + + // Setting the checksum annotation ensures the MCP server pod is automatically redeployed + // whenever any associated ConfigMap or Secret changes. + deployment.Spec.Template.Annotations[OpenStackLightspeedChecksumAnnotation] = deploymentChecksum + + return nil + }) + if err != nil { + return err + } + + service := BuildMCPServerService(instance) + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), &service, func() error { + return nil + }) + + return err +} + +// GetMCPServerDeployment returns (true, nil) if the MCPServer deployment is ready. +// Otherwise, it returns (false, err) or (false, nil) when the deployment is not found. +func GetMCPServerDeployment( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (*appsv1.Deployment, error) { + deployment := BuildMCPServerDeployment(instance) + + latestDeployment := &appsv1.Deployment{} + err := helper.GetClient().Get(ctx, client.ObjectKey{ + Name: deployment.Name, + Namespace: deployment.Namespace, + }, latestDeployment) + if err != nil && k8s_errors.IsNotFound(err) { + return nil, nil + } else if err != nil { + return nil, err + } + + return latestDeployment, nil +} + +// MCPServerDeploymentOwnResources sets the specified resources in mcpServerResources to be owned +// by the MCP Server Deployment by assigning an OwnerReference to each resource. This ensures that +// these resources are automatically cleaned up when the deployment is deleted. +func MCPServerDeploymentOwnResources( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + mcpServerResources ...client.Object, +) error { + MCPServerDeployemnt, err := GetMCPServerDeployment(ctx, helper, instance) + if err != nil { + return err + } + + if MCPServerDeployemnt != nil { + deploymentOwnerRefs := []metav1.OwnerReference{ + { + APIVersion: MCPServerDeployemnt.APIVersion, + Kind: MCPServerDeployemnt.Kind, + Name: MCPServerDeployemnt.Name, + UID: MCPServerDeployemnt.UID, + }, + } + for _, obj := range mcpServerResources { + obj.SetOwnerReferences(deploymentOwnerRefs) + err = helper.GetClient().Update(ctx, obj) + if err != nil { + return err + } + } + } + + return nil +} + +// IsMCPServerDeploymentReady returns true if the MCP server deployment is fully +// ready and available for use. +func IsMCPServerDeploymentReady( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (bool, error) { + MCPServerDeployemnt, err := GetMCPServerDeployment(ctx, helper, instance) + if err != nil { + return false, err + } + + return common_deployment.IsReady(*MCPServerDeployemnt), nil +} + +// --------------------------------------------------------------------------- +// End Reconcile +// --------------------------------------------------------------------------- diff --git a/internal/controller/mcp_server_config.yaml b/internal/controller/mcp_server_config.yaml new file mode 100644 index 00000000..5c59add2 --- /dev/null +++ b/internal/controller/mcp_server_config.yaml @@ -0,0 +1,22 @@ +--- +ip: 0.0.0.0 +port: 8080 +debug: false +workers: 1 +processes_pool_size: 10 + +openstack: + allow_write: false + ca_cert: ./tls-ca-bundle.pem + insecure: false + +openshift: + allow_write: false + insecure: false + +mcp_transport_security: + enable_dns_rebinding_protection: false + allowed_hosts: + - "*:*" + allowed_origins: + - "http://*:*" diff --git a/internal/controller/openstacklightspeed_controller.go b/internal/controller/openstacklightspeed_controller.go index 67c32dd2..1ac3a5ee 100644 --- a/internal/controller/openstacklightspeed_controller.go +++ b/internal/controller/openstacklightspeed_controller.go @@ -19,12 +19,16 @@ package controller import ( "context" "fmt" + "sync/atomic" "time" "github.com/go-logr/logr" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -32,20 +36,32 @@ import ( "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" ) -// OpenStackLightspeedReconciler reconciles a OpenStackLightspeed object +type DynamicWatchCRD map[schema.GroupVersionKind]*atomic.Bool + +// OpenStackLightspeedReconciler is responsible for reconciling OpenStackLightspeed objects. type OpenStackLightspeedReconciler struct { client.Client - Scheme *runtime.Scheme - Kclient kubernetes.Interface + Scheme *runtime.Scheme + Kclient kubernetes.Interface + controller controller.Controller + Cache cache.Cache + + // DynamicWatchCRD contains the list of CRDs that the operator should monitor. + // These CRDs do not need to exist when the operator starts. Once the operator + // detects that a CRD exists, it automatically registers a watch for it using Watch(). + DynamicWatchCRD DynamicWatchCRD } // GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields @@ -64,6 +80,10 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // +kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,namespace=openshift-lightspeed,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=operators.coreos.com,resources=installplans,namespace=openshift-lightspeed,verbs=get;list;watch;update;delete // +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch +// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch +// +kubebuilder:rbac:groups=core.openstack.org,resources=openstackcontrolplanes,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -74,7 +94,7 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/reconcile -func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, e error) { Log := r.GetLogger(ctx) Log.Info("OpenStackLightspeed Reconciling") @@ -99,6 +119,11 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, err } + err = r.WatchDynamicCRD(ctx, helper) + if err != nil { + return ctrl.Result{}, err + } + // Save a copy of the conditions so that we can restore the LastTransitionTime // when a condition's state doesn't change. savedConditions := instance.Status.Conditions.DeepCopy() @@ -130,6 +155,11 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return } + for _, GVKSeen := range r.DynamicWatchCRD { + if !GVKSeen.Load() { + result.RequeueAfter = 30 * time.Second + } + } }() cl := condition.CreateList( @@ -162,6 +192,19 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. instance.Spec.MaxTokensForResponse = apiv1beta1.OpenStackLightspeedDefaultValues.MaxTokensForResponse } + res, err := r.ReconcileMCPServer(ctx, helper, instance) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error(), + )) + + return res, err + } + // Ensure a compatible version of the OpenShift Lightspeed Operator is running in the cluster. // This checks if the correct OLS Operator version is present and installs it if necessary. isOLSOperatorInstalled, err := EnsureOLSOperatorInstalled(ctx, helper, instance) @@ -221,7 +264,7 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return fmt.Errorf("OLSConfig is managed by different OpenStackLightspeed instance") } - err = PatchOLSConfig(helper, instance, &olsConfig) + err = PatchOLSConfig(ctx, helper, r.Scheme, instance, &olsConfig, &r.DynamicWatchCRD) if err != nil { return err } @@ -388,10 +431,11 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error Kind: "ClusterVersion", }) - return ctrl.NewControllerManagedBy(mgr). + c, err := ctrl.NewControllerManagedBy(mgr). For(&apiv1beta1.OpenStackLightspeed{}). Owns(&operatorsv1alpha1.ClusterServiceVersion{}). Owns(&operatorsv1alpha1.Subscription{}). + Owns(&appsv1.Deployment{}). Watches( &operatorsv1alpha1.InstallPlan{}, handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), @@ -402,7 +446,28 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). - Complete(r) + Watches( + &apiextensionsv1.CustomResourceDefinition{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Build(r) + if err != nil { + return err + } + + r.controller = c + return nil } // NotifyAllOpenStackLightspeeds returns a list of reconcile requests for all OpenStackLightspeed objects. @@ -436,3 +501,44 @@ func (r *OpenStackLightspeedReconciler) NotifyAllOpenStackLightspeeds(ctx contex return requests } + +// WatchDynamicCRD dynamically registers watches for resources whose CRDs are listed +// in r.DynamicWatchCRD. When a target CRD is detected as existing and available in the +// cluster, this method ensures that the controller starts watching resources of that type. +// This enables reconciliation to be triggered whenever those resources are created or modified. +func (r *OpenStackLightspeedReconciler) WatchDynamicCRD( + ctx context.Context, + helper *common_helper.Helper, +) error { + for gvk, seen := range r.DynamicWatchCRD { + crdAvailable, err := IsCRDEstablished(ctx, helper, gvk) + if err != nil { + return err + } + + if !crdAvailable { + seen.Store(false) + continue + } + + GVKUnstructObj := &uns.Unstructured{} + GVKUnstructObj.SetGroupVersionKind(gvk) + err = r.controller.Watch( + source.Kind( + r.Cache, + GVKUnstructObj, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, o *uns.Unstructured) []ctrl.Request { + return r.NotifyAllOpenStackLightspeeds(ctx, o) + }), + predicate.TypedResourceVersionChangedPredicate[*uns.Unstructured]{}, + ), + ) + if err != nil { + return fmt.Errorf("failed to set up watch for %s: %w", GetCRDName(gvk), err) + } + + seen.Store(true) + } + + return nil +} diff --git a/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml b/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml new file mode 100644 index 00000000..73ba4525 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed +spec: + replicas: 1 +status: + readyReplicas: 1 + availableReplicas: 1 +--- +apiVersion: lightspeed.openstack.org/v1beta1 +kind: OpenStackLightspeed +metadata: + name: openstack-lightspeed + namespace: openshift-lightspeed +status: + conditions: + - type: Ready + status: "True" + reason: Ready + - type: OCPRAGReady + status: "True" + reason: Ready + - type: OpenShiftLightspeedOperatorReady + status: "True" + reason: Ready + - type: OpenStackLightspeedMCPServerReady + status: "True" + reason: Ready + - type: OpenStackLightspeedReady + status: "True" + reason: Ready +--- +apiVersion: ols.openshift.io/v1alpha1 +kind: OLSConfig +metadata: + name: cluster +spec: + mcpServers: + - name: openshift-lightspeed-mcp + streamableHTTP: + url: http://mcp-server-service:8080/openshift + headers: + OCP_TOKEN: kubernetes + - name: openstack-lightspeed-mcp + streamableHTTP: + url: http://mcp-server-service:8080/openstack + featureGates: + - MCPServer diff --git a/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml b/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml new file mode 100644 index 00000000..7f44ca8e --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: openstack +status: + phase: Active diff --git a/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml b/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml new file mode 100644 index 00000000..595f3b60 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +--- +apiVersion: core.openstack.org/v1beta1 +kind: OpenStackControlPlane +metadata: + name: openstack-control-plane + namespace: openstack diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml new file mode 100644 index 00000000..4f06a7b7 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: v1 + kind: Namespace + name: openstack diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..8eb884aa --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + name: openstackcontrolplanes.core.openstack.org diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml new file mode 100644 index 00000000..f56b52e3 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: core.openstack.org/v1beta1 + kind: OpenStackControlPlane + name: openstack-control-plane + namespace: openstack + - apiVersion: v1 + kind: ConfigMap + name: openstack-config + namespace: openstack + - apiVersion: v1 + kind: Secret + name: openstack-config-secret + namespace: openstack + - apiVersion: v1 + kind: Secret + name: combined-ca-bundle + namespace: openstack diff --git a/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml b/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml new file mode 100644 index 00000000..fbf1395e --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: openstack diff --git a/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml b/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml new file mode 100644 index 00000000..1eaa75cd --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +data: + clouds.yaml: | + clouds: + default: + auth: + auth_url: http://keystone.openstack.svc:5000 + project_name: admin + username: admin + user_domain_name: Default + project_domain_name: Default + region_name: regionOne + interface: public + identity_api_version: 3 +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +type: Opaque +stringData: + secure.yaml: | + clouds: + default: + auth: + password: "test-password-12345" +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +type: Opaque +stringData: + tls-ca-bundle.pem: | + -----BEGIN CERTIFICATE----- + cert value + -----END CERTIFICATE----- +--- +apiVersion: core.openstack.org/v1beta1 +kind: OpenStackControlPlane +metadata: + name: openstack-control-plane + namespace: openstack +spec: + secret: openstack-admin-secret + storageClass: local-storage + openstackclient: + template: + openStackConfigMap: openstack-config + openStackConfigSecret: openstack-config-secret diff --git a/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml b/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..b1a42b6a --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl apply --server-side -f https://raw.githubusercontent.com/openstack-k8s-operators/openstack-operator/main/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml + ignoreFailure: false diff --git a/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml b/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml new file mode 100644 index 00000000..53e5571f --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl patch openstackcontrolplane openstack-control-plane -n openstack --type=merge --subresource=status --patch '{"status":{"tls":{"caBundleSecretName":"combined-ca-bundle"}}}' + ignoreFailure: false diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml index 608fcf6c..44753e71 100644 --- a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml +++ b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml @@ -128,6 +128,9 @@ status: status: "True" reason: Ready message: OpenShift Lightspeed operator is ready. + - type: OpenStackLightspeedMCPServerReady + status: "True" + reason: Ready - type: OpenStackLightspeedReady status: "True" reason: Ready diff --git a/test/kuttl/common/scripts/assert-checksums-match.yaml b/test/kuttl/common/scripts/assert-checksums-match.yaml new file mode 100644 index 00000000..45c8bb52 --- /dev/null +++ b/test/kuttl/common/scripts/assert-checksums-match.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: bash ../../common/scripts/validate-checksums.sh + ignoreFailure: false diff --git a/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml b/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..fc661d7c --- /dev/null +++ b/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: bash ../../common/scripts/validate-openstackcontrolplane-crd.sh + ignoreFailure: false diff --git a/test/kuttl/common/scripts/validate-checksums.sh b/test/kuttl/common/scripts/validate-checksums.sh new file mode 100755 index 00000000..f7628f54 --- /dev/null +++ b/test/kuttl/common/scripts/validate-checksums.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e +echo "Validating checksum annotations match between resources and deployment..." + +# Get checksum from ConfigMap +CM_CHECKSUM=$(kubectl get configmap openstack-config \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from Secret (openstack-config-secret) +SECRET_CHECKSUM=$(kubectl get secret openstack-config-secret \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from CA Bundle Secret +CA_CHECKSUM=$(kubectl get secret combined-ca-bundle \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from Deployment pod template for secrets.yaml +DEP_CHECKSUM=$(kubectl get deployment mcp-server \ + -n openshift-lightspeed \ + -o jsonpath='{.spec.template.metadata.annotations.openstack\.org/checksum}') + + +# Validate all checksums match +FAILED=0 +COMBINED_CHECKSUM="${CM_CHECKSUM:0:10}${SECRET_CHECKSUM:0:10}${CA_CHECKSUM:0:10}" +if [ "$COMBINED_CHECKSUM" = "$DEP_CHECKSUM" ]; then + echo "✓ Combined checksum matches: $COMBINED_CHECKSUM" +else + echo "✗ Combined checksum mismatch!" + echo " Combined checksum: $COMBINED_CHECKSUM" + echo " Deployment checksum: $DEP_CHECKSUM" + FAILED=1 +fi + +if [ $FAILED -eq 1 ]; then + echo "Checksum validation failed!" + exit 1 +fi + +echo "All checksums match successfully!" +exit 0 diff --git a/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh b/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh new file mode 100755 index 00000000..ff2961fd --- /dev/null +++ b/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +for i in {1..5}; do + echo "Attempt $i: Waiting for CRD to be established..." + if oc wait --for=condition=Established crd/openstackcontrolplanes.core.openstack.org --timeout=20s; then + echo "CRD is Established." + exit 0 + fi + echo "Status field not ready yet, retrying in 3s..." + sleep 3 +done + +exit 1 diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..bcf5580f --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/install-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml new file mode 120000 index 00000000..1bbaea32 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml @@ -0,0 +1 @@ +../../common/scripts/check-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml new file mode 120000 index 00000000..a785ea07 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml new file mode 120000 index 00000000..ce5d01a3 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstack-namespace-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml new file mode 120000 index 00000000..fe28f159 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml new file mode 120000 index 00000000..05a13e67 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/patch-openstack-status.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml new file mode 120000 index 00000000..8f270429 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml new file mode 120000 index 00000000..8235a1fd --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml @@ -0,0 +1 @@ +../../common/mock-objects/mock-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml new file mode 120000 index 00000000..07f977a1 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml @@ -0,0 +1 @@ +../../common/mock-objects/assert-mock-objects-created.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..d08d5827 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/create-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml new file mode 120000 index 00000000..8a870474 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-mcp-server-deployed.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml new file mode 120000 index 00000000..5a2e65c9 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..50cb4312 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml new file mode 100644 index 00000000..12ebfd14 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mcp-server-config + namespace: openshift-lightspeed diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml new file mode 100644 index 00000000..691a0b88 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: ols.openshift.io/v1alpha1 +kind: OLSConfig +metadata: + name: cluster +spec: + mcpServers: [] + featureGates: [] diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..6b2075b0 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/cleanup-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml new file mode 120000 index 00000000..410c9278 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml @@ -0,0 +1 @@ +../../common/mock-objects/cleanup-mock-objects.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml new file mode 120000 index 00000000..f2fe34db --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..bcf5580f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/install-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml new file mode 120000 index 00000000..1bbaea32 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml @@ -0,0 +1 @@ +../../common/scripts/check-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml new file mode 120000 index 00000000..a785ea07 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml new file mode 120000 index 00000000..ce5d01a3 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstack-namespace-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml new file mode 120000 index 00000000..fe28f159 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml new file mode 120000 index 00000000..05a13e67 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/patch-openstack-status.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml new file mode 120000 index 00000000..8f270429 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml new file mode 120000 index 00000000..8235a1fd --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml @@ -0,0 +1 @@ +../../common/mock-objects/mock-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml new file mode 120000 index 00000000..07f977a1 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml @@ -0,0 +1 @@ +../../common/mock-objects/assert-mock-objects-created.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..d08d5827 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/create-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml new file mode 120000 index 00000000..8a870474 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-mcp-server-deployed.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml new file mode 120000 index 00000000..6991f16f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml @@ -0,0 +1 @@ +../../common/scripts/assert-checksums-match.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml new file mode 100644 index 00000000..f0dfa24e --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +data: + clouds.yaml: | + clouds: + default: + auth: + auth_url: http://keystone-updated.openstack.svc:5000 + project_name: admin-updated + username: admin + user_domain_name: Default + project_domain_name: Default + region_name: regionTwo + interface: public + identity_api_version: 3 +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +type: Opaque +stringData: + secure.yaml: | + clouds: + default: + auth: + password: "test-password-67890-UPDATED" +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +type: Opaque +stringData: + tls-ca-bundle.pem: | + -----BEGIN CERTIFICATE----- + cert update + -----END CERTIFICATE----- diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml new file mode 100644 index 00000000..b466569d --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed + generation: 2 +spec: + replicas: 1 +status: + observedGeneration: 2 + updatedReplicas: 1 + readyReplicas: 1 + availableReplicas: 1 diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml new file mode 120000 index 00000000..6991f16f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml @@ -0,0 +1 @@ +../../common/scripts/assert-checksums-match.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..6b2075b0 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/cleanup-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml new file mode 120000 index 00000000..5a2e65c9 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml new file mode 120000 index 00000000..410c9278 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml @@ -0,0 +1 @@ +../../common/mock-objects/cleanup-mock-objects.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml new file mode 120000 index 00000000..f2fe34db --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..50cb4312 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml b/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml index e5946314..20a0a3ca 100644 --- a/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml @@ -13,5 +13,7 @@ status: status: "True" - type: OpenShiftLightspeedOperatorReady status: "True" + - type: OpenStackLightspeedMCPServerReady + status: "True" - type: OpenStackLightspeedReady status: "True"