@@ -20,20 +20,23 @@ import (
20
20
"context"
21
21
"encoding/json"
22
22
"fmt"
23
+ "slices"
24
+ "strings"
25
+
23
26
v1alpha1 "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
24
27
"github.com/red-hat-storage/ocs-client-operator/pkg/templates"
25
28
"github.com/red-hat-storage/ocs-client-operator/pkg/utils"
26
- "slices"
27
- "strings"
28
29
29
30
csiopv1a1 "github.com/ceph/ceph-csi-operator/api/v1alpha1"
30
31
"github.com/go-logr/logr"
31
32
32
33
replicationv1alpha1 "github.com/csi-addons/kubernetes-csi-addons/api/replication.storage/v1alpha1"
34
+ groupsnapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1alpha1"
33
35
snapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
34
36
providerclient "github.com/red-hat-storage/ocs-operator/services/provider/api/v4/client"
35
37
corev1 "k8s.io/api/core/v1"
36
38
storagev1 "k8s.io/api/storage/v1"
39
+ extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
37
40
"k8s.io/apimachinery/pkg/api/errors"
38
41
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39
42
"k8s.io/apimachinery/pkg/runtime"
@@ -42,6 +45,7 @@ import (
42
45
"sigs.k8s.io/controller-runtime/pkg/cache"
43
46
"sigs.k8s.io/controller-runtime/pkg/client"
44
47
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
48
+ "sigs.k8s.io/controller-runtime/pkg/handler"
45
49
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
46
50
"sigs.k8s.io/controller-runtime/pkg/predicate"
47
51
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -52,8 +56,11 @@ const (
52
56
storageClaimAnnotation = "ocs.openshift.io/storageclaim"
53
57
keyRotationAnnotation = "keyrotation.csiaddons.openshift.io/schedule"
54
58
55
- pvClusterIDIndexName = "index:persistentVolumeClusterID"
56
- vscClusterIDIndexName = "index:volumeSnapshotContentCSIDriver"
59
+ pvClusterIDIndexName = "index:persistentVolumeClusterID"
60
+ vscClusterIDIndexName = "index:volumeSnapshotContentCSIDriver"
61
+ vgscClusterIDIndexName = "index:volumeGroupSnapshotContentCSIDriver"
62
+ volumeGroupSnapshotClassCrd = "groupsnapshot.storage.k8s.io/volumegroupsnapshotclass"
63
+ drClusterConfigCRDName = "drclusterconfigs.ramendr.openshift.io"
57
64
)
58
65
59
66
// StorageClaimReconciler reconciles a StorageClaim object
@@ -62,6 +69,7 @@ type StorageClaimReconciler struct {
62
69
cache.Cache
63
70
Scheme * runtime.Scheme
64
71
OperatorNamespace string
72
+ AvailableCrds map [string ]bool
65
73
66
74
log logr.Logger
67
75
ctx context.Context
@@ -104,13 +112,46 @@ func (r *StorageClaimReconciler) SetupWithManager(mgr ctrl.Manager) error {
104
112
return fmt .Errorf ("unable to set up FieldIndexer for VSC csi driver name: %v" , err )
105
113
}
106
114
115
+ if err := mgr .GetCache ().IndexField (ctx , & groupsnapapi.VolumeGroupSnapshotContent {}, vgscClusterIDIndexName , func (o client.Object ) []string {
116
+ vgsc := o .(* groupsnapapi.VolumeGroupSnapshotContent )
117
+ if vgsc != nil &&
118
+ slices .Contains (csiDrivers , vgsc .Spec .Driver ) &&
119
+ vgsc .Status != nil &&
120
+ vgsc .Status .VolumeGroupSnapshotHandle != nil {
121
+ parts := strings .Split (* vgsc .Status .VolumeGroupSnapshotHandle , "-" )
122
+ if len (parts ) == 9 {
123
+ // second entry in the volumeID is clusterID which is unique across the cluster
124
+ return []string {parts [2 ]}
125
+ }
126
+ }
127
+ return nil
128
+ }); err != nil {
129
+ return fmt .Errorf ("unable to set up FieldIndexer for VSC csi driver name: %v" , err )
130
+ }
131
+
107
132
generationChangePredicate := predicate.GenerationChangedPredicate {}
108
133
bldr := ctrl .NewControllerManagedBy (mgr ).
109
134
For (& v1alpha1.StorageClaim {}, builder .WithPredicates (generationChangePredicate )).
110
135
Owns (& storagev1.StorageClass {}).
111
136
Owns (& snapapi.VolumeSnapshotClass {}).
112
- Owns (& replicationv1alpha1.VolumeReplicationClass {}, builder .WithPredicates (generationChangePredicate )).
113
- Owns (& csiopv1a1.ClientProfile {}, builder .WithPredicates (generationChangePredicate ))
137
+ Owns (& csiopv1a1.ClientProfile {}, builder .WithPredicates (generationChangePredicate )).
138
+ Watches (
139
+ & extv1.CustomResourceDefinition {},
140
+ & handler.EnqueueRequestForObject {},
141
+ builder .WithPredicates (
142
+ utils .NamePredicate (volumeGroupSnapshotClassCrd ),
143
+ utils .EventTypePredicate (
144
+ ! r .AvailableCrds [volumeGroupSnapshotClassCrd ],
145
+ false ,
146
+ r .AvailableCrds [volumeGroupSnapshotClassCrd ],
147
+ false ,
148
+ ),
149
+ ),
150
+ builder .OnlyMetadata ,
151
+ )
152
+ if r .AvailableCrds [volumeGroupSnapshotClassCrd ] {
153
+ bldr = bldr .Owns (& groupsnapapi.VolumeGroupSnapshotClass {})
154
+ }
114
155
115
156
return bldr .Complete (r )
116
157
}
@@ -121,8 +162,10 @@ func (r *StorageClaimReconciler) SetupWithManager(mgr ctrl.Manager) error {
121
162
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete
122
163
//+kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get;list;watch;create;update;patch;delete
123
164
//+kubebuilder:rbac:groups=snapshot.storage.k8s.io,resources=volumesnapshotclasses,verbs=get;list;watch;create;delete
165
+ //+kubebuilder:rbac:groups=groupsnapshot.storage.k8s.io,resources=volumegroupsnapshotclasses,verbs=create;delete;get;list;watch
124
166
//+kubebuilder:rbac:groups=core,resources=persistentvolumes,verbs=get;list;watch
125
167
//+kubebuilder:rbac:groups=snapshot.storage.k8s.io,resources=volumesnapshotcontents,verbs=get;list;watch
168
+ //+kubebuilder:rbac:groups=groupsnapshot.storage.k8s.io,resources=volumegroupsnapshotcontents,verbs=get;list;watch
126
169
//+kubebuilder:rbac:groups=csi.ceph.io,resources=clientprofiles,verbs=get;list;update;create;watch;delete
127
170
//+kubebuilder:rbac:groups=replication.storage.openshift.io,resources=volumereplicationclass,verbs=get;list;watch;create;delete
128
171
@@ -141,6 +184,17 @@ func (r *StorageClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request
141
184
r .ctx = ctrllog .IntoContext (ctx , r .log )
142
185
r .log .Info ("Reconciling StorageClaim." )
143
186
187
+ crd := & metav1.PartialObjectMetadata {}
188
+ for _ , crdName := range []string {volumeGroupSnapshotClassCrd , drClusterConfigCRDName } {
189
+ crd .SetGroupVersionKind (extv1 .SchemeGroupVersion .WithKind ("CustomResourceDefinition" ))
190
+ crd .Name = crdName
191
+ if err := r .Client .Get (ctx , client .ObjectKeyFromObject (crd ), crd ); client .IgnoreNotFound (err ) != nil {
192
+ r .log .Error (err , "Failed to get CRD" , "CRD" , crdName )
193
+ return reconcile.Result {}, err
194
+ }
195
+ }
196
+ utils .AssertEqual (r .AvailableCrds [crd .Name ], crd .UID != "" , utils .ExitCodeThatShouldRestartTheProcess )
197
+
144
198
// Fetch the StorageClaim instance
145
199
r .storageClaim = & v1alpha1.StorageClaim {}
146
200
r .storageClaim .Name = req .Name
@@ -401,7 +455,31 @@ func (r *StorageClaimReconciler) reconcilePhases() (reconcile.Result, error) {
401
455
return nil
402
456
})
403
457
if err != nil {
404
- return reconcile.Result {}, fmt .Errorf ("failed to create or update VolumeSnapshotClass: %s" , err )
458
+ return reconcile.Result {}, fmt .Errorf ("failed to create or update StorageClass: %s" , err )
459
+ }
460
+ case "VolumeGroupSnapshotClass" :
461
+ // check for CRD availability
462
+ if r .AvailableCrds [("VolumeGroupSnapshotClass" )] {
463
+ var volumeGroupSnapshotClass * groupsnapapi.VolumeGroupSnapshotClass
464
+ data := map [string ]string {}
465
+ err = json .Unmarshal (resource .Data , & data )
466
+ if err != nil {
467
+ return reconcile.Result {}, fmt .Errorf ("failed to unmarshal StorageClaim configuration response: %v" , err )
468
+ }
469
+ data ["csi.storage.k8s.io/group-snapshotter-secret-namespace" ] = r .OperatorNamespace
470
+ // generate a new clusterID for cephfs subvolumegroup, as
471
+ // storageclaim is clusterscoped resources using its
472
+ // hash as the clusterID
473
+ data ["clusterID" ] = r .storageClaimHash
474
+ volumeGroupSnapshotClass = r .getCephDriverVolumeGroupSnapshotClass (resource .Labels , resource .Annotations )
475
+ utils .AddAnnotation (volumeGroupSnapshotClass , storageClaimAnnotation , r .storageClaim .Name )
476
+ err = utils .CreateOrReplace (r .ctx , r .Client , volumeGroupSnapshotClass , func () error {
477
+ volumeGroupSnapshotClass .Parameters = data
478
+ return nil
479
+ })
480
+ if err != nil {
481
+ return reconcile.Result {}, fmt .Errorf ("failed to create or update StorageClass: %s" , err )
482
+ }
405
483
}
406
484
case "VolumeReplicationClass" :
407
485
vrc := & replicationv1alpha1.VolumeReplicationClass {}
@@ -466,6 +544,11 @@ func (r *StorageClaimReconciler) reconcilePhases() (reconcile.Result, error) {
466
544
} else if exist {
467
545
return reconcile.Result {}, fmt .Errorf ("one or more volumesnapshotcontents exist that are dependent on storageclaim %s" , r .storageClaim .Name )
468
546
}
547
+ if exist , err := r .hasVolumeGroupSnapshotContents (); err != nil {
548
+ return reconcile.Result {}, fmt .Errorf ("failed to verify volumegroupsnapshotcontents dependent on storageclaim %q: %v" , r .storageClaim .Name , err )
549
+ } else if exist {
550
+ return reconcile.Result {}, fmt .Errorf ("one or more volumegroupsnapshotcontents exist that are dependent on storageclaim %s" , r .storageClaim .Name )
551
+ }
469
552
470
553
// Call `RevokeStorageClaim` service on the provider server with StorageClaim as a request message.
471
554
// Check if StorageClaim is still exists (it might have been manually removed during the StorageClass
@@ -550,6 +633,20 @@ func (r *StorageClaimReconciler) getCephRBDVolumeSnapshotClass() *snapapi.Volume
550
633
return volumesnapshotclass
551
634
}
552
635
636
+ func (r * StorageClaimReconciler ) getCephDriverVolumeGroupSnapshotClass (
637
+ labels map [string ]string , annotations map [string ]string ) * groupsnapapi.VolumeGroupSnapshotClass {
638
+ volumegroupsnapshotclass := & groupsnapapi.VolumeGroupSnapshotClass {
639
+ ObjectMeta : metav1.ObjectMeta {
640
+ Name : r .storageClaim .Name ,
641
+ Labels : labels ,
642
+ Annotations : annotations ,
643
+ },
644
+ Driver : templates .RBDDriverName ,
645
+ DeletionPolicy : snapapi .VolumeSnapshotContentDelete ,
646
+ }
647
+ return volumegroupsnapshotclass
648
+ }
649
+
553
650
func (r * StorageClaimReconciler ) get (obj client.Object ) error {
554
651
key := client .ObjectKeyFromObject (obj )
555
652
return r .Client .Get (r .ctx , key , obj )
@@ -594,3 +691,17 @@ func (r *StorageClaimReconciler) hasVolumeSnapshotContents() (bool, error) {
594
691
595
692
return false , nil
596
693
}
694
+
695
+ func (r * StorageClaimReconciler ) hasVolumeGroupSnapshotContents () (bool , error ) {
696
+ vscList := & groupsnapapi.VolumeGroupSnapshotContentList {}
697
+ if err := r .list (vscList , client.MatchingFields {vgscClusterIDIndexName : r .storageClaimHash }); err != nil {
698
+ return false , fmt .Errorf ("failed to list volume group snapshot content resources: %v" , err )
699
+ }
700
+
701
+ if len (vscList .Items ) != 0 {
702
+ r .log .Info (fmt .Sprintf ("VolumeGroupSnapshotContent referring storageclaim %q exists" , r .storageClaim .Name ))
703
+ return true , nil
704
+ }
705
+
706
+ return false , nil
707
+ }
0 commit comments