Skip to content

Commit c22cf93

Browse files
Merge pull request #280 from openshift-cherrypick-robot/cherry-pick-264-to-release-4.18
DFBUGS-925: [release-4.18] controllers: new controller for maintenance mode
2 parents b1babc6 + 75dc0d6 commit c22cf93

File tree

14 files changed

+339
-45
lines changed

14 files changed

+339
-45
lines changed

api/v1alpha1/storageclient_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type StorageClientSpec struct {
5050
type StorageClientStatus struct {
5151
Phase storageClientPhase `json:"phase,omitempty"`
5252

53+
InMaintenanceMode bool `json:"inMaintenanceMode"`
54+
5355
// ConsumerID will hold the identity of this cluster inside the attached provider cluster
5456
ConsumerID string `json:"id,omitempty"`
5557
}

bundle/manifests/ocs-client-operator.clusterserviceversion.yaml

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ metadata:
77
categories: Storage
88
console.openshift.io/plugins: '["odf-client-console"]'
99
containerImage: quay.io/ocs-dev/ocs-client-operator:latest
10-
createdAt: "2024-11-22T04:24:54Z"
10+
createdAt: "2024-11-22T06:22:20Z"
1111
description: OpenShift Data Foundation client operator enables consumption of
1212
storage services from a remote centralized OpenShift Data Foundation provider
1313
cluster.
@@ -241,6 +241,15 @@ spec:
241241
- patch
242242
- update
243243
- watch
244+
- apiGroups:
245+
- ocs.openshift.io
246+
resources:
247+
- storageclaims
248+
- storageclients
249+
verbs:
250+
- get
251+
- list
252+
- watch
244253
- apiGroups:
245254
- ocs.openshift.io
246255
resources:
@@ -315,6 +324,25 @@ spec:
315324
- list
316325
- update
317326
- watch
327+
- apiGroups:
328+
- ramendr.openshift.io
329+
resources:
330+
- maintenancemodes
331+
verbs:
332+
- create
333+
- delete
334+
- get
335+
- list
336+
- update
337+
- watch
338+
- apiGroups:
339+
- ramendr.openshift.io
340+
resources:
341+
- maintenancemodes/status
342+
verbs:
343+
- get
344+
- patch
345+
- update
318346
- apiGroups:
319347
- replication.storage.openshift.io
320348
resources:

bundle/manifests/ocs.openshift.io_storageclients.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,12 @@ spec:
6565
description: ConsumerID will hold the identity of this cluster inside
6666
the attached provider cluster
6767
type: string
68+
inMaintenanceMode:
69+
type: boolean
6870
phase:
6971
type: string
72+
required:
73+
- inMaintenanceMode
7074
type: object
7175
type: object
7276
served: true

cmd/main.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func main() {
154154
os.Exit(1)
155155
}
156156

157-
_, err = getAvailableCRDNames(context.Background(), apiClient)
157+
availCrds, err := getAvailableCRDNames(context.Background(), apiClient)
158158
if err != nil {
159159
setupLog.Error(err, "Unable get a list of available CRD names")
160160
os.Exit(1)
@@ -204,11 +204,22 @@ func main() {
204204
Scheme: mgr.GetScheme(),
205205
OperatorNamespace: utils.GetOperatorNamespace(),
206206
ConsolePort: int32(consolePort),
207+
AvailableCrds: availCrds,
207208
}).SetupWithManager(mgr); err != nil {
208209
setupLog.Error(err, "unable to create controller", "controller", "OperatorConfigMapReconciler")
209210
os.Exit(1)
210211
}
211212

213+
if availCrds[controller.MaintenanceModeCRDName] {
214+
if err = (&controller.MaintenanceModeReconciler{
215+
Client: mgr.GetClient(),
216+
Scheme: mgr.GetScheme(),
217+
}).SetupWithManager(mgr); err != nil {
218+
setupLog.Error(err, "unable to create controller", "controller", "MaintenanceMode")
219+
os.Exit(1)
220+
}
221+
}
222+
212223
setupLog.Info("starting manager")
213224
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
214225
setupLog.Error(err, "problem running manager")

config/crd/bases/ocs.openshift.io_storageclients.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,12 @@ spec:
6565
description: ConsumerID will hold the identity of this cluster inside
6666
the attached provider cluster
6767
type: string
68+
inMaintenanceMode:
69+
type: boolean
6870
phase:
6971
type: string
72+
required:
73+
- inMaintenanceMode
7074
type: object
7175
type: object
7276
served: true

config/rbac/role.yaml

+28
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,15 @@ rules:
198198
- patch
199199
- update
200200
- watch
201+
- apiGroups:
202+
- ocs.openshift.io
203+
resources:
204+
- storageclaims
205+
- storageclients
206+
verbs:
207+
- get
208+
- list
209+
- watch
201210
- apiGroups:
202211
- ocs.openshift.io
203212
resources:
@@ -272,6 +281,25 @@ rules:
272281
- list
273282
- update
274283
- watch
284+
- apiGroups:
285+
- ramendr.openshift.io
286+
resources:
287+
- maintenancemodes
288+
verbs:
289+
- create
290+
- delete
291+
- get
292+
- list
293+
- update
294+
- watch
295+
- apiGroups:
296+
- ramendr.openshift.io
297+
resources:
298+
- maintenancemodes/status
299+
verbs:
300+
- get
301+
- patch
302+
- update
275303
- apiGroups:
276304
- replication.storage.openshift.io
277305
resources:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package controller
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/go-logr/logr"
7+
ramenv1alpha1 "github.com/ramendr/ramen/api/v1alpha1"
8+
"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
9+
"github.com/red-hat-storage/ocs-client-operator/pkg/utils"
10+
providerclient "github.com/red-hat-storage/ocs-operator/services/provider/api/v4/client"
11+
"k8s.io/apimachinery/pkg/api/meta"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
"sigs.k8s.io/controller-runtime/pkg/builder"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"sigs.k8s.io/controller-runtime/pkg/event"
18+
"sigs.k8s.io/controller-runtime/pkg/handler"
19+
"sigs.k8s.io/controller-runtime/pkg/log"
20+
"sigs.k8s.io/controller-runtime/pkg/predicate"
21+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22+
"slices"
23+
)
24+
25+
const (
26+
MaintenanceModeCRDName = "maintenancemodes.ramendr.openshift.io"
27+
)
28+
29+
// MaintenanceModeReconciler reconciles a ClusterVersion object
30+
type MaintenanceModeReconciler struct {
31+
client.Client
32+
Scheme *runtime.Scheme
33+
34+
log logr.Logger
35+
ctx context.Context
36+
}
37+
38+
// SetupWithManager sets up the controller with the Manager.
39+
func (r *MaintenanceModeReconciler) SetupWithManager(mgr ctrl.Manager) error {
40+
generationChangePredicate := predicate.GenerationChangedPredicate{}
41+
maintenanceModeChangedPredicate := predicate.Funcs{
42+
UpdateFunc: func(e event.UpdateEvent) bool {
43+
if e.ObjectOld == nil || e.ObjectNew == nil {
44+
return false
45+
}
46+
oldObj := e.ObjectOld.(*v1alpha1.StorageClient)
47+
newObj := e.ObjectNew.(*v1alpha1.StorageClient)
48+
return oldObj.Status.InMaintenanceMode != newObj.Status.InMaintenanceMode
49+
},
50+
}
51+
return ctrl.NewControllerManagedBy(mgr).
52+
Named("MaintenanceMode").
53+
Watches(
54+
&ramenv1alpha1.MaintenanceMode{},
55+
&handler.EnqueueRequestForObject{},
56+
builder.WithPredicates(generationChangePredicate),
57+
).
58+
Watches(
59+
&v1alpha1.StorageClaim{},
60+
&handler.EnqueueRequestForObject{},
61+
builder.WithPredicates(generationChangePredicate),
62+
).
63+
Watches(
64+
&v1alpha1.StorageClient{},
65+
&handler.EnqueueRequestForObject{},
66+
builder.WithPredicates(
67+
generationChangePredicate,
68+
maintenanceModeChangedPredicate,
69+
),
70+
).
71+
Complete(r)
72+
}
73+
74+
//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=maintenancemodes,verbs=get;list;update;create;watch;delete
75+
//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=maintenancemodes/status,verbs=get;update;patch
76+
//+kubebuilder:rbac:groups=ocs.openshift.io,resources=storageclients;storageclaims,verbs=get;list;watch
77+
78+
func (r *MaintenanceModeReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) {
79+
r.ctx = ctx
80+
r.log = log.FromContext(ctx)
81+
r.log.Info("Starting reconcile")
82+
83+
nameToStorageClient := map[string]*v1alpha1.StorageClient{}
84+
85+
maintenanceModes := &ramenv1alpha1.MaintenanceModeList{}
86+
if err := r.list(maintenanceModes); err != nil {
87+
r.log.Error(err, "failed to list the MaintenanceMode CRs")
88+
return reconcile.Result{}, err
89+
}
90+
91+
for i := range maintenanceModes.Items {
92+
mm := &maintenanceModes.Items[i]
93+
sc := &v1alpha1.StorageClaim{}
94+
// MMode's TargetID is replicationID, which in our case is storageClaim name
95+
sc.Name = mm.Spec.TargetID
96+
if err := r.get(sc); err != nil {
97+
return ctrl.Result{}, err
98+
}
99+
clientName := sc.Spec.StorageClient
100+
if clientName == "" {
101+
return ctrl.Result{}, fmt.Errorf("StorageClaim %s does not have a StorageClient defined", sc.Name)
102+
}
103+
if nameToStorageClient[clientName] == nil {
104+
storageClient := &v1alpha1.StorageClient{}
105+
storageClient.Name = clientName
106+
if err := r.get(storageClient); err != nil {
107+
return ctrl.Result{}, err
108+
}
109+
nameToStorageClient[clientName] = storageClient
110+
}
111+
if nameToStorageClient[clientName].Status.InMaintenanceMode {
112+
if err := r.updateStatusCompletedForMM(mm); err != nil {
113+
return ctrl.Result{}, fmt.Errorf("failed to update status for MaintenanceMode %s: %w", mm.Name, err)
114+
}
115+
}
116+
}
117+
118+
storageClients := &v1alpha1.StorageClientList{}
119+
if err := r.list(storageClients); err != nil {
120+
r.log.Error(err, "failed to list the Storage Clients")
121+
return reconcile.Result{}, err
122+
}
123+
124+
for i := range storageClients.Items {
125+
storageClient := &storageClients.Items[i]
126+
_, needsMaintenanceMode := nameToStorageClient[storageClient.Name]
127+
if needsMaintenanceMode != storageClient.Status.InMaintenanceMode {
128+
if err := r.toggleMaintenanceModeForClient(storageClient, needsMaintenanceMode); err != nil {
129+
return ctrl.Result{}, err
130+
}
131+
}
132+
}
133+
134+
return ctrl.Result{}, nil
135+
}
136+
137+
func (r *MaintenanceModeReconciler) toggleMaintenanceModeForClient(storageClient *v1alpha1.StorageClient, enable bool) error {
138+
providerClient, err := providerclient.NewProviderClient(
139+
r.ctx,
140+
storageClient.Spec.StorageProviderEndpoint,
141+
utils.OcsClientTimeout,
142+
)
143+
if err != nil {
144+
return fmt.Errorf(
145+
"failed to create provider client with endpoint %v: %v",
146+
storageClient.Spec.StorageProviderEndpoint,
147+
err,
148+
)
149+
}
150+
// Close client-side connections.
151+
defer providerClient.Close()
152+
153+
_, err = providerClient.RequestMaintenanceMode(r.ctx, storageClient.Status.ConsumerID, enable)
154+
if err != nil {
155+
return fmt.Errorf("failed to Request maintenance mode: %v", err)
156+
}
157+
return nil
158+
}
159+
160+
func (r *MaintenanceModeReconciler) updateStatusCompletedForMM(maintenanceMode *ramenv1alpha1.MaintenanceMode) error {
161+
// Ramen reads the State and Conditions in order to determine that the MaintenanceMode is Completed
162+
163+
condition := metav1.Condition{
164+
Type: string(ramenv1alpha1.MModeConditionFailoverActivated),
165+
Status: metav1.ConditionTrue,
166+
Reason: string(ramenv1alpha1.MModeStateCompleted),
167+
ObservedGeneration: maintenanceMode.Generation,
168+
}
169+
170+
updateRequired := false
171+
updateRequired = updateRequired || (maintenanceMode.Status.State != ramenv1alpha1.MModeStateCompleted)
172+
updateRequired = updateRequired || slices.Contains(maintenanceMode.Status.Conditions, condition)
173+
174+
if updateRequired {
175+
maintenanceMode.Status.State = ramenv1alpha1.MModeStateCompleted
176+
maintenanceMode.Status.ObservedGeneration = maintenanceMode.Generation
177+
meta.SetStatusCondition(&maintenanceMode.Status.Conditions, condition)
178+
179+
if err := r.Client.Status().Update(r.ctx, maintenanceMode); err != nil {
180+
return err
181+
}
182+
}
183+
return nil
184+
}
185+
186+
func (r *MaintenanceModeReconciler) list(obj client.ObjectList, opts ...client.ListOption) error {
187+
return r.List(r.ctx, obj, opts...)
188+
}
189+
190+
func (r *MaintenanceModeReconciler) get(obj client.Object, opts ...client.GetOption) error {
191+
return r.Get(r.ctx, client.ObjectKeyFromObject(obj), obj, opts...)
192+
}

internal/controller/operatorconfigmap_controller.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type OperatorConfigMapReconciler struct {
8080
OperatorNamespace string
8181
ConsolePort int32
8282
Scheme *runtime.Scheme
83+
AvailableCrds map[string]bool
8384

8485
log logr.Logger
8586
ctx context.Context
@@ -157,7 +158,20 @@ func (c *OperatorConfigMapReconciler) SetupWithManager(mgr ctrl.Manager) error {
157158
Owns(&csiopv1a1.OperatorConfig{}, builder.WithPredicates(generationChangePredicate)).
158159
Owns(&csiopv1a1.Driver{}, builder.WithPredicates(generationChangePredicate)).
159160
Watches(&configv1.ClusterVersion{}, enqueueConfigMapRequest, clusterVersionPredicates).
160-
Watches(&extv1.CustomResourceDefinition{}, enqueueConfigMapRequest, builder.OnlyMetadata).
161+
Watches(
162+
&extv1.CustomResourceDefinition{},
163+
enqueueConfigMapRequest,
164+
builder.WithPredicates(
165+
utils.NamePredicate(MaintenanceModeCRDName),
166+
utils.EventTypePredicate(
167+
!c.AvailableCrds[MaintenanceModeCRDName],
168+
false,
169+
c.AvailableCrds[MaintenanceModeCRDName],
170+
false,
171+
),
172+
),
173+
builder.OnlyMetadata,
174+
).
161175
Watches(&opv1a1.Subscription{}, enqueueConfigMapRequest, subscriptionPredicates).
162176
Watches(&admrv1.ValidatingWebhookConfiguration{}, enqueueConfigMapRequest, webhookPredicates).
163177
Watches(&v1alpha1.StorageClient{}, enqueueConfigMapRequest, builder.WithPredicates(predicate.AnnotationChangedPredicate{}))
@@ -188,6 +202,15 @@ func (c *OperatorConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Re
188202
c.log = log.FromContext(ctx, "OperatorConfigMap", req)
189203
c.log.Info("Reconciling OperatorConfigMap")
190204

205+
crd := &metav1.PartialObjectMetadata{}
206+
crd.SetGroupVersionKind(extv1.SchemeGroupVersion.WithKind("CustomResourceDefinition"))
207+
crd.Name = MaintenanceModeCRDName
208+
if err := c.Client.Get(ctx, client.ObjectKeyFromObject(crd), crd); client.IgnoreNotFound(err) != nil {
209+
c.log.Error(err, "Failed to get CRD", "CRD", crd.Name)
210+
return reconcile.Result{}, err
211+
}
212+
utils.AssertEqual(c.AvailableCrds[crd.Name], crd.UID != "", utils.ExitCodeThatShouldRestartTheProcess)
213+
191214
c.operatorConfigMap = &corev1.ConfigMap{}
192215
c.operatorConfigMap.Name = req.Name
193216
c.operatorConfigMap.Namespace = req.Namespace

0 commit comments

Comments
 (0)