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