github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/apis/apps/v1alpha1/cluster_webhook_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1alpha1
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	"k8s.io/apimachinery/pkg/util/yaml"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  )
    32  
    33  var _ = Describe("cluster webhook", func() {
    34  	var (
    35  		randomStr               string
    36  		clusterName             string
    37  		clusterDefinitionName   string
    38  		secondClusterDefinition string
    39  		clusterVersionName      string
    40  	)
    41  
    42  	initParams := func() {
    43  		randomStr = testCtx.GetRandomStr()
    44  		clusterName = "cluster-webhook-mysql-" + randomStr
    45  		clusterDefinitionName = "cluster-webhook-mysql-definition-" + randomStr
    46  		secondClusterDefinition = "cluster-webhook-mysql-definition2-" + randomStr
    47  		clusterVersionName = "cluster-webhook-mysql-clusterversion-" + randomStr
    48  	}
    49  	cleanupObjects := func() {
    50  		// Add any setup steps that needs to be executed before each test
    51  		err := k8sClient.DeleteAllOf(ctx, &Cluster{}, client.InNamespace(testCtx.DefaultNamespace), client.HasLabels{testCtx.TestObjLabelKey})
    52  		Expect(err).NotTo(HaveOccurred())
    53  		err = k8sClient.DeleteAllOf(ctx, &ClusterVersion{}, client.HasLabels{testCtx.TestObjLabelKey})
    54  		Expect(err).NotTo(HaveOccurred())
    55  		err = k8sClient.DeleteAllOf(ctx, &ClusterDefinition{}, client.HasLabels{testCtx.TestObjLabelKey})
    56  		Expect(err).NotTo(HaveOccurred())
    57  	}
    58  	BeforeEach(func() {
    59  		initParams()
    60  		cleanupObjects()
    61  	})
    62  
    63  	AfterEach(func() {
    64  		cleanupObjects()
    65  	})
    66  
    67  	Context("When cluster create and update", func() {
    68  		It("Should webhook validate passed", func() {
    69  			By("By testing creating a new clusterDefinition when no clusterVersion and clusterDefinition")
    70  			cluster, _ := createTestCluster(clusterDefinitionName, clusterVersionName, clusterName)
    71  			Expect(testCtx.CreateObj(ctx, cluster).Error()).To(ContainSubstring("not found"))
    72  
    73  			By("By creating a new clusterDefinition")
    74  			clusterDef, _ := createTestClusterDefinitionObj(clusterDefinitionName)
    75  			Expect(testCtx.CreateObj(ctx, clusterDef)).Should(Succeed())
    76  
    77  			clusterDefSecond, _ := createTestClusterDefinitionObj(secondClusterDefinition)
    78  			Expect(testCtx.CreateObj(ctx, clusterDefSecond)).Should(Succeed())
    79  
    80  			// wait until ClusterDefinition created
    81  			Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterDefinitionName}, clusterDef)).Should(Succeed())
    82  
    83  			By("By creating a new clusterVersion")
    84  			clusterVersion := createTestClusterVersionObj(clusterDefinitionName, clusterVersionName)
    85  			Expect(testCtx.CreateObj(ctx, clusterVersion)).Should(Succeed())
    86  			// wait until ClusterVersion created
    87  			Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterVersionName}, clusterVersion)).Should(Succeed())
    88  
    89  			By("By creating a new Cluster")
    90  			cluster, _ = createTestCluster(clusterDefinitionName, clusterVersionName, clusterName)
    91  			Expect(testCtx.CreateObj(ctx, cluster)).Should(Succeed())
    92  
    93  			By("By testing update spec.clusterDefinitionRef")
    94  			patch := client.MergeFrom(cluster.DeepCopy())
    95  			cluster.Spec.ClusterDefRef = secondClusterDefinition
    96  			Expect(k8sClient.Patch(ctx, cluster, patch).Error()).To(ContainSubstring("spec.clusterDefinitionRef"))
    97  			// restore
    98  			cluster.Spec.ClusterDefRef = clusterDefinitionName
    99  
   100  			By("By testing spec.components[?].type not found in clusterDefinitionRef")
   101  			patch = client.MergeFrom(cluster.DeepCopy())
   102  			cluster.Spec.ComponentSpecs[0].ComponentDefRef = "replicaset"
   103  			Expect(k8sClient.Patch(ctx, cluster, patch).Error()).To(ContainSubstring("componentDefRef is immutable"))
   104  			// restore
   105  			cluster.Spec.ComponentSpecs[0].ComponentDefRef = "replicasets"
   106  
   107  			// restore
   108  			cluster.Spec.ComponentSpecs[0].Name = "replicasets"
   109  
   110  			By("By updating spec.components[?].volumeClaimTemplates storage size, expect succeed")
   111  			patch = client.MergeFrom(cluster.DeepCopy())
   112  			cluster.Spec.ComponentSpecs[0].VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = resource.MustParse("2Gi")
   113  			Expect(k8sClient.Patch(ctx, cluster, patch)).Should(Succeed())
   114  
   115  			By("By updating spec.components[?].volumeClaimTemplates[?].name, expect not succeed")
   116  			patch = client.MergeFrom(cluster.DeepCopy())
   117  			cluster.Spec.ComponentSpecs[0].VolumeClaimTemplates[0].Name = "test"
   118  			Expect(k8sClient.Patch(ctx, cluster, patch).Error()).To(ContainSubstring("volumeClaimTemplates is forbidden modification except for storage size."))
   119  
   120  			By("By updating component resources")
   121  			// restore test volume claim template name to data
   122  			patch = client.MergeFrom(cluster.DeepCopy())
   123  			cluster.Spec.ComponentSpecs[0].VolumeClaimTemplates[0].Name = "data"
   124  			cluster.Spec.ComponentSpecs[0].Resources = corev1.ResourceRequirements{
   125  				Requests: corev1.ResourceList{
   126  					"cpu":    resource.MustParse("100m"),
   127  					"memory": resource.MustParse("200Mi"),
   128  				},
   129  			}
   130  			Expect(k8sClient.Patch(ctx, cluster, patch)).Should(Succeed())
   131  			patch = client.MergeFrom(cluster.DeepCopy())
   132  			cluster.Spec.ComponentSpecs[0].Resources = corev1.ResourceRequirements{
   133  				Requests: corev1.ResourceList{
   134  					"cpu":     resource.MustParse("100m"),
   135  					"memory1": resource.MustParse("200Mi"),
   136  				},
   137  			}
   138  			Expect(k8sClient.Patch(ctx, cluster, patch).Error()).To(ContainSubstring("resource key is not cpu or memory or hugepages- "))
   139  			patch = client.MergeFrom(cluster.DeepCopy())
   140  			cluster.Spec.ComponentSpecs[0].Resources = corev1.ResourceRequirements{
   141  				Requests: corev1.ResourceList{
   142  					"cpu":    resource.MustParse("100m"),
   143  					"memory": resource.MustParse("200Mi"),
   144  				},
   145  				Limits: corev1.ResourceList{
   146  					"cpu":    resource.MustParse("100m"),
   147  					"memory": resource.MustParse("100Mi"),
   148  				},
   149  			}
   150  			Expect(k8sClient.Patch(ctx, cluster, patch).Error()).To(ContainSubstring("must be less than or equal to memory limit"))
   151  			patch = client.MergeFrom(cluster.DeepCopy())
   152  			cluster.Spec.ComponentSpecs[0].Resources.Requests[corev1.ResourceMemory] = resource.MustParse("80Mi")
   153  			Expect(k8sClient.Patch(ctx, cluster, patch)).Should(Succeed())
   154  		})
   155  	})
   156  
   157  	Context("tls validation", func() {
   158  		BeforeEach(func() {
   159  			By("By creating a new clusterDefinition")
   160  			clusterDef, _ := createTestClusterDefinitionObj(clusterDefinitionName)
   161  			Expect(testCtx.CreateObj(ctx, clusterDef)).Should(Succeed())
   162  
   163  			// wait until ClusterDefinition created
   164  			Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterDefinitionName}, clusterDef)).Should(Succeed())
   165  
   166  			By("By creating a new clusterVersion")
   167  			clusterVersion := createTestClusterVersionObj(clusterDefinitionName, clusterVersionName)
   168  			Expect(testCtx.CreateObj(ctx, clusterVersion)).Should(Succeed())
   169  			// wait until ClusterVersion created
   170  			Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterVersionName}, clusterVersion)).Should(Succeed())
   171  		})
   172  		It("should assure tls fields setting properly", func() {
   173  			By("creating cluster with nil issuer")
   174  			cluster, _ := createTestCluster(clusterDefinitionName, clusterVersionName, clusterName)
   175  			cluster.Spec.ComponentSpecs[0].TLS = true
   176  			Expect(testCtx.CreateObj(ctx, cluster)).ShouldNot(Succeed())
   177  
   178  			By("creating cluster with nil secret ref")
   179  			cluster.Spec.ComponentSpecs[0].Issuer = &Issuer{Name: IssuerUserProvided}
   180  			Expect(testCtx.CreateObj(ctx, cluster)).ShouldNot(Succeed())
   181  
   182  			By("creating cluster with KubeBlocks issuer")
   183  			cluster.Spec.ComponentSpecs[0].Issuer = &Issuer{Name: IssuerKubeBlocks}
   184  			Expect(testCtx.CreateObj(ctx, cluster)).Should(Succeed())
   185  
   186  			By("creating cluster with UserProvided issuer and secret ref provided")
   187  			Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed())
   188  			err := k8sClient.Get(ctx, client.ObjectKeyFromObject(cluster), &Cluster{})
   189  			Expect(apierrors.IsNotFound(err)).Should(BeTrue())
   190  			cluster, _ = createTestCluster(clusterDefinitionName, clusterVersionName, clusterName)
   191  			cluster.Spec.ComponentSpecs[0].TLS = true
   192  			cluster.Spec.ComponentSpecs[0].Issuer = &Issuer{
   193  				Name: IssuerUserProvided,
   194  				SecretRef: &TLSSecretRef{
   195  					Name: "test-tls-secret",
   196  					CA:   "ca.crt",
   197  					Cert: "cert.crt",
   198  					Key:  "key.crt",
   199  				},
   200  			}
   201  			Expect(testCtx.CreateObj(ctx, cluster)).Should(Succeed())
   202  		})
   203  	})
   204  })
   205  
   206  func createTestCluster(clusterDefinitionName, clusterVersionName, clusterName string) (*Cluster, error) {
   207  	clusterYaml := fmt.Sprintf(`
   208  apiVersion: apps.kubeblocks.io/v1alpha1
   209  kind: Cluster
   210  metadata:
   211    name: %s
   212    namespace: default
   213  spec:
   214    clusterDefinitionRef: %s
   215    clusterVersionRef: %s
   216    componentSpecs:
   217    - name: replicasets
   218      componentDefRef: replicasets
   219      replicas: 1
   220      volumeClaimTemplates:
   221      - name: data
   222        spec:
   223          storageClassName: standard
   224          resources:
   225            requests:
   226              storage: 1Gi
   227    - name: proxy
   228      componentDefRef: proxy
   229      replicas: 1
   230  `, clusterName, clusterDefinitionName, clusterVersionName)
   231  	cluster := &Cluster{}
   232  	err := yaml.Unmarshal([]byte(clusterYaml), cluster)
   233  	cluster.Spec.TerminationPolicy = WipeOut
   234  	return cluster, err
   235  }