@@ -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/v1beta1"
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,10 @@ 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"
57
63
)
58
64
59
65
// StorageClaimReconciler reconciles a StorageClaim object
@@ -62,6 +68,7 @@ type StorageClaimReconciler struct {
62
68
cache.Cache
63
69
Scheme * runtime.Scheme
64
70
OperatorNamespace string
71
+ AvailableCrds map [string ]bool
65
72
66
73
log logr.Logger
67
74
ctx context.Context
@@ -104,13 +111,46 @@ func (r *StorageClaimReconciler) SetupWithManager(mgr ctrl.Manager) error {
104
111
return fmt .Errorf ("unable to set up FieldIndexer for VSC csi driver name: %v" , err )
105
112
}
106
113
114
+ if err := mgr .GetCache ().IndexField (ctx , & groupsnapapi.VolumeGroupSnapshotContent {}, vgscClusterIDIndexName , func (o client.Object ) []string {
115
+ vgsc := o .(* groupsnapapi.VolumeGroupSnapshotContent )
116
+ if vgsc != nil &&
117
+ slices .Contains (csiDrivers , vgsc .Spec .Driver ) &&
118
+ vgsc .Status != nil &&
119
+ vgsc .Status .VolumeGroupSnapshotHandle != nil {
120
+ parts := strings .Split (* vgsc .Status .VolumeGroupSnapshotHandle , "-" )
121
+ if len (parts ) == 9 {
122
+ // second entry in the volumeID is clusterID which is unique across the cluster
123
+ return []string {parts [2 ]}
124
+ }
125
+ }
126
+ return nil
127
+ }); err != nil {
128
+ return fmt .Errorf ("unable to set up FieldIndexer for VSC csi driver name: %v" , err )
129
+ }
130
+
107
131
generationChangePredicate := predicate.GenerationChangedPredicate {}
108
132
bldr := ctrl .NewControllerManagedBy (mgr ).
109
133
For (& v1alpha1.StorageClaim {}, builder .WithPredicates (generationChangePredicate )).
110
134
Owns (& storagev1.StorageClass {}).
111
135
Owns (& snapapi.VolumeSnapshotClass {}).
112
- Owns (& replicationv1alpha1.VolumeReplicationClass {}, builder .WithPredicates (generationChangePredicate )).
113
- Owns (& csiopv1a1.ClientProfile {}, builder .WithPredicates (generationChangePredicate ))
136
+ Owns (& csiopv1a1.ClientProfile {}, builder .WithPredicates (generationChangePredicate )).
137
+ Watches (
138
+ & extv1.CustomResourceDefinition {},
139
+ & handler.EnqueueRequestForObject {},
140
+ builder .WithPredicates (
141
+ utils .NamePredicate (volumeGroupSnapshotClassCrd ),
142
+ utils .EventTypePredicate (
143
+ ! r .AvailableCrds [volumeGroupSnapshotClassCrd ],
144
+ false ,
145
+ r .AvailableCrds [volumeGroupSnapshotClassCrd ],
146
+ false ,
147
+ ),
148
+ ),
149
+ builder .OnlyMetadata ,
150
+ )
151
+ if r .AvailableCrds [volumeGroupSnapshotClassCrd ] {
152
+ bldr = bldr .Owns (& groupsnapapi.VolumeGroupSnapshotClass {})
153
+ }
114
154
115
155
return bldr .Complete (r )
116
156
}
@@ -121,8 +161,10 @@ func (r *StorageClaimReconciler) SetupWithManager(mgr ctrl.Manager) error {
121
161
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete
122
162
//+kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get;list;watch;create;update;patch;delete
123
163
//+kubebuilder:rbac:groups=snapshot.storage.k8s.io,resources=volumesnapshotclasses,verbs=get;list;watch;create;delete
164
+ //+kubebuilder:rbac:groups=groupsnapshot.storage.k8s.io,resources=volumegroupsnapshotclasses,verbs=create;delete;get;list;watch
124
165
//+kubebuilder:rbac:groups=core,resources=persistentvolumes,verbs=get;list;watch
125
166
//+kubebuilder:rbac:groups=snapshot.storage.k8s.io,resources=volumesnapshotcontents,verbs=get;list;watch
167
+ //+kubebuilder:rbac:groups=groupsnapshot.storage.k8s.io,resources=volumegroupsnapshotcontents,verbs=get;list;watch
126
168
//+kubebuilder:rbac:groups=csi.ceph.io,resources=clientprofiles,verbs=get;list;update;create;watch;delete
127
169
//+kubebuilder:rbac:groups=replication.storage.openshift.io,resources=volumereplicationclasses,verbs=get;list;watch;create;delete
128
170
@@ -141,6 +183,17 @@ func (r *StorageClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request
141
183
r .ctx = ctrllog .IntoContext (ctx , r .log )
142
184
r .log .Info ("Reconciling StorageClaim." )
143
185
186
+ crd := & metav1.PartialObjectMetadata {}
187
+ for _ , crdName := range []string {volumeGroupSnapshotClassCrd } {
188
+ crd .SetGroupVersionKind (extv1 .SchemeGroupVersion .WithKind ("CustomResourceDefinition" ))
189
+ crd .Name = crdName
190
+ if err := r .Client .Get (ctx , client .ObjectKeyFromObject (crd ), crd ); client .IgnoreNotFound (err ) != nil {
191
+ r .log .Error (err , "Failed to get CRD" , "CRD" , crdName )
192
+ return reconcile.Result {}, err
193
+ }
194
+ }
195
+ utils .AssertEqual (r .AvailableCrds [crd .Name ], crd .UID != "" , utils .ExitCodeThatShouldRestartTheProcess )
196
+
144
197
// Fetch the StorageClaim instance
145
198
r .storageClaim = & v1alpha1.StorageClaim {}
146
199
r .storageClaim .Name = req .Name
@@ -401,7 +454,35 @@ func (r *StorageClaimReconciler) reconcilePhases() (reconcile.Result, error) {
401
454
return nil
402
455
})
403
456
if err != nil {
404
- return reconcile.Result {}, fmt .Errorf ("failed to create or update VolumeSnapshotClass: %s" , err )
457
+ return reconcile.Result {}, fmt .Errorf ("failed to create or update StorageClass: %s" , err )
458
+ }
459
+ case "VolumeGroupSnapshotClass" :
460
+ // check for CRD availability
461
+ if r .AvailableCrds [("VolumeGroupSnapshotClass" )] {
462
+ var volumeGroupSnapshotClass * groupsnapapi.VolumeGroupSnapshotClass
463
+ data := map [string ]string {}
464
+ err = json .Unmarshal (resource .Data , & data )
465
+ if err != nil {
466
+ return reconcile.Result {}, fmt .Errorf ("failed to unmarshal StorageClaim configuration response: %v" , err )
467
+ }
468
+ data ["csi.storage.k8s.io/group-snapshotter-secret-namespace" ] = r .OperatorNamespace
469
+ // generate a new clusterID for cephfs subvolumegroup, as
470
+ // storageclaim is clusterscoped resources using its
471
+ // hash as the clusterID
472
+ data ["clusterID" ] = r .storageClaimHash
473
+ driverName := templates .CephFsDriverName
474
+ if strings .Contains (strings .ToLower (resource .Name ), "rbd" ) {
475
+ driverName = templates .RBDDriverName
476
+ }
477
+ volumeGroupSnapshotClass = r .getCephDriverVolumeGroupSnapshotClass (resource .Labels , resource .Annotations , driverName )
478
+ utils .AddAnnotation (volumeGroupSnapshotClass , storageClaimAnnotation , r .storageClaim .Name )
479
+ err = utils .CreateOrReplace (r .ctx , r .Client , volumeGroupSnapshotClass , func () error {
480
+ volumeGroupSnapshotClass .Parameters = data
481
+ return nil
482
+ })
483
+ if err != nil {
484
+ return reconcile.Result {}, fmt .Errorf ("failed to create or update VolumeGroupSnapshotClass: %s" , err )
485
+ }
405
486
}
406
487
case "VolumeReplicationClass" :
407
488
vrc := & replicationv1alpha1.VolumeReplicationClass {}
@@ -466,6 +547,11 @@ func (r *StorageClaimReconciler) reconcilePhases() (reconcile.Result, error) {
466
547
} else if exist {
467
548
return reconcile.Result {}, fmt .Errorf ("one or more volumesnapshotcontents exist that are dependent on storageclaim %s" , r .storageClaim .Name )
468
549
}
550
+ if exist , err := r .hasVolumeGroupSnapshotContents (); err != nil {
551
+ return reconcile.Result {}, fmt .Errorf ("failed to verify volumegroupsnapshotcontents dependent on storageclaim %q: %v" , r .storageClaim .Name , err )
552
+ } else if exist {
553
+ return reconcile.Result {}, fmt .Errorf ("one or more volumegroupsnapshotcontents exist that are dependent on storageclaim %s" , r .storageClaim .Name )
554
+ }
469
555
470
556
// Call `RevokeStorageClaim` service on the provider server with StorageClaim as a request message.
471
557
// Check if StorageClaim is still exists (it might have been manually removed during the StorageClass
@@ -550,6 +636,20 @@ func (r *StorageClaimReconciler) getCephRBDVolumeSnapshotClass() *snapapi.Volume
550
636
return volumesnapshotclass
551
637
}
552
638
639
+ func (r * StorageClaimReconciler ) getCephDriverVolumeGroupSnapshotClass (
640
+ labels map [string ]string , annotations map [string ]string , driver string ) * groupsnapapi.VolumeGroupSnapshotClass {
641
+ volumegroupsnapshotclass := & groupsnapapi.VolumeGroupSnapshotClass {
642
+ ObjectMeta : metav1.ObjectMeta {
643
+ Name : r .storageClaim .Name ,
644
+ Labels : labels ,
645
+ Annotations : annotations ,
646
+ },
647
+ Driver : driver ,
648
+ DeletionPolicy : snapapi .VolumeSnapshotContentDelete ,
649
+ }
650
+ return volumegroupsnapshotclass
651
+ }
652
+
553
653
func (r * StorageClaimReconciler ) get (obj client.Object ) error {
554
654
key := client .ObjectKeyFromObject (obj )
555
655
return r .Client .Get (r .ctx , key , obj )
@@ -594,3 +694,17 @@ func (r *StorageClaimReconciler) hasVolumeSnapshotContents() (bool, error) {
594
694
595
695
return false , nil
596
696
}
697
+
698
+ func (r * StorageClaimReconciler ) hasVolumeGroupSnapshotContents () (bool , error ) {
699
+ vscList := & groupsnapapi.VolumeGroupSnapshotContentList {}
700
+ if err := r .list (vscList , client.MatchingFields {vgscClusterIDIndexName : r .storageClaimHash }); err != nil {
701
+ return false , fmt .Errorf ("failed to list volume group snapshot content resources: %v" , err )
702
+ }
703
+
704
+ if len (vscList .Items ) != 0 {
705
+ r .log .Info (fmt .Sprintf ("VolumeGroupSnapshotContent referring storageclaim %q exists" , r .storageClaim .Name ))
706
+ return true , nil
707
+ }
708
+
709
+ return false , nil
710
+ }
0 commit comments