github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/component/component_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 component 21 22 import ( 23 "reflect" 24 "testing" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/util/intstr" 33 ctrl "sigs.k8s.io/controller-runtime" 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 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 39 viper "github.com/1aal/kubeblocks/pkg/viperx" 40 ) 41 42 var tlog = ctrl.Log.WithName("component_testing") 43 44 var _ = Describe("component module", func() { 45 46 Context("has the BuildComponent function", func() { 47 const ( 48 clusterDefName = "test-clusterdef" 49 clusterVersionName = "test-clusterversion" 50 clusterName = "test-cluster" 51 mysqlCompDefName = "replicasets" 52 mysqlCompName = "mysql" 53 proxyCompDefName = "proxy" 54 mysqlSecretUserEnvName = "MYSQL_ROOT_USER" 55 mysqlSecretPasswdEnvName = "MYSQL_ROOT_PASSWORD" 56 ) 57 58 var ( 59 clusterDef *appsv1alpha1.ClusterDefinition 60 clusterVersion *appsv1alpha1.ClusterVersion 61 cluster *appsv1alpha1.Cluster 62 ) 63 64 BeforeEach(func() { 65 clusterDef = testapps.NewClusterDefFactory(clusterDefName). 66 AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName). 67 AddComponentDef(testapps.StatelessNginxComponent, proxyCompDefName). 68 GetObject() 69 clusterVersion = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName). 70 AddComponentVersion(mysqlCompDefName). 71 AddContainerShort("mysql", testapps.ApeCloudMySQLImage). 72 AddComponentVersion(proxyCompDefName). 73 AddInitContainerShort("nginx-init", testapps.NginxImage). 74 AddContainerShort("nginx", testapps.NginxImage). 75 GetObject() 76 pvcSpec := testapps.NewPVCSpec("1Gi") 77 cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, 78 clusterDef.Name, clusterVersion.Name). 79 AddComponent(mysqlCompName, mysqlCompDefName). 80 AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec). 81 GetObject() 82 }) 83 84 It("should work as expected with various inputs", func() { 85 By("assign every available fields") 86 reqCtx := intctrlutil.RequestCtx{ 87 Ctx: ctx, 88 Log: tlog, 89 } 90 component, err := BuildComponent( 91 reqCtx, 92 nil, 93 cluster, 94 clusterDef, 95 &clusterDef.Spec.ComponentDefs[0], 96 &cluster.Spec.ComponentSpecs[0], 97 nil, 98 &clusterVersion.Spec.ComponentVersions[0]) 99 Expect(err).Should(Succeed()) 100 Expect(component).ShouldNot(BeNil()) 101 102 By("leave clusterVersion.versionCtx empty initContains and containers") 103 clusterVersion.Spec.ComponentVersions[0].VersionsCtx.Containers = nil 104 clusterVersion.Spec.ComponentVersions[0].VersionsCtx.InitContainers = nil 105 component, err = BuildComponent( 106 reqCtx, 107 nil, 108 cluster, 109 clusterDef, 110 &clusterDef.Spec.ComponentDefs[0], 111 &cluster.Spec.ComponentSpecs[0], 112 nil, 113 &clusterVersion.Spec.ComponentVersions[0]) 114 Expect(err).Should(Succeed()) 115 Expect(component).ShouldNot(BeNil()) 116 117 By("new container in clusterVersion not in clusterDefinition") 118 component, err = BuildComponent( 119 reqCtx, 120 nil, 121 cluster, 122 clusterDef, 123 &clusterDef.Spec.ComponentDefs[0], 124 &cluster.Spec.ComponentSpecs[0], 125 nil, 126 &clusterVersion.Spec.ComponentVersions[1]) 127 Expect(err).Should(Succeed()) 128 Expect(len(component.PodSpec.Containers) >= 3).Should(BeTrue()) 129 130 By("new init container in clusterVersion not in clusterDefinition") 131 component, err = BuildComponent( 132 reqCtx, 133 nil, 134 cluster, 135 clusterDef, 136 &clusterDef.Spec.ComponentDefs[0], 137 &cluster.Spec.ComponentSpecs[0], 138 nil, 139 &clusterVersion.Spec.ComponentVersions[1]) 140 Expect(err).Should(Succeed()) 141 Expect(len(component.PodSpec.InitContainers)).Should(Equal(1)) 142 }) 143 144 It("should auto fill first component if it's empty", func() { 145 reqCtx := intctrlutil.RequestCtx{ 146 Ctx: ctx, 147 Log: tlog, 148 } 149 150 By("fill simplified fields") 151 r := int32(3) 152 cluster.Spec.Replicas = &r 153 cluster.Spec.Resources.CPU = resource.MustParse("1000m") 154 cluster.Spec.Resources.Memory = resource.MustParse("2Gi") 155 cluster.Spec.Storage.Size = resource.MustParse("20Gi") 156 157 By("clear cluster's component spec") 158 cluster.Spec.ComponentSpecs = nil 159 160 By("build first component from simplified fields") 161 component, err := buildComponent( 162 reqCtx, 163 nil, 164 cluster, 165 clusterDef, 166 &clusterDef.Spec.ComponentDefs[0], 167 nil, 168 nil, 169 &clusterVersion.Spec.ComponentVersions[0]) 170 Expect(err).Should(Succeed()) 171 Expect(component).ShouldNot(BeNil()) 172 Expect(component.Replicas).Should(Equal(*cluster.Spec.Replicas)) 173 Expect(component.VolumeClaimTemplates[0].Spec.Resources.Requests["storage"]).Should(Equal(cluster.Spec.Storage.Size)) 174 175 By("build second component will be nil") 176 component, err = buildComponent( 177 reqCtx, 178 nil, 179 cluster, 180 clusterDef, 181 &clusterDef.Spec.ComponentDefs[1], 182 nil, 183 nil, 184 &clusterVersion.Spec.ComponentVersions[0]) 185 Expect(err).Should(Succeed()) 186 Expect(component).Should(BeNil()) 187 }) 188 189 It("build affinity correctly", func() { 190 reqCtx := intctrlutil.RequestCtx{ 191 Ctx: ctx, 192 Log: tlog, 193 } 194 By("fill affinity") 195 cluster.Spec.AvailabilityPolicy = appsv1alpha1.AvailabilityPolicyZone 196 cluster.Spec.Tenancy = appsv1alpha1.DedicatedNode 197 By("clear cluster's component spec") 198 cluster.Spec.ComponentSpecs = nil 199 By("call build") 200 component, err := buildComponent( 201 reqCtx, 202 nil, 203 cluster, 204 clusterDef, 205 &clusterDef.Spec.ComponentDefs[0], 206 nil, 207 nil, 208 &clusterVersion.Spec.ComponentVersions[0]) 209 Expect(err).Should(Succeed()) 210 Expect(component).ShouldNot(BeNil()) 211 Expect(component.PodSpec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey).Should(Equal("topology.kubernetes.io/zone")) 212 Expect(component.PodSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey).Should(Equal("kubernetes.io/hostname")) 213 }) 214 215 It("build monitor correctly", func() { 216 reqCtx := intctrlutil.RequestCtx{ 217 Ctx: ctx, 218 Log: tlog, 219 } 220 By("enable monitor config in clusterdefinition") 221 clusterDef.Spec.ComponentDefs[0].Monitor = &appsv1alpha1.MonitorConfig{ 222 BuiltIn: true, 223 } 224 By("fill monitor") 225 interval := intstr.Parse("0") 226 cluster.Spec.Monitor.MonitoringInterval = &interval 227 By("clear cluster's component spec") 228 cluster.Spec.ComponentSpecs = nil 229 By("call build") 230 component, err := buildComponent( 231 reqCtx, 232 nil, 233 cluster, 234 clusterDef, 235 &clusterDef.Spec.ComponentDefs[0], 236 nil, 237 nil, 238 &clusterVersion.Spec.ComponentVersions[0]) 239 Expect(err).Should(Succeed()) 240 Expect(component).ShouldNot(BeNil()) 241 Expect(component.Monitor.Enable).Should(Equal(false)) 242 By("set monitor interval to 10s") 243 interval2 := intstr.Parse("10s") 244 cluster.Spec.Monitor.MonitoringInterval = &interval2 245 By("call build") 246 component, err = buildComponent( 247 reqCtx, 248 nil, 249 cluster, 250 clusterDef, 251 &clusterDef.Spec.ComponentDefs[0], 252 nil, 253 nil, 254 &clusterVersion.Spec.ComponentVersions[0]) 255 Expect(err).Should(Succeed()) 256 Expect(component).ShouldNot(BeNil()) 257 Expect(component.Monitor.Enable).Should(Equal(true)) 258 }) 259 260 It("build network correctly", func() { 261 reqCtx := intctrlutil.RequestCtx{ 262 Ctx: ctx, 263 Log: tlog, 264 } 265 By("setup cloud provider") 266 viper.Set(constant.CfgKeyServerInfo, "v1.26.5-gke.1200") 267 By("fill network") 268 cluster.Spec.Network = &appsv1alpha1.ClusterNetwork{ 269 HostNetworkAccessible: true, 270 PubliclyAccessible: false, 271 } 272 By("clear cluster's component spec") 273 cluster.Spec.ComponentSpecs = nil 274 By("call build") 275 component, err := buildComponent( 276 reqCtx, 277 nil, 278 cluster, 279 clusterDef, 280 &clusterDef.Spec.ComponentDefs[0], 281 nil, 282 nil, 283 &clusterVersion.Spec.ComponentVersions[0]) 284 Expect(err).Should(Succeed()) 285 Expect(component).ShouldNot(BeNil()) 286 Expect(component.Services[1].Name).Should(Equal("vpc")) 287 Expect(component.Services[1].Annotations["networking.gke.io/load-balancer-type"]).Should(Equal("Internal")) 288 Expect(component.Services[1].Spec.Type).Should(BeEquivalentTo("LoadBalancer")) 289 }) 290 291 It("Test replace secretRef env placeholder token", func() { 292 By("mock connect credential and do replace placeholder token") 293 credentialMap := GetEnvReplacementMapForConnCredential(cluster.Name) 294 mockEnvs := []corev1.EnvVar{ 295 { 296 Name: mysqlSecretUserEnvName, 297 ValueFrom: &corev1.EnvVarSource{ 298 SecretKeyRef: &corev1.SecretKeySelector{ 299 Key: "username", 300 LocalObjectReference: corev1.LocalObjectReference{ 301 Name: constant.KBConnCredentialPlaceHolder, 302 }, 303 }, 304 }, 305 }, 306 { 307 Name: mysqlSecretPasswdEnvName, 308 ValueFrom: &corev1.EnvVarSource{ 309 SecretKeyRef: &corev1.SecretKeySelector{ 310 Key: "password", 311 LocalObjectReference: corev1.LocalObjectReference{ 312 Name: constant.KBConnCredentialPlaceHolder, 313 }, 314 }, 315 }, 316 }, 317 } 318 mockEnvs = ReplaceSecretEnvVars(credentialMap, mockEnvs) 319 Expect(len(mockEnvs)).Should(Equal(2)) 320 for _, env := range mockEnvs { 321 Expect(env.ValueFrom).ShouldNot(BeNil()) 322 Expect(env.ValueFrom.SecretKeyRef).ShouldNot(BeNil()) 323 Expect(env.ValueFrom.SecretKeyRef.Name).Should(Equal(GenerateConnCredential(cluster.Name))) 324 } 325 }) 326 327 It("should not fill component if none of simplified api is present", func() { 328 reqCtx := intctrlutil.RequestCtx{ 329 Ctx: ctx, 330 Log: tlog, 331 } 332 By("clear cluster's component spec") 333 cluster.Spec.ComponentSpecs = nil 334 By("call build") 335 component, err := buildComponent( 336 reqCtx, 337 nil, 338 cluster, 339 clusterDef, 340 &clusterDef.Spec.ComponentDefs[0], 341 nil, 342 nil, 343 &clusterVersion.Spec.ComponentVersions[0]) 344 Expect(err).Should(Succeed()) 345 Expect(component).Should(BeNil()) 346 }) 347 348 It("build serviceReference correctly", func() { 349 reqCtx := intctrlutil.RequestCtx{ 350 Ctx: ctx, 351 Log: tlog, 352 } 353 const ( 354 name = "nginx" 355 ns = "default" 356 kind = "mock-kind" 357 version = "mock-version" 358 ) 359 By("generate serviceReference") 360 serviceDescriptor := &appsv1alpha1.ServiceDescriptor{ 361 ObjectMeta: metav1.ObjectMeta{ 362 Name: name, 363 Namespace: ns, 364 }, 365 Spec: appsv1alpha1.ServiceDescriptorSpec{ 366 ServiceKind: kind, 367 ServiceVersion: version, 368 }, 369 } 370 serviceReferenceMap := map[string]*appsv1alpha1.ServiceDescriptor{ 371 testapps.NginxImage: serviceDescriptor, 372 } 373 By("call build") 374 component, err := buildComponent( 375 reqCtx, 376 nil, 377 cluster, 378 clusterDef, 379 &clusterDef.Spec.ComponentDefs[0], 380 &cluster.Spec.ComponentSpecs[0], 381 serviceReferenceMap, 382 &clusterVersion.Spec.ComponentVersions[0]) 383 Expect(err).Should(Succeed()) 384 Expect(component).ShouldNot(BeNil()) 385 Expect(component.ServiceReferences).ShouldNot(BeNil()) 386 Expect(component.ServiceReferences[testapps.NginxImage].Name).Should(Equal(name)) 387 Expect(component.ServiceReferences[testapps.NginxImage].Spec.ServiceKind).Should(Equal(kind)) 388 Expect(component.ServiceReferences[testapps.NginxImage].Spec.ServiceVersion).Should(Equal(version)) 389 }) 390 }) 391 }) 392 393 func TestGetConfigSpecByName(t *testing.T) { 394 type args struct { 395 component *SynthesizedComponent 396 configSpec string 397 } 398 tests := []struct { 399 name string 400 args args 401 want *appsv1alpha1.ComponentConfigSpec 402 }{{ 403 name: "test", 404 args: args{ 405 component: &SynthesizedComponent{}, 406 configSpec: "for_test", 407 }, 408 want: nil, 409 }, { 410 name: "test", 411 args: args{ 412 component: &SynthesizedComponent{ 413 ConfigTemplates: []appsv1alpha1.ComponentConfigSpec{{ 414 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 415 Name: "test", 416 }}}, 417 }, 418 configSpec: "for-test", 419 }, 420 want: nil, 421 }, { 422 name: "test", 423 args: args{ 424 component: &SynthesizedComponent{ 425 ConfigTemplates: []appsv1alpha1.ComponentConfigSpec{{ 426 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 427 Name: "for-test", 428 }}}, 429 }, 430 configSpec: "for-test", 431 }, 432 want: &appsv1alpha1.ComponentConfigSpec{ 433 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 434 Name: "for-test", 435 }}, 436 }} 437 for _, tt := range tests { 438 t.Run(tt.name, func(t *testing.T) { 439 if got := GetConfigSpecByName(tt.args.component, tt.args.configSpec); !reflect.DeepEqual(got, tt.want) { 440 t.Errorf("GetConfigSpecByName() = %v, want %v", got, tt.want) 441 } 442 }) 443 } 444 }