github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/plan/service_descriptor_utils_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package plan 21 22 import ( 23 "context" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 ctrl "sigs.k8s.io/controller-runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/log" 34 35 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 36 "github.com/1aal/kubeblocks/pkg/constant" 37 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 38 "github.com/1aal/kubeblocks/pkg/generics" 39 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 40 testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s" 41 ) 42 43 var _ = Describe("generate service descriptor", func() { 44 cleanEnv := func() { 45 // must wait till resources deleted and no longer existed before the testcases start, 46 // otherwise if later it needs to create some new resource objects with the same name, 47 // in race conditions, it will find the existence of old objects, resulting failure to 48 // create the new objects. 49 By("clean resources") 50 51 inNS := client.InNamespace(testCtx.DefaultNamespace) 52 ml := client.HasLabels{testCtx.TestObjLabelKey} 53 54 // resources should be released in following order 55 // non-namespaced 56 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ClusterVersionSignature, true, ml) 57 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ClusterDefinitionSignature, true, ml) 58 testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) 59 60 // namespaced 61 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigMapSignature, true, inNS, ml) 62 } 63 64 var ( 65 mockClient *testutil.K8sClientMockHelper 66 clusterDef *appsv1alpha1.ClusterDefinition 67 clusterVersion *appsv1alpha1.ClusterVersion 68 cluster *appsv1alpha1.Cluster 69 beReferencedCluster *appsv1alpha1.Cluster 70 ) 71 72 var ( 73 namespace = "default" 74 clusterName = "cluster" 75 beReferencedClusterName = "cluster-be-referenced" 76 clusterDefName = "test-cd" 77 clusterVersionName = "test-cv" 78 nginxCompName = "nginx" 79 nginxCompDefName = "nginx" 80 mysqlCompName = "mysql" 81 mysqlCompDefName = "mysql" 82 externalServiceDescriptorName = "mock-external-service-descriptor-name" 83 externalServiceDescriptorKind = "redis" 84 externalServiceDescriptorVersion = "7.0.1" 85 internalClusterServiceRefKind = "mysql" 86 internalClusterServiceRefVersion = "8.0.2" 87 secretName = beReferencedClusterName + "-conn-credential" 88 redisServiceRefDeclarationName = "redis" 89 mysqlServiceRefDeclarationName = "mysql" 90 ) 91 92 BeforeEach(func() { 93 cleanEnv() 94 mockClient = testutil.NewK8sMockClient() 95 }) 96 97 AfterEach(func() { 98 mockClient.Finish() 99 cleanEnv() 100 }) 101 102 // for test GetContainerWithVolumeMount 103 Context("generate service descriptor test", func() { 104 BeforeEach(func() { 105 serviceRefDeclarations := []appsv1alpha1.ServiceRefDeclaration{ 106 { 107 Name: redisServiceRefDeclarationName, 108 ServiceRefDeclarationSpecs: []appsv1alpha1.ServiceRefDeclarationSpec{ 109 { 110 ServiceKind: externalServiceDescriptorKind, 111 ServiceVersion: externalServiceDescriptorVersion, 112 }, 113 }, 114 }, 115 { 116 Name: mysqlServiceRefDeclarationName, 117 ServiceRefDeclarationSpecs: []appsv1alpha1.ServiceRefDeclarationSpec{ 118 { 119 ServiceKind: internalClusterServiceRefKind, 120 ServiceVersion: internalClusterServiceRefVersion, 121 }, 122 }, 123 }, 124 } 125 clusterDef = testapps.NewClusterDefFactory(clusterDefName). 126 AddComponentDef(testapps.StatelessNginxComponent, nginxCompDefName). 127 AddServiceRefDeclarations(serviceRefDeclarations). 128 Create(&testCtx).GetObject() 129 clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName). 130 AddComponentVersion(nginxCompDefName). 131 AddInitContainerShort("nginx-init", testapps.NginxImage). 132 AddContainerShort("nginx", testapps.NginxImage). 133 Create(&testCtx).GetObject() 134 }) 135 136 It("serviceRefDeclaration serviceVersion regex validation test", func() { 137 type versionCmp struct { 138 serviceRefDeclRegex string 139 serviceDescriptorVersion string 140 } 141 tests := []struct { 142 name string 143 fields versionCmp 144 want bool 145 }{{ 146 name: "version string test true", 147 fields: versionCmp{ 148 serviceRefDeclRegex: "8.0.8", 149 serviceDescriptorVersion: "8.0.8", 150 }, 151 want: true, 152 }, { 153 name: "version string test false", 154 fields: versionCmp{ 155 serviceRefDeclRegex: "8.0.8", 156 serviceDescriptorVersion: "8.0.7", 157 }, 158 want: false, 159 }, { 160 name: "version string test false", 161 fields: versionCmp{ 162 serviceRefDeclRegex: "^8.0.8$", 163 serviceDescriptorVersion: "v8.0.8", 164 }, 165 want: false, 166 }, { 167 name: "version string test true", 168 fields: versionCmp{ 169 serviceRefDeclRegex: "8.0.\\d{1,2}$", 170 serviceDescriptorVersion: "8.0.6", 171 }, 172 want: true, 173 }, { 174 name: "version string test false", 175 fields: versionCmp{ 176 serviceRefDeclRegex: "8.0.\\d{1,2}$", 177 serviceDescriptorVersion: "8.0.8.8.8", 178 }, 179 want: false, 180 }, { 181 name: "version string test true", 182 fields: versionCmp{ 183 serviceRefDeclRegex: "^[v\\-]*?(\\d{1,2}\\.){0,3}\\d{1,2}$", 184 serviceDescriptorVersion: "v-8.0.8.0", 185 }, 186 want: true, 187 }, { 188 name: "version string test false", 189 fields: versionCmp{ 190 serviceRefDeclRegex: "^[v\\-]*?(\\d{1,2}\\.){0,3}\\d{1,2}$", 191 serviceDescriptorVersion: "mysql-8.0.8", 192 }, 193 want: false, 194 }} 195 for _, tt := range tests { 196 match := verifyServiceVersion(tt.fields.serviceDescriptorVersion, tt.fields.serviceRefDeclRegex) 197 Expect(match).Should(Equal(tt.want)) 198 } 199 }) 200 201 It("generate service descriptor test", func() { 202 By("Create cluster and beReferencedCluster object") 203 beReferencedCluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, beReferencedClusterName, 204 clusterDef.Name, clusterVersion.Name). 205 AddComponent(mysqlCompName, mysqlCompDefName). 206 Create(&testCtx).GetObject() 207 208 serviceRefs := []appsv1alpha1.ServiceRef{ 209 { 210 Name: redisServiceRefDeclarationName, 211 ServiceDescriptor: externalServiceDescriptorName, 212 }, 213 { 214 Name: mysqlServiceRefDeclarationName, 215 Cluster: beReferencedCluster.Name, 216 }, 217 } 218 cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, 219 clusterDef.Name, clusterVersion.Name). 220 AddComponent(nginxCompName, nginxCompDefName). 221 SetServiceRefs(serviceRefs). 222 Create(&testCtx).GetObject() 223 224 clusterKey := client.ObjectKeyFromObject(cluster) 225 req := ctrl.Request{ 226 NamespacedName: clusterKey, 227 } 228 reqCtx := intctrlutil.RequestCtx{ 229 Ctx: testCtx.Ctx, 230 Req: req, 231 Log: log.FromContext(ctx).WithValues("cluster", req.NamespacedName), 232 } 233 By("GenServiceReferences failed because external service descriptor not found") 234 serviceReferences, err := GenServiceReferences(reqCtx, testCtx.Cli, cluster, &clusterDef.Spec.ComponentDefs[0], &cluster.Spec.ComponentSpecs[0]) 235 Expect(err).ShouldNot(Succeed()) 236 Expect(apierrors.IsNotFound(err)).Should(BeTrue()) 237 Expect(serviceReferences).Should(BeNil()) 238 239 By("create external service descriptor") 240 endpoint := appsv1alpha1.CredentialVar{ 241 Value: "mock-endpoint", 242 } 243 port := appsv1alpha1.CredentialVar{ 244 Value: "mock-port", 245 } 246 auth := appsv1alpha1.ConnectionCredentialAuth{ 247 Username: &appsv1alpha1.CredentialVar{ 248 Value: "mock-username", 249 }, 250 Password: &appsv1alpha1.CredentialVar{ 251 Value: "mock-password", 252 }, 253 } 254 externalServiceDescriptor := testapps.NewServiceDescriptorFactory(testCtx.DefaultNamespace, externalServiceDescriptorName). 255 SetEndpoint(endpoint). 256 SetPort(port). 257 SetAuth(auth). 258 Create(&testCtx).GetObject() 259 260 By("GenServiceReferences failed because external service descriptor status is not available") 261 serviceReferences, err = GenServiceReferences(reqCtx, testCtx.Cli, cluster, &clusterDef.Spec.ComponentDefs[0], &cluster.Spec.ComponentSpecs[0]) 262 Expect(err).ShouldNot(Succeed()) 263 Expect(err.Error()).Should(ContainSubstring("status is not available")) 264 Expect(serviceReferences).Should(BeNil()) 265 266 By("update external service descriptor status to available") 267 Expect(testapps.ChangeObjStatus(&testCtx, externalServiceDescriptor, func() { 268 externalServiceDescriptor.Status.Phase = appsv1alpha1.AvailablePhase 269 })).Should(Succeed()) 270 271 By("GenServiceReferences failed because external service descriptor kind and version not match") 272 serviceReferences, err = GenServiceReferences(reqCtx, testCtx.Cli, cluster, &clusterDef.Spec.ComponentDefs[0], &cluster.Spec.ComponentSpecs[0]) 273 Expect(err).ShouldNot(Succeed()) 274 Expect(err.Error()).Should(ContainSubstring("kind or version is not match with")) 275 Expect(serviceReferences).Should(BeNil()) 276 277 By("update external service descriptor kind and version") 278 Expect(testapps.ChangeObj(&testCtx, externalServiceDescriptor, func(externalServiceDescriptor *appsv1alpha1.ServiceDescriptor) { 279 externalServiceDescriptor.Spec.ServiceKind = externalServiceDescriptorKind 280 externalServiceDescriptor.Spec.ServiceVersion = externalServiceDescriptorVersion 281 })).Should(Succeed()) 282 283 By("GenServiceReferences succeed because external service descriptor found and internal cluster reference found") 284 secret := &corev1.Secret{ 285 ObjectMeta: metav1.ObjectMeta{ 286 Name: secretName, 287 Namespace: namespace, 288 }, 289 Data: map[string][]byte{ 290 constant.ServiceDescriptorPasswordKey: []byte("NHpycWZsMnI="), 291 constant.ServiceDescriptorUsernameKey: []byte("cm9vdA=="), 292 constant.ServiceDescriptorEndpointKey: []byte("my-mysql-0.default.svc.cluster.local"), 293 constant.ServiceDescriptorPortKey: []byte("3306"), 294 }, 295 } 296 Expect(testCtx.CheckedCreateObj(ctx, secret)).Should(Succeed()) 297 Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: secret.Name, 298 Namespace: secret.Namespace}, secret)).Should(Succeed()) 299 serviceReferences, err = GenServiceReferences(reqCtx, testCtx.Cli, cluster, &clusterDef.Spec.ComponentDefs[0], &cluster.Spec.ComponentSpecs[0]) 300 Expect(err).Should(Succeed()) 301 Expect(serviceReferences).ShouldNot(BeNil()) 302 Expect(len(serviceReferences)).Should(Equal(2)) 303 Expect(serviceReferences[redisServiceRefDeclarationName]).ShouldNot(BeNil()) 304 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Endpoint).ShouldNot(BeNil()) 305 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Endpoint.Value).ShouldNot(BeEmpty()) 306 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Endpoint.ValueFrom).Should(BeNil()) 307 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Port).ShouldNot(BeNil()) 308 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Port.Value).ShouldNot(BeEmpty()) 309 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Port.ValueFrom).Should(BeNil()) 310 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Auth).ShouldNot(BeNil()) 311 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Auth.Username.Value).ShouldNot(BeEmpty()) 312 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Auth.Username.ValueFrom).Should(BeNil()) 313 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Auth.Password.Value).ShouldNot(BeEmpty()) 314 Expect(serviceReferences[redisServiceRefDeclarationName].Spec.Auth.Password.ValueFrom).Should(BeNil()) 315 316 Expect(serviceReferences[mysqlServiceRefDeclarationName]).ShouldNot(BeNil()) 317 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Endpoint).ShouldNot(BeNil()) 318 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Endpoint.Value).Should(BeEmpty()) 319 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Endpoint.ValueFrom).ShouldNot(BeNil()) 320 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Endpoint.ValueFrom.SecretKeyRef).ShouldNot(BeNil()) 321 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Port).ShouldNot(BeNil()) 322 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Port.Value).Should(BeEmpty()) 323 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Port.ValueFrom).ShouldNot(BeNil()) 324 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Port.ValueFrom.SecretKeyRef).ShouldNot(BeNil()) 325 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth).ShouldNot(BeNil()) 326 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Username.Value).Should(BeEmpty()) 327 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Username.ValueFrom).ShouldNot(BeNil()) 328 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Username.ValueFrom.SecretKeyRef).ShouldNot(BeNil()) 329 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Password.Value).Should(BeEmpty()) 330 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Password.ValueFrom).ShouldNot(BeNil()) 331 Expect(serviceReferences[mysqlServiceRefDeclarationName].Spec.Auth.Password.ValueFrom.SecretKeyRef).ShouldNot(BeNil()) 332 }) 333 }) 334 })