Skip to content

Commit d614299

Browse files
committed
Add support for PVCs in captured blueprints
1 parent dad2e73 commit d614299

File tree

3 files changed

+275
-42
lines changed

3 files changed

+275
-42
lines changed

pkg/syncer/cnsoperator/controller/cnsregistervolume/cnsregistervolume_controller.go

Lines changed: 106 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343

4444
clientConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
4545

46+
clientset "k8s.io/client-go/kubernetes"
4647
apis "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator"
4748
cnsregistervolumev1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/cnsregistervolume/v1alpha1"
4849
storagepolicyusagev1alpha2 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/storagepolicy/v1alpha2"
@@ -557,53 +558,65 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
557558
setInstanceError(ctx, r, instance, "Duplicate Request")
558559
return reconcile.Result{RequeueAfter: timeout}, nil
559560
}
560-
// Create PVC mapping to above created PV.
561-
log.Infof("Creating PVC: %s", instance.Spec.PvcName)
562-
pvcSpec, err := getPersistentVolumeClaimSpec(ctx, instance.Spec.PvcName, instance.Namespace, capacityInMb,
563-
storageClassName, accessMode, pvName, datastoreAccessibleTopology, instance)
561+
562+
// Check if PVC already exists and has valid DataSourceRef
563+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, instance.Spec.PvcName, instance.Namespace)
564564
if err != nil {
565-
msg := fmt.Sprintf("Failed to create spec for PVC: %q. Error: %v", instance.Spec.PvcName, err)
566-
log.Errorf(msg)
567-
setInstanceError(ctx, r, instance, msg)
565+
log.Errorf("Failed to check existing PVC %s/%s with DataSourceRef: %+v", instance.Namespace,
566+
instance.Spec.PvcName, err)
567+
setInstanceError(ctx, r, instance, fmt.Sprintf("Failed to check existing PVC %s/%s with DataSourceRef: %+v",
568+
instance.Namespace, instance.Spec.PvcName, err))
568569
return reconcile.Result{RequeueAfter: timeout}, nil
569570
}
570-
log.Debugf("PVC spec is: %+v", pvcSpec)
571-
pvc, err := k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Create(ctx,
572-
pvcSpec, metav1.CreateOptions{})
573-
if err != nil {
574-
if apierrors.IsAlreadyExists(err) {
575-
log.Infof("PVC: %s already exists", instance.Spec.PvcName)
576-
pvc, err = k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Get(ctx,
577-
instance.Spec.PvcName, metav1.GetOptions{})
571+
572+
if pvc != nil {
573+
log.Infof("PVC: %s already exists", instance.Spec.PvcName)
574+
if pvc.Status.Phase == v1.ClaimBound && pvc.Spec.VolumeName != pvName {
575+
// This is handle cases where PVC with this name already exists and
576+
// is bound. This happens when a new CnsRegisterVolume instance is
577+
// created to import a new volume with PVC name which is already
578+
// created and is bound.
579+
msg := fmt.Sprintf("Another PVC: %s already exists in namespace: %s which is Bound to a different PV",
580+
instance.Spec.PvcName, instance.Namespace)
581+
log.Errorf(msg)
582+
setInstanceError(ctx, r, instance, msg)
583+
// Untag the CNS volume which was created previously.
584+
_, err = common.DeleteVolumeUtil(ctx, r.volumeManager, volumeID, false)
578585
if err != nil {
579-
msg := fmt.Sprintf("Failed to get PVC: %s on namespace: %s", instance.Spec.PvcName, instance.Namespace)
580-
log.Errorf(msg)
581-
setInstanceError(ctx, r, instance, msg)
582-
return reconcile.Result{RequeueAfter: timeout}, nil
583-
}
584-
if pvc.Status.Phase == v1.ClaimBound && pvc.Spec.VolumeName != pvName {
585-
// This is handle cases where PVC with this name already exists and
586-
// is bound. This happens when a new CnsRegisterVolume instance is
587-
// created to import a new volume with PVC name which is already
588-
// created and is bound.
589-
msg := fmt.Sprintf("Another PVC: %s already exists in namespace: %s which is Bound to a different PV",
590-
instance.Spec.PvcName, instance.Namespace)
591-
log.Errorf(msg)
592-
setInstanceError(ctx, r, instance, msg)
593-
// Untag the CNS volume which was created previously.
594-
_, err = common.DeleteVolumeUtil(ctx, r.volumeManager, volumeID, false)
586+
log.Errorf("Failed to untag CNS volume: %s with error: %+v", volumeID, err)
587+
} else {
588+
// Delete PV created above.
589+
err = k8sclient.CoreV1().PersistentVolumes().Delete(ctx, pvName, *metav1.NewDeleteOptions(0))
595590
if err != nil {
596-
log.Errorf("Failed to untag CNS volume: %s with error: %+v", volumeID, err)
597-
} else {
598-
// Delete PV created above.
599-
err = k8sclient.CoreV1().PersistentVolumes().Delete(ctx, pvName, *metav1.NewDeleteOptions(0))
600-
if err != nil {
601-
log.Errorf("Failed to delete PV: %s with error: %+v", pvName, err)
602-
}
591+
log.Errorf("Failed to delete PV: %s with error: %+v", pvName, err)
603592
}
604-
return reconcile.Result{RequeueAfter: timeout}, nil
605593
}
606-
} else {
594+
return reconcile.Result{RequeueAfter: timeout}, nil
595+
}
596+
597+
if pvc.Spec.DataSourceRef != nil {
598+
apiGroup := ""
599+
if pvc.Spec.DataSourceRef.APIGroup != nil {
600+
apiGroup = *pvc.Spec.DataSourceRef.APIGroup
601+
}
602+
log.Infof("PVC %s in namespace %s has valid DataSourceRef with apiGroup: %s, kind: %s, name: %s",
603+
pvc.Name, pvc.Namespace, apiGroup, pvc.Spec.DataSourceRef.Kind, pvc.Spec.DataSourceRef.Name)
604+
}
605+
} else {
606+
// Create PVC mapping to above created PV.
607+
log.Infof("Creating PVC: %s", instance.Spec.PvcName)
608+
pvcSpec, err := getPersistentVolumeClaimSpec(ctx, instance.Spec.PvcName, instance.Namespace, capacityInMb,
609+
storageClassName, accessMode, pvName, datastoreAccessibleTopology, instance)
610+
if err != nil {
611+
msg := fmt.Sprintf("Failed to create spec for PVC: %q. Error: %v", instance.Spec.PvcName, err)
612+
log.Errorf(msg)
613+
setInstanceError(ctx, r, instance, msg)
614+
return reconcile.Result{RequeueAfter: timeout}, nil
615+
}
616+
log.Debugf("PVC spec is: %+v", pvcSpec)
617+
pvc, err = k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Create(ctx,
618+
pvcSpec, metav1.CreateOptions{})
619+
if err != nil {
607620
log.Errorf("Failed to create PVC with spec: %+v. Error: %+v", pvcSpec, err)
608621
setInstanceError(ctx, r, instance,
609622
fmt.Sprintf("Failed to create PVC: %s for volume with err: %+v", instance.Spec.PvcName, err))
@@ -615,9 +628,9 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
615628
setInstanceError(ctx, r, instance,
616629
fmt.Sprintf("Delete PV %s failed with error: %+v", pvName, err))
617630
return reconcile.Result{RequeueAfter: timeout}, nil
631+
} else {
632+
log.Infof("PVC: %s is created successfully", instance.Spec.PvcName)
618633
}
619-
} else {
620-
log.Infof("PVC: %s is created successfully", instance.Spec.PvcName)
621634
}
622635
// Watch for PVC to be bound.
623636
isBound, err := isPVCBound(ctx, k8sclient, pvc, time.Duration(1*time.Minute))
@@ -742,6 +755,57 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
742755
return reconcile.Result{}, nil
743756
}
744757

758+
// checkExistingPVCDataSourceRef checks if a PVC already exists and validates its DataSourceRef.
759+
// Returns the existing PVC if it exists regardless if it has a supported DataSourceRef, otherwise returns nil.
760+
func checkExistingPVCDataSourceRef(ctx context.Context, k8sclient clientset.Interface,
761+
pvcName, namespace string) (*v1.PersistentVolumeClaim, error) {
762+
log := logger.GetLogger(ctx)
763+
764+
// Try to get the existing PVC
765+
existingPVC, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{})
766+
if err != nil {
767+
if apierrors.IsNotFound(err) {
768+
// PVC doesn't exist, return nil (we'll create a new one)
769+
return nil, nil
770+
}
771+
// Some other error occurred
772+
return nil, fmt.Errorf("failed to check existing PVC %s in namespace %s: %+v", pvcName, namespace, err)
773+
}
774+
775+
// PVC exists, check if it has DataSourceRef
776+
if existingPVC.Spec.DataSourceRef == nil {
777+
log.Infof("Existing PVC %s in namespace %s has no DataSourceRef, can reuse", pvcName, namespace)
778+
return existingPVC, nil
779+
}
780+
781+
// Check if DataSourceRef matches supported types
782+
apiGroup := ""
783+
if existingPVC.Spec.DataSourceRef.APIGroup != nil {
784+
apiGroup = *existingPVC.Spec.DataSourceRef.APIGroup
785+
}
786+
787+
for _, supportedType := range supportedDataSourceTypes {
788+
if supportedType.apiGroup == apiGroup && supportedType.kind == existingPVC.Spec.DataSourceRef.Kind {
789+
log.Infof("Existing PVC %s in namespace %s has valid DataSourceRef (apiGroup: %s, kind: %s), can reuse",
790+
pvcName, namespace, apiGroup, existingPVC.Spec.DataSourceRef.Kind)
791+
return existingPVC, nil
792+
}
793+
}
794+
795+
// Check if DataSourceRef is VolumeSnapshot
796+
if existingPVC.Spec.DataSourceRef.Kind == "VolumeSnapshot" &&
797+
existingPVC.Spec.DataSourceRef.APIGroup != nil &&
798+
*existingPVC.Spec.DataSourceRef.APIGroup == "snapshot.storage.k8s.io" {
799+
log.Infof("WARNING: Existing PVC %s in namespace %s has valid VolumeSnapshot DataSourceRef, "+
800+
"however, it is not supported for CNSRegisterVolume", pvcName, namespace)
801+
}
802+
803+
// DataSourceRef is not supported
804+
return nil, fmt.Errorf("existing PVC %s in namespace %s has unsupported DataSourceRef for CNSRegisterVolume. "+
805+
"APIGroup: %s, Kind: %s is not supported. Supported types: %+v",
806+
pvcName, namespace, apiGroup, existingPVC.Spec.DataSourceRef.Kind, supportedDataSourceTypes)
807+
}
808+
745809
// validateCnsRegisterVolumeSpec validates the input params of
746810
// CnsRegisterVolume instance.
747811
func validateCnsRegisterVolumeSpec(ctx context.Context, instance *cnsregistervolumev1alpha1.CnsRegisterVolume) error {

pkg/syncer/cnsoperator/controller/cnsregistervolume/cnsregistervolume_controller_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"k8s.io/apimachinery/pkg/runtime"
3535
"k8s.io/apimachinery/pkg/runtime/schema"
3636
"k8s.io/apimachinery/pkg/types"
37+
k8sfake "k8s.io/client-go/kubernetes/fake"
3738
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3839
restclient "k8s.io/client-go/rest"
3940
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -613,6 +614,164 @@ var _ = Describe("Reconcile Accessibility Logic", func() {
613614
})
614615
})
615616

617+
var _ = Describe("checkExistingPVCDataSourceRef", func() {
618+
var (
619+
ctx context.Context
620+
k8sclient *k8sfake.Clientset
621+
namespace string
622+
pvcName string
623+
)
624+
625+
BeforeEach(func() {
626+
ctx = context.Background()
627+
k8sclient = k8sfake.NewSimpleClientset()
628+
namespace = "test-namespace"
629+
pvcName = "test-pvc"
630+
})
631+
632+
Context("when PVC does not exist", func() {
633+
It("should return nil without error", func() {
634+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
635+
Expect(err).To(BeNil())
636+
Expect(pvc).To(BeNil())
637+
})
638+
})
639+
640+
Context("when PVC exists without DataSourceRef", func() {
641+
BeforeEach(func() {
642+
pvc := &corev1.PersistentVolumeClaim{
643+
ObjectMeta: metav1.ObjectMeta{
644+
Name: pvcName,
645+
Namespace: namespace,
646+
},
647+
Spec: corev1.PersistentVolumeClaimSpec{
648+
DataSourceRef: nil,
649+
},
650+
}
651+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
652+
Expect(err).To(BeNil())
653+
})
654+
655+
It("should return the PVC without error", func() {
656+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
657+
Expect(err).To(BeNil())
658+
Expect(pvc).ToNot(BeNil())
659+
Expect(pvc.Name).To(Equal(pvcName))
660+
Expect(pvc.Spec.DataSourceRef).To(BeNil())
661+
})
662+
})
663+
664+
Context("when PVC exists with VolumeSnapshot DataSourceRef", func() {
665+
BeforeEach(func() {
666+
apiGroup := "snapshot.storage.k8s.io"
667+
pvc := &corev1.PersistentVolumeClaim{
668+
ObjectMeta: metav1.ObjectMeta{
669+
Name: pvcName,
670+
Namespace: namespace,
671+
},
672+
Spec: corev1.PersistentVolumeClaimSpec{
673+
DataSourceRef: &corev1.TypedObjectReference{
674+
APIGroup: &apiGroup,
675+
Kind: "VolumeSnapshot",
676+
Name: "test-snapshot",
677+
},
678+
},
679+
}
680+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
681+
Expect(err).To(BeNil())
682+
})
683+
684+
It("should return an error since VolumeSnapshots are not supported for CNSRegisterVolume", func() {
685+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
686+
Expect(err).ToNot(BeNil())
687+
Expect(pvc).To(BeNil())
688+
})
689+
})
690+
691+
Context("when PVC exists with supported DataSourceRef", func() {
692+
BeforeEach(func() {
693+
apiGroup := "vmoperator.vmware.com"
694+
pvc := &corev1.PersistentVolumeClaim{
695+
ObjectMeta: metav1.ObjectMeta{
696+
Name: pvcName,
697+
Namespace: namespace,
698+
},
699+
Spec: corev1.PersistentVolumeClaimSpec{
700+
DataSourceRef: &corev1.TypedObjectReference{
701+
APIGroup: &apiGroup,
702+
Kind: "VirtualMachine",
703+
Name: "test-vm",
704+
},
705+
},
706+
}
707+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
708+
Expect(err).To(BeNil())
709+
})
710+
711+
It("should return the PVC without error", func() {
712+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
713+
Expect(err).To(BeNil())
714+
Expect(pvc).ToNot(BeNil())
715+
Expect(pvc.Name).To(Equal(pvcName))
716+
Expect(pvc.Spec.DataSourceRef.Kind).To(Equal("VirtualMachine"))
717+
Expect(*pvc.Spec.DataSourceRef.APIGroup).To(Equal("vmoperator.vmware.com"))
718+
})
719+
})
720+
721+
Context("when PVC exists with unsupported DataSourceRef", func() {
722+
BeforeEach(func() {
723+
apiGroup := "unsupported.example.com"
724+
pvc := &corev1.PersistentVolumeClaim{
725+
ObjectMeta: metav1.ObjectMeta{
726+
Name: pvcName,
727+
Namespace: namespace,
728+
},
729+
Spec: corev1.PersistentVolumeClaimSpec{
730+
DataSourceRef: &corev1.TypedObjectReference{
731+
APIGroup: &apiGroup,
732+
Kind: "UnsupportedKind",
733+
Name: "test-resource",
734+
},
735+
},
736+
}
737+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
738+
Expect(err).To(BeNil())
739+
})
740+
741+
It("should return an error", func() {
742+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
743+
Expect(err).ToNot(BeNil())
744+
Expect(pvc).To(BeNil())
745+
})
746+
})
747+
748+
Context("when PVC exists with empty APIGroup DataSourceRef", func() {
749+
BeforeEach(func() {
750+
pvc := &corev1.PersistentVolumeClaim{
751+
ObjectMeta: metav1.ObjectMeta{
752+
Name: pvcName,
753+
Namespace: namespace,
754+
},
755+
Spec: corev1.PersistentVolumeClaimSpec{
756+
DataSourceRef: &corev1.TypedObjectReference{
757+
APIGroup: nil,
758+
Kind: "SomeKind",
759+
Name: "test-resource",
760+
},
761+
},
762+
}
763+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
764+
Expect(err).To(BeNil())
765+
})
766+
767+
It("should return an error for unsupported empty APIGroup", func() {
768+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
769+
Expect(err).ToNot(BeNil())
770+
Expect(pvc).To(BeNil())
771+
})
772+
})
773+
})
774+
616775
func TestCnsRegisterVolumeController(t *testing.T) {
617776
backOffDuration = make(map[types.NamespacedName]time.Duration)
618777

pkg/syncer/cnsoperator/controller/cnsregistervolume/util.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ const (
4949
scResourceNameSuffix = ".storageclass.storage.k8s.io/requests.storage"
5050
)
5151

52+
var (
53+
// Supported data source types for PVCs
54+
supportedDataSourceTypes = []struct {
55+
apiGroup string
56+
kind string
57+
}{
58+
{"vmoperator.vmware.com", "VirtualMachine"},
59+
}
60+
)
61+
5262
// isDatastoreAccessibleToCluster verifies if the datastoreUrl is accessible to
5363
// cluster with clusterID.
5464
func isDatastoreAccessibleToCluster(ctx context.Context, vc *vsphere.VirtualCenter,

0 commit comments

Comments
 (0)