diff --git a/api/v1/atlasdeployment_types.go b/api/v1/atlasdeployment_types.go index 2e43c26992..340ce79a73 100644 --- a/api/v1/atlasdeployment_types.go +++ b/api/v1/atlasdeployment_types.go @@ -43,6 +43,7 @@ const ( // Only one of DeploymentSpec, AdvancedDeploymentSpec and ServerlessSpec should be defined. // +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef))",message="must define only one project reference through externalProjectRef or projectRef" // +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef)",message="must define a local connection secret when referencing an external project" +// +kubebuilder:validation:XValidation:rule="!has(self.serverlessSpec) || (oldSelf.hasValue() && oldSelf.value().serverlessSpec != null)",optionalOldSelf=true,message="serverlessSpec cannot be added - serverless instances are deprecated",fieldPath=.serverlessSpec type AtlasDeploymentSpec struct { // ProjectReference is the dual external or kubernetes reference with access credentials ProjectDualReference `json:",inline"` diff --git a/api/v1/atlasdeployment_types_test.go b/api/v1/atlasdeployment_types_test.go index af2576974e..0e942e5f02 100644 --- a/api/v1/atlasdeployment_types_test.go +++ b/api/v1/atlasdeployment_types_test.go @@ -49,6 +49,74 @@ func TestDeploymentCELChecks(t *testing.T) { }, expectedErrors: []string{"spec.deploymentSpec.name: Invalid value: \"string\": Name cannot be modified after deployment creation"}, }, + { + title: "Cannot add a serverless deployment", + old: nil, + obj: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + }, + }, + }, + expectedErrors: []string{"spec.serverlessSpec: Invalid value: \"object\": serverlessSpec cannot be added - serverless instances are deprecated"}, + }, + { + title: "Can modify to a serverless deployment", + old: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + TerminationProtectionEnabled: false, + }, + }, + }, + obj: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + TerminationProtectionEnabled: true, + }, + }, + }, + }, + { + title: "Existing serverless deployment can continue existing when not modified", + old: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + TerminationProtectionEnabled: false, + }, + }, + }, + obj: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + BackupScheduleRef: common.ResourceRefNamespaced{}, + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + TerminationProtectionEnabled: false, + }, + }, + }, + }, + { + title: "can migrate from serverless to flex cluster", + old: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + ServerlessSpec: &ServerlessSpec{ + Name: "my-serverless", + }, + }, + }, + obj: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + FlexSpec: &FlexSpec{ + Name: "my-serverless", + }, + }, + }, + }, } { t.Run(tc.title, func(t *testing.T) { // inject a project to avoid other CEL validations being hit diff --git a/devbox.json b/devbox.json index f649f62389..cb21bd5ed6 100644 --- a/devbox.json +++ b/devbox.json @@ -19,7 +19,7 @@ "operator-sdk": "1.36.1", "shellcheck": "latest", "golangci-lint": "2", - "kubernetes-controller-tools": "0.17.2", + "kubernetes-controller-tools": "0.19.0", "setup-envtest": "0.19.0", "awscli2": "latest", "docker-sbom": "latest", diff --git a/devbox.lock b/devbox.lock index 60f21439b8..237ee9a072 100644 --- a/devbox.lock +++ b/devbox.lock @@ -1189,51 +1189,51 @@ } } }, - "kubernetes-controller-tools@0.17.2": { - "last_modified": "2025-03-23T05:31:05Z", - "resolved": "github:NixOS/nixpkgs/dd613136ee91f67e5dba3f3f41ac99ae89c5406b#kubernetes-controller-tools", + "kubernetes-controller-tools@0.19.0": { + "last_modified": "2025-11-23T21:50:36Z", + "resolved": "github:NixOS/nixpkgs/ee09932cedcef15aaf476f9343d1dea2cb77e261#kubernetes-controller-tools", "source": "devbox-search", - "version": "0.17.2", + "version": "0.19.0", "systems": { "aarch64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/x2g121jdk7fdxb7vx964rrhbiwd0g0rm-controller-tools-0.17.2", + "path": "/nix/store/brz84pzrcnll7i85bpgil58ldlkwrngp-controller-tools-0.19.0", "default": true } ], - "store_path": "/nix/store/x2g121jdk7fdxb7vx964rrhbiwd0g0rm-controller-tools-0.17.2" + "store_path": "/nix/store/brz84pzrcnll7i85bpgil58ldlkwrngp-controller-tools-0.19.0" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/jaz48mw3pinfdw9gd8fng63gn7wwzmxy-controller-tools-0.17.2", + "path": "/nix/store/anb3mpv9m3dklylhqbsjh04iv70gn8vj-controller-tools-0.19.0", "default": true } ], - "store_path": "/nix/store/jaz48mw3pinfdw9gd8fng63gn7wwzmxy-controller-tools-0.17.2" + "store_path": "/nix/store/anb3mpv9m3dklylhqbsjh04iv70gn8vj-controller-tools-0.19.0" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/3wdf34xlpff7m01ggzr7pb9f3w3cl95f-controller-tools-0.17.2", + "path": "/nix/store/jjm90vri9j4j2zacasrfkhny98vsh10n-controller-tools-0.19.0", "default": true } ], - "store_path": "/nix/store/3wdf34xlpff7m01ggzr7pb9f3w3cl95f-controller-tools-0.17.2" + "store_path": "/nix/store/jjm90vri9j4j2zacasrfkhny98vsh10n-controller-tools-0.19.0" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/xgppkdij5f73igwfp5as058fmh21wn9p-controller-tools-0.17.2", + "path": "/nix/store/mldi6bp7a0bdq5vjyfahay8h19f0wwch-controller-tools-0.19.0", "default": true } ], - "store_path": "/nix/store/xgppkdij5f73igwfp5as058fmh21wn9p-controller-tools-0.17.2" + "store_path": "/nix/store/mldi6bp7a0bdq5vjyfahay8h19f0wwch-controller-tools-0.19.0" } } }, diff --git a/docs/api-docs.md b/docs/api-docs.md index 12d6b53d22..09a1160935 100644 --- a/docs/api-docs.md +++ b/docs/api-docs.md @@ -2755,7 +2755,7 @@ AtlasDeployment is the Schema for the atlasdeployments API AtlasDeploymentSpec defines the desired state of AtlasDeployment. Only one of DeploymentSpec, AdvancedDeploymentSpec and ServerlessSpec should be defined.

- Validations:
  • (has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef)): must define only one project reference through externalProjectRef or projectRef
  • (has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef): must define a local connection secret when referencing an external project
  • + Validations:
  • (has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef)): must define only one project reference through externalProjectRef or projectRef
  • (has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef): must define a local connection secret when referencing an external project
  • !has(self.serverlessSpec) || (oldSelf.hasValue() && oldSelf.value().serverlessSpec != null): serverlessSpec cannot be added - serverless instances are deprecated
  • false diff --git a/internal/controller/atlasdatabaseuser/databaseuser.go b/internal/controller/atlasdatabaseuser/databaseuser.go index 430d967ea1..7a9c22226a 100644 --- a/internal/controller/atlasdatabaseuser/databaseuser.go +++ b/internal/controller/atlasdatabaseuser/databaseuser.go @@ -66,7 +66,7 @@ func (r *AtlasDatabaseUserReconciler) handleDatabaseUser(ctx *workflow.Context, return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err) } dbUserService := dbuser.NewAtlasUsers(sdkClientSet.SdkClient20250312006.DatabaseUsersApi) - deploymentService := deployment.NewAtlasDeployments(sdkClientSet.SdkClient20250312006.ClustersApi, sdkClientSet.SdkClient20250312006.ServerlessInstancesApi, sdkClientSet.SdkClient20250312006.GlobalClustersApi, sdkClientSet.SdkClient20250312006.FlexClustersApi, r.AtlasProvider.IsCloudGov()) + deploymentService := deployment.NewAtlasDeployments(sdkClientSet.SdkClient20250312006.ClustersApi, sdkClientSet.SdkClient20250312006.GlobalClustersApi, sdkClientSet.SdkClient20250312006.FlexClustersApi, r.AtlasProvider.IsCloudGov()) atlasProject, err := r.ResolveProject(ctx.Context, sdkClientSet.SdkClient20250312006, atlasDatabaseUser) if err != nil { return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err) diff --git a/internal/controller/atlasdeployment/atlasdeployment_controller.go b/internal/controller/atlasdeployment/atlasdeployment_controller.go index a81e7cb35b..f9e3976cf1 100644 --- a/internal/controller/atlasdeployment/atlasdeployment_controller.go +++ b/internal/controller/atlasdeployment/atlasdeployment_controller.go @@ -141,7 +141,7 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ } workflowCtx.SdkClientSet = sdkClientSet projectService := project.NewProjectAPIService(sdkClientSet.SdkClient20250312006.ProjectsApi) - deploymentService := deployment.NewAtlasDeployments(sdkClientSet.SdkClient20250312006.ClustersApi, sdkClientSet.SdkClient20250312006.ServerlessInstancesApi, sdkClientSet.SdkClient20250312006.GlobalClustersApi, sdkClientSet.SdkClient20250312006.FlexClustersApi, r.AtlasProvider.IsCloudGov()) + deploymentService := deployment.NewAtlasDeployments(sdkClientSet.SdkClient20250312006.ClustersApi, sdkClientSet.SdkClient20250312006.GlobalClustersApi, sdkClientSet.SdkClient20250312006.FlexClustersApi, r.AtlasProvider.IsCloudGov()) atlasProject, err := r.ResolveProject(workflowCtx.Context, sdkClientSet.SdkClient20250312006, atlasDeployment) if err != nil { return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) @@ -176,10 +176,7 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ } switch { - case atlasDeployment.IsServerless(): - return r.handleServerlessInstance(workflowCtx, projectService, deploymentService, deploymentInAKO, deploymentInAtlas) - - case atlasDeployment.IsFlex(): + case atlasDeployment.IsServerless(), atlasDeployment.IsFlex(): return r.handleFlexInstance(workflowCtx, projectService, deploymentService, deploymentInAKO, deploymentInAtlas) case atlasDeployment.IsAdvancedDeployment(): diff --git a/internal/controller/atlasdeployment/atlasdeployment_controller_test.go b/internal/controller/atlasdeployment/atlasdeployment_controller_test.go index de21d08973..fd577b5132 100644 --- a/internal/controller/atlasdeployment/atlasdeployment_controller_test.go +++ b/internal/controller/atlasdeployment/atlasdeployment_controller_test.go @@ -465,13 +465,6 @@ func TestRegularClusterReconciliation(t *testing.T) { flexAPI := mockadmin.NewFlexClustersApi(t) - serverlessErr := &admin.GenericOpenAPIError{} - serverlessErr.SetModel(admin.ApiError{ErrorCode: atlas.ClusterInstanceFromServerlessAPI}) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, project.ID(), mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, serverlessErr) - cloudBackupsAPI := mockadmin.NewCloudBackupsApi(t) cloudBackupsAPI.EXPECT().GetBackupSchedule(mock.Anything, project.ID(), d.Spec.DeploymentSpec.Name). Return(admin.GetBackupScheduleApiRequest{ApiService: cloudBackupsAPI}) @@ -502,13 +495,12 @@ func TestRegularClusterReconciliation(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ClustersApi: clusterAPI, - AtlasSearchApi: searchAPI, - ServerlessInstancesApi: serverlessAPI, - GlobalClustersApi: globalAPI, - ProjectsApi: projectAPI, - CloudBackupsApi: cloudBackupsAPI, + FlexClustersApi: flexAPI, + ClustersApi: clusterAPI, + AtlasSearchApi: searchAPI, + GlobalClustersApi: globalAPI, + ProjectsApi: projectAPI, + CloudBackupsApi: cloudBackupsAPI, }, }, nil }, @@ -594,38 +586,32 @@ func TestServerlessInstanceReconciliation(t *testing.T) { Build() atlasProvider := &atlasmock.TestProvider{ + //nolint:dupl SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { + clusterErr := &admin.GenericOpenAPIError{} + clusterErr.SetModel(admin.ApiError{ErrorCode: atlas.ServerlessInstanceFromClusterAPI}) clusterAPI := mockadmin.NewClustersApi(t) + clusterAPI.EXPECT().GetCluster(mock.Anything, "abc123", mock.Anything). + Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) + clusterAPI.EXPECT().GetClusterExecute(mock.Anything).Return(nil, nil, clusterErr) flexAPI := mockadmin.NewFlexClustersApi(t) - - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, project.ID(), mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return( - &admin.ServerlessInstanceDescription{ - GroupId: pointer.MakePtr(project.ID()), - Name: pointer.MakePtr(d.GetDeploymentName()), - ProviderSettings: admin.ServerlessProviderSettings{ - BackingProviderName: "AWS", - ProviderName: pointer.MakePtr("SERVERLESS"), - RegionName: "US_EAST_1", - }, - ServerlessBackupOptions: &admin.ClusterServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: pointer.MakePtr(false), - }, - StateName: pointer.MakePtr("IDLE"), - TerminationProtectionEnabled: pointer.MakePtr(false), + flexAPI.EXPECT().GetFlexCluster(mock.Anything, "abc123", mock.Anything). + Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().GetFlexClusterExecute(mock.Anything).Return( + &admin.FlexClusterDescription20241113{ + GroupId: pointer.MakePtr("abc123"), + Name: pointer.MakePtr("test-serverless-instance"), + ProviderSettings: admin.FlexProviderSettings20241113{ + BackingProviderName: pointer.MakePtr("AWS"), + ProviderName: pointer.MakePtr("FLEX"), + RegionName: pointer.MakePtr("US_EAST_1"), }, - &http.Response{}, - nil, - ) - - speClient := mockadmin.NewServerlessPrivateEndpointsApi(t) - speClient.EXPECT().ListServerlessPrivateEndpoints(mock.Anything, project.ID(), d.GetDeploymentName()). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speClient}) - speClient.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return(nil, &http.Response{}, nil) + StateName: pointer.MakePtr("IDLE"), + TerminationProtectionEnabled: pointer.MakePtr(false), + }, + nil, + nil, + ) projectAPI := mockadmin.NewProjectsApi(t) projectAPI.EXPECT().GetProjectByName(mock.Anything, "MyProject"). @@ -635,11 +621,9 @@ func TestServerlessInstanceReconciliation(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ClustersApi: clusterAPI, - ServerlessInstancesApi: serverlessAPI, - ServerlessPrivateEndpointsApi: speClient, - ProjectsApi: projectAPI, + FlexClustersApi: flexAPI, + ClustersApi: clusterAPI, + ProjectsApi: projectAPI, }, }, nil }, @@ -755,13 +739,6 @@ func TestFlexClusterReconciliation(t *testing.T) { clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, clusterErr) - serverlessErr := &admin.GenericOpenAPIError{} - serverlessErr.SetModel(admin.ApiError{ErrorCode: atlas.ClusterInstanceFromServerlessAPI}) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, project.ID(), mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, serverlessErr) - projectAPI := mockadmin.NewProjectsApi(t) projectAPI.EXPECT().GetProjectByName(mock.Anything, "MyProject"). Return(admin.GetProjectByNameApiRequest{ApiService: projectAPI}) @@ -770,10 +747,9 @@ func TestFlexClusterReconciliation(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ClustersApi: clusterAPI, - ServerlessInstancesApi: serverlessAPI, - ProjectsApi: projectAPI, + FlexClustersApi: flexAPI, + ClustersApi: clusterAPI, + ProjectsApi: projectAPI, }, }, nil }, @@ -903,13 +879,6 @@ func TestDeletionReconciliation(t *testing.T) { SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { flexAPI := mockadmin.NewFlexClustersApi(t) - serverlessErr := &admin.GenericOpenAPIError{} - serverlessErr.SetModel(admin.ApiError{ErrorCode: atlas.ClusterInstanceFromServerlessAPI}) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, project.ID(), mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, serverlessErr) - clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(mock.Anything, project.ID(), mock.Anything). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -954,10 +923,9 @@ func TestDeletionReconciliation(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ClustersApi: clusterAPI, - ServerlessInstancesApi: serverlessAPI, - ProjectsApi: projectAPI, + FlexClustersApi: flexAPI, + ClustersApi: clusterAPI, + ProjectsApi: projectAPI, }, }, nil }, @@ -1288,15 +1256,6 @@ func TestChangeDeploymentType(t *testing.T) { return true }, SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { - flexAPI := mockadmin.NewFlexClustersApi(t) - - serverlessErr := &admin.GenericOpenAPIError{} - serverlessErr.SetModel(admin.ApiError{ErrorCode: atlas.ClusterInstanceFromServerlessAPI}) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "abc123", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, serverlessErr) - clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(mock.Anything, "abc123", mock.Anything). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -1337,15 +1296,13 @@ func TestChangeDeploymentType(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ServerlessInstancesApi: serverlessAPI, - ClustersApi: clusterAPI, - ProjectsApi: projectAPI, + ClustersApi: clusterAPI, + ProjectsApi: projectAPI, }, }, nil }, }, - errorMsg: "deployment in Atlas is not a serverless cluster", + errorMsg: "deployment in Atlas is not a flex cluster", }, "should fail when existing cluster is regular but manifest defines a flex instance": { //nolint:dupl deployment: &akov2.AtlasDeployment{ @@ -1381,15 +1338,6 @@ func TestChangeDeploymentType(t *testing.T) { return true }, SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { - flexAPI := mockadmin.NewFlexClustersApi(t) - - serverlessErr := &admin.GenericOpenAPIError{} - serverlessErr.SetModel(admin.ApiError{ErrorCode: atlas.ClusterInstanceFromServerlessAPI}) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "abc123", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, serverlessErr) - clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(mock.Anything, "abc123", mock.Anything). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -1430,17 +1378,15 @@ func TestChangeDeploymentType(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - FlexClustersApi: flexAPI, - ServerlessInstancesApi: serverlessAPI, - ClustersApi: clusterAPI, - ProjectsApi: projectAPI, + ClustersApi: clusterAPI, + ProjectsApi: projectAPI, }, }, nil }, }, errorMsg: "deployment in Atlas is not a flex cluster", }, - "should fail when existing cluster is serverless instance but manifest defines a regular deployment": { + "should fail when existing cluster is flex instance but manifest defines a regular deployment": { deployment: &akov2.AtlasDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster0", @@ -1470,23 +1416,23 @@ func TestChangeDeploymentType(t *testing.T) { return true }, SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { + clusterErr := &admin.GenericOpenAPIError{} + clusterErr.SetModel(admin.ApiError{ErrorCode: atlas.FlexFromClusterAPI}) clusterAPI := mockadmin.NewClustersApi(t) + clusterAPI.EXPECT().GetCluster(mock.Anything, "abc123", mock.Anything). + Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) + clusterAPI.EXPECT().GetClusterExecute(mock.Anything).Return(nil, nil, clusterErr) flexAPI := mockadmin.NewFlexClustersApi(t) - - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "abc123", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return( - &admin.ServerlessInstanceDescription{ + flexAPI.EXPECT().GetFlexCluster(mock.Anything, "abc123", mock.Anything). + Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().GetFlexClusterExecute(mock.Anything).Return( + &admin.FlexClusterDescription20241113{ GroupId: pointer.MakePtr("abc123"), Name: pointer.MakePtr("cluster0"), - ProviderSettings: admin.ServerlessProviderSettings{ - BackingProviderName: "AWS", - ProviderName: pointer.MakePtr("SERVERLESS"), - RegionName: "US_EAST_1", - }, - ServerlessBackupOptions: &admin.ClusterServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: pointer.MakePtr(false), + ProviderSettings: admin.FlexProviderSettings20241113{ + BackingProviderName: pointer.MakePtr("AWS"), + ProviderName: pointer.MakePtr("FLEX"), + RegionName: pointer.MakePtr("US_EAST_1"), }, StateName: pointer.MakePtr("IDLE"), TerminationProtectionEnabled: pointer.MakePtr(false), @@ -1503,10 +1449,9 @@ func TestChangeDeploymentType(t *testing.T) { return &atlas.ClientSet{ SdkClient20250312006: &admin.APIClient{ - ClustersApi: clusterAPI, - FlexClustersApi: flexAPI, - ServerlessInstancesApi: serverlessAPI, - ProjectsApi: projectAPI, + ClustersApi: clusterAPI, + FlexClustersApi: flexAPI, + ProjectsApi: projectAPI, }, }, nil }, diff --git a/internal/controller/atlasdeployment/serverless_deployment.go b/internal/controller/atlasdeployment/serverless_deployment.go deleted file mode 100644 index 420e092bac..0000000000 --- a/internal/controller/atlasdeployment/serverless_deployment.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2025 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package atlasdeployment - -import ( - "errors" - "fmt" - "reflect" - - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/customresource" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" -) - -func (r *AtlasDeploymentReconciler) handleServerlessInstance(ctx *workflow.Context, projectService project.ProjectService, - deploymentService deployment.AtlasDeploymentsService, akoDeployment, atlasDeployment deployment.Deployment) (ctrl.Result, error) { - akoServerless, ok := akoDeployment.(*deployment.Serverless) - if !ok { - return r.terminate(ctx, workflow.Internal, errors.New("deployment in AKO is not a serverless cluster")) - } - - var atlasServerless *deployment.Serverless - if atlasServerless, ok = atlasDeployment.(*deployment.Serverless); atlasDeployment != nil && !ok { - return r.terminate(ctx, workflow.Internal, errors.New("deployment in Atlas is not a serverless cluster")) - } - - if atlasServerless == nil { - ctx.Log.Infof("Serverless Instance %s doesn't exist in Atlas - creating", akoServerless.GetName()) - newServerlessDeployment, err := deploymentService.CreateDeployment(ctx.Context, akoServerless) - if err != nil { - return r.terminate(ctx, workflow.DeploymentNotCreatedInAtlas, err) - } - - atlasServerless = newServerlessDeployment.(*deployment.Serverless) - } - - switch atlasServerless.GetState() { - case status.StateIDLE: - if !reflect.DeepEqual(akoServerless.ServerlessSpec, atlasServerless.ServerlessSpec) { - _, err := deploymentService.UpdateDeployment(ctx.Context, akoServerless) - if err != nil { - return r.terminate(ctx, workflow.DeploymentNotUpdatedInAtlas, err) - } - - return r.inProgress(ctx, akoServerless.GetCustomResource(), atlasServerless, workflow.DeploymentUpdating, "deployment is updating") - } - - err := r.ensureConnectionSecrets(ctx, projectService, akoServerless, atlasServerless.GetConnection()) - if err != nil { - return r.terminate(ctx, workflow.DeploymentConnectionSecretsNotCreated, err) - } - - // Note: Serverless Private endpoints keep theirs flows without translation layer (yet) - result := ensureServerlessPrivateEndpoints(ctx, akoServerless.GetProjectID(), akoServerless.GetCustomResource()) - - switch { - case result.IsInProgress(): - return r.inProgress(ctx, akoServerless.GetCustomResource(), atlasServerless, workflow.ServerlessPrivateEndpointInProgress, result.GetMessage()) - case !result.IsOk(): - return r.terminate(ctx, workflow.ServerlessPrivateEndpointFailed, errors.New(result.GetMessage())) - } - - err = customresource.ApplyLastConfigApplied(ctx.Context, akoServerless.GetCustomResource(), r.Client) - if err != nil { - return r.terminate(ctx, workflow.Internal, err) - } - - return r.ready(ctx, akoServerless, atlasServerless) - case status.StateCREATING: - return r.inProgress(ctx, akoServerless.GetCustomResource(), atlasServerless, workflow.DeploymentCreating, "deployment is provisioning") - case status.StateUPDATING, status.StateREPAIRING: - return r.inProgress(ctx, akoServerless.GetCustomResource(), atlasServerless, workflow.DeploymentUpdating, "deployment is updating") - default: - return r.terminate(ctx, workflow.Internal, fmt.Errorf("unknown deployment state: %s", atlasServerless.GetState())) - } -} diff --git a/internal/controller/atlasdeployment/serverless_deployment_test.go b/internal/controller/atlasdeployment/serverless_deployment_test.go deleted file mode 100644 index 723aa41056..0000000000 --- a/internal/controller/atlasdeployment/serverless_deployment_test.go +++ /dev/null @@ -1,972 +0,0 @@ -// Copyright 2025 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package atlasdeployment - -import ( - "context" - "errors" - "net/http" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "go.mongodb.org/atlas-sdk/v20250312006/admin" - "go.mongodb.org/atlas-sdk/v20250312006/mockadmin" - "go.uber.org/zap/zaptest" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" - akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/provider" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlas" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/reconciler" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/indexer" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/translation" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" -) - -func TestHandleServerlessInstance(t *testing.T) { - type workflowRes struct { - res ctrl.Result - err error - } - tests := map[string]struct { - atlasDeployment *akov2.AtlasDeployment - deploymentInAtlas *deployment.Serverless - deploymentService func() deployment.AtlasDeploymentsService - sdkMock func() *admin.APIClient - expectedResult workflowRes - expectedConditions []api.Condition - }{ - "fail to create a new serverless instance in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: nil, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - service.EXPECT().CreateDeployment(context.Background(), mock.AnythingOfType("*deployment.Serverless")). - Return(nil, errors.New("failed to create serverless instance")) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - err: errors.New("failed to create serverless instance"), - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentNotCreatedInAtlas)). - WithMessageRegexp("failed to create serverless instance"), - }, - }, - "create a new serverless instance in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: nil, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - service.EXPECT().CreateDeployment(context.Background(), mock.AnythingOfType("*deployment.Serverless")). - Return( - &deployment.Serverless{ - ProjectID: "project-id", - State: "CREATING", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - nil, - ) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - res: ctrl.Result{RequeueAfter: workflow.DefaultRetry}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentCreating)). - WithMessageRegexp("deployment is provisioning"), - }, - }, - "fail to update a serverless instance in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - service.EXPECT().UpdateDeployment(context.Background(), mock.AnythingOfType("*deployment.Serverless")). - Return(nil, errors.New("failed to update serverless instance")) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - err: errors.New("failed to update serverless instance"), - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentNotUpdatedInAtlas)). - WithMessageRegexp("failed to update serverless instance"), - }, - }, - "update a serverless instance in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - service.EXPECT().UpdateDeployment(context.Background(), mock.AnythingOfType("*deployment.Serverless")). - Return( - &deployment.Serverless{ - ProjectID: "project-id", - State: "UPDATING", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - nil, - ) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - res: ctrl.Result{RequeueAfter: workflow.DefaultRetry}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentUpdating)). - WithMessageRegexp("deployment is updating"), - }, - }, - "serverless instance is updating in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "UPDATING", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - res: ctrl.Result{RequeueAfter: workflow.DefaultRetry}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentUpdating)). - WithMessageRegexp("deployment is updating"), - }, - }, - "update tags of a serverless instance in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - { - Key: "newTag", - Value: "newValue", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - service.EXPECT().UpdateDeployment(context.Background(), mock.AnythingOfType("*deployment.Serverless")). - Return( - &deployment.Serverless{ - ProjectID: "project-id", - State: "UPDATING", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - { - Key: "newTag", - Value: "newValue", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - nil, - ) - - return service - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - res: ctrl.Result{RequeueAfter: workflow.DefaultRetry}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.DeploymentUpdating)). - WithMessageRegexp("deployment is updating"), - }, - }, - "serverless instance fails when private endpoints fails": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ProjectDualReference: akov2.ProjectDualReference{ - ProjectRef: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - }, - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe1", - CloudProviderEndpointID: "arn-12345", - }, - }, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - - return service - }, - sdkMock: func() *admin.APIClient { - speClient := mockadmin.NewServerlessPrivateEndpointsApi(t) - speClient.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speClient}) - speClient.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return(nil, &http.Response{}, errors.New("failed to list private endpoints")) - - return &admin.APIClient{ServerlessPrivateEndpointsApi: speClient} - }, - expectedResult: workflowRes{ - err: errors.New("unable to retrieve list of serverless private endpoints from Atlas: failed to list private endpoints"), - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.ServerlessPrivateEndpointReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointFailed)). - WithMessageRegexp("unable to retrieve list of serverless private endpoints from Atlas: failed to list private endpoints"), - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointFailed)). - WithMessageRegexp("unable to retrieve list of serverless private endpoints from Atlas: failed to list private endpoints"), - }, - }, - "serverless flex instance fails when private endpoints are set": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ProjectDualReference: akov2.ProjectDualReference{ - ProjectRef: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - }, - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe1", - CloudProviderEndpointID: "arn-12345", - }, - }, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - - return service - }, - sdkMock: func() *admin.APIClient { - mockError := &admin.GenericOpenAPIError{} - model := *admin.NewApiErrorWithDefaults() - model.SetErrorCode("NOT_SERVERLESS_TENANT_CLUSTER") - mockError.SetModel(model) - - speClient := mockadmin.NewServerlessPrivateEndpointsApi(t) - speClient.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speClient}) - speClient.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return(nil, &http.Response{}, mockError) - - return &admin.APIClient{ServerlessPrivateEndpointsApi: speClient} - }, - expectedResult: workflowRes{ - err: errors.New("serverless private endpoints are not supported: "), - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.ServerlessPrivateEndpointReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointFailed)). - WithMessageRegexp("serverless private endpoints are not supported: "), - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointFailed)). - WithMessageRegexp("serverless private endpoints are not supported: "), - }, - }, - "serverless instance is updating when private endpoints are in progress": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ProjectDualReference: akov2.ProjectDualReference{ - ProjectRef: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - }, - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe1", - CloudProviderEndpointID: "arn-12345", - }, - }, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - - return service - }, - sdkMock: func() *admin.APIClient { - speClient := mockadmin.NewServerlessPrivateEndpointsApi(t) - speClient.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speClient}) - speClient.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return(nil, &http.Response{}, nil) - - speClient.EXPECT().CreateServerlessPrivateEndpoint(context.Background(), "project-id", "instance0", mock.AnythingOfType("*admin.ServerlessTenantCreateRequest")). - Return(admin.CreateServerlessPrivateEndpointApiRequest{ApiService: speClient}) - speClient.EXPECT().CreateServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.CreateServerlessPrivateEndpointApiRequest")). - Return(&admin.ServerlessTenantEndpoint{}, &http.Response{}, nil) - - return &admin.APIClient{ServerlessPrivateEndpointsApi: speClient} - }, - expectedResult: workflowRes{ - res: ctrl.Result{RequeueAfter: workflow.DefaultRetry}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.ServerlessPrivateEndpointReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointInProgress)). - WithMessageRegexp("Waiting serverless private endpoint to be configured"), - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.ServerlessPrivateEndpointInProgress)). - WithMessageRegexp("Waiting serverless private endpoint to be configured"), - }, - }, - "serverless instance is ready in atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ProjectDualReference: akov2.ProjectDualReference{ - ProjectRef: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - }, - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "IDLE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - service := translation.NewAtlasDeploymentsServiceMock(t) - - return service - }, - sdkMock: func() *admin.APIClient { - speClient := mockadmin.NewServerlessPrivateEndpointsApi(t) - speClient.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speClient}) - speClient.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return(nil, &http.Response{}, nil) - - return &admin.APIClient{ServerlessPrivateEndpointsApi: speClient} - }, - expectedResult: workflowRes{ - res: ctrl.Result{}, - err: nil, - }, - expectedConditions: []api.Condition{ - api.TrueCondition(api.DeploymentReadyType), - api.TrueCondition(api.ReadyType). - WithMessageRegexp("WARNING: Serverless is deprecated. See https://dochub.mongodb.org/core/atlas-flex-migration for details."), - }, - }, - "serverless instance has an unknown state": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ProjectDualReference: akov2.ProjectDualReference{ - ProjectRef: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - }, - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - deploymentInAtlas: &deployment.Serverless{ - ProjectID: "project-id", - State: "NEW_UNKNOWN_STATE", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderServerless, - BackingProviderName: "AWS", - RegionName: "us-east-1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "test", - Value: "e2e", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: false, - }, - TerminationProtectionEnabled: true, - }, - }, - deploymentService: func() deployment.AtlasDeploymentsService { - return translation.NewAtlasDeploymentsServiceMock(t) - }, - sdkMock: func() *admin.APIClient { - return &admin.APIClient{} - }, - expectedResult: workflowRes{ - err: errors.New("unknown deployment state: NEW_UNKNOWN_STATE"), - }, - expectedConditions: []api.Condition{ - api.FalseCondition(api.DeploymentReadyType). - WithReason(string(workflow.Internal)). - WithMessageRegexp("unknown deployment state: NEW_UNKNOWN_STATE"), - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - logger := zaptest.NewLogger(t) - testScheme := runtime.NewScheme() - require.NoError(t, akov2.AddToScheme(testScheme)) - dbUserProjectIndexer := indexer.NewAtlasDatabaseUserByProjectIndexer(ctx, nil, logger) - k8sClient := fake.NewClientBuilder(). - WithScheme(testScheme). - WithObjects(tt.atlasDeployment). - WithIndex(dbUserProjectIndexer.Object(), dbUserProjectIndexer.Name(), dbUserProjectIndexer.Keys). - Build() - reconciler := &AtlasDeploymentReconciler{ - AtlasReconciler: reconciler.AtlasReconciler{ - Client: k8sClient, - Log: logger.Sugar(), - }, - } - workflowCtx := &workflow.Context{ - Context: ctx, - Log: logger.Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: tt.sdkMock(), - }, - } - - deploymentInAKO := deployment.NewDeployment("project-id", tt.atlasDeployment).(*deployment.Serverless) - var projectService project.ProjectService - result, err := reconciler.handleServerlessInstance(workflowCtx, projectService, tt.deploymentService(), deploymentInAKO, tt.deploymentInAtlas) - //require.NoError(t, err) - assert.Equal(t, tt.expectedResult, workflowRes{ - res: result, - err: err, - }) - assert.True( - t, - cmp.Equal( - tt.expectedConditions, - workflowCtx.Conditions(), - cmpopts.IgnoreFields(api.Condition{}, "LastTransitionTime"), - ), - ) - }) - } -} diff --git a/internal/controller/atlasdeployment/serverless_private_endpoint.go b/internal/controller/atlasdeployment/serverless_private_endpoint.go deleted file mode 100644 index 3bc5d7aabf..0000000000 --- a/internal/controller/atlasdeployment/serverless_private_endpoint.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2025 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package atlasdeployment - -import ( - "errors" - "fmt" - - "go.mongodb.org/atlas-sdk/v20250312006/admin" - - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" - akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/provider" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow" -) - -// Status transitions: -// RESERVATION_REQUESTED -> RESERVED -> INITIATING -> AVAILABLE -> DELETING -// I assume FAILED state can be reach from any other state transition -const ( - SPEStatusDeleting = "DELETING" - SPEStatusReserved = "RESERVED" - SPEStatusAvailable = "AVAILABLE" -) - -func ensureServerlessPrivateEndpoints(service *workflow.Context, projectID string, deployment *akov2.AtlasDeployment) workflow.DeprecatedResult { - if deployment == nil || deployment.Spec.ServerlessSpec == nil { - return workflow.Terminate(workflow.Internal, errors.New("serverless deployment spec is empty")) - } - - deploymentSpec := deployment.Spec.ServerlessSpec - - if isGCPWithPrivateEndpoints(deploymentSpec) { - return workflow.Terminate(workflow.AtlasUnsupportedFeature, errors.New("serverless private endpoints are not supported for GCP")) - } - - if isGCPWithoutPrivateEndpoints(deploymentSpec) { - return workflow.OK() - } - - finished, err := syncServerlessPrivateEndpoints(service, projectID, deploymentSpec) - var result workflow.DeprecatedResult - switch { - case err != nil: - result = workflow.Terminate(workflow.ServerlessPrivateEndpointFailed, err) - case err == nil && !finished: - result = workflow.InProgress(workflow.ServerlessPrivateEndpointInProgress, "Waiting serverless private endpoint to be configured") - default: - result = workflow.OK() - } - - switch len(deploymentSpec.PrivateEndpoints) { - case 0: - service.UnsetCondition(api.ServerlessPrivateEndpointReadyType) - default: - service.SetConditionFromResult(api.ServerlessPrivateEndpointReadyType, result) - } - - return result -} - -func syncServerlessPrivateEndpoints(service *workflow.Context, projectID string, deployment *akov2.ServerlessSpec) (bool, error) { - service.Log.Debugf("Syncing serverless private endpoints for deployment %s", deployment.Name) - - atlasPrivateEndpoints, err := listServerlessPrivateEndpoints(service, projectID, deployment.Name) - // This is a shimmed flex cluster, if there are private serverless endpoints configured, we have to return an error. - if admin.IsErrorCode(err, "NOT_SERVERLESS_TENANT_CLUSTER") { - if len(deployment.PrivateEndpoints) > 0 { - return false, fmt.Errorf("serverless private endpoints are not supported: %w", err) - } - return true, nil - } - - if err != nil { - return false, fmt.Errorf("unable to retrieve list of serverless private endpoints from Atlas: %w", err) - } - - toCreate, toUpdate, toDelete := sortTasks(deployment.PrivateEndpoints, atlasPrivateEndpoints) - speStatusMap := newSPEStatusMap(atlasPrivateEndpoints) - - service.Log.Debugf("Creating %d serverless private endpoints for deployment %s", len(toCreate), deployment.Name) - for i := range toCreate { - privateEndpointCreate := toCreate[i] - service.Log.Debugf("Creating serverless private endpoint %s", privateEndpointCreate.Name) - atlasPrivateEndpoint, err := createServerLessPrivateEndpoint(service, projectID, deployment.Name, &privateEndpointCreate) - if err != nil { - return false, fmt.Errorf("unable to create serverless private endpoint on Atlas: %w", err) - } - - speStatusMap[privateEndpointCreate.Name] = speStatusFromAtlas(atlasPrivateEndpoint) - } - - service.Log.Debugf("Connecting %d serverless private endpoints for deployment %s", len(toUpdate), deployment.Name) - for i := range toUpdate { - privateEndpointUpdate := toUpdate[i] - service.Log.Debugf("Connecting serverless private endpoint %s", privateEndpointUpdate.Name) - latestPEStatus := speStatusMap[privateEndpointUpdate.Name] - atlasPrivateEndpoint, err := updateServerLessPrivateEndpoint(service, projectID, deployment.Name, latestPEStatus.ID, latestPEStatus.ProviderName, &privateEndpointUpdate) - if err != nil { - return false, fmt.Errorf("unable to update/connect serverless private endpoint on Atlas: %w", err) - } - - speStatusMap[privateEndpointUpdate.Name] = speStatusFromAtlas(atlasPrivateEndpoint) - } - - service.Log.Debugf("Deleting %d serverless private endpoints for deployment %s", len(toDelete), deployment.Name) - for _, privateEndpointDelete := range toDelete { - service.Log.Debugf("Deleting serverless private endpoint with ID %s", privateEndpointDelete) - err = deleteServerLessPrivateEndpoint(service, projectID, deployment.Name, privateEndpointDelete.GetId()) - if err != nil { - return false, fmt.Errorf("unable to delete serverless private endpoint on Atlas: %w", err) - } - - // Serverless private endpoints first go through DELETING state before they are gone - speStatus := speStatusMap[privateEndpointDelete.GetComment()] - speStatus.Status = SPEStatusDeleting - speStatusMap[privateEndpointDelete.GetComment()] = speStatus - } - - speStatuses := make([]status.ServerlessPrivateEndpoint, 0, len(speStatusMap)) - for _, speStatus := range speStatusMap { - speStatuses = append(speStatuses, speStatus) - } - service.EnsureStatusOption(status.AtlasDeploymentSPEOption(speStatuses)) - - return areSPEsAvailable(speStatuses), nil -} - -func isGCPWithPrivateEndpoints(deployment *akov2.ServerlessSpec) bool { - if provider.ProviderName(deployment.ProviderSettings.BackingProviderName) == provider.ProviderGCP && - len(deployment.PrivateEndpoints) > 0 { - return true - } - - return false -} - -func isGCPWithoutPrivateEndpoints(deployment *akov2.ServerlessSpec) bool { - if provider.ProviderName(deployment.ProviderSettings.BackingProviderName) == provider.ProviderGCP && - len(deployment.PrivateEndpoints) == 0 { - return true - } - - return false -} - -func listServerlessPrivateEndpoints(service *workflow.Context, projectID, deploymentName string) ([]admin.ServerlessTenantEndpoint, error) { - // this endpoint does not offer paginated responses - privateEndpoints, _, err := service.SdkClientSet.SdkClient20250312006.ServerlessPrivateEndpointsApi. - ListServerlessPrivateEndpoints(service.Context, projectID, deploymentName). - Execute() - - return privateEndpoints, err -} - -func createServerLessPrivateEndpoint(service *workflow.Context, projectID, deploymentName string, privateEndpoint *akov2.ServerlessPrivateEndpoint) (*admin.ServerlessTenantEndpoint, error) { - request := admin.ServerlessTenantCreateRequest{ - Comment: &privateEndpoint.Name, - } - - atlasPrivateEndpoint, _, err := service.SdkClientSet.SdkClient20250312006.ServerlessPrivateEndpointsApi. - CreateServerlessPrivateEndpoint(service.Context, projectID, deploymentName, &request). - Execute() - - return atlasPrivateEndpoint, err -} - -func updateServerLessPrivateEndpoint(service *workflow.Context, projectID, deploymentName, endpointID, providerName string, privateEndpoint *akov2.ServerlessPrivateEndpoint) (*admin.ServerlessTenantEndpoint, error) { - // we don't allow update name (comment) once it's used as identifier - request := admin.ServerlessTenantEndpointUpdate{ - ProviderName: providerName, - CloudProviderEndpointId: &privateEndpoint.CloudProviderEndpointID, - } - - // when provider is Azure we expect IP Address to be set - if privateEndpoint.PrivateEndpointIPAddress != "" { - request.PrivateEndpointIpAddress = &privateEndpoint.PrivateEndpointIPAddress - } - - atlasPrivateEndpoint, _, err := service.SdkClientSet.SdkClient20250312006.ServerlessPrivateEndpointsApi. - UpdateServerlessPrivateEndpoint(service.Context, projectID, deploymentName, endpointID, &request). - Execute() - - return atlasPrivateEndpoint, err -} - -func deleteServerLessPrivateEndpoint(service *workflow.Context, projectID, deploymentName, endpointID string) error { - _, err := service.SdkClientSet.SdkClient20250312006.ServerlessPrivateEndpointsApi. - DeleteServerlessPrivateEndpoint(service.Context, projectID, deploymentName, endpointID). - Execute() - - return err -} - -// sortTasks Build and return all the operations pending to reconcile -// There are 3 possible operations: -// CREATE: A private endpoint with a given name doesn't exist on Atlas -// UPDATE: A private endpoint with a given name exists on Atlas with status RESERVED (waiting to be connected) -// DELETE: A private endpoint with a given name exists on Atlas, but it's not describe in Kubernetes resource -// -// A private endpoint is not expected to have duplicated name as validation happens at very beginning of the -// reconciliation. See validate.serverlessPrivateEndpoints -func sortTasks( - privateEndpoints []akov2.ServerlessPrivateEndpoint, - atlasPrivateEndpoints []admin.ServerlessTenantEndpoint, -) ([]akov2.ServerlessPrivateEndpoint, []akov2.ServerlessPrivateEndpoint, []admin.ServerlessTenantEndpoint) { - toCreate := make([]akov2.ServerlessPrivateEndpoint, 0, len(privateEndpoints)) - toUpdate := make([]akov2.ServerlessPrivateEndpoint, 0, len(privateEndpoints)) - toDelete := make([]admin.ServerlessTenantEndpoint, 0, len(atlasPrivateEndpoints)) - - privateEndpointsByName := map[string]*akov2.ServerlessPrivateEndpoint{} - for i := range privateEndpoints { - privateEndpoint := privateEndpoints[i] - privateEndpointsByName[privateEndpoint.Name] = &privateEndpoint - } - - atlasPrivateEndpointsByComment := map[string]*admin.ServerlessTenantEndpoint{} - for i := range atlasPrivateEndpoints { - atlasPrivateEndpoint := atlasPrivateEndpoints[i] - atlasPrivateEndpointsByComment[atlasPrivateEndpoint.GetComment()] = &atlasPrivateEndpoint - } - - // Collect all endpoints to create and update (connect) - for i := range privateEndpoints { - privateEndpoint := privateEndpoints[i] - atlasPrivateEndpoint, ok := atlasPrivateEndpointsByComment[privateEndpoint.Name] - - // If a private endpoint with a given name doesn't exist on Atlas, add to creation list - if !ok { - toCreate = append(toCreate, privateEndpoint) - } - - // If a private endpoint with a given name exists on Atlas with status RESERVED, add to update list (need to be connected) - if isReadyToConnect(&privateEndpoint, atlasPrivateEndpoint) { - toUpdate = append(toUpdate, privateEndpoint) - } - } - - for _, atlasPrivateEndpoint := range atlasPrivateEndpoints { - // If an existing Atlas private endpoint is not present in kubernetes resource, add to deletion list - if _, ok := privateEndpointsByName[atlasPrivateEndpoint.GetComment()]; !ok { - toDelete = append(toDelete, atlasPrivateEndpoint) - } - } - - return toCreate, toUpdate, toDelete -} - -func newSPEStatusMap(atlasPrivateEndpoints []admin.ServerlessTenantEndpoint) map[string]status.ServerlessPrivateEndpoint { - statuses := map[string]status.ServerlessPrivateEndpoint{} - - for i := range atlasPrivateEndpoints { - atlasPrivateEndpoint := atlasPrivateEndpoints[i] - statuses[atlasPrivateEndpoint.GetComment()] = speStatusFromAtlas(&atlasPrivateEndpoint) - } - - return statuses -} - -func isReadyToConnect(privateEndpoint *akov2.ServerlessPrivateEndpoint, atlasPrivateEndpoint *admin.ServerlessTenantEndpoint) bool { - if atlasPrivateEndpoint.GetStatus() != SPEStatusReserved { - return false - } - - switch provider.ProviderName(atlasPrivateEndpoint.GetProviderName()) { - case provider.ProviderAWS: - return privateEndpoint.CloudProviderEndpointID != "" - case provider.ProviderAzure: - return privateEndpoint.CloudProviderEndpointID != "" && privateEndpoint.PrivateEndpointIPAddress != "" - } - - return false -} - -func areSPEsAvailable(pe []status.ServerlessPrivateEndpoint) bool { - for _, p := range pe { - if p.Status != SPEStatusAvailable { - return false - } - } - - return true -} - -func speStatusFromAtlas(in *admin.ServerlessTenantEndpoint) status.ServerlessPrivateEndpoint { - return status.ServerlessPrivateEndpoint{ - ID: in.GetId(), - // Comment property is internally used as name to identify and match items on the operator against their peers on Atlas - Name: in.GetComment(), - ProviderName: in.GetProviderName(), - CloudProviderEndpointID: in.GetCloudProviderEndpointId(), - PrivateEndpointIPAddress: in.GetPrivateEndpointIpAddress(), - EndpointServiceName: in.GetEndpointServiceName(), - PrivateLinkServiceResourceID: in.GetPrivateLinkServiceResourceId(), - Status: in.GetStatus(), - ErrorMessage: in.GetErrorMessage(), - } -} diff --git a/internal/controller/atlasdeployment/serverless_private_endpoint_test.go b/internal/controller/atlasdeployment/serverless_private_endpoint_test.go deleted file mode 100644 index 87ec595b8c..0000000000 --- a/internal/controller/atlasdeployment/serverless_private_endpoint_test.go +++ /dev/null @@ -1,769 +0,0 @@ -// Copyright 2025 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package atlasdeployment - -import ( - "context" - "errors" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "go.mongodb.org/atlas-sdk/v20250312006/admin" - "go.mongodb.org/atlas-sdk/v20250312006/mockadmin" - "go.uber.org/zap/zaptest" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" - akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlas" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow" - "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" -) - -func TestEnsureServerlessPrivateEndpoints(t *testing.T) { - t.Run("should fail when deployment is nil", func(t *testing.T) { - result := ensureServerlessPrivateEndpoints(&workflow.Context{}, "project-id", nil) - - assert.Equal( - t, - workflow.Terminate(workflow.Internal, errors.New("serverless deployment spec is empty")), - result, - ) - }) - - t.Run("should fail when serverless spec is nil", func(t *testing.T) { - result := ensureServerlessPrivateEndpoints(&workflow.Context{}, "project-id", &akov2.AtlasDeployment{}) - - assert.Equal( - t, - workflow.Terminate(workflow.Internal, errors.New("serverless deployment spec is empty")), - result, - ) - }) - - t.Run("should fail when setting a GCP serverless instance with a private endpoint", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "GCP", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&workflow.Context{}, "project-id", &deployment) - - assert.Equal( - t, - workflow.Terminate(workflow.AtlasUnsupportedFeature, errors.New("serverless private endpoints are not supported for GCP")), - result, - ) - }) - - t.Run("should succeed when setting a GCP serverless instance without private endpoints", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "GCP", - }, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&workflow.Context{}, "project-id", &deployment) - - assert.Equal(t, workflow.OK(), result) - }) - - t.Run("should succeed when there are nothing to sync", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(mock.Anything, mock.Anything, mock.Anything). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.Anything). - Return([]admin.ServerlessTenantEndpoint{}, &http.Response{}, nil) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&service, "project-id", &deployment) - - assert.Equal(t, workflow.OK(), result) - }) - - t.Run("should fail when error happens syncing private endpoints", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(mock.Anything, mock.Anything, mock.Anything). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.Anything). - Return(nil, &http.Response{}, errors.New("connection failed")) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&service, "project-id", &deployment) - - assert.Equal(t, result.GetError().Error(), "unable to retrieve list of serverless private endpoints from Atlas: connection failed") - expected := workflow.Terminate(workflow.ServerlessPrivateEndpointFailed, fmt.Errorf("unable to retrieve list of serverless private endpoints from Atlas: connection failed")) - assert.Equal(t, result.CloneWithoutError(), expected.CloneWithoutError()) - }) - - t.Run("should succeed when syncing private endpoints still in progress", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(mock.Anything, mock.Anything, mock.Anything). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.Anything). - Return([]admin.ServerlessTenantEndpoint{}, &http.Response{}, nil) - speAPI.EXPECT().CreateServerlessPrivateEndpoint(mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(admin.CreateServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().CreateServerlessPrivateEndpointExecute(mock.Anything). - Return( - &admin.ServerlessTenantEndpoint{ - Id: pointer.MakePtr("spe-id"), - Comment: pointer.MakePtr("spe-1"), - Status: pointer.MakePtr("RESERVATION_REQUESTED"), - }, - &http.Response{}, - nil, - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&service, "project-id", &deployment) - - assert.Equal( - t, - workflow.InProgress(workflow.ServerlessPrivateEndpointInProgress, "Waiting serverless private endpoint to be configured"), - result, - ) - - condition, ok := service.GetCondition(api.ServerlessPrivateEndpointReadyType) - assert.True(t, ok) - condition.LastTransitionTime = metav1.Time{} // not relevant for the unit test - assert.Equal( - t, - api.Condition{ - Type: api.ServerlessPrivateEndpointReadyType, - Status: corev1.ConditionFalse, - Reason: string(workflow.ServerlessPrivateEndpointInProgress), - Message: "Waiting serverless private endpoint to be configured", - }, - condition, - ) - }) - - t.Run("should succeed when finish syncing private endpoints", func(t *testing.T) { - deployment := akov2.AtlasDeployment{ - Spec: akov2.AtlasDeploymentSpec{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - CloudProviderEndpointID: "aws-endpoint-id", - }, - }, - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(mock.Anything, mock.Anything, mock.Anything). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.Anything). - Return( - []admin.ServerlessTenantEndpoint{ - { - Id: pointer.MakePtr("spe-id"), - ProviderName: pointer.MakePtr("AWS"), - CloudProviderEndpointId: pointer.MakePtr("aws-endpoint-id"), - Comment: pointer.MakePtr("spe-1"), - Status: pointer.MakePtr(SPEStatusAvailable), - }, - }, - &http.Response{}, - nil, - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - result := ensureServerlessPrivateEndpoints(&service, "project-id", &deployment) - - assert.Equal(t, workflow.OK(), result) - - condition, ok := service.GetCondition(api.ServerlessPrivateEndpointReadyType) - assert.True(t, ok) - condition.LastTransitionTime = metav1.Time{} // not relevant for the unit test - assert.Equal( - t, - api.Condition{ - Type: api.ServerlessPrivateEndpointReadyType, - Status: corev1.ConditionTrue, - }, - condition, - ) - }) -} - -func TestSyncServerlessPrivateEndpoints(t *testing.T) { - t.Run("should succeed adding, creating and deleting private endpoints", func(t *testing.T) { - spec := akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - { - Name: "spe-2", - CloudProviderEndpointID: "aws-endpoint-id", - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance-0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return( - []admin.ServerlessTenantEndpoint{ - { - Id: pointer.MakePtr("spe-2-id"), - ProviderName: pointer.MakePtr("AWS"), - Comment: pointer.MakePtr("spe-2"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - { - Id: pointer.MakePtr("spe-3-id"), - ProviderName: pointer.MakePtr("AWS"), - Comment: pointer.MakePtr("spe-3"), - Status: pointer.MakePtr(SPEStatusAvailable), - }, - }, - &http.Response{}, - nil, - ) - speAPI.EXPECT().CreateServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", mock.AnythingOfType("*admin.ServerlessTenantCreateRequest")). - Return(admin.CreateServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().CreateServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.CreateServerlessPrivateEndpointApiRequest")). - Return( - &admin.ServerlessTenantEndpoint{ - Id: pointer.MakePtr("spe-1-id"), - ProviderName: pointer.MakePtr("AWS"), - Comment: pointer.MakePtr("spe-1"), - Status: pointer.MakePtr("RESERVATION_REQUESTED"), - }, - &http.Response{}, - nil, - ) - speAPI.EXPECT().UpdateServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", "spe-2-id", mock.AnythingOfType("*admin.ServerlessTenantEndpointUpdate")). - Return(admin.UpdateServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().UpdateServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.UpdateServerlessPrivateEndpointApiRequest")). - Return( - &admin.ServerlessTenantEndpoint{ - Id: pointer.MakePtr("spe-2-id"), - ProviderName: pointer.MakePtr("AWS"), - CloudProviderEndpointId: pointer.MakePtr("aws-endpoint-id"), - Comment: pointer.MakePtr("spe-2"), - Status: pointer.MakePtr("INITIATING"), - }, - &http.Response{}, - nil, - ) - speAPI.EXPECT().DeleteServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", "spe-3-id"). - Return(admin.DeleteServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().DeleteServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.DeleteServerlessPrivateEndpointApiRequest")). - Return( - &http.Response{}, - nil, - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - - finished, err := syncServerlessPrivateEndpoints(&service, "project-id", &spec) - assert.NoError(t, err) - assert.False(t, finished) - }) - - t.Run("should fail adding a private endpoint", func(t *testing.T) { - spec := akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance-0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return([]admin.ServerlessTenantEndpoint{}, &http.Response{}, nil) - speAPI.EXPECT().CreateServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", mock.AnythingOfType("*admin.ServerlessTenantCreateRequest")). - Return(admin.CreateServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().CreateServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.CreateServerlessPrivateEndpointApiRequest")). - Return( - nil, - &http.Response{}, - errors.New("failed to create serverless private endpoint"), - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - - finished, err := syncServerlessPrivateEndpoints(&service, "project-id", &spec) - assert.ErrorContains(t, err, "failed to create serverless private endpoint") - assert.False(t, finished) - }) - - t.Run("should fail updating a private endpoint", func(t *testing.T) { - spec := akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - CloudProviderEndpointID: "endpoint-id", - }, - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance-0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return( - []admin.ServerlessTenantEndpoint{ - { - Id: pointer.MakePtr("spe-1-id"), - Comment: pointer.MakePtr("spe-1"), - Status: pointer.MakePtr(SPEStatusReserved), - ProviderName: pointer.MakePtr("AWS"), - }, - }, - &http.Response{}, - nil, - ) - speAPI.EXPECT().UpdateServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", "spe-1-id", mock.AnythingOfType("*admin.ServerlessTenantEndpointUpdate")). - Return(admin.UpdateServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().UpdateServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.UpdateServerlessPrivateEndpointApiRequest")). - Return( - nil, - &http.Response{}, - errors.New("failed to update serverless private endpoint"), - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - - finished, err := syncServerlessPrivateEndpoints(&service, "project-id", &spec) - assert.ErrorContains(t, err, "failed to update serverless private endpoint") - assert.False(t, finished) - }) - - t.Run("should fail delete a private endpoint", func(t *testing.T) { - spec := akov2.ServerlessSpec{ - Name: "instance-0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - }, - } - speAPI := mockadmin.NewServerlessPrivateEndpointsApi(t) - speAPI.EXPECT().ListServerlessPrivateEndpoints(context.Background(), "project-id", "instance-0"). - Return(admin.ListServerlessPrivateEndpointsApiRequest{ApiService: speAPI}) - speAPI.EXPECT().ListServerlessPrivateEndpointsExecute(mock.AnythingOfType("admin.ListServerlessPrivateEndpointsApiRequest")). - Return( - []admin.ServerlessTenantEndpoint{ - { - Id: pointer.MakePtr("spe-1-id"), - Comment: pointer.MakePtr("spe-1"), - Status: pointer.MakePtr(SPEStatusAvailable), - ProviderName: pointer.MakePtr("AWS"), - }, - }, - &http.Response{}, - nil, - ) - speAPI.EXPECT().DeleteServerlessPrivateEndpoint(context.Background(), "project-id", "instance-0", "spe-1-id"). - Return(admin.DeleteServerlessPrivateEndpointApiRequest{ApiService: speAPI}) - speAPI.EXPECT().DeleteServerlessPrivateEndpointExecute(mock.AnythingOfType("admin.DeleteServerlessPrivateEndpointApiRequest")). - Return( - &http.Response{}, - errors.New("failed to delete serverless private endpoint"), - ) - service := workflow.Context{ - Context: context.Background(), - Log: zaptest.NewLogger(t).Sugar(), - SdkClientSet: &atlas.ClientSet{ - SdkClient20250312006: &admin.APIClient{ - ServerlessPrivateEndpointsApi: speAPI, - }, - }, - } - - finished, err := syncServerlessPrivateEndpoints(&service, "project-id", &spec) - assert.ErrorContains(t, err, "failed to delete serverless private endpoint") - assert.False(t, finished) - }) -} - -func TestIsGCPWithPrivateEndpoints(t *testing.T) { - t.Run("should return true when is GCP serverless instance containing private endpoint configuration", func(t *testing.T) { - deployment := akov2.ServerlessSpec{ - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - BackingProviderName: "GCP", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - } - - assert.True(t, isGCPWithPrivateEndpoints(&deployment)) - }) - - t.Run("should return false when is GCP serverless instance without private endpoint configuration", func(t *testing.T) { - deployment := akov2.ServerlessSpec{ - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - BackingProviderName: "GCP", - }, - } - - assert.False(t, isGCPWithPrivateEndpoints(&deployment)) - }) -} - -func TestIsGCPWithoutPrivateEndpoints(t *testing.T) { - t.Run("should return false when is GCP serverless instance containing private endpoint configuration", func(t *testing.T) { - deployment := akov2.ServerlessSpec{ - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - BackingProviderName: "GCP", - }, - PrivateEndpoints: []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - } - - assert.False(t, isGCPWithoutPrivateEndpoints(&deployment)) - }) - - t.Run("should return true when is GCP serverless instance without private endpoint configuration", func(t *testing.T) { - deployment := akov2.ServerlessSpec{ - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - BackingProviderName: "GCP", - }, - } - - assert.True(t, isGCPWithoutPrivateEndpoints(&deployment)) - }) -} - -func TestSortTasks(t *testing.T) { - t.Run("should sort one of each operation", func(t *testing.T) { - spes := []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - { - Name: "spe-2", - CloudProviderEndpointID: "endpoint-id", - }, - } - atlas := []admin.ServerlessTenantEndpoint{ - { - ProviderName: pointer.MakePtr("AWS"), - Comment: pointer.MakePtr("spe-2"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - { - Comment: pointer.MakePtr("spe-3"), - }, - } - - toCreate, toUpdate, toDelete := sortTasks(spes, atlas) - - assert.Equal( - t, - []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-1", - }, - }, - toCreate, - ) - assert.Equal( - t, - []akov2.ServerlessPrivateEndpoint{ - { - Name: "spe-2", - CloudProviderEndpointID: "endpoint-id", - }, - }, - toUpdate, - ) - assert.Equal( - t, - []admin.ServerlessTenantEndpoint{ - { - Comment: pointer.MakePtr("spe-3"), - }, - }, - toDelete, - ) - }) -} - -func TestIsReadyToConnect(t *testing.T) { - data := map[string]struct { - spe akov2.ServerlessPrivateEndpoint - atlas admin.ServerlessTenantEndpoint - expected bool - }{ - "should return false when private endpoint is not in RESERVED state": { - spe: akov2.ServerlessPrivateEndpoint{}, - atlas: admin.ServerlessTenantEndpoint{ - Status: pointer.MakePtr("RESERVATION_REQUESTED"), - }, - expected: false, - }, - "should return false when a AWS private endpoint is in RESERVED state but miss endpoint ID": { - spe: akov2.ServerlessPrivateEndpoint{}, - atlas: admin.ServerlessTenantEndpoint{ - ProviderName: pointer.MakePtr("AWS"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - expected: false, - }, - "should return false when a Azure private endpoint is in RESERVED state but miss endpoint ID": { - spe: akov2.ServerlessPrivateEndpoint{ - PrivateEndpointIPAddress: "some-ip-address", - }, - atlas: admin.ServerlessTenantEndpoint{ - ProviderName: pointer.MakePtr("AZURE"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - expected: false, - }, - "should return false when a Azure private endpoint is in RESERVED state but miss IP address": { - spe: akov2.ServerlessPrivateEndpoint{ - CloudProviderEndpointID: "azure-endpoint-id", - }, - atlas: admin.ServerlessTenantEndpoint{ - ProviderName: pointer.MakePtr("AZURE"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - expected: false, - }, - "should return true when a Azure private endpoint is in RESERVED state and has connection data": { - spe: akov2.ServerlessPrivateEndpoint{ - CloudProviderEndpointID: "azure-endpoint-id", - PrivateEndpointIPAddress: "some-ip-address", - }, - atlas: admin.ServerlessTenantEndpoint{ - ProviderName: pointer.MakePtr("AZURE"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - expected: true, - }, - "should return true when a AWS private endpoint is in RESERVED state and has connection data": { - spe: akov2.ServerlessPrivateEndpoint{ - CloudProviderEndpointID: "aws-endpoint-id", - }, - atlas: admin.ServerlessTenantEndpoint{ - ProviderName: pointer.MakePtr("AWS"), - Status: pointer.MakePtr(SPEStatusReserved), - }, - expected: true, - }, - } - - for desc, val := range data { - t.Run(desc, func(t *testing.T) { - spe := val.spe - atlas := val.atlas - assert.Equal(t, val.expected, isReadyToConnect(&spe, &atlas)) - }) - } -} - -func TestCheckStatuses(t *testing.T) { - data := map[string]struct { - spes []status.ServerlessPrivateEndpoint - expected bool - }{ - "should return true when nil": { - spes: nil, - expected: true, - }, - "should return true when empty": { - spes: []status.ServerlessPrivateEndpoint{}, - expected: true, - }, - "should return true when all status are available": { - spes: []status.ServerlessPrivateEndpoint{ - { - Status: SPEStatusAvailable, - }, - { - Status: SPEStatusAvailable, - }, - }, - expected: true, - }, - "should return false when all status are not available": { - spes: []status.ServerlessPrivateEndpoint{ - { - Status: SPEStatusReserved, - }, - { - Status: SPEStatusDeleting, - }, - }, - expected: false, - }, - "should return false when at least one status is not available": { - spes: []status.ServerlessPrivateEndpoint{ - { - Status: SPEStatusReserved, - }, - { - Status: SPEStatusAvailable, - }, - { - Status: SPEStatusAvailable, - }, - }, - expected: false, - }, - } - - for desc, val := range data { - t.Run(desc, func(t *testing.T) { - assert.Equal(t, val.expected, areSPEsAvailable(val.spes)) - }) - } -} diff --git a/internal/translation/deployment/conversion.go b/internal/translation/deployment/conversion.go index d87caec586..6a94fb4b4b 100644 --- a/internal/translation/deployment/conversion.go +++ b/internal/translation/deployment/conversion.go @@ -153,64 +153,6 @@ func deprecatedSpecs(specs *akov2.Specs) bool { return false } -type Serverless struct { - *akov2.ServerlessSpec - ProjectID string - State string - MongoDBVersion string - Connection *status.ConnectionStrings - - customResource *akov2.AtlasDeployment -} - -func (s *Serverless) GetName() string { - return s.Name -} - -func (s *Serverless) GetProjectID() string { - return s.ProjectID -} - -func (s *Serverless) GetState() string { - return s.State -} - -func (s *Serverless) GetMongoDBVersion() string { - return s.MongoDBVersion -} - -func (s *Serverless) GetConnection() *status.ConnectionStrings { - return s.Connection -} - -func (s *Serverless) GetReplicaSet() []status.ReplicaSet { - return nil -} - -func (s *Serverless) GetCustomResource() *akov2.AtlasDeployment { - return s.customResource -} - -func (s *Serverless) IsServerless() bool { - return true -} - -func (s *Serverless) IsFlex() bool { - return false -} - -func (s *Serverless) IsTenant() bool { - return false -} - -func (s *Serverless) IsDedicated() bool { - return false -} - -func (s *Serverless) Notifications() (bool, string, string) { - return true, NOTIFICATION_REASON_DEPRECATION, "WARNING: Serverless is deprecated. See https://dochub.mongodb.org/core/atlas-flex-migration for details." -} - type Flex struct { *akov2.FlexSpec ProjectID string @@ -266,6 +208,10 @@ func (f *Flex) IsDedicated() bool { } func (f *Flex) Notifications() (bool, string, string) { + if f.customResource.IsServerless() { + return true, NOTIFICATION_REASON_DEPRECATION, "WARNING: Serverless is deprecated. See https://dochub.mongodb.org/core/atlas-flex-migration for details." + } + return false, "", "" } @@ -294,14 +240,14 @@ type Endpoint struct { func NewDeployment(projectID string, atlasDeployment *akov2.AtlasDeployment) Deployment { if atlasDeployment.IsServerless() { - serverless := &Serverless{ + flex := &Flex{ customResource: atlasDeployment, ProjectID: projectID, - ServerlessSpec: atlasDeployment.Spec.ServerlessSpec.DeepCopy(), + FlexSpec: serverlessToFlexSpec(atlasDeployment.Spec.ServerlessSpec.DeepCopy()), } - normalizeServerlessDeployment(serverless) + normalizeFlexDeployment(flex) - return serverless + return flex } if atlasDeployment.IsFlex() { @@ -325,15 +271,19 @@ func NewDeployment(projectID string, atlasDeployment *akov2.AtlasDeployment) Dep return cluster } -func normalizeServerlessDeployment(serverless *Serverless) { - serverless.ServerlessSpec.PrivateEndpoints = nil - if serverless.ServerlessSpec.Tags == nil { - serverless.ServerlessSpec.Tags = []*akov2.TagSpec{} +func serverlessToFlexSpec(serverless *akov2.ServerlessSpec) *akov2.FlexSpec { + settings := &akov2.FlexProviderSettings{} + if serverless.ProviderSettings != nil { + settings.BackingProviderName = serverless.ProviderSettings.BackingProviderName + settings.RegionName = serverless.ProviderSettings.RegionName } - cmp.NormalizeSlice(serverless.Tags, func(a, b *akov2.TagSpec) int { - return strings.Compare(a.Key, b.Key) - }) + return &akov2.FlexSpec{ + Name: serverless.Name, + Tags: serverless.Tags, + TerminationProtectionEnabled: serverless.TerminationProtectionEnabled, + ProviderSettings: settings, + } } func normalizeFlexDeployment(flex *Flex) { @@ -997,85 +947,6 @@ func processArgsToAtlas(config *akov2.ProcessArgs) (*admin.ClusterDescriptionPro }, nil } -func serverlessFromAtlas(instance *admin.ServerlessInstanceDescription) *Serverless { - providerSettings := instance.GetProviderSettings() - serverlessBackupOptions := instance.GetServerlessBackupOptions() - connectionStrings := instance.GetConnectionStrings() - - pes := make([]status.PrivateEndpoint, 0, len(connectionStrings.GetPrivateEndpoint())) - for _, pe := range connectionStrings.GetPrivateEndpoint() { - eps := make([]status.Endpoint, 0, len(pe.GetEndpoints())) - for _, ep := range pe.GetEndpoints() { - eps = append( - eps, - status.Endpoint{ - EndpointID: ep.GetEndpointId(), - ProviderName: ep.GetProviderName(), - Region: ep.GetRegion(), - }) - } - - pes = append( - pes, - status.PrivateEndpoint{ - SRVConnectionString: pe.GetSrvConnectionString(), - Endpoints: eps, - }) - } - - s := &Serverless{ - ProjectID: instance.GetGroupId(), - ServerlessSpec: &akov2.ServerlessSpec{ - Name: instance.GetName(), - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: provider.ProviderName(providerSettings.GetProviderName()), - BackingProviderName: providerSettings.GetBackingProviderName(), - RegionName: providerSettings.GetRegionName(), - }, - Tags: tag.FromAtlas(instance.GetTags()), - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: serverlessBackupOptions.GetServerlessContinuousBackupEnabled(), - }, - TerminationProtectionEnabled: instance.GetTerminationProtectionEnabled(), - }, - State: instance.GetStateName(), - MongoDBVersion: instance.GetMongoDBVersion(), - Connection: &status.ConnectionStrings{ - StandardSrv: connectionStrings.GetStandardSrv(), - PrivateEndpoint: pes, - }, - } - normalizeServerlessDeployment(s) - - return s -} - -func serverlessCreateToAtlas(serverless *Serverless) *admin.ServerlessInstanceDescriptionCreate { - return &admin.ServerlessInstanceDescriptionCreate{ - Name: serverless.Name, - ProviderSettings: admin.ServerlessProviderSettings{ - ProviderName: pointer.MakePtr(string(serverless.ProviderSettings.ProviderName)), - BackingProviderName: serverless.ProviderSettings.BackingProviderName, - RegionName: serverless.ProviderSettings.RegionName, - }, - ServerlessBackupOptions: &admin.ClusterServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: &serverless.BackupOptions.ServerlessContinuousBackupEnabled, - }, - Tags: tag.ToAtlas(serverless.Tags), - TerminationProtectionEnabled: &serverless.TerminationProtectionEnabled, - } -} - -func serverlessUpdateToAtlas(serverless *Serverless) *admin.ServerlessInstanceDescriptionUpdate { - return &admin.ServerlessInstanceDescriptionUpdate{ - ServerlessBackupOptions: &admin.ClusterServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: &serverless.BackupOptions.ServerlessContinuousBackupEnabled, - }, - Tags: tag.ToAtlas(serverless.Tags), - TerminationProtectionEnabled: &serverless.TerminationProtectionEnabled, - } -} - func clustersToConnections(clusters []admin.ClusterDescription20240805) []Connection { conns := []Connection{} for _, c := range clusters { @@ -1104,30 +975,6 @@ func fillClusterPrivateEndpoints(cpeList []admin.ClusterDescriptionConnectionStr return pes } -func serverlessToConnections(serverless []admin.ServerlessInstanceDescription) []Connection { - conns := []Connection{} - for _, s := range serverless { - conns = append(conns, Connection{ - Name: s.GetName(), - ConnURL: "", - SrvConnURL: s.ConnectionStrings.GetStandardSrv(), - Serverless: true, - PrivateEndpoints: fillServerlessPrivateEndpoints(s.ConnectionStrings.GetPrivateEndpoint()), - }) - } - return conns -} - -func fillServerlessPrivateEndpoints(cpeList []admin.ServerlessConnectionStringsPrivateEndpointList) []PrivateEndpoint { - pes := []PrivateEndpoint{} - for _, cpe := range cpeList { - pes = append(pes, PrivateEndpoint{ - ServerURL: cpe.GetSrvConnectionString(), - }) - } - return pes -} - func flexToConnections(flex []admin.FlexClusterDescription20241113) []Connection { conns := []Connection{} for _, f := range flex { diff --git a/internal/translation/deployment/conversion_test.go b/internal/translation/deployment/conversion_test.go index bdfd4eb1e2..316b67e9b9 100644 --- a/internal/translation/deployment/conversion_test.go +++ b/internal/translation/deployment/conversion_test.go @@ -33,7 +33,7 @@ func TestNewDeployment(t *testing.T) { cr *akov2.AtlasDeployment expected Deployment }{ - "should create a new serverless deployment": { + "should create a new serverless deployment as flex": { cr: &akov2.AtlasDeployment{ Spec: akov2.AtlasDeploymentSpec{ ServerlessSpec: &akov2.ServerlessSpec{ @@ -62,11 +62,10 @@ func TestNewDeployment(t *testing.T) { }, }, }, - expected: &Serverless{ - ServerlessSpec: &akov2.ServerlessSpec{ + expected: &Flex{ + FlexSpec: &akov2.FlexSpec{ Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", + ProviderSettings: &akov2.FlexProviderSettings{ BackingProviderName: "AWS", RegionName: "US_EAST_1", }, @@ -76,9 +75,6 @@ func TestNewDeployment(t *testing.T) { Value: "test", }, }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, TerminationProtectionEnabled: true, }, ProjectID: "project-id", @@ -303,103 +299,6 @@ func TestNewDeployment(t *testing.T) { } } -func TestNormalizeServerlessDeployment(t *testing.T) { - tests := map[string]struct { - deployment *Serverless - expected *Serverless - }{ - "normalize deployment without tags": { - deployment: &Serverless{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - expected: &Serverless{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - Tags: []*akov2.TagSpec{}, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - "normalize deployment with tags": { - deployment: &Serverless{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "b", - Value: "b", - }, - { - Key: "a", - Value: "a", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - expected: &Serverless{ - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - Tags: []*akov2.TagSpec{ - { - Key: "a", - Value: "a", - }, - { - Key: "b", - Value: "b", - }, - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - normalizeServerlessDeployment(tt.deployment) - - assert.Equal(t, tt.expected, tt.deployment) - }) - } -} - func TestNormalizeClusterDeployment(t *testing.T) { tests := map[string]struct { deployment *Cluster @@ -1168,8 +1067,8 @@ func TestIsType(t *testing.T) { ServerlessSpec: &akov2.ServerlessSpec{}, }, }, - wantServerless: true, - wantFlex: false, + wantServerless: false, + wantFlex: true, wantTenant: false, wantDedicated: false, }, diff --git a/internal/translation/deployment/deployment.go b/internal/translation/deployment/deployment.go index 804083079b..2f9bfbc3ba 100644 --- a/internal/translation/deployment/deployment.go +++ b/internal/translation/deployment/deployment.go @@ -58,14 +58,13 @@ type GlobalClusterService interface { type ProductionAtlasDeployments struct { clustersAPI admin.ClustersApi - serverlessAPI admin.ServerlessInstancesApi flexAPI admin.FlexClustersApi globalClusterAPI admin.GlobalClustersApi isGov bool } -func NewAtlasDeployments(clusterService admin.ClustersApi, serverlessAPI admin.ServerlessInstancesApi, globalClusterAPI admin.GlobalClustersApi, flexAPI admin.FlexClustersApi, isGov bool) *ProductionAtlasDeployments { - return &ProductionAtlasDeployments{clustersAPI: clusterService, serverlessAPI: serverlessAPI, globalClusterAPI: globalClusterAPI, flexAPI: flexAPI, isGov: isGov} +func NewAtlasDeployments(clusterService admin.ClustersApi, globalClusterAPI admin.GlobalClustersApi, flexAPI admin.FlexClustersApi, isGov bool) *ProductionAtlasDeployments { + return &ProductionAtlasDeployments{clustersAPI: clusterService, globalClusterAPI: globalClusterAPI, flexAPI: flexAPI, isGov: isGov} } func (ds *ProductionAtlasDeployments) ListDeploymentNames(ctx context.Context, projectID string) ([]string, error) { @@ -96,16 +95,6 @@ func (ds *ProductionAtlasDeployments) ListDeploymentNames(ctx context.Context, p } } - serverless, _, err := ds.serverlessAPI.ListServerlessInstances(ctx, projectID).Execute() - if err != nil { - return nil, fmt.Errorf("failed to list serverless deployments for project %s: %w", projectID, err) - } - for _, d := range serverless.GetResults() { - name := pointer.GetOrDefault(d.Name, "") - if name != "" { - deploymentNames = append(deploymentNames, name) - } - } return deploymentNames, nil } @@ -126,13 +115,7 @@ func (ds *ProductionAtlasDeployments) ListDeploymentConnections(ctx context.Cont } flexConns := flexToConnections(flex.GetResults()) - serverless, _, serverlessErr := ds.serverlessAPI.ListServerlessInstances(ctx, projectID).Execute() - if serverlessErr != nil { - return nil, fmt.Errorf("failed to list serverless deployments for project %s: %w", projectID, err) - } - serverlessConns := serverlessToConnections(serverless.GetResults()) - - return connectionSet(clusterConns, serverlessConns, flexConns), nil + return connectionSet(clusterConns, flexConns), nil } func (ds *ProductionAtlasDeployments) ClusterExists(ctx context.Context, projectID, name string) (bool, error) { @@ -152,14 +135,6 @@ func (ds *ProductionAtlasDeployments) ClusterExists(ctx context.Context, project return true, nil } - serverless, err := ds.GetServerless(ctx, projectID, name) - if !admin.IsErrorCode(err, atlas.ClusterInstanceFromServerlessAPI) && err != nil { - return false, err - } - if serverless != nil { - return true, nil - } - return false, nil } @@ -202,36 +177,11 @@ func (ds *ProductionAtlasDeployments) GetCluster(ctx context.Context, projectID, return nil, nil } -func (ds *ProductionAtlasDeployments) GetServerless(ctx context.Context, projectID, name string) (*Serverless, error) { - if ds.isGov { - return nil, nil - } - - serverless, _, err := ds.serverlessAPI.GetServerlessInstance(ctx, projectID, name).Execute() - if err == nil { - return serverlessFromAtlas(serverless), err - } - - if !admin.IsErrorCode(err, atlas.ServerlessInstanceNotFound) && !admin.IsErrorCode(err, atlas.ProviderUnsupported) { - return nil, err - } - - return nil, nil -} - func (ds *ProductionAtlasDeployments) GetDeployment(ctx context.Context, projectID string, deployment *akov2.AtlasDeployment) (Deployment, error) { if deployment == nil { return nil, errors.New("deployment is nil") } - serverless, err := ds.GetServerless(ctx, projectID, deployment.GetDeploymentName()) - if !admin.IsErrorCode(err, atlas.ClusterInstanceFromServerlessAPI) && err != nil { - return nil, err - } - if serverless != nil { - return serverless, nil - } - cluster, err := ds.GetCluster(ctx, projectID, deployment.GetDeploymentName()) if !admin.IsErrorCode(err, atlas.ServerlessInstanceFromClusterAPI) && !admin.IsErrorCode(err, atlas.FlexFromClusterAPI) && err != nil { return nil, err @@ -261,13 +211,6 @@ func (ds *ProductionAtlasDeployments) CreateDeployment(ctx context.Context, depl } return clusterFromAtlas(cluster), nil - case *Serverless: - serverless, _, err := ds.serverlessAPI.CreateServerlessInstance(ctx, deployment.GetProjectID(), serverlessCreateToAtlas(d)).Execute() - if err != nil { - return nil, err - } - - return serverlessFromAtlas(serverless), nil case *Flex: flex, _, err := ds.flexAPI.CreateFlexCluster(ctx, deployment.GetProjectID(), flexCreateToAtlas(d)).Execute() if err != nil { @@ -288,13 +231,6 @@ func (ds *ProductionAtlasDeployments) UpdateDeployment(ctx context.Context, depl } return clusterFromAtlas(cluster), nil - case *Serverless: - serverless, _, err := ds.serverlessAPI.UpdateServerlessInstance(ctx, deployment.GetProjectID(), deployment.GetName(), serverlessUpdateToAtlas(d)).Execute() - if err != nil { - return nil, err - } - - return serverlessFromAtlas(serverless), nil case *Flex: flex, _, err := ds.flexAPI.UpdateFlexCluster(ctx, deployment.GetProjectID(), deployment.GetName(), flexUpdateToAtlas(d)).Execute() if err != nil { @@ -313,8 +249,6 @@ func (ds *ProductionAtlasDeployments) DeleteDeployment(ctx context.Context, depl switch deployment.(type) { case *Cluster: _, err = ds.clustersAPI.DeleteCluster(ctx, deployment.GetProjectID(), deployment.GetName()).Execute() - case *Serverless: - _, _, err = ds.serverlessAPI.DeleteServerlessInstance(ctx, deployment.GetProjectID(), deployment.GetName()).Execute() case *Flex: _, err = ds.flexAPI.DeleteFlexCluster(ctx, deployment.GetProjectID(), deployment.GetName()).Execute() } @@ -334,8 +268,6 @@ func (ds *ProductionAtlasDeployments) UpgradeToDedicated(ctx context.Context, cu switch currentDeployment.(type) { case *Cluster: return nil, errors.New("upgrade from shared to dedicated is not supported") - case *Serverless: - return nil, errors.New("upgrade from serverless to dedicated is not supported") case *Flex: d := targetDeployment.(*Cluster) flex, _, err := ds.flexAPI.UpgradeFlexCluster(ctx, targetDeployment.GetProjectID(), flexUpgradeToAtlas(d)).Execute() diff --git a/internal/translation/deployment/deployment_test.go b/internal/translation/deployment/deployment_test.go index 5939a2bcb5..a67fd4b2aa 100644 --- a/internal/translation/deployment/deployment_test.go +++ b/internal/translation/deployment/deployment_test.go @@ -44,15 +44,12 @@ func TestProductionAtlasDeployments_ListDeploymentConnections(t *testing.T) { mockClustersAPI.EXPECT().ListClustersExecute(admin.ListClustersApiRequest{ApiService: mockClustersAPI}).Return( nil, &http.Response{StatusCode: http.StatusOK}, nil) - mockServerlessAPI := mockadmin.NewServerlessInstancesApi(t) - mockServerlessAPI.EXPECT().ListServerlessInstancesExecute(mock.Anything).Unset() mockFlexAPI := mockadmin.NewFlexClustersApi(t) mockFlexAPI.EXPECT().ListFlexClustersExecute(mock.Anything).Unset() ds := &ProductionAtlasDeployments{ - clustersAPI: mockClustersAPI, - serverlessAPI: mockServerlessAPI, - flexAPI: mockFlexAPI, - isGov: true, + clustersAPI: mockClustersAPI, + flexAPI: mockFlexAPI, + isGov: true, } projectID := "testProjectID" _, err := ds.ListDeploymentConnections(context.Background(), projectID) @@ -66,13 +63,6 @@ func TestProductionAtlasDeployments_ListDeploymentConnections(t *testing.T) { mockClustersAPI.EXPECT().ListClustersExecute(admin.ListClustersApiRequest{ApiService: mockClustersAPI}).Return( nil, &http.Response{StatusCode: http.StatusOK}, nil) - mockServerlessAPI := mockadmin.NewServerlessInstancesApi(t) - mockServerlessAPI.EXPECT().ListServerlessInstances(context.Background(), mock.Anything).Return( - admin.ListServerlessInstancesApiRequest{ApiService: mockServerlessAPI}) - mockServerlessAPI.EXPECT().ListServerlessInstancesExecute( - admin.ListServerlessInstancesApiRequest{ApiService: mockServerlessAPI}).Return( - nil, &http.Response{StatusCode: http.StatusOK}, nil) - mockFlexAPI := mockadmin.NewFlexClustersApi(t) mockFlexAPI.EXPECT().ListFlexClusters(context.Background(), mock.Anything).Return( admin.ListFlexClustersApiRequest{ApiService: mockFlexAPI}) @@ -81,10 +71,9 @@ func TestProductionAtlasDeployments_ListDeploymentConnections(t *testing.T) { nil, &http.Response{StatusCode: http.StatusOK}, nil) ds := &ProductionAtlasDeployments{ - clustersAPI: mockClustersAPI, - serverlessAPI: mockServerlessAPI, - flexAPI: mockFlexAPI, - isGov: false, + clustersAPI: mockClustersAPI, + flexAPI: mockFlexAPI, + isGov: false, } projectID := "testProjectID" _, err := ds.ListDeploymentConnections(context.Background(), projectID) @@ -105,20 +94,6 @@ func TestProductionAtlasDeployments_ListDeploymentConnections(t *testing.T) { }, }, &http.Response{StatusCode: http.StatusOK}, nil) - mockServerlessAPI := mockadmin.NewServerlessInstancesApi(t) - mockServerlessAPI.EXPECT().ListServerlessInstances(context.Background(), mock.Anything).Return( - admin.ListServerlessInstancesApiRequest{ApiService: mockServerlessAPI}) - mockServerlessAPI.EXPECT().ListServerlessInstancesExecute( - admin.ListServerlessInstancesApiRequest{ApiService: mockServerlessAPI}).Return( - &admin.PaginatedServerlessInstanceDescription{ - Results: &[]admin.ServerlessInstanceDescription{ - { - Name: pointer.MakePtr("testServerless"), - ConnectionStrings: &admin.ServerlessInstanceDescriptionConnectionStrings{StandardSrv: pointer.MakePtr("serverlessSRV")}, - }, - }, - }, &http.Response{StatusCode: http.StatusOK}, nil) - mockFlexAPI := mockadmin.NewFlexClustersApi(t) mockFlexAPI.EXPECT().ListFlexClusters(context.Background(), mock.Anything).Return( admin.ListFlexClustersApiRequest{ApiService: mockFlexAPI}) @@ -134,29 +109,28 @@ func TestProductionAtlasDeployments_ListDeploymentConnections(t *testing.T) { }, &http.Response{StatusCode: http.StatusOK}, nil) ds := &ProductionAtlasDeployments{ - clustersAPI: mockClustersAPI, - serverlessAPI: mockServerlessAPI, - flexAPI: mockFlexAPI, - isGov: false, + clustersAPI: mockClustersAPI, + flexAPI: mockFlexAPI, + isGov: false, } projectID := "testProjectID" conns, err := ds.ListDeploymentConnections(context.Background(), projectID) assert.Nil(t, err) - assert.Equal(t, len(conns), 3) + assert.Equal(t, len(conns), 2) }) } func TestClusterExists(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) gov bool result bool err error }{ "should fail to assert a cluster exists in atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) @@ -164,54 +138,34 @@ func TestClusterExists(t *testing.T) { Return(nil, nil, errors.New("failed to get cluster from atlas")) clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to get cluster from atlas"), }, "should fail to assert a serverless instance exists in atlas": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). - Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) - clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). - Return(nil, nil, atlasAPIError(atlas.ClusterNotFound)) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return(nil, nil, errors.New("failed to get serverless instance from atlas")) - - err := &admin.GenericOpenAPIError{} - err.SetModel(admin.ApiError{ErrorCode: atlas.ClusterNotFound}) flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(context.Background(), "project-id", "instance0"). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). - Return(nil, nil, err) + Return(nil, nil, errors.New("failed to get serverless instance from atlas")) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to get serverless instance from atlas"), }, "should return false when cluster doesn't exist": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, atlasAPIError(atlas.ClusterNotFound)) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "cluster0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return(nil, nil, atlasAPIError(atlas.ProviderUnsupported)) - err := &admin.GenericOpenAPIError{} err.SetModel(admin.ApiError{ErrorCode: atlas.NonFlexInFlexAPI}) @@ -221,39 +175,30 @@ func TestClusterExists(t *testing.T) { flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). Return(nil, nil, err) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, }, "should return false when serverless instance doesn't exist": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceFromClusterAPI)) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceNotFound)) - - err := &admin.GenericOpenAPIError{} - err.SetModel(admin.ApiError{ErrorCode: atlas.NonFlexInFlexAPI}) - flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(context.Background(), "project-id", "instance0"). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). - Return(nil, nil, err) + Return(nil, nil, atlasAPIError(atlas.ClusterNotFound)) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, }, "should return a cluster exists": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -264,8 +209,6 @@ func TestClusterExists(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - err := &admin.GenericOpenAPIError{} err.SetModel(admin.ApiError{ErrorCode: atlas.NonFlexInFlexAPI}) @@ -275,55 +218,37 @@ func TestClusterExists(t *testing.T) { flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). Return(nil, &http.Response{}, err) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: true, }, "should return a serverless instance exists": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). - Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) - clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). - Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceFromClusterAPI)) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return( - atlasServerlessInstance(), - nil, - nil, - ) - - err := &admin.GenericOpenAPIError{} - err.SetModel(admin.ApiError{ErrorCode: atlas.NonFlexInFlexAPI}) flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(context.Background(), "project-id", "instance0"). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). - Return(nil, nil, err) + Return(&admin.FlexClusterDescription20241113{}, nil, nil) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: true, }, "should return false when asserting serverless instance exists in gov": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceFromClusterAPI)) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, gov: true, result: false, @@ -332,8 +257,8 @@ func TestClusterExists(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, tt.gov) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, tt.gov) result, err := service.ClusterExists(context.Background(), "project-id", tt.deployment.GetDeploymentName()) require.Equal(t, tt.err, err) @@ -345,72 +270,64 @@ func TestClusterExists(t *testing.T) { func TestGetDeployment(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to retrieve cluster from atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, errors.New("failed to get cluster from atlas")) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "project-id", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.ClusterInstanceFromServerlessAPI)) - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to get cluster from atlas"), }, "should fail to retrieve serverless instance from atlas": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return(nil, nil, errors.New("failed to get serverless instance from atlas")) + clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). + Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) + clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). + Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceFromClusterAPI)) flexAPI := mockadmin.NewFlexClustersApi(t) + flexAPI.EXPECT().GetFlexCluster(context.Background(), "project-id", "instance0"). + Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().GetFlexClusterExecute(mock.AnythingOfType("admin.GetFlexClusterApiRequest")). + Return(nil, nil, errors.New("failed to get serverless instance from atlas")) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to get serverless instance from atlas"), }, "should return nil when cluster doesn't exist": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterExecute(mock.AnythingOfType("admin.GetClusterApiRequest")). Return(nil, nil, atlasAPIError(atlas.ClusterNotFound)) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "project-id", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.ClusterInstanceFromServerlessAPI)) - flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(mock.Anything, "project-id", mock.Anything). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) flexAPI.EXPECT().GetFlexClusterExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.NonFlexInFlexAPI)) - return clusterAPI, serverlessAPI, flexAPI + return clusterAPI, flexAPI }, }, "should return nil when serverless instance doesn't exist": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "instance0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -420,20 +337,14 @@ func TestGetDeployment(t *testing.T) { flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().GetFlexCluster(mock.Anything, "project-id", mock.Anything). Return(admin.GetFlexClusterApiRequest{ApiService: flexAPI}) - flexAPI.EXPECT().GetFlexClusterExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.NonFlexInFlexAPI)) + flexAPI.EXPECT().GetFlexClusterExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.ClusterNotFound)) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return(nil, nil, atlasAPIError(atlas.ServerlessInstanceNotFound)) - - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, }, "should return a cluster": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetCluster(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterApiRequest{ApiService: clusterAPI}) @@ -444,44 +355,18 @@ func TestGetDeployment(t *testing.T) { nil, ) - serverlessAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessAPI.EXPECT().GetServerlessInstance(mock.Anything, "project-id", mock.Anything). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessAPI}) - serverlessAPI.EXPECT().GetServerlessInstanceExecute(mock.Anything).Return(nil, nil, atlasAPIError(atlas.ClusterInstanceFromServerlessAPI)) - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessAPI, flexAPI + return clusterAPI, flexAPI }, result: expectedGeoShardedCluster(), }, - "should return a serverless instance": { - deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { - clusterAPI := mockadmin.NewClustersApi(t) - - flexAPI := mockadmin.NewFlexClustersApi(t) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().GetServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.GetServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().GetServerlessInstanceExecute(mock.AnythingOfType("admin.GetServerlessInstanceApiRequest")). - Return( - atlasServerlessInstance(), - nil, - nil, - ) - - return clusterAPI, serverlessInstanceAPI, flexAPI - }, - result: expectedServerlessInstance(), - }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) result, err := service.GetDeployment(context.Background(), "project-id", tt.deployment) require.Equal(t, tt.err, err) @@ -493,44 +378,42 @@ func TestGetDeployment(t *testing.T) { func TestCreateDeployment(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to create cluster in atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().CreateCluster(context.Background(), "project-id", mock.AnythingOfType("*admin.ClusterDescription20240805")). Return(admin.CreateClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().CreateClusterExecute(mock.AnythingOfType("admin.CreateClusterApiRequest")). Return(nil, nil, errors.New("failed to create cluster in atlas")) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to create cluster in atlas"), }, - "should fail to create serverless instance in atlas": { + "should fail to create flex cluster in atlas": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().CreateServerlessInstance(context.Background(), "project-id", mock.AnythingOfType("*admin.ServerlessInstanceDescriptionCreate")). - Return(admin.CreateServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().CreateServerlessInstanceExecute(mock.AnythingOfType("admin.CreateServerlessInstanceApiRequest")). - Return(nil, nil, errors.New("failed to create serverless instance in atlas")) - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + flexAPI.EXPECT().CreateFlexCluster(context.Background(), "project-id", mock.AnythingOfType("*admin.FlexClusterDescriptionCreate20241113")). + Return(admin.CreateFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().CreateFlexClusterExecute(mock.AnythingOfType("admin.CreateFlexClusterApiRequest")). + Return(nil, nil, errors.New("failed to create flex cluster in atlas")) + + return clusterAPI, flexAPI }, - err: errors.New("failed to create serverless instance in atlas"), + err: errors.New("failed to create flex cluster in atlas"), }, "should create a cluster": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().CreateCluster(context.Background(), "project-id", mock.AnythingOfType("*admin.ClusterDescription20240805")). Return(admin.CreateClusterApiRequest{ApiService: clusterAPI}) @@ -541,39 +424,18 @@ func TestCreateDeployment(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: expectedGeoShardedCluster(), }, - "should create a serverless instance": { - deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { - clusterAPI := mockadmin.NewClustersApi(t) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().CreateServerlessInstance(context.Background(), "project-id", mock.AnythingOfType("*admin.ServerlessInstanceDescriptionCreate")). - Return(admin.CreateServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().CreateServerlessInstanceExecute(mock.AnythingOfType("admin.CreateServerlessInstanceApiRequest")). - Return( - atlasServerlessInstance(), - nil, - nil, - ) - - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI - }, - result: expectedServerlessInstance(), - }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) result, err := service.CreateDeployment(context.Background(), NewDeployment("project-id", tt.deployment)) require.Equal(t, tt.err, err) @@ -585,44 +447,42 @@ func TestCreateDeployment(t *testing.T) { func TestUpdateDeployment(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to update cluster in atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().UpdateCluster(context.Background(), "project-id", "cluster0", mock.AnythingOfType("*admin.ClusterDescription20240805")). Return(admin.UpdateClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().UpdateClusterExecute(mock.AnythingOfType("admin.UpdateClusterApiRequest")). Return(nil, nil, errors.New("failed to update cluster in atlas")) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to update cluster in atlas"), }, - "should fail to update serverless instance in atlas": { + "should fail to update flex cluster in atlas": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().UpdateServerlessInstance(context.Background(), "project-id", "instance0", mock.AnythingOfType("*admin.ServerlessInstanceDescriptionUpdate")). - Return(admin.UpdateServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().UpdateServerlessInstanceExecute(mock.AnythingOfType("admin.UpdateServerlessInstanceApiRequest")). - Return(nil, nil, errors.New("failed to update serverless instance in atlas")) - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + flexAPI.EXPECT().UpdateFlexCluster(context.Background(), "project-id", "instance0", mock.AnythingOfType("*admin.FlexClusterDescriptionUpdate20241113")). + Return(admin.UpdateFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().UpdateFlexClusterExecute(mock.AnythingOfType("admin.UpdateFlexClusterApiRequest")). + Return(nil, nil, errors.New("failed to update flex cluster in atlas")) + + return clusterAPI, flexAPI }, - err: errors.New("failed to update serverless instance in atlas"), + err: errors.New("failed to update flex cluster in atlas"), }, "should update a cluster": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().UpdateCluster(context.Background(), "project-id", "cluster0", mock.AnythingOfType("*admin.ClusterDescription20240805")). Return(admin.UpdateClusterApiRequest{ApiService: clusterAPI}) @@ -633,39 +493,17 @@ func TestUpdateDeployment(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: expectedGeoShardedCluster(), }, - "should update a serverless instance": { - deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { - clusterAPI := mockadmin.NewClustersApi(t) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().UpdateServerlessInstance(context.Background(), "project-id", "instance0", mock.AnythingOfType("*admin.ServerlessInstanceDescriptionUpdate")). - Return(admin.UpdateServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().UpdateServerlessInstanceExecute(mock.AnythingOfType("admin.UpdateServerlessInstanceApiRequest")). - Return( - atlasServerlessInstance(), - nil, - nil, - ) - - flexAPI := mockadmin.NewFlexClustersApi(t) - - return clusterAPI, serverlessInstanceAPI, flexAPI - }, - result: expectedServerlessInstance(), - }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) result, err := service.UpdateDeployment(context.Background(), NewDeployment("project-id", tt.deployment)) require.Equal(t, tt.err, err) @@ -677,82 +515,61 @@ func TestUpdateDeployment(t *testing.T) { func TestDeleteDeployment(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to delete cluster in atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().DeleteCluster(context.Background(), "project-id", "cluster0"). Return(admin.DeleteClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().DeleteClusterExecute(mock.AnythingOfType("admin.DeleteClusterApiRequest")). Return(nil, errors.New("failed to delete cluster in atlas")) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to delete cluster in atlas"), }, - "should fail to delete serverless instance in atlas": { + "should fail to delete flex cluster in atlas": { deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().DeleteServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.DeleteServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().DeleteServerlessInstanceExecute(mock.AnythingOfType("admin.DeleteServerlessInstanceApiRequest")). - Return(nil, nil, errors.New("failed to delete serverless instance in atlas")) - flexAPI := mockadmin.NewFlexClustersApi(t) + flexAPI.EXPECT().DeleteFlexCluster(context.Background(), "project-id", "instance0"). + Return(admin.DeleteFlexClusterApiRequest{ApiService: flexAPI}) + flexAPI.EXPECT().DeleteFlexClusterExecute(mock.AnythingOfType("admin.DeleteFlexClusterApiRequest")). + Return(nil, errors.New("failed to delete flex cluster in atlas")) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, - err: errors.New("failed to delete serverless instance in atlas"), + err: errors.New("failed to delete flex cluster in atlas"), }, "should delete a cluster": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().DeleteCluster(context.Background(), "project-id", "cluster0"). Return(admin.DeleteClusterApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().DeleteClusterExecute(mock.AnythingOfType("admin.DeleteClusterApiRequest")). Return(nil, nil) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: expectedGeoShardedCluster(), }, - "should delete a serverless instance": { - deployment: serverlessInstance(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { - clusterAPI := mockadmin.NewClustersApi(t) - - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - serverlessInstanceAPI.EXPECT().DeleteServerlessInstance(context.Background(), "project-id", "instance0"). - Return(admin.DeleteServerlessInstanceApiRequest{ApiService: serverlessInstanceAPI}) - serverlessInstanceAPI.EXPECT().DeleteServerlessInstanceExecute(mock.AnythingOfType("admin.DeleteServerlessInstanceApiRequest")). - Return(nil, nil, nil) - - flexAPI := mockadmin.NewFlexClustersApi(t) - - return clusterAPI, serverlessInstanceAPI, flexAPI - }, - result: expectedServerlessInstance(), - }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) err := service.DeleteDeployment(context.Background(), NewDeployment("project-id", tt.deployment)) require.Equal(t, tt.err, err) @@ -763,29 +580,28 @@ func TestDeleteDeployment(t *testing.T) { func TestClusterWithProcessArgs(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to retrieve cluster process args from atlas": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetClusterAdvancedConfiguration(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterAdvancedConfigurationApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().GetClusterAdvancedConfigurationExecute(mock.AnythingOfType("admin.GetClusterAdvancedConfigurationApiRequest")). Return(nil, nil, errors.New("failed to get cluster process args from atlas")) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to get cluster process args from atlas"), }, "should return process args with default settings": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetClusterAdvancedConfiguration(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterAdvancedConfigurationApiRequest{ApiService: clusterAPI}) @@ -800,10 +616,9 @@ func TestClusterWithProcessArgs(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: &Cluster{ ProcessArgs: &akov2.ProcessArgs{ @@ -815,7 +630,7 @@ func TestClusterWithProcessArgs(t *testing.T) { }, "should return process args": { deployment: geoShardedCluster(), - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().GetClusterAdvancedConfiguration(context.Background(), "project-id", "cluster0"). Return(admin.GetClusterAdvancedConfigurationApiRequest{ApiService: clusterAPI}) @@ -835,10 +650,9 @@ func TestClusterWithProcessArgs(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: &Cluster{ ProcessArgs: &akov2.ProcessArgs{ @@ -857,8 +671,8 @@ func TestClusterWithProcessArgs(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) d := NewDeployment("project-id", tt.deployment) cluster := d.(*Cluster) @@ -874,7 +688,7 @@ func TestClusterWithProcessArgs(t *testing.T) { func TestUpdateProcessArgs(t *testing.T) { tests := map[string]struct { deployment Deployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ @@ -888,12 +702,11 @@ func TestUpdateProcessArgs(t *testing.T) { OplogMinRetentionHours: "wrong", }, }, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: &strconv.NumError{Func: "ParseFloat", Num: "wrong", Err: errors.New("invalid syntax")}, }, @@ -913,17 +726,16 @@ func TestUpdateProcessArgs(t *testing.T) { OplogMinRetentionHours: "12.0", }, }, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().UpdateClusterAdvancedConfiguration(context.Background(), "project-id", "cluster0", mock.AnythingOfType("*admin.ClusterDescriptionProcessArgs20240805")). Return(admin.UpdateClusterAdvancedConfigurationApiRequest{ApiService: clusterAPI}) clusterAPI.EXPECT().UpdateClusterAdvancedConfigurationExecute(mock.AnythingOfType("admin.UpdateClusterAdvancedConfigurationApiRequest")). Return(nil, nil, errors.New("failed to update cluster process args in atlas")) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to update cluster process args in atlas"), }, @@ -943,7 +755,7 @@ func TestUpdateProcessArgs(t *testing.T) { OplogMinRetentionHours: "12.0", }, }, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) clusterAPI.EXPECT().UpdateClusterAdvancedConfiguration(context.Background(), "project-id", "cluster0", mock.AnythingOfType("*admin.ClusterDescriptionProcessArgs20240805")). Return(admin.UpdateClusterAdvancedConfigurationApiRequest{ApiService: clusterAPI}) @@ -963,10 +775,9 @@ func TestUpdateProcessArgs(t *testing.T) { nil, ) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: &Cluster{ ProcessArgs: &akov2.ProcessArgs{ @@ -985,8 +796,8 @@ func TestUpdateProcessArgs(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) cluster := tt.deployment.(*Cluster) err := service.UpdateProcessArgs(context.Background(), cluster) @@ -1002,30 +813,19 @@ func TestUpgradeCluster(t *testing.T) { tests := map[string]struct { currentDeployment Deployment targetDeployment Deployment - apiMocker func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) + apiMocker func() (admin.ClustersApi, admin.FlexClustersApi) result Deployment err error }{ "should fail to upgrade shared cluster in atlas": { currentDeployment: &Cluster{}, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("upgrade from shared to dedicated is not supported"), }, - "should fail to upgrade serverless instance in atlas": { - currentDeployment: &Serverless{}, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { - clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) - flexAPI := mockadmin.NewFlexClustersApi(t) - return clusterAPI, serverlessInstanceAPI, flexAPI - }, - err: errors.New("upgrade from serverless to dedicated is not supported"), - }, "should fail to upgrade flex instance in atlas": { currentDeployment: &Flex{}, targetDeployment: &Cluster{ @@ -1038,15 +838,14 @@ func TestUpgradeCluster(t *testing.T) { }, }, }, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().UpgradeFlexCluster(context.Background(), "project-id", mock.AnythingOfType("*admin.AtlasTenantClusterUpgradeRequest20240805")). Return(admin.UpgradeFlexClusterApiRequest{ApiService: flexAPI}) flexAPI.EXPECT().UpgradeFlexClusterExecute(mock.AnythingOfType("admin.UpgradeFlexClusterApiRequest")). Return(nil, &http.Response{}, errors.New("failed to upgrade flex cluster in atlas")) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, err: errors.New("failed to upgrade flex cluster in atlas"), }, @@ -1062,9 +861,8 @@ func TestUpgradeCluster(t *testing.T) { }, }, }, - apiMocker: func() (admin.ClustersApi, admin.ServerlessInstancesApi, admin.FlexClustersApi) { + apiMocker: func() (admin.ClustersApi, admin.FlexClustersApi) { clusterAPI := mockadmin.NewClustersApi(t) - serverlessInstanceAPI := mockadmin.NewServerlessInstancesApi(t) flexAPI := mockadmin.NewFlexClustersApi(t) flexAPI.EXPECT().UpgradeFlexCluster(context.Background(), "project-id", mock.AnythingOfType("*admin.AtlasTenantClusterUpgradeRequest20240805")). Return(admin.UpgradeFlexClusterApiRequest{ApiService: flexAPI}) @@ -1074,7 +872,7 @@ func TestUpgradeCluster(t *testing.T) { &http.Response{}, nil, ) - return clusterAPI, serverlessInstanceAPI, flexAPI + return clusterAPI, flexAPI }, result: &Flex{ FlexSpec: &akov2.FlexSpec{ @@ -1089,8 +887,8 @@ func TestUpgradeCluster(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - clusterAPI, serverlessInstanceAPI, flexAPI := tt.apiMocker() - service := NewAtlasDeployments(clusterAPI, serverlessInstanceAPI, nil, flexAPI, false) + clusterAPI, flexAPI := tt.apiMocker() + service := NewAtlasDeployments(clusterAPI, nil, flexAPI, false) result, err := service.UpgradeToDedicated(context.Background(), tt.currentDeployment, tt.targetDeployment) require.Equal(t, tt.err, err) @@ -1789,92 +1587,3 @@ func serverlessInstance() *akov2.AtlasDeployment { }, } } - -func expectedServerlessInstance() *Serverless { - return &Serverless{ - ProjectID: "project-id", - ServerlessSpec: &akov2.ServerlessSpec{ - Name: "instance0", - ProviderSettings: &akov2.ServerlessProviderSettingsSpec{ - ProviderName: "SERVERLESS", - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - BackupOptions: akov2.ServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: true, - }, - TerminationProtectionEnabled: true, - Tags: []*akov2.TagSpec{ - { - Key: "A", - Value: "A", - }, - { - Key: "B", - Value: "B", - }, - }, - }, - State: "IDLE", - MongoDBVersion: "7.3.3", - Connection: &status.ConnectionStrings{ - StandardSrv: "standard-str", - PrivateEndpoint: []status.PrivateEndpoint{ - { - SRVConnectionString: "connection-srv-str", - Endpoints: []status.Endpoint{ - { - ProviderName: "AWS", - Region: "US_EAST_1", - EndpointID: "arn-endpoint-id", - }, - }, - }, - }, - }, - } -} - -func atlasServerlessInstance() *admin.ServerlessInstanceDescription { - return &admin.ServerlessInstanceDescription{ - GroupId: pointer.MakePtr("project-id"), - Name: pointer.MakePtr("instance0"), - ProviderSettings: admin.ServerlessProviderSettings{ - ProviderName: pointer.MakePtr("SERVERLESS"), - BackingProviderName: "AWS", - RegionName: "US_EAST_1", - }, - ServerlessBackupOptions: &admin.ClusterServerlessBackupOptions{ - ServerlessContinuousBackupEnabled: pointer.MakePtr(true), - }, - TerminationProtectionEnabled: pointer.MakePtr(true), - Tags: &[]admin.ResourceTag{ - { - Key: "B", - Value: "B", - }, - { - Key: "A", - Value: "A", - }, - }, - StateName: pointer.MakePtr("IDLE"), - MongoDBVersion: pointer.MakePtr("7.3.3"), - ConnectionStrings: &admin.ServerlessInstanceDescriptionConnectionStrings{ - StandardSrv: pointer.MakePtr("standard-str"), - PrivateEndpoint: &[]admin.ServerlessConnectionStringsPrivateEndpointList{ - { - SrvConnectionString: pointer.MakePtr("connection-srv-str"), - Endpoints: &[]admin.ServerlessConnectionStringsPrivateEndpointItem{ - { - ProviderName: pointer.MakePtr("AWS"), - Region: pointer.MakePtr("US_EAST_1"), - EndpointId: pointer.MakePtr("arn-endpoint-id"), - }, - }, - Type: pointer.MakePtr("MONGOS"), - }, - }, - }, - } -} diff --git a/test/int/deployment_test.go b/test/int/deployment_test.go index db44e568be..4dc5d4b1a2 100644 --- a/test/int/deployment_test.go +++ b/test/int/deployment_test.go @@ -79,7 +79,7 @@ var _ = Describe("AtlasDeployment", Label("int", "AtlasDeployment", "deployment- BeforeEach(func() { prepareControllers(false) - deploymentService = deployment.NewAtlasDeployments(atlasClient.ClustersApi, atlasClient.ServerlessInstancesApi, atlasClient.GlobalClustersApi, atlasClient.FlexClustersApi, false) + deploymentService = deployment.NewAtlasDeployments(atlasClient.ClustersApi, atlasClient.GlobalClustersApi, atlasClient.FlexClustersApi, false) createdDeployment = &akov2.AtlasDeployment{} manualDeletion = false @@ -1439,7 +1439,7 @@ var _ = Describe("AtlasDeploymentSharding", Label("int", "AtlasDeploymentShardin BeforeEach(func() { prepareControllers(false) - deployment.NewAtlasDeployments(atlasClient.ClustersApi, atlasClient.ServerlessInstancesApi, atlasClient.GlobalClustersApi, atlasClient.FlexClustersApi, false) + deployment.NewAtlasDeployments(atlasClient.ClustersApi, atlasClient.GlobalClustersApi, atlasClient.FlexClustersApi, false) createdDeployment = &akov2.AtlasDeployment{} manualDeletion = false diff --git a/version.json b/version.json index c44ac30594..2ce490c8e9 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "current": "2.11.1", - "next": "2.11.2" + "next": "2.12.0" }