diff --git a/internal/controller/dataplane/openstackdataplanedeployment_controller.go b/internal/controller/dataplane/openstackdataplanedeployment_controller.go index c690ae6b7..9ee3d35a9 100644 --- a/internal/controller/dataplane/openstackdataplanedeployment_controller.go +++ b/internal/controller/dataplane/openstackdataplanedeployment_controller.go @@ -451,6 +451,11 @@ func (r *OpenStackDataPlaneDeploymentReconciler) setHashes( for _, nodeSet := range nodeSets.Items { instance.Status.NodeSetHashes[nodeSet.Name] = nodeSet.Status.ConfigHash + + err = setNodeSetAnsibleVarsFromHashes(ctx, helper, &nodeSet, instance.Status.ConfigMapHashes, instance.Status.SecretHashes) + if err != nil { + return err + } } return nil @@ -547,3 +552,27 @@ func (r *OpenStackDataPlaneDeploymentReconciler) listNodeSets(ctx context.Contex } return &nodeSets, err } + +func setNodeSetAnsibleVarsFromHashes( + ctx context.Context, + helper *helper.Helper, + nodeSetInstance *dataplanev1.OpenStackDataPlaneNodeSet, + configMapHashes map[string]string, // configMapHashes from deployment.instance.configMapHashes + secretHashes map[string]string, // secretHashes from deployment.instance.secretHashes +) error { + namespace := nodeSetInstance.Namespace + + // Process NodeTemplate level AnsibleVarsFrom + if err := deployment.ProcessAnsibleVarsFrom(ctx, helper, namespace, configMapHashes, secretHashes, nodeSetInstance.Spec.NodeTemplate.Ansible.AnsibleVarsFrom); err != nil { + return err + } + + // Process individual Node level AnsibleVarsFrom + for _, node := range nodeSetInstance.Spec.Nodes { + if err := deployment.ProcessAnsibleVarsFrom(ctx, helper, namespace, configMapHashes, secretHashes, node.Ansible.AnsibleVarsFrom); err != nil { + return err + } + } + + return nil +} diff --git a/internal/controller/dataplane/openstackdataplanenodeset_controller.go b/internal/controller/dataplane/openstackdataplanenodeset_controller.go index 648905033..ea529dc05 100644 --- a/internal/controller/dataplane/openstackdataplanenodeset_controller.go +++ b/internal/controller/dataplane/openstackdataplanenodeset_controller.go @@ -576,6 +576,17 @@ func checkDeployment(ctx context.Context, helper *helper.Helper, deployment.Status.BmhRefHashes[instance.Name] != instance.Status.BmhRefHash) { continue } + + hasAnsibleVarsFromChanged, err := checkAnsibleVarsFromChanged(ctx, helper, instance, deployment.Status.ConfigMapHashes, deployment.Status.SecretHashes) + + if err != nil { + return isNodeSetDeploymentReady, isNodeSetDeploymentRunning, isNodeSetDeploymentFailed, failedDeploymentName, err + } + + if hasAnsibleVarsFromChanged { + continue + } + isNodeSetDeploymentReady = true for k, v := range deployment.Status.ConfigMapHashes { instance.Status.ConfigMapHashes[k] = v @@ -911,3 +922,49 @@ func (r *OpenStackDataPlaneNodeSetReconciler) GetSpecConfigHash(instance *datapl } return configHash, nil } + +// checkAnsibleVarsFromChanged computes current hashes for ConfigMaps/Secrets +// referenced in AnsibleVarsFrom and compares them with deployed hashes. +// Returns true if any content has changed, false otherwise. +func checkAnsibleVarsFromChanged( + ctx context.Context, + helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + deployedConfigMapHashes map[string]string, + deployedSecretHashes map[string]string, +) (bool, error) { + currentConfigMapHashes := make(map[string]string) + currentSecretHashes := make(map[string]string) + + namespace := instance.Namespace + + // Process NodeTemplate level AnsibleVarsFrom + if err := deployment.ProcessAnsibleVarsFrom(ctx, helper, namespace, currentConfigMapHashes, currentSecretHashes, instance.Spec.NodeTemplate.Ansible.AnsibleVarsFrom); err != nil { + return false, err + } + + // Process individual Node level AnsibleVarsFrom + for _, node := range instance.Spec.Nodes { + if err := deployment.ProcessAnsibleVarsFrom(ctx, helper, namespace, currentConfigMapHashes, currentSecretHashes, node.Ansible.AnsibleVarsFrom); err != nil { + return false, err + } + } + + // Compare current ConfigMap hashes with deployed hashes + for name, currentHash := range currentConfigMapHashes { + if deployedHash, exists := deployedConfigMapHashes[name]; !exists || deployedHash != currentHash { + helper.GetLogger().Info("ConfigMap content changed", "configMap", name) + return true, nil + } + } + + // Compare current Secret hashes with deployed hashes + for name, currentHash := range currentSecretHashes { + if deployedHash, exists := deployedSecretHashes[name]; !exists || deployedHash != currentHash { + helper.GetLogger().Info("Secret content changed", "secret", name) + return true, nil + } + } + + return false, nil +} diff --git a/internal/dataplane/hashes.go b/internal/dataplane/hashes.go index 33d7653f2..7374ee56d 100644 --- a/internal/dataplane/hashes.go +++ b/internal/dataplane/hashes.go @@ -100,3 +100,42 @@ func GetDeploymentHashesForService( return nil } + +// ProcessAnsibleVarsFrom computes hashes for ConfigMaps and Secrets +// referenced in the NodeSet's AnsibleVarsFrom field (both NodeTemplate and +// individual Nodes) +func ProcessAnsibleVarsFrom( + ctx context.Context, + helper *helper.Helper, + namespace string, + configMapHashes map[string]string, + secretHashes map[string]string, + varsFrom []dataplanev1.DataSource, +) error { + for _, dataSource := range varsFrom { + cm, sec, err := dataplaneutil.GetDataSourceCmSecret(ctx, helper, namespace, dataSource) + + if err != nil { + return err + } + + if cm != nil { + hash, err := configmap.Hash(cm) + if err != nil { + helper.GetLogger().Error(err, "Unable to hash ConfigMap", "configMap", cm.Name) + return err + } + configMapHashes[cm.Name] = hash + } + + if sec != nil { + hash, err := secret.Hash(sec) + if err != nil { + helper.GetLogger().Error(err, "Unable to hash Secret", "secret", sec.Name) + return err + } + secretHashes[sec.Name] = hash + } + } + return nil +} diff --git a/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-ansiblevars-deploy.yaml b/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-ansiblevars-deploy.yaml new file mode 100644 index 000000000..d1651e982 --- /dev/null +++ b/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-ansiblevars-deploy.yaml @@ -0,0 +1,48 @@ +# Test ansibleVarsFrom ConfigMap/Secret change detection +# First, delete the failed deployment from step 05 to clear the NodeSet error state +# This step can be ignored if we did not want to check the status of the nodeSet +# and verify if that is waiting for deployment. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: oc delete openstackdataplanedeployment edpm-compute-no-nodes-non-existent-service -n openstack-kuttl-tests --ignore-not-found=true +--- +# Create ConfigMap and Secret for ansibleVarsFrom +apiVersion: v1 +kind: ConfigMap +metadata: + name: ansiblevars-test-cm +data: + test_var: original-value +--- +apiVersion: v1 +kind: Secret +metadata: + name: ansiblevars-test-secret +stringData: + secret_var: original-secret-value +--- +# Patch existing NodeSet to add ansibleVarsFrom +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + nodeTemplate: + ansible: + ansibleVarsFrom: + - configMapRef: + name: ansiblevars-test-cm + - secretRef: + name: ansiblevars-test-secret +--- +# Create deployment to capture the hashes +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-ansiblevars-deploy +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - configure-os diff --git a/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-assert.yaml b/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-assert.yaml new file mode 100644 index 000000000..19c952c20 --- /dev/null +++ b/test/kuttl/tests/dataplane-deploy-no-nodes-test/08-assert.yaml @@ -0,0 +1,61 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=openstack + name: operator-logs +--- +# Assert NodeSet is Ready with configMapHashes populated +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +status: + conditions: + - message: NodeSet Ready + 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: NodeSetDNSDataReady ready + reason: Ready + status: "True" + type: NodeSetDNSDataReady + - message: NodeSetIPReservationReady ready + reason: Ready + status: "True" + type: NodeSetIPReservationReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady + configMapHashes: + ansiblevars-test-cm: n5bbhc6h5fdhf8h57bh665h5b9h576h598h7dh5fdh667hch5b5h89h9dh668hc4hc6h669h74h575hf4h596h66dhd5h5f7hb7h689hf9hddh64q + secretHashes: + ansiblevars-test-secret: n5d8h67h5b8hc9h5cfh54bh76h544h66chf7h8bh5fbh59chdch76h96h54bh74h9bh5bh5cdh94h566h699h99hdch65fh5d6h5fbh5bfh586hfcq +--- +# Assert deployment completed with hashes +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-ansiblevars-deploy + namespace: openstack-kuttl-tests +status: + deployed: true + configMapHashes: + ansiblevars-test-cm: n5bbhc6h5fdhf8h57bh665h5b9h576h598h7dh5fdh667hch5b5h89h9dh668hc4hc6h669h74h575hf4h596h66dhd5h5f7hb7h689hf9hddh64q + secretHashes: + ansiblevars-test-secret: n5d8h67h5b8hc9h5cfh54bh76h544h66chf7h8bh5fbh59chdch76h96h54bh74h9bh5bh5cdh94h566h699h99hdch65fh5d6h5fbh5bfh586hfcq diff --git a/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-ansiblevars-update.yaml b/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-ansiblevars-update.yaml new file mode 100644 index 000000000..05e6dc1ab --- /dev/null +++ b/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-ansiblevars-update.yaml @@ -0,0 +1,25 @@ +# Update ConfigMap and Secret content to trigger change detection +apiVersion: v1 +kind: ConfigMap +metadata: + name: ansiblevars-test-cm +data: + test_var: updated-value +--- +apiVersion: v1 +kind: Secret +metadata: + name: ansiblevars-test-secret +stringData: + secret_var: updated-secret-value +--- +# Create deployment to capture the new hashes after content update +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-ansiblevars-deploy-update +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - configure-os diff --git a/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-assert.yaml b/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-assert.yaml new file mode 100644 index 000000000..8e807b4d1 --- /dev/null +++ b/test/kuttl/tests/dataplane-deploy-no-nodes-test/09-assert.yaml @@ -0,0 +1,63 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=openstack + name: operator-logs +--- +# Assert NodeSet is Ready after deployment with updated ConfigMap/Secret completes +# The hash values will differ from step 08 due to content change +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +status: + conditions: + - message: NodeSet Ready + 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: NodeSetDNSDataReady ready + reason: Ready + status: "True" + type: NodeSetDNSDataReady + - message: NodeSetIPReservationReady ready + reason: Ready + status: "True" + type: NodeSetIPReservationReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady + configMapHashes: + ansiblevars-test-cm: nd7h5fdh54bh8chb7h5hc8h654hf8h5d5h66bh54dh56fh7bhf5h98h66fh59ch87h9ch5d6h664h668hb7h66fh7fh598h9fh57dhfh5fch655q + secretHashes: + ansiblevars-test-secret: n56hffh656h98hc9h4h79h86h5bfh695hd7h5f9h5d4h87h85h546h7dhc5h98h676h5fh66bh66bhb5h679h77hf8hdbh569h66dhb5h59dq +--- +# Assert deployment completed with new hashes +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-ansiblevars-deploy-update + namespace: openstack-kuttl-tests +status: + deployed: true + configMapHashes: + ansiblevars-test-cm: nd7h5fdh54bh8chb7h5hc8h654hf8h5d5h66bh54dh56fh7bhf5h98h66fh59ch87h9ch5d6h664h668hb7h66fh7fh598h9fh57dhfh5fch655q + secretHashes: + ansiblevars-test-secret: n56hffh656h98hc9h4h79h86h5bfh695hd7h5f9h5d4h87h85h546h7dhc5h98h676h5fh66bh66bhb5h679h77hf8hdbh569h66dhb5h59dq +