From 57335bc806c8a1c6b6a6a1d78f7f10f7e3d74ac8 Mon Sep 17 00:00:00 2001 From: Bohdan Siryk Date: Thu, 8 Feb 2024 15:39:09 +0200 Subject: [PATCH] redis codebase refactor --- .secrets.baseline | 18 +- apis/clusters/v1beta1/redis_types.go | 292 +++++++---- apis/clusters/v1beta1/redis_webhook.go | 32 +- apis/clusters/v1beta1/structs.go | 23 + .../clusters/v1beta1/zz_generated.deepcopy.go | 98 +++- .../bases/clusters.instaclustr.com_redis.yaml | 38 +- .../samples/clusters_v1beta1_cassandra.yaml | 2 +- config/samples/clusters_v1beta1_redis.yaml | 26 +- controllers/clusters/redis_controller.go | 483 +++++++++--------- pkg/instaclustr/client.go | 10 +- pkg/instaclustr/interfaces.go | 2 +- pkg/instaclustr/mock/client.go | 2 +- pkg/models/redis_apiv2.go | 32 +- 13 files changed, 600 insertions(+), 458 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index fdc5c9870..91ddac180 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -75,10 +75,6 @@ { "path": "detect_secrets.filters.allowlist.is_line_allowlisted" }, - { - "path": "detect_secrets.filters.common.is_baseline_file", - "filename": ".secrets.baseline" - }, { "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2 @@ -342,21 +338,21 @@ "filename": "apis/clusters/v1beta1/redis_types.go", "hashed_secret": "bc1c5ae5fd4a238d86261f422e62c489de408c22", "is_verified": false, - "line_number": 159 + "line_number": 169 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/redis_types.go", "hashed_secret": "d62d56668a8c859e768e8250ed2fb690d03cead3", "is_verified": false, - "line_number": 228 + "line_number": 224 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/redis_types.go", - "hashed_secret": "5ba903f624ef2afb380a91d9c08efed6ef1c531d", + "hashed_secret": "d0e8e6fc5dce4d2b452e344ae41900b566ac01d1", "is_verified": false, - "line_number": 281 + "line_number": 269 } ], "apis/clusters/v1beta1/redis_webhook.go": [ @@ -365,7 +361,7 @@ "filename": "apis/clusters/v1beta1/redis_webhook.go", "hashed_secret": "bc1c5ae5fd4a238d86261f422e62c489de408c22", "is_verified": false, - "line_number": 345 + "line_number": 343 } ], "apis/clusters/v1beta1/zookeeper_types.go": [ @@ -739,7 +735,7 @@ "filename": "pkg/instaclustr/client.go", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 2054 + "line_number": 2060 } ], "pkg/instaclustr/mock/client.go": [ @@ -1130,5 +1126,5 @@ } ] }, - "generated_at": "2024-02-08T08:15:55Z" + "generated_at": "2024-02-08T13:39:05Z" } diff --git a/apis/clusters/v1beta1/redis_types.go b/apis/clusters/v1beta1/redis_types.go index 9d3d3c4d1..0f71faafd 100644 --- a/apis/clusters/v1beta1/redis_types.go +++ b/apis/clusters/v1beta1/redis_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1beta1 import ( - "encoding/json" "strconv" k8scorev1 "k8s.io/api/core/v1" @@ -28,21 +27,23 @@ import ( clusterresourcesv1beta1 "github.com/instaclustr/operator/apis/clusterresources/v1beta1" "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/utils/slices" ) type RedisDataCentre struct { - DataCentre `json:",inline"` + GenericDataCentreSpec `json:",inline"` - MasterNodes int `json:"masterNodes"` + NodeSize string `json:"nodeSize"` + MasterNodes int `json:"masterNodes"` + ReplicaNodes int `json:"replicaNodes,omitempty"` //+kubebuilder:validation:Minimum:=0 //+kubebuilder:validation:Maximum:=5 - // ReplicationFactor defines how many replica nodes (aka nodesNumber) should be created for each master node + // ReplicationFactor defines how many replica nodes should be created for each master node // (e.a. if there are 3 masterNodes and replicationFactor 1 then it creates 1 replicaNode for each accordingly). ReplicationFactor int `json:"replicationFactor,omitempty"` - //+kubebuilder:validation:MaxItems:=1 - PrivateLink []*PrivateLink `json:"privateLink,omitempty"` + PrivateLink PrivateLinkSpec `json:"privateLink,omitempty"` } type RedisRestoreFrom struct { @@ -64,25 +65,36 @@ type RedisRestoreFrom struct { // RedisSpec defines the desired state of Redis type RedisSpec struct { + GenericClusterSpec `json:",inline"` + RestoreFrom *RedisRestoreFrom `json:"restoreFrom,omitempty"` - Cluster `json:",inline"` - OnPremisesSpec *OnPremisesSpec `json:"onPremisesSpec,omitempty"` + OnPremisesSpec *OnPremisesSpec `json:"onPremisesSpec,omitempty"` // Enables client to node encryption - ClientEncryption bool `json:"clientEncryption,omitempty"` - PasswordAndUserAuth bool `json:"passwordAndUserAuth,omitempty"` + ClientEncryption bool `json:"clientEncryption"` + // Enables Password Authentication and User Authorization + PasswordAndUserAuth bool `json:"passwordAndUserAuth"` + //+kubebuilder:validation:MaxItems:=2 - DataCentres []*RedisDataCentre `json:"dataCentres,omitempty"` + DataCentres []*RedisDataCentre `json:"dataCentres"` + + ResizeSettings GenericResizeSettings `json:"resizeSettings,omitempty"` + UserRefs References `json:"userRefs,omitempty"` +} - UserRefs References `json:"userRefs,omitempty"` - //+kubebuilder:validation:MaxItems:=1 - ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` +type RedisDataCentreStatus struct { + GenericDataCentreStatus `json:",inline"` + + Nodes []*Node `json:"nodes"` + PrivateLink PrivateLinkStatuses `json:"privateLink"` } // RedisStatus defines the observed state of Redis type RedisStatus struct { - ClusterStatus `json:",inline"` - AvailableUsers References `json:"availableUsers,omitempty"` + GenericStatus `json:",inline"` + + DataCentres []*RedisDataCentreStatus `json:"dataCentres,omitempty"` + AvailableUsers References `json:"availableUsers,omitempty"` } //+kubebuilder:object:root=true @@ -146,21 +158,16 @@ func (r *Redis) NewBackupSpec(startTimestamp int) *clusterresourcesv1beta1.Clust func (c *Redis) GetSpec() RedisSpec { return c.Spec } func (c *Redis) IsSpecEqual(spec RedisSpec) bool { - return c.Spec.IsEqual(spec) + return c.Spec.IsEqual(&spec) } func (rs *RedisSpec) ToInstAPI() *models.RedisCluster { return &models.RedisCluster{ - Name: rs.Name, + GenericClusterFields: rs.GenericClusterSpec.ToInstAPI(), RedisVersion: rs.Version, ClientToNodeEncryption: rs.ClientEncryption, - PCIComplianceMode: rs.PCICompliance, - PrivateNetworkCluster: rs.PrivateNetworkCluster, PasswordAndUserAuth: rs.PasswordAndUserAuth, - SLATier: rs.SLATier, - Description: rs.Description, DataCentres: rs.DCsToInstAPI(), - TwoFactorDelete: rs.TwoFactorDeletesToInstAPI(), } } @@ -183,24 +190,13 @@ func (r *Redis) RestoreInfoToInstAPI(restoreData *RedisRestoreFrom) any { func (rs *RedisSpec) DCsToInstAPI() (iDCs []*models.RedisDataCentre) { for _, redisDC := range rs.DataCentres { - iSettings := redisDC.CloudProviderSettingsToInstAPI() iDC := &models.RedisDataCentre{ - DataCentre: models.DataCentre{ - Name: redisDC.Name, - Network: redisDC.Network, - NodeSize: redisDC.NodeSize, - AWSSettings: iSettings.AWSSettings, - GCPSettings: iSettings.GCPSettings, - AzureSettings: iSettings.AzureSettings, - Tags: redisDC.TagsToInstAPI(), - CloudProvider: redisDC.CloudProvider, - Region: redisDC.Region, - ProviderAccountName: redisDC.ProviderAccountName, - }, - MasterNodes: redisDC.MasterNodes, - ReplicaNodes: redisDC.NodesNumber, - PrivateLink: privateLinksToInstAPI(redisDC.PrivateLink), - ReplicationFactor: redisDC.ReplicationFactor, + GenericDataCentreFields: redisDC.GenericDataCentreSpec.ToInstAPI(), + NodeSize: redisDC.NodeSize, + MasterNodes: redisDC.MasterNodes, + ReplicaNodes: redisDC.ReplicaNodes, + ReplicationFactor: redisDC.ReplicationFactor, + PrivateLink: privateLinksToInstAPI(redisDC.PrivateLink), } iDCs = append(iDCs, iDC) } @@ -210,7 +206,7 @@ func (rs *RedisSpec) DCsToInstAPI() (iDCs []*models.RedisDataCentre) { func (rs *RedisSpec) DCsUpdateToInstAPI() *models.RedisDataCentreUpdate { return &models.RedisDataCentreUpdate{ DataCentres: rs.DCsToInstAPI(), - ResizeSettings: resizeSettingsToInstAPI(rs.ResizeSettings), + ResizeSettings: rs.ResizeSettings.ToInstAPI(), } } @@ -222,28 +218,38 @@ func (rs *RedisSpec) HasRestore() bool { return false } -func (rs *RedisSpec) IsEqual(iRedis RedisSpec) bool { - return rs.Cluster.IsEqual(iRedis.Cluster) && - iRedis.ClientEncryption == rs.ClientEncryption && - iRedis.PasswordAndUserAuth == rs.PasswordAndUserAuth && - rs.AreDCsEqual(iRedis.DataCentres) && - rs.IsTwoFactorDeleteEqual(iRedis.TwoFactorDelete) +func (rs *RedisSpec) IsEqual(o *RedisSpec) bool { + return rs.GenericClusterSpec.Equals(&o.GenericClusterSpec) && + o.ClientEncryption == rs.ClientEncryption && + o.PasswordAndUserAuth == rs.PasswordAndUserAuth && + rs.DCsEqual(o.DataCentres) +} + +func (rdc *RedisDataCentre) Equals(o *RedisDataCentre) bool { + return rdc.GenericDataCentreSpec.Equals(&o.GenericDataCentreSpec) && + rdc.NodeSize == o.NodeSize && + rdc.ReplicaNodes == o.ReplicaNodes && + rdc.MasterNodes == o.MasterNodes && + slices.EqualsPtr(rdc.PrivateLink, o.PrivateLink) } -func (rs *RedisSpec) AreDCsEqual(iDCs []*RedisDataCentre) bool { - if len(iDCs) != len(rs.DataCentres) { +func (rs *RedisSpec) DCsEqual(o []*RedisDataCentre) bool { + if len(o) != len(rs.DataCentres) { return false } - for i, iDC := range iDCs { - dataCentre := rs.DataCentres[i] + m := map[string]*RedisDataCentre{} + for _, dc := range rs.DataCentres { + m[dc.Name] = dc + } - if iDC.Name != dataCentre.Name { - continue + for _, dc := range o { + mDC, ok := m[dc.Name] + if !ok { + return false } - if !dataCentre.IsEqual(iDC.DataCentre) || - iDC.MasterNodes != dataCentre.MasterNodes { + if !mDC.Equals(dc) { return false } } @@ -251,71 +257,139 @@ func (rs *RedisSpec) AreDCsEqual(iDCs []*RedisDataCentre) bool { return true } -func (r *Redis) FromInstAPI(iData []byte) (*Redis, error) { - iRedis := &models.RedisCluster{} - err := json.Unmarshal(iData, iRedis) - if err != nil { - return nil, err +func (r *Redis) FromInstAPI(instaModel *models.RedisCluster) { + r.Spec.FromInstAPI(instaModel) + r.Status.FromInstAPI(instaModel) +} + +func (rs *RedisSpec) FromInstAPI(instaModel *models.RedisCluster) { + rs.GenericClusterSpec.FromInstAPI(&instaModel.GenericClusterFields) + + rs.ClientEncryption = instaModel.ClientToNodeEncryption + rs.PasswordAndUserAuth = instaModel.PasswordAndUserAuth + rs.Version = instaModel.RedisVersion + + rs.DCsFromInstAPI(instaModel.DataCentres) +} + +func (rs *RedisSpec) DCsFromInstAPI(instaModels []*models.RedisDataCentre) { + rs.DataCentres = make([]*RedisDataCentre, len(instaModels)) + for i, instaModel := range instaModels { + dc := RedisDataCentre{} + dc.FromInstAPI(instaModel) + rs.DataCentres[i] = &dc } +} - return &Redis{ - TypeMeta: r.TypeMeta, - ObjectMeta: r.ObjectMeta, - Spec: r.Spec.FromInstAPI(iRedis), - Status: r.Status.FromInstAPI(iRedis), - }, nil -} - -func (rs *RedisSpec) FromInstAPI(iRedis *models.RedisCluster) RedisSpec { - return RedisSpec{ - Cluster: Cluster{ - Name: iRedis.Name, - Version: iRedis.RedisVersion, - PCICompliance: iRedis.PCIComplianceMode, - PrivateNetworkCluster: iRedis.PrivateNetworkCluster, - SLATier: iRedis.SLATier, - TwoFactorDelete: rs.Cluster.TwoFactorDeleteFromInstAPI(iRedis.TwoFactorDelete), - Description: iRedis.Description, - }, - ClientEncryption: iRedis.ClientToNodeEncryption, - PasswordAndUserAuth: iRedis.PasswordAndUserAuth, - DataCentres: rs.DCsFromInstAPI(iRedis.DataCentres), +func (rdc *RedisDataCentre) FromInstAPI(instaModel *models.RedisDataCentre) { + rdc.GenericDataCentreSpec.FromInstAPI(&instaModel.GenericDataCentreFields) + + rdc.NodeSize = instaModel.NodeSize + rdc.MasterNodes = instaModel.MasterNodes + rdc.ReplicaNodes = instaModel.ReplicaNodes + rdc.ReplicationFactor = instaModel.ReplicationFactor + + rdc.PrivateLink.FromInstAPI(instaModel.PrivateLink) +} + +func (rs *RedisStatus) FromInstAPI(instaModel *models.RedisCluster) { + rs.GenericStatus.FromInstAPI(&instaModel.GenericClusterFields) + rs.DCsFromInstAPI(instaModel.DataCentres) +} + +func (rs *RedisStatus) DCsFromInstAPI(instaModels []*models.RedisDataCentre) { + rs.DataCentres = make([]*RedisDataCentreStatus, len(instaModels)) + for i, instaModel := range instaModels { + dc := RedisDataCentreStatus{} + dc.FromInstAPI(instaModel) + rs.DataCentres[i] = &dc } } -func (rs *RedisSpec) DCsFromInstAPI(iDCs []*models.RedisDataCentre) (dcs []*RedisDataCentre) { - for _, iDC := range iDCs { - iDC.NumberOfNodes = iDC.ReplicaNodes - dcs = append(dcs, &RedisDataCentre{ - DataCentre: rs.Cluster.DCFromInstAPI(iDC.DataCentre), - MasterNodes: iDC.MasterNodes, - PrivateLink: privateLinksFromInstAPI(iDC.PrivateLink), - ReplicationFactor: iDC.ReplicationFactor, - }) +func (s *RedisDataCentreStatus) FromInstAPI(instaModel *models.RedisDataCentre) { + s.GenericDataCentreStatus.FromInstAPI(&instaModel.GenericDataCentreFields) + s.PrivateLink.FromInstAPI(instaModel.PrivateLink) + s.nodesFromInstAPI(instaModel.Nodes) +} + +func (s *RedisDataCentreStatus) nodesFromInstAPI(instaModels []*models.Node) { + s.Nodes = make([]*Node, len(instaModels)) + for i, instaModel := range instaModels { + n := Node{} + n.FromInstAPI(instaModel) + s.Nodes[i] = &n } - return } -func (rs *RedisStatus) FromInstAPI(iRedis *models.RedisCluster) RedisStatus { - return RedisStatus{ - ClusterStatus: ClusterStatus{ - ID: iRedis.ID, - State: iRedis.Status, - DataCentres: rs.DCsFromInstAPI(iRedis.DataCentres), - CurrentClusterOperationStatus: iRedis.CurrentClusterOperationStatus, - MaintenanceEvents: rs.MaintenanceEvents, - }, +func (s *RedisDataCentreStatus) Equals(o *RedisDataCentreStatus) bool { + return s.GenericDataCentreStatus.Equals(&o.GenericDataCentreStatus) && + s.nodesEqual(o.Nodes) && + slices.EqualsPtr(s.PrivateLink, o.PrivateLink) +} + +func (rs *RedisStatus) Equals(o *RedisStatus) bool { + return rs.GenericStatus.Equals(&o.GenericStatus) && + rs.DCsEqual(o.DataCentres) +} + +func (rs *RedisStatus) DCsEqual(o []*RedisDataCentreStatus) bool { + if len(rs.DataCentres) != len(o) { + return false + } + + m := map[string]*RedisDataCentreStatus{} + for _, dc := range rs.DataCentres { + m[dc.Name] = dc + } + + for _, dc := range o { + mDC, ok := m[dc.Name] + if !ok { + return false + } + + if !mDC.Equals(dc) { + return false + } } + + return true } -func (rs *RedisStatus) DCsFromInstAPI(iDCs []*models.RedisDataCentre) (dcs []*DataCentreStatus) { - for _, iDC := range iDCs { - dc := rs.ClusterStatus.DCFromInstAPI(iDC.DataCentre) - dc.PrivateLink = privateLinkStatusesFromInstAPI(iDC.PrivateLink) - dc.NodesNumber += iDC.MasterNodes - dcs = append(dcs, dc) +func (s *RedisDataCentreStatus) nodesEqual(o []*Node) bool { + if len(s.Nodes) != len(o) { + return false + } + + m := map[string]*Node{} + for _, node := range s.Nodes { + m[node.ID] = node + } + + for _, node := range o { + mNode, ok := m[node.ID] + if !ok { + return false + } + + if !mNode.Equals(node) { + return false + } + } + + return true +} + +func (s *RedisStatus) ToOnPremises() ClusterStatus { + dc := &DataCentreStatus{ + ID: s.DataCentres[0].ID, + Nodes: s.DataCentres[0].Nodes, + } + + return ClusterStatus{ + ID: s.ID, + DataCentres: []*DataCentreStatus{dc}, } - return } func (r *Redis) GetUserRefs() References { @@ -360,7 +434,7 @@ func init() { func (r *Redis) GetExposePorts() []k8scorev1.ServicePort { var exposePorts []k8scorev1.ServicePort - if !r.Spec.PrivateNetworkCluster { + if !r.Spec.PrivateNetwork { exposePorts = []k8scorev1.ServicePort{ { Name: models.RedisDB, diff --git a/apis/clusters/v1beta1/redis_webhook.go b/apis/clusters/v1beta1/redis_webhook.go index fa9aec8a5..37cf377d4 100644 --- a/apis/clusters/v1beta1/redis_webhook.go +++ b/apis/clusters/v1beta1/redis_webhook.go @@ -66,13 +66,11 @@ func (r *Redis) Default() { } for _, dataCentre := range r.Spec.DataCentres { - dataCentre.SetDefaultValues() - if dataCentre.MasterNodes != 0 { if dataCentre.ReplicationFactor > 0 { - dataCentre.NodesNumber = dataCentre.MasterNodes * dataCentre.ReplicationFactor + dataCentre.ReplicaNodes = dataCentre.MasterNodes * dataCentre.ReplicationFactor } else { - dataCentre.ReplicationFactor = dataCentre.NodesNumber / dataCentre.MasterNodes + dataCentre.ReplicationFactor = dataCentre.ReplicaNodes / dataCentre.MasterNodes } } } @@ -100,7 +98,7 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object } } - err := r.Spec.Cluster.ValidateCreation() + err := r.Spec.GenericClusterSpec.ValidateCreation() if err != nil { return err } @@ -118,7 +116,7 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object if err != nil { return err } - if r.Spec.PrivateNetworkCluster { + if r.Spec.PrivateNetwork { err = r.Spec.OnPremisesSpec.ValidateSSHGatewayCreation() if err != nil { return err @@ -155,20 +153,16 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object for _, dc := range r.Spec.DataCentres { if r.Spec.OnPremisesSpec != nil { - err = dc.DataCentre.ValidateOnPremisesCreation() + err = dc.GenericDataCentreSpec.ValidateOnPremisesCreation() if err != nil { return err } } else { - err = dc.DataCentre.ValidateCreation() + err = dc.ValidateCreate() if err != nil { return err } } - - if !r.Spec.PrivateNetworkCluster && dc.PrivateLink != nil { - return models.ErrPrivateLinkOnlyWithPrivateNetworkCluster - } } for _, rs := range r.Spec.ResizeSettings { @@ -190,6 +184,10 @@ func (rv *redisValidator) ValidateUpdate(ctx context.Context, old runtime.Object redislog.Info("validate update", "name", r.Name) + if r.Annotations[models.ResourceStateAnnotation] == models.CreatingEvent { + return nil + } + // skip validation when we receive cluster specification update from the Instaclustr Console. if r.Annotations[models.ExternalChangesAnnotation] == models.True { return nil @@ -317,8 +315,8 @@ func (rs *RedisSpec) validateDCsUpdate(oldSpec RedisSpec) error { return fmt.Errorf("deleting nodes is not supported. Master nodes number must be greater than: %v", oldDC.MasterNodes) } - if newDC.NodesNumber < oldDC.NodesNumber { - return fmt.Errorf("deleting nodes is not supported. Number of nodes must be greater than: %v", oldDC.NodesNumber) + if newDC.ReplicaNodes < oldDC.ReplicaNodes { + return fmt.Errorf("deleting nodes is not supported. Number of replica nodes must be greater than: %v", oldDC.ReplicaNodes) } err = validatePrivateLinkUpdate(newDC.PrivateLink, oldDC.PrivateLink) @@ -344,7 +342,7 @@ func (rs *RedisSpec) newImmutableFields() *immutableRedisFields { ClientEncryption: rs.ClientEncryption, PasswordAndUserAuth: rs.PasswordAndUserAuth, }, - immutableCluster: rs.Cluster.newImmutableFields(), + immutableCluster: rs.GenericClusterSpec.immutableFields(), } } @@ -371,7 +369,7 @@ func (rdc *RedisDataCentre) ValidateUpdate() error { } func (rdc *RedisDataCentre) ValidateCreate() error { - err := rdc.DataCentre.ValidateCreation() + err := rdc.GenericDataCentreSpec.validateCreation() if err != nil { return err } @@ -390,7 +388,7 @@ func (rdc *RedisDataCentre) ValidateCreate() error { } func (rdc *RedisDataCentre) ValidateNodesNumber() error { - if rdc.NodesNumber < 0 || rdc.NodesNumber > 100 { + if rdc.ReplicaNodes < 0 || rdc.ReplicaNodes > 100 { return fmt.Errorf("replica nodes number should not be less than 0 or more than 100") } diff --git a/apis/clusters/v1beta1/structs.go b/apis/clusters/v1beta1/structs.go index 8dfe8199f..105d25e0d 100644 --- a/apis/clusters/v1beta1/structs.go +++ b/apis/clusters/v1beta1/structs.go @@ -143,6 +143,29 @@ type PrivateLink struct { AdvertisedHostname string `json:"advertisedHostname"` } +// +kubebuilder:validation:MaxItems:=1 +type PrivateLinkSpec []*PrivateLink + +func (p PrivateLinkSpec) ToInstAPI() []*models.PrivateLink { + instaModels := make([]*models.PrivateLink, len(p)) + for _, pl := range p { + instaModels = append(instaModels, &models.PrivateLink{ + AdvertisedHostname: pl.AdvertisedHostname, + }) + } + + return instaModels +} + +func (p *PrivateLinkSpec) FromInstAPI(o []*models.PrivateLink) { + *p = make(PrivateLinkSpec, len(o)) + for i, instaModel := range o { + (*p)[i] = &PrivateLink{ + AdvertisedHostname: instaModel.AdvertisedHostname, + } + } +} + type privateLinkStatus struct { AdvertisedHostname string `json:"advertisedHostname"` EndPointServiceID string `json:"endPointServiceId,omitempty"` diff --git a/apis/clusters/v1beta1/zz_generated.deepcopy.go b/apis/clusters/v1beta1/zz_generated.deepcopy.go index 2f3ac5970..0baf832be 100644 --- a/apis/clusters/v1beta1/zz_generated.deepcopy.go +++ b/apis/clusters/v1beta1/zz_generated.deepcopy.go @@ -2311,6 +2311,31 @@ func (in *PrivateLink) DeepCopy() *PrivateLink { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in PrivateLinkSpec) DeepCopyInto(out *PrivateLinkSpec) { + { + in := &in + *out = make(PrivateLinkSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PrivateLink) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateLinkSpec. +func (in PrivateLinkSpec) DeepCopy() PrivateLinkSpec { + if in == nil { + return nil + } + out := new(PrivateLinkSpec) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in PrivateLinkStatuses) DeepCopyInto(out *PrivateLinkStatuses) { { @@ -2386,10 +2411,10 @@ func (in *Redis) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisDataCentre) DeepCopyInto(out *RedisDataCentre) { *out = *in - in.DataCentre.DeepCopyInto(&out.DataCentre) + in.GenericDataCentreSpec.DeepCopyInto(&out.GenericDataCentreSpec) if in.PrivateLink != nil { in, out := &in.PrivateLink, &out.PrivateLink - *out = make([]*PrivateLink, len(*in)) + *out = make(PrivateLinkSpec, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] @@ -2410,6 +2435,44 @@ func (in *RedisDataCentre) DeepCopy() *RedisDataCentre { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisDataCentreStatus) DeepCopyInto(out *RedisDataCentreStatus) { + *out = *in + in.GenericDataCentreStatus.DeepCopyInto(&out.GenericDataCentreStatus) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]*Node, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Node) + (*in).DeepCopyInto(*out) + } + } + } + if in.PrivateLink != nil { + in, out := &in.PrivateLink, &out.PrivateLink + *out = make(PrivateLinkStatuses, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(privateLinkStatus) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisDataCentreStatus. +func (in *RedisDataCentreStatus) DeepCopy() *RedisDataCentreStatus { + if in == nil { + return nil + } + out := new(RedisDataCentreStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisList) DeepCopyInto(out *RedisList) { *out = *in @@ -2471,12 +2534,12 @@ func (in *RedisRestoreFrom) DeepCopy() *RedisRestoreFrom { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { *out = *in + in.GenericClusterSpec.DeepCopyInto(&out.GenericClusterSpec) if in.RestoreFrom != nil { in, out := &in.RestoreFrom, &out.RestoreFrom *out = new(RedisRestoreFrom) (*in).DeepCopyInto(*out) } - in.Cluster.DeepCopyInto(&out.Cluster) if in.OnPremisesSpec != nil { in, out := &in.OnPremisesSpec, &out.OnPremisesSpec *out = new(OnPremisesSpec) @@ -2493,24 +2556,24 @@ func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { } } } - if in.UserRefs != nil { - in, out := &in.UserRefs, &out.UserRefs - *out = make(References, len(*in)) + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make(GenericResizeSettings, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(apiextensions.ObjectReference) + *out = new(ResizeSettings) **out = **in } } } - if in.ResizeSettings != nil { - in, out := &in.ResizeSettings, &out.ResizeSettings - *out = make([]*ResizeSettings, len(*in)) + if in.UserRefs != nil { + in, out := &in.UserRefs, &out.UserRefs + *out = make(References, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(ResizeSettings) + *out = new(apiextensions.ObjectReference) **out = **in } } @@ -2530,7 +2593,18 @@ func (in *RedisSpec) DeepCopy() *RedisSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisStatus) DeepCopyInto(out *RedisStatus) { *out = *in - in.ClusterStatus.DeepCopyInto(&out.ClusterStatus) + in.GenericStatus.DeepCopyInto(&out.GenericStatus) + if in.DataCentres != nil { + in, out := &in.DataCentres, &out.DataCentres + *out = make([]*RedisDataCentreStatus, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RedisDataCentreStatus) + (*in).DeepCopyInto(*out) + } + } + } if in.AvailableUsers != nil { in, out := &in.AvailableUsers, &out.AvailableUsers *out = make(References, len(*in)) diff --git a/config/crd/bases/clusters.instaclustr.com_redis.yaml b/config/crd/bases/clusters.instaclustr.com_redis.yaml index 299de65c5..db7c7266f 100644 --- a/config/crd/bases/clusters.instaclustr.com_redis.yaml +++ b/config/crd/bases/clusters.instaclustr.com_redis.yaml @@ -81,8 +81,6 @@ spec: type: string nodeSize: type: string - nodesNumber: - type: integer privateLink: items: properties: @@ -96,11 +94,13 @@ spec: type: array region: type: string + replicaNodes: + type: integer replicationFactor: description: ReplicationFactor defines how many replica nodes - (aka nodesNumber) should be created for each master node (e.a. - if there are 3 masterNodes and replicationFactor 1 then it - creates 1 replicaNode for each accordingly). + should be created for each master node (e.a. if there are + 3 masterNodes and replicationFactor 1 then it creates 1 replicaNode + for each accordingly). maximum: 5 minimum: 0 type: integer @@ -113,7 +113,6 @@ spec: - masterNodes - network - nodeSize - - nodesNumber - region type: object maxItems: 2 @@ -167,6 +166,7 @@ spec: - storageClassName type: object passwordAndUserAuth: + description: Enables Password Authentication and User Authorization type: boolean pciCompliance: description: The PCI compliance standards relate to the security of @@ -174,7 +174,7 @@ spec: provisioned on AWS_VPC, running Cassandra, Kafka, Elasticsearch and Redis. type: boolean - privateNetworkCluster: + privateNetwork: type: boolean resizeSettings: items: @@ -266,6 +266,10 @@ spec: type: array version: type: string + required: + - clientEncryption + - dataCentres + - passwordAndUserAuth type: object status: description: RedisStatus defines the observed state of Redis @@ -283,15 +287,11 @@ spec: - namespace type: object type: array - cdcid: - type: string currentClusterOperationStatus: type: string dataCentres: items: properties: - encryptionKeyId: - type: string id: type: string name: @@ -317,8 +317,6 @@ spec: type: string type: object type: array - nodesNumber: - type: integer privateLink: items: properties: @@ -388,6 +386,9 @@ spec: type: array status: type: string + required: + - nodes + - privateLink type: object type: array id: @@ -478,19 +479,8 @@ spec: type: array type: object type: array - options: - properties: - dataNodeSize: - type: string - masterNodeSize: - type: string - openSearchDashboardsNodeSize: - type: string - type: object state: type: string - twoFactorDeleteEnabled: - type: boolean type: object type: object served: true diff --git a/config/samples/clusters_v1beta1_cassandra.yaml b/config/samples/clusters_v1beta1_cassandra.yaml index 1f3797102..7c2fa3338 100644 --- a/config/samples/clusters_v1beta1_cassandra.yaml +++ b/config/samples/clusters_v1beta1_cassandra.yaml @@ -3,7 +3,7 @@ kind: Cassandra metadata: name: cassandra-cluster spec: - name: "example-cassandra" #(immutable) + name: "bohdan-cassandra" #(immutable) version: "4.0.10" #(immutable) privateNetwork: false #(immutable) dataCentres: diff --git a/config/samples/clusters_v1beta1_redis.yaml b/config/samples/clusters_v1beta1_redis.yaml index aeb40bab6..9a4d8bc14 100644 --- a/config/samples/clusters_v1beta1_redis.yaml +++ b/config/samples/clusters_v1beta1_redis.yaml @@ -9,12 +9,12 @@ metadata: app.kubernetes.io/created-by: operator name: redis-sample spec: - name: "Username-redis" + name: "bohdan-redis" version: "7.0.14" slaTier: "NON_PRODUCTION" clientEncryption: false passwordAndUserAuth: true - privateNetworkCluster: false + privateNetwork: true userRefs: # - name: redisuser-sample-1 # namespace: default @@ -29,17 +29,17 @@ spec: name: "testDC1" cloudProvider: "AWS_VPC" network: "10.1.0.0/16" -# nodeSize: "r6g.large-100-r" + nodeSize: "r6g.large-100-r" # nodeSize: "RDS-DEV-t4g.medium-80" - nodeSize: "RDS-DEV-t4g.small-20" +# nodeSize: "RDS-DEV-t4g.small-20" masterNodes: 3 - nodesNumber: 0 + replicaNodes: 0 replicationFactor: 0 -# tags: -# tag: "oneTag" -# tag2: "twoTags" -# privateLink: -# - advertisedHostname: redis-sample-test.com + tags: + tag: "oneTag" + tag2: "twoTags" + privateLink: + - advertisedHostname: redis-sample-test.com # - region: "US_WEST_2" # name: "testDC2" # cloudProvider: "AWS_VPC" @@ -48,6 +48,6 @@ spec: # nodesNumber: 0 # masterNodes: 3 # replicationFactor: 0 -# resizeSettings: -# - notifySupportContacts: false -# concurrency: 1 \ No newline at end of file + resizeSettings: + - notifySupportContacts: false + concurrency: 1 \ No newline at end of file diff --git a/controllers/clusters/redis_controller.go b/controllers/clusters/redis_controller.go index f70874fe3..9c7ffdef7 100644 --- a/controllers/clusters/redis_controller.go +++ b/controllers/clusters/redis_controller.go @@ -18,7 +18,9 @@ package clusters import ( "context" + "encoding/json" "errors" + "fmt" "strconv" "github.com/go-logr/logr" @@ -115,243 +117,238 @@ func (r *RedisReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } } -func (r *RedisReconciler) handleCreateCluster( - ctx context.Context, - redis *v1beta1.Redis, - l logr.Logger, -) (reconcile.Result, error) { +func (r *RedisReconciler) createFromRestore(redis *v1beta1.Redis, l logr.Logger) (*models.RedisCluster, error) { + l.Info( + "Creating Redis cluster from backup", + "original cluster ID", redis.Spec.RestoreFrom.ClusterID, + ) + + id, err := r.API.RestoreCluster(redis.RestoreInfoToInstAPI(redis.Spec.RestoreFrom), models.RedisAppKind) + if err != nil { + return nil, fmt.Errorf("failed to restore cluster, err: %w", err) + } + + instaModel, err := r.API.GetRedis(id) + if err != nil { + return nil, fmt.Errorf("failed to get redis cluster details, err: %w", err) + } + + l.Info( + "Redis cluster was created from backup", + "original cluster ID", redis.Spec.RestoreFrom.ClusterID, + ) + + r.EventRecorder.Eventf( + redis, models.Normal, models.Created, + "Cluster restore request is sent. Original cluster ID: %s, new cluster ID: %s", + redis.Spec.RestoreFrom.ClusterID, + id, + ) + + return instaModel, nil +} + +func (r *RedisReconciler) createRedis(redis *v1beta1.Redis, l logr.Logger) (*models.RedisCluster, error) { + l.Info( + "Creating Redis cluster", + "cluster name", redis.Spec.Name, + "data centres", redis.Spec.DataCentres, + ) + + b, err := r.API.CreateClusterRaw(instaclustr.RedisEndpoint, redis.Spec.ToInstAPI()) + if err != nil { + return nil, fmt.Errorf("failed to create redis cluster, err: %w", err) + } + + var instaModel models.RedisCluster + err = json.Unmarshal(b, &instaModel) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal body to redis model, err: %w", err) + } + + l.Info( + "Redis cluster was created", + "cluster ID", instaModel.ID, + "cluster name", redis.Spec.Name, + ) + r.EventRecorder.Eventf( + redis, models.Normal, models.Created, + "Cluster creation request is sent. Cluster ID: %s", + instaModel.ID, + ) + + return &instaModel, nil +} + +func (r *RedisReconciler) createCluster(ctx context.Context, redis *v1beta1.Redis, l logr.Logger) error { + var instaModel *models.RedisCluster var err error - if redis.Status.ID == "" { - var id string - if redis.Spec.HasRestore() { - l.Info( - "Creating Redis cluster from backup", - "original cluster ID", redis.Spec.RestoreFrom.ClusterID, - ) - id, err = r.API.RestoreCluster(redis.RestoreInfoToInstAPI(redis.Spec.RestoreFrom), models.RedisAppKind) - if err != nil { - l.Error( - err, "Cannot restore Redis cluster from backup", - "original cluster ID", redis.Spec.RestoreFrom.ClusterID, - ) - r.EventRecorder.Eventf( - redis, models.Warning, models.CreationFailed, - "Cluster restore from backup on the Instaclustr is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + if redis.Spec.HasRestore() { + instaModel, err = r.createFromRestore(redis, l) + } else { + instaModel, err = r.createRedis(redis, l) + } + if err != nil { + return err + } - l.Info( - "Redis cluster was created from backup", - "original cluster ID", redis.Spec.RestoreFrom.ClusterID, - ) + redis.Spec.FromInstAPI(instaModel) + err = r.Update(ctx, redis) + if err != nil { + return fmt.Errorf("failed to update redis spec, err: %w", err) + } - r.EventRecorder.Eventf( - redis, models.Normal, models.Created, - "Cluster restore request is sent. Original cluster ID: %s, new cluster ID: %s", - redis.Spec.RestoreFrom.ClusterID, - id, - ) - } else { - l.Info( - "Creating Redis cluster", - "cluster name", redis.Spec.Name, - "data centres", redis.Spec.DataCentres, - ) + redis.Status.FromInstAPI(instaModel) + err = r.Status().Update(ctx, redis) + if err != nil { + return fmt.Errorf("failed to update redis status, err: %w", err) + } - id, err = r.API.CreateCluster(instaclustr.RedisEndpoint, redis.Spec.ToInstAPI()) - if err != nil { - l.Error( - err, "Cannot create Redis cluster", - "cluster manifest", redis.Spec, - ) - r.EventRecorder.Eventf( - redis, models.Warning, models.CreationFailed, - "Cluster creation on the Instaclustr is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + l.Info("Redis resource has been created", + "cluster name", redis.Name, + "cluster ID", redis.Status.ID, + ) - l.Info( - "Redis cluster was created", - "cluster ID", id, - "cluster name", redis.Spec.Name, - ) - r.EventRecorder.Eventf( - redis, models.Normal, models.Created, - "Cluster creation request is sent. Cluster ID: %s", - id, - ) - } + return nil +} - patch := redis.NewPatch() - redis.Status.ID = id - err = r.Status().Patch(ctx, redis, patch) - if err != nil { - l.Error(err, "Cannot patch Redis cluster status", - "cluster name", redis.Spec.Name) - r.EventRecorder.Eventf(redis, models.Warning, models.PatchFailed, - "Cluster resource status patch is failed. Reason: %v", err) +func (r *RedisReconciler) startClusterJobs(redis *v1beta1.Redis) error { + err := r.startSyncJob(redis) + if err != nil { + return fmt.Errorf("failed to start cluster sync job, err: %w", err) + } - return reconcile.Result{}, err + r.EventRecorder.Eventf( + redis, models.Normal, models.Created, + "Cluster sync job is started", + ) + + err = r.startClusterBackupsJob(redis) + if err != nil { + return fmt.Errorf("failed to start cluster backups check job, err: %w", err) + } + + r.EventRecorder.Eventf( + redis, models.Normal, models.Created, + "Cluster backups check job is started", + ) + + if redis.Spec.UserRefs != nil && redis.Status.AvailableUsers == nil { + err = r.startUsersCreationJob(redis) + if err != nil { + return fmt.Errorf("failed to start user creation job, err: %w", err) } - l.Info("Redis resource has been created", - "cluster name", redis.Name, - "cluster ID", redis.Status.ID, - "api version", redis.APIVersion) + r.EventRecorder.Event(redis, models.Normal, models.Created, + "Cluster user creation job is started") } - patch := redis.NewPatch() - controllerutil.AddFinalizer(redis, models.DeletionFinalizer) - redis.Annotations[models.ResourceStateAnnotation] = models.CreatedEvent - err = r.Patch(ctx, redis, patch) + return nil +} + +func (r *RedisReconciler) handleOnPremisesCreation(ctx context.Context, redis *v1beta1.Redis, l logr.Logger) error { + instaModel, err := r.API.GetRedis(redis.Status.ID) if err != nil { - l.Error(err, "Cannot patch Redis cluster", + l.Error(err, "Cannot get cluster from the Instaclustr API", "cluster name", redis.Spec.Name, - "cluster metadata", redis.ObjectMeta, + "data centres", redis.Spec.DataCentres, + "cluster ID", redis.Status.ID, ) r.EventRecorder.Eventf( - redis, models.Warning, models.PatchFailed, - "Cluster resource patch is failed. Reason: %v", + redis, models.Warning, models.FetchFailed, + "Cluster fetch from the Instaclustr API is failed. Reason: %v", err, ) - return reconcile.Result{}, err + return err } - if redis.Status.State != models.DeletedStatus { - err = r.startClusterStatusJob(redis) - if err != nil { - l.Error(err, "Cannot start cluster status job", - "redis cluster ID", redis.Status.ID, - ) - - r.EventRecorder.Eventf( - redis, models.Warning, models.CreationFailed, - "Cluster status job creation is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + iRedis := v1beta1.Redis{} + iRedis.FromInstAPI(instaModel) + + bootstrap := newOnPremisesBootstrap( + r.Client, + redis, + r.EventRecorder, + iRedis.Status.ToOnPremises(), + redis.Spec.OnPremisesSpec, + newExposePorts(redis.GetExposePorts()), + redis.GetHeadlessPorts(), + redis.Spec.PrivateNetwork, + ) + err = handleCreateOnPremisesClusterResources(ctx, bootstrap) + if err != nil { + l.Error( + err, "Cannot create resources for on-premises cluster", + "cluster spec", redis.Spec.OnPremisesSpec, + ) r.EventRecorder.Eventf( - redis, models.Normal, models.Created, - "Cluster status check job is started", + redis, models.Warning, models.CreationFailed, + "Resources creation for on-premises cluster is failed. Reason: %v", + err, ) + return err + } - if redis.Spec.OnPremisesSpec != nil && redis.Spec.OnPremisesSpec.EnableAutomation { - iData, err := r.API.GetRedis(redis.Status.ID) - if err != nil { - l.Error(err, "Cannot get cluster from the Instaclustr API", - "cluster name", redis.Spec.Name, - "data centres", redis.Spec.DataCentres, - "cluster ID", redis.Status.ID, - ) - r.EventRecorder.Eventf( - redis, models.Warning, models.FetchFailed, - "Cluster fetch from the Instaclustr API is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } - iRedis, err := redis.FromInstAPI(iData) - if err != nil { - l.Error( - err, "Cannot convert cluster from the Instaclustr API", - "cluster name", redis.Spec.Name, - "cluster ID", redis.Status.ID, - ) - r.EventRecorder.Eventf( - redis, models.Warning, models.ConversionFailed, - "Cluster convertion from the Instaclustr API to k8s resource is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } - - bootstrap := newOnPremisesBootstrap( - r.Client, - redis, - r.EventRecorder, - iRedis.Status.ClusterStatus, - redis.Spec.OnPremisesSpec, - newExposePorts(redis.GetExposePorts()), - redis.GetHeadlessPorts(), - redis.Spec.PrivateNetworkCluster, - ) - - err = handleCreateOnPremisesClusterResources(ctx, bootstrap) - if err != nil { - l.Error( - err, "Cannot create resources for on-premises cluster", - "cluster spec", redis.Spec.OnPremisesSpec, - ) - r.EventRecorder.Eventf( - redis, models.Warning, models.CreationFailed, - "Resources creation for on-premises cluster is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + err = r.startClusterOnPremisesIPsJob(redis, bootstrap) + if err != nil { + l.Error(err, "Cannot start on-premises cluster IPs check job", + "cluster ID", redis.Status.ID, + ) - err = r.startClusterOnPremisesIPsJob(redis, bootstrap) - if err != nil { - l.Error(err, "Cannot start on-premises cluster IPs check job", - "cluster ID", redis.Status.ID, - ) + r.EventRecorder.Eventf( + redis, models.Warning, models.CreationFailed, + "On-premises cluster IPs check job is failed. Reason: %v", + err, + ) + return err + } - r.EventRecorder.Eventf( - redis, models.Warning, models.CreationFailed, - "On-premises cluster IPs check job is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + l.Info("On-premises resources have been created", + "cluster name", redis.Spec.Name, + "on-premises Spec", redis.Spec.OnPremisesSpec, + "cluster ID", redis.Status.ID, + ) - l.Info( - "On-premises resources have been created", - "cluster name", redis.Spec.Name, - "on-premises Spec", redis.Spec.OnPremisesSpec, - "cluster ID", redis.Status.ID, - ) - return models.ExitReconcile, nil - } + return nil +} - err = r.startClusterBackupsJob(redis) +func (r *RedisReconciler) handleCreateCluster( + ctx context.Context, + redis *v1beta1.Redis, + l logr.Logger, +) (reconcile.Result, error) { + if redis.Status.ID == "" { + err := r.createCluster(ctx, redis, l) if err != nil { - l.Error(err, "Cannot start Redis cluster backups check job", - "cluster ID", redis.Status.ID, - ) - r.EventRecorder.Eventf( redis, models.Warning, models.CreationFailed, - "Cluster backups job creation is failed. Reason: %v", - err, + "Creation of Redis cluster is failed. Reason: %v", err, ) return reconcile.Result{}, err } + } - r.EventRecorder.Eventf( - redis, models.Normal, models.Created, - "Cluster backups check job is started", - ) + if redis.Status.State != models.DeletedStatus { + patch := redis.NewPatch() + controllerutil.AddFinalizer(redis, models.DeletionFinalizer) + redis.Annotations[models.ResourceStateAnnotation] = models.CreatedEvent + err := r.Patch(ctx, redis, patch) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update redis metadata, err: %w", err) + } - if redis.Spec.UserRefs != nil { - err = r.startUsersCreationJob(redis) + err = r.startClusterJobs(redis) + if err != nil { + return reconcile.Result{}, err + } + if redis.Spec.OnPremisesSpec != nil && redis.Spec.OnPremisesSpec.EnableAutomation { + err = r.handleOnPremisesCreation(ctx, redis, l) if err != nil { - l.Error(err, "Failed to start user creation job") - r.EventRecorder.Eventf(redis, models.Warning, models.CreationFailed, - "User creation job is failed. Reason: %v", err, - ) return reconcile.Result{}, err } - - r.EventRecorder.Event(redis, models.Normal, models.Created, - "Cluster user creation job is started") } } @@ -359,9 +356,6 @@ func (r *RedisReconciler) handleCreateCluster( "Redis resource has been created", "cluster name", redis.Name, "cluster ID", redis.Status.ID, - "kind", redis.Kind, - "api version", redis.APIVersion, - "namespace", redis.Namespace, ) return models.ExitReconcile, nil @@ -384,7 +378,7 @@ func (r *RedisReconciler) handleUpdateCluster( req ctrl.Request, l logr.Logger, ) (reconcile.Result, error) { - iData, err := r.API.GetRedis(redis.Status.ID) + instaModel, err := r.API.GetRedis(redis.Status.ID) if err != nil { l.Error( err, "Cannot get Redis cluster from the Instaclustr API", @@ -400,28 +394,15 @@ func (r *RedisReconciler) handleUpdateCluster( return reconcile.Result{}, err } - iRedis, err := redis.FromInstAPI(iData) - if err != nil { - l.Error( - err, "Cannot convert Redis cluster from the Instaclustr API", - "cluster name", redis.Spec.Name, - "cluster ID", redis.Status.ID, - ) - - r.EventRecorder.Eventf( - redis, models.Warning, models.ConversionFailed, - "Cluster convertion from the Instaclustr API to k8s resource is failed. Reason: %v", - err, - ) - return reconcile.Result{}, err - } + iRedis := v1beta1.Redis{} + iRedis.FromInstAPI(instaModel) if redis.Annotations[models.ExternalChangesAnnotation] == models.True || r.RateLimiter.NumRequeues(req) == rlimiter.DefaultMaxTries { - return handleExternalChanges[v1beta1.RedisSpec](r.EventRecorder, r.Client, redis, iRedis, l) + return handleExternalChanges[v1beta1.RedisSpec](r.EventRecorder, r.Client, redis, &iRedis, l) } - if redis.Spec.ClusterSettingsNeedUpdate(iRedis.Spec.Cluster) { + if redis.Spec.ClusterSettingsNeedUpdate(&iRedis.Spec.GenericClusterSpec) { l.Info("Updating cluster settings", "instaclustr description", iRedis.Spec.Description, "instaclustr two factor delete", iRedis.Spec.TwoFactorDelete) @@ -437,12 +418,15 @@ func (r *RedisReconciler) handleUpdateCluster( } } - if !redis.Spec.IsEqual(iRedis.Spec) { + if !redis.Spec.IsEqual(&iRedis.Spec) { l.Info("Update request to Instaclustr API has been sent", "spec data centres", redis.Spec.DataCentres, "resize settings", redis.Spec.ResizeSettings, ) + // TODO delet logs + l.Info("redis.Spec.DCsUpdateToInstAPI()", "redis.Spec.DCsUpdateToInstAPI()", redis.Spec.DCsUpdateToInstAPI()) + err = r.API.UpdateRedis(redis.Status.ID, redis.Spec.DCsUpdateToInstAPI()) if err != nil { l.Error(err, "Cannot update Redis cluster data centres", @@ -490,6 +474,8 @@ func (r *RedisReconciler) handleUpdateCluster( "data centres", redis.Spec.DataCentres, ) + r.EventRecorder.Event(redis, models.Normal, models.UpdatedEvent, "Cluster has been updated") + return models.ExitReconcile, nil } @@ -669,8 +655,8 @@ func (r *RedisReconciler) startClusterOnPremisesIPsJob(redis *v1beta1.Redis, b * return nil } -func (r *RedisReconciler) startClusterStatusJob(cluster *v1beta1.Redis) error { - job := r.newWatchStatusJob(cluster) +func (r *RedisReconciler) startSyncJob(cluster *v1beta1.Redis) error { + job := r.newSyncJob(cluster) err := r.Scheduler.ScheduleJob(cluster.GetJobID(scheduler.StatusChecker), scheduler.ClusterStatusInterval, job) if err != nil { @@ -736,8 +722,9 @@ func (r *RedisReconciler) newUsersCreationJob(redis *v1beta1.Redis) scheduler.Jo } } -func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job { - l := log.Log.WithValues("component", "redisStatusClusterJob") +func (r *RedisReconciler) newSyncJob(redis *v1beta1.Redis) scheduler.Job { + l := log.Log.WithValues("syncJob", redis.GetJobID(scheduler.StatusChecker), "clusterID", redis.Status.ID) + return func() error { namespacedName := client.ObjectKeyFromObject(redis) err := r.Get(context.Background(), namespacedName, redis) @@ -750,7 +737,7 @@ func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job return nil } - iData, err := r.API.GetRedis(redis.Status.ID) + instaModel, err := r.API.GetRedis(redis.Status.ID) if err != nil { if errors.Is(err, instaclustr.NotFound) { if redis.DeletionTimestamp != nil { @@ -768,26 +755,19 @@ func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job return err } - iRedis, err := redis.FromInstAPI(iData) - if err != nil { - l.Error(err, "Cannot convert Redis cluster status from Instaclustr", - "cluster ID", redis.Status.ID, - ) + iRedis := v1beta1.Redis{} + iRedis.FromInstAPI(instaModel) - return err - } + l.Info("Synchronizer job returns clust api state BEFORE EQUALS FUNC", "nodesize", iRedis.Status.DataCentres, + "k8s redis state", redis.Status.DataCentres) - if !areStatusesEqual(&iRedis.Status.ClusterStatus, &redis.Status.ClusterStatus) { - l.Info("Updating Redis cluster status", - "new status", iRedis.Status, - "old status", redis.Status, - ) + if !redis.Status.Equals(&iRedis.Status) { + l.Info("Updating Redis cluster status") - areDCsEqual := areDataCentresEqual(iRedis.Status.ClusterStatus.DataCentres, redis.Status.ClusterStatus.DataCentres) + areDCsEqual := redis.Status.DCsEqual(iRedis.Status.DataCentres) - patch := redis.NewPatch() - redis.Status.ClusterStatus = iRedis.Status.ClusterStatus - err = r.Status().Patch(context.Background(), redis, patch) + redis.Status.FromInstAPI(instaModel) + err = r.Status().Update(context.Background(), redis) if err != nil { l.Error(err, "Cannot patch Redis cluster", "cluster name", redis.Spec.Name, @@ -797,17 +777,20 @@ func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job return err } + l.Info("Synchronizer job returns clust api state ISDIDE EQUALS FUNC ISDIDE", "nodesize", iRedis.Status.DataCentres, + "k8s redis state", redis.Status.DataCentres) + if !areDCsEqual { var nodes []*v1beta1.Node - for _, dc := range iRedis.Status.ClusterStatus.DataCentres { + for _, dc := range iRedis.Status.DataCentres { nodes = append(nodes, dc.Nodes...) } err = exposeservice.Create(r.Client, redis.Name, redis.Namespace, - redis.Spec.PrivateNetworkCluster, + redis.Spec.PrivateNetwork, nodes, models.RedisConnectionPort) if err != nil { @@ -816,19 +799,13 @@ func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job } } - equals := redis.Spec.IsEqual(iRedis.Spec) + equals := redis.Spec.IsEqual(&iRedis.Spec) if equals && redis.Annotations[models.ExternalChangesAnnotation] == models.True { - patch := redis.NewPatch() - delete(redis.Annotations, models.ExternalChangesAnnotation) - err := r.Patch(context.Background(), redis, patch) + err := reconcileExternalChanges(r.Client, r.EventRecorder, redis) if err != nil { return err } - - r.EventRecorder.Event(redis, models.Normal, models.ExternalChanges, - "External changes were automatically reconciled", - ) } else if redis.Status.CurrentClusterOperationStatus == models.NoOperation && redis.Annotations[models.ResourceStateAnnotation] != models.UpdatingEvent && !equals { @@ -1082,6 +1059,10 @@ func (r *RedisReconciler) SetupWithManager(mgr ctrl.Manager) error { newObj := event.ObjectNew.(*v1beta1.Redis) + if newObj.Status.ID == "" && newObj.Annotations[models.ResourceStateAnnotation] == models.CreatingEvent { + return true + } + if newObj.Status.ID == "" { newObj.Annotations[models.ResourceStateAnnotation] = models.CreatingEvent return true @@ -1113,7 +1094,7 @@ func (r *RedisReconciler) reconcileMaintenanceEvents(ctx context.Context, redis return err } - if !redis.Status.AreMaintenanceEventStatusesEqual(iMEStatuses) { + if !redis.Status.MaintenanceEventsEqual(iMEStatuses) { patch := redis.NewPatch() redis.Status.MaintenanceEvents = iMEStatuses err = r.Status().Patch(ctx, redis, patch) diff --git a/pkg/instaclustr/client.go b/pkg/instaclustr/client.go index 42aaa45fa..90048aad2 100644 --- a/pkg/instaclustr/client.go +++ b/pkg/instaclustr/client.go @@ -183,7 +183,7 @@ func (c *Client) UpdateOpenSearch(id string, o models.OpenSearchInstAPIUpdateReq return nil } -func (c *Client) GetRedis(id string) ([]byte, error) { +func (c *Client) GetRedis(id string) (*models.RedisCluster, error) { url := c.serverHostname + RedisEndpoint + id resp, err := c.DoRequest(url, http.MethodGet, nil) @@ -205,7 +205,13 @@ func (c *Client) GetRedis(id string) ([]byte, error) { return nil, fmt.Errorf("status code: %d, message: %s", resp.StatusCode, body) } - return body, nil + var redis models.RedisCluster + err = json.Unmarshal(body, &redis) + if err != nil { + return nil, err + } + + return &redis, nil } func (c *Client) GetRedisUser(id string) (*models.RedisUser, error) { diff --git a/pkg/instaclustr/interfaces.go b/pkg/instaclustr/interfaces.go index b1dba4831..528be78c1 100644 --- a/pkg/instaclustr/interfaces.go +++ b/pkg/instaclustr/interfaces.go @@ -69,7 +69,7 @@ type API interface { RescheduleMaintenanceEvent(me *clusterresourcesv1beta1.MaintenanceEventReschedule) error CreateNodeReload(nr *clusterresourcesv1beta1.Node) error GetNodeReloadStatus(nodeID string) (*models.NodeReloadStatus, error) - GetRedis(id string) ([]byte, error) + GetRedis(id string) (*models.RedisCluster, error) UpdateRedis(id string, r *models.RedisDataCentreUpdate) error CreateRedisUser(user *models.RedisUser) (string, error) UpdateRedisUser(user *models.RedisUserUpdate) error diff --git a/pkg/instaclustr/mock/client.go b/pkg/instaclustr/mock/client.go index 9054993cd..64abb0c61 100644 --- a/pkg/instaclustr/mock/client.go +++ b/pkg/instaclustr/mock/client.go @@ -298,7 +298,7 @@ func (c *mockClient) GetCassandra(id string) (*models.CassandraCluster, error) { panic("GetCassandra: is not implemented") } -func (c *mockClient) GetRedis(id string) ([]byte, error) { +func (c *mockClient) GetRedis(id string) (*models.RedisCluster, error) { panic("GetRedis: is not implemented") } diff --git a/pkg/models/redis_apiv2.go b/pkg/models/redis_apiv2.go index a52155d59..6434353af 100644 --- a/pkg/models/redis_apiv2.go +++ b/pkg/models/redis_apiv2.go @@ -17,25 +17,25 @@ limitations under the License. package models type RedisCluster struct { - ClusterStatus `json:",inline"` - Name string `json:"name"` - RedisVersion string `json:"redisVersion"` - ClientToNodeEncryption bool `json:"clientToNodeEncryption"` - PCIComplianceMode bool `json:"pciComplianceMode"` - DataCentres []*RedisDataCentre `json:"dataCentres,omitempty"` - PrivateNetworkCluster bool `json:"privateNetworkCluster"` - PasswordAndUserAuth bool `json:"passwordAndUserAuth"` - TwoFactorDelete []*TwoFactorDelete `json:"twoFactorDelete,omitempty"` - SLATier string `json:"slaTier"` - Description string `json:"description,omitempty"` + GenericClusterFields `json:",inline"` + + RedisVersion string `json:"redisVersion"` + ClientToNodeEncryption bool `json:"clientToNodeEncryption"` + PasswordAndUserAuth bool `json:"passwordAndUserAuth"` + + DataCentres []*RedisDataCentre `json:"dataCentres,omitempty"` } type RedisDataCentre struct { - DataCentre `json:",inline"` - MasterNodes int `json:"masterNodes"` - ReplicaNodes int `json:"replicaNodes"` - ReplicationFactor int `json:"replicationFactor,omitempty"` - PrivateLink []*PrivateLink `json:"privateLink,omitempty"` + GenericDataCentreFields `json:",inline"` + + NodeSize string `json:"nodeSize"` + MasterNodes int `json:"masterNodes"` + ReplicaNodes int `json:"replicaNodes,omitempty"` + ReplicationFactor int `json:"replicationFactor,omitempty"` + + Nodes []*Node `json:"nodes,omitempty"` + PrivateLink []*PrivateLink `json:"privateLink,omitempty"` } type RedisDataCentreUpdate struct {