@@ -319,7 +319,8 @@ func validateGuestPVCOperation(ctx context.Context, req *admissionv1.AdmissionRe
319319
320320 // ValidateLinkedCloneRequest validates the various conditions necessary for a valid linkedclone request.
321321 // 1. The PVC datasource needs to be of type VolumeSnapshot
322- // 2. The storageclass associated LinkedClone PVC should be the same the source PVC
322+ // 2. The svStorageClass parameter in the storageclass associated with the LinkedClone PVC
323+ // should match the svStorageClass in the storageclass of the source PVC
323324 // 3. The size should be the same as the source PVC
324325 // 4. Should not be a second level LinkedClone
325326 // 5. VS is not under deletion
@@ -462,14 +463,131 @@ func validateGuestPVCOperation(ctx context.Context, req *admissionv1.AdmissionRe
462463 },
463464 }
464465 }
465-
466- // The storageclass associated LinkedClone PVC should be the same the source PVC
466+ // The svStorageClass parameter in the storageclass associated with the LinkedClone PVC
467+ // should be the same as the svStorageClass in the storageclass of the source PVC
467468 sourcePVCStorageClassName := sourcePVC .Spec .StorageClassName
468- same := strings .Compare (* pvc .Spec .StorageClassName , * sourcePVCStorageClassName )
469- if same != 0 {
470- errMsg := fmt .Sprintf ("StorageClass mismatch, Namespace: %s, LinkedClone StorageClass: " +
471- "%s, source PVC StorageClass: %s" , sourcePVC .Namespace , * pvc .Spec .StorageClassName ,
472- * sourcePVCStorageClassName )
469+ linkedClonePVCStorageClassName := pvc .Spec .StorageClassName
470+
471+ // Validate source PVC StorageClass
472+ if sourcePVCStorageClassName == nil {
473+ errMsg := "source PVC does not have a StorageClass specified, " +
474+ "please specify a StorageClass for linked clone creation"
475+ return & admissionv1.AdmissionResponse {
476+ Allowed : false ,
477+ Result : & metav1.Status {
478+ Message : errMsg ,
479+ },
480+ }
481+ }
482+ if * sourcePVCStorageClassName == "" {
483+ errMsg := "source PVC has an empty StorageClass name and cannot be validated for linked clone creation"
484+ return & admissionv1.AdmissionResponse {
485+ Allowed : false ,
486+ Result : & metav1.Status {
487+ Message : errMsg ,
488+ },
489+ }
490+ }
491+
492+ // Validate LinkedClone PVC StorageClass
493+ if linkedClonePVCStorageClassName == nil {
494+ errMsg := "LinkedClone PVC does not have a StorageClass specified," +
495+ "please specify a StorageClass for linked clone creation"
496+ return & admissionv1.AdmissionResponse {
497+ Allowed : false ,
498+ Result : & metav1.Status {
499+ Message : errMsg ,
500+ },
501+ }
502+ }
503+ if * linkedClonePVCStorageClassName == "" {
504+ errMsg := "LinkedClone PVC has an empty StorageClass name and cannot be validated for linked clone creation"
505+ return & admissionv1.AdmissionResponse {
506+ Allowed : false ,
507+ Result : & metav1.Status {
508+ Message : errMsg ,
509+ },
510+ }
511+ }
512+
513+ // Retrieve the StorageClass objects
514+ sourceStorageClass , err := k8sClient .StorageV1 ().StorageClasses ().Get (ctx , * sourcePVCStorageClassName ,
515+ metav1.GetOptions {})
516+ if err != nil {
517+ errMsg := fmt .Sprintf ("error getting source PVC StorageClass %s from api server: %v" ,
518+ * sourcePVCStorageClassName , err )
519+ return & admissionv1.AdmissionResponse {
520+ Allowed : false ,
521+ Result : & metav1.Status {
522+ Message : errMsg ,
523+ },
524+ }
525+ }
526+
527+ linkedCloneStorageClass , err := k8sClient .StorageV1 ().StorageClasses ().Get (ctx , * linkedClonePVCStorageClassName ,
528+ metav1.GetOptions {})
529+ if err != nil {
530+ errMsg := fmt .Sprintf ("error getting LinkedClone PVC StorageClass %s from api server: %v" ,
531+ * linkedClonePVCStorageClassName , err )
532+ return & admissionv1.AdmissionResponse {
533+ Allowed : false ,
534+ Result : & metav1.Status {
535+ Message : errMsg ,
536+ },
537+ }
538+ }
539+
540+ // Extract svStorageClass from StorageClass parameters (case-insensitive lookup)
541+ var sourceSvStorageClass string
542+ var sourceHasSvStorageClass bool
543+ for param , value := range sourceStorageClass .Parameters {
544+ if strings .ToLower (param ) == common .AttributeSupervisorStorageClass {
545+ sourceSvStorageClass = value
546+ sourceHasSvStorageClass = true
547+ break
548+ }
549+ }
550+
551+ var linkedCloneSvStorageClass string
552+ var linkedCloneHasSvStorageClass bool
553+ for param , value := range linkedCloneStorageClass .Parameters {
554+ if strings .ToLower (param ) == common .AttributeSupervisorStorageClass {
555+ linkedCloneSvStorageClass = value
556+ linkedCloneHasSvStorageClass = true
557+ break
558+ }
559+ }
560+
561+ // Both storage classes must have svStorageClass parameter
562+ if ! sourceHasSvStorageClass {
563+ errMsg := fmt .Sprintf ("source PVC StorageClass %s does not have %s parameter" ,
564+ * sourcePVCStorageClassName , common .AttributeSupervisorStorageClass )
565+ return & admissionv1.AdmissionResponse {
566+ Allowed : false ,
567+ Result : & metav1.Status {
568+ Message : errMsg ,
569+ },
570+ }
571+ }
572+
573+ if ! linkedCloneHasSvStorageClass {
574+ errMsg := fmt .Sprintf ("LinkedClone PVC StorageClass %s does not have %s parameter" ,
575+ * linkedClonePVCStorageClassName , common .AttributeSupervisorStorageClass )
576+ return & admissionv1.AdmissionResponse {
577+ Allowed : false ,
578+ Result : & metav1.Status {
579+ Message : errMsg ,
580+ },
581+ }
582+ }
583+
584+ // Compare svStorageClass values
585+ if sourceSvStorageClass != linkedCloneSvStorageClass {
586+ errMsg := fmt .Sprintf ("StorageClass svStorageClass mismatch, Namespace: %s, " +
587+ "LinkedClone StorageClass: %s (svStorageClass: %s), " +
588+ "source PVC StorageClass: %s (svStorageClass: %s)" ,
589+ sourcePVC .Namespace , * linkedClonePVCStorageClassName , linkedCloneSvStorageClass ,
590+ * sourcePVCStorageClassName , sourceSvStorageClass )
473591 return & admissionv1.AdmissionResponse {
474592 Allowed : false ,
475593 Result : & metav1.Status {
0 commit comments