diff --git a/apis/dataplane/v1beta1/conditions.go b/apis/dataplane/v1beta1/conditions.go index 89c0ffbad..531a8d132 100644 --- a/apis/dataplane/v1beta1/conditions.go +++ b/apis/dataplane/v1beta1/conditions.go @@ -109,4 +109,11 @@ const ( // NodeSetServiceDeploymentErrorMessage error NodeSetServiceDeploymentErrorMessage = "Deployment error occurred in %s service" + + // NodeSetMinorUpdateReadyCondition Status=True condition indicates if the + // NodeSet minor update is finished and successful. + NodeSetMinorUpdateReadyCondition condition.Type = "NodeSetMinorUpdateReady" + + // NodeSetMinorUpdateReadyMessage ready + NodeSetMinorUpdateReadyMessage = "Minor update ready for NodeSet" ) diff --git a/controllers/dataplane/openstackdataplanenodeset_controller.go b/controllers/dataplane/openstackdataplanenodeset_controller.go index 90eac8d04..5712ea267 100644 --- a/controllers/dataplane/openstackdataplanenodeset_controller.go +++ b/controllers/dataplane/openstackdataplanenodeset_controller.go @@ -385,7 +385,7 @@ func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req } } - isDeploymentReady, isDeploymentRunning, isDeploymentFailed, failedDeployment, err := checkDeployment(helper, instance) + isDeploymentReady, isDeploymentRunning, isDeploymentFailed, failedDeployment, err := checkDeployment(helper, instance, version) if !isDeploymentFailed && err != nil { instance.Status.Conditions.MarkFalse( condition.DeploymentReadyCondition, @@ -464,6 +464,7 @@ func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req func checkDeployment(helper *helper.Helper, instance *dataplanev1.OpenStackDataPlaneNodeSet, + version *openstackv1.OpenStackVersion, ) (bool, bool, bool, string, error) { // Get all completed deployments var failedDeployment string @@ -530,8 +531,41 @@ func checkDeployment(helper *helper.Helper, } instance.Status.DeployedConfigHash = deployment.Status.NodeSetHashes[instance.Name] instance.Status.DeployedVersion = deployment.Status.DeployedVersion - } + // Get list of services by name, either from ServicesOverride or + // the NodeSet. + var services []string + if len(deployment.Spec.ServicesOverride) != 0 { + services = deployment.Spec.ServicesOverride + } else { + services = instance.Spec.Services + } + + // For each service, check if EDPMServiceType is "update", and the + // deployment deployed the latest version. + for _, serviceName := range services { + service := &dataplanev1.OpenStackDataPlaneService{} + name := types.NamespacedName{ + Namespace: instance.Namespace, + Name: serviceName, + } + err := helper.GetClient().Get(context.Background(), name, service) + if err != nil { + helper.GetLogger().Error(err, "Unable to retrieve OpenStackDataPlaneService %v") + return false, false, false, failedDeployment, err + } + + if service.Spec.EDPMServiceType != "update" { + continue + } + + if deployment.Status.DeployedVersion == version.Spec.TargetVersion { + instance.Status.Conditions.MarkTrue( + dataplanev1.NodeSetMinorUpdateReadyCondition, + dataplanev1.NodeSetMinorUpdateReadyMessage) + } + } + } } } diff --git a/tests/functional/dataplane/base_test.go b/tests/functional/dataplane/base_test.go index 509a9a23f..033cfcf6d 100644 --- a/tests/functional/dataplane/base_test.go +++ b/tests/functional/dataplane/base_test.go @@ -112,8 +112,31 @@ func CreateSSHSecret(name types.NamespacedName) *corev1.Secret { ) } +// Create OpenStackVersion +func CreateOpenStackVersion(name types.NamespacedName) *unstructured.Unstructured { + raw := DefaultOpenStackVersion(name) + return th.CreateUnstructured(raw) +} + // Struct initialization +func DefaultOpenStackVersion(name types.NamespacedName) map[string]interface{} { + return map[string]interface{}{ + "apiVersion": "core.openstack.org/v1beta1", + "kind": "OpenStackVersion", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{ + "targetVersion": "0.0.1", + }, + "status": map[string]interface{}{ + "availableVersion": "0.0.1", + }, + } +} + // Build OpenStackDataPlaneNodeSetSpec struct and fill it with preset values func DefaultDataPlaneNodeSetSpec(nodeSetName string) map[string]interface{} { @@ -180,6 +203,16 @@ func DefaultDataPlaneDeploymentSpec() map[string]interface{} { } } +func MinorUpdateDataPlaneDeploymentSpec() map[string]interface{} { + + return map[string]interface{}{ + "nodeSets": []string{ + "edpm-compute-nodeset", + }, + "servicesOverride": []string{"update"}, + } +} + func DefaultNetConfigSpec() map[string]interface{} { return map[string]interface{}{ "networks": []map[string]interface{}{{ diff --git a/tests/functional/dataplane/openstackdataplanenodeset_controller_test.go b/tests/functional/dataplane/openstackdataplanenodeset_controller_test.go index 8dd1384c6..7f3332e58 100644 --- a/tests/functional/dataplane/openstackdataplanenodeset_controller_test.go +++ b/tests/functional/dataplane/openstackdataplanenodeset_controller_test.go @@ -23,6 +23,8 @@ import ( . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports . "github.com/onsi/gomega" //revive:disable:dot-imports "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" //revive:disable-next-line:dot-imports @@ -64,6 +66,7 @@ var _ = Describe("Dataplane NodeSet Test", func() { var dataplaneDeploymentName types.NamespacedName var dataplaneConfigHash string var dataplaneGlobalServiceName types.NamespacedName + var dataplaneUpdateServiceName types.NamespacedName defaultEdpmServiceList := []string{ "edpm_frr_image", @@ -108,6 +111,10 @@ var _ = Describe("Dataplane NodeSet Test", func() { Name: "global-service", Namespace: namespace, } + dataplaneUpdateServiceName = types.NamespacedName{ + Name: "update", + Namespace: namespace, + } err := os.Setenv("OPERATOR_SERVICES", "../../../config/services") Expect(err).NotTo(HaveOccurred()) }) @@ -1290,4 +1297,60 @@ var _ = Describe("Dataplane NodeSet Test", func() { }).Should(BeTrue()) }) }) + + When("A DataPlaneNodeSet is created with NoNodes and a MinorUpdate OpenStackDataPlaneDeployment is created", func() { + BeforeEach(func() { + + updateServiceSpec := map[string]interface{}{ + "playbook": "osp.edpm.update", + } + CreateDataPlaneServiceFromSpec(dataplaneUpdateServiceName, updateServiceSpec) + DeferCleanup(th.DeleteService, dataplaneUpdateServiceName) + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(false))) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, MinorUpdateDataPlaneDeploymentSpec())) + openstackVersionName := types.NamespacedName{ + Name: "openstackversion", + Namespace: namespace, + } + err := os.Setenv("OPENSTACK_RELEASE_VERSION", "0.0.1") + Expect(err).NotTo(HaveOccurred()) + openstackv1.SetupVersionDefaults() + DeferCleanup(th.DeleteInstance, CreateOpenStackVersion(openstackVersionName)) + + CreateSSHSecret(dataplaneSSHSecretName) + SimulateDNSMasqComplete(dnsMasqName) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + + Eventually(func(g Gomega) { + // Make an AnsibleEE name for each service + ansibleeeName := types.NamespacedName{ + Name: "update-edpm-deployment-edpm-compute-nodeset", + Namespace: namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + ansibleEE.Status.JobStatus = ansibleeev1.JobStatusSucceeded + g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed()) + }, th.Timeout, th.Interval).Should(Succeed()) + + }) + It("Should reach Input, Setup, and MinorUpdate Ready completion", Label("update"), func() { + var conditionList = []condition.Type{ + condition.InputReadyCondition, + dataplanev1.SetupReadyCondition, + dataplanev1.NodeSetMinorUpdateReadyCondition, + } + for _, cond := range conditionList { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + cond, + corev1.ConditionTrue, + ) + } + }) + }) + }) diff --git a/tests/functional/dataplane/suite_test.go b/tests/functional/dataplane/suite_test.go index bb56410aa..a48575275 100644 --- a/tests/functional/dataplane/suite_test.go +++ b/tests/functional/dataplane/suite_test.go @@ -177,6 +177,9 @@ var _ = BeforeSuite(func() { err = (&dataplanev1.OpenStackDataPlaneService{}).SetupWebhookWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) + err = (&openstackv1.OpenStackVersion{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + kclient, err := kubernetes.NewForConfig(cfg) Expect(err).ToNot(HaveOccurred(), "failed to create kclient") err = (&dataplanecontrollers.OpenStackDataPlaneNodeSetReconciler{