diff --git a/apis/clusters/v1beta1/cadence_types.go b/apis/clusters/v1beta1/cadence_types.go index 84229262e..ef1f71ed4 100644 --- a/apis/clusters/v1beta1/cadence_types.go +++ b/apis/clusters/v1beta1/cadence_types.go @@ -73,6 +73,7 @@ type CadenceSpec struct { SharedProvisioning []*SharedProvisioning `json:"sharedProvisioning,omitempty"` PackagedProvisioning []*PackagedProvisioning `json:"packagedProvisioning,omitempty"` TargetPrimaryCadence []*TargetCadence `json:"targetPrimaryCadence,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type AWSArchival struct { @@ -190,6 +191,7 @@ func (cs *CadenceSpec) ToInstAPI(ctx context.Context, k8sClient client.Client) ( SharedProvisioning: sharedProvisioning, StandardProvisioning: standardProvisioning, TargetPrimaryCadence: cs.TargetCadenceToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(cs.ResizeSettings), }, nil } @@ -352,6 +354,7 @@ func (c *Cadence) FromInstAPI(iData []byte) (*Cadence, error) { func (cs *CadenceSpec) FromInstAPI(iCad *models.CadenceCluster) (spec CadenceSpec) { spec.DataCentres = cs.DCsFromInstAPI(iCad.DataCentres) + spec.ResizeSettings = resizeSettingsFromInstAPI(iCad.ResizeSettings) return } diff --git a/apis/clusters/v1beta1/cadence_webhook.go b/apis/clusters/v1beta1/cadence_webhook.go index 7a2e55e12..f7fe05572 100644 --- a/apis/clusters/v1beta1/cadence_webhook.go +++ b/apis/clusters/v1beta1/cadence_webhook.go @@ -184,6 +184,13 @@ func (cv *cadenceValidator) ValidateCreate(ctx context.Context, obj runtime.Obje } } + for _, rs := range c.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } @@ -211,6 +218,13 @@ func (cv *cadenceValidator) ValidateUpdate(ctx context.Context, old runtime.Obje return fmt.Errorf("cannot update immutable fields: %v", err) } + for _, rs := range c.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } diff --git a/apis/clusters/v1beta1/cassandra_types.go b/apis/clusters/v1beta1/cassandra_types.go index 4899c1e99..723b4b1bf 100644 --- a/apis/clusters/v1beta1/cassandra_types.go +++ b/apis/clusters/v1beta1/cassandra_types.go @@ -70,6 +70,8 @@ type CassandraSpec struct { Spark []*Spark `json:"spark,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` UserRefs []*UserReference `json:"userRefs,omitempty"` + //+kubebuilder:validate:MaxItems:=1 + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } // CassandraStatus defines the observed state of Cassandra @@ -181,9 +183,10 @@ func (cs *CassandraSpec) HasRestore() bool { return false } -func (cs *CassandraSpec) NewDCsUpdate() models.CassandraClusterAPIUpdate { +func (cs *CassandraSpec) ToClusterUpdate() models.CassandraClusterAPIUpdate { return models.CassandraClusterAPIUpdate{ - DataCentres: cs.DCsToInstAPI(), + DataCentres: cs.DCsToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(cs.ResizeSettings), } } @@ -218,6 +221,13 @@ func (cs *CassandraSpec) validateUpdate(oldSpec CassandraSpec) error { return err } + for _, dc := range cs.DataCentres { + err = cs.validateResizeSettings(dc.NodesNumber) + if err != nil { + return err + } + } + return nil } @@ -302,6 +312,7 @@ func (cs *CassandraSpec) FromInstAPI(iCass *models.CassandraCluster) CassandraSp PasswordAndUserAuth: iCass.PasswordAndUserAuth, Spark: cs.SparkFromInstAPI(iCass.Spark), BundledUseOnly: iCass.BundledUseOnly, + ResizeSettings: resizeSettingsFromInstAPI(iCass.ResizeSettings), } } @@ -347,6 +358,7 @@ func (cs *CassandraSpec) ToInstAPI() *models.CassandraCluster { PCIComplianceMode: cs.PCICompliance, TwoFactorDelete: cs.TwoFactorDeletesToInstAPI(), BundledUseOnly: cs.BundledUseOnly, + ResizeSettings: resizeSettingsToInstAPI(cs.ResizeSettings), } } @@ -452,6 +464,16 @@ func (cdc *CassandraDataCentre) newImmutableFields() *immutableCassandraDCFields } } +func (c *CassandraSpec) validateResizeSettings(nodeNumber int) error { + for _, rs := range c.ResizeSettings { + if rs.Concurrency > nodeNumber { + return fmt.Errorf("resizeSettings.concurrency cannot be greater than number of nodes: %v", nodeNumber) + } + } + + return nil +} + func init() { SchemeBuilder.Register(&Cassandra{}, &CassandraList{}) } diff --git a/apis/clusters/v1beta1/cassandra_webhook.go b/apis/clusters/v1beta1/cassandra_webhook.go index 8026fec2c..8195d30f6 100644 --- a/apis/clusters/v1beta1/cassandra_webhook.go +++ b/apis/clusters/v1beta1/cassandra_webhook.go @@ -132,6 +132,10 @@ func (cv *cassandraValidator) ValidateCreate(ctx context.Context, obj runtime.Ob return fmt.Errorf("number of nodes must be a multiple of replication factor: %v", dc.ReplicationFactor) } + err = c.Spec.validateResizeSettings(dc.NodesNumber) + if err != nil { + return err + } } return nil diff --git a/apis/clusters/v1beta1/kafka_types.go b/apis/clusters/v1beta1/kafka_types.go index c4540915b..d4952fbbf 100644 --- a/apis/clusters/v1beta1/kafka_types.go +++ b/apis/clusters/v1beta1/kafka_types.go @@ -78,6 +78,8 @@ type KafkaSpec struct { //+kubebuilder:validation:MinItems:=1 //+kubebuilder:validation:MaxItems:=1 DataCentres []*KafkaDataCentre `json:"dataCentres"` + //+kubebuilder:validation:MaxItems:=1 + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` // Provision additional dedicated nodes for Apache Zookeeper to run on. // Zookeeper nodes will be co-located with Kafka if this is not provided @@ -161,6 +163,7 @@ func (k *KafkaSpec) ToInstAPI() *models.KafkaCluster { Kraft: k.kraftToInstAPI(), KarapaceRestProxy: k.karapaceRestProxyToInstAPI(), KarapaceSchemaRegistry: k.karapaceSchemaRegistryToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(k.ResizeSettings), } } @@ -254,6 +257,7 @@ func (k *KafkaSpec) ToInstAPIUpdate() *models.KafkaInstAPIUpdateRequest { return &models.KafkaInstAPIUpdateRequest{ DataCentre: k.dcToInstAPI(), DedicatedZookeeper: k.dedicatedZookeeperToInstAPIUpdate(), + ResizeSettings: resizeSettingsToInstAPI(k.ResizeSettings), } } @@ -306,6 +310,7 @@ func (ks *KafkaSpec) FromInstAPI(iKafka *models.KafkaCluster) KafkaSpec { Kraft: ks.kraftFromInstAPI(iKafka.Kraft), KarapaceSchemaRegistry: ks.KarapaceSchemaRegistryFromInstAPI(iKafka.KarapaceSchemaRegistry), BundledUseOnly: iKafka.BundledUseOnly, + ResizeSettings: resizeSettingsFromInstAPI(iKafka.ResizeSettings), } } diff --git a/apis/clusters/v1beta1/kafka_webhook.go b/apis/clusters/v1beta1/kafka_webhook.go index 4518c97a2..4ce2a0868 100644 --- a/apis/clusters/v1beta1/kafka_webhook.go +++ b/apis/clusters/v1beta1/kafka_webhook.go @@ -140,6 +140,13 @@ func (kv *kafkaValidator) ValidateCreate(ctx context.Context, obj runtime.Object } } + for _, rs := range k.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } @@ -171,6 +178,13 @@ func (kv *kafkaValidator) ValidateUpdate(ctx context.Context, old runtime.Object return fmt.Errorf("cannot update, error: %v", err) } + for _, rs := range k.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + if k.Status.ID == "" { return kv.ValidateCreate(ctx, k) } diff --git a/apis/clusters/v1beta1/opensearch_types.go b/apis/clusters/v1beta1/opensearch_types.go index 97df48367..db5f8bb86 100644 --- a/apis/clusters/v1beta1/opensearch_types.go +++ b/apis/clusters/v1beta1/opensearch_types.go @@ -18,6 +18,7 @@ package v1beta1 import ( "encoding/json" + "fmt" "strconv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,6 +54,8 @@ type OpenSearchSpec struct { AlertingPlugin bool `json:"alertingPlugin,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` UserRefs []*UserReference `json:"userRefs,omitempty"` + //+kubuilder:validation:MaxItems:=1 + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type OpenSearchDataCentre struct { @@ -108,6 +111,7 @@ func (oss *OpenSearchSpec) ToInstAPI() *models.OpenSearchCluster { IndexManagementPlugin: oss.IndexManagementPlugin, SLATier: oss.SLATier, AlertingPlugin: oss.AlertingPlugin, + ResizeSettings: resizeSettingsToInstAPI(oss.ResizeSettings), } } @@ -246,6 +250,7 @@ func (oss *OpenSearchSpec) FromInstAPI(iOpenSearch *models.OpenSearchCluster) Op IndexManagementPlugin: oss.IndexManagementPlugin, AlertingPlugin: oss.AlertingPlugin, BundledUseOnly: oss.BundledUseOnly, + ResizeSettings: resizeSettingsFromInstAPI(iOpenSearch.ResizeSettings), } } @@ -451,6 +456,7 @@ func (oss *OpenSearchSpec) ToInstAPIUpdate() models.OpenSearchInstAPIUpdateReque DataNodes: oss.dataNodesToInstAPI(), OpenSearchDashboards: oss.dashboardsToInstAPI(), ClusterManagerNodes: oss.clusterManagerNodesToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(oss.ResizeSettings), } } @@ -545,6 +551,16 @@ func (oss *OpenSearchSpec) HasRestore() bool { return false } +func (oss *OpenSearchSpec) validateResizeSettings(nodesNumber int) error { + for _, rs := range oss.ResizeSettings { + if rs.Concurrency > nodesNumber { + return fmt.Errorf("resizeSettings.concurrency cannot be greater than number of nodes: %v", nodesNumber) + } + } + + return nil +} + func init() { SchemeBuilder.Register(&OpenSearch{}, &OpenSearchList{}) } diff --git a/apis/clusters/v1beta1/opensearch_webhook.go b/apis/clusters/v1beta1/opensearch_webhook.go index 61beba24a..5bf96e3b8 100644 --- a/apis/clusters/v1beta1/opensearch_webhook.go +++ b/apis/clusters/v1beta1/opensearch_webhook.go @@ -134,6 +134,18 @@ func (osv *openSearchValidator) ValidateCreate(ctx context.Context, obj runtime. return err } + err = os.Spec.validateResizeSettings(len(os.Spec.ClusterManagerNodes)) + if err != nil { + return err + } + + for _, node := range os.Spec.DataNodes { + err = os.Spec.validateResizeSettings(node.NodesNumber) + if err != nil { + return err + } + } + return nil } diff --git a/apis/clusters/v1beta1/postgresql_types.go b/apis/clusters/v1beta1/postgresql_types.go index 95520269b..168ba4b32 100644 --- a/apis/clusters/v1beta1/postgresql_types.go +++ b/apis/clusters/v1beta1/postgresql_types.go @@ -89,6 +89,8 @@ type PgSpec struct { ClusterConfigurations map[string]string `json:"clusterConfigurations,omitempty"` Description string `json:"description,omitempty"` SynchronousModeStrict bool `json:"synchronousModeStrict,omitempty"` + //+kubebuilder:validate:MaxItems:=1 + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } // PgStatus defines the observed state of PostgreSQL @@ -196,6 +198,13 @@ func (pgs *PgSpec) DCsToInstAPI() (iDCs []*models.PGDataCentre) { return } +func (pgs *PgSpec) ToClusterUpdate() *models.PGClusterUpdate { + return &models.PGClusterUpdate{ + DataCentres: pgs.DCsToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(pgs.ResizeSettings), + } +} + func (pdc *PgDataCentre) ToInstAPI() *models.PGDataCentre { return &models.PGDataCentre{ DataCentre: pdc.DataCentre.ToInstAPI(), diff --git a/apis/clusters/v1beta1/postgresql_webhook.go b/apis/clusters/v1beta1/postgresql_webhook.go index b3374f7f7..903062490 100644 --- a/apis/clusters/v1beta1/postgresql_webhook.go +++ b/apis/clusters/v1beta1/postgresql_webhook.go @@ -128,6 +128,13 @@ func (pgv *pgValidator) ValidateCreate(ctx context.Context, obj runtime.Object) } } + for _, rs := range pg.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } @@ -163,6 +170,13 @@ func (pgv *pgValidator) ValidateUpdate(ctx context.Context, old runtime.Object, return fmt.Errorf("immutable fields validation error: %v", err) } + for _, rs := range pg.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } diff --git a/apis/clusters/v1beta1/redis_types.go b/apis/clusters/v1beta1/redis_types.go index 86a268b8a..64e60779c 100644 --- a/apis/clusters/v1beta1/redis_types.go +++ b/apis/clusters/v1beta1/redis_types.go @@ -80,6 +80,8 @@ type RedisSpec struct { DataCentres []*RedisDataCentre `json:"dataCentres,omitempty"` Description string `json:"description,omitempty"` UserRefs []*UserReference `json:"userRefs,omitempty"` + //+kubebuilder:validation:MaxItems:=1 + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } // RedisStatus defines the observed state of Redis @@ -192,9 +194,10 @@ func (rs *RedisSpec) DCsToInstAPI() (iDCs []*models.RedisDataCentre) { return } -func (rs *RedisSpec) DCsToInstAPIUpdate() *models.RedisDataCentreUpdate { - return &models.RedisDataCentreUpdate{ - DataCentres: rs.DCsToInstAPI(), +func (rs *RedisSpec) ToClusterUpdate() *models.RedisUpdate { + return &models.RedisUpdate{ + DataCentres: rs.DCsToInstAPI(), + ResizeSettings: resizeSettingsToInstAPI(rs.ResizeSettings), } } diff --git a/apis/clusters/v1beta1/redis_webhook.go b/apis/clusters/v1beta1/redis_webhook.go index efe074509..09c48b48e 100644 --- a/apis/clusters/v1beta1/redis_webhook.go +++ b/apis/clusters/v1beta1/redis_webhook.go @@ -123,6 +123,13 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object } } + for _, rs := range r.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } @@ -157,6 +164,13 @@ func (rv *redisValidator) ValidateUpdate(ctx context.Context, old runtime.Object return fmt.Errorf("update validation error: %v", err) } + for _, rs := range r.Spec.ResizeSettings { + err := validateSingleConcurrentResize(rs.Concurrency) + if err != nil { + return err + } + } + return nil } diff --git a/apis/clusters/v1beta1/structs.go b/apis/clusters/v1beta1/structs.go index da46ff1f8..1dd2012e8 100644 --- a/apis/clusters/v1beta1/structs.go +++ b/apis/clusters/v1beta1/structs.go @@ -42,12 +42,13 @@ type DataCentre struct { } type DataCentreStatus struct { - ID string `json:"id,omitempty"` - Status string `json:"status,omitempty"` - Nodes []*Node `json:"nodes,omitempty"` - NodeNumber int `json:"nodeNumber,omitempty"` - EncryptionKeyID string `json:"encryptionKeyId,omitempty"` - PrivateLink PrivateLinkStatuses `json:"privateLink,omitempty"` + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + Nodes []*Node `json:"nodes,omitempty"` + NodeNumber int `json:"nodeNumber,omitempty"` + EncryptionKeyID string `json:"encryptionKeyId,omitempty"` + PrivateLink PrivateLinkStatuses `json:"privateLink,omitempty"` + ResizeOperations []*ResizeOperation `json:"resizeOperations,omitempty"` } type Node struct { @@ -207,6 +208,71 @@ type immutableDC struct { Network string } +type ResizeOperation struct { + // Number of nodes that can be concurrently resized at a given time + ConcurrentResizes int `json:"concurrentResizes,omitempty"` + // Replace operations + ReplaceOperations []*ReplaceOperationInfoV2 `json:"replaceOperations,omitempty"` + // Timestamp of the creation of the operation + Created string `json:"created,omitempty"` + // Timestamp of the completion of the operation + Completed string `json:"completed,omitempty"` + // ID of the operation + ID string `json:"id,omitempty"` + // New size of the node + NewNodeSize string `json:"newNodeSize,omitempty"` + // Timestamp of when Instaclustr Support has been alerted to the resize operation. + InstaclustrSupportAlerted string `json:"instaclustrSupportAlerted,omitempty"` + // Purpose of the node + NodePurpose string `json:"nodePurpose,omitempty"` + // Status of the operation + Status string `json:"status,omitempty"` +} + +type ResizeSettings struct { + NotifySupportContacts bool `json:"notifySupportContacts,omitempty"` + Concurrency int `json:"concurrency,omitempty"` +} + +func resizeSettingsToInstAPI(rss []*ResizeSettings) []*models.ResizeSettings { + iRS := make([]*models.ResizeSettings, 0, len(rss)) + + for _, rs := range rss { + iRS = append(iRS, &models.ResizeSettings{ + NotifySupportContacts: rs.NotifySupportContacts, + Concurrency: rs.Concurrency, + }) + } + + return iRS +} + +func resizeSettingsFromInstAPI(rss []*models.ResizeSettings) []*ResizeSettings { + iRS := make([]*ResizeSettings, 0, len(rss)) + + for _, rs := range rss { + iRS = append(iRS, &ResizeSettings{ + NotifySupportContacts: rs.NotifySupportContacts, + Concurrency: rs.Concurrency, + }) + } + + return iRS +} + +type ReplaceOperationInfoV2 struct { + // ID of the new node in the replacement operation + NewNodeID string `json:"newNodeId,omitempty"` + // Timestamp of the creation of the node replacement operation + Created string `json:"created,omitempty"` + // ID of the node replacement operation + ID string `json:"id,omitempty"` + // ID of the node being replaced + NodeID string `json:"nodeId,omitempty"` + // Status of the node replacement operation + Status string `json:"status,omitempty"` +} + func (c *Cluster) IsEqual(cluster Cluster) bool { return c.Name == cluster.Name && c.Version == cluster.Version && diff --git a/apis/clusters/v1beta1/validation.go b/apis/clusters/v1beta1/validation.go index a825f7957..8575a3f2b 100644 --- a/apis/clusters/v1beta1/validation.go +++ b/apis/clusters/v1beta1/validation.go @@ -205,3 +205,11 @@ func validatePrivateLinkUpdate(new, old []*PrivateLink) error { return nil } + +func validateSingleConcurrentResize(concurrentResizes int) error { + if concurrentResizes > 1 { + return models.ErrOnlySingleConcurrentResizeAvailable + } + + return nil +} diff --git a/apis/clusters/v1beta1/zz_generated.deepcopy.go b/apis/clusters/v1beta1/zz_generated.deepcopy.go index aeedbf2bb..f18d7190f 100644 --- a/apis/clusters/v1beta1/zz_generated.deepcopy.go +++ b/apis/clusters/v1beta1/zz_generated.deepcopy.go @@ -297,6 +297,17 @@ func (in *CadenceSpec) DeepCopyInto(out *CadenceSpec) { } } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CadenceSpec. @@ -488,6 +499,17 @@ func (in *CassandraSpec) DeepCopyInto(out *CassandraSpec) { } } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CassandraSpec. @@ -720,6 +742,17 @@ func (in *DataCentreStatus) DeepCopyInto(out *DataCentreStatus) { } } } + if in.ResizeOperations != nil { + in, out := &in.ResizeOperations, &out.ResizeOperations + *out = make([]*ResizeOperation, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeOperation) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataCentreStatus. @@ -1070,6 +1103,17 @@ func (in *KafkaSpec) DeepCopyInto(out *KafkaSpec) { } } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } if in.DedicatedZookeeper != nil { in, out := &in.DedicatedZookeeper, &out.DedicatedZookeeper *out = make([]*DedicatedZookeeper, len(*in)) @@ -1475,6 +1519,17 @@ func (in *OpenSearchSpec) DeepCopyInto(out *OpenSearchSpec) { } } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSearchSpec. @@ -1700,6 +1755,17 @@ func (in *PgSpec) DeepCopyInto(out *PgSpec) { (*out)[key] = val } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PgSpec. @@ -2005,6 +2071,17 @@ func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { } } } + if in.ResizeSettings != nil { + in, out := &in.ResizeSettings, &out.ResizeSettings + *out = make([]*ResizeSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ResizeSettings) + **out = **in + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSpec. @@ -2033,6 +2110,62 @@ func (in *RedisStatus) DeepCopy() *RedisStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplaceOperationInfoV2) DeepCopyInto(out *ReplaceOperationInfoV2) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplaceOperationInfoV2. +func (in *ReplaceOperationInfoV2) DeepCopy() *ReplaceOperationInfoV2 { + if in == nil { + return nil + } + out := new(ReplaceOperationInfoV2) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResizeOperation) DeepCopyInto(out *ResizeOperation) { + *out = *in + if in.ReplaceOperations != nil { + in, out := &in.ReplaceOperations, &out.ReplaceOperations + *out = make([]*ReplaceOperationInfoV2, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ReplaceOperationInfoV2) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResizeOperation. +func (in *ResizeOperation) DeepCopy() *ResizeOperation { + if in == nil { + return nil + } + out := new(ResizeOperation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResizeSettings) DeepCopyInto(out *ResizeSettings) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResizeSettings. +func (in *ResizeSettings) DeepCopy() *ResizeSettings { + if in == nil { + return nil + } + out := new(ResizeSettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestProxy) DeepCopyInto(out *RestProxy) { *out = *in diff --git a/config/crd/bases/clusters.instaclustr.com_cadences.yaml b/config/crd/bases/clusters.instaclustr.com_cadences.yaml index f87d635d0..e90251d2e 100644 --- a/config/crd/bases/clusters.instaclustr.com_cadences.yaml +++ b/config/crd/bases/clusters.instaclustr.com_cadences.yaml @@ -104,6 +104,8 @@ spec: - nodesNumber - region type: object + maxItems: 1 + minItems: 1 type: array description: type: string @@ -182,6 +184,15 @@ spec: type: boolean privateNetworkCluster: type: boolean + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + type: array sharedProvisioning: items: properties: @@ -325,6 +336,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml index a53b68e77..fdd318380 100644 --- a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml +++ b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml @@ -106,6 +106,15 @@ spec: type: boolean privateNetworkCluster: type: boolean + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + type: array restoreFrom: properties: cdcInfos: @@ -239,6 +248,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml b/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml index 7b1ef9981..488f1a88c 100644 --- a/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml +++ b/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml @@ -294,6 +294,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_kafkas.yaml b/config/crd/bases/clusters.instaclustr.com_kafkas.yaml index 07c923f6c..7939f6014 100644 --- a/config/crd/bases/clusters.instaclustr.com_kafkas.yaml +++ b/config/crd/bases/clusters.instaclustr.com_kafkas.yaml @@ -95,6 +95,7 @@ spec: - region type: object maxItems: 1 + minItems: 1 type: array dedicatedZookeeper: description: Provision additional dedicated nodes for Apache Zookeeper @@ -167,6 +168,16 @@ spec: description: ReplicationFactor to use for new topic. Also represents the number of racks to use when allocating nodes. type: integer + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + maxItems: 1 + type: array restProxy: items: properties: @@ -287,6 +298,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml index e250c4814..d4018b983 100644 --- a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml +++ b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml @@ -147,6 +147,15 @@ spec: type: boolean reportingPlugin: type: boolean + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + type: array restoreFrom: properties: cdcInfos: @@ -273,6 +282,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_postgresqls.yaml b/config/crd/bases/clusters.instaclustr.com_postgresqls.yaml index 2d0fa3286..b4dc8c097 100644 --- a/config/crd/bases/clusters.instaclustr.com_postgresqls.yaml +++ b/config/crd/bases/clusters.instaclustr.com_postgresqls.yaml @@ -163,6 +163,15 @@ spec: type: object privateNetworkCluster: type: boolean + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + type: array slaTier: description: 'Non-production clusters may receive lower priority support and reduced SLAs. Production tier is not available when using Developer @@ -237,6 +246,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_redis.yaml b/config/crd/bases/clusters.instaclustr.com_redis.yaml index 82ff9c2fd..674c7e382 100644 --- a/config/crd/bases/clusters.instaclustr.com_redis.yaml +++ b/config/crd/bases/clusters.instaclustr.com_redis.yaml @@ -112,6 +112,16 @@ spec: type: boolean privateNetworkCluster: type: boolean + resizeSettings: + items: + properties: + concurrency: + type: integer + notifySupportContacts: + type: boolean + type: object + maxItems: 1 + type: array restoreFrom: properties: cdcInfos: @@ -236,6 +246,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/crd/bases/clusters.instaclustr.com_zookeepers.yaml b/config/crd/bases/clusters.instaclustr.com_zookeepers.yaml index 0dbff225b..c7805cca2 100644 --- a/config/crd/bases/clusters.instaclustr.com_zookeepers.yaml +++ b/config/crd/bases/clusters.instaclustr.com_zookeepers.yaml @@ -171,6 +171,60 @@ spec: - advertisedHostname type: object type: array + resizeOperations: + items: + properties: + completed: + description: Timestamp of the completion of the operation + type: string + concurrentResizes: + description: Number of nodes that can be concurrently + resized at a given time + type: integer + created: + description: Timestamp of the creation of the operation + type: string + id: + description: ID of the operation + type: string + instaclustrSupportAlerted: + description: Timestamp of when Instaclustr Support has + been alerted to the resize operation. + type: string + newNodeSize: + description: New size of the node + type: string + nodePurpose: + description: Purpose of the node + type: string + replaceOperations: + description: Replace operations + items: + properties: + created: + description: Timestamp of the creation of the node + replacement operation + type: string + id: + description: ID of the node replacement operation + type: string + newNodeId: + description: ID of the new node in the replacement + operation + type: string + nodeId: + description: ID of the node being replaced + type: string + status: + description: Status of the node replacement operation + type: string + type: object + type: array + status: + description: Status of the operation + type: string + type: object + type: array status: type: string type: object diff --git a/config/samples/clusters_v1beta1_cadence.yaml b/config/samples/clusters_v1beta1_cadence.yaml index b50c5cae9..56bfedb17 100644 --- a/config/samples/clusters_v1beta1_cadence.yaml +++ b/config/samples/clusters_v1beta1_cadence.yaml @@ -58,3 +58,6 @@ spec: # targetPrimaryCadence: # - dependencyCdcId: "cce79be3-7f41-4cad-837c-86d3d8b4be77" # dependencyVpcType: "SEPARATE_VPC" + resizeSettings: + - notifySupportContacts: false + concurrency: 1 \ No newline at end of file diff --git a/config/samples/clusters_v1beta1_cassandra.yaml b/config/samples/clusters_v1beta1_cassandra.yaml index 13aad4f35..a75269fcf 100644 --- a/config/samples/clusters_v1beta1_cassandra.yaml +++ b/config/samples/clusters_v1beta1_cassandra.yaml @@ -43,6 +43,9 @@ spec: # - namespace: default # name: cassandrauser-sample2 slaTier: "NON_PRODUCTION" +# resizeSettings: +# - notifySupportContacts: false +# concurrency: 2 # description: "this is a sample of description" # twoFactorDelete: # - email: "rostyslp@netapp.com" diff --git a/config/samples/clusters_v1beta1_kafka.yaml b/config/samples/clusters_v1beta1_kafka.yaml index b8393e081..6b6c632f4 100644 --- a/config/samples/clusters_v1beta1_kafka.yaml +++ b/config/samples/clusters_v1beta1_kafka.yaml @@ -60,4 +60,7 @@ spec: # nodesNumber: 3 # userRefs: # - name: kafkauser-sample -# namespace: default \ No newline at end of file +# namespace: default + resizeSettings: + - notifySupportContacts: false + concurrency: 1 \ No newline at end of file diff --git a/config/samples/clusters_v1beta1_opensearch.yaml b/config/samples/clusters_v1beta1_opensearch.yaml index 5e49657e6..3e006b323 100644 --- a/config/samples/clusters_v1beta1_opensearch.yaml +++ b/config/samples/clusters_v1beta1_opensearch.yaml @@ -48,3 +48,6 @@ spec: reportingPlugin: false slaTier: NON_PRODUCTION sqlPlugin: false +# resizeSettings: +# - notifySupportContacts: false +# concurrency: 3 diff --git a/config/samples/clusters_v1beta1_postgresql.yaml b/config/samples/clusters_v1beta1_postgresql.yaml index b92140b4b..b8549dd47 100644 --- a/config/samples/clusters_v1beta1_postgresql.yaml +++ b/config/samples/clusters_v1beta1_postgresql.yaml @@ -44,3 +44,6 @@ spec: slaTier: "NON_PRODUCTION" privateNetworkCluster: false synchronousModeStrict: false +# resizeSettings: +# - notifySupportContacts: false +# concurrency: 1 \ No newline at end of file diff --git a/config/samples/clusters_v1beta1_redis.yaml b/config/samples/clusters_v1beta1_redis.yaml index 4cec23d5a..fb33d29d5 100644 --- a/config/samples/clusters_v1beta1_redis.yaml +++ b/config/samples/clusters_v1beta1_redis.yaml @@ -43,4 +43,7 @@ spec: # nodeSize: "t3.small-20-r" # nodesNumber: 0 # masterNodes: 3 -# replicationFactor: 0 \ No newline at end of file +# replicationFactor: 0 +# resizeSettings: +# - notifySupportContacts: false +# concurrency: 1 \ No newline at end of file diff --git a/controllers/clusters/cadence_controller.go b/controllers/clusters/cadence_controller.go index 250f3f691..b7cc2f05a 100644 --- a/controllers/clusters/cadence_controller.go +++ b/controllers/clusters/cadence_controller.go @@ -316,6 +316,10 @@ func (r *CadenceReconciler) HandleUpdateCluster( return models.ReconcileRequeue } + logger.Info("Update request to Instaclustr API has been sent", + "spec data centres", cadence.Spec.DataCentres, + "resize settings", cadence.Spec.ResizeSettings, + ) err = r.API.UpdateCluster(cadence.Status.ID, instaclustr.CadenceEndpoint, cadence.Spec.NewDCsUpdate()) if err != nil { logger.Error(err, "Cannot update Cadence cluster", @@ -344,9 +348,11 @@ func (r *CadenceReconciler) HandleUpdateCluster( return models.ReconcileRequeue } - logger.Info("Cadence cluster was updated", + logger.Info( + "Cluster has been updated", "cluster name", cadence.Spec.Name, - "cluster status", cadence.Status, + "cluster ID", cadence.Status.ID, + "data centres", cadence.Spec.DataCentres, ) return models.ExitReconcile @@ -933,6 +939,34 @@ func (r *CadenceReconciler) newWatchStatusJob(cadence *v1beta1.Cadence) schedule ) } + if cadence.Status.State == models.RunningStatus && cadence.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := cadence.NewPatch() + for _, dc := range cadence.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", cadence.Spec.Name, + "cluster ID", cadence.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), cadence, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", cadence.Spec.Name, + "cluster ID", cadence.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } diff --git a/controllers/clusters/cassandra_controller.go b/controllers/clusters/cassandra_controller.go index 42c8a0576..7feaae308 100644 --- a/controllers/clusters/cassandra_controller.go +++ b/controllers/clusters/cassandra_controller.go @@ -345,7 +345,12 @@ func (r *CassandraReconciler) handleUpdateCluster( } if !cassandra.Spec.AreDCsEqual(iCassandra.Spec.DataCentres) { - err = r.API.UpdateCassandra(cassandra.Status.ID, cassandra.Spec.NewDCsUpdate()) + l.Info("Update request to Instaclustr API has been sent", + "spec data centres", cassandra.Spec.DataCentres, + "resize settings", cassandra.Spec.ResizeSettings, + ) + + err = r.API.UpdateCassandra(cassandra.Status.ID, cassandra.Spec.ToClusterUpdate()) if err != nil { l.Error(err, "Cannot update cluster", "cluster ID", cassandra.Status.ID, @@ -409,7 +414,6 @@ func (r *CassandraReconciler) handleUpdateCluster( "Cluster has been updated", "cluster name", cassandra.Spec.Name, "cluster ID", cassandra.Status.ID, - "namespace", cassandra.Namespace, "data centres", cassandra.Spec.DataCentres, ) @@ -1023,6 +1027,34 @@ func (r *CassandraReconciler) newWatchStatusJob(cassandra *v1beta1.Cassandra) sc ) } + if cassandra.Status.State == models.RunningStatus && cassandra.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := cassandra.NewPatch() + for _, dc := range cassandra.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", cassandra.Spec.Name, + "cluster ID", cassandra.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), cassandra, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", cassandra.Spec.Name, + "cluster ID", cassandra.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } diff --git a/controllers/clusters/kafka_controller.go b/controllers/clusters/kafka_controller.go index 9af9c5954..b597ddba0 100644 --- a/controllers/clusters/kafka_controller.go +++ b/controllers/clusters/kafka_controller.go @@ -236,7 +236,10 @@ func (r *KafkaReconciler) handleUpdateCluster( return models.ExitReconcile } - l.Info("Sending update request to Instaclustr API", "kafka", k.Spec, "kafka ID", k.Status.ID) + l.Info("Update request to Instaclustr API has been sent", + "spec data centres", k.Spec.DataCentres, + "resize settings", k.Spec.ResizeSettings, + ) err = r.API.UpdateCluster(k.Status.ID, instaclustr.KafkaEndpoint, k.Spec.ToInstAPIUpdate()) if err != nil { @@ -278,6 +281,13 @@ func (r *KafkaReconciler) handleUpdateCluster( return models.ReconcileRequeue } + l.Info( + "Cluster has been updated", + "cluster name", k.Spec.Name, + "cluster ID", k.Status.ID, + "data centres", k.Spec.DataCentres, + ) + return models.ExitReconcile } @@ -782,6 +792,34 @@ func (r *KafkaReconciler) newWatchStatusJob(kafka *v1beta1.Kafka) scheduler.Job ) } + if kafka.Status.State == models.RunningStatus && kafka.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := kafka.NewPatch() + for _, dc := range kafka.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", kafka.Spec.Name, + "cluster ID", kafka.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), kafka, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", kafka.Spec.Name, + "cluster ID", kafka.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } diff --git a/controllers/clusters/kafkaconnect_controller.go b/controllers/clusters/kafkaconnect_controller.go index 3af39fb4b..cb7424934 100644 --- a/controllers/clusters/kafkaconnect_controller.go +++ b/controllers/clusters/kafkaconnect_controller.go @@ -209,6 +209,10 @@ func (r *KafkaConnectReconciler) handleUpdateCluster(ctx context.Context, kc *v1 } if !kc.Spec.IsEqual(iKC.Spec) { + l.Info("Update request to Instaclustr API has been sent", + "spec data centres", kc.Spec.DataCentres, + ) + err = r.API.UpdateKafkaConnect(kc.Status.ID, kc.Spec.NewDCsUpdate()) if err != nil { l.Error(err, "Unable to update Kafka Connect cluster", @@ -257,8 +261,11 @@ func (r *KafkaConnectReconciler) handleUpdateCluster(ctx context.Context, kc *v1 return models.ReconcileRequeue } - l.Info("Kafka Connect cluster was updated", + l.Info( + "Cluster has been updated", + "cluster name", kc.Spec.Name, "cluster ID", kc.Status.ID, + "data centres", kc.Spec.DataCentres, ) return models.ExitReconcile diff --git a/controllers/clusters/opensearch_controller.go b/controllers/clusters/opensearch_controller.go index dcbcd887b..94e0ecbc1 100644 --- a/controllers/clusters/opensearch_controller.go +++ b/controllers/clusters/opensearch_controller.go @@ -307,6 +307,11 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( } if !o.Spec.IsEqual(iOpenSearch.Spec) { + logger.Info("Update request to Instaclustr API has been sent", + "spec data centres", o.Spec.DataCentres, + "resize settings", o.Spec.ResizeSettings, + ) + err = r.API.UpdateOpenSearch(o.Status.ID, o.Spec.ToInstAPIUpdate()) if err != nil { logger.Error(err, "Cannot update cluster", @@ -336,9 +341,12 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( return models.ReconcileRequeue } - logger.Info("OpenSearch cluster update request is sent", + logger.Info( + "Cluster has been updated", "cluster name", o.Spec.Name, - "cluster ID", o.Status.ID) + "cluster ID", o.Status.ID, + "data centres", o.Spec.DataCentres, + ) } patch := o.NewPatch() @@ -736,6 +744,34 @@ func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) schedule ) } + if o.Status.State == models.RunningStatus && o.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := o.NewPatch() + for _, dc := range o.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", o.Spec.Name, + "cluster ID", o.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), o, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", o.Spec.Name, + "cluster ID", o.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } diff --git a/controllers/clusters/postgresql_controller.go b/controllers/clusters/postgresql_controller.go index 64e538d5e..cc10b1718 100644 --- a/controllers/clusters/postgresql_controller.go +++ b/controllers/clusters/postgresql_controller.go @@ -350,7 +350,12 @@ func (r *PostgreSQLReconciler) handleUpdateCluster( } if !pg.Spec.AreDCsEqual(iPg.Spec.DataCentres) { - err = r.updateDataCentres(pg) + logger.Info("Update request to Instaclustr API has been sent", + "spec data centres", pg.Spec.DataCentres, + "resize settings", pg.Spec.ResizeSettings, + ) + + err = r.updateCluster(pg) if err != nil { logger.Error(err, "Cannot update Data Centres", "cluster name", pg.Spec.Name, @@ -381,8 +386,11 @@ func (r *PostgreSQLReconciler) handleUpdateCluster( return models.ReconcileRequeue } - logger.Info("PostgreSQL cluster data centres were updated", + logger.Info( + "Cluster has been updated", "cluster name", pg.Spec.Name, + "cluster ID", pg.Status.ID, + "data centres", pg.Spec.DataCentres, ) } @@ -954,6 +962,34 @@ func (r *PostgreSQLReconciler) newWatchStatusJob(pg *v1beta1.PostgreSQL) schedul ) } + if pg.Status.State == models.RunningStatus && pg.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := pg.NewPatch() + for _, dc := range pg.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", pg.Spec.Name, + "cluster ID", pg.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), pg, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", pg.Spec.Name, + "cluster ID", pg.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } @@ -1204,9 +1240,8 @@ func (r *PostgreSQLReconciler) deleteSecret(ctx context.Context, pgCluster *v1be return nil } -func (r *PostgreSQLReconciler) updateDataCentres(cluster *v1beta1.PostgreSQL) error { - instDCs := cluster.Spec.DCsToInstAPI() - err := r.API.UpdatePostgreSQLDataCentres(cluster.Status.ID, instDCs) +func (r *PostgreSQLReconciler) updateCluster(cluster *v1beta1.PostgreSQL) error { + err := r.API.UpdatePostgreSQL(cluster.Status.ID, cluster.Spec.ToClusterUpdate()) if err != nil { return err } diff --git a/controllers/clusters/redis_controller.go b/controllers/clusters/redis_controller.go index 239bee6e5..9c235fcad 100644 --- a/controllers/clusters/redis_controller.go +++ b/controllers/clusters/redis_controller.go @@ -328,7 +328,12 @@ func (r *RedisReconciler) handleUpdateCluster( } if !redis.Spec.IsEqual(iRedis.Spec) { - err = r.API.UpdateRedis(redis.Status.ID, redis.Spec.DCsToInstAPIUpdate()) + logger.Info("Update request to Instaclustr API has been sent", + "spec data centres", redis.Spec.DataCentres, + "resize settings", redis.Spec.ResizeSettings, + ) + + err = r.API.UpdateRedis(redis.Status.ID, redis.Spec.ToClusterUpdate()) if err != nil { logger.Error(err, "Cannot update Redis cluster data centres", "cluster name", redis.Spec.Name, @@ -380,8 +385,11 @@ func (r *RedisReconciler) handleUpdateCluster( return models.ReconcileRequeue } - logger.Info("Redis cluster was updated", + logger.Info( + "Cluster has been updated", "cluster name", redis.Spec.Name, + "cluster ID", redis.Status.ID, + "data centres", redis.Spec.DataCentres, ) return models.ExitReconcile @@ -1036,6 +1044,34 @@ func (r *RedisReconciler) newWatchStatusJob(redis *v1beta1.Redis) scheduler.Job ) } + if redis.Status.State == models.RunningStatus && redis.Status.CurrentClusterOperationStatus == models.OperationInProgress { + patch := redis.NewPatch() + for _, dc := range redis.Status.DataCentres { + resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) + if err != nil { + l.Error(err, "Cannot get data centre resize operations", + "cluster name", redis.Spec.Name, + "cluster ID", redis.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + + dc.ResizeOperations = resizeOperations + err = r.Status().Patch(context.Background(), redis, patch) + if err != nil { + l.Error(err, "Cannot patch data centre resize operations", + "cluster name", redis.Spec.Name, + "cluster ID", redis.Status.ID, + "data centre ID", dc.ID, + ) + + return err + } + } + } + return nil } } diff --git a/pkg/instaclustr/client.go b/pkg/instaclustr/client.go index 16d837a60..1030331d0 100644 --- a/pkg/instaclustr/client.go +++ b/pkg/instaclustr/client.go @@ -208,7 +208,7 @@ func (c *Client) GetRedisUser(id string) (*models.RedisUser, error) { return userRedis, nil } -func (c *Client) UpdateRedis(id string, r *models.RedisDataCentreUpdate) error { +func (c *Client) UpdateRedis(id string, r *models.RedisUpdate) error { url := c.serverHostname + RedisEndpoint + id data, err := json.Marshal(r) if err != nil { @@ -1864,17 +1864,12 @@ func (c *Client) GetPostgreSQL(id string) ([]byte, error) { return body, nil } -func (c *Client) UpdatePostgreSQLDataCentres(id string, dataCentres []*models.PGDataCentre) error { - updateRequest := struct { - DataCentres []*models.PGDataCentre `json:"dataCentres"` - }{ - DataCentres: dataCentres, - } - - reqData, err := json.Marshal(updateRequest) +func (c *Client) UpdatePostgreSQL(id string, r *models.PGClusterUpdate) error { + reqData, err := json.Marshal(r) if err != nil { return err } + url := c.serverHostname + PGSQLEndpoint + id resp, err := c.DoRequest(url, http.MethodPut, reqData) if err != nil { @@ -2407,3 +2402,32 @@ func (c *Client) DeleteAWSEndpointServicePrincipal(principalID string) error { return nil } + +func (c *Client) GetResizeOperationsByClusterDataCentreID(cdcID string) ([]*v1beta1.ResizeOperation, error) { + url := c.serverHostname + fmt.Sprintf(ClusterResizeOperationsEndpoint, cdcID) + "?activeOnly=true" + resp, err := c.DoRequest(url, http.MethodGet, nil) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("status code: %d, message: %s", resp.StatusCode, b) + } + + resize := struct { + Operations []*v1beta1.ResizeOperation `json:"operations"` + }{} + + err = json.Unmarshal(b, &resize) + if err != nil { + return nil, err + } + + return resize.Operations, nil +} diff --git a/pkg/instaclustr/config.go b/pkg/instaclustr/config.go index f2c768abf..bf4b05420 100644 --- a/pkg/instaclustr/config.go +++ b/pkg/instaclustr/config.go @@ -55,6 +55,7 @@ const ( ListAppsVersionsEndpoint = "%s/cluster-management/v2/data-sources/applications/%s/versions/v2/" ClusterSettingsEndpoint = "%s/cluster-management/v2/operations/clusters/v2/%s/change-settings/v2" AWSEndpointServicePrincipalEndpoint = "/cluster-management/v2/resources/aws-endpoint-service-principals/v2/" + ClusterResizeOperationsEndpoint = "/cluster-management/v2/data-sources/cluster-data-centres/v2/%s/resize/operations/v2" ) // constants for API v1 diff --git a/pkg/instaclustr/interfaces.go b/pkg/instaclustr/interfaces.go index 53f4fbf19..721604450 100644 --- a/pkg/instaclustr/interfaces.go +++ b/pkg/instaclustr/interfaces.go @@ -69,7 +69,7 @@ type API interface { CreateNodeReload(nr *clusterresourcesv1beta1.Node) error GetNodeReloadStatus(nodeID string) (*models.NodeReloadStatus, error) GetRedis(id string) ([]byte, error) - UpdateRedis(id string, r *models.RedisDataCentreUpdate) error + UpdateRedis(id string, r *models.RedisUpdate) error CreateRedisUser(user *models.RedisUser) (string, error) UpdateRedisUser(user *models.RedisUserUpdate) error DeleteRedisUser(id string) error @@ -86,7 +86,7 @@ type API interface { GetZookeeper(id string) ([]byte, error) RestoreCassandra(restoreData v1beta1.CassandraRestoreFrom) (string, error) GetPostgreSQL(id string) ([]byte, error) - UpdatePostgreSQLDataCentres(id string, dataCentres []*models.PGDataCentre) error + UpdatePostgreSQL(id string, r *models.PGClusterUpdate) error GetPostgreSQLConfigs(id string) ([]*models.PGConfigs, error) CreatePostgreSQLConfiguration(id, name, value string) error UpdatePostgreSQLConfiguration(id, name, value string) error @@ -104,4 +104,5 @@ type API interface { UpdateClusterSettings(clusterID string, settings *models.ClusterSettings) error CreateAWSEndpointServicePrincipal(spec any) ([]byte, error) DeleteAWSEndpointServicePrincipal(principalID string) error + GetResizeOperationsByClusterDataCentreID(cdcID string) ([]*v1beta1.ResizeOperation, error) } diff --git a/pkg/instaclustr/mock/client.go b/pkg/instaclustr/mock/client.go index b595dafc3..90b5fe88e 100644 --- a/pkg/instaclustr/mock/client.go +++ b/pkg/instaclustr/mock/client.go @@ -244,7 +244,7 @@ func (c *mockClient) GetRedis(id string) ([]byte, error) { panic("GetRedis: is not implemented") } -func (c *mockClient) UpdateRedis(id string, r *models.RedisDataCentreUpdate) error { +func (c *mockClient) UpdateRedis(id string, r *models.RedisUpdate) error { panic("UpdateRedis: is not implemented") } @@ -272,7 +272,7 @@ func (c *mockClient) GetPostgreSQL(id string) ([]byte, error) { panic("GetPostgreSQL: is not implemented") } -func (c *mockClient) UpdatePostgreSQLDataCentres(id string, dataCentres []*models.PGDataCentre) error { +func (c *mockClient) UpdatePostgreSQL(id string, r *models.PGClusterUpdate) error { panic("UpdatePostgreSQLDataCentres: is not implemented") } @@ -375,3 +375,7 @@ func (c *mockClient) DeleteAWSEndpointServicePrincipal(principalID string) error func (c *mockClient) GetRedisUser(id string) (*models.RedisUser, error) { panic("GetRedisUser: is not implemented") } + +func (c *mockClient) GetResizeOperationsByClusterDataCentreID(cdcID string) ([]*clustersv1beta1.ResizeOperation, error) { + panic("GetResizeOperationsByClusterDataCentreID: is not implemented") +} diff --git a/pkg/models/apiv2.go b/pkg/models/apiv2.go index 3efd69009..4dae502e3 100644 --- a/pkg/models/apiv2.go +++ b/pkg/models/apiv2.go @@ -17,7 +17,8 @@ limitations under the License. package models const ( - NoOperation = "NO_OPERATION" + NoOperation = "NO_OPERATION" + OperationInProgress = "OPERATION_IN_PROGRESS" DefaultAccountName = "INSTACLUSTR" @@ -126,3 +127,13 @@ type ClusterSettings struct { Description string `json:"description"` TwoFactorDelete *TwoFactorDelete `json:"twoFactorDelete"` } + +// ResizeSettings determines how resize requests will be performed for the cluster +type ResizeSettings struct { + // Setting this property to true will notify the Instaclustr + // Account's designated support contacts on resize completion + NotifySupportContacts bool `json:"notifySupportContacts,omitempty"` + + // Number of concurrent nodes to resize during a resize operation + Concurrency int `json:"concurrency,omitempty"` +} diff --git a/pkg/models/cadence_apiv2.go b/pkg/models/cadence_apiv2.go index db859aaa5..b12315dc3 100644 --- a/pkg/models/cadence_apiv2.go +++ b/pkg/models/cadence_apiv2.go @@ -40,6 +40,7 @@ type CadenceCluster struct { AWSArchival []*AWSArchival `json:"awsArchival,omitempty"` TargetPrimaryCadence []*TargetCadence `json:"targetPrimaryCadence,omitempty"` TargetSecondaryCadence []*TargetCadence `json:"targetSecondaryCadence,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type CadenceDataCentre struct { diff --git a/pkg/models/cassandra_apiv2.go b/pkg/models/cassandra_apiv2.go index 0d7f79da7..475485910 100644 --- a/pkg/models/cassandra_apiv2.go +++ b/pkg/models/cassandra_apiv2.go @@ -29,6 +29,7 @@ type CassandraCluster struct { PCIComplianceMode bool `json:"pciComplianceMode"` TwoFactorDelete []*TwoFactorDelete `json:"twoFactorDelete,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings"` } type CassandraDataCentre struct { @@ -44,5 +45,6 @@ type Spark struct { } type CassandraClusterAPIUpdate struct { - DataCentres []*CassandraDataCentre `json:"dataCentres"` + DataCentres []*CassandraDataCentre `json:"dataCentres"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } diff --git a/pkg/models/errors.go b/pkg/models/errors.go index 515fa02eb..c62812c1f 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -61,4 +61,5 @@ var ( ErrPrivateLinkSupportedOnlyForSingleDC = errors.New("private link is only supported for a single data centre") ErrPrivateLinkSupportedOnlyForAWS = errors.New("private link is supported only for an AWS cloud provider") ErrImmutableSpec = errors.New("resource specification is immutable") + ErrOnlySingleConcurrentResizeAvailable = errors.New("only 1 concurrent resize is allowed") ) diff --git a/pkg/models/kafka_apiv2.go b/pkg/models/kafka_apiv2.go index 88c94917a..ffe39a220 100644 --- a/pkg/models/kafka_apiv2.go +++ b/pkg/models/kafka_apiv2.go @@ -38,6 +38,7 @@ type KafkaCluster struct { PCIComplianceMode bool `json:"pciComplianceMode"` RestProxy []*RestProxy `json:"restProxy"` SchemaRegistry []*SchemaRegistry `json:"schemaRegistry"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type KafkaDataCentre struct { @@ -79,6 +80,7 @@ type DedicatedZookeeper struct { type KafkaInstAPIUpdateRequest struct { DataCentre []*KafkaDataCentre `json:"dataCentres"` DedicatedZookeeper []*DedicatedZookeeperUpdate `json:"dedicatedZookeeper,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type DedicatedZookeeperUpdate struct { diff --git a/pkg/models/kafka_connect_apiv2.go b/pkg/models/kafka_connect_apiv2.go index 023d4a517..841aafc64 100644 --- a/pkg/models/kafka_connect_apiv2.go +++ b/pkg/models/kafka_connect_apiv2.go @@ -26,6 +26,7 @@ type KafkaConnectCluster struct { CustomConnectors []*CustomConnectors `json:"customConnectors,omitempty"` TargetCluster []*TargetCluster `json:"targetCluster,omitempty"` DataCentres []*KafkaConnectDataCentre `json:"dataCentres,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type ManagedCluster struct { @@ -83,5 +84,6 @@ type KafkaConnectDataCentre struct { } type KafkaConnectAPIUpdate struct { - DataCentres []*KafkaConnectDataCentre `json:"dataCentres,omitempty"` + DataCentres []*KafkaConnectDataCentre `json:"dataCentres,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } diff --git a/pkg/models/opensearch_apiv2.go b/pkg/models/opensearch_apiv2.go index af3848876..ac776aaed 100644 --- a/pkg/models/opensearch_apiv2.go +++ b/pkg/models/opensearch_apiv2.go @@ -39,6 +39,7 @@ type OpenSearchCluster struct { IndexManagementPlugin bool `json:"indexManagementPlugin"` SLATier string `json:"slaTier,omitempty"` AlertingPlugin bool `json:"alertingPlugin"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type OpenSearchDataNodes struct { @@ -67,4 +68,5 @@ type OpenSearchInstAPIUpdateRequest struct { DataNodes []*OpenSearchDataNodes `json:"dataNodes,omitempty"` OpenSearchDashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } diff --git a/pkg/models/postgresql_apiv2.go b/pkg/models/postgresql_apiv2.go index 762973f7e..b31bd8123 100644 --- a/pkg/models/postgresql_apiv2.go +++ b/pkg/models/postgresql_apiv2.go @@ -62,3 +62,8 @@ type ConfigurationProperties struct { ID string `json:"id,omitempty"` Value string `json:"value"` } + +type PGClusterUpdate struct { + DataCentres []*PGDataCentre `json:"dataCentres"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` +} diff --git a/pkg/models/redis_apiv2.go b/pkg/models/redis_apiv2.go index 902e16445..23b1741ed 100644 --- a/pkg/models/redis_apiv2.go +++ b/pkg/models/redis_apiv2.go @@ -37,8 +37,9 @@ type RedisDataCentre struct { PrivateLink []*PrivateLink `json:"privateLink,omitempty"` } -type RedisDataCentreUpdate struct { - DataCentres []*RedisDataCentre `json:"dataCentres"` +type RedisUpdate struct { + DataCentres []*RedisDataCentre `json:"dataCentres"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` } type RedisUser struct {