-
Notifications
You must be signed in to change notification settings - Fork 39
fix: Avoid error in unpause operation of Cluster resource #1469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
73d97e7
16ffc23
b7cc45d
19ae278
ae4126d
1684889
a367cdf
b776377
43a556b
3903fde
f4acd9d
ecddf7b
e6d5b76
f620f31
0c3cc15
d43392a
cfd5afe
a843f04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,9 +120,19 @@ func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler | |
| return updateClusterCallback(client, currentModel, *currentModel.ProjectId) | ||
| } | ||
| currentModel.validateDefaultLabel() | ||
|
|
||
| currentCluster, resp, err := client.Atlas20231115014.ClustersApi.GetCluster(context.Background(), *currentModel.ProjectId, *currentModel.Name).Execute() | ||
| if pe := util.HandleClusterError(err, resp); pe != nil { | ||
| return *pe, nil | ||
| } | ||
|
|
||
| // Unpausing must be handled separately from other updates to avoid errors from the API. | ||
| if pe := handleUnpausingUpdate(client, currentCluster, currentModel); pe != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any need to wait for a stable cluster state? Is it ok we immediately send a different update afterwards? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is not, was able to verify that once a first PATCH is sent doing the unpause, having the cluster in |
||
| return *pe, nil | ||
| } | ||
|
|
||
| adminCluster, errEvent := setClusterRequest(currentModel) | ||
| if len(adminCluster.GetReplicationSpecs()) > 0 { | ||
| currentCluster, _, _ := client.Atlas20231115014.ClustersApi.GetCluster(context.Background(), *currentModel.ProjectId, *currentModel.Name).Execute() | ||
| if currentCluster != nil { | ||
| adminCluster.ReplicationSpecs = AddReplicationSpecIDs(currentCluster.GetReplicationSpecs(), adminCluster.GetReplicationSpecs()) | ||
| } | ||
|
|
@@ -150,6 +160,15 @@ func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler | |
| return event, nil | ||
| } | ||
|
|
||
| func handleUnpausingUpdate(client *util.MongoDBClient, currentCluster *admin20231115014.AdvancedClusterDescription, currentModel *Model) *handler.ProgressEvent { | ||
| if (currentCluster.Paused != nil && *currentCluster.Paused) && (currentModel.Paused == nil || !*currentModel.Paused) { | ||
| _, resp, err := client.Atlas20231115014.ClustersApi.UpdateCluster(context.Background(), *currentModel.ProjectId, *currentModel.Name, | ||
| &admin20231115014.AdvancedClusterDescription{Paused: admin20231115014.PtrBool(false)}).Execute() | ||
| return util.HandleClusterError(err, resp) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Delete handles the Delete event from the Cloudformation service. | ||
| func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { | ||
| client, setupErr := setupRequest(req, currentModel, createReadUpdateDeleteRequiredFields) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| { | ||
| "AWSTemplateFormatVersion": "2010-09-09", | ||
| "Description": "Minimal template to exercise pausing/unpausing a single REPLICASET cluster", | ||
| "Resources": { | ||
| "Cluster": { | ||
| "Type": "{{ .ResourceTypeName }}", | ||
| "Properties": { | ||
| "Name": "{{ .Name }}", | ||
| "ProjectId": "{{ .ProjectID }}", | ||
| "Profile": "{{ .Profile }}", | ||
| "ClusterType": "REPLICASET", | ||
| "Paused": "{{ .Paused }}", | ||
| "ReplicationSpecs": [ | ||
| { | ||
| "NumShards": 1, | ||
| "AdvancedRegionConfigs": [ | ||
| { | ||
| "RegionName": "US_EAST_1", | ||
| "Priority": 7, | ||
| "ProviderName": "AWS", | ||
| "ElectableSpecs": { | ||
| "EbsVolumeType": "STANDARD", | ||
| "InstanceSize": "M10", | ||
| "NodeCount": 3 | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| }, | ||
| "Outputs": { | ||
| "MongoDBAtlasClusterID": { | ||
| "Description": "Cluster Id", | ||
| "Value": { "Fn::GetAtt": ["Cluster", "Id"] } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| // Copyright 2024 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 cluster_test | ||
|
|
||
| import ( | ||
| ctx "context" | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/aws/aws-sdk-go-v2/service/cloudformation" | ||
| "github.com/mongodb/mongodbatlas-cloudformation-resources/test/e2e/utility" | ||
| "github.com/stretchr/testify/assert" | ||
| admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin" | ||
| ) | ||
|
|
||
| type pauseTestContext struct { | ||
| cfnClient *cloudformation.Client | ||
| atlasClient20231115014 *admin20231115014.APIClient | ||
| resourceCtx utility.ResourceContext | ||
| template string | ||
| clusterTmplObj pauseTestCluster | ||
| } | ||
|
|
||
| type pauseTestCluster struct { | ||
| ResourceTypeName string | ||
| Name string | ||
| Profile string | ||
| ProjectID string | ||
| Paused string | ||
| } | ||
|
|
||
| const ( | ||
| pauseCfnTemplatePath = "cluster_pause.json.template" | ||
| ) | ||
|
|
||
| // Replication specs are hardcoded in the CFN template for this test. | ||
|
|
||
| var ( | ||
| pauseProfile = os.Getenv("MONGODB_ATLAS_SECRET_PROFILE") | ||
| pauseOrgID = os.Getenv("MONGODB_ATLAS_ORG_ID") | ||
| pauseRandSuffix = utility.GetRandNum().String() | ||
| pauseProjectName = "cfn-e2e-cluster-pause" + pauseRandSuffix | ||
| pauseClusterName = "cfn-e2e-cluster-pause" + pauseRandSuffix | ||
| pauseStackName = "stack-cluster-pause-e2e-" + pauseRandSuffix | ||
| ) | ||
|
|
||
| func TestClusterPauseCFN(t *testing.T) { | ||
| testCtx := setupPauseSuite(t) | ||
|
|
||
| t.Run("Validate Template", func(t *testing.T) { | ||
| utility.TestIsTemplateValid(t, testCtx.cfnClient, testCtx.template) | ||
| }) | ||
|
|
||
| t.Run("Create Stack", func(t *testing.T) { | ||
| testCreatePauseStack(t, testCtx) | ||
| }) | ||
|
|
||
| t.Run("Pause Cluster", func(t *testing.T) { | ||
| testUpdatePauseState(t, testCtx, true) | ||
| }) | ||
|
|
||
| t.Run("Unpause Cluster", func(t *testing.T) { | ||
| testUpdatePauseState(t, testCtx, false) | ||
| }) | ||
|
|
||
| t.Run("Delete Stack", func(t *testing.T) { | ||
| testDeletePauseStack(t, testCtx) | ||
| }) | ||
| } | ||
|
|
||
| func setupPauseSuite(t *testing.T) *pauseTestContext { | ||
| t.Helper() | ||
| t.Log("Setting up pause suite") | ||
| testCtx := new(pauseTestContext) | ||
| testCtx.setUp(t) | ||
| return testCtx | ||
| } | ||
|
|
||
| func (c *pauseTestContext) setUp(t *testing.T) { | ||
| t.Helper() | ||
| c.resourceCtx = utility.InitResourceCtx(pauseStackName, pauseRandSuffix, resourceTypeName, resourceDirectory) | ||
| c.cfnClient, _ = utility.NewClients(t) | ||
| _, c.atlasClient20231115014 = utility.NewClients20231115014(t) | ||
|
|
||
| utility.PublishToPrivateRegistry(t, c.resourceCtx) | ||
| c.setupPrerequisites(t) | ||
| } | ||
|
|
||
| func (c *pauseTestContext) setupPrerequisites(t *testing.T) { | ||
| t.Helper() | ||
| t.Cleanup(func() { | ||
| cleanupPausePrerequisites(t, c) | ||
| cleanupPauseResources(t, c) | ||
| }) | ||
| t.Log("Setting up prerequisites for pause test") | ||
|
|
||
| var projectID string | ||
| if projectIDEnvVar := os.Getenv("MONGODB_ATLAS_PROJECT_ID"); projectIDEnvVar != "" { | ||
| t.Logf("using projectID from env var %s", projectIDEnvVar) | ||
| projectID = projectIDEnvVar | ||
| } else { | ||
| projectID = utility.CreateProject(t, c.atlasClient20231115014, pauseOrgID, pauseProjectName) | ||
| } | ||
|
|
||
| c.clusterTmplObj = pauseTestCluster{ | ||
| Name: pauseClusterName, | ||
| ProjectID: projectID, | ||
| Profile: pauseProfile, | ||
| Paused: "false", | ||
| ResourceTypeName: os.Getenv("RESOURCE_TYPE_NAME_FOR_E2E"), | ||
| } | ||
|
|
||
| var err error | ||
| c.template, err = newPauseCFNTemplate(c.clusterTmplObj) | ||
| utility.FailNowIfError(t, "Error while reading pause CFN Template: %v", err) | ||
| t.Logf("Pause test setup complete. ProjectID: %s, ClusterName: %s", c.clusterTmplObj.ProjectID, c.clusterTmplObj.Name) | ||
| } | ||
|
|
||
| func newPauseCFNTemplate(tmpl pauseTestCluster) (string, error) { | ||
| return utility.ExecuteGoTemplate(pauseCfnTemplatePath, tmpl) | ||
| } | ||
|
|
||
| func testCreatePauseStack(t *testing.T, c *pauseTestContext) { | ||
| t.Helper() | ||
| t.Logf("Creating pause stack with template:\n%s", c.template) | ||
|
|
||
| output := utility.CreateStack(t, c.cfnClient, pauseStackName, c.template) | ||
| clusterID := getPauseClusterIDFromStack(output) | ||
|
|
||
| cluster := readPauseClusterFromAtlas(t, c) | ||
|
|
||
| assert.Equal(t, cluster.GetId(), clusterID) | ||
| assert.False(t, cluster.GetPaused()) | ||
| } | ||
|
|
||
| func testUpdatePauseState(t *testing.T, c *pauseTestContext, pause bool) { | ||
| t.Helper() | ||
|
|
||
| if pause { | ||
| c.clusterTmplObj.Paused = "true" | ||
| } else { | ||
| c.clusterTmplObj.Paused = "false" | ||
| } | ||
|
|
||
| var err error | ||
| c.template, err = newPauseCFNTemplate(c.clusterTmplObj) | ||
| utility.FailNowIfError(t, "Error while reading pause CFN Template: %v", err) | ||
|
|
||
| output := utility.UpdateStack(t, c.cfnClient, pauseStackName, c.template) | ||
| _ = getPauseClusterIDFromStack(output) | ||
|
|
||
| cluster := readPauseClusterFromAtlas(t, c) | ||
|
|
||
| assert.Equal(t, pause, cluster.GetPaused()) | ||
| } | ||
|
|
||
| func testDeletePauseStack(t *testing.T, c *pauseTestContext) { | ||
| t.Helper() | ||
| utility.DeleteStack(t, c.cfnClient, pauseStackName) | ||
| _, resp, _ := c.atlasClient20231115014.ClustersApi.GetCluster(ctx.Background(), c.clusterTmplObj.ProjectID, c.clusterTmplObj.Name).Execute() | ||
| assert.Equal(t, 404, resp.StatusCode) | ||
| } | ||
|
|
||
| func cleanupPauseResources(t *testing.T, c *pauseTestContext) { | ||
| t.Helper() | ||
| utility.DeleteStackForCleanup(t, c.cfnClient, pauseStackName) | ||
| } | ||
|
|
||
| func cleanupPausePrerequisites(t *testing.T, c *pauseTestContext) { | ||
| t.Helper() | ||
| t.Log("Cleaning up pause test prerequisites") | ||
| if os.Getenv("MONGODB_ATLAS_PROJECT_ID") == "" { | ||
| utility.DeleteProject(t, c.atlasClient20231115014, c.clusterTmplObj.ProjectID) | ||
| } else { | ||
| t.Log("skipping project deletion (project managed outside of test)") | ||
| } | ||
| } | ||
|
|
||
| func readPauseClusterFromAtlas(t *testing.T, c *pauseTestContext) *admin20231115014.AdvancedClusterDescription { | ||
| t.Helper() | ||
| context := ctx.Background() | ||
| projectID := c.clusterTmplObj.ProjectID | ||
| cluster, resp, err := c.atlasClient20231115014.ClustersApi.GetCluster(context, projectID, c.clusterTmplObj.Name).Execute() | ||
| utility.FailNowIfError(t, "Err while retrieving Cluster from Atlas: %v", err) | ||
| assert.Equal(t, 200, resp.StatusCode) | ||
| return cluster | ||
| } | ||
|
|
||
| func getPauseClusterIDFromStack(output *cloudformation.DescribeStacksOutput) string { | ||
| stackOutputs := output.Stacks[0].Outputs | ||
| for i := 0; i < len(stackOutputs); i++ { | ||
| if *stackOutputs[i].OutputKey == "MongoDBAtlasClusterID" { | ||
| return *stackOutputs[i].OutputValue | ||
| } | ||
| } | ||
| return "" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty for the comment