From 8526bf56cb978e1464551302550ed2940ee5edf9 Mon Sep 17 00:00:00 2001 From: free6om Date: Thu, 25 Apr 2024 19:59:36 +0800 Subject: [PATCH] chore: remove mock StatefulSet in pkg controllerutil&testutil (#7174) --- .../apps/operations/switchover_util.go | 7 +- pkg/controllerutil/pod_utils.go | 26 -- pkg/controllerutil/pod_utils_test.go | 23 +- pkg/controllerutil/stateful_set_utils.go | 88 ------- pkg/controllerutil/stateful_set_utils_test.go | 55 ----- pkg/testutil/apps/constant.go | 12 +- pkg/testutil/k8s/deployment_util.go | 74 ------ pkg/testutil/k8s/instance_set_util.go | 72 ------ pkg/testutil/k8s/pod_util.go | 80 +++++++ pkg/testutil/k8s/statefulset_util.go | 222 ------------------ pkg/testutil/type.go | 2 - 11 files changed, 91 insertions(+), 570 deletions(-) delete mode 100644 pkg/controllerutil/stateful_set_utils.go delete mode 100644 pkg/controllerutil/stateful_set_utils_test.go delete mode 100644 pkg/testutil/k8s/deployment_util.go create mode 100644 pkg/testutil/k8s/pod_util.go delete mode 100644 pkg/testutil/k8s/statefulset_util.go diff --git a/controllers/apps/operations/switchover_util.go b/controllers/apps/operations/switchover_util.go index d1b0ec7a37a..bdd8420c188 100644 --- a/controllers/apps/operations/switchover_util.go +++ b/controllers/apps/operations/switchover_util.go @@ -35,6 +35,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" + "github.com/apecloud/kubeblocks/pkg/controller/instanceset" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -90,9 +91,9 @@ func needDoSwitchover(ctx context.Context, if err != nil { return false, err } - podParent, _ := intctrlutil.ParseParentNameAndOrdinal(pod.Name) - siParent, o := intctrlutil.ParseParentNameAndOrdinal(switchover.InstanceName) - if podParent != siParent || o < 0 || o >= int32(len(podList.Items)) { + podParent, _ := instanceset.ParseParentNameAndOrdinal(pod.Name) + siParent, o := instanceset.ParseParentNameAndOrdinal(switchover.InstanceName) + if podParent != siParent || o < 0 || o >= len(podList.Items) { return false, errors.New("switchover.InstanceName is invalid") } // If the current instance is already the primary, then no switchover will be performed. diff --git a/pkg/controllerutil/pod_utils.go b/pkg/controllerutil/pod_utils.go index bdc95f6f1f6..3efaa16f5a8 100644 --- a/pkg/controllerutil/pod_utils.go +++ b/pkg/controllerutil/pod_utils.go @@ -21,7 +21,6 @@ package controllerutil import ( "fmt" - "regexp" "strconv" "strings" "time" @@ -44,26 +43,6 @@ const ( PodScheduledFailedTimeout = 30 * time.Second ) -// statefulPodRegex is a regular expression that extracts the parent StatefulSet and ordinal from the Name of a Pod -var statefulPodRegex = regexp.MustCompile("(.*)-([0-9]+)$") - -// GetParentNameAndOrdinal gets the name of pod's parent StatefulSet and pod's ordinal as extracted from its Name. If -// the Pod was not created by a StatefulSet, its parent is considered to be empty string, and its ordinal is considered -// to be -1. -func GetParentNameAndOrdinal(pod *corev1.Pod) (string, int) { - parent := "" - ordinal := -1 - subMatches := statefulPodRegex.FindStringSubmatch(pod.Name) - if len(subMatches) < 3 { - return parent, ordinal - } - parent = subMatches[1] - if i, err := strconv.ParseInt(subMatches[2], 10, 32); err == nil { - ordinal = int(i) - } - return parent, ordinal -} - // GetContainerByConfigSpec searches for container using the configmap of config from the pod // // e.g.: @@ -403,11 +382,6 @@ func PodIsReadyWithLabel(pod corev1.Pod) bool { return PodIsReady(&pod) } -// PodIsControlledByLatestRevision checks if the pod is controlled by latest controller revision. -func PodIsControlledByLatestRevision(pod *corev1.Pod, sts *appsv1.StatefulSet) bool { - return GetPodRevision(pod) == sts.Status.UpdateRevision && sts.Status.ObservedGeneration == sts.Generation -} - // GetPodRevision gets the revision of Pod by inspecting the StatefulSetRevisionLabel. If pod has no revision empty // string is returned. func GetPodRevision(pod *corev1.Pod) string { diff --git a/pkg/controllerutil/pod_utils_test.go b/pkg/controllerutil/pod_utils_test.go index ee2d8c673a7..f3c49eff992 100644 --- a/pkg/controllerutil/pod_utils_test.go +++ b/pkg/controllerutil/pod_utils_test.go @@ -49,8 +49,7 @@ type TestResourceUnit struct { } func TestPodIsReady(t *testing.T) { - set := testk8s.NewFakeStatefulSet("foo", 3) - pod := testk8s.NewFakeStatefulSetPod(set, 1) + pod := testk8s.NewFakePod("foo", 1) pod.Status.Conditions = []corev1.PodCondition{ { Type: corev1.PodReady, @@ -83,26 +82,8 @@ func TestPodIsReady(t *testing.T) { } } -func TestPodIsControlledByLatestRevision(t *testing.T) { - set := testk8s.NewFakeStatefulSet("foo", 3) - pod := testk8s.NewFakeStatefulSetPod(set, 1) - pod.Labels = map[string]string{ - appsv1.ControllerRevisionHashLabelKey: "test", - } - set.Generation = 1 - set.Status.UpdateRevision = "test" - if PodIsControlledByLatestRevision(pod, set) { - t.Errorf("PodIsControlledByLatestRevision returned false positive") - } - set.Status.ObservedGeneration = 1 - if !PodIsControlledByLatestRevision(pod, set) { - t.Errorf("PodIsControlledByLatestRevision returned false positive") - } -} - func TestGetPodRevision(t *testing.T) { - set := testk8s.NewFakeStatefulSet("foo", 3) - pod := testk8s.NewFakeStatefulSetPod(set, 1) + pod := testk8s.NewFakePod("foo", 1) if GetPodRevision(pod) != "" { t.Errorf("revision should be empty") } diff --git a/pkg/controllerutil/stateful_set_utils.go b/pkg/controllerutil/stateful_set_utils.go deleted file mode 100644 index d4150e6796a..00000000000 --- a/pkg/controllerutil/stateful_set_utils.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package controllerutil - -import ( - "context" - "regexp" - "strconv" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// statefulSetRegex is a regular expression that extracts StatefulSet's ordinal from the Name of StatefulSet -var statefulSetRegex = regexp.MustCompile("(.*)-([0-9]+)$") - -// getParentName gets the name of pod's parent StatefulSet. If pod has not parent, the empty string is returned. -func getParentName(pod *corev1.Pod) string { - parent, _ := GetParentNameAndOrdinal(pod) - return parent -} - -// isMemberOf tests if pod is a member of set. -func isMemberOf(set *appsv1.StatefulSet, pod *corev1.Pod) bool { - return getParentName(pod) == set.Name -} - -// ParseParentNameAndOrdinal gets the name of cluster-component and StatefulSet's ordinal as extracted from its Name. If -// the StatefulSet's Name was not match a statefulSetRegex, its parent is considered to be empty string, -// and its ordinal is considered to be -1. -func ParseParentNameAndOrdinal(s string) (string, int32) { - parent := "" - ordinal := int32(-1) - subMatches := statefulSetRegex.FindStringSubmatch(s) - if len(subMatches) < 3 { - return parent, ordinal - } - parent = subMatches[1] - if i, err := strconv.ParseInt(subMatches[2], 10, 32); err == nil { - ordinal = int32(i) - } - return parent, ordinal -} - -// GetPodListByStatefulSet gets statefulSet pod list. -func GetPodListByStatefulSet(ctx context.Context, cli client.Client, stsObj *appsv1.StatefulSet) ([]corev1.Pod, error) { - selector, err := metav1.LabelSelectorAsMap(stsObj.Spec.Selector) - if err != nil { - return nil, err - } - return getPodListByStatefulSetWithSelector(ctx, cli, stsObj, selector) -} - -// getPodListByStatefulSetWithSelector gets statefulSet pod list. -func getPodListByStatefulSetWithSelector(ctx context.Context, cli client.Client, stsObj *appsv1.StatefulSet, selector client.MatchingLabels) ([]corev1.Pod, error) { - podList := &corev1.PodList{} - if err := cli.List(ctx, podList, - &client.ListOptions{Namespace: stsObj.Namespace}, - selector); err != nil { - return nil, err - } - var pods []corev1.Pod - for _, pod := range podList.Items { - if isMemberOf(stsObj, &pod) { - pods = append(pods, pod) - } - } - return pods, nil -} diff --git a/pkg/controllerutil/stateful_set_utils_test.go b/pkg/controllerutil/stateful_set_utils_test.go deleted file mode 100644 index 238ef7bf083..00000000000 --- a/pkg/controllerutil/stateful_set_utils_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package controllerutil - -import ( - "testing" - - testk8s "github.com/apecloud/kubeblocks/pkg/testutil/k8s" -) - -func TestGetParentNameAndOrdinal(t *testing.T) { - set := testk8s.NewFakeStatefulSet("foo", 3) - pod := testk8s.NewFakeStatefulSetPod(set, 1) - if parent, ordinal := GetParentNameAndOrdinal(pod); parent != set.Name { - t.Errorf("Extracted the wrong parent name expected %s found %s", set.Name, parent) - } else if ordinal != 1 { - t.Errorf("Extracted the wrong ordinal expected %d found %d", 1, ordinal) - } - pod.Name = "1-bar" - if parent, ordinal := GetParentNameAndOrdinal(pod); parent != "" { - t.Error("Expected empty string for non-member Pod parent") - } else if ordinal != -1 { - t.Error("Expected -1 for non member Pod ordinal") - } -} - -func TestIsMemberOf(t *testing.T) { - set := testk8s.NewFakeStatefulSet("foo", 3) - set2 := testk8s.NewFakeStatefulSet("bar", 3) - set2.Name = "foo2" - pod := testk8s.NewFakeStatefulSetPod(set, 1) - if !isMemberOf(set, pod) { - t.Error("isMemberOf returned false negative") - } - if isMemberOf(set2, pod) { - t.Error("isMemberOf returned false positive") - } -} diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go index 7916feb0c29..d52923f677d 100644 --- a/pkg/testutil/apps/constant.go +++ b/pkg/testutil/apps/constant.go @@ -38,10 +38,9 @@ const ( ServiceVPCName = "vpc-lb" ServiceInternetName = "internet-lb" - ReplicationPodRoleVolume = "pod-role" - ReplicationRoleLabelFieldPath = "metadata.labels['kubeblocks.io/role']" - DefaultReplicationCandidateIndex = 0 - DefaultReplicationReplicas = 2 + ReplicationPodRoleVolume = "pod-role" + ReplicationRoleLabelFieldPath = "metadata.labels['kubeblocks.io/role']" + DefaultReplicationReplicas = 2 ApeCloudMySQLImage = "docker.io/apecloud/apecloud-mysql-server:latest" DefaultMySQLContainerName = "mysql" @@ -55,9 +54,8 @@ const ( DefaultRedisContainerName = "redis" DefaultRedisInitContainerName = "redis-init-container" - StorageClassName = "test-sc" - EnvKeyImageTag = "IMAGE_TAG" - DefaultImageTag = "test" + EnvKeyImageTag = "IMAGE_TAG" + DefaultImageTag = "test" DefaultConfigSpecName = "config-cm" DefaultConfigSpecTplRef = "env-from-config-tpl" diff --git a/pkg/testutil/k8s/deployment_util.go b/pkg/testutil/k8s/deployment_util.go deleted file mode 100644 index 64c97745a14..00000000000 --- a/pkg/testutil/k8s/deployment_util.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package testutil - -import ( - "fmt" - - "github.com/onsi/gomega" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/testutil" -) - -// MockDeploymentReady mocks deployment is ready -func MockDeploymentReady(deploy *appsv1.Deployment, rsAvailableReason, rsName string) { - deploy.Status.AvailableReplicas = *deploy.Spec.Replicas - deploy.Status.ReadyReplicas = *deploy.Spec.Replicas - deploy.Status.Replicas = *deploy.Spec.Replicas - deploy.Status.ObservedGeneration = deploy.Generation - deploy.Status.UpdatedReplicas = *deploy.Spec.Replicas - deploy.Status.Conditions = []appsv1.DeploymentCondition{ - { - Type: appsv1.DeploymentProgressing, - Reason: rsAvailableReason, - Status: corev1.ConditionTrue, - Message: fmt.Sprintf(`ReplicaSet "%s" has successfully progressed.`, rsName), - }, - } -} - -// MockPodAvailable mocks pod is available -func MockPodAvailable(pod *corev1.Pod, lastTransitionTime metav1.Time) { - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: corev1.ConditionTrue, - LastTransitionTime: lastTransitionTime, - }, - } -} - -func ListAndCheckDeployment(testCtx *testutil.TestContext, key types.NamespacedName) *appsv1.DeploymentList { - deployList := &appsv1.DeploymentList{} - gomega.Eventually(func(g gomega.Gomega) { - g.Expect(testCtx.Cli.List(testCtx.Ctx, deployList, client.MatchingLabels{ - constant.AppInstanceLabelKey: key.Name, - }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) - g.Expect(deployList.Items).ShouldNot(gomega.BeNil()) - g.Expect(deployList.Items).ShouldNot(gomega.BeEmpty()) - }).Should(gomega.Succeed()) - return deployList -} diff --git a/pkg/testutil/k8s/instance_set_util.go b/pkg/testutil/k8s/instance_set_util.go index d5da501dfbc..f7cff71dde0 100644 --- a/pkg/testutil/k8s/instance_set_util.go +++ b/pkg/testutil/k8s/instance_set_util.go @@ -20,70 +20,18 @@ along with this program. If not, see . package testutil import ( - "fmt" - "reflect" "strings" "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/testutil" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" ) -// NewFakeInstanceSet creates a fake ITS workload object for testing. -func NewFakeInstanceSet(name string, replicas int) *workloads.InstanceSet { - template := corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - } - - template.Labels = map[string]string{"foo": "bar"} - itsReplicas := int32(replicas) - Revision := name + "-d5df5b8d6" - return &workloads.InstanceSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: corev1.NamespaceDefault, - }, - Spec: workloads.InstanceSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"foo": "bar"}, - }, - Replicas: &itsReplicas, - Template: template, - ServiceName: "governingsvc", - }, - Status: workloads.InstanceSetStatus{ - InitReplicas: itsReplicas, - AvailableReplicas: itsReplicas, - ObservedGeneration: 0, - ReadyReplicas: itsReplicas, - UpdatedReplicas: itsReplicas, - CurrentRevision: Revision, - UpdateRevision: Revision, - }, - } -} - -// NewFakeInstanceSetPod creates a fake pod of the ITS workload for testing. -func NewFakeInstanceSetPod(its *workloads.InstanceSet, ordinal int) *corev1.Pod { - pod := &corev1.Pod{} - pod.Name = fmt.Sprintf("%s-%d", its.Name, ordinal) - return pod -} - // MockInstanceSetReady mocks the ITS workload to ready state. func MockInstanceSetReady(its *workloads.InstanceSet, pods ...*corev1.Pod) { its.Status.InitReplicas = *its.Spec.Replicas @@ -154,23 +102,3 @@ func ListAndCheckInstanceSetWithComponent(testCtx *testutil.TestContext, key typ }).Should(gomega.Succeed()) return itsList } - -func PatchInstanceSetStatus(testCtx *testutil.TestContext, stsName string, status workloads.InstanceSetStatus) { - objectKey := client.ObjectKey{Name: stsName, Namespace: testCtx.DefaultNamespace} - gomega.Expect(testapps.GetAndChangeObjStatus(testCtx, objectKey, func(newITS *workloads.InstanceSet) { - newITS.Status = status - })()).Should(gomega.Succeed()) - gomega.Eventually(testapps.CheckObj(testCtx, objectKey, func(g gomega.Gomega, newITS *workloads.InstanceSet) { - g.Expect(reflect.DeepEqual(newITS.Status, status)).Should(gomega.BeTrue()) - })).Should(gomega.Succeed()) -} - -func InitInstanceSetStatus(testCtx testutil.TestContext, its *workloads.InstanceSet, controllerRevision string) { - gomega.Expect(testapps.ChangeObjStatus(&testCtx, its, func() { - its.Status.InitReplicas = *its.Spec.Replicas - its.Status.Replicas = *its.Spec.Replicas - its.Status.UpdateRevision = controllerRevision - its.Status.CurrentRevision = controllerRevision - its.Status.ObservedGeneration = its.Generation - })).Should(gomega.Succeed()) -} diff --git a/pkg/testutil/k8s/pod_util.go b/pkg/testutil/k8s/pod_util.go new file mode 100644 index 00000000000..ee54389a2c0 --- /dev/null +++ b/pkg/testutil/k8s/pod_util.go @@ -0,0 +1,80 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package testutil + +import ( + "context" + "fmt" + + "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/apecloud/kubeblocks/pkg/testutil" +) + +const ( + testFinalizer = "test.kubeblocks.io/finalizer" +) + +// NewFakePod creates a fake pod of the StatefulSet workload for testing. +func NewFakePod(parentName string, ordinal int) *corev1.Pod { + pod := &corev1.Pod{} + pod.Name = fmt.Sprintf("%s-%d", parentName, ordinal) + return pod +} + +// MockPodAvailable mocks pod is available +func MockPodAvailable(pod *corev1.Pod, lastTransitionTime metav1.Time) { + pod.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + LastTransitionTime: lastTransitionTime, + }, + } +} + +// MockPodIsTerminating mocks pod is terminating. +func MockPodIsTerminating(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { + patch := client.MergeFrom(pod.DeepCopy()) + pod.Finalizers = []string{testFinalizer} + gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) + gomega.Expect(testCtx.Cli.Delete(ctx, pod)).Should(gomega.Succeed()) + gomega.Eventually(func(g gomega.Gomega) { + tmpPod := &corev1.Pod{} + _ = testCtx.Cli.Get(context.Background(), + client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, tmpPod) + g.Expect(!tmpPod.DeletionTimestamp.IsZero()).Should(gomega.BeTrue()) + }).Should(gomega.Succeed()) +} + +// RemovePodFinalizer removes the pod finalizer to delete the pod finally. +func RemovePodFinalizer(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { + patch := client.MergeFrom(pod.DeepCopy()) + pod.Finalizers = []string{} + gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) + gomega.Eventually(func() error { + return testCtx.Cli.Get(context.Background(), + client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, &corev1.Pod{}) + }).Should(gomega.Satisfy(apierrors.IsNotFound)) +} diff --git a/pkg/testutil/k8s/statefulset_util.go b/pkg/testutil/k8s/statefulset_util.go deleted file mode 100644 index b95ef3be585..00000000000 --- a/pkg/testutil/k8s/statefulset_util.go +++ /dev/null @@ -1,222 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package testutil - -import ( - "context" - "fmt" - "reflect" - - "github.com/onsi/gomega" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/testutil" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" -) - -const ( - testFinalizer = "test.kubeblocks.io/finalizer" -) - -// NewFakeStatefulSet creates a fake StatefulSet workload object for testing. -func NewFakeStatefulSet(name string, replicas int) *appsv1.StatefulSet { - template := corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - } - - template.Labels = map[string]string{"foo": "bar"} - statefulSetReplicas := int32(replicas) - Revision := name + "-d5df5b8d6" - return &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: corev1.NamespaceDefault, - }, - Spec: appsv1.StatefulSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"foo": "bar"}, - }, - Replicas: &statefulSetReplicas, - Template: template, - ServiceName: "governingsvc", - }, - Status: appsv1.StatefulSetStatus{ - AvailableReplicas: statefulSetReplicas, - ObservedGeneration: 0, - ReadyReplicas: statefulSetReplicas, - UpdatedReplicas: statefulSetReplicas, - CurrentRevision: Revision, - UpdateRevision: Revision, - }, - } -} - -// NewFakeStatefulSetPod creates a fake pod of the StatefulSet workload for testing. -func NewFakeStatefulSetPod(set *appsv1.StatefulSet, ordinal int) *corev1.Pod { - pod := &corev1.Pod{} - pod.Name = fmt.Sprintf("%s-%d", set.Name, ordinal) - return pod -} - -// MockStatefulSetReady mocks the StatefulSet workload is ready. -func MockStatefulSetReady(sts *appsv1.StatefulSet) { - sts.Status.AvailableReplicas = *sts.Spec.Replicas - sts.Status.ObservedGeneration = sts.Generation - sts.Status.Replicas = *sts.Spec.Replicas - sts.Status.ReadyReplicas = *sts.Spec.Replicas - sts.Status.CurrentRevision = sts.Status.UpdateRevision -} - -// DeletePodLabelKey deletes the specified label of the pod. -func DeletePodLabelKey(ctx context.Context, testCtx testutil.TestContext, podName, labelKey string) { - pod := &corev1.Pod{} - gomega.Expect(testCtx.Cli.Get(ctx, client.ObjectKey{Name: podName, Namespace: testCtx.DefaultNamespace}, pod)).Should(gomega.Succeed()) - if pod.Labels == nil { - return - } - patch := client.MergeFrom(pod.DeepCopy()) - delete(pod.Labels, labelKey) - gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) - gomega.Eventually(func(g gomega.Gomega) { - tmpPod := &corev1.Pod{} - _ = testCtx.Cli.Get(context.Background(), client.ObjectKey{Name: podName, Namespace: testCtx.DefaultNamespace}, tmpPod) - g.Expect(tmpPod.Labels == nil || tmpPod.Labels[labelKey] == "").Should(gomega.BeTrue()) - }).Should(gomega.Succeed()) -} - -// UpdatePodStatusScheduleFailed updates the pod status to mock the schedule failure. -func UpdatePodStatusScheduleFailed(ctx context.Context, testCtx testutil.TestContext, podName, namespace string) { - pod := &corev1.Pod{} - gomega.Expect(testCtx.Cli.Get(ctx, client.ObjectKey{Name: podName, Namespace: namespace}, pod)).Should(gomega.Succeed()) - patch := client.MergeFrom(pod.DeepCopy()) - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodScheduled, - Status: corev1.ConditionFalse, - Message: "0/1 node cpu Insufficient", - Reason: "Unschedulable", - }, - } - gomega.Expect(testCtx.Cli.Status().Patch(ctx, pod, patch)).Should(gomega.Succeed()) -} - -// MockPodIsTerminating mocks pod is terminating. -func MockPodIsTerminating(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { - patch := client.MergeFrom(pod.DeepCopy()) - pod.Finalizers = []string{testFinalizer} - gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) - gomega.Expect(testCtx.Cli.Delete(ctx, pod)).Should(gomega.Succeed()) - gomega.Eventually(func(g gomega.Gomega) { - tmpPod := &corev1.Pod{} - _ = testCtx.Cli.Get(context.Background(), - client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, tmpPod) - g.Expect(!tmpPod.DeletionTimestamp.IsZero()).Should(gomega.BeTrue()) - }).Should(gomega.Succeed()) -} - -// RemovePodFinalizer removes the pod finalizer to delete the pod finally. -func RemovePodFinalizer(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { - patch := client.MergeFrom(pod.DeepCopy()) - pod.Finalizers = []string{} - gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) - gomega.Eventually(func() error { - return testCtx.Cli.Get(context.Background(), - client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, &corev1.Pod{}) - }).Should(gomega.Satisfy(apierrors.IsNotFound)) -} - -func ListAndCheckStatefulSet(testCtx *testutil.TestContext, key types.NamespacedName) *appsv1.StatefulSetList { - stsList := &appsv1.StatefulSetList{} - gomega.Eventually(func(g gomega.Gomega) { - g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ - constant.AppInstanceLabelKey: key.Name, - }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) - g.Expect(stsList.Items).ShouldNot(gomega.BeNil()) - g.Expect(stsList.Items).ShouldNot(gomega.BeEmpty()) - }).Should(gomega.Succeed()) - return stsList -} - -func ListAndCheckStatefulSetItemsCount(testCtx *testutil.TestContext, key types.NamespacedName, cnt int) *appsv1.StatefulSetList { - stsList := &appsv1.StatefulSetList{} - gomega.Eventually(func(g gomega.Gomega) { - g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ - constant.AppInstanceLabelKey: key.Name, - }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) - g.Expect(len(stsList.Items)).Should(gomega.Equal(cnt)) - }).Should(gomega.Succeed()) - return stsList -} - -func ListAndCheckStatefulSetWithComponent(testCtx *testutil.TestContext, key types.NamespacedName, componentName string) *appsv1.StatefulSetList { - stsList := &appsv1.StatefulSetList{} - gomega.Eventually(func(g gomega.Gomega) { - g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ - constant.AppInstanceLabelKey: key.Name, - constant.KBAppComponentLabelKey: componentName, - }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) - g.Expect(stsList.Items).ShouldNot(gomega.BeNil()) - g.Expect(stsList.Items).ShouldNot(gomega.BeEmpty()) - }).Should(gomega.Succeed()) - return stsList -} - -func ListAndCheckPodCountWithComponent(testCtx *testutil.TestContext, key types.NamespacedName, componentName string, cnt int) *corev1.PodList { - podList := &corev1.PodList{} - gomega.Eventually(func(g gomega.Gomega) { - g.Expect(testCtx.Cli.List(testCtx.Ctx, podList, client.MatchingLabels{ - constant.AppInstanceLabelKey: key.Name, - constant.KBAppComponentLabelKey: componentName, - }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) - g.Expect(len(podList.Items)).Should(gomega.Equal(cnt)) - }).Should(gomega.Succeed()) - return podList -} - -func PatchStatefulSetStatus(testCtx *testutil.TestContext, stsName string, status appsv1.StatefulSetStatus) { - objectKey := client.ObjectKey{Name: stsName, Namespace: testCtx.DefaultNamespace} - gomega.Expect(testapps.GetAndChangeObjStatus(testCtx, objectKey, func(newSts *appsv1.StatefulSet) { - newSts.Status = status - })()).Should(gomega.Succeed()) - gomega.Eventually(testapps.CheckObj(testCtx, objectKey, func(g gomega.Gomega, newSts *appsv1.StatefulSet) { - g.Expect(reflect.DeepEqual(newSts.Status, status)).Should(gomega.BeTrue()) - })).Should(gomega.Succeed()) -} - -func InitStatefulSetStatus(testCtx testutil.TestContext, statefulset *appsv1.StatefulSet, controllerRevision string) { - gomega.Expect(testapps.ChangeObjStatus(&testCtx, statefulset, func() { - statefulset.Status.UpdateRevision = controllerRevision - statefulset.Status.CurrentRevision = controllerRevision - statefulset.Status.ObservedGeneration = statefulset.Generation - })).Should(gomega.Succeed()) -} diff --git a/pkg/testutil/type.go b/pkg/testutil/type.go index 5d52c6923e2..44cc7bf1ab0 100644 --- a/pkg/testutil/type.go +++ b/pkg/testutil/type.go @@ -54,8 +54,6 @@ type TestContext struct { CheckedCreateObj func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error } -var ErrUninitError = fmt.Errorf("cli uninitialized error") - const ( envExistingClusterType = "EXISTING_CLUSTER_TYPE" envUseExistingCluster = "USE_EXISTING_CLUSTER"