diff --git a/apis/clusters/v1beta1/generic_status_fields.go b/apis/clusters/v1beta1/generic_status_fields.go new file mode 100644 index 000000000..8a8845ac3 --- /dev/null +++ b/apis/clusters/v1beta1/generic_status_fields.go @@ -0,0 +1,86 @@ +package v1beta1 + +import ( + "k8s.io/utils/strings/slices" + + "github.com/instaclustr/operator/apis/clusterresources/v1beta1" + "github.com/instaclustr/operator/pkg/models" +) + +type GenericStatus struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + CurrentClusterOperationStatus string `json:"currentClusterOperationStatus,omitempty"` + + MaintenanceEvents []*v1beta1.ClusteredMaintenanceEventStatus `json:"maintenanceEvents,omitempty"` +} + +func (s *GenericStatus) Equals(o *GenericStatus) bool { + return s.ID == o.ID && + s.Status == o.Status && + s.CurrentClusterOperationStatus == o.CurrentClusterOperationStatus +} + +func (s *GenericStatus) FromInstAPI(model *models.GenericClusterFields) { + s.ID = model.ID + s.Status = model.Status + s.CurrentClusterOperationStatus = model.CurrentClusterOperationStatus +} + +func (s *GenericStatus) MaintenanceEventsEqual(events []*v1beta1.ClusteredMaintenanceEventStatus) bool { + if len(s.MaintenanceEvents) != len(events) { + return false + } + + for i := range events { + if !areEventStatusesEqual(events[i], s.MaintenanceEvents[i]) { + return false + } + } + + return true +} + +type GenericDataCentreStatus struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + + ResizeOperations []*ResizeOperation `json:"resizeOperations,omitempty"` +} + +func (s *GenericDataCentreStatus) Equals(o *GenericDataCentreStatus) bool { + return s.Name == o.Name && + s.Status == o.Status && + s.ID == o.ID +} + +type Node struct { + ID string `json:"id,omitempty"` + Size string `json:"size,omitempty"` + PublicAddress string `json:"publicAddress,omitempty"` + PrivateAddress string `json:"privateAddress,omitempty"` + Status string `json:"status,omitempty"` + Roles []string `json:"roles,omitempty"` + Rack string `json:"rack,omitempty"` +} + +func (n *Node) Equals(o *Node) bool { + return n.ID == o.ID && + n.Size == o.Size && + n.PublicAddress == o.PublicAddress && + n.PrivateAddress == o.PrivateAddress && + n.Status == o.Status && + n.Rack == o.Rack && + slices.Equal(n.Roles, o.Roles) +} + +func (n *Node) FromInstAPI(model *models.Node) { + n.ID = model.ID + n.Size = model.Size + n.PublicAddress = model.PublicAddress + n.PrivateAddress = model.PrivateAddress + n.Status = model.Status + n.Roles = model.Roles + n.Rack = model.Rack +} diff --git a/apis/clusters/v1beta1/opensearch_types.go b/apis/clusters/v1beta1/opensearch_types.go index 27ec1229e..25337f867 100644 --- a/apis/clusters/v1beta1/opensearch_types.go +++ b/apis/clusters/v1beta1/opensearch_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1beta1 import ( - "encoding/json" "fmt" "strconv" @@ -40,16 +39,16 @@ type OpenSearchSpec struct { Cluster `json:",inline"` DataCentres []*OpenSearchDataCentre `json:"dataCentres,omitempty"` DataNodes []*OpenSearchDataNodes `json:"dataNodes,omitempty"` + Dashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` + ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes,omitempty"` ICUPlugin bool `json:"icuPlugin,omitempty"` AsynchronousSearchPlugin bool `json:"asynchronousSearchPlugin,omitempty"` KNNPlugin bool `json:"knnPlugin,omitempty"` - Dashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` ReportingPlugin bool `json:"reportingPlugin,omitempty"` SQLPlugin bool `json:"sqlPlugin,omitempty"` NotificationsPlugin bool `json:"notificationsPlugin,omitempty"` AnomalyDetectionPlugin bool `json:"anomalyDetectionPlugin,omitempty"` LoadBalancer bool `json:"loadBalancer,omitempty"` - ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes,omitempty"` IndexManagementPlugin bool `json:"indexManagementPlugin,omitempty"` AlertingPlugin bool `json:"alertingPlugin,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` @@ -60,6 +59,91 @@ type OpenSearchSpec struct { IngestNodes []*OpenSearchIngestNodes `json:"ingestNodes,omitempty"` } +func (s *OpenSearchSpec) FromInstAPI(model *models.OpenSearchCluster) { + s.Cluster.FromInstAPI(&model.GenericClusterFields) + + s.Version = model.OpenSearchVersion + s.ICUPlugin = model.ICUPlugin + s.AsynchronousSearchPlugin = model.AsynchronousSearchPlugin + s.KNNPlugin = model.KNNPlugin + s.ReportingPlugin = model.ReportingPlugin + s.SQLPlugin = model.SQLPlugin + s.NotificationsPlugin = model.NotificationsPlugin + s.AnomalyDetectionPlugin = model.AnomalyDetectionPlugin + s.LoadBalancer = model.LoadBalancer + s.IndexManagementPlugin = model.IndexManagementPlugin + s.AlertingPlugin = model.AlertingPlugin + s.BundledUseOnly = model.BundledUseOnly + + s.dcsFromInstAPI(model.DataCentres) + s.dataNodesFromInstAPI(model.DataNodes) + s.dashboardsFromInstAPI(model.OpenSearchDashboards) + s.ingestNodesFromInstAPI(model.IngestNodes) + s.clusterManagerNodesFromInstAPI(model.ClusterManagerNodes) +} + +func (s *OpenSearchSpec) dcsFromInstAPI(dcModels []*models.OpenSearchDataCentre) { + s.DataCentres = make([]*OpenSearchDataCentre, 0, len(dcModels)) + for _, model := range dcModels { + dc := &OpenSearchDataCentre{} + dc.FromInstAPI(model) + s.DataCentres = append(s.DataCentres, dc) + } +} + +func (dc *OpenSearchDataCentre) FromInstAPI(model *models.OpenSearchDataCentre) { + dc.PrivateLink = model.PrivateLink + dc.Name = model.Name + dc.Region = model.Region + dc.CloudProvider = model.CloudProvider + dc.ProviderAccountName = model.ProviderAccountName + dc.Network = model.Network + dc.NumberOfRacks = model.NumberOfRacks + dc.Tags = tagsFromInstAPI(model.Tags) + dc.CloudProviderSettings = cloudProviderSettingsFromInstAPI(&model.GenericDataCentreFields) +} + +func (s *OpenSearchSpec) dataNodesFromInstAPI(dataNodeModels []*models.OpenSearchDataNodes) { + s.DataNodes = make([]*OpenSearchDataNodes, 0, len(dataNodeModels)) + for _, model := range dataNodeModels { + s.DataNodes = append(s.DataNodes, &OpenSearchDataNodes{ + NodeSize: model.NodeSize, + NodesNumber: model.NodeCount, + }) + } +} + +func (s *OpenSearchSpec) dashboardsFromInstAPI(dashboardsModels []*models.OpenSearchDashboards) { + s.Dashboards = make([]*OpenSearchDashboards, 0, len(dashboardsModels)) + for _, model := range dashboardsModels { + s.Dashboards = append(s.Dashboards, &OpenSearchDashboards{ + NodeSize: model.NodeSize, + OIDCProvider: model.OIDCProvider, + Version: model.Version, + }) + } +} + +func (s *OpenSearchSpec) ingestNodesFromInstAPI(ingestNodeModels []*models.OpenSearchIngestNodes) { + s.IngestNodes = make([]*OpenSearchIngestNodes, 0, len(ingestNodeModels)) + for _, model := range ingestNodeModels { + s.IngestNodes = append(s.IngestNodes, &OpenSearchIngestNodes{ + NodeSize: model.NodeSize, + NodeCount: model.NodeCount, + }) + } +} + +func (s *OpenSearchSpec) clusterManagerNodesFromInstAPI(clusterManagerNodeModels []*models.ClusterManagerNodes) { + s.ClusterManagerNodes = make([]*ClusterManagerNodes, 0, len(clusterManagerNodeModels)) + for _, model := range clusterManagerNodeModels { + s.ClusterManagerNodes = append(s.ClusterManagerNodes, &ClusterManagerNodes{ + NodeSize: model.NodeSize, + DedicatedManager: model.DedicatedManager, + }) + } +} + type OpenSearchDataCentre struct { PrivateLink bool `json:"privateLink,omitempty"` Name string `json:"name,omitempty"` @@ -69,9 +153,7 @@ type OpenSearchDataCentre struct { CloudProviderSettings []*CloudProviderSettings `json:"cloudProviderSettings,omitempty"` Network string `json:"network"` Tags map[string]string `json:"tags,omitempty"` - - // ReplicationFactor is a number of racks to use when allocating data nodes. - ReplicationFactor int `json:"replicationFactor"` + NumberOfRacks int `json:"numberOfRacks,omitempty"` } type OpenSearchDataNodes struct { @@ -97,29 +179,32 @@ type OpenSearchIngestNodes struct { func (oss *OpenSearchSpec) ToInstAPI() *models.OpenSearchCluster { return &models.OpenSearchCluster{ + GenericClusterFields: models.GenericClusterFields{ + Name: oss.Name, + Description: oss.Description, + PCIComplianceMode: oss.PCICompliance, + PrivateNetworkCluster: oss.PrivateNetworkCluster, + SLATier: oss.SLATier, + ResizeSettings: resizeSettingsToInstAPI(oss.ResizeSettings), + TwoFactorDelete: oss.TwoFactorDeletesToInstAPI(), + }, + DataNodes: oss.dataNodesToInstAPI(), - PCIComplianceMode: oss.PCICompliance, ICUPlugin: oss.ICUPlugin, OpenSearchVersion: oss.Version, AsynchronousSearchPlugin: oss.AsynchronousSearchPlugin, - TwoFactorDelete: oss.TwoFactorDeletesToInstAPI(), KNNPlugin: oss.KNNPlugin, OpenSearchDashboards: oss.dashboardsToInstAPI(), ReportingPlugin: oss.ReportingPlugin, SQLPlugin: oss.SQLPlugin, - Description: oss.Description, NotificationsPlugin: oss.NotificationsPlugin, DataCentres: oss.dcsToInstAPI(), AnomalyDetectionPlugin: oss.AnomalyDetectionPlugin, LoadBalancer: oss.LoadBalancer, - PrivateNetworkCluster: oss.PrivateNetworkCluster, - Name: oss.Name, BundledUseOnly: oss.BundledUseOnly, ClusterManagerNodes: oss.clusterManagerNodesToInstAPI(), IndexManagementPlugin: oss.IndexManagementPlugin, - SLATier: oss.SLATier, AlertingPlugin: oss.AlertingPlugin, - ResizeSettings: resizeSettingsToInstAPI(oss.ResizeSettings), IngestNodes: oss.ingestNodesToInstAPI(), } } @@ -129,7 +214,7 @@ func (oss *OpenSearchSpec) dcsToInstAPI() (iDCs []*models.OpenSearchDataCentre) providerSettings := cloudProviderSettingsToInstAPI(dc) iDCs = append(iDCs, &models.OpenSearchDataCentre{ - DataCentre: models.DataCentre{ + GenericDataCentreFields: models.GenericDataCentreFields{ Name: dc.Name, Network: dc.Network, AWSSettings: providerSettings.AWSSettings, @@ -141,7 +226,7 @@ func (oss *OpenSearchSpec) dcsToInstAPI() (iDCs []*models.OpenSearchDataCentre) ProviderAccountName: dc.ProviderAccountName, }, PrivateLink: dc.PrivateLink, - NumberOfRacks: dc.ReplicationFactor, + NumberOfRacks: dc.NumberOfRacks, }) } @@ -230,50 +315,9 @@ func (oss *OpenSearchSpec) ingestNodesToInstAPI() (iIngestNodes []*models.OpenSe return } -func (oss *OpenSearch) FromInstAPI(iData []byte) (*OpenSearch, error) { - iOpenSearch := &models.OpenSearchCluster{} - err := json.Unmarshal(iData, iOpenSearch) - if err != nil { - return nil, err - } - - return &OpenSearch{ - TypeMeta: oss.TypeMeta, - ObjectMeta: oss.ObjectMeta, - Spec: oss.Spec.FromInstAPI(iOpenSearch), - Status: oss.Status.FromInstAPI(iOpenSearch), - }, nil -} - -func (oss *OpenSearchSpec) FromInstAPI(iOpenSearch *models.OpenSearchCluster) OpenSearchSpec { - return OpenSearchSpec{ - Cluster: Cluster{ - Name: iOpenSearch.Name, - Version: iOpenSearch.OpenSearchVersion, - PCICompliance: iOpenSearch.PCIComplianceMode, - Description: iOpenSearch.Description, - PrivateNetworkCluster: iOpenSearch.PrivateNetworkCluster, - SLATier: iOpenSearch.SLATier, - TwoFactorDelete: oss.Cluster.TwoFactorDeleteFromInstAPI(iOpenSearch.TwoFactorDelete), - }, - DataCentres: oss.DCsFromInstAPI(iOpenSearch.DataCentres), - DataNodes: oss.DataNodesFromInstAPI(iOpenSearch.DataNodes), - ICUPlugin: oss.ICUPlugin, - AsynchronousSearchPlugin: oss.AsynchronousSearchPlugin, - KNNPlugin: oss.KNNPlugin, - Dashboards: oss.DashboardsFromInstAPI(iOpenSearch.OpenSearchDashboards), - ReportingPlugin: oss.ReportingPlugin, - SQLPlugin: oss.SQLPlugin, - NotificationsPlugin: oss.NotificationsPlugin, - AnomalyDetectionPlugin: oss.AnomalyDetectionPlugin, - LoadBalancer: oss.LoadBalancer, - ClusterManagerNodes: oss.ClusterManagerNodesFromInstAPI(iOpenSearch.ClusterManagerNodes), - IndexManagementPlugin: oss.IndexManagementPlugin, - AlertingPlugin: oss.AlertingPlugin, - BundledUseOnly: oss.BundledUseOnly, - ResizeSettings: resizeSettingsFromInstAPI(iOpenSearch.ResizeSettings), - IngestNodes: oss.IngestNodesFromInstAPI(iOpenSearch.IngestNodes), - } +func (oss *OpenSearch) FromInstAPI(model *models.OpenSearchCluster) { + oss.Spec.FromInstAPI(model) + oss.Status.FromInstAPI(model) } func (oss *OpenSearch) RestoreInfoToInstAPI(restoreData *OpenSearchRestoreFrom) any { @@ -294,24 +338,6 @@ func (oss *OpenSearch) RestoreInfoToInstAPI(restoreData *OpenSearchRestoreFrom) return iRestore } -func (oss *OpenSearchSpec) DCsFromInstAPI(iDCs []*models.OpenSearchDataCentre) (dcs []*OpenSearchDataCentre) { - for _, iDC := range iDCs { - dcs = append(dcs, &OpenSearchDataCentre{ - Name: iDC.Name, - Region: iDC.Region, - CloudProvider: iDC.CloudProvider, - ProviderAccountName: iDC.ProviderAccountName, - CloudProviderSettings: cloudProviderSettingsFromInstAPI(iDC), - Network: iDC.Network, - Tags: tagsFromInstAPI(iDC.Tags), - PrivateLink: iDC.PrivateLink, - ReplicationFactor: iDC.NumberOfRacks, - }) - } - - return -} - func tagsFromInstAPI(iTags []*models.Tag) map[string]string { newTags := map[string]string{} for _, iTag := range iTags { @@ -320,10 +346,8 @@ func tagsFromInstAPI(iTags []*models.Tag) map[string]string { return newTags } -func cloudProviderSettingsFromInstAPI(iDC *models.OpenSearchDataCentre) (settings []*CloudProviderSettings) { - if isCloudProviderSettingsEmpty(iDC.DataCentre) { - return nil - } +func cloudProviderSettingsFromInstAPI(iDC *models.GenericDataCentreFields) []*CloudProviderSettings { + settings := []*CloudProviderSettings{} switch iDC.CloudProvider { case models.AWSVPC: @@ -346,67 +370,8 @@ func cloudProviderSettingsFromInstAPI(iDC *models.OpenSearchDataCentre) (setting }) } } - return -} - -func (oss *OpenSearchSpec) DataNodesFromInstAPI(iDataNodes []*models.OpenSearchDataNodes) (dataNodes []*OpenSearchDataNodes) { - for _, iNode := range iDataNodes { - dataNodes = append(dataNodes, &OpenSearchDataNodes{ - NodeSize: iNode.NodeSize, - NodesNumber: iNode.NodeCount, - }) - } - return -} - -func (oss *OpenSearchSpec) IngestNodesFromInstAPI(iIngestNodes []*models.OpenSearchIngestNodes) (ingestNodes []*OpenSearchIngestNodes) { - for _, iNode := range iIngestNodes { - ingestNodes = append(ingestNodes, &OpenSearchIngestNodes{ - NodeSize: iNode.NodeSize, - NodeCount: iNode.NodeCount, - }) - } - return -} - -func (oss *OpenSearchSpec) DashboardsFromInstAPI(iDashboards []*models.OpenSearchDashboards) (dashboards []*OpenSearchDashboards) { - for _, iDashboard := range iDashboards { - dashboards = append(dashboards, &OpenSearchDashboards{ - NodeSize: iDashboard.NodeSize, - OIDCProvider: iDashboard.OIDCProvider, - Version: iDashboard.Version, - }) - } - return -} - -func (oss *OpenSearchSpec) ClusterManagerNodesFromInstAPI(iManagerNodes []*models.ClusterManagerNodes) (managerNodes []*ClusterManagerNodes) { - for _, iNode := range iManagerNodes { - managerNodes = append(managerNodes, &ClusterManagerNodes{ - NodeSize: iNode.NodeSize, - DedicatedManager: iNode.DedicatedManager, - }) - } - return -} -func (oss *OpenSearchStatus) FromInstAPI(iOpenSearch *models.OpenSearchCluster) OpenSearchStatus { - return OpenSearchStatus{ - ClusterStatus: ClusterStatus{ - ID: iOpenSearch.ID, - State: iOpenSearch.Status, - DataCentres: oss.DCsFromInstAPI(iOpenSearch.DataCentres), - CurrentClusterOperationStatus: iOpenSearch.CurrentClusterOperationStatus, - MaintenanceEvents: oss.MaintenanceEvents, - }, - } -} - -func (oss *OpenSearchStatus) DCsFromInstAPI(iDCs []*models.OpenSearchDataCentre) (dcs []*DataCentreStatus) { - for _, iDC := range iDCs { - dcs = append(dcs, oss.ClusterStatus.DCFromInstAPI(iDC.DataCentre)) - } - return + return settings } func (a *OpenSearchSpec) IsEqual(b OpenSearchSpec) bool { @@ -448,7 +413,7 @@ func (oss *OpenSearchSpec) areDCsEqual(b []*OpenSearchDataCentre) bool { a[i].Network != b[i].Network && areTagsEqual(a[i].Tags, b[i].Tags) && a[i].PrivateLink != b[i].PrivateLink || - a[i].ReplicationFactor != b[i].ReplicationFactor { + a[i].NumberOfRacks != b[i].NumberOfRacks { return false } } @@ -531,16 +496,145 @@ type OpenSearchRestoreFrom struct { // OpenSearchStatus defines the observed state of OpenSearch type OpenSearchStatus struct { - ClusterStatus `json:",inline"` + GenericStatus `json:",inline"` + + DataCentres []OpenSearchDataCentreStatus `json:"dataCentres,omitempty"` + + LoadBalancerConnectionURL string `json:"loadBalancerConnectionURL,omitempty"` + IngestNodesLoadBalancerConnectionURL string `json:"ingestNodesLoadBalancerConnectionURL,omitempty"` + PrivateEndpoint string `json:"privateEndpoint,omitempty"` + PublicEndpoint string `json:"publicEndpoint,omitempty"` + AvailableUsers References `json:"availableUsers,omitempty"` } +func (oss *OpenSearchStatus) FromInstAPI(model *models.OpenSearchCluster) { + oss.GenericStatus.FromInstAPI(&model.GenericClusterFields) + + oss.PrivateEndpoint = model.PrivateEndpoint + oss.PublicEndpoint = model.PublicEndpoint + oss.IngestNodesLoadBalancerConnectionURL = model.IngestNodesLoadBalancerConnectionURL + oss.LoadBalancerConnectionURL = model.LoadBalancerConnectionURL + + oss.DataCentres = make([]OpenSearchDataCentreStatus, 0, len(model.DataCentres)) + for _, dc := range model.DataCentres { + d := OpenSearchDataCentreStatus{} + d.FromInstAPI(dc) + oss.DataCentres = append(oss.DataCentres, d) + } +} + +type OpenSearchDataCentreStatus struct { + GenericDataCentreStatus `json:",inline"` + + PrivateLinkEndpointServiceName string `json:"privateLinkEndpointServiceName,omitempty"` + PrivateLinkRestAPIURL string `json:"privateLinkRestApiUrl,omitempty"` + + Nodes []OpenSearchNodeStatus `json:"nodes,omitempty"` +} + +func (s *OpenSearchDataCentreStatus) FromInstAPI(model *models.OpenSearchDataCentre) { + s.ID = model.ID + s.PrivateLinkRestAPIURL = model.PrivateLinkRestAPIURL + s.PrivateLinkEndpointServiceName = model.PrivateLinkEndpointServiceName + s.Status = model.Status + + s.Nodes = make([]OpenSearchNodeStatus, 0, len(model.Nodes)) + for _, node := range model.Nodes { + n := OpenSearchNodeStatus{} + n.FromInstAPI(&node) + s.Nodes = append(s.Nodes, n) + } +} + +type OpenSearchNodeStatus struct { + Node `json:",inline"` + + PublicEndpoint string `json:"publicEndpoint,omitempty"` + PrivateEndpoint string `json:"privateEndpoint,omitempty"` + PrivateLinkConnectionURL string `json:"privateLinkConnectionUrl,omitempty"` +} + +func (s *OpenSearchNodeStatus) FromInstAPI(model *models.OpenSearchNode) { + s.Node.FromInstAPI(&model.Node) + + s.PrivateLinkConnectionURL = model.PrivateLinkConnectionURL + s.PrivateEndpoint = model.PrivateEndpoint + s.PublicEndpoint = model.PublicEndpoint +} + +func (s *OpenSearchNodeStatus) Equals(o *OpenSearchNodeStatus) bool { + return s.Node.Equals(&o.Node) && + s.PublicEndpoint == o.PublicEndpoint && + s.PrivateEndpoint == o.PrivateEndpoint && + s.PrivateLinkConnectionURL == o.PrivateLinkConnectionURL +} + +func (s *OpenSearchDataCentreStatus) Equals(o *OpenSearchDataCentreStatus) bool { + if len(s.Nodes) != len(o.Nodes) { + return false + } + + compare := map[string]OpenSearchNodeStatus{} + for _, k8sNode := range s.Nodes { + compare[k8sNode.ID] = k8sNode + } + + for _, iNode := range o.Nodes { + k8sNode, ok := compare[iNode.ID] + if !ok { + return false + } + + if !k8sNode.Equals(&iNode) { + return false + } + } + + return s.GenericDataCentreStatus.Equals(&o.GenericDataCentreStatus) && + s.PrivateLinkRestAPIURL == o.PrivateLinkRestAPIURL && + s.PrivateLinkEndpointServiceName == o.PrivateLinkEndpointServiceName +} + +func (oss *OpenSearchStatus) DataCentreEquals(s *OpenSearchStatus) bool { + if len(oss.DataCentres) != len(s.DataCentres) { + return false + } + + compare := map[string]OpenSearchDataCentreStatus{} + for _, k8sDC := range oss.DataCentres { + compare[k8sDC.ID] = k8sDC + } + + for _, iDC := range s.DataCentres { + k8sDC, ok := compare[iDC.ID] + if !ok { + return false + } + + if !k8sDC.Equals(&iDC) { + return false + } + } + + return true +} + +func (oss *OpenSearchStatus) Equals(o *OpenSearchStatus) bool { + return oss.GenericStatus.Equals(&o.GenericStatus) && + oss.PublicEndpoint == o.PublicEndpoint && + oss.PrivateEndpoint == o.PrivateEndpoint && + oss.LoadBalancerConnectionURL == o.LoadBalancerConnectionURL && + oss.IngestNodesLoadBalancerConnectionURL == o.IngestNodesLoadBalancerConnectionURL && + oss.DataCentreEquals(o) +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" //+kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version" //+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id" -//+kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" // OpenSearch is the Schema for the opensearches API type OpenSearch struct { diff --git a/apis/clusters/v1beta1/opensearch_types_test.go b/apis/clusters/v1beta1/opensearch_types_test.go new file mode 100644 index 000000000..2a4698390 --- /dev/null +++ b/apis/clusters/v1beta1/opensearch_types_test.go @@ -0,0 +1,181 @@ +package v1beta1 + +import ( + "testing" +) + +func TestOpenSearchStatus_Equals(t *testing.T) { + tests := map[string]struct { + fields OpenSearchStatus + args OpenSearchStatus + want bool + }{ + "Equal Empty Objects": { + fields: OpenSearchStatus{}, + args: OpenSearchStatus{}, + want: true, + }, + "Different LoadBalancerConnectionURL": { + fields: OpenSearchStatus{LoadBalancerConnectionURL: ""}, + args: OpenSearchStatus{LoadBalancerConnectionURL: "http://loadbalancer1"}, + want: false, + }, + "Different PrivateEndpoint": { + fields: OpenSearchStatus{PrivateEndpoint: ""}, + args: OpenSearchStatus{PrivateEndpoint: "http://private1"}, + want: false, + }, + "Different PublicEndpoint": { + fields: OpenSearchStatus{PublicEndpoint: ""}, + args: OpenSearchStatus{PublicEndpoint: "http://public1"}, + want: false, + }, + "Different IngestNodesLoadBalancerConnectionURL": { + fields: OpenSearchStatus{IngestNodesLoadBalancerConnectionURL: ""}, + args: OpenSearchStatus{IngestNodesLoadBalancerConnectionURL: "http://ingest1"}, + want: false, + }, + + "Different GenericStatus ID": { + fields: OpenSearchStatus{GenericStatus: GenericStatus{ID: ""}}, + args: OpenSearchStatus{GenericStatus: GenericStatus{ID: "1"}}, + want: false, + }, + "Different GenericStatus Status": { + fields: OpenSearchStatus{GenericStatus: GenericStatus{Status: "PROVISIONING"}}, + args: OpenSearchStatus{GenericStatus: GenericStatus{Status: "RUNNING"}}, + want: false, + }, + "Different GenericStatus CurrentClusterOperationStatus": { + fields: OpenSearchStatus{GenericStatus: GenericStatus{CurrentClusterOperationStatus: "NO_OPERATION"}}, + args: OpenSearchStatus{GenericStatus: GenericStatus{CurrentClusterOperationStatus: "OPERATION_IN_PROGRESS"}}, + want: false, + }, + + "Different DataCentre ID": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{ID: ""}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{ID: "dc1"}}}}, + want: false, + }, + "Different DataCentre Name": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{Name: ""}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{Name: "DataCentre1"}}}}, + want: false, + }, + "Different DataCentre Status": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{Status: "PROVISIONING"}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{GenericDataCentreStatus: GenericDataCentreStatus{Status: "RUNNING"}}}}, + want: false, + }, + + "Different Node PublicEndpoint": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PublicEndpoint: ""}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PublicEndpoint: "http://public.node1"}}}}}, + want: false, + }, + "Different Node PrivateEndpoint": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PrivateEndpoint: ""}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PrivateEndpoint: "http://private.node1"}}}}}, + want: false, + }, + "Different Node PrivateLinkConnectionURL": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PrivateLinkConnectionURL: ""}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{PrivateLinkConnectionURL: "http://privatelink.node1"}}}}}, + want: false, + }, + + "Different Node ID": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{ID: "node1"}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{ID: "node2"}}}}}}, + want: false, + }, + "Different Node Size": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Size: "large"}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Size: "small"}}}}}}, + want: false, + }, + "Different Node PublicAddress": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{PublicAddress: ""}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{PublicAddress: "192.168.1.2"}}}}}}, + want: false, + }, + "Different Node PrivateAddress": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{PrivateAddress: ""}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{PrivateAddress: "10.0.0.2"}}}}}}, + want: false, + }, + "Different Node Status": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Status: "PROVISIONING"}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Status: "RUNNING"}}}}}}, + want: false, + }, + "Different Node Roles": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Roles: []string{""}}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Roles: []string{"MASTER"}}}}}}}, + want: false, + }, + "Different Node Rack": { + fields: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Rack: ""}}}}}}, + args: OpenSearchStatus{DataCentres: []OpenSearchDataCentreStatus{{Nodes: []OpenSearchNodeStatus{{Node: Node{Rack: "rack1"}}}}}}, + want: false, + }, + + "Different OpenSearchNodeStatus PublicEndpoint": { + fields: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PublicEndpoint: ""}}}, + }, + }, + args: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PublicEndpoint: "http://public.node1"}}}, + }, + }, + want: false, + }, + "Different OpenSearchNodeStatus PrivateEndpoint": { + fields: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PrivateEndpoint: ""}}}, + }, + }, + args: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PrivateEndpoint: "http://private.node1"}}}, + }, + }, + want: false, + }, + "Different OpenSearchNodeStatus PrivateLinkConnectionURL": { + fields: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PrivateLinkConnectionURL: ""}}}, + }, + }, + args: OpenSearchStatus{ + DataCentres: []OpenSearchDataCentreStatus{ + {Nodes: []OpenSearchNodeStatus{{PrivateLinkConnectionURL: "http://privatelink.node1"}}}, + }, + }, + want: false, + }, + } + + for name, tt := range tests { + tt := tt + + t.Run(name, func(t *testing.T) { + oss := &OpenSearchStatus{ + GenericStatus: tt.fields.GenericStatus, + DataCentres: tt.fields.DataCentres, + LoadBalancerConnectionURL: tt.fields.LoadBalancerConnectionURL, + PrivateEndpoint: tt.fields.PrivateEndpoint, + PublicEndpoint: tt.fields.PublicEndpoint, + IngestNodesLoadBalancerConnectionURL: tt.fields.IngestNodesLoadBalancerConnectionURL, + } + if got := oss.Equals(&tt.args); got != tt.want { + t.Errorf("Equals() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/apis/clusters/v1beta1/opensearch_webhook.go b/apis/clusters/v1beta1/opensearch_webhook.go index d2992aa8a..2f284d301 100644 --- a/apis/clusters/v1beta1/opensearch_webhook.go +++ b/apis/clusters/v1beta1/opensearch_webhook.go @@ -121,7 +121,7 @@ func (osv *openSearchValidator) ValidateCreate(ctx context.Context, obj runtime. return err } - err = validateReplicationFactor(models.OpenSearchReplicationFactors, dc.ReplicationFactor) + err = validateReplicationFactor(models.OpenSearchReplicationFactors, dc.NumberOfRacks) if err != nil { return err } @@ -239,7 +239,7 @@ func (osv *openSearchValidator) ValidateUpdate(ctx context.Context, old runtime. } // ensuring if the cluster is ready for the spec updating - if (os.Status.CurrentClusterOperationStatus != models.NoOperation || os.Status.State != models.RunningStatus) && os.Generation != oldCluster.Generation { + if (os.Status.CurrentClusterOperationStatus != models.NoOperation || os.Status.Status != models.RunningStatus) && os.Generation != oldCluster.Generation { return models.ErrClusterIsNotReadyToUpdate } @@ -318,7 +318,7 @@ func (oss *OpenSearchDataCentre) newImmutableFields() *immutableOpenSearchDCFiel }, specificOpenSearchDC{ PrivateLink: oss.PrivateLink, - ReplicationFactor: oss.ReplicationFactor, + ReplicationFactor: oss.NumberOfRacks, }, } } @@ -402,8 +402,8 @@ func (oss *OpenSearchSpec) validateImmutableDataCentresUpdate(oldDCs []*OpenSear func (dc *OpenSearchDataCentre) validateDataNode(nodes []*OpenSearchDataNodes) error { for _, node := range nodes { - if node.NodesNumber%dc.ReplicationFactor != 0 { - return fmt.Errorf("number of data nodes must be a multiple of replication factor: %v", dc.ReplicationFactor) + if node.NodesNumber%dc.NumberOfRacks != 0 { + return fmt.Errorf("number of data nodes must be a multiple of replication factor: %v", dc.NumberOfRacks) } } diff --git a/apis/clusters/v1beta1/structs.go b/apis/clusters/v1beta1/structs.go index 64f59407b..013a7fcc7 100644 --- a/apis/clusters/v1beta1/structs.go +++ b/apis/clusters/v1beta1/structs.go @@ -65,16 +65,6 @@ type RestoreCustomVPCSettings struct { Network string `json:"network"` } -type Node struct { - ID string `json:"id,omitempty"` - Size string `json:"size,omitempty"` - PublicAddress string `json:"publicAddress,omitempty"` - PrivateAddress string `json:"privateAddress,omitempty"` - Status string `json:"status,omitempty"` - Roles []string `json:"roles,omitempty"` - Rack string `json:"rack,omitempty"` -} - type Options struct { DataNodeSize string `json:"dataNodeSize,omitempty"` MasterNodeSize string `json:"masterNodeSize,omitempty"` @@ -103,6 +93,14 @@ type Cluster struct { Description string `json:"description,omitempty"` } +func (c *Cluster) FromInstAPI(model *models.GenericClusterFields) { + c.Name = model.Name + c.PCICompliance = model.PCIComplianceMode + c.PrivateNetworkCluster = model.PrivateNetworkCluster + c.SLATier = model.SLATier + c.Description = model.Description +} + type ClusterStatus struct { ID string `json:"id,omitempty"` State string `json:"state,omitempty"` diff --git a/apis/clusters/v1beta1/zz_generated.deepcopy.go b/apis/clusters/v1beta1/zz_generated.deepcopy.go index 466b2b8db..61f8974e9 100644 --- a/apis/clusters/v1beta1/zz_generated.deepcopy.go +++ b/apis/clusters/v1beta1/zz_generated.deepcopy.go @@ -893,6 +893,58 @@ func (in *GCPConnectorSettings) DeepCopy() *GCPConnectorSettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericDataCentreStatus) DeepCopyInto(out *GenericDataCentreStatus) { + *out = *in + 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 GenericDataCentreStatus. +func (in *GenericDataCentreStatus) DeepCopy() *GenericDataCentreStatus { + if in == nil { + return nil + } + out := new(GenericDataCentreStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericStatus) DeepCopyInto(out *GenericStatus) { + *out = *in + if in.MaintenanceEvents != nil { + in, out := &in.MaintenanceEvents, &out.MaintenanceEvents + *out = make([]*clusterresourcesv1beta1.ClusteredMaintenanceEventStatus, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(clusterresourcesv1beta1.ClusteredMaintenanceEventStatus) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericStatus. +func (in *GenericStatus) DeepCopy() *GenericStatus { + if in == nil { + return nil + } + out := new(GenericStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InterDataCentreReplication) DeepCopyInto(out *InterDataCentreReplication) { *out = *in @@ -1481,6 +1533,29 @@ func (in *OpenSearchDataCentre) DeepCopy() *OpenSearchDataCentre { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenSearchDataCentreStatus) DeepCopyInto(out *OpenSearchDataCentreStatus) { + *out = *in + in.GenericDataCentreStatus.DeepCopyInto(&out.GenericDataCentreStatus) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]OpenSearchNodeStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSearchDataCentreStatus. +func (in *OpenSearchDataCentreStatus) DeepCopy() *OpenSearchDataCentreStatus { + if in == nil { + return nil + } + out := new(OpenSearchDataCentreStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenSearchDataNodes) DeepCopyInto(out *OpenSearchDataNodes) { *out = *in @@ -1543,6 +1618,22 @@ func (in *OpenSearchList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenSearchNodeStatus) DeepCopyInto(out *OpenSearchNodeStatus) { + *out = *in + in.Node.DeepCopyInto(&out.Node) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSearchNodeStatus. +func (in *OpenSearchNodeStatus) DeepCopy() *OpenSearchNodeStatus { + if in == nil { + return nil + } + out := new(OpenSearchNodeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenSearchRestoreFrom) DeepCopyInto(out *OpenSearchRestoreFrom) { *out = *in @@ -1670,7 +1761,14 @@ func (in *OpenSearchSpec) DeepCopy() *OpenSearchSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenSearchStatus) DeepCopyInto(out *OpenSearchStatus) { *out = *in - in.ClusterStatus.DeepCopyInto(&out.ClusterStatus) + in.GenericStatus.DeepCopyInto(&out.GenericStatus) + if in.DataCentres != nil { + in, out := &in.DataCentres, &out.DataCentres + *out = make([]OpenSearchDataCentreStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.AvailableUsers != nil { in, out := &in.AvailableUsers, &out.AvailableUsers *out = make(References, len(*in)) diff --git a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml index 7ba8cc888..608cad626 100644 --- a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml +++ b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml @@ -26,7 +26,7 @@ spec: name: ID type: string - jsonPath: .status.state - name: State + name: Status type: string name: v1beta1 schema: @@ -90,14 +90,12 @@ spec: type: string network: type: string + numberOfRacks: + type: integer privateLink: type: boolean region: type: string - replicationFactor: - description: ReplicationFactor is a number of racks to use when - allocating data nodes. - type: integer tags: additionalProperties: type: string @@ -106,7 +104,6 @@ spec: - cloudProvider - network - region - - replicationFactor type: object type: array dataNodes: @@ -280,15 +277,11 @@ spec: - namespace type: object type: array - cdcid: - type: string currentClusterOperationStatus: type: string dataCentres: items: properties: - encryptionKeyId: - type: string id: type: string name: @@ -300,8 +293,14 @@ spec: type: string privateAddress: type: string + privateEndpoint: + type: string + privateLinkConnectionUrl: + type: string publicAddress: type: string + publicEndpoint: + type: string rack: type: string roles: @@ -314,21 +313,10 @@ spec: type: string type: object type: array - nodesNumber: - type: integer - privateLink: - items: - properties: - advertisedHostname: - type: string - endPointServiceId: - type: string - endPointServiceName: - type: string - required: - - advertisedHostname - type: object - type: array + privateLinkEndpointServiceName: + type: string + privateLinkRestApiUrl: + type: string resizeOperations: items: properties: @@ -389,6 +377,10 @@ spec: type: array id: type: string + ingestNodesLoadBalancerConnectionURL: + type: string + loadBalancerConnectionURL: + type: string maintenanceEvents: items: properties: @@ -475,19 +467,12 @@ spec: type: array type: object type: array - options: - properties: - dataNodeSize: - type: string - masterNodeSize: - type: string - openSearchDashboardsNodeSize: - type: string - type: object - state: + privateEndpoint: + type: string + publicEndpoint: + type: string + status: type: string - twoFactorDeleteEnabled: - type: boolean type: object type: object served: true diff --git a/controllers/clusters/cadence_controller.go b/controllers/clusters/cadence_controller.go index 2d7778551..33b5c2965 100644 --- a/controllers/clusters/cadence_controller.go +++ b/controllers/clusters/cadence_controller.go @@ -489,6 +489,7 @@ func (r *CadenceReconciler) handleExternalChanges(c, iCadence *v1beta1.Cadence, func (r *CadenceReconciler) handleDeleteCluster( ctx context.Context, c *v1beta1.Cadence, + l logr.Logger, ) (ctrl.Result, error) { _, err := r.API.GetCadence(c.Status.ID) @@ -1217,7 +1218,7 @@ func (r *CadenceReconciler) newOpenSearchSpec(c *v1beta1.Cadence, oldestOpenSear CloudProvider: cloudProvider, ProviderAccountName: providerAccountName, Network: osNetwork, - ReplicationFactor: osReplicationFactor, + NumberOfRacks: osReplicationFactor, }, } spec := v1beta1.OpenSearchSpec{ diff --git a/controllers/clusters/datatest/opensearch_v1beta1.yaml b/controllers/clusters/datatest/opensearch_v1beta1.yaml index 2d861038d..6d40fbf88 100644 --- a/controllers/clusters/datatest/opensearch_v1beta1.yaml +++ b/controllers/clusters/datatest/opensearch_v1beta1.yaml @@ -19,7 +19,7 @@ spec: - cloudProvider: AWS_VPC name: AWS_VPC_US_EAST_1 network: 10.0.0.0/16 - replicationFactor: 3 + numberOfRacks: 3 privateLink: false region: US_EAST_1 # dataNodes: diff --git a/controllers/clusters/helpers.go b/controllers/clusters/helpers.go index 94c7a2b30..0c47a4f11 100644 --- a/controllers/clusters/helpers.go +++ b/controllers/clusters/helpers.go @@ -55,6 +55,7 @@ func convertAPIv2ConfigToMap(instConfigs []*models.ConfigurationProperties) map[ } return newConfigs } + func areStatusesEqual(a, b *v1beta1.ClusterStatus) bool { if a == nil && b == nil { return true diff --git a/controllers/clusters/opensearch_controller.go b/controllers/clusters/opensearch_controller.go index a4000377d..05fa65408 100644 --- a/controllers/clusters/opensearch_controller.go +++ b/controllers/clusters/opensearch_controller.go @@ -173,7 +173,7 @@ func (r *OpenSearchReconciler) HandleCreateCluster( err = r.Status().Patch(ctx, o, patch) if err != nil { logger.Error(err, "Cannot patch OpenSearch cluster status", - "original cluster ID", o.Spec.RestoreFrom.ClusterID, + //"original cluster ID", o.Spec.RestoreFrom.ClusterID, "cluster ID", id) r.EventRecorder.Eventf(o, models.Warning, models.PatchFailed, @@ -202,7 +202,7 @@ func (r *OpenSearchReconciler) HandleCreateCluster( ) } - if o.Status.State != models.DeletedStatus { + if o.Status.Status != models.DeletedStatus { err = r.startClusterStatusJob(o) if err != nil { logger.Error(err, "Cannot start OpenSearch cluster status job", @@ -256,7 +256,7 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( ) (reconcile.Result, error) { logger = logger.WithName("OpenSearch update event") - iData, err := r.API.GetOpenSearch(o.Status.ID) + opensearchModel, err := r.API.GetOpenSearch(o.Status.ID) if err != nil { logger.Error(err, "Cannot get OpenSearch cluster from the Instaclustr API", "cluster name", o.Spec.Name, @@ -269,20 +269,10 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( return reconcile.Result{}, err } - iOpenSearch, err := o.FromInstAPI(iData) - if err != nil { - logger.Error(err, "Cannot get OpenSearch cluster from the Instaclustr API", - "cluster name", o.Spec.Name, - "cluster ID", o.Status.ID, - ) - - r.EventRecorder.Eventf(o, models.Warning, models.ConversionFailed, - "Cluster convertion from the Instaclustr API to k8s resource is failed. Reason: %v", err) - - return reconcile.Result{}, err - } + iOpenSearch := &v1beta1.OpenSearch{} + iOpenSearch.FromInstAPI(opensearchModel) - if iOpenSearch.Status.State != models.RunningStatus { + if iOpenSearch.Status.Status != models.RunningStatus { logger.Info("OpenSearch cluster is not ready to update", "cluster Name", o.Spec.Name, "reason", instaclustr.ClusterNotRunning, @@ -338,7 +328,7 @@ func (r *OpenSearchReconciler) HandleUpdateCluster( logger.Error(err, "Cannot update cluster", "cluster ID", o.Status.ID, "cluster spec", o.Spec, - "cluster state", o.Status.State) + "cluster state", o.Status.Status) r.EventRecorder.Eventf(o, models.Warning, models.UpdateFailed, "Cluster update on the Instaclustr API is failed. Reason: %v", err) @@ -451,7 +441,7 @@ func (r *OpenSearchReconciler) HandleDeleteCluster( if err != nil && !errors.Is(err, instaclustr.NotFound) { logger.Error(err, "Cannot get OpenSearch cluster", "cluster name", o.Spec.Name, - "cluster status", o.Status.State) + "cluster status", o.Status.Status) r.EventRecorder.Eventf(o, models.Warning, models.FetchFailed, "Cluster resource fetch from the Instaclustr API is failed. Reason: %v", err) @@ -470,7 +460,7 @@ func (r *OpenSearchReconciler) HandleDeleteCluster( if err != nil { logger.Error(err, "Cannot delete OpenSearch cluster", "cluster name", o.Spec.Name, - "cluster status", o.Status.State) + "cluster status", o.Status.Status) r.EventRecorder.Eventf(o, models.Warning, models.DeletionFailed, "Cluster deletion is failed on the Instaclustr. Reason: %v", err) @@ -488,7 +478,7 @@ func (r *OpenSearchReconciler) HandleDeleteCluster( if err != nil { logger.Error(err, "Cannot patch cluster resource", "cluster name", o.Spec.Name, - "cluster state", o.Status.State) + "cluster state", o.Status.Status) r.EventRecorder.Eventf(o, models.Warning, models.PatchFailed, "Cluster resource patch is failed. Reason: %v", err) @@ -607,7 +597,7 @@ func (r *OpenSearchReconciler) startUsersCreationJob(cluster *v1beta1.OpenSearch } func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) scheduler.Job { - l := log.Log.WithValues("component", "openSearchStatusClusterJob") + l := log.Log.WithValues("openSearchStatusJob", o.GetJobID(scheduler.StatusChecker)) return func() error { namespacedName := client.ObjectKeyFromObject(o) err := r.Get(context.Background(), namespacedName, o) @@ -626,7 +616,7 @@ func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) schedule return err } - iData, err := r.API.GetOpenSearch(o.Status.ID) + opensearchModel, err := r.API.GetOpenSearch(o.Status.ID) if err != nil { if errors.Is(err, instaclustr.NotFound) { if o.DeletionTimestamp != nil { @@ -643,39 +633,34 @@ func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) schedule return err } - iO, err := o.FromInstAPI(iData) - if err != nil { - l.Error(err, "Cannot convert OpenSearch cluster from the Instaclustr API", - "cluster ID", o.Status.ID) + iO := &v1beta1.OpenSearch{} + iO.FromInstAPI(opensearchModel) - return err - } - - if !areStatusesEqual(&iO.Status.ClusterStatus, &o.Status.ClusterStatus) { - l.Info("Updating OpenSearch cluster status", - "new status", iO.Status.ClusterStatus, - "old status", o.Status.ClusterStatus, - ) - - areDCsEqual := areDataCentresEqual(iO.Status.ClusterStatus.DataCentres, o.Status.ClusterStatus.DataCentres) + if !iO.Status.Equals(&o.Status) { + l.Info("Updating OpenSearch cluster status", "old", o.Status, "new", iO.Status) patch := o.NewPatch() - o.Status.ClusterStatus = iO.Status.ClusterStatus + o.Status.FromInstAPI(opensearchModel) err = r.Status().Patch(context.Background(), o, patch) if err != nil { l.Error(err, "Cannot patch OpenSearch cluster", "cluster name", o.Spec.Name, - "status", o.Status.State, + "status", o.Status.Status, ) return err } - if !areDCsEqual { + dcEquals := o.Status.DataCentreEquals(&iO.Status) + + if !dcEquals { var nodes []*v1beta1.Node - for _, dc := range iO.Status.ClusterStatus.DataCentres { - nodes = append(nodes, dc.Nodes...) + for _, dc := range iO.Status.DataCentres { + for _, node := range dc.Nodes { + n := node.Node + nodes = append(nodes, &n) + } } err = exposeservice.Create(r.Client, @@ -720,7 +705,7 @@ func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) schedule err = r.Patch(context.Background(), o, patch) if err != nil { l.Error(err, "Cannot patch cluster cluster", - "cluster name", o.Spec.Name, "cluster state", o.Status.State) + "cluster name", o.Spec.Name, "cluster state", o.Status.Status) return err } @@ -744,7 +729,7 @@ func (r *OpenSearchReconciler) newWatchStatusJob(o *v1beta1.OpenSearch) schedule return err } - if o.Status.State == models.RunningStatus && o.Status.CurrentClusterOperationStatus == models.OperationInProgress { + if o.Status.Status == models.RunningStatus && o.Status.CurrentClusterOperationStatus == models.OperationInProgress { patch := o.NewPatch() for _, dc := range o.Status.DataCentres { resizeOperations, err := r.API.GetResizeOperationsByClusterDataCentreID(dc.ID) @@ -900,7 +885,7 @@ func (r *OpenSearchReconciler) newUsersCreationJob(o *v1beta1.OpenSearch) schedu return err } - if o.Status.State != models.RunningStatus { + if o.Status.Status != models.RunningStatus { logger.Info("User creation job is scheduled") r.EventRecorder.Eventf(o, models.Normal, models.CreationFailed, "User creation job is scheduled, cluster is not in the running state", @@ -1032,7 +1017,7 @@ func (r *OpenSearchReconciler) reconcileMaintenanceEvents(ctx context.Context, o return err } - if !o.Status.AreMaintenanceEventStatusesEqual(iMEStatuses) { + if !o.Status.MaintenanceEventsEqual(iMEStatuses) { patch := o.NewPatch() o.Status.MaintenanceEvents = iMEStatuses err = r.Status().Patch(ctx, o, patch) @@ -1053,7 +1038,7 @@ func (r *OpenSearchReconciler) handleExternalDelete(ctx context.Context, o *v1be l := log.FromContext(ctx) patch := o.NewPatch() - o.Status.State = models.DeletedStatus + o.Status.Status = models.DeletedStatus err := r.Status().Patch(ctx, o, patch) if err != nil { return err diff --git a/controllers/clusters/opensearch_controller_test.go b/controllers/clusters/opensearch_controller_test.go index 761e6e3ca..091916c24 100644 --- a/controllers/clusters/opensearch_controller_test.go +++ b/controllers/clusters/opensearch_controller_test.go @@ -126,7 +126,7 @@ var _ = Describe("OpenSearch Controller", func() { return false } - if openSearch2.Status.State != models.RunningStatus { + if openSearch2.Status.Status != models.RunningStatus { return false } @@ -146,7 +146,7 @@ var _ = Describe("OpenSearch Controller", func() { return false } - return openSearch2.Status.State == models.DeletedStatus + return openSearch2.Status.Status == models.DeletedStatus }, timeout, interval).Should(BeTrue()) }) }) diff --git a/pkg/instaclustr/client.go b/pkg/instaclustr/client.go index 80a0ba746..4dbe3ad25 100644 --- a/pkg/instaclustr/client.go +++ b/pkg/instaclustr/client.go @@ -106,7 +106,7 @@ func (c *Client) CreateCluster(url string, cluster any) (string, error) { return creationResponse.ID, nil } -func (c *Client) GetOpenSearch(id string) ([]byte, error) { +func (c *Client) GetOpenSearch(id string) (*models.OpenSearchCluster, error) { url := c.serverHostname + OpenSearchEndpoint + id resp, err := c.DoRequest(url, http.MethodGet, nil) @@ -124,7 +124,13 @@ func (c *Client) GetOpenSearch(id string) ([]byte, error) { return nil, NotFound } - return body, nil + var model models.OpenSearchCluster + err = json.Unmarshal(body, &model) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal to model, err: %w", err) + } + + return &model, nil } func (c *Client) UpdateOpenSearch(id string, o models.OpenSearchInstAPIUpdateRequest) error { @@ -1533,6 +1539,10 @@ func (c *Client) FetchMaintenanceEventStatuses( return nil, err } + if len(inProgressMEStatus) == 0 && len(pastMEStatus) == 0 && len(upcomingMEStatus) == 0 { + return nil, nil + } + iMEStatuses := []*clusterresourcesv1beta1.ClusteredMaintenanceEventStatus{ { InProgress: inProgressMEStatus, diff --git a/pkg/instaclustr/interfaces.go b/pkg/instaclustr/interfaces.go index 0f63517fe..2c3e65edc 100644 --- a/pkg/instaclustr/interfaces.go +++ b/pkg/instaclustr/interfaces.go @@ -28,7 +28,7 @@ import ( type API interface { DoRequest(url string, method string, data []byte) (*http.Response, error) CreateCluster(url string, clusterSpec any) (string, error) - GetOpenSearch(id string) ([]byte, error) + GetOpenSearch(id string) (*models.OpenSearchCluster, error) UpdateOpenSearch(id string, o models.OpenSearchInstAPIUpdateRequest) error UpdateDescriptionAndTwoFactorDelete(clusterEndpoint, clusterID, description string, twoFactorDelete *v1beta1.TwoFactorDelete) error UpdateCluster(id, clusterEndpoint string, instaDCs any) error diff --git a/pkg/instaclustr/mock/client.go b/pkg/instaclustr/mock/client.go index ac4ff867b..469c0e609 100644 --- a/pkg/instaclustr/mock/client.go +++ b/pkg/instaclustr/mock/client.go @@ -47,7 +47,7 @@ func (c *mockClient) DoRequest(url string, method string, data []byte) (*http.Re panic("DoRequest: is not implemented") } -func (c *mockClient) GetOpenSearch(id string) ([]byte, error) { +func (c *mockClient) GetOpenSearch(id string) (*models.OpenSearchCluster, error) { panic("GetOpenSearch: is not implemented") } diff --git a/pkg/instaclustr/mock/server/go/api_open_search_provisioning_v2.go b/pkg/instaclustr/mock/server/go/api_open_search_provisioning_v2.go index 1585c1ce9..db24c23b7 100644 --- a/pkg/instaclustr/mock/server/go/api_open_search_provisioning_v2.go +++ b/pkg/instaclustr/mock/server/go/api_open_search_provisioning_v2.go @@ -204,7 +204,7 @@ func (c *OpenSearchProvisioningV2APIController) ClusterManagementV2ResourcesAppl func (c *OpenSearchProvisioningV2APIController) ClusterManagementV2ResourcesApplicationsOpensearchClustersV2Post(w http.ResponseWriter, r *http.Request) { openSearchClusterV2Param := OpenSearchClusterV2{} d := json.NewDecoder(r.Body) - d.DisallowUnknownFields() + //d.DisallowUnknownFields() if err := d.Decode(&openSearchClusterV2Param); err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return diff --git a/pkg/models/apiv2_generic.go b/pkg/models/apiv2_generic.go new file mode 100644 index 000000000..c22b9909c --- /dev/null +++ b/pkg/models/apiv2_generic.go @@ -0,0 +1,30 @@ +package models + +type GenericClusterFields struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + CurrentClusterOperationStatus string `json:"currentClusterOperationStatus,omitempty"` + + Name string `json:"name"` + Description string `json:"description,omitempty"` + PCIComplianceMode bool `json:"pciComplianceMode"` + PrivateNetworkCluster bool `json:"privateNetworkCluster"` + SLATier string `json:"slaTier,omitempty"` + ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` + TwoFactorDelete []*TwoFactorDelete `json:"twoFactorDelete,omitempty"` +} + +type GenericDataCentreFields struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + + Name string `json:"name"` + Network string `json:"network"` + CloudProvider string `json:"cloudProvider"` + Region string `json:"region"` + ProviderAccountName string `json:"providerAccountName,omitempty"` + AWSSettings []*AWSSetting `json:"awsSettings,omitempty"` + GCPSettings []*GCPSetting `json:"gcpSettings,omitempty"` + AzureSettings []*AzureSetting `json:"azureSettings,omitempty"` + Tags []*Tag `json:"tags,omitempty"` +} diff --git a/pkg/models/opensearch_apiv2.go b/pkg/models/opensearch_apiv2.go index cd5806cf7..a2a3e802d 100644 --- a/pkg/models/opensearch_apiv2.go +++ b/pkg/models/opensearch_apiv2.go @@ -17,31 +17,30 @@ limitations under the License. package models type OpenSearchCluster struct { - ClusterStatus `json:",inline"` - DataNodes []*OpenSearchDataNodes `json:"dataNodes,omitempty"` - PCIComplianceMode bool `json:"pciComplianceMode"` - ICUPlugin bool `json:"icuPlugin"` + GenericClusterFields `json:",inline"` + OpenSearchVersion string `json:"opensearchVersion"` + ICUPlugin bool `json:"icuPlugin"` AsynchronousSearchPlugin bool `json:"asynchronousSearchPlugin"` - TwoFactorDelete []*TwoFactorDelete `json:"twoFactorDelete,omitempty"` KNNPlugin bool `json:"knnPlugin"` - OpenSearchDashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` ReportingPlugin bool `json:"reportingPlugin"` SQLPlugin bool `json:"sqlPlugin"` NotificationsPlugin bool `json:"notificationsPlugin"` - DataCentres []*OpenSearchDataCentre `json:"dataCentres"` AnomalyDetectionPlugin bool `json:"anomalyDetectionPlugin"` LoadBalancer bool `json:"loadBalancer"` - PrivateNetworkCluster bool `json:"privateNetworkCluster"` - Name string `json:"name"` BundledUseOnly bool `json:"bundledUseOnly"` - ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes"` IndexManagementPlugin bool `json:"indexManagementPlugin"` - SLATier string `json:"slaTier,omitempty"` AlertingPlugin bool `json:"alertingPlugin"` - ResizeSettings []*ResizeSettings `json:"resizeSettings,omitempty"` - Description string `json:"description,omitempty"` + DataCentres []*OpenSearchDataCentre `json:"dataCentres"` + DataNodes []*OpenSearchDataNodes `json:"dataNodes,omitempty"` + OpenSearchDashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` + ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes"` IngestNodes []*OpenSearchIngestNodes `json:"ingestNodes,omitempty"` + + LoadBalancerConnectionURL string `json:"loadBalancerConnectionURL,omitempty"` + PrivateEndpoint string `json:"privateEndpoint,omitempty"` + PublicEndpoint string `json:"publicEndpoint,omitempty"` + IngestNodesLoadBalancerConnectionURL string `json:"ingestNodesLoadBalancerConnectionURL,omitempty"` } type OpenSearchIngestNodes struct { @@ -61,9 +60,22 @@ type OpenSearchDashboards struct { } type OpenSearchDataCentre struct { - DataCentre `json:",inline"` + GenericDataCentreFields `json:",inline"` + PrivateLink bool `json:"privateLink"` NumberOfRacks int `json:"numberOfRacks"` + + PrivateLinkEndpointServiceName string `json:"privateLinkEndpointServiceName,omitempty"` + PrivateLinkRestAPIURL string `json:"privateLinkRestApiUrl,omitempty"` + Nodes []OpenSearchNode `json:"nodes,omitempty"` +} + +type OpenSearchNode struct { + Node `json:",inline"` + + PublicEndpoint string `json:"publicEndpoint,omitempty"` + PrivateEndpoint string `json:"privateEndpoint,omitempty"` + PrivateLinkConnectionURL string `json:"privateLinkConnectionUrl,omitempty"` } type ClusterManagerNodes struct {