Skip to content

Commit c5aaeeb

Browse files
committed
Add support for PVCs in captured blueprints
1 parent cae0b46 commit c5aaeeb

File tree

3 files changed

+272
-42
lines changed

3 files changed

+272
-42
lines changed

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

Lines changed: 103 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"
@@ -551,53 +552,63 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
551552
setInstanceError(ctx, r, instance, "Duplicate Request")
552553
return reconcile.Result{RequeueAfter: timeout}, nil
553554
}
554-
// Create PVC mapping to above created PV.
555-
log.Infof("Creating PVC: %s", instance.Spec.PvcName)
556-
pvcSpec, err := getPersistentVolumeClaimSpec(ctx, instance.Spec.PvcName, instance.Namespace, capacityInMb,
557-
storageClassName, accessMode, pvName, datastoreAccessibleTopology)
555+
556+
// Check if PVC already exists and has valid DataSourceRef
557+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, instance.Spec.PvcName, instance.Namespace)
558558
if err != nil {
559-
msg := fmt.Sprintf("Failed to create spec for PVC: %q. Error: %v", instance.Spec.PvcName, err)
560-
log.Errorf(msg)
561-
setInstanceError(ctx, r, instance, msg)
559+
log.Errorf("Failed to check existing PVC %s/%s with DataSourceRef: %+v", instance.Namespace, instance.Spec.PvcName, err)
560+
setInstanceError(ctx, r, instance, fmt.Sprintf("Failed to check existing PVC %s/%s with DataSourceRef: %+v", instance.Namespace, instance.Spec.PvcName, err))
562561
return reconcile.Result{RequeueAfter: timeout}, nil
563562
}
564-
log.Debugf("PVC spec is: %+v", pvcSpec)
565-
pvc, err := k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Create(ctx,
566-
pvcSpec, metav1.CreateOptions{})
567-
if err != nil {
568-
if apierrors.IsAlreadyExists(err) {
569-
log.Infof("PVC: %s already exists", instance.Spec.PvcName)
570-
pvc, err = k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Get(ctx,
571-
instance.Spec.PvcName, metav1.GetOptions{})
563+
564+
if pvc != nil {
565+
log.Infof("PVC: %s already exists", instance.Spec.PvcName)
566+
if pvc.Status.Phase == v1.ClaimBound && pvc.Spec.VolumeName != pvName {
567+
// This is handle cases where PVC with this name already exists and
568+
// is bound. This happens when a new CnsRegisterVolume instance is
569+
// created to import a new volume with PVC name which is already
570+
// created and is bound.
571+
msg := fmt.Sprintf("Another PVC: %s already exists in namespace: %s which is Bound to a different PV",
572+
instance.Spec.PvcName, instance.Namespace)
573+
log.Errorf(msg)
574+
setInstanceError(ctx, r, instance, msg)
575+
// Untag the CNS volume which was created previously.
576+
_, err = common.DeleteVolumeUtil(ctx, r.volumeManager, volumeID, false)
572577
if err != nil {
573-
msg := fmt.Sprintf("Failed to get PVC: %s on namespace: %s", instance.Spec.PvcName, instance.Namespace)
574-
log.Errorf(msg)
575-
setInstanceError(ctx, r, instance, msg)
576-
return reconcile.Result{RequeueAfter: timeout}, nil
577-
}
578-
if pvc.Status.Phase == v1.ClaimBound && pvc.Spec.VolumeName != pvName {
579-
// This is handle cases where PVC with this name already exists and
580-
// is bound. This happens when a new CnsRegisterVolume instance is
581-
// created to import a new volume with PVC name which is already
582-
// created and is bound.
583-
msg := fmt.Sprintf("Another PVC: %s already exists in namespace: %s which is Bound to a different PV",
584-
instance.Spec.PvcName, instance.Namespace)
585-
log.Errorf(msg)
586-
setInstanceError(ctx, r, instance, msg)
587-
// Untag the CNS volume which was created previously.
588-
_, err = common.DeleteVolumeUtil(ctx, r.volumeManager, volumeID, false)
578+
log.Errorf("Failed to untag CNS volume: %s with error: %+v", volumeID, err)
579+
} else {
580+
// Delete PV created above.
581+
err = k8sclient.CoreV1().PersistentVolumes().Delete(ctx, pvName, *metav1.NewDeleteOptions(0))
589582
if err != nil {
590-
log.Errorf("Failed to untag CNS volume: %s with error: %+v", volumeID, err)
591-
} else {
592-
// Delete PV created above.
593-
err = k8sclient.CoreV1().PersistentVolumes().Delete(ctx, pvName, *metav1.NewDeleteOptions(0))
594-
if err != nil {
595-
log.Errorf("Failed to delete PV: %s with error: %+v", pvName, err)
596-
}
583+
log.Errorf("Failed to delete PV: %s with error: %+v", pvName, err)
597584
}
598-
return reconcile.Result{RequeueAfter: timeout}, nil
599585
}
600-
} else {
586+
return reconcile.Result{RequeueAfter: timeout}, nil
587+
}
588+
589+
if pvc.Spec.DataSourceRef != nil {
590+
apiGroup := ""
591+
if pvc.Spec.DataSourceRef.APIGroup != nil {
592+
apiGroup = *pvc.Spec.DataSourceRef.APIGroup
593+
}
594+
log.Infof("PVC %s in namespace %s has valid DataSourceRef with apiGroup: %s, kind: %s, name: %s",
595+
pvc.Name, pvc.Namespace, apiGroup, pvc.Spec.DataSourceRef.Kind, pvc.Spec.DataSourceRef.Name)
596+
}
597+
} else {
598+
// Create PVC mapping to above created PV.
599+
log.Infof("Creating PVC: %s", instance.Spec.PvcName)
600+
pvcSpec, err := getPersistentVolumeClaimSpec(ctx, instance.Spec.PvcName, instance.Namespace, capacityInMb,
601+
storageClassName, accessMode, pvName, datastoreAccessibleTopology)
602+
if err != nil {
603+
msg := fmt.Sprintf("Failed to create spec for PVC: %q. Error: %v", instance.Spec.PvcName, err)
604+
log.Errorf(msg)
605+
setInstanceError(ctx, r, instance, msg)
606+
return reconcile.Result{RequeueAfter: timeout}, nil
607+
}
608+
log.Debugf("PVC spec is: %+v", pvcSpec)
609+
pvc, err = k8sclient.CoreV1().PersistentVolumeClaims(instance.Namespace).Create(ctx,
610+
pvcSpec, metav1.CreateOptions{})
611+
if err != nil {
601612
log.Errorf("Failed to create PVC with spec: %+v. Error: %+v", pvcSpec, err)
602613
setInstanceError(ctx, r, instance,
603614
fmt.Sprintf("Failed to create PVC: %s for volume with err: %+v", instance.Spec.PvcName, err))
@@ -609,9 +620,9 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
609620
setInstanceError(ctx, r, instance,
610621
fmt.Sprintf("Delete PV %s failed with error: %+v", pvName, err))
611622
return reconcile.Result{RequeueAfter: timeout}, nil
623+
} else {
624+
log.Infof("PVC: %s is created successfully", instance.Spec.PvcName)
612625
}
613-
} else {
614-
log.Infof("PVC: %s is created successfully", instance.Spec.PvcName)
615626
}
616627
// Watch for PVC to be bound.
617628
isBound, err := isPVCBound(ctx, k8sclient, pvc, time.Duration(1*time.Minute))
@@ -736,6 +747,56 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
736747
return reconcile.Result{}, nil
737748
}
738749

750+
// checkExistingPVCDataSourceRef checks if a PVC already exists and validates its DataSourceRef.
751+
// Returns the existing PVC if it exists regardless if it has a supported DataSourceRef, otherwise returns nil.
752+
func checkExistingPVCDataSourceRef(ctx context.Context, k8sclient clientset.Interface,
753+
pvcName, namespace string) (*v1.PersistentVolumeClaim, error) {
754+
log := logger.GetLogger(ctx)
755+
756+
// Try to get the existing PVC
757+
existingPVC, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{})
758+
if err != nil {
759+
if apierrors.IsNotFound(err) {
760+
// PVC doesn't exist, return nil (we'll create a new one)
761+
return nil, nil
762+
}
763+
// Some other error occurred
764+
return nil, fmt.Errorf("failed to check existing PVC %s in namespace %s: %+v", pvcName, namespace, err)
765+
}
766+
767+
// PVC exists, check if it has DataSourceRef
768+
if existingPVC.Spec.DataSourceRef == nil {
769+
log.Infof("Existing PVC %s in namespace %s has no DataSourceRef, can reuse", pvcName, namespace)
770+
return existingPVC, nil
771+
}
772+
773+
// Check if DataSourceRef matches supported types
774+
apiGroup := ""
775+
if existingPVC.Spec.DataSourceRef.APIGroup != nil {
776+
apiGroup = *existingPVC.Spec.DataSourceRef.APIGroup
777+
}
778+
779+
for _, supportedType := range supportedDataSourceTypes {
780+
if supportedType.apiGroup == apiGroup && supportedType.kind == existingPVC.Spec.DataSourceRef.Kind {
781+
log.Infof("Existing PVC %s in namespace %s has valid DataSourceRef (apiGroup: %s, kind: %s), can reuse",
782+
pvcName, namespace, apiGroup, existingPVC.Spec.DataSourceRef.Kind)
783+
return existingPVC, nil
784+
}
785+
}
786+
787+
// Check if DataSourceRef is VolumeSnapshot
788+
if existingPVC.Spec.DataSourceRef.Kind == "VolumeSnapshot" &&
789+
existingPVC.Spec.DataSourceRef.APIGroup != nil &&
790+
*existingPVC.Spec.DataSourceRef.APIGroup == "snapshot.storage.k8s.io" {
791+
log.Infof("WARNING: Existing PVC %s in namespace %s has valid VolumeSnapshot DataSourceRef, however, it is not supported for CNSRegisterVolume", pvcName, namespace)
792+
}
793+
794+
// DataSourceRef is not supported
795+
return nil, fmt.Errorf("existing PVC %s in namespace %s has unsupported DataSourceRef for CNSRegisterVolume. "+
796+
"APIGroup: %s, Kind: %s is not supported. Supported types: %+v",
797+
pvcName, namespace, apiGroup, existingPVC.Spec.DataSourceRef.Kind, supportedDataSourceTypes)
798+
}
799+
739800
// validateCnsRegisterVolumeSpec validates the input params of
740801
// CnsRegisterVolume instance.
741802
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"
@@ -509,6 +510,164 @@ var _ = Describe("Reconcile Accessibility Logic", func() {
509510
})
510511
})
511512

513+
var _ = Describe("checkExistingPVCDataSourceRef", func() {
514+
var (
515+
ctx context.Context
516+
k8sclient *k8sfake.Clientset
517+
namespace string
518+
pvcName string
519+
)
520+
521+
BeforeEach(func() {
522+
ctx = context.Background()
523+
k8sclient = k8sfake.NewSimpleClientset()
524+
namespace = "test-namespace"
525+
pvcName = "test-pvc"
526+
})
527+
528+
Context("when PVC does not exist", func() {
529+
It("should return nil without error", func() {
530+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
531+
Expect(err).To(BeNil())
532+
Expect(pvc).To(BeNil())
533+
})
534+
})
535+
536+
Context("when PVC exists without DataSourceRef", func() {
537+
BeforeEach(func() {
538+
pvc := &corev1.PersistentVolumeClaim{
539+
ObjectMeta: metav1.ObjectMeta{
540+
Name: pvcName,
541+
Namespace: namespace,
542+
},
543+
Spec: corev1.PersistentVolumeClaimSpec{
544+
DataSourceRef: nil,
545+
},
546+
}
547+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
548+
Expect(err).To(BeNil())
549+
})
550+
551+
It("should return the PVC without error", func() {
552+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
553+
Expect(err).To(BeNil())
554+
Expect(pvc).ToNot(BeNil())
555+
Expect(pvc.Name).To(Equal(pvcName))
556+
Expect(pvc.Spec.DataSourceRef).To(BeNil())
557+
})
558+
})
559+
560+
Context("when PVC exists with VolumeSnapshot DataSourceRef", func() {
561+
BeforeEach(func() {
562+
apiGroup := "snapshot.storage.k8s.io"
563+
pvc := &corev1.PersistentVolumeClaim{
564+
ObjectMeta: metav1.ObjectMeta{
565+
Name: pvcName,
566+
Namespace: namespace,
567+
},
568+
Spec: corev1.PersistentVolumeClaimSpec{
569+
DataSourceRef: &corev1.TypedObjectReference{
570+
APIGroup: &apiGroup,
571+
Kind: "VolumeSnapshot",
572+
Name: "test-snapshot",
573+
},
574+
},
575+
}
576+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
577+
Expect(err).To(BeNil())
578+
})
579+
580+
It("should return an error since VolumeSnapshots are not supported for CNSRegisterVolume", func() {
581+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
582+
Expect(err).ToNot(BeNil())
583+
Expect(pvc).To(BeNil())
584+
})
585+
})
586+
587+
Context("when PVC exists with supported DataSourceRef", func() {
588+
BeforeEach(func() {
589+
apiGroup := "vmoperator.vmware.com"
590+
pvc := &corev1.PersistentVolumeClaim{
591+
ObjectMeta: metav1.ObjectMeta{
592+
Name: pvcName,
593+
Namespace: namespace,
594+
},
595+
Spec: corev1.PersistentVolumeClaimSpec{
596+
DataSourceRef: &corev1.TypedObjectReference{
597+
APIGroup: &apiGroup,
598+
Kind: "VirtualMachine",
599+
Name: "test-vm",
600+
},
601+
},
602+
}
603+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
604+
Expect(err).To(BeNil())
605+
})
606+
607+
It("should return the PVC without error", func() {
608+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
609+
Expect(err).To(BeNil())
610+
Expect(pvc).ToNot(BeNil())
611+
Expect(pvc.Name).To(Equal(pvcName))
612+
Expect(pvc.Spec.DataSourceRef.Kind).To(Equal("VirtualMachine"))
613+
Expect(*pvc.Spec.DataSourceRef.APIGroup).To(Equal("vmoperator.vmware.com"))
614+
})
615+
})
616+
617+
Context("when PVC exists with unsupported DataSourceRef", func() {
618+
BeforeEach(func() {
619+
apiGroup := "unsupported.example.com"
620+
pvc := &corev1.PersistentVolumeClaim{
621+
ObjectMeta: metav1.ObjectMeta{
622+
Name: pvcName,
623+
Namespace: namespace,
624+
},
625+
Spec: corev1.PersistentVolumeClaimSpec{
626+
DataSourceRef: &corev1.TypedObjectReference{
627+
APIGroup: &apiGroup,
628+
Kind: "UnsupportedKind",
629+
Name: "test-resource",
630+
},
631+
},
632+
}
633+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
634+
Expect(err).To(BeNil())
635+
})
636+
637+
It("should return an error", func() {
638+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
639+
Expect(err).ToNot(BeNil())
640+
Expect(pvc).To(BeNil())
641+
})
642+
})
643+
644+
Context("when PVC exists with empty APIGroup DataSourceRef", func() {
645+
BeforeEach(func() {
646+
pvc := &corev1.PersistentVolumeClaim{
647+
ObjectMeta: metav1.ObjectMeta{
648+
Name: pvcName,
649+
Namespace: namespace,
650+
},
651+
Spec: corev1.PersistentVolumeClaimSpec{
652+
DataSourceRef: &corev1.TypedObjectReference{
653+
APIGroup: nil,
654+
Kind: "SomeKind",
655+
Name: "test-resource",
656+
},
657+
},
658+
}
659+
_, err := k8sclient.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
660+
Expect(err).To(BeNil())
661+
})
662+
663+
It("should return an error for unsupported empty APIGroup", func() {
664+
pvc, err := checkExistingPVCDataSourceRef(ctx, k8sclient, pvcName, namespace)
665+
Expect(err).ToNot(BeNil())
666+
Expect(pvc).To(BeNil())
667+
})
668+
})
669+
})
670+
512671
func TestCnsRegisterVolumeController(t *testing.T) {
513672
backOffDuration = make(map[types.NamespacedName]time.Duration)
514673

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

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

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

0 commit comments

Comments
 (0)