diff --git a/controllers/apps/components/component.go b/controllers/apps/components/component.go index 733c35f997e2..aa9c1f94cdc9 100644 --- a/controllers/apps/components/component.go +++ b/controllers/apps/components/component.go @@ -766,13 +766,13 @@ func (c *rsmComponent) expandVolume(reqCtx intctrlutil.RequestCtx, cli client.Cl func (c *rsmComponent) expandVolumes(reqCtx intctrlutil.RequestCtx, cli client.Client, vctName string, proto *corev1.PersistentVolumeClaimTemplate) error { - pvcNotFound := false for i := *c.runningWorkload.Spec.Replicas - 1; i >= 0; i-- { pvc := &corev1.PersistentVolumeClaim{} pvcKey := types.NamespacedName{ Namespace: c.GetNamespace(), Name: fmt.Sprintf("%s-%s-%d", vctName, c.runningWorkload.Name, i), } + pvcNotFound := false if err := cli.Get(reqCtx.Ctx, pvcKey, pvc); err != nil { if apierrors.IsNotFound(err) { pvcNotFound = true @@ -780,6 +780,18 @@ func (c *rsmComponent) expandVolumes(reqCtx intctrlutil.RequestCtx, cli client.C return err } } + + if !pvcNotFound { + quantity := pvc.Spec.Resources.Requests.Storage() + newQuantity := proto.Spec.Resources.Requests.Storage() + if quantity.Cmp(*pvc.Status.Capacity.Storage()) == 0 && newQuantity.Cmp(*quantity) < 0 { + errMsg := fmt.Sprintf("shrinking the volume is not supported, volume: %s, quantity: %s, new quantity: %s", + pvc.GetName(), quantity.String(), newQuantity.String()) + reqCtx.Event(c.Cluster, corev1.EventTypeWarning, "VolumeExpansionFailed", errMsg) + return fmt.Errorf("%s", errMsg) + } + } + if err := c.updatePVCSize(reqCtx, cli, pvcKey, pvc, pvcNotFound, proto); err != nil { return err } diff --git a/controllers/apps/components/component_test.go b/controllers/apps/components/component_test.go new file mode 100644 index 000000000000..c956d50e6a46 --- /dev/null +++ b/controllers/apps/components/component_test.go @@ -0,0 +1,405 @@ +/* +Copyright (C) 2022-2023 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 components + +import ( + "context" + "fmt" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + apiresource "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/internal/constant" + "github.com/apecloud/kubeblocks/internal/controller/graph" + ictrltypes "github.com/apecloud/kubeblocks/internal/controller/types" + intctrlutil "github.com/apecloud/kubeblocks/internal/controllerutil" + "github.com/apecloud/kubeblocks/internal/generics" + testapps "github.com/apecloud/kubeblocks/internal/testutil/apps" + testk8s "github.com/apecloud/kubeblocks/internal/testutil/k8s" +) + +var _ = Describe("Component", func() { + const ( + statefulCompName = "stateful" + statefulCompDefName = "stateful" + ) + + var ( + random = testCtx.GetRandomStr() + clusterDefName = "test-clusterdef-" + random + clusterVerName = "test-clusterver-" + random + clusterName = "test-cluster" + random + clusterDefObj *appsv1alpha1.ClusterDefinition + clusterVerObj *appsv1alpha1.ClusterVersion + clusterObj *appsv1alpha1.Cluster + reqCtx intctrlutil.RequestCtx + dag *graph.DAG + + defaultStorageClass *storagev1.StorageClass + + defaultReplicas = 2 + defaultVolumeSize = "2Gi" + defaultVolumeQuantity = apiresource.MustParse(defaultVolumeSize) + ) + + cleanAll := func() { + // must wait until resources deleted and no longer exist before the testcases start, + // otherwise if later it needs to create some new resource objects with the same name, + // in race conditions, it will find the existence of old objects, resulting failure to + // create the new objects. + By("clean resources") + // delete cluster(and all dependent sub-resources), clusterversion and clusterdef + testapps.ClearClusterResources(&testCtx) + + // clear rest resources + inNS := client.InNamespace(testCtx.DefaultNamespace) + ml := client.HasLabels{testCtx.TestObjLabelKey} + // namespaced resources + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.RSMSignature, true, inNS, ml) + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS, ml) + // non-namespaced + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeSignature, true, inNS, ml) + testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml) + } + + BeforeEach(cleanAll) + + AfterEach(cleanAll) + + setup := func() { + defaultStorageClass = testk8s.CreateMockStorageClass(&testCtx, testk8s.DefaultStorageClassName) + Expect(*defaultStorageClass.AllowVolumeExpansion).Should(BeTrue()) + + clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). + AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName). + GetObject() + + clusterVerObj = testapps.NewClusterVersionFactory(clusterVerName, clusterDefObj.GetName()). + AddComponentVersion(statefulCompDefName).AddContainerShort(testapps.DefaultMySQLContainerName, testapps.ApeCloudMySQLImage). + GetObject() + + clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, clusterDefObj.Name, clusterVerObj.Name). + AddComponent(statefulCompName, statefulCompDefName). + SetReplicas(int32(defaultReplicas)). + AddVolumeClaimTemplate(testapps.LogVolumeName, testapps.NewPVCSpec(defaultVolumeSize)). + AddVolumeClaimTemplate(testapps.DataVolumeName, testapps.NewPVCSpec(defaultVolumeSize)). + GetObject() + + reqCtx = intctrlutil.RequestCtx{Ctx: ctx, Log: logger, Recorder: recorder} + dag = graph.NewDAG() + } + + resetDag := func(comp Component) { + Expect(comp).ShouldNot(BeNil()) + rsmComp, ok := comp.(*rsmComponent) + Expect(ok).Should(BeTrue()) + dag = graph.NewDAG() + rsmComp.dag = dag + } + + submitChanges := func(ctx context.Context, cli client.Client, dag *graph.DAG) error { + walking := func(v graph.Vertex) error { + node, ok := v.(*ictrltypes.LifecycleVertex) + Expect(ok).Should(BeTrue()) + + _, ok = node.Obj.(*appsv1alpha1.Cluster) + Expect(!ok || *node.Action == ictrltypes.NOOP).Should(BeTrue()) + + switch *node.Action { + case ictrltypes.CREATE: + err := cli.Create(ctx, node.Obj) + if err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + case ictrltypes.UPDATE: + if node.Immutable { + return nil + } + err := cli.Update(ctx, node.Obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + case ictrltypes.PATCH: + patch := client.MergeFrom(node.ObjCopy) + if err := cli.Patch(ctx, node.Obj, patch); err != nil && !apierrors.IsNotFound(err) { + return err + } + case ictrltypes.DELETE: + if controllerutil.RemoveFinalizer(node.Obj, constant.DBClusterFinalizerName) { + err := cli.Update(ctx, node.Obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + if _, ok := node.Obj.(*appsv1alpha1.Cluster); !ok { + err := cli.Delete(ctx, node.Obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + case ictrltypes.STATUS: + patch := client.MergeFrom(node.ObjCopy) + if err := cli.Status().Patch(ctx, node.Obj, patch); err != nil { + return err + } + case ictrltypes.NOOP: + // nothing + } + return nil + } + if dag.Root() != nil { + return dag.WalkReverseTopoOrder(walking, nil) + } else { + withRoot := graph.NewDAG() + ictrltypes.LifecycleObjectNoop(withRoot, &appsv1alpha1.Cluster{}, nil) + withRoot.Merge(dag) + return withRoot.WalkReverseTopoOrder(walking, nil) + } + } + + newComponent := func(compName string) Component { + comp, err := NewComponent(reqCtx, testCtx.Cli, clusterDefObj, clusterVerObj, clusterObj, compName, dag) + Expect(comp).ShouldNot(BeNil()) + Expect(err).Should(Succeed()) + return comp + } + + createComponent := func(comp Component) error { + if err := comp.Create(reqCtx, testCtx.Cli); err != nil { + return err + } + return submitChanges(testCtx.Ctx, testCtx.Cli, dag) + } + + deleteComponent := func(comp Component) error { + resetDag(comp) + if err := comp.Delete(reqCtx, testCtx.Cli); err != nil { + return err + } + return submitChanges(testCtx.Ctx, testCtx.Cli, dag) + } + + updateComponent := func(comp Component) error { + resetDag(comp) + comp.GetCluster().Generation = comp.GetCluster().Status.ObservedGeneration + 1 + if err := comp.Update(reqCtx, testCtx.Cli); err != nil { + return err + } + return submitChanges(testCtx.Ctx, testCtx.Cli, dag) + } + + pvcKey := func(clusterName, compName, vctName string, ordinal int) types.NamespacedName { + return types.NamespacedName{ + Namespace: testCtx.DefaultNamespace, + Name: fmt.Sprintf("%s-%s-%s-%d", vctName, clusterName, compName, ordinal), + } + } + + pvKey := func(clusterName, compName, vctName string, ordinal int) types.NamespacedName { + return types.NamespacedName{ + Namespace: testCtx.DefaultNamespace, + Name: fmt.Sprintf("pvc-%s-%s-%s-%d", clusterName, compName, vctName, ordinal), + } + } + + createPVCs := func(spec *appsv1alpha1.ClusterComponentSpec, labels client.MatchingLabels) { + for _, vct := range spec.VolumeClaimTemplates { + for i := 0; i < int(spec.Replicas); i++ { + var ( + pvcName = pvcKey(clusterName, spec.Name, vct.Name, i).Name + pvName = pvKey(clusterName, spec.Name, vct.Name, i).Name + ) + pvc := testapps.NewPersistentVolumeClaimFactory(testCtx.DefaultNamespace, pvcName, clusterName, spec.Name, vct.Name). + AddLabelsInMap(labels). + SetStorageClass(defaultStorageClass.Name). + SetStorage(defaultVolumeSize). + SetVolumeName(pvName). + CheckedCreate(&testCtx). + GetObject() + testapps.NewPersistentVolumeFactory(testCtx.DefaultNamespace, pvName, pvcName). + SetStorage(defaultVolumeSize). + SetClaimRef(pvc). + CheckedCreate(&testCtx) + Eventually(testapps.GetAndChangeObjStatus(&testCtx, pvcKey(clusterName, spec.Name, vct.Name, i), + func(pvc *corev1.PersistentVolumeClaim) { + pvc.Status.Phase = corev1.ClaimBound + if pvc.Status.Capacity == nil { + pvc.Status.Capacity = corev1.ResourceList{} + } + pvc.Status.Capacity[corev1.ResourceStorage] = pvc.Spec.Resources.Requests[corev1.ResourceStorage] + })).Should(Succeed()) + Eventually(testapps.GetAndChangeObjStatus(&testCtx, pvKey(clusterName, spec.Name, vct.Name, i), + func(pv *corev1.PersistentVolume) { + pvc.Status.Phase = corev1.ClaimBound + })).Should(Succeed()) + } + } + } + + Context("new component object", func() { + BeforeEach(func() { + setup() + }) + + It("ok", func() { + By("new cluster component ok") + comp := newComponent(statefulCompName) + Expect(comp.GetNamespace()).Should(Equal(clusterObj.GetNamespace())) + Expect(comp.GetClusterName()).Should(Equal(clusterObj.GetName())) + Expect(comp.GetName()).Should(Equal(statefulCompName)) + Expect(comp.GetCluster()).Should(Equal(clusterObj)) + Expect(comp.GetClusterVersion()).Should(Equal(clusterVerObj)) + Expect(comp.GetSynthesizedComponent()).ShouldNot(BeNil()) + }) + + It("w/o component definition", func() { + By("new cluster component without component definition") + clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, clusterDefObj.Name, clusterVerObj.Name). + AddComponent(statefulCompName, statefulCompDefName+random). // with a random component def name + GetObject() + _, err := NewComponent(reqCtx, testCtx.Cli, clusterDefObj, clusterVerObj, clusterObj, statefulCompName, dag) + Expect(err).ShouldNot(Succeed()) + }) + + It("w/o component definition and spec", func() { + By("new cluster component without component definition and spec") + clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, clusterDefObj.Name, clusterVerObj.Name). + AddComponent(statefulCompName+random, statefulCompDefName+random). // with a random component spec and def name + GetObject() + comp, err := NewComponent(reqCtx, testCtx.Cli, clusterDefObj, clusterVerObj, clusterObj, statefulCompName, dag) + Expect(comp).Should(BeNil()) + Expect(err).Should(BeNil()) + }) + }) + + Context("create and delete component", func() { + var ( + comp Component + labels client.MatchingLabels + ) + + BeforeEach(func() { + setup() + + comp = newComponent(statefulCompName) + Expect(createComponent(comp)).Should(Succeed()) + + labels = client.MatchingLabels{ + constant.AppManagedByLabelKey: constant.AppName, + constant.AppInstanceLabelKey: comp.GetClusterName(), + constant.KBAppComponentLabelKey: comp.GetName(), + } + }) + + It("create component resources", func() { + By("check workload resources created") + Eventually(testapps.List(&testCtx, generics.RSMSignature, labels)).Should(HaveLen(1)) + }) + + It("delete component doesn't affect resources", func() { + By("delete the component") + Expect(deleteComponent(comp)).Should(Succeed()) + + By("check workload resources still exist") + Eventually(testapps.List(&testCtx, generics.RSMSignature, labels)).Should(HaveLen(1)) + }) + }) + + Context("update component", func() { + var ( + comp Component + labels client.MatchingLabels + ) + + spec := func() *appsv1alpha1.ClusterComponentSpec { + return clusterObj.Spec.GetComponentByName(comp.GetName()) + } + + // rsmKey := func() types.NamespacedName { + // return types.NamespacedName{ + // Namespace: comp.GetNamespace(), + // Name: fmt.Sprintf("%s-%s", clusterObj.GetName(), comp.GetName()), + // } + // } + + BeforeEach(func() { + setup() + + comp = newComponent(statefulCompName) + Expect(createComponent(comp)).Should(Succeed()) + + labels = client.MatchingLabels{ + constant.AppManagedByLabelKey: constant.AppName, + constant.AppInstanceLabelKey: comp.GetClusterName(), + constant.KBAppComponentLabelKey: comp.GetName(), + } + + // create all PVCs ann PVs + createPVCs(spec(), labels) + }) + + It("expand volume", func() { + By("up the log volume size with 1Gi") + vct := spec().VolumeClaimTemplates[0] + quantity := vct.Spec.Resources.Requests.Storage() + quantity.Add(apiresource.MustParse("1Gi")) + spec().VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = *quantity + Expect(updateComponent(comp)).Should(Succeed()) + + By("check all the log PVCs updated") + Eventually(func(g Gomega) { + objs, err := listObjWithLabelsInNamespace(testCtx.Ctx, testCtx.Cli, generics.PersistentVolumeClaimSignature, comp.GetNamespace(), labels) + g.Expect(err).Should(Succeed()) + g.Expect(objs).Should(HaveLen(int(spec().Replicas) * len(spec().VolumeClaimTemplates))) + for _, pvc := range objs { + if strings.HasPrefix(pvc.GetName(), vct.Name) { + g.Expect(pvc.Spec.Resources.Requests.Storage().Cmp(defaultVolumeQuantity)).Should(Equal(1)) + } else { + g.Expect(pvc.Spec.Resources.Requests.Storage().Cmp(defaultVolumeQuantity)).Should(Equal(0)) + } + } + }).Should(Succeed()) + }) + + It("shrink volume", func() { + By("shrink the log volume with 1Gi") + quantity := spec().VolumeClaimTemplates[0].Spec.Resources.Requests.Storage() + quantity.Sub(apiresource.MustParse("1Gi")) + spec().VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = *quantity + Expect(updateComponent(comp)).Should(HaveOccurred()) + + By("check all the PVCs unchanged") + Eventually(func(g Gomega) { + objs, err := listObjWithLabelsInNamespace(testCtx.Ctx, testCtx.Cli, generics.PersistentVolumeClaimSignature, comp.GetNamespace(), labels) + g.Expect(err).Should(Succeed()) + g.Expect(objs).Should(HaveLen(int(spec().Replicas) * len(spec().VolumeClaimTemplates))) + for _, pvc := range objs { + g.Expect(pvc.Spec.Resources.Requests.Storage().Cmp(defaultVolumeQuantity)).Should(Equal(0)) + } + }).ShouldNot(HaveOccurred()) + }) + }) +}) diff --git a/controllers/apps/components/suite_test.go b/controllers/apps/components/suite_test.go index 4f70fae453c2..5b34ebe7ac9d 100644 --- a/controllers/apps/components/suite_test.go +++ b/controllers/apps/components/suite_test.go @@ -27,10 +27,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/go-logr/logr" "go.uber.org/zap/zapcore" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -38,10 +40,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" intctrlutil "github.com/apecloud/kubeblocks/internal/controllerutil" "github.com/apecloud/kubeblocks/internal/testutil" viper "github.com/apecloud/kubeblocks/internal/viperx" + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -53,6 +57,8 @@ var testEnv *envtest.Environment var ctx context.Context var cancel context.CancelFunc var testCtx testutil.TestContext +var logger logr.Logger +var recorder record.EventRecorder func init() { viper.AutomaticEnv() @@ -72,6 +78,8 @@ var _ = BeforeSuite(func() { } ctx, cancel = context.WithCancel(context.TODO()) + logger = logf.FromContext(ctx).WithValues() + logger.Info("logger start") By("bootstrapping test environment") testEnv = &envtest.Environment{ @@ -88,12 +96,18 @@ var _ = BeforeSuite(func() { err = appsv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = corev1.AddToScheme(scheme.Scheme) + err = dpv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = snapshotv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) err = workloads.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = corev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -108,6 +122,7 @@ var _ = BeforeSuite(func() { }) Expect(err).ToNot(HaveOccurred()) + recorder = k8sManager.GetEventRecorderFor("cluster-component") testCtx = testutil.NewDefaultTestContext(ctx, k8sClient, testEnv) go func() { diff --git a/controllers/apps/components/utils_test.go b/controllers/apps/components/utils_test.go index fd38f183b960..6a2dcc392d0f 100644 --- a/controllers/apps/components/utils_test.go +++ b/controllers/apps/components/utils_test.go @@ -63,7 +63,7 @@ func TestIsProbeTimeout(t *testing.T) { } } -var _ = Describe("Component", func() { +var _ = Describe("Component Utils", func() { var ( randomStr = testCtx.GetRandomStr() clusterDefName = "mysql-clusterdef-" + randomStr @@ -254,9 +254,7 @@ var _ = Describe("Component", func() { }) }) }) -}) -var _ = Describe("Component utils test", func() { Context("test mergeServiceAnnotations", func() { It("should merge annotations from original that not exist in target to final result", func() { originalKey := "only-existing-in-original" diff --git a/internal/testutil/apps/pv_factoy.go b/internal/testutil/apps/pv_factoy.go new file mode 100644 index 000000000000..345063e4cd1f --- /dev/null +++ b/internal/testutil/apps/pv_factoy.go @@ -0,0 +1,78 @@ +/* +Copyright (C) 2022-2023 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 apps + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type MockPersistentVolumeFactory struct { + BaseFactory[corev1.PersistentVolume, *corev1.PersistentVolume, MockPersistentVolumeFactory] +} + +func NewPersistentVolumeFactory(namespace, name, pvcName string) *MockPersistentVolumeFactory { + f := &MockPersistentVolumeFactory{} + volumeMode := corev1.PersistentVolumeFilesystem + f.Init(namespace, name, + &corev1.PersistentVolume{ + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/tmp/hostpath-provisioner/default/" + pvcName, + }, + }, + VolumeMode: &volumeMode, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete, + }, + }, f) + return f +} + +func (f *MockPersistentVolumeFactory) SetStorageClass(storageClassName string) *MockPersistentVolumeFactory { + f.Get().Spec.StorageClassName = storageClassName + return f +} + +func (f *MockPersistentVolumeFactory) SetStorage(storageSize string) *MockPersistentVolumeFactory { + f.Get().Spec.Capacity = corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(storageSize), + } + return f +} + +func (f *MockPersistentVolumeFactory) SetPersistentVolumeReclaimPolicy(reclaimPolicy corev1.PersistentVolumeReclaimPolicy) *MockPersistentVolumeFactory { + f.Get().Spec.PersistentVolumeReclaimPolicy = reclaimPolicy + return f +} + +func (f *MockPersistentVolumeFactory) SetClaimRef(obj client.Object) *MockPersistentVolumeFactory { + f.Get().Spec.ClaimRef = &corev1.ObjectReference{ + Kind: obj.GetObjectKind().GroupVersionKind().Kind, + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + UID: obj.GetUID(), + APIVersion: "v1", + ResourceVersion: obj.GetResourceVersion(), + } + return f +} diff --git a/internal/testutil/apps/pvc_factoy.go b/internal/testutil/apps/pvc_factoy.go index 9e6865612a7f..7f566e1d43a4 100644 --- a/internal/testutil/apps/pvc_factoy.go +++ b/internal/testutil/apps/pvc_factoy.go @@ -71,6 +71,11 @@ func (factory *MockPersistentVolumeClaimFactory) SetStorage(storageSize string) return factory } +func (factory *MockPersistentVolumeClaimFactory) SetVolumeName(volName string) *MockPersistentVolumeClaimFactory { + factory.Get().Spec.VolumeName = volName + return factory +} + func (factory *MockPersistentVolumeClaimFactory) SetAnnotations(annotations map[string]string) *MockPersistentVolumeClaimFactory { factory.Get().Annotations = annotations return factory