Skip to content

Commit 5d378e0

Browse files
committed
Checks and reports S3 health
Signed-off-by: raaizik <[email protected]>
1 parent 3b6482c commit 5d378e0

File tree

6 files changed

+147
-26
lines changed

6 files changed

+147
-26
lines changed

api/v1alpha1/drclusterconfig_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type DRClusterConfigSpec struct {
3333

3434
const (
3535
DRClusterConfigConfigurationProcessed string = "Processed"
36-
DRClusterConfigS3Reachable string = "Reachable"
36+
DRClusterConfigS3Healthy string = "MetaDataStoresHealthy"
3737
)
3838

3939
// DRClusterConfigStatus defines the observed state of DRClusterConfig

cmd/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,10 @@ func setupReconcilersCluster(mgr ctrl.Manager, ramenConfig *ramendrv1alpha1.Rame
181181
}
182182

183183
if err := (&controllers.DRClusterConfigReconciler{
184-
Client: mgr.GetClient(),
185-
Scheme: mgr.GetScheme(),
186-
Log: ctrl.Log.WithName("drcc"),
184+
Client: mgr.GetClient(),
185+
Scheme: mgr.GetScheme(),
186+
Log: ctrl.Log.WithName("drcc"),
187+
ObjectStoreGetter: controllers.S3ObjectStoreGetter(),
187188
}).SetupWithManager(mgr); err != nil {
188189
setupLog.Error(err, "unable to create controller", "controller", "DRClusterConfig")
189190
os.Exit(1)

internal/controller/drcluster_controller.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ func validateS3Profile(ctx context.Context, apiReader client.Reader,
500500
drcluster *ramen.DRCluster, listKeyPrefix string, log logr.Logger,
501501
) (string, error) {
502502
if drcluster.Spec.S3ProfileName != NoS3StoreAvailable {
503-
if reason, err := s3ProfileValidate(ctx, apiReader, objectStoreGetter,
503+
if reason, err := S3ProfileValidate(ctx, apiReader, objectStoreGetter,
504504
drcluster.Spec.S3ProfileName, listKeyPrefix, log); err != nil {
505505
return reason, err
506506
}
@@ -509,23 +509,6 @@ func validateS3Profile(ctx context.Context, apiReader client.Reader,
509509
return "", nil
510510
}
511511

512-
func s3ProfileValidate(ctx context.Context, apiReader client.Reader,
513-
objectStoreGetter ObjectStoreGetter, s3ProfileName, listKeyPrefix string,
514-
log logr.Logger,
515-
) (string, error) {
516-
objectStore, _, err := objectStoreGetter.ObjectStore(
517-
ctx, apiReader, s3ProfileName, "drpolicy validation", log)
518-
if err != nil {
519-
return "s3ConnectionFailed", fmt.Errorf("%s: %w", s3ProfileName, err)
520-
}
521-
522-
if _, err := objectStore.ListKeys(listKeyPrefix); err != nil {
523-
return "s3ListFailed", fmt.Errorf("%s: %w", s3ProfileName, err)
524-
}
525-
526-
return "", nil
527-
}
528-
529512
func validateCIDRsFormat(drcluster *ramen.DRCluster, log logr.Logger) error {
530513
// validate the CIDRs format
531514
invalidCidrs := []string{}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-FileCopyrightText: The RamenDR authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package controllers
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/go-logr/logr"
11+
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
)
14+
15+
func S3ProfileValidate(ctx context.Context, apiReader client.Reader,
16+
objectStoreGetter ObjectStoreGetter, s3ProfileName, listKeyPrefix string,
17+
log logr.Logger,
18+
) (string, error) {
19+
objectStore, _, err := objectStoreGetter.ObjectStore(
20+
ctx, apiReader, s3ProfileName, "drpolicy validation", log)
21+
if err != nil {
22+
return "s3ConnectionFailed", fmt.Errorf("%s: %w", s3ProfileName, err)
23+
}
24+
25+
if _, err := objectStore.ListKeys(listKeyPrefix); err != nil {
26+
return "s3ListFailed", fmt.Errorf("%s: %w", s3ProfileName, err)
27+
}
28+
29+
return "", nil
30+
}

internal/controller/drclusterconfig_controller.go

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"time"
1212

1313
csiaddonsv1alpha1 "github.com/csi-addons/kubernetes-csi-addons/api/csiaddons/v1alpha1"
14+
v1 "k8s.io/api/core/v1"
15+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
16+
1417
volrep "github.com/csi-addons/kubernetes-csi-addons/api/replication.storage/v1alpha1"
1518
snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
1619
groupsnapv1beta1 "github.com/red-hat-storage/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1"
@@ -56,9 +59,10 @@ const (
5659
// DRClusterConfigReconciler reconciles a DRClusterConfig object
5760
type DRClusterConfigReconciler struct {
5861
client.Client
59-
Scheme *runtime.Scheme
60-
Log logr.Logger
61-
RateLimiter *workqueue.TypedRateLimiter[reconcile.Request]
62+
Scheme *runtime.Scheme
63+
Log logr.Logger
64+
RateLimiter *workqueue.TypedRateLimiter[reconcile.Request]
65+
ObjectStoreGetter ObjectStoreGetter
6266
}
6367

6468
//nolint:lll
@@ -148,7 +152,7 @@ func setDRClusterConfigInitialCondition(conditions *[]metav1.Condition, observed
148152
Message: message,
149153
})
150154
util.SetStatusConditionIfNotFound(conditions, metav1.Condition{
151-
Type: ramen.DRClusterConfigS3Reachable,
155+
Type: ramen.DRClusterConfigS3Healthy,
152156
Reason: DRClusterConfigConditionReasonInitializing,
153157
ObservedGeneration: observedGeneration,
154158
Status: metav1.ConditionUnknown,
@@ -168,6 +172,18 @@ func setDRClusterConfigConfigurationProcessedCondition(conditions *[]metav1.Cond
168172
})
169173
}
170174

175+
func setDRClusterConfigS3ReachableCondition(conditions *[]metav1.Condition, observedGeneration int64,
176+
message string, conditionStatus metav1.ConditionStatus, reason string,
177+
) {
178+
util.SetStatusCondition(conditions, metav1.Condition{
179+
Type: ramen.DRClusterConfigS3Healthy,
180+
Reason: reason,
181+
ObservedGeneration: observedGeneration,
182+
Status: conditionStatus,
183+
Message: message,
184+
})
185+
}
186+
171187
func (r *DRClusterConfigReconciler) GetDRClusterConfig(ctx context.Context) (*ramen.DRClusterConfig, error) {
172188
drcConfigs := &ramen.DRClusterConfigList{}
173189
if err := r.Client.List(ctx, drcConfigs); err != nil {
@@ -275,9 +291,66 @@ func (r *DRClusterConfigReconciler) processCreateOrUpdate(
275291
setDRClusterConfigConfigurationProcessedCondition(&drCConfig.Status.Conditions, drCConfig.Generation,
276292
"Configuration processed and validated", metav1.ConditionTrue, DRClusterConfigConditionConfigurationProcessed)
277293

294+
if err := r.validateS3Profiles(ctx, drCConfig); err != nil {
295+
log.Info("Reconcile error", "error", err)
296+
297+
return ctrl.Result{Requeue: true}, err
298+
}
299+
278300
return ctrl.Result{}, nil
279301
}
280302

303+
func (r *DRClusterConfigReconciler) validateS3Profiles(ctx context.Context, drCConfig *ramen.DRClusterConfig) error {
304+
// Fetch the ramen config resource
305+
_, ramenConfig, err := ConfigMapGet(ctx, r.Client)
306+
if err != nil {
307+
return fmt.Errorf("failed to get Ramen configmap: %w", err)
308+
}
309+
310+
// Iterate all profiles listed in it and check for existing healthy ones
311+
for profileIdx := range ramenConfig.S3StoreProfiles {
312+
// for each profile, check that it has an actual secret attached to its secretRef ID
313+
profile := ramenConfig.S3StoreProfiles[profileIdx]
314+
secretRef := profile.S3SecretRef
315+
secret := &v1.Secret{
316+
ObjectMeta: metav1.ObjectMeta{Name: secretRef.Name, Namespace: secretRef.Namespace},
317+
}
318+
319+
if err := r.Client.Get(ctx, types.NamespacedName{
320+
Namespace: secret.Namespace,
321+
Name: secret.Name,
322+
}, secret); err != nil {
323+
if !k8serrors.IsNotFound(err) {
324+
setDRClusterConfigS3ReachableCondition(&drCConfig.Status.Conditions, drCConfig.Generation,
325+
fmt.Sprintf("Found an unhealthy S3 profile %q for which there's no secret", profile.S3ProfileName),
326+
metav1.ConditionFalse, DRClusterConfigS3Unreachable)
327+
328+
return fmt.Errorf("failed to get secret: %w", err)
329+
}
330+
// If there's no secret attached to the secretRef's namespacedname -- mark profile as unhealthy
331+
setDRClusterConfigS3ReachableCondition(&drCConfig.Status.Conditions, drCConfig.Generation,
332+
fmt.Sprintf("Found an unhealthy S3 profile %q for which there's no secret", profile.S3ProfileName),
333+
metav1.ConditionFalse, DRClusterConfigS3Unreachable)
334+
335+
return fmt.Errorf("secrect not found: %w", err)
336+
}
337+
// Profile does have a secret. Check if it has connectivity and record in status accordingly
338+
if reason, err := S3ProfileValidate(ctx, r.Client, r.ObjectStoreGetter, profile.S3ProfileName, types.NamespacedName{
339+
Name: drCConfig.Name, Namespace: drCConfig.Namespace,
340+
}.String(), r.Log); err != nil {
341+
setDRClusterConfigS3ReachableCondition(&drCConfig.Status.Conditions, drCConfig.Generation, err.Error(),
342+
metav1.ConditionFalse, reason)
343+
344+
return fmt.Errorf("failed to validate s3 profile: %w", err)
345+
}
346+
}
347+
// All S3 profiles are healthy -- record to status and exit
348+
setDRClusterConfigS3ReachableCondition(&drCConfig.Status.Conditions, drCConfig.Generation,
349+
fmt.Sprintf("All S3 profiles are healthy"), metav1.ConditionTrue, DRClusterConfigS3Reachable)
350+
351+
return nil
352+
}
353+
281354
// UpdateSupportedClasses updates DRClusterConfig status with a list of storage related classes that are marked for DR
282355
// support. The list is sorted alphabetically to avoid out of order listing and status updates due to the same
283356
func (r *DRClusterConfigReconciler) UpdateSupportedClasses(

internal/controller/drclusterconfig_controller_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"time"
1212

1313
csiaddonsv1alpha1 "github.com/csi-addons/kubernetes-csi-addons/api/csiaddons/v1alpha1"
14+
corev1 "k8s.io/api/core/v1"
15+
1416
volrep "github.com/csi-addons/kubernetes-csi-addons/api/replication.storage/v1alpha1"
1517
snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
1618
. "github.com/onsi/ginkgo/v2"
@@ -77,6 +79,7 @@ var _ = Describe("DRClusterConfigControllerTests", Ordered, func() {
7779
baseVGSC, vgsc1, vgsc2 *groupsnapv1beta1.VolumeGroupSnapshotClass
7880
baseNFC, nfc1, nfc2 *csiaddonsv1alpha1.NetworkFenceClass
7981
classes Classes
82+
ramenConfig *ramen.RamenConfig
8083
)
8184

8285
BeforeAll(func() {
@@ -109,6 +112,37 @@ var _ = Describe("DRClusterConfigControllerTests", Ordered, func() {
109112
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
110113
Expect(err).NotTo(HaveOccurred())
111114

115+
By("Creating namespaces")
116+
117+
Expect(k8sClient.Create(context.TODO(),
118+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ramenNamespace}})).To(Succeed())
119+
120+
By("Defining a ramen configuration")
121+
122+
ramenConfig = &ramen.RamenConfig{
123+
TypeMeta: metav1.TypeMeta{
124+
Kind: "RamenConfig",
125+
APIVersion: ramen.GroupVersion.String(),
126+
},
127+
LeaderElection: &config.LeaderElectionConfiguration{
128+
LeaderElect: new(bool),
129+
ResourceName: ramencontrollers.HubLeaderElectionResourceName,
130+
},
131+
Metrics: ramen.ControllerMetrics{
132+
BindAddress: "0", // Disable metrics
133+
},
134+
RamenControllerType: ramen.DRHubType,
135+
}
136+
ramenConfig.DrClusterOperator.DeploymentAutomationEnabled = true
137+
ramenConfig.DrClusterOperator.S3SecretDistributionEnabled = true
138+
configMap, err := ramencontrollers.ConfigMapNew(
139+
ramenNamespace,
140+
ramencontrollers.HubOperatorConfigMapName,
141+
ramenConfig,
142+
)
143+
Expect(err).NotTo(HaveOccurred())
144+
Expect(k8sClient.Create(context.TODO(), configMap)).To(Succeed())
145+
112146
By("starting the DRClusterConfig reconciler")
113147

114148
ramenConfig := &ramen.RamenConfig{

0 commit comments

Comments
 (0)