diff --git a/apis/clusterresources/v1beta1/opensearchuser_types.go b/apis/clusterresources/v1beta1/opensearchuser_types.go index 96e283710..47da79187 100644 --- a/apis/clusterresources/v1beta1/opensearchuser_types.go +++ b/apis/clusterresources/v1beta1/opensearchuser_types.go @@ -73,6 +73,14 @@ func (u *OpenSearchUser) GetDeletionFinalizer() string { return models.DeletionFinalizer + "_" + u.Namespace + "_" + u.Name } +func (u *OpenSearchUser) GetClusterEvents() map[string]string { + return u.Status.ClustersEvents +} + +func (u *OpenSearchUser) SetClusterEvents(events map[string]string) { + u.Status.ClustersEvents = events +} + func init() { SchemeBuilder.Register(&OpenSearchUser{}, &OpenSearchUserList{}) } diff --git a/apis/clusterresources/v1beta1/redisuser_types.go b/apis/clusterresources/v1beta1/redisuser_types.go index 29d4065d1..f53305a4c 100644 --- a/apis/clusterresources/v1beta1/redisuser_types.go +++ b/apis/clusterresources/v1beta1/redisuser_types.go @@ -81,6 +81,14 @@ func (r *RedisUser) NewPatch() client.Patch { return client.MergeFrom(old) } +func (r *RedisUser) GetClusterEvents() map[string]string { + return r.Status.ClustersEvents +} + +func (r *RedisUser) SetClusterEvents(events map[string]string) { + r.Status.ClustersEvents = events +} + func init() { SchemeBuilder.Register(&RedisUser{}, &RedisUserList{}) } diff --git a/apis/clusters/v1beta1/opensearch_types.go b/apis/clusters/v1beta1/opensearch_types.go index a0ca59d28..db983fbe0 100644 --- a/apis/clusters/v1beta1/opensearch_types.go +++ b/apis/clusters/v1beta1/opensearch_types.go @@ -53,7 +53,7 @@ type OpenSearchSpec struct { IndexManagementPlugin bool `json:"indexManagementPlugin,omitempty"` AlertingPlugin bool `json:"alertingPlugin,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` - UserRefs []*Reference `json:"userRefs,omitempty"` + UserRefs References `json:"userRefs,omitempty"` //+kubuilder:validation:MaxItems:=1 ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` //+kubuilder:validation:MaxItems:=1 @@ -531,7 +531,8 @@ type OpenSearchRestoreFrom struct { // OpenSearchStatus defines the observed state of OpenSearch type OpenSearchStatus struct { - ClusterStatus `json:",inline"` + ClusterStatus `json:",inline"` + AvailableUsers References `json:"availableUsers,omitempty"` } //+kubebuilder:object:root=true @@ -607,6 +608,30 @@ func (oss *OpenSearchSpec) validateResizeSettings(nodesNumber int) error { return nil } +func (oss *OpenSearch) GetUserRefs() References { + return oss.Spec.UserRefs +} + +func (oss *OpenSearch) SetUserRefs(refs References) { + oss.Spec.UserRefs = refs +} + +func (oss *OpenSearch) GetAvailableUsers() References { + return oss.Status.AvailableUsers +} + +func (oss *OpenSearch) SetAvailableUsers(users References) { + oss.Status.AvailableUsers = users +} + +func (oss *OpenSearch) GetClusterID() string { + return oss.Status.ID +} + +func (oss *OpenSearch) SetClusterID(id string) { + oss.Status.ID = id +} + func init() { SchemeBuilder.Register(&OpenSearch{}, &OpenSearchList{}) } diff --git a/apis/clusters/v1beta1/redis_types.go b/apis/clusters/v1beta1/redis_types.go index 0bc202f00..02c1df4b4 100644 --- a/apis/clusters/v1beta1/redis_types.go +++ b/apis/clusters/v1beta1/redis_types.go @@ -69,14 +69,15 @@ type RedisSpec struct { //+kubebuilder:validation:MaxItems:=2 DataCentres []*RedisDataCentre `json:"dataCentres,omitempty"` - UserRefs []*Reference `json:"userRefs,omitempty"` + UserRefs References `json:"userRefs,omitempty"` //+kubebuilder:validation:MaxItems:=1 ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } // RedisStatus defines the observed state of Redis type RedisStatus struct { - ClusterStatus `json:",inline"` + ClusterStatus `json:",inline"` + AvailableUsers References `json:"availableUsers,omitempty"` } //+kubebuilder:object:root=true @@ -297,7 +298,7 @@ func (rs *RedisSpec) DCsFromInstAPI(iDCs []*models.RedisDataCentre) (dcs []*Redi func (rs *RedisStatus) FromInstAPI(iRedis *models.RedisCluster) RedisStatus { return RedisStatus{ - ClusterStatus{ + ClusterStatus: ClusterStatus{ ID: iRedis.ID, State: iRedis.Status, DataCentres: rs.DCsFromInstAPI(iRedis.DataCentres), @@ -464,6 +465,30 @@ func (rs *RedisSpec) ValidatePrivateLink() error { return nil } +func (r *Redis) GetUserRefs() References { + return r.Spec.UserRefs +} + +func (r *Redis) SetUserRefs(refs References) { + r.Spec.UserRefs = refs +} + +func (r *Redis) GetAvailableUsers() References { + return r.Status.AvailableUsers +} + +func (r *Redis) SetAvailableUsers(users References) { + r.Status.AvailableUsers = users +} + +func (r *Redis) GetClusterID() string { + return r.Status.ID +} + +func (r *Redis) SetClusterID(id string) { + r.Status.ID = id +} + func init() { SchemeBuilder.Register(&Redis{}, &RedisList{}) } diff --git a/apis/clusters/v1beta1/zz_generated.deepcopy.go b/apis/clusters/v1beta1/zz_generated.deepcopy.go index 50328d2aa..0dec9bf8d 100644 --- a/apis/clusters/v1beta1/zz_generated.deepcopy.go +++ b/apis/clusters/v1beta1/zz_generated.deepcopy.go @@ -1546,7 +1546,7 @@ func (in *OpenSearchSpec) DeepCopyInto(out *OpenSearchSpec) { } if in.UserRefs != nil { in, out := &in.UserRefs, &out.UserRefs - *out = make([]*Reference, len(*in)) + *out = make(References, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] @@ -1593,6 +1593,17 @@ func (in *OpenSearchSpec) DeepCopy() *OpenSearchSpec { func (in *OpenSearchStatus) DeepCopyInto(out *OpenSearchStatus) { *out = *in in.ClusterStatus.DeepCopyInto(&out.ClusterStatus) + if in.AvailableUsers != nil { + in, out := &in.AvailableUsers, &out.AvailableUsers + *out = make(References, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Reference) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSearchStatus. @@ -2090,7 +2101,7 @@ func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { } if in.UserRefs != nil { in, out := &in.UserRefs, &out.UserRefs - *out = make([]*Reference, len(*in)) + *out = make(References, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] @@ -2126,6 +2137,17 @@ func (in *RedisSpec) DeepCopy() *RedisSpec { func (in *RedisStatus) DeepCopyInto(out *RedisStatus) { *out = *in in.ClusterStatus.DeepCopyInto(&out.ClusterStatus) + if in.AvailableUsers != nil { + in, out := &in.AvailableUsers, &out.AvailableUsers + *out = make(References, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Reference) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisStatus. diff --git a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml index 66c626ebe..9852b8b2b 100644 --- a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml +++ b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml @@ -266,6 +266,18 @@ spec: status: description: OpenSearchStatus defines the observed state of OpenSearch properties: + availableUsers: + items: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + type: array cdcid: type: string currentClusterOperationStatus: diff --git a/config/crd/bases/clusters.instaclustr.com_redis.yaml b/config/crd/bases/clusters.instaclustr.com_redis.yaml index af22bf018..df4104371 100644 --- a/config/crd/bases/clusters.instaclustr.com_redis.yaml +++ b/config/crd/bases/clusters.instaclustr.com_redis.yaml @@ -218,6 +218,18 @@ spec: status: description: RedisStatus defines the observed state of Redis properties: + availableUsers: + items: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + type: array cdcid: type: string currentClusterOperationStatus: diff --git a/config/samples/clusters_v1beta1_opensearch.yaml b/config/samples/clusters_v1beta1_opensearch.yaml index c7ddc75a6..11268d699 100644 --- a/config/samples/clusters_v1beta1_opensearch.yaml +++ b/config/samples/clusters_v1beta1_opensearch.yaml @@ -22,7 +22,7 @@ spec: # namespace: "default" clusterManagerNodes: - dedicatedManager: false - nodeSize: SRH-DEV-t4g.small-30 + nodeSize: SRH-DEV-t4g.small-5 dataCentres: - cloudProvider: AWS_VPC name: AWS_VPC_US_EAST_1 @@ -30,10 +30,10 @@ spec: replicationFactor: 3 privateLink: false region: US_EAST_1 - ingestNodes: -# - nodeSize: SRH-DI-PRD-m6g.large-10 - - nodeSize: SRH-DI-PRD-m6g.xlarge-10 - nodeCount: 3 +# ingestNodes: +## - nodeSize: SRH-DI-PRD-m6g.large-10 +# - nodeSize: SRH-DI-PRD-m6g.xlarge-10 +# nodeCount: 3 # dataNodes: # - nodeNumber: 3 # nodeSize: SRH-DEV-t4g.small-5 diff --git a/config/samples/clusters_v1beta1_redis.yaml b/config/samples/clusters_v1beta1_redis.yaml index fb33d29d5..9b1411529 100644 --- a/config/samples/clusters_v1beta1_redis.yaml +++ b/config/samples/clusters_v1beta1_redis.yaml @@ -16,12 +16,12 @@ spec: passwordAndUserAuth: true privateNetworkCluster: false userRefs: -# - name: redisuser-sample-1 -# namespace: default -# - name: redisuser-sample-2 -# namespace: default -# - name: redisuser-sample-3 -# namespace: default + - name: redisuser-sample-1 + namespace: default + - name: redisuser-sample-2 + namespace: default + - name: redisuser-sample-3 + namespace: default # twoFactorDelete: # - email: "rostyslp@netapp.com" dataCentres: diff --git a/controllers/clusters/opensearch_controller.go b/controllers/clusters/opensearch_controller.go index b19a53b27..b34807a30 100644 --- a/controllers/clusters/opensearch_controller.go +++ b/controllers/clusters/opensearch_controller.go @@ -370,6 +370,15 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( ) } + err = handleUsersChanges(ctx, r.Client, r, o) + if err != nil { + logger.Error(err, "Failed to handle users changes") + r.EventRecorder.Eventf(o, models.Warning, models.PatchFailed, + "Handling users changes is failed. Reason: %w", err, + ) + return reconcile.Result{}, err + } + patch := o.NewPatch() o.Annotations[models.ResourceStateAnnotation] = models.UpdatedEvent o.Annotations[models.UpdateQueuedAnnotation] = "" @@ -519,11 +528,13 @@ func (r *OpenSearchReconciler) HandleDeleteCluster( "cluster ID", o.Status.ID, ) - for _, ref := range o.Spec.UserRefs { - err = r.detachUserResource(ctx, logger, o, ref) - if err != nil { - return reconcile.Result{}, err - } + err = detachUsers(ctx, r.Client, r, o) + if err != nil { + logger.Error(err, "Failed to detach users from the cluster") + r.EventRecorder.Eventf(o, models.Warning, models.DeletionFailed, + "Detaching users from the cluster is failed. Reason: %w", err, + ) + return reconcile.Result{}, err } controllerutil.RemoveFinalizer(o, models.DeletionFinalizer) @@ -878,17 +889,12 @@ func (r *OpenSearchReconciler) newUsersCreationJob(o *v1beta1.OpenSearch) schedu return nil } - for _, ref := range o.Spec.UserRefs { - err = r.createUser(ctx, logger, o, ref) - if err != nil { - logger.Error(err, "Failed to create a user for the cluster", - "user ref", ref, - ) - r.EventRecorder.Eventf(o, models.Warning, models.CreationFailed, - "Failed to create a user for the cluster. Reason: %v", err, - ) - return err - } + err = handleUsersChanges(ctx, r.Client, r, o) + if err != nil { + logger.Error(err, "Failed to create users for the cluster") + r.EventRecorder.Eventf(o, models.Warning, models.CreationFailed, + "Failed to create users for the cluster. Reason: %v", err) + return err } logger.Info("User creation job successfully finished") @@ -948,220 +954,8 @@ func (r *OpenSearchReconciler) deleteBackups(ctx context.Context, clusterID, nam return nil } -func (r *OpenSearchReconciler) deleteUser( - ctx context.Context, - l logr.Logger, - c *v1beta1.OpenSearch, - uRef *v1beta1.Reference, -) error { - req := types.NamespacedName{ - Namespace: uRef.Namespace, - Name: uRef.Name, - } - - u := &clusterresourcesv1beta1.OpenSearchUser{} - err := r.Get(ctx, req, u) - if err != nil { - if k8serrors.IsNotFound(err) { - l.Error(err, "OpenSearch user is not found", "request", req) - r.EventRecorder.Eventf(c, models.Warning, models.NotFound, - "User resource is not found, please provide correct userRef."+ - "Current provided reference: %v", uRef) - return nil - } - - l.Error(err, "Cannot get OpenSearch user", "user", u.Spec) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot get OpenSearch user. user reference: %v", uRef) - return err - } - - if _, exist := u.Status.ClustersEvents[c.Status.ID]; !exist { - l.Info("User is not existing on the cluster", - "user reference", uRef) - r.EventRecorder.Eventf(c, models.Normal, models.DeletionFailed, - "User is not existing on the cluster. User reference: %v", uRef) - - return nil - } - - patch := u.NewPatch() - u.Status.ClustersEvents[c.Status.ID] = models.DeletingEvent - err = r.Status().Patch(ctx, u, patch) - if err != nil { - l.Error(err, "Cannot patch the OpenSearch user status with the DeletingEvent", - "cluster name", c.Spec.Name, "cluster ID", c.Status.ID) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot patch the OpenSearch user status with the DeletingEvent. Reason: %v", err) - return err - } - - l.Info("User has been added to the queue for deletion", "username", u.Name) - - return nil -} - -func (r *OpenSearchReconciler) createUser( - ctx context.Context, - logger logr.Logger, - c *v1beta1.OpenSearch, - uRef *v1beta1.Reference, -) error { - req := types.NamespacedName{ - Namespace: uRef.Namespace, - Name: uRef.Name, - } - - u := &clusterresourcesv1beta1.OpenSearchUser{} - err := r.Get(ctx, req, u) - if err != nil { - if k8serrors.IsNotFound(err) { - logger.Error(err, "OpenSearch user is not found", "request", req) - r.EventRecorder.Eventf(c, models.Warning, models.NotFound, - "User is not found, create a new one or provide correct userRef."+ - "Current provided reference: %v", uRef) - return err - } - - logger.Error(err, "Cannot get OpenSearch user", "user", u.Spec) - r.EventRecorder.Eventf(c, models.Warning, models.CreationFailed, - "Cannot get OpenSearch user. user reference: %v", uRef) - return err - } - - if _, exist := u.Status.ClustersEvents[c.Status.ID]; exist { - logger.Info("User is already existing on the cluster", - "user reference", uRef) - r.EventRecorder.Eventf(c, models.Normal, models.CreationFailed, - "User is already existing on the cluster. User reference: %v", uRef) - - return nil - } - - patch := u.NewPatch() - - if u.Status.ClustersEvents == nil { - u.Status.ClustersEvents = make(map[string]string) - } - - u.Status.ClustersEvents[c.Status.ID] = models.CreatingEvent - - err = r.Status().Patch(ctx, u, patch) - if err != nil { - logger.Error(err, "Cannot patch the OpenSearch User status with the CreatingEvent", - "cluster name", c.Spec.Name, "cluster ID", c.Status.ID) - r.EventRecorder.Eventf(c, models.Warning, models.CreationFailed, - "Cannot add OpenSearch User to the cluster. Reason: %v", err) - return err - } - - logger.Info("User has been added to the queue for creation", "username", u.Name) - - return nil -} - -func (r *OpenSearchReconciler) detachUserResource( - ctx context.Context, - l logr.Logger, - c *v1beta1.OpenSearch, - uRef *v1beta1.Reference, -) error { - req := types.NamespacedName{ - Namespace: uRef.Namespace, - Name: uRef.Name, - } - - u := &clusterresourcesv1beta1.OpenSearchUser{} - err := r.Get(ctx, req, u) - if err != nil { - if k8serrors.IsNotFound(err) { - l.Error(err, "OpenSearch user is not found", "request", req) - r.EventRecorder.Eventf(c, models.Warning, models.NotFound, - "User resource is not found, please provide correct userRef."+ - "Current provided reference: %v", uRef) - return nil - } - - l.Error(err, "Cannot get OpenSearch user", "user", u.Spec) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot get OpenSearch user. user reference: %v", uRef) - return err - } - - if _, exist := u.Status.ClustersEvents[c.Status.ID]; !exist { - return nil - } - - patch := u.NewPatch() - u.Status.ClustersEvents[c.Status.ID] = models.ClusterDeletingEvent - err = r.Status().Patch(ctx, u, patch) - if err != nil { - l.Error(err, "Cannot patch the OpenSearch user status with the ClusterDeletingEvent", - "cluster name", c.Spec.Name, "cluster ID", c.Status.ID) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot patch the OpenSearch user status with the ClusterDeletingEvent. Reason: %v", err) - return err - } - - l.Info("The user has been detached from the cluster") - - return nil -} - -func (r *OpenSearchReconciler) handleUserEvent( - newObj *v1beta1.OpenSearch, - oldUsers []*v1beta1.Reference, -) { - ctx := context.TODO() - l := log.FromContext(ctx) - - for _, newUser := range newObj.Spec.UserRefs { - var exist bool - - for _, oldUser := range oldUsers { - - if *newUser == *oldUser { - exist = true - break - } - } - - if exist { - continue - } - - err := r.createUser(ctx, l, newObj, newUser) - if err != nil { - l.Error(err, "Cannot create OpenSearch user in predicate", "user", newUser) - r.EventRecorder.Eventf(newObj, models.Warning, models.CreatingEvent, - "Cannot create user. Reason: %v", err) - } - - oldUsers = append(oldUsers, newUser) - } - - for _, oldUser := range oldUsers { - var exist bool - - for _, newUser := range newObj.Spec.UserRefs { - - if *oldUser == *newUser { - exist = true - break - } - } - - if exist { - continue - } - - err := r.deleteUser(ctx, l, newObj, oldUser) - if err != nil { - l.Error(err, "Cannot delete OpenSearch user", "user", oldUser) - r.EventRecorder.Eventf(newObj, models.Warning, models.CreatingEvent, - "Cannot delete user from cluster. Reason: %v", err) - } - } +func (r *OpenSearchReconciler) NewUserResource() userObject { + return &clusterresourcesv1beta1.OpenSearchUser{} } // SetupWithManager sets up the controller with the Manager. @@ -1197,10 +991,6 @@ func (r *OpenSearchReconciler) SetupWithManager(mgr ctrl.Manager) error { return false } - oldObj := event.ObjectOld.(*v1beta1.OpenSearch) - - r.handleUserEvent(newObj, oldObj.Spec.UserRefs) - newObj.Annotations[models.ResourceStateAnnotation] = models.UpdatingEvent return true }, diff --git a/controllers/clusters/redis_controller.go b/controllers/clusters/redis_controller.go index 0be37a0f8..e11f6d03f 100644 --- a/controllers/clusters/redis_controller.go +++ b/controllers/clusters/redis_controller.go @@ -369,8 +369,7 @@ func (r *RedisReconciler) handleUpdateCluster( patch := redis.NewPatch() redis.Annotations[models.UpdateQueuedAnnotation] = models.True - err = r.Patch(ctx, redis, patch) - if err != nil { + if err := r.Patch(ctx, redis, patch); err != nil { logger.Error(err, "Cannot patch metadata", "cluster name", redis.Spec.Name, "cluster metadata", redis.ObjectMeta, @@ -387,6 +386,15 @@ func (r *RedisReconciler) handleUpdateCluster( } } + err = handleUsersChanges(ctx, r.Client, r, redis) + if err != nil { + logger.Error(err, "Failed to handle users changes") + r.EventRecorder.Eventf(redis, models.Warning, models.PatchFailed, + "Handling users changes is failed. Reason: %w", err, + ) + return reconcile.Result{}, err + } + patch := redis.NewPatch() redis.Annotations[models.ResourceStateAnnotation] = models.UpdatedEvent redis.Annotations[models.UpdateQueuedAnnotation] = "" @@ -415,66 +423,6 @@ func (r *RedisReconciler) handleUpdateCluster( return models.ExitReconcile, nil } -func (r *RedisReconciler) handleCreateUsers( - ctx context.Context, - redis *v1beta1.Redis, - l logr.Logger, - userRefs *v1beta1.Reference, -) error { - req := types.NamespacedName{ - Namespace: userRefs.Namespace, - Name: userRefs.Name, - } - - u := &clusterresourcesv1beta1.RedisUser{} - err := r.Get(ctx, req, u) - if err != nil { - if k8serrors.IsNotFound(err) { - l.Info("Redis user is not found", "request", req) - r.EventRecorder.Event(u, models.Warning, "Not Found", - "User is not found, create a new one Redis User or provide right userRef.") - - return err - } - - l.Error(err, "Cannot get Redis user secret", "request", req) - r.EventRecorder.Eventf(u, models.Warning, "Cannot Get", - "Cannot get Redis user secret. Reason: %v", err) - - return err - } - - if _, exist := u.Status.ClustersEvents[redis.Status.ID]; exist { - l.Info("User is already existing on the cluster", - "user reference", userRefs) - r.EventRecorder.Eventf(redis, models.Normal, models.CreationFailed, - "User is already existing on the cluster. User reference: %v", userRefs) - - return nil - } - - patch := u.NewPatch() - - if u.Status.ClustersEvents == nil { - u.Status.ClustersEvents = make(map[string]string) - } - - u.Status.ClustersEvents[redis.Status.ID] = models.CreatingEvent - - err = r.Status().Patch(ctx, u, patch) - if err != nil { - l.Error(err, "Cannot patch the Redis User status with the CreatingEvent", - "cluster name", redis.Spec.Name, "cluster ID", redis.Status.ID) - r.EventRecorder.Eventf(redis, models.Warning, models.CreationFailed, - "Cannot add Redis User to the cluster. Reason: %v", err) - return err - } - - l.Info("User has been added to the queue for creation", "username", u.Name) - - return nil -} - func (r *RedisReconciler) handleExternalChanges(redis, iRedis *v1beta1.Redis, l logr.Logger) (reconcile.Result, error) { if !redis.Spec.IsEqual(iRedis.Spec) { l.Info(msgSpecStillNoMatch, @@ -611,21 +559,13 @@ func (r *RedisReconciler) handleDeleteCluster( "cluster ID", redis.Status.ID, ) - 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 reconcile.Result{}, err - } + err = detachUsers(ctx, r.Client, r, redis) + if err != nil { + logger.Error(err, "Failed to detach users from the cluster") + r.EventRecorder.Eventf(redis, models.Warning, models.DeletionFailed, + "Detaching users from the cluster is failed. Reason: %w", err, + ) + return reconcile.Result{}, err } patch := redis.NewPatch() @@ -669,165 +609,6 @@ func (r *RedisReconciler) handleDeleteCluster( return models.ExitReconcile, nil } -func (r *RedisReconciler) detachUserResource( - ctx context.Context, - l logr.Logger, - redis *v1beta1.Redis, - uRef *v1beta1.Reference, -) 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 nil - } - - 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 - } - - l.Info("The user has been detached from the cluster") - - return nil -} - -func (r *RedisReconciler) handleUserEvent( - newObj *v1beta1.Redis, - oldUsers []*v1beta1.Reference, -) { - ctx := context.TODO() - l := log.FromContext(ctx) - - for _, newUser := range newObj.Spec.UserRefs { - var exist bool - - for _, oldUser := range oldUsers { - - if *newUser == *oldUser { - exist = true - break - } - } - - if exist { - continue - } - - err := r.handleCreateUsers(ctx, newObj, l, newUser) - if err != nil { - l.Error(err, "Cannot create Redis user in predicate", "user", newUser) - r.EventRecorder.Eventf(newObj, models.Warning, models.CreatingEvent, - "Cannot create user. Reason: %v", err) - } - - oldUsers = append(oldUsers, newUser) - } - - for _, oldUser := range oldUsers { - var exist bool - - for _, newUser := range newObj.Spec.UserRefs { - - if *oldUser == *newUser { - exist = true - break - } - } - - if exist { - continue - } - - err := r.handleUsersDelete(ctx, l, newObj, oldUser) - if err != nil { - l.Error(err, "Cannot delete Redis user", "user", oldUser) - r.EventRecorder.Eventf(newObj, models.Warning, models.CreatingEvent, - "Cannot delete user from cluster. Reason: %v", err) - } - } -} - -func (r *RedisReconciler) handleUsersDelete( - ctx context.Context, - l logr.Logger, - c *v1beta1.Redis, - uRef *v1beta1.Reference, -) 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(c, models.Warning, models.NotFound, - "User is not found, create a new one Redis User or provide correct userRef."+ - "Current provided reference: %v", uRef) - return nil - } - - l.Error(err, "Cannot get Redis user", "user", u.Spec) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot get Redis user. User reference: %v", uRef) - return err - } - - if _, exist := u.Status.ClustersEvents[c.Status.ID]; !exist { - l.Info("User is not existing on the cluster", - "user reference", uRef) - r.EventRecorder.Eventf(c, models.Normal, models.DeletionFailed, - "User is not existing on the cluster. User reference: %v", uRef) - - return nil - } - - patch := u.NewPatch() - - u.Status.ClustersEvents[c.Status.ID] = models.DeletingEvent - - err = r.Status().Patch(ctx, u, patch) - if err != nil { - l.Error(err, "Cannot patch the Redis User status with the DeletingEvent", - "cluster name", c.Spec.Name, "cluster ID", c.Status.ID) - r.EventRecorder.Eventf(c, models.Warning, models.DeletionFailed, - "Cannot patch the Redis User status with the DeletingEvent. Reason: %v", err) - return err - } - - l.Info("User has been added to the queue for deletion", "username", u.Name) - - return nil -} - func (r *RedisReconciler) startClusterStatusJob(cluster *v1beta1.Redis) error { job := r.newWatchStatusJob(cluster) @@ -875,17 +656,13 @@ func (r *RedisReconciler) newUsersCreationJob(redis *v1beta1.Redis) scheduler.Jo return nil } - for _, ref := range redis.Spec.UserRefs { - err = r.handleCreateUsers(ctx, redis, logger, ref) - if err != nil { - logger.Error(err, "Failed to create a user for the cluster", - "user ref", ref, - ) - r.EventRecorder.Eventf(redis, models.Warning, models.CreationFailed, - "Failed to create a user for the cluster. Reason: %v", err, - ) - return err - } + err = handleUsersChanges(ctx, r.Client, r, redis) + if err != nil { + logger.Error(err, "Failed to create users") + r.EventRecorder.Eventf(redis, models.Warning, models.PatchFailed, + "Creating users is failed. Reason: %w", err, + ) + return err } logger.Info("User creation job successfully finished") @@ -1199,6 +976,10 @@ func (r *RedisReconciler) deleteBackups(ctx context.Context, clusterID, namespac return nil } +func (r *RedisReconciler) NewUserResource() userObject { + return &clusterresourcesv1beta1.RedisUser{} +} + // SetupWithManager sets up the controller with the Manager. func (r *RedisReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). @@ -1232,10 +1013,6 @@ func (r *RedisReconciler) SetupWithManager(mgr ctrl.Manager) error { return false } - oldObj := event.ObjectOld.(*v1beta1.Redis) - - r.handleUserEvent(newObj, oldObj.Spec.UserRefs) - newObj.Annotations[models.ResourceStateAnnotation] = models.UpdatingEvent return true }, diff --git a/controllers/tests/opensearch_plus_users_test.go b/controllers/tests/opensearch_plus_users_test.go index a6fd11740..283e7e277 100644 --- a/controllers/tests/opensearch_plus_users_test.go +++ b/controllers/tests/opensearch_plus_users_test.go @@ -152,6 +152,7 @@ var _ = Describe("Basic openSearch User controller + Basic openSearch cluster co // adding user openSearch1.Spec.UserRefs = newUsers Expect(k8sClient.Patch(ctx, &openSearch1, patch)).Should(Succeed()) + done := NewChannelWithTimeout(timeout) By("going to openSearch(cluster) controller predicate and put user entity to creation state. " + "Finally creates the user for the corresponded cluster") Eventually(func() bool { @@ -165,6 +166,7 @@ var _ = Describe("Basic openSearch User controller + Basic openSearch cluster co return true }, timeout, interval).Should(BeTrue()) + <-done }) })