From f84d84cb8a1bef275d1b9e743ff1e353870a6f84 Mon Sep 17 00:00:00 2001 From: Francesco Pantano Date: Fri, 4 Aug 2023 14:17:40 +0200 Subject: [PATCH] Introduce envTest for glanceapi_controller This patch introduces envTests for glanceapi_controller, and improves the way the top-level glance_controller is tested. Signed-off-by: Francesco Pantano --- tests/functional/base_test.go | 16 +- tests/functional/glance_controller_test.go | 5 +- tests/functional/glance_test_data.go | 56 ++-- tests/functional/glanceapi_controller_test.go | 244 ++++++++++++++++++ 4 files changed, 290 insertions(+), 31 deletions(-) create mode 100644 tests/functional/glanceapi_controller_test.go diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 3fcd619e..259d00d3 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -152,18 +152,18 @@ func GetDefaultGlanceSpec() map[string]interface{} { return map[string]interface{}{ "databaseInstance": "openstack", "secret": SecretName, - "glanceAPIInternal": GetDefaultGlanceAPISpec(), - "glanceAPIExternal": GetDefaultGlanceAPISpec(), + "glanceAPIInternal": GetDefaultGlanceAPISpec(GlanceAPITypeInternal), + "glanceAPIExternal": GetDefaultGlanceAPISpec(GlanceAPITypeExternal), } } -func GetDefaultGlanceAPISpec() map[string]interface{} { +func GetDefaultGlanceAPISpec(apiType APIType) map[string]interface{} { return map[string]interface{}{ - "secret": SecretName, - "replicas": 1, - "containerImage": "test://glance", - "serviceAccount": glanceTest.GlanceSA.Name, - "databaseInstance": "openstack", + "secret": SecretName, + "replicas": 1, + "containerImage": glanceTest.ContainerImage, + "serviceAccount": glanceTest.GlanceSA.Name, + "apiType": apiType, } } diff --git a/tests/functional/glance_controller_test.go b/tests/functional/glance_controller_test.go index 7a36166a..a3c0c5e3 100644 --- a/tests/functional/glance_controller_test.go +++ b/tests/functional/glance_controller_test.go @@ -229,7 +229,8 @@ var _ = Describe("Glance controller", func() { BeforeEach(func() { DeferCleanup(th.DeleteInstance, CreateGlance(glanceTest.Instance, GetGlanceDefaultSpec())) // Get Default GlanceAPI (internal/external) - DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.Instance, GetDefaultGlanceAPISpec())) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.Instance, GetDefaultGlanceAPISpec(GlanceAPITypeExternal))) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.Instance, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) DeferCleanup( th.DeleteDBService, th.CreateDBService( @@ -262,7 +263,7 @@ var _ = Describe("Glance controller", func() { BeforeEach(func() { DeferCleanup(th.DeleteInstance, CreateGlance(glanceTest.Instance, GetGlanceDefaultSpec())) // Get Default GlanceAPI (internal/external) - DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.Instance, GetDefaultGlanceAPISpec())) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.Instance, GetDefaultGlanceAPISpec(GlanceAPITypeExternal))) DeferCleanup( th.DeleteDBService, th.CreateDBService( diff --git a/tests/functional/glance_test_data.go b/tests/functional/glance_test_data.go index 23d8272c..f2b15b59 100644 --- a/tests/functional/glance_test_data.go +++ b/tests/functional/glance_test_data.go @@ -28,25 +28,29 @@ const ( ) type GlanceTestData struct { - GlanceDatabaseUser string - GlancePassword string - GlanceServiceUser string - GlancePVCSize string - GlanceQuotas map[string]interface{} - Instance types.NamespacedName - GlanceInternal types.NamespacedName - GlanceExternal types.NamespacedName - GlanceRole types.NamespacedName - GlanceRoleBinding types.NamespacedName - GlanceSA types.NamespacedName - GlanceDBSync types.NamespacedName - GlancePublicRoute types.NamespacedName - GlanceInternalRoute types.NamespacedName - GlanceConfigMapData types.NamespacedName - GlanceConfigMapScripts types.NamespacedName - GlanceInternalAPI types.NamespacedName - GlanceExternalAPI types.NamespacedName - InternalAPINAD types.NamespacedName + ContainerImage string + GlanceDatabaseUser string + GlancePassword string + GlanceServiceUser string + GlancePVCSize string + GlancePort string + GlanceQuotas map[string]interface{} + Instance types.NamespacedName + GlanceInternal types.NamespacedName + GlanceExternal types.NamespacedName + GlanceRole types.NamespacedName + GlanceRoleBinding types.NamespacedName + GlanceSA types.NamespacedName + GlanceDBSync types.NamespacedName + GlancePublicRoute types.NamespacedName + GlanceInternalRoute types.NamespacedName + GlanceService types.NamespacedName + GlanceConfigMapData types.NamespacedName + GlanceInternalConfigMapData types.NamespacedName + GlanceConfigMapScripts types.NamespacedName + GlanceInternalAPI types.NamespacedName + GlanceExternalAPI types.NamespacedName + InternalAPINAD types.NamespacedName } // GetGlanceTestData is a function that initialize the GlanceTestData @@ -97,15 +101,23 @@ func GetGlanceTestData(glanceName types.NamespacedName) GlanceTestData { Namespace: glanceName.Namespace, Name: fmt.Sprintf("%s-%s", glanceName.Name, "scripts"), }, - GlancePublicRoute: types.NamespacedName{ + GlanceInternalConfigMapData: types.NamespacedName{ Namespace: glanceName.Namespace, - Name: fmt.Sprintf("%s-public", glanceName.Name), + Name: fmt.Sprintf("%s-%s", glanceName.Name, "internal-config-data"), + }, + GlanceService: types.NamespacedName{ + Namespace: glanceName.Namespace, + Name: "image", }, // Also used to identify GlanceKeystoneService GlanceInternalRoute: types.NamespacedName{ Namespace: glanceName.Namespace, Name: fmt.Sprintf("%s-internal", glanceName.Name), }, + GlancePublicRoute: types.NamespacedName{ + Namespace: glanceName.Namespace, + Name: fmt.Sprintf("%s-public", glanceName.Name), + }, GlanceQuotas: map[string]interface{}{ "imageSizeTotal": 1000, "imageStageTotal": 1000, @@ -121,5 +133,7 @@ func GetGlanceTestData(glanceName types.NamespacedName) GlanceTestData { GlancePassword: "12345678", GlanceServiceUser: "glance", GlancePVCSize: "10G", + ContainerImage: "test://glance", + GlancePort: "9292", } } diff --git a/tests/functional/glanceapi_controller_test.go b/tests/functional/glanceapi_controller_test.go new file mode 100644 index 00000000..7602dfd9 --- /dev/null +++ b/tests/functional/glanceapi_controller_test.go @@ -0,0 +1,244 @@ +/* +Copyright 2023. + +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 functional + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("Glanceapi controller", func() { + When("a GlanceAPI CR is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) + }) + + It("is not Ready", func() { + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("has empty Status fields", func() { + instance := GetGlanceAPI(glanceTest.GlanceInternal) + Expect(instance.Status.Hash).To(BeEmpty()) + Expect(instance.Status.ReadyCount).To(Equal(int32(0))) + }) + }) + When("an unrelated Secret is created the CR state does not change", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "not-relevant-secret", + Namespace: glanceTest.Instance.Namespace, + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + }) + + It("is not Ready", func() { + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + }) + When("the Secret is created with all the expected fields", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDefaultGlance(glanceTest.Instance)) + spec := GetDefaultGlanceAPISpec(GlanceAPITypeInternal) + spec["customServiceConfig"] = "foo=bar" + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, spec)) + DeferCleanup(th.DeleteKeystoneAPI, th.CreateKeystoneAPI(glanceTest.Instance.Namespace)) + th.SimulateKeystoneEndpointReady(glanceTest.GlanceInternalRoute) + }) + + It("reports that input is ready", func() { + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("generated configs successfully", func() { + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + configDataMap := th.GetConfigMap(glanceTest.GlanceInternalConfigMapData) + Expect(configDataMap).ShouldNot(BeNil()) + Expect(configDataMap.Data).Should(HaveKey("custom.conf")) + //Double check customServiceConfig has been applied + configData := string(configDataMap.Data["custom.conf"]) + Expect(configData).Should(ContainSubstring("foo=bar")) + }) + + It("stored the input hash in the Status", func() { + Eventually(func(g Gomega) { + novaAPI := GetGlanceAPI(glanceTest.GlanceInternal) + g.Expect(novaAPI.Status.Hash).Should(HaveKeyWithValue("input", Not(BeEmpty()))) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("GlanceAPI is generated by the top-level CR", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDefaultGlance(glanceTest.Instance)) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceExternal, GetDefaultGlanceAPISpec(GlanceAPITypeExternal))) + DeferCleanup(th.DeleteKeystoneAPI, th.CreateKeystoneAPI(glanceTest.Instance.Namespace)) + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + glanceTest.GlanceExternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("creates a Deployment for glance-api service - Internal", func() { + th.SimulateDeploymentReplicaReady(glanceTest.GlanceInternalAPI) + + ss := th.GetDeployment(glanceTest.GlanceInternalAPI) + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(4)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + container := ss.Spec.Template.Spec.Containers[0] + Expect(container.VolumeMounts).To(HaveLen(3)) + Expect(container.Image).To(Equal(glanceTest.ContainerImage)) + Expect(container.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9292))) + Expect(container.ReadinessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9292))) + }) + + It("creates a Deployment for glance-api service - External", func() { + th.SimulateDeploymentReplicaReady(glanceTest.GlanceExternalAPI) + + ss := th.GetDeployment(glanceTest.GlanceExternalAPI) + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(4)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + container := ss.Spec.Template.Spec.Containers[0] + Expect(container.VolumeMounts).To(HaveLen(3)) + Expect(container.Image).To(Equal(glanceTest.ContainerImage)) + Expect(container.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9292))) + Expect(container.ReadinessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9292))) + }) + }) + When("the Deployment has at least one Replica ready - External", func() { + BeforeEach(func() { + //DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceExternal, GetDefaultGlanceAPISpec(GlanceAPITypeExternal))) + DeferCleanup(th.DeleteKeystoneAPI, th.CreateKeystoneAPI(glanceTest.GlanceExternal.Namespace)) + th.SimulateDeploymentReplicaReady(glanceTest.GlanceExternalAPI) + th.SimulateKeystoneEndpointReady(glanceTest.GlanceExternal) + }) + + It("reports that Deployment is ready", func() { + th.ExpectCondition( + glanceTest.GlanceExternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + // Deployment is Ready, check the actual ReadyCount is > 0 + glanceAPI := GetGlanceAPI(glanceTest.GlanceExternal) + Expect(glanceAPI.Status.ReadyCount).To(BeNumerically(">", 0)) + }) + + It("exposes the service", func() { + // Only a Public Route is exposed outside + apiInstance := th.GetService(glanceTest.GlancePublicRoute) + Expect(apiInstance.Labels["service"]).To(Equal("glance-external")) + // Route is created on top of the existing service + th.AssertRouteExists(glanceTest.GlancePublicRoute) + }) + + It("creates KeystoneEndpoint", func() { + keystoneEndpoint := th.GetKeystoneEndpoint(glanceTest.GlanceExternal) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", "http:")) + th.ExpectCondition( + glanceTest.GlanceExternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + When("the Deployment has at least one Replica ready - Internal", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, GetDefaultGlanceAPISpec(GlanceAPITypeInternal))) + DeferCleanup(th.DeleteKeystoneAPI, th.CreateKeystoneAPI(glanceTest.GlanceInternal.Namespace)) + th.SimulateDeploymentReplicaReady(glanceTest.GlanceInternalAPI) + th.SimulateKeystoneEndpointReady(glanceTest.GlanceInternalRoute) + }) + + It("reports that Deployment is ready", func() { + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + // Deployment is Ready, check the actual ReadyCount is > 0 + glanceAPI := GetGlanceAPI(glanceTest.GlanceInternal) + Expect(glanceAPI.Status.ReadyCount).To(BeNumerically(">", 0)) + }) + + It("exposes the service", func() { + apiInstance := th.GetService(glanceTest.GlanceInternalRoute) + Expect(apiInstance.Labels["service"]).To(Equal("glance-internal")) + }) + + It("creates KeystoneEndpoint", func() { + keystoneEndpoint := th.GetKeystoneEndpoint(glanceTest.GlanceInternal) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("internal", "http://glance-internal."+glanceTest.Instance.Namespace+".svc:9292")) + th.ExpectCondition( + glanceTest.GlanceInternal, + ConditionGetterFunc(GlanceAPIConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + }) + }) +})