From 6ad24e6f8a997c62cabc09fb60b3343a92fc7252 Mon Sep 17 00:00:00 2001 From: Jiri Podivin Date: Mon, 23 Sep 2024 13:49:23 +0200 Subject: [PATCH] Adding general map field for configuring AEE Signed-off-by: Jiri Podivin --- ...ack.org_openstackdataplanedeployments.yaml | 2 + apis/dataplane/v1beta1/common.go | 3 + .../openstackdataplanedeployment_types.go | 6 + .../v1beta1/zz_generated.deepcopy.go | 15 ++ ...ack.org_openstackdataplanedeployments.yaml | 2 + pkg/dataplane/deployment.go | 30 ++++ pkg/dataplane/inventory.go | 21 +-- pkg/dataplane/util/ansible_execution.go | 3 +- pkg/dataplane/util/ansibleee.go | 15 ++ ...tackdataplanedeployment_controller_test.go | 145 ++++++++++++++++++ 10 files changed, 221 insertions(+), 21 deletions(-) diff --git a/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml index 403b30db0..b540a7480 100644 --- a/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml +++ b/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -44,6 +44,8 @@ spec: type: object spec: properties: + ansibleEeConfig: + x-kubernetes-preserve-unknown-fields: true ansibleExtraVars: x-kubernetes-preserve-unknown-fields: true ansibleLimit: diff --git a/apis/dataplane/v1beta1/common.go b/apis/dataplane/v1beta1/common.go index eef607d35..7094bb754 100644 --- a/apis/dataplane/v1beta1/common.go +++ b/apis/dataplane/v1beta1/common.go @@ -198,4 +198,7 @@ type AnsibleEESpec struct { // the ansible execution run with. Without specifying, it will run with // default serviceaccount ServiceAccountName string + // EnvConfigMapName name of configuration config map to be used + // defaults to openstack-aee-default-env + EnvConfigMapName string `json:"envConfigMapName,omitempty"` } diff --git a/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go b/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go index 4f062da84..68c14c9c1 100644 --- a/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go +++ b/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go @@ -67,6 +67,12 @@ type OpenStackDataPlaneDeploymentSpec struct { // +kubebuilder:validation:Minimum:=1 // +kubebuilder:default:=15 DeploymentRequeueTime int `json:"deploymentRequeueTime"` + + // Additional configuration options for AnsibleExecutionEnvironment + // +kubebuilder:validation:Optional + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Schemaless + AnsibleEEConfig map[string]json.RawMessage `json:"ansibleEeConfig,omitempty"` } // OpenStackDataPlaneDeploymentStatus defines the observed state of OpenStackDataPlaneDeployment diff --git a/apis/dataplane/v1beta1/zz_generated.deepcopy.go b/apis/dataplane/v1beta1/zz_generated.deepcopy.go index dec42fefe..bea6c3b41 100644 --- a/apis/dataplane/v1beta1/zz_generated.deepcopy.go +++ b/apis/dataplane/v1beta1/zz_generated.deepcopy.go @@ -355,6 +355,21 @@ func (in *OpenStackDataPlaneDeploymentSpec) DeepCopyInto(out *OpenStackDataPlane *out = make([]string, len(*in)) copy(*out, *in) } + if in.AnsibleEEConfig != nil { + in, out := &in.AnsibleEEConfig, &out.AnsibleEEConfig + *out = make(map[string]json.RawMessage, len(*in)) + for key, val := range *in { + var outVal []byte + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneDeploymentSpec. diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml index 403b30db0..b540a7480 100644 --- a/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -44,6 +44,8 @@ spec: type: object spec: properties: + ansibleEeConfig: + x-kubernetes-preserve-unknown-fields: true ansibleExtraVars: x-kubernetes-preserve-unknown-fields: true ansibleLimit: diff --git a/pkg/dataplane/deployment.go b/pkg/dataplane/deployment.go index 1483a9b9e..4c007a43f 100644 --- a/pkg/dataplane/deployment.go +++ b/pkg/dataplane/deployment.go @@ -133,6 +133,14 @@ func (d *Deployer) Deploy(services []string) (*ctrl.Result, error) { } } + // set additional ansible options + // don't fail if there is a problem with struct + // since it's purely optional + err = d.setAdditionalAEEOptions() + if err != nil { + log.Error(err, fmt.Sprintf("error: %e while parsing ansibleEeConfig, defaults will be used", err)) + } + err = d.ConditionalDeploy( readyCondition, readyMessage, @@ -496,3 +504,25 @@ func (d *Deployer) addServiceExtraMounts( return d.AeeSpec, nil } + +func (d *Deployer) setAdditionalAEEOptions() error { + additionalAEEOptions := make(map[string]interface{}) + var test bool + + err := dataplaneutil.UnmarshalRawStrings( + d.Deployment.Spec.AnsibleEEConfig, additionalAEEOptions) + if err != nil { + return err + } + + // Check presence of the key, type and contents + configMapName, test := additionalAEEOptions["envConfMapName"] + + if test && reflect.TypeOf(configMapName).Kind() == reflect.String && configMapName != "" { + d.AeeSpec.EnvConfigMapName = configMapName.(string) + } else { + d.AeeSpec.EnvConfigMapName = "openstack-aee-default-env" + } + + return nil +} diff --git a/pkg/dataplane/inventory.go b/pkg/dataplane/inventory.go index e96ff5c6a..66cd6ac65 100644 --- a/pkg/dataplane/inventory.go +++ b/pkg/dataplane/inventory.go @@ -24,8 +24,6 @@ import ( "strconv" "strings" - yaml "gopkg.in/yaml.v3" - infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/ansible" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" @@ -303,7 +301,7 @@ func resolveGroupAnsibleVars(template *dataplanev1.NodeTemplate, group *ansible. group.Vars["edpm_telemetry_kepler_image"] = containerImages.EdpmKeplerImage } - err := unmarshalAnsibleVars(template.Ansible.AnsibleVars, group.Vars) + err := util.UnmarshalRawStrings(template.Ansible.AnsibleVars, group.Vars) if err != nil { return err } @@ -332,7 +330,7 @@ func resolveHostAnsibleVars(node *dataplanev1.NodeSection, host.Vars["management_network"] = node.ManagementNetwork } - err := unmarshalAnsibleVars(node.Ansible.AnsibleVars, host.Vars) + err := util.UnmarshalRawStrings(node.Ansible.AnsibleVars, host.Vars) if err != nil { return err } @@ -347,21 +345,6 @@ func resolveHostAnsibleVars(node *dataplanev1.NodeSection, } -// unmarshal raw strings into an ansible vars dictionary -func unmarshalAnsibleVars(ansibleVars map[string]json.RawMessage, - parsedVars map[string]interface{}) error { - - for key, val := range ansibleVars { - var v interface{} - err := yaml.Unmarshal(val, &v) - if err != nil { - return err - } - parsedVars[key] = v - } - return nil -} - func buildNetworkVars(networks []infranetworkv1.IPSetNetwork, netServiceNetMap map[string]string) ([]string, map[string]string) { serviceNetMap := make(map[string]string) diff --git a/pkg/dataplane/util/ansible_execution.go b/pkg/dataplane/util/ansible_execution.go index a18aacb5e..488faf578 100644 --- a/pkg/dataplane/util/ansible_execution.go +++ b/pkg/dataplane/util/ansible_execution.go @@ -50,7 +50,6 @@ func AnsibleExecution( nodeSet client.Object, ) error { var err error - executionName, labels := GetAnsibleExecutionNameAndLabels(service, deployment.GetName(), nodeSet.GetName()) existingAnsibleEE, err := GetAnsibleExecution(ctx, helper, deployment, labels) @@ -67,7 +66,7 @@ func AnsibleExecution( Name: executionName, Namespace: deployment.GetNamespace(), Labels: labels, - EnvConfigMapName: "openstack-aee-default-env", + EnvConfigMapName: aeeSpec.EnvConfigMapName, } ansibleEE.NetworkAttachments = aeeSpec.NetworkAttachments diff --git a/pkg/dataplane/util/ansibleee.go b/pkg/dataplane/util/ansibleee.go index e5b73d143..9de643bf1 100644 --- a/pkg/dataplane/util/ansibleee.go +++ b/pkg/dataplane/util/ansibleee.go @@ -295,3 +295,18 @@ func calculateHash(envVar string) (string, error) { } return hash, nil } + +// unmarshal raw strings into a dictionary +func UnmarshalRawStrings(rawStrings map[string]json.RawMessage, + parsedVars map[string]interface{}) error { + + for key, val := range rawStrings { + var v interface{} + err := yaml.Unmarshal(val, &v) + if err != nil { + return err + } + parsedVars[key] = v + } + return nil +} diff --git a/tests/functional/dataplane/openstackdataplanedeployment_controller_test.go b/tests/functional/dataplane/openstackdataplanedeployment_controller_test.go index 29abd907d..8f30667c4 100644 --- a/tests/functional/dataplane/openstackdataplanedeployment_controller_test.go +++ b/tests/functional/dataplane/openstackdataplanedeployment_controller_test.go @@ -1,6 +1,7 @@ package functional import ( + "encoding/json" "fmt" "os" @@ -1777,4 +1778,148 @@ var _ = Describe("Dataplane Deployment Test", func() { }) + When("A dataplaneDeployment is created with matching NodeSet and additional AEE options", func() { + BeforeEach(func() { + CreateSSHSecret(dataplaneSSHSecretName) + DeferCleanup(th.DeleteInstance, th.CreateSecret(neutronOvnMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaNeutronMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaCellComputeConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaMigrationSSHKey, map[string][]byte{ + "ssh-privatekey": []byte("fake-ssh-private-key"), + "ssh-publickey": []byte("fake-ssh-public-key"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(ceilometerConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + // DefaultDataPlanenodeSetSpec comes with three mock services + // default service + CreateDataplaneService(dataplaneServiceName, false) + // marked for deployment on all nodesets + CreateDataplaneService(dataplaneGlobalServiceName, true) + // with EDPMServiceType set + CreateDataPlaneServiceFromSpec(dataplaneUpdateServiceName, map[string]interface{}{ + "edpmServiceType": "foo-update-service", + "openStackAnsibleEERunnerImage": "foo-image:latest"}) + deploymentSpec := DefaultDataPlaneDeploymentSpec() + deploymentSpec["ansibleEeConfig"] = map[string]json.RawMessage{ + "envConfMapName": json.RawMessage([]byte("\"some-map\"")), + } + DeferCleanup(th.DeleteService, dataplaneServiceName) + DeferCleanup(th.DeleteService, dataplaneGlobalServiceName) + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + SimulateDNSMasqComplete(dnsMasqName) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNodeSetSpec(dataplaneNodeSetName.Name))) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, deploymentSpec)) + }) + + It("Should have Spec fields initialized", func() { + dataplaneDeploymentInstance := GetDataplaneDeployment(dataplaneDeploymentName) + expectedSpec := dataplanev1.OpenStackDataPlaneDeploymentSpec{ + NodeSets: []string{"edpm-compute-nodeset"}, + AnsibleTags: "", + AnsibleLimit: "", + AnsibleSkipTags: "", + BackoffLimit: &DefaultBackoffLimit, + PreserveJobs: true, + DeploymentRequeueTime: 15, + ServicesOverride: nil, + AnsibleEEConfig: map[string]json.RawMessage{ + "envConfMapName": json.RawMessage([]byte(`"some-map"`)), + }, + } + Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec)) + }) + + It("should have conditions set", func() { + + nodeSet := dataplanev1.OpenStackDataPlaneNodeSet{} + baremetal := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeSet.Name, + Namespace: nodeSet.Namespace, + }, + } + // Create config map for OVN service + ovnConfigMapName := types.NamespacedName{ + Namespace: namespace, + Name: "ovncontroller-config", + } + mapData := map[string]interface{}{ + "ovsdb-config": "test-ovn-config", + } + th.CreateConfigMap(ovnConfigMapName, mapData) + + nodeSet = *GetDataplaneNodeSet(dataplaneNodeSetName) + + // Set baremetal provisioning conditions to True + Eventually(func(g Gomega) { + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, dataplaneNodeSetName, &baremetal)).To(Succeed()) + baremetal.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetal)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + + // Create all services necessary for deployment + for _, serviceName := range nodeSet.Spec.Services { + dataplaneServiceName := types.NamespacedName{ + Name: serviceName, + Namespace: namespace, + } + service := GetService(dataplaneServiceName) + deployment := GetDataplaneDeployment(dataplaneDeploymentName) + //Retrieve service AnsibleEE and set JobStatus to Successful + aeeName, _ := dataplaneutil.GetAnsibleExecutionNameAndLabels( + service, deployment.GetName(), nodeSet.GetName()) + Eventually(func(g Gomega) { + // Make an AnsibleEE name for each service + ansibleeeName := types.NamespacedName{ + Name: aeeName, + Namespace: dataplaneDeploymentName.Namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + + ansibleEE.Status.Succeeded = 1 + g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed()) + if service.Spec.EDPMServiceType != "" { + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring("edpm_service_type")) + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring(service.Spec.EDPMServiceType)) + } else { + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring(serviceName)) + } + if service.Spec.DeployOnAllNodeSets { + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring("edpm_override_hosts")) + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring("all")) + } else { + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring("edpm_override_hosts")) + g.Expect(findEnvVar(ansibleEE.Spec.Template.Spec.Containers[0].Env).Value).To(ContainSubstring(dataplaneNodeSetName.Name)) + } + }, th.Timeout, th.Interval).Should(Succeed()) + } + + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + }) })