From a18e89e838bf4e527d00da3f115e822e5f6267b2 Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Tue, 14 Jan 2025 16:30:45 +0100 Subject: [PATCH 1/3] Integrate WatcherAPI creation and update on the main Watcher reconcile This patch integrates the creation/update/delete of WatcherAPI in the main Watcher reconcile loop. It: - Adds the relevant WatcherAPI CRD atributes into the Watcher CRD. - Adds the logic to create/update/delete WatcherAPI from the Watcher CR. - Modifty existing envtest functional tests to validate the WatcherAPI creation from the Watcher objects. --- ...watcher.openstack.org_watcherappliers.yaml | 7 -- ....openstack.org_watcherdecisionengines.yaml | 7 -- api/bases/watcher.openstack.org_watchers.yaml | 90 +++++++++++++++++++ api/v1beta1/common_types.go | 36 ++++++-- api/v1beta1/conditions.go | 10 +++ api/v1beta1/watcher_types.go | 3 + api/v1beta1/watcherapi_types.go | 6 ++ api/v1beta1/zz_generated.deepcopy.go | 75 +++++++++++++--- ...watcher.openstack.org_watcherappliers.yaml | 7 -- ....openstack.org_watcherdecisionengines.yaml | 7 -- .../bases/watcher.openstack.org_watchers.yaml | 90 +++++++++++++++++++ controllers/watcher_controller.go | 86 ++++++++++++++++++ tests/functional/base_test.go | 13 ++- tests/functional/watcher_controller_test.go | 76 +++++++++++++++- 14 files changed, 462 insertions(+), 51 deletions(-) diff --git a/api/bases/watcher.openstack.org_watcherappliers.yaml b/api/bases/watcher.openstack.org_watcherappliers.yaml index 4b6e01a1..a44661ec 100644 --- a/api/bases/watcher.openstack.org_watcherappliers.yaml +++ b/api/bases/watcher.openstack.org_watcherappliers.yaml @@ -43,13 +43,6 @@ spec: description: The service specific Container Image URL (will be set to environmental default if empty) type: string - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector to target subset of worker nodes running this component. Setting here overrides - any global NodeSelector settings within the Watcher CR. - type: object replicas: default: 1 description: Replicas of Watcher service to run diff --git a/api/bases/watcher.openstack.org_watcherdecisionengines.yaml b/api/bases/watcher.openstack.org_watcherdecisionengines.yaml index 9e03459f..acfcb9f6 100644 --- a/api/bases/watcher.openstack.org_watcherdecisionengines.yaml +++ b/api/bases/watcher.openstack.org_watcherdecisionengines.yaml @@ -44,13 +44,6 @@ spec: description: The service specific Container Image URL (will be set to environmental default if empty) type: string - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector to target subset of worker nodes running this component. Setting here overrides - any global NodeSelector settings within the Watcher CR. - type: object replicas: default: 1 description: Replicas of Watcher service to run diff --git a/api/bases/watcher.openstack.org_watchers.yaml b/api/bases/watcher.openstack.org_watchers.yaml index ee557748..d22ad36a 100644 --- a/api/bases/watcher.openstack.org_watchers.yaml +++ b/api/bases/watcher.openstack.org_watchers.yaml @@ -42,6 +42,83 @@ spec: apiContainerImageURL: description: APIContainerImageURL type: string + apiServiceTemplate: + default: + replicas: 1 + description: APIServiceTemplate - define the watcher-api service + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector to target subset of worker nodes running this component. Setting here overrides + any global NodeSelector settings within the Watcher CR. + type: object + replicas: + default: 1 + description: Replicas of Watcher service to run + format: int32 + maximum: 32 + minimum: 0 + type: integer + resources: + description: |- + Resources - Compute Resources required by this service (Limits/Requests). + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object applierContainerImageURL: description: ApplierContainerImageURL type: string @@ -63,6 +140,13 @@ spec: description: MemcachedInstance is the name of the Memcached CR that all watcher service will use. type: string + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector to target subset of worker nodes running this component. Setting here overrides + any global NodeSelector settings within the Watcher CR. + type: object passwordSelectors: default: service: WatcherPassword @@ -97,6 +181,7 @@ spec: type: string required: - apiContainerImageURL + - apiServiceTemplate - applierContainerImageURL - databaseInstance - decisionengineContainerImageURL @@ -105,6 +190,11 @@ spec: status: description: WatcherStatus defines the observed state of Watcher properties: + apiServiceReadyCount: + description: APIServiceReadyCount defines the number or replicas ready + from watcher-api + format: int32 + type: integer conditions: description: Conditions items: diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index f7c9cca9..26cf138e 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -50,6 +50,11 @@ type WatcherCommon struct { // +kubebuilder:default=false // PreserveJobs - do not delete jobs after they finished e.g. to check logs PreserveJobs bool `json:"preserveJobs"` + + // +kubebuilder:validation:Optional + // NodeSelector to target subset of worker nodes running this component. Setting here overrides + // any global NodeSelector settings within the Watcher CR. + NodeSelector *map[string]string `json:"nodeSelector,omitempty"` } // WatcherTemplate defines the fields used in the top level CR @@ -78,6 +83,11 @@ type WatcherTemplate struct { // +kubebuilder:default=watcher // DatabaseAccount - MariaDBAccount CR name used for watcher DB, defaults to watcher DatabaseAccount string `json:"databaseAccount"` + + // +kubebuilder:validation:Required + // +kubebuilder:default={replicas:1} + // APIServiceTemplate - define the watcher-api service + APIServiceTemplate WatcherAPITemplate `json:"apiServiceTemplate"` } // PasswordSelector to identify the DB and AdminUser password from the Secret @@ -94,11 +104,6 @@ type WatcherSubCrsCommon struct { // The service specific Container Image URL (will be set to environmental default if empty) ContainerImage string `json:"containerImage"` - // +kubebuilder:validation:Optional - // NodeSelector to target subset of worker nodes running this component. Setting here overrides - // any global NodeSelector settings within the Watcher CR. - NodeSelector *map[string]string `json:"nodeSelector,omitempty"` - // +kubebuilder:validation:Optional // +kubebuilder:default=1 // +kubebuilder:validation:Maximum=32 @@ -117,6 +122,27 @@ type WatcherSubCrsCommon struct { ServiceAccount string `json:"serviceAccount"` } +// WatcherSubCrsTemplate define de common part of the input parameters specified by the user to +// create a 2nd CR via higher level CRDs. +type WatcherSubCrsTemplate struct { + // +kubebuilder:validation:Optional + // +kubebuilder:default=1 + // +kubebuilder:validation:Maximum=32 + // +kubebuilder:validation:Minimum=0 + // Replicas of Watcher service to run + Replicas *int32 `json:"replicas"` + + // +kubebuilder:validation:Optional + // Resources - Compute Resources required by this service (Limits/Requests). + // https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + + // +kubebuilder:validation:Optional + // NodeSelector to target subset of worker nodes running this component. Setting here overrides + // any global NodeSelector settings within the Watcher CR. + NodeSelector *map[string]string `json:"nodeSelector,omitempty"` +} + type WatcherImages struct { // +kubebuilder:validation:Required // APIContainerImageURL diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index 20b1e982..3beed3f0 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -5,6 +5,8 @@ import "github.com/openstack-k8s-operators/lib-common/modules/common/condition" const ( // WatcherRabbitMQTransportURLReadyCondition - WatcherRabbitMQTransportURLReadyCondition condition.Type = "WatcherRabbitMQTransportURLReady" + // WatcherAPIReadyCondition - + WatcherAPIReadyCondition condition.Type = "WatcherAPIReady" ) const ( @@ -14,4 +16,12 @@ const ( WatcherRabbitMQTransportURLReadyMessage = "WatcherRabbitMQTransportURL successfully created" // WatcherRabbitMQTransportURLReadyErrorMessage - WatcherRabbitMQTransportURLReadyErrorMessage = "WatcherRabbitMQTransportURL error occured %s" + // WatcherAPIReadyInitMessage - + WatcherAPIReadyInitMessage = "WatcherAPI creation not started" + // WatcherAPIReadyRunningMessage - + WatcherAPIReadyRunningMessage = "WatcherAPI creation in progress" + // WatcherAPIReadyMessage - + WatcherAPIReadyMessage = "WatcherAPI successfully created" + // WatcherAPIReadyErrorMessage - + WatcherAPIReadyErrorMessage = "WatcherAPI error occured %s" ) diff --git a/api/v1beta1/watcher_types.go b/api/v1beta1/watcher_types.go index ae36b54e..a50cc23f 100644 --- a/api/v1beta1/watcher_types.go +++ b/api/v1beta1/watcher_types.go @@ -52,6 +52,9 @@ type WatcherStatus struct { // then the controller has not processed the latest changes injected by // the opentack-operator in the top-level CR (e.g. the ContainerImage) ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // APIServiceReadyCount defines the number or replicas ready from watcher-api + APIServiceReadyCount int32 `json:"apiServiceReadyCount,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/watcherapi_types.go b/api/v1beta1/watcherapi_types.go index 2b6b9da3..c7e08173 100644 --- a/api/v1beta1/watcherapi_types.go +++ b/api/v1beta1/watcherapi_types.go @@ -53,6 +53,12 @@ type WatcherAPIStatus struct { Hash map[string]string `json:"hash,omitempty"` } +// WatcherAPITemplate defines the input parameters specified by the user to +// create a WatcherAPI via higher level CRDs. +type WatcherAPITemplate struct { + WatcherSubCrsTemplate `json:",inline"` +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 768464b0..51e3612b 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -45,7 +45,7 @@ func (in *Watcher) DeepCopyInto(out *Watcher) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -129,7 +129,7 @@ func (in *WatcherAPIList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatcherAPISpec) DeepCopyInto(out *WatcherAPISpec) { *out = *in - out.WatcherCommon = in.WatcherCommon + in.WatcherCommon.DeepCopyInto(&out.WatcherCommon) in.WatcherSubCrsCommon.DeepCopyInto(&out.WatcherSubCrsCommon) } @@ -172,6 +172,22 @@ func (in *WatcherAPIStatus) DeepCopy() *WatcherAPIStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WatcherAPITemplate) DeepCopyInto(out *WatcherAPITemplate) { + *out = *in + in.WatcherSubCrsTemplate.DeepCopyInto(&out.WatcherSubCrsTemplate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherAPITemplate. +func (in *WatcherAPITemplate) DeepCopy() *WatcherAPITemplate { + if in == nil { + return nil + } + out := new(WatcherAPITemplate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatcherApplier) DeepCopyInto(out *WatcherApplier) { *out = *in @@ -266,6 +282,17 @@ func (in *WatcherApplierStatus) DeepCopy() *WatcherApplierStatus { func (in *WatcherCommon) DeepCopyInto(out *WatcherCommon) { *out = *in out.PasswordSelectors = in.PasswordSelectors + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherCommon. @@ -433,7 +460,7 @@ func (in *WatcherList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatcherSpec) DeepCopyInto(out *WatcherSpec) { *out = *in - out.WatcherTemplate = in.WatcherTemplate + in.WatcherTemplate.DeepCopyInto(&out.WatcherTemplate) out.WatcherImages = in.WatcherImages } @@ -479,6 +506,33 @@ func (in *WatcherStatus) DeepCopy() *WatcherStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatcherSubCrsCommon) DeepCopyInto(out *WatcherSubCrsCommon) { *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + in.Resources.DeepCopyInto(&out.Resources) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherSubCrsCommon. +func (in *WatcherSubCrsCommon) DeepCopy() *WatcherSubCrsCommon { + if in == nil { + return nil + } + out := new(WatcherSubCrsCommon) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WatcherSubCrsTemplate) DeepCopyInto(out *WatcherSubCrsTemplate) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + in.Resources.DeepCopyInto(&out.Resources) if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = new(map[string]string) @@ -490,20 +544,14 @@ func (in *WatcherSubCrsCommon) DeepCopyInto(out *WatcherSubCrsCommon) { } } } - if in.Replicas != nil { - in, out := &in.Replicas, &out.Replicas - *out = new(int32) - **out = **in - } - in.Resources.DeepCopyInto(&out.Resources) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherSubCrsCommon. -func (in *WatcherSubCrsCommon) DeepCopy() *WatcherSubCrsCommon { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherSubCrsTemplate. +func (in *WatcherSubCrsTemplate) DeepCopy() *WatcherSubCrsTemplate { if in == nil { return nil } - out := new(WatcherSubCrsCommon) + out := new(WatcherSubCrsTemplate) in.DeepCopyInto(out) return out } @@ -511,7 +559,8 @@ func (in *WatcherSubCrsCommon) DeepCopy() *WatcherSubCrsCommon { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatcherTemplate) DeepCopyInto(out *WatcherTemplate) { *out = *in - out.WatcherCommon = in.WatcherCommon + in.WatcherCommon.DeepCopyInto(&out.WatcherCommon) + in.APIServiceTemplate.DeepCopyInto(&out.APIServiceTemplate) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatcherTemplate. diff --git a/config/crd/bases/watcher.openstack.org_watcherappliers.yaml b/config/crd/bases/watcher.openstack.org_watcherappliers.yaml index 4b6e01a1..a44661ec 100644 --- a/config/crd/bases/watcher.openstack.org_watcherappliers.yaml +++ b/config/crd/bases/watcher.openstack.org_watcherappliers.yaml @@ -43,13 +43,6 @@ spec: description: The service specific Container Image URL (will be set to environmental default if empty) type: string - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector to target subset of worker nodes running this component. Setting here overrides - any global NodeSelector settings within the Watcher CR. - type: object replicas: default: 1 description: Replicas of Watcher service to run diff --git a/config/crd/bases/watcher.openstack.org_watcherdecisionengines.yaml b/config/crd/bases/watcher.openstack.org_watcherdecisionengines.yaml index 9e03459f..acfcb9f6 100644 --- a/config/crd/bases/watcher.openstack.org_watcherdecisionengines.yaml +++ b/config/crd/bases/watcher.openstack.org_watcherdecisionengines.yaml @@ -44,13 +44,6 @@ spec: description: The service specific Container Image URL (will be set to environmental default if empty) type: string - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector to target subset of worker nodes running this component. Setting here overrides - any global NodeSelector settings within the Watcher CR. - type: object replicas: default: 1 description: Replicas of Watcher service to run diff --git a/config/crd/bases/watcher.openstack.org_watchers.yaml b/config/crd/bases/watcher.openstack.org_watchers.yaml index ee557748..d22ad36a 100644 --- a/config/crd/bases/watcher.openstack.org_watchers.yaml +++ b/config/crd/bases/watcher.openstack.org_watchers.yaml @@ -42,6 +42,83 @@ spec: apiContainerImageURL: description: APIContainerImageURL type: string + apiServiceTemplate: + default: + replicas: 1 + description: APIServiceTemplate - define the watcher-api service + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector to target subset of worker nodes running this component. Setting here overrides + any global NodeSelector settings within the Watcher CR. + type: object + replicas: + default: 1 + description: Replicas of Watcher service to run + format: int32 + maximum: 32 + minimum: 0 + type: integer + resources: + description: |- + Resources - Compute Resources required by this service (Limits/Requests). + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object applierContainerImageURL: description: ApplierContainerImageURL type: string @@ -63,6 +140,13 @@ spec: description: MemcachedInstance is the name of the Memcached CR that all watcher service will use. type: string + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector to target subset of worker nodes running this component. Setting here overrides + any global NodeSelector settings within the Watcher CR. + type: object passwordSelectors: default: service: WatcherPassword @@ -97,6 +181,7 @@ spec: type: string required: - apiContainerImageURL + - apiServiceTemplate - applierContainerImageURL - databaseInstance - decisionengineContainerImageURL @@ -105,6 +190,11 @@ spec: status: description: WatcherStatus defines the observed state of Watcher properties: + apiServiceReadyCount: + description: APIServiceReadyCount defines the number or replicas ready + from watcher-api + format: int32 + type: integer conditions: description: Conditions items: diff --git a/controllers/watcher_controller.go b/controllers/watcher_controller.go index 4a40d498..50f1e448 100644 --- a/controllers/watcher_controller.go +++ b/controllers/watcher_controller.go @@ -289,6 +289,15 @@ func (r *WatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re return ctrlResult, nil } + // Create Watcher API + _, _, err = r.ensureAPI(ctx, instance) + + if err != nil { + return ctrl.Result{}, err + } + + // End of Watcher API creation + // // remove finalizers from unused MariaDBAccount records // this assumes all database-depedendent deployments are up and @@ -374,6 +383,10 @@ func (r *WatcherReconciler) initConditions(instance *watcherv1beta1.Watcher) err condition.DBSyncReadyCondition, condition.InitReason, condition.DBSyncReadyInitMessage), + condition.UnknownCondition( + watcherv1beta1.WatcherAPIReadyCondition, + condition.InitReason, + watcherv1beta1.WatcherAPIReadyInitMessage), ) instance.Status.Conditions.Init(&cl) @@ -730,6 +743,79 @@ func (r *WatcherReconciler) createSubLevelSecret( return secretName, err } +func (r *WatcherReconciler) ensureAPI( + ctx context.Context, + instance *watcherv1beta1.Watcher, +) (*watcherv1beta1.WatcherAPI, controllerutil.OperationResult, error) { + Log := r.GetLogger(ctx) + Log.Info(fmt.Sprintf("Creating WatcherAPI '%s'", instance.Name)) + + watcherAPISpec := watcherv1beta1.WatcherAPISpec{ + Secret: instance.Name, + WatcherCommon: watcherv1beta1.WatcherCommon{ + ServiceUser: instance.Spec.ServiceUser, + PasswordSelectors: instance.Spec.PasswordSelectors, + MemcachedInstance: instance.Spec.MemcachedInstance, + NodeSelector: instance.Spec.APIServiceTemplate.NodeSelector, + PreserveJobs: instance.Spec.PreserveJobs, + }, + WatcherSubCrsCommon: watcherv1beta1.WatcherSubCrsCommon{ + ContainerImage: instance.Spec.APIContainerImageURL, + Replicas: instance.Spec.APIServiceTemplate.Replicas, + Resources: instance.Spec.APIServiceTemplate.Resources, + ServiceAccount: "watcher-" + instance.Name, + }, + } + + // If NodeSelector is not specified in Watcher APIServiceTemplate, the current + // API instance inherits the value from the top-level Watcher CR. + if watcherAPISpec.NodeSelector == nil { + watcherAPISpec.NodeSelector = instance.Spec.NodeSelector + } + + apiDeployment := &watcherv1beta1.WatcherAPI{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-api", instance.Name), + Namespace: instance.Namespace, + }, + } + + op, err := controllerutil.CreateOrPatch(ctx, r.Client, apiDeployment, func() error { + apiDeployment.Spec = watcherAPISpec + err := controllerutil.SetControllerReference(instance, apiDeployment, r.Scheme) + if err != nil { + return err + } + return nil + }) + + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + watcherv1beta1.WatcherAPIReadyCondition, + condition.ErrorReason, + condition.SeverityError, + watcherv1beta1.WatcherAPIReadyErrorMessage, + err.Error())) + return nil, op, err + } + if op != controllerutil.OperationResultNone { + Log.Info(fmt.Sprintf("WatcherAPI %s , WatcherPI.Name %s.", string(op), apiDeployment.Name)) + } + + if apiDeployment.Generation == apiDeployment.Status.ObservedGeneration { + c := apiDeployment.Status.Conditions.Mirror(watcherv1beta1.WatcherAPIReadyCondition) + // NOTE(gibi): it can be nil if the WatcherAPI CR is created but no + // reconciliation is run on it to initialize the ReadyCondition yet. + if c != nil { + instance.Status.Conditions.Set(c) + } + instance.Status.APIServiceReadyCount = apiDeployment.Status.ReadyCount + } + + return apiDeployment, op, nil + +} + func (r *WatcherReconciler) reconcileDelete(ctx context.Context, instance *watcherv1beta1.Watcher, helper *helper.Helper) (ctrl.Result, error) { Log := r.GetLogger(ctx) Log.Info(fmt.Sprintf("Reconcile Service '%s' delete started", instance.Name)) diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index ef6a4317..459199f0 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -39,10 +39,15 @@ func GetDefaultWatcherSpec() map[string]interface{} { // Second Watcher Spec to test proper parameters substitution func GetNonDefaultWatcherSpec() map[string]interface{} { return map[string]interface{}{ - "secret": SecretName, - "preserveJobs": true, - "databaseInstance": "fakeopenstack", - "serviceUser": "fakeuser", + "apiContainerImageURL": "fake-API-Container-URL", + "secret": SecretName, + "preserveJobs": true, + "databaseInstance": "fakeopenstack", + "serviceUser": "fakeuser", + "apiServiceTemplate": map[string]interface{}{ + "replicas": 2, + "nodeSelector": map[string]string{"foo": "bar"}, + }, } } diff --git a/tests/functional/watcher_controller_test.go b/tests/functional/watcher_controller_test.go index 2b28ac85..4af5edae 100644 --- a/tests/functional/watcher_controller_test.go +++ b/tests/functional/watcher_controller_test.go @@ -8,6 +8,7 @@ import ( //revive:disable-next-line:dot-imports "os" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" keystonev1beta1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" @@ -16,6 +17,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -180,10 +182,17 @@ var _ = Describe("Watcher controller", func() { }) - When("Watcher DB and RabbitMQ are created", func() { + When("Watcher is created with default Spec", func() { BeforeEach(func() { DeferCleanup(th.DeleteInstance, CreateWatcher(watcherTest.Instance, GetDefaultWatcherSpec())) DeferCleanup(k8sClient.Delete, ctx, CreateWatcherMessageBusSecret(watcherTest.Instance.Namespace, "rabbitmq-secret")) + memcachedSpec := memcachedv1.MemcachedSpec{ + MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ + Replicas: ptr.To(int32(1)), + }, + } + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(watcherTest.Watcher.Namespace, MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(watcherTest.MemcachedNamespace) DeferCleanup( mariadb.DeleteDBService, mariadb.CreateDBService( @@ -194,6 +203,7 @@ var _ = Describe("Watcher controller", func() { }, ), ) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(watcherTest.WatcherAPI.Namespace)) }) It("Should set DBReady Condition Status when DB is Created", func() { @@ -240,6 +250,9 @@ var _ = Describe("Watcher controller", func() { th.SimulateJobSuccess(watcherTest.WatcherDBSync) // We validate the full Watcher CR readiness status here // DB Ready + + // Simulate WatcherAPI deployment + th.SimulateDeploymentReplicaReady(watcherTest.WatcherAPIDeployment) th.ExpectCondition( watcherTest.Instance, ConditionGetterFunc(WatcherConditionGetter), @@ -291,6 +304,14 @@ var _ = Describe("Watcher controller", func() { corev1.ConditionTrue, ) + // Get WatcherAPI Ready condition + th.ExpectCondition( + watcherTest.Instance, + ConditionGetterFunc(WatcherConditionGetter), + watcherv1beta1.WatcherAPIReadyCondition, + corev1.ConditionTrue, + ) + // Global status Ready th.ExpectCondition( watcherTest.Instance, @@ -319,6 +340,22 @@ var _ = Describe("Watcher controller", func() { Expect(createdSecret.Data["WatcherPassword"]).To(Equal([]byte("password"))) Expect(createdSecret.Data["transport_url"]).To(Equal([]byte("rabbit://rabbitmq-secret/fake"))) + // Check WatcherAPI is created + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) + //Expect(WatcherAPI.Spec.Replicas).To(Equal(int(1))) + Expect(WatcherAPI.Spec.ContainerImage).To(Equal(watcherv1beta1.WatcherAPIContainerImage)) + Expect(WatcherAPI.Spec.Secret).To(Equal("watcher")) + Expect(WatcherAPI.Spec.ServiceAccount).To(Equal("watcher-watcher")) + Expect(int(*WatcherAPI.Spec.Replicas)).To(Equal(1)) + Expect(WatcherAPI.Spec.NodeSelector).To(BeNil()) + + // Assert that the watcher deployment is created + deployment := th.GetDeployment(watcherTest.WatcherAPIDeployment) + Expect(deployment.Spec.Template.Spec.ServiceAccountName).To(Equal("watcher-watcher")) + Expect(int(*deployment.Spec.Replicas)).To(Equal(1)) + Expect(deployment.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(2)) + Expect(deployment.Spec.Selector.MatchLabels).To(Equal(map[string]string{"service": "watcher-api"})) }) It("Should fail to register watcher service to keystone when has not the expected secret", func() { @@ -498,6 +535,13 @@ var _ = Describe("Watcher controller", func() { BeforeEach(func() { DeferCleanup(th.DeleteInstance, CreateWatcher(watcherTest.Instance, GetNonDefaultWatcherSpec())) DeferCleanup(k8sClient.Delete, ctx, CreateWatcherMessageBusSecret(watcherTest.Instance.Namespace, "rabbitmq-secret")) + memcachedSpec := memcachedv1.MemcachedSpec{ + MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ + Replicas: ptr.To(int32(1)), + }, + } + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(watcherTest.Watcher.Namespace, MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(watcherTest.MemcachedNamespace) DeferCleanup( mariadb.DeleteDBService, mariadb.CreateDBService( @@ -508,6 +552,7 @@ var _ = Describe("Watcher controller", func() { }, ), ) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(watcherTest.WatcherAPI.Namespace)) }) It("should have the Spec fields with the expected values", func() { @@ -538,6 +583,10 @@ var _ = Describe("Watcher controller", func() { // Simulate dbsync success th.SimulateJobSuccess(watcherTest.WatcherDBSync) + + // Simulate WatcherAPI deployment + th.SimulateDeploymentReplicaReady(watcherTest.WatcherAPIDeployment) + // We validate the full Watcher CR readiness status here // DB Ready th.ExpectCondition( @@ -591,6 +640,14 @@ var _ = Describe("Watcher controller", func() { corev1.ConditionTrue, ) + // Get WatcherAPI Ready condition + th.ExpectCondition( + watcherTest.Instance, + ConditionGetterFunc(WatcherConditionGetter), + watcherv1beta1.WatcherAPIReadyCondition, + corev1.ConditionTrue, + ) + // Global status Ready th.ExpectCondition( watcherTest.Instance, @@ -623,6 +680,23 @@ var _ = Describe("Watcher controller", func() { Watcher := GetWatcher(watcherTest.Instance) Expect(Watcher.Status.Hash[watcherv1beta1.DbSyncHash]).ShouldNot(BeNil()) + // Check WatcherAPI is created with non-default values + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) + //Expect(WatcherAPI.Spec.Replicas).To(Equal(int(1))) + Expect(WatcherAPI.Spec.ContainerImage).To(Equal("fake-API-Container-URL")) + Expect(WatcherAPI.Spec.Secret).To(Equal("watcher")) + Expect(WatcherAPI.Spec.ServiceAccount).To(Equal("watcher-watcher")) + Expect(int(*WatcherAPI.Spec.Replicas)).To(Equal(2)) + Expect(*WatcherAPI.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + + // Assert that the watcher deployment is created + deployment := th.GetDeployment(watcherTest.WatcherAPIDeployment) + Expect(deployment.Spec.Template.Spec.ServiceAccountName).To(Equal("watcher-watcher")) + Expect(int(*deployment.Spec.Replicas)).To(Equal(2)) + Expect(deployment.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(2)) + Expect(deployment.Spec.Selector.MatchLabels).To(Equal(map[string]string{"service": "watcher-api"})) + }) }) From 0b3cb9c1be6542c2fc842fe39ea35406c71bf1be Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Tue, 14 Jan 2025 18:12:53 +0100 Subject: [PATCH 2/3] Add kuttl tests for watcherapi creation from watcher top CR After integrating watcherapi into watcher we need to validate watcherapi and the nested resources are properly created as part of the Watcher creation. Also, this patch is removing the previous kuttl scenario which created watcherapi directly without watcher top level CR. --- .../default/common/deploy-with-defaults.yaml | 3 + .../watcher-api/00-cleanup-watcher.yaml | 1 - .../watcher-api/01-cleanup-watcherapi.yaml | 9 -- .../watcher-api/02-deploy-with-defaults.yaml | 1 - .../default/watcher-api/03-assert.yaml | 67 --------------- .../watcher-api/03-deploy-watcher-api.yaml | 9 -- .../watcher-api/04-cleanup-watcher.yaml | 1 - .../default/watcher-api/05-assert.yaml | 9 -- .../watcher-api/05-cleanup-watcherapi.yaml | 1 - .../default/watcher-api/05-errors.yaml | 15 ---- .../default/watcher/01-assert.yaml | 83 ++++++++++++++++++- .../default/watcher/04-assert.yaml | 64 ++++++++++++++ .../04-deploy-with-precreated-account.yaml | 5 ++ .../default/watcher/05-errors.yaml | 18 ++++ 14 files changed, 170 insertions(+), 116 deletions(-) delete mode 120000 tests/kuttl/test-suites/default/watcher-api/00-cleanup-watcher.yaml delete mode 100644 tests/kuttl/test-suites/default/watcher-api/01-cleanup-watcherapi.yaml delete mode 120000 tests/kuttl/test-suites/default/watcher-api/02-deploy-with-defaults.yaml delete mode 100644 tests/kuttl/test-suites/default/watcher-api/03-assert.yaml delete mode 100644 tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml delete mode 120000 tests/kuttl/test-suites/default/watcher-api/04-cleanup-watcher.yaml delete mode 100644 tests/kuttl/test-suites/default/watcher-api/05-assert.yaml delete mode 120000 tests/kuttl/test-suites/default/watcher-api/05-cleanup-watcherapi.yaml delete mode 100644 tests/kuttl/test-suites/default/watcher-api/05-errors.yaml diff --git a/tests/kuttl/test-suites/default/common/deploy-with-defaults.yaml b/tests/kuttl/test-suites/default/common/deploy-with-defaults.yaml index 3b969bae..b008f3ff 100644 --- a/tests/kuttl/test-suites/default/common/deploy-with-defaults.yaml +++ b/tests/kuttl/test-suites/default/common/deploy-with-defaults.yaml @@ -5,3 +5,6 @@ metadata: namespace: watcher-kuttl-default spec: databaseInstance: "openstack" + apiContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-api:current-podified" + decisionengineContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-decision-engine:current-podified" + applierContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-applier:current-podified" diff --git a/tests/kuttl/test-suites/default/watcher-api/00-cleanup-watcher.yaml b/tests/kuttl/test-suites/default/watcher-api/00-cleanup-watcher.yaml deleted file mode 120000 index 92ed6e0b..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/00-cleanup-watcher.yaml +++ /dev/null @@ -1 +0,0 @@ -../common/cleanup-watcher.yaml \ No newline at end of file diff --git a/tests/kuttl/test-suites/default/watcher-api/01-cleanup-watcherapi.yaml b/tests/kuttl/test-suites/default/watcher-api/01-cleanup-watcherapi.yaml deleted file mode 100644 index f78c95e3..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/01-cleanup-watcherapi.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -delete: -- apiVersion: watcher.openstack.org/v1beta1 - kind: WatcherAPI - name: watcherapi-kuttl -- apiVersion: v1 - kind: Secret - name: watcherapi-secret diff --git a/tests/kuttl/test-suites/default/watcher-api/02-deploy-with-defaults.yaml b/tests/kuttl/test-suites/default/watcher-api/02-deploy-with-defaults.yaml deleted file mode 120000 index ecf8d373..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/02-deploy-with-defaults.yaml +++ /dev/null @@ -1 +0,0 @@ -../common/deploy-with-defaults.yaml \ No newline at end of file diff --git a/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml b/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml deleted file mode 100644 index 4bda4a60..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml +++ /dev/null @@ -1,67 +0,0 @@ -apiVersion: watcher.openstack.org/v1beta1 -kind: WatcherAPI -metadata: - finalizers: - - openstack.org/watcherapi - name: watcherapi-kuttl -spec: - passwordSelectors: - service: WatcherPassword - secret: watcher-kuttl -status: - conditions: - - message: Setup complete - reason: Ready - status: "True" - type: Ready - - message: Deployment completed - reason: Ready - status: "True" - type: DeploymentReady - - message: Input data complete - reason: Ready - status: "True" - type: InputReady - - message: " Memcached instance has been provisioned" - reason: Ready - status: "True" - type: MemcachedReady - - message: Service config create completed - reason: Ready - status: "True" - type: ServiceConfigReady ---- -apiVersion: v1 -kind: Secret -metadata: - name: watcher-kuttl -type: Opaque ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: watcherapi-kuttl-api - labels: - service: watcher-api -spec: - replicas: 1 - template: - spec: - containers: - - name: watcherapi-kuttl-log - - name: watcher-api -status: - readyReplicas: 1 - replicas: 1 ---- -apiVersion: v1 -kind: Pod -metadata: - labels: - service: watcher-api -spec: - containers: - - name: watcherapi-kuttl-log - - name: watcher-api -status: - phase: Running diff --git a/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml b/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml deleted file mode 100644 index fcd45859..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: watcher.openstack.org/v1beta1 -kind: WatcherAPI -metadata: - name: watcherapi-kuttl -spec: - secret: watcher-kuttl - memcachedInstance: "memcached" - containerImage: "quay.io/podified-master-centos9/openstack-watcher-api:current-podified" - serviceAccount: watcher-watcher-kuttl diff --git a/tests/kuttl/test-suites/default/watcher-api/04-cleanup-watcher.yaml b/tests/kuttl/test-suites/default/watcher-api/04-cleanup-watcher.yaml deleted file mode 120000 index 92ed6e0b..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/04-cleanup-watcher.yaml +++ /dev/null @@ -1 +0,0 @@ -../common/cleanup-watcher.yaml \ No newline at end of file diff --git a/tests/kuttl/test-suites/default/watcher-api/05-assert.yaml b/tests/kuttl/test-suites/default/watcher-api/05-assert.yaml deleted file mode 100644 index 6de52af3..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/05-assert.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -namespaced: true -commands: - - script: | - set -ex - oc get mariadbaccount -n ${NAMESPACE} --no-headers=true | [ $(grep -c ^watcher) == 0 ] - oc get mariadbdatabase -n ${NAMESPACE} --no-headers=true | [ $(grep -c ^watcher) == 0 ] - oc get secret -n ${NAMESPACE} --no-headers=true | [ $(grep -c ^watcher) == 0 ] diff --git a/tests/kuttl/test-suites/default/watcher-api/05-cleanup-watcherapi.yaml b/tests/kuttl/test-suites/default/watcher-api/05-cleanup-watcherapi.yaml deleted file mode 120000 index 40ff4783..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/05-cleanup-watcherapi.yaml +++ /dev/null @@ -1 +0,0 @@ -01-cleanup-watcherapi.yaml \ No newline at end of file diff --git a/tests/kuttl/test-suites/default/watcher-api/05-errors.yaml b/tests/kuttl/test-suites/default/watcher-api/05-errors.yaml deleted file mode 100644 index 672ed5d8..00000000 --- a/tests/kuttl/test-suites/default/watcher-api/05-errors.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: watcher.openstack.org/v1beta1 -kind: WatcherAPI -metadata: - name: watcherapi-kuttl ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: watcherapi-kuttl-api ---- -apiVersion: v1 -kind: Pod -metadata: - labels: - service: watcher-api diff --git a/tests/kuttl/test-suites/default/watcher/01-assert.yaml b/tests/kuttl/test-suites/default/watcher/01-assert.yaml index 4a3eeedb..3dc9302d 100644 --- a/tests/kuttl/test-suites/default/watcher/01-assert.yaml +++ b/tests/kuttl/test-suites/default/watcher/01-assert.yaml @@ -6,9 +6,9 @@ metadata: name: watcher-kuttl namespace: watcher-kuttl-default spec: - apiContainerImageURL: "quay.io/podified-antelope-centos9/openstack-watcher-api:current-podified" - decisionengineContainerImageURL: "quay.io/podified-antelope-centos9/openstack-watcher-decision-engine:current-podified" - applierContainerImageURL: "quay.io/podified-antelope-centos9/openstack-watcher-applier:current-podified" + apiContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-api:current-podified" + decisionengineContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-decision-engine:current-podified" + applierContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-applier:current-podified" databaseAccount: watcher databaseInstance: openstack passwordSelectors: @@ -17,7 +17,11 @@ spec: rabbitMqClusterName: rabbitmq secret: osp-secret serviceUser: watcher + apiServiceTemplate: + replicas: 1 + resources: {} status: + apiServiceReadyCount: 1 conditions: - message: Setup complete reason: Ready @@ -59,6 +63,10 @@ status: reason: Ready status: "True" type: ServiceConfigReady + - message: Setup complete + reason: Ready + status: "True" + type: WatcherAPIReady - message: WatcherRabbitMQTransportURL successfully created reason: Ready status: "True" @@ -173,6 +181,75 @@ kind: Secret metadata: name: watcher-kuttl-config-data --- +apiVersion: watcher.openstack.org/v1beta1 +kind: WatcherAPI +metadata: + finalizers: + - openstack.org/watcherapi + name: watcher-kuttl-api +spec: + containerImage: quay.io/podified-master-centos9/openstack-watcher-api:current-podified + memcachedInstance: memcached + passwordSelectors: + service: WatcherPassword + preserveJobs: false + replicas: 1 + resources: {} + secret: watcher-kuttl + serviceAccount: watcher-watcher-kuttl + serviceUser: watcher +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: " Memcached instance has been provisioned" + reason: Ready + status: "True" + type: MemcachedReady + - message: Service config create completed + reason: Ready + status: "True" + type: ServiceConfigReady +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: watcher-kuttl-api-api + labels: + service: watcher-api +spec: + replicas: 1 + template: + spec: + containers: + - name: watcher-kuttl-api-log + - name: watcher-api +status: + readyReplicas: 1 + replicas: 1 +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + service: watcher-api +spec: + containers: + - name: watcher-kuttl-api-log + - name: watcher-api +status: + phase: Running +--- apiVersion: kuttl.dev/v1beta1 kind: TestAssert namespaced: true diff --git a/tests/kuttl/test-suites/default/watcher/04-assert.yaml b/tests/kuttl/test-suites/default/watcher/04-assert.yaml index d92296a0..fcbe128c 100644 --- a/tests/kuttl/test-suites/default/watcher/04-assert.yaml +++ b/tests/kuttl/test-suites/default/watcher/04-assert.yaml @@ -11,7 +11,11 @@ spec: passwordSelectors: service: WatcherPassword secret: osp-secret + apiServiceTemplate: + replicas: 2 + resources: {} status: + apiServiceReadyCount: 2 conditions: - message: Setup complete reason: Ready @@ -53,6 +57,10 @@ status: reason: Ready status: "True" type: ServiceConfigReady + - message: Setup complete + reason: Ready + status: "True" + type: WatcherAPIReady - message: WatcherRabbitMQTransportURL successfully created reason: Ready status: "True" @@ -154,3 +162,59 @@ kind: Secret metadata: name: watcher-kuttl-config-data --- +apiVersion: watcher.openstack.org/v1beta1 +kind: WatcherAPI +metadata: + finalizers: + - openstack.org/watcherapi + name: watcher-kuttl-api +spec: + containerImage: quay.io/podified-master-centos9/openstack-watcher-api:current-podified + memcachedInstance: memcached + passwordSelectors: + service: WatcherPassword + preserveJobs: false + replicas: 2 + resources: {} + secret: watcher-kuttl + serviceAccount: watcher-watcher-kuttl + serviceUser: watcher +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: " Memcached instance has been provisioned" + reason: Ready + status: "True" + type: MemcachedReady + - message: Service config create completed + reason: Ready + status: "True" + type: ServiceConfigReady +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: watcher-kuttl-api-api + labels: + service: watcher-api +spec: + replicas: 2 + template: + spec: + containers: + - name: watcher-kuttl-api-log + - name: watcher-api +status: + readyReplicas: 2 + replicas: 2 diff --git a/tests/kuttl/test-suites/default/watcher/04-deploy-with-precreated-account.yaml b/tests/kuttl/test-suites/default/watcher/04-deploy-with-precreated-account.yaml index 6cda9cf9..87b740ec 100644 --- a/tests/kuttl/test-suites/default/watcher/04-deploy-with-precreated-account.yaml +++ b/tests/kuttl/test-suites/default/watcher/04-deploy-with-precreated-account.yaml @@ -6,3 +6,8 @@ metadata: spec: databaseInstance: "openstack" databaseAccount: watcher-precreated + apiContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-api:current-podified" + decisionengineContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-decision-engine:current-podified" + applierContainerImageURL: "quay.io/podified-master-centos9/openstack-watcher-applier:current-podified" + apiServiceTemplate: + replicas: 2 diff --git a/tests/kuttl/test-suites/default/watcher/05-errors.yaml b/tests/kuttl/test-suites/default/watcher/05-errors.yaml index 53702f0a..c891e5c6 100644 --- a/tests/kuttl/test-suites/default/watcher/05-errors.yaml +++ b/tests/kuttl/test-suites/default/watcher/05-errors.yaml @@ -49,3 +49,21 @@ apiVersion: watcher.openstack.org/v1beta1 kind: Watcher metadata: name: watcher-kuttl +--- +apiVersion: watcher.openstack.org/v1beta1 +kind: WatcherAPI +metadata: + finalizers: + - openstack.org/watcherapi + name: watcher-kuttl-api +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: watcher-kuttl-api-api +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + service: watcher-api From a5612dd60efa2f3a354b397f55fe79d76112b063 Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Tue, 14 Jan 2025 19:31:49 +0100 Subject: [PATCH 3/3] fix command execution in kuttl assert for case 01 We are defining the command in the TestAssert twice and the second one is just overriding the first one which is never executed. This patch is merging both in just one assignment which is what kuttl tests definition seems to expect. --- .../kuttl/test-suites/default/watcher/01-assert.yaml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/kuttl/test-suites/default/watcher/01-assert.yaml b/tests/kuttl/test-suites/default/watcher/01-assert.yaml index 3dc9302d..8837c8ab 100644 --- a/tests/kuttl/test-suites/default/watcher/01-assert.yaml +++ b/tests/kuttl/test-suites/default/watcher/01-assert.yaml @@ -258,16 +258,9 @@ commands: set -euxo pipefail oc exec -n watcher-kuttl-default openstackclient -- openstack service list -f value -c Name -c Type |[ $(grep -c ^watcher) == 1 ] SERVICEID=$(oc exec -n watcher-kuttl-default openstackclient -- openstack service list -f value -c Name -c Type -c ID | grep watcher| awk '{print $1}') - [ $(oc get -n watcher-kuttl-default keystoneservice watcher -o jsonpath={.status.serviceID}) == $SERVICEID ] + [ $(oc get -n watcher-kuttl-default keystoneservice watcher -o jsonpath={.status.serviceID}) == ${SERVICEID} ] [ -n "$(oc get -n watcher-kuttl-default watcher watcher-kuttl -o jsonpath={.status.hash.dbsync})" ] ---- -# Check for Container Image environment variables -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: - - script: | - set -euxo pipefail - # If we are running the container locally, skip this test + # If we are running the container locally, skip following test if [ "$(oc get pods -n openstack-operators -o name -l openstack.org/operator-name=watcher)" == "" ]; then exit 0 fi