diff --git a/apis/clusterresources/v1beta1/redisuser_types.go b/apis/clusterresources/v1beta1/redisuser_types.go index da9a488d6..a2c911de3 100644 --- a/apis/clusterresources/v1beta1/redisuser_types.go +++ b/apis/clusterresources/v1beta1/redisuser_types.go @@ -65,6 +65,10 @@ func (rs *RedisUserSpec) ToInstAPI(password, clusterID, username string) *models } } +func (r *RedisUser) GetDeletionFinalizer() string { + return models.DeletionFinalizer + "_" + r.Namespace + "_" + r.Name +} + func (r *RedisUser) DeletionUserFinalizer(clusterID, namespace string) string { return models.DeletionUserFinalizer + clusterID + "_" + namespace } diff --git a/config/samples/clusters_v1beta1_redis.yaml b/config/samples/clusters_v1beta1_redis.yaml index 178b26e96..227dfc287 100644 --- a/config/samples/clusters_v1beta1_redis.yaml +++ b/config/samples/clusters_v1beta1_redis.yaml @@ -30,7 +30,7 @@ spec: # nodeSize: "t3.medium-80-r" nodeSize: "t3.small-20-r" masterNodes: 3 - nodesNumber: 3 + nodesNumber: 0 # - region: "US_WEST_2" # name: "testDC2" # cloudProvider: "AWS_VPC" diff --git a/controllers/clusterresources/redisuser_controller.go b/controllers/clusterresources/redisuser_controller.go index f88e6a59c..9c3b8b008 100644 --- a/controllers/clusterresources/redisuser_controller.go +++ b/controllers/clusterresources/redisuser_controller.go @@ -139,28 +139,28 @@ func (r *RedisUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( "User has been created for a Redis cluster. Cluster ID: %s, username: %s", clusterID, username) - finalizerNeeded := controllerutil.AddFinalizer(secret, models.DeletionFinalizer) + finalizerNeeded := controllerutil.AddFinalizer(secret, user.GetDeletionFinalizer()) if finalizerNeeded { err = r.Update(ctx, secret) if err != nil { - l.Error(err, "Cannot update Cassandra user secret", + l.Error(err, "Cannot update Redis user secret with deletion finalizer", "secret name", secret.Name, "secret namespace", secret.Namespace) r.EventRecorder.Eventf(user, models.Warning, models.UpdatedEvent, - "Cannot assign Cassandra user to a k8s secret. Reason: %v", err) + "Cannot assign k8s secret to a Redis user. Reason: %v", err) return models.ReconcileRequeue, nil } } - controllerutil.AddFinalizer(user, user.DeletionUserFinalizer(clusterID, username)) + controllerutil.AddFinalizer(user, user.DeletionUserFinalizer(clusterID, user.Namespace)) err = r.Patch(ctx, user, patch) if err != nil { - l.Error(err, "Cannot patch Cassandra user resource", + l.Error(err, "Cannot patch Redis user resource with deletion finalizer", "secret name", secret.Name, "secret namespace", secret.Namespace) r.EventRecorder.Eventf(user, models.Warning, models.PatchFailed, - "Resource patch is failed. Reason: %v", err) + "Resource patch with deletion user finalizer is failed. Reason: %v", err) return models.ReconcileRequeue, nil } @@ -174,9 +174,9 @@ func (r *RedisUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( userID := fmt.Sprintf(instaclustr.RedisUserIDFmt, clusterID, user.Namespace) err = r.API.DeleteRedisUser(userID) if err != nil { - l.Error(err, "Cannot delete Redis user", "user", user.Name) + l.Error(err, "Cannot delete Redis user from the cluster. Cluster ID: %s.", "user", clusterID, user.Name) r.EventRecorder.Eventf(user, models.Warning, models.DeletingEvent, - "Cannot delete user. Reason: %v", err) + "Cannot delete Redis user from the cluster. Cluster ID: %s. Reason: %v", clusterID, err) return models.ReconcileRequeue, nil } @@ -201,7 +201,7 @@ func (r *RedisUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( controllerutil.RemoveFinalizer(user, user.DeletionUserFinalizer(clusterID, user.Namespace)) err = r.Patch(ctx, user, patch) if err != nil { - l.Error(err, "Cannot patch Cassandra user resource", + l.Error(err, "Cannot patch Redis user resource", "secret name", secret.Name, "secret namespace", secret.Namespace) r.EventRecorder.Eventf(user, models.Warning, models.PatchFailed, @@ -213,10 +213,23 @@ func (r *RedisUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( continue } + if event == models.ClusterDeletingEvent { + err = r.detachUserFromDeletedCluster(ctx, clusterID, user, l) + if err != nil { + l.Error(err, "Cannot detach Redis user resource", + "secret name", secret.Name, + "secret namespace", secret.Namespace) + r.EventRecorder.Eventf(user, models.Warning, models.DeletionFailed, + "Resource detach is failed. Reason: %v", err) + + return models.ReconcileRequeue, nil + } + } + userID := fmt.Sprintf(instaclustr.RedisUserIDFmt, clusterID, username) err = r.API.UpdateRedisUser(user.ToInstAPIUpdate(password, userID)) if err != nil { - l.Error(err, "Cannot update redis user", + l.Error(err, "Cannot update redis user password", "secret name", user.Spec.SecretRef.Name, "secret namespace", user.Spec.SecretRef.Namespace, "username", username, @@ -254,6 +267,55 @@ func (r *RedisUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return models.ExitReconcile, nil } +func (r *RedisUserReconciler) detachUserFromDeletedCluster( + ctx context.Context, + clusterID string, + user *v1beta1.RedisUser, + logger logr.Logger, +) error { + + patch := user.NewPatch() + delete(user.Status.ClustersEvents, clusterID) + + err := r.Status().Patch(ctx, user, patch) + if err != nil { + logger.Error(err, "Cannot detach clusterID from the Redis user resource", + "cluster ID", clusterID, + ) + r.EventRecorder.Eventf( + user, models.Warning, models.PatchFailed, + "Detaching clusterID from the Redis user resource has been failed. Reason: %v", + err, + ) + return err + } + + controllerutil.RemoveFinalizer(user, user.DeletionUserFinalizer(clusterID, user.Namespace)) + + err = r.Patch(ctx, user, patch) + if err != nil { + logger.Error(err, "Cannot delete finalizer from the Redis user resource", + "cluster ID", clusterID, + ) + r.EventRecorder.Eventf( + user, models.Warning, models.PatchFailed, + "Deleting finalizer from the Redis user resource has been failed. Reason: %v", + err, + ) + return err + } + + logger.Info("Redis user has been deleted from the cluster", + "cluster ID", clusterID, + ) + r.EventRecorder.Eventf( + user, models.Normal, models.Deleted, + "Redis user resource has been deleted from the cluster (clusterID: %v)", clusterID, + ) + + return err +} + func (r *RedisUserReconciler) handleDeleteUser( ctx context.Context, l logr.Logger, @@ -280,7 +342,7 @@ func (r *RedisUserReconciler) handleDeleteUser( } } - controllerutil.RemoveFinalizer(s, models.DeletionFinalizer) + controllerutil.RemoveFinalizer(s, u.GetDeletionFinalizer()) err = r.Update(ctx, s) if err != nil { l.Error(err, "Cannot remove finalizer from secret", "secret name", s.Name) diff --git a/controllers/clusters/redis_controller.go b/controllers/clusters/redis_controller.go index 22b3b2d09..1693d8c3c 100644 --- a/controllers/clusters/redis_controller.go +++ b/controllers/clusters/redis_controller.go @@ -461,6 +461,7 @@ func (r *RedisReconciler) handleDeleteCluster( redis *v1beta1.Redis, logger logr.Logger, ) reconcile.Result { + _, err := r.API.GetRedis(redis.Status.ID) if err != nil && !errors.Is(err, instaclustr.NotFound) { logger.Error(err, "Cannot get Redis cluster status from Instaclustr", @@ -476,6 +477,23 @@ func (r *RedisReconciler) handleDeleteCluster( return models.ReconcileRequeue } + for _, ref := range redis.Spec.UserRefs { + err = r.detachUserResource(ctx, logger, redis, ref) + if err != nil { + logger.Error(err, "Cannot detach Redis user", + "cluster name", redis.Spec.Name, + "cluster status", redis.Status.State, + ) + + r.EventRecorder.Eventf( + redis, models.Warning, models.DeletionFailed, + "Cluster detaching on the Instaclustr is failed. Reason: %v", + err, + ) + return models.ReconcileRequeue + } + } + if !errors.Is(err, instaclustr.NotFound) { logger.Info("Sending cluster deletion to the Instaclustr API", "cluster name", redis.Spec.Name, @@ -596,6 +614,52 @@ func (r *RedisReconciler) handleDeleteCluster( return models.ExitReconcile } +func (r *RedisReconciler) detachUserResource( + ctx context.Context, + l logr.Logger, + redis *v1beta1.Redis, + uRef *v1beta1.UserReference, +) error { + req := types.NamespacedName{ + Namespace: uRef.Namespace, + Name: uRef.Name, + } + + u := &clusterresourcesv1beta1.RedisUser{} + err := r.Get(ctx, req, u) + if err != nil { + if k8serrors.IsNotFound(err) { + l.Error(err, "Redis user is not found", "request", req) + r.EventRecorder.Eventf(redis, models.Warning, models.NotFound, + "User resource is not found, please provide correct userRef."+ + "Current provided reference: %v", uRef) + return err + } + + l.Error(err, "Cannot get Redis user", "user", u.Spec) + r.EventRecorder.Eventf(redis, models.Warning, models.DeletionFailed, + "Cannot get Redis user. User reference: %v", uRef) + return err + } + + if _, exist := u.Status.ClustersEvents[redis.Status.ID]; !exist { + return nil + } + + patch := u.NewPatch() + u.Status.ClustersEvents[redis.Status.ID] = models.ClusterDeletingEvent + err = r.Status().Patch(ctx, u, patch) + if err != nil { + l.Error(err, "Cannot patch the Redis user status with the ClusterDeletingEvent", + "cluster name", redis.Spec.Name, "cluster ID", redis.Status.ID) + r.EventRecorder.Eventf(redis, models.Warning, models.DeletionFailed, + "Cannot patch the Redis user status with the ClusterDeletingEvent. Reason: %v", err) + return err + } + + return nil +} + func (r *RedisReconciler) handleUserEvent( newObj *v1beta1.Redis, oldUsers []*v1beta1.UserReference,