Skip to content

Commit 802db8c

Browse files
roothorps-urbaniak
andauthored
CLOUDP-292365: Support Flex Clusters (#2030)
* Add initial Flex types * Add Flex to translation layer * Add Flex to Deployment controller * Generate mocks * Correctly propogate Flex API to controllers * fix tests * Add SDK Client Set to Contract tests * fix int tests * check for flex before normal cluster * Add flex helper funcs * Update deployment validation to account for flex * small fixes after rebase * Fix remaining broken tests * add e2e test * changes per reviews * Ignore dupl in _test.go files * api: add optional kubebuilder for flex spec * be more tolerant for flex API not available * flex: respect gov * address linter comments * enable flex * use ako deployment type for retrieving clusters from Atlas * fix unit tests * fix unit tests * regenerate mocks * remove checking for serverless vs. non-serverless constraints as it breaks flex * check for wrong cluster types explicitely * act on not found errors only in GetFlexCluster * use deployment names for getcluster * add error code from Atlas signalling wrong API call * fix tests * fix linter * Fix flex e2e * address review comments * add unit tests for flex handler --------- Co-authored-by: Sergiusz Urbaniak <[email protected]>
1 parent 7225b1e commit 802db8c

35 files changed

+3363
-543
lines changed

.github/workflows/test-e2e.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ jobs:
182182
"reconcile-one",
183183
"reconcile-two",
184184
"backup-compliance",
185+
"flex",
185186
]
186187
steps:
187188
- name: Get repo files from cache

api/v1/atlasdeployment_types.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ type AtlasDeploymentSpec struct {
6868
// ProcessArgs allows to modify Advanced Configuration Options
6969
// +optional
7070
ProcessArgs *ProcessArgs `json:"processArgs,omitempty"`
71+
72+
// Configuration for the Flex cluster API. https://www.mongodb.com/docs/atlas/reference/api-resources-spec/v2/#tag/Flex-Clusters
73+
// +optional
74+
FlexSpec *FlexSpec `json:"flexSpec,omitempty"`
7175
}
7276

7377
type SearchNode struct {
@@ -465,6 +469,9 @@ func (c *AtlasDeployment) GetDeploymentName() string {
465469
if c.IsServerless() {
466470
return c.Spec.ServerlessSpec.Name
467471
}
472+
if c.IsFlex() {
473+
return c.Spec.FlexSpec.Name
474+
}
468475
if c.IsAdvancedDeployment() {
469476
return c.Spec.DeploymentSpec.Name
470477
}
@@ -482,6 +489,10 @@ func (c *AtlasDeployment) IsAdvancedDeployment() bool {
482489
return c.Spec.DeploymentSpec != nil
483490
}
484491

492+
func (c *AtlasDeployment) IsFlex() bool {
493+
return c.Spec.FlexSpec != nil
494+
}
495+
485496
func (c *AtlasDeployment) GetReplicationSetID() string {
486497
if len(c.Status.ReplicaSets) > 0 {
487498
return c.Status.ReplicaSets[0].ID
@@ -530,6 +541,41 @@ func (c *AtlasDeployment) ProjectDualRef() *ProjectDualReference {
530541
return &c.Spec.ProjectDualReference
531542
}
532543

544+
type FlexSpec struct {
545+
// Human-readable label that identifies the instance.
546+
// +required
547+
Name string `json:"name"`
548+
549+
// List that contains key-value pairs between 1 to 255 characters in length for tagging and categorizing the instance.
550+
// +kubebuilder:validation:MaxItems=50
551+
// +optional
552+
Tags []*TagSpec `json:"tags,omitempty"`
553+
554+
// Flag that indicates whether termination protection is enabled on the cluster.
555+
// If set to true, MongoDB Cloud won't delete the cluster. If set to false, MongoDB Cloud will delete the cluster.
556+
// +kubebuilder:default:=false
557+
// +optional
558+
TerminationProtectionEnabled bool `json:"terminationProtectionEnabled,omitempty"`
559+
560+
// Group of cloud provider settings that configure the provisioned MongoDB flex cluster.
561+
// +required
562+
ProviderSettings *FlexProviderSettings `json:"providerSettings"`
563+
}
564+
565+
type FlexProviderSettings struct {
566+
// Cloud service provider on which MongoDB Atlas provisions the flex cluster.
567+
// +kubebuilder:validation:Enum=AWS;GCP;AZURE
568+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Backing Provider cannot be modified after cluster creation"
569+
// +required
570+
BackingProviderName string `json:"backingProviderName,omitempty"`
571+
572+
// Human-readable label that identifies the geographic location of your MongoDB flex cluster.
573+
// The region you choose can affect network latency for clients accessing your databases.
574+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Region Name cannot be modified after cluster creation"
575+
// +required
576+
RegionName string `json:"regionName,omitempty"`
577+
}
578+
533579
// ************************************ Builder methods *************************************************
534580

535581
func NewDeployment(namespace, name, nameInAtlas string) *AtlasDeployment {
@@ -583,6 +629,24 @@ func newServerlessInstance(namespace, name, nameInAtlas, backingProviderName, re
583629
}
584630
}
585631

632+
func newFlexInstance(namespace, name, nameInAtlas, backingProviderName, regionName string) *AtlasDeployment {
633+
return &AtlasDeployment{
634+
ObjectMeta: metav1.ObjectMeta{
635+
Name: name,
636+
Namespace: namespace,
637+
},
638+
Spec: AtlasDeploymentSpec{
639+
FlexSpec: &FlexSpec{
640+
Name: nameInAtlas,
641+
ProviderSettings: &FlexProviderSettings{
642+
BackingProviderName: backingProviderName,
643+
RegionName: regionName,
644+
},
645+
},
646+
},
647+
}
648+
}
649+
586650
func addReplicaIfNotAdded(deployment *AtlasDeployment) {
587651
if deployment == nil {
588652
return
@@ -767,6 +831,16 @@ func NewDefaultAWSServerlessInstance(namespace, projectName string) *AtlasDeploy
767831
).WithProjectName(projectName)
768832
}
769833

834+
func NewDefaultAWSFlexInstance(namespace, projectName string) *AtlasDeployment {
835+
return newFlexInstance(
836+
namespace,
837+
"test-flex-instance-k8s",
838+
"test-flex-instance",
839+
"AWS",
840+
"US_EAST_1",
841+
).WithProjectName(projectName)
842+
}
843+
770844
func (c *AtlasDeployment) AtlasName() string {
771845
if c.Spec.DeploymentSpec != nil {
772846
return c.Spec.DeploymentSpec.Name

api/v1/zz_generated.deepcopy.go

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,73 @@ spec:
628628
required:
629629
- id
630630
type: object
631+
flexSpec:
632+
description: Configuration for the Flex cluster API. https://www.mongodb.com/docs/atlas/reference/api-resources-spec/v2/#tag/Flex-Clusters
633+
properties:
634+
name:
635+
description: Human-readable label that identifies the instance.
636+
type: string
637+
providerSettings:
638+
description: Group of cloud provider settings that configure the
639+
provisioned MongoDB flex cluster.
640+
properties:
641+
backingProviderName:
642+
description: Cloud service provider on which MongoDB Atlas
643+
provisions the flex cluster.
644+
enum:
645+
- AWS
646+
- GCP
647+
- AZURE
648+
type: string
649+
x-kubernetes-validations:
650+
- message: Backing Provider cannot be modified after cluster
651+
creation
652+
rule: self == oldSelf
653+
regionName:
654+
description: |-
655+
Human-readable label that identifies the geographic location of your MongoDB flex cluster.
656+
The region you choose can affect network latency for clients accessing your databases.
657+
type: string
658+
x-kubernetes-validations:
659+
- message: Region Name cannot be modified after cluster creation
660+
rule: self == oldSelf
661+
required:
662+
- backingProviderName
663+
- regionName
664+
type: object
665+
tags:
666+
description: List that contains key-value pairs between 1 to 255
667+
characters in length for tagging and categorizing the instance.
668+
items:
669+
description: TagSpec holds a key-value pair for resource tagging
670+
on this deployment.
671+
properties:
672+
key:
673+
maxLength: 255
674+
minLength: 1
675+
pattern: ^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$
676+
type: string
677+
value:
678+
maxLength: 255
679+
minLength: 1
680+
pattern: ^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$
681+
type: string
682+
required:
683+
- key
684+
- value
685+
type: object
686+
maxItems: 50
687+
type: array
688+
terminationProtectionEnabled:
689+
default: false
690+
description: |-
691+
Flag that indicates whether termination protection is enabled on the cluster.
692+
If set to true, MongoDB Cloud won't delete the cluster. If set to false, MongoDB Cloud will delete the cluster.
693+
type: boolean
694+
required:
695+
- name
696+
- providerSettings
697+
type: object
631698
processArgs:
632699
description: ProcessArgs allows to modify Advanced Configuration Options
633700
properties:

internal/controller/atlas/api_error.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const (
2727
// a serverless instance from the cluster API, which is not allowed
2828
ServerlessInstanceFromClusterAPI = "CANNOT_USE_SERVERLESS_INSTANCE_IN_CLUSTER_API"
2929

30+
ClusterInstanceFromServerlessAPI = "CANNOT_USE_CLUSTER_IN_SERVERLESS_INSTANCE_API"
31+
3032
// Resource not found
3133
ResourceNotFound = "RESOURCE_NOT_FOUND"
3234

@@ -37,4 +39,9 @@ const (
3739
BackupComplianceNotMet = "BACKUP_POLICIES_NOT_MEETING_BACKUP_COMPLIANCE_POLICY_REQUIREMENTS"
3840

3941
ProviderUnsupported = "PROVIDER_UNSUPPORTED"
42+
43+
// Cannot use the Flex API to interact with non-Flex clusters
44+
NonFlexInFlexAPI = "CANNOT_USE_NON_FLEX_CLUSTER_IN_FLEX_API"
45+
46+
FeatureUnsupported = "FEATURE_UNSUPPORTED"
4047
)

internal/controller/atlas/provider.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ func (p *ProductionProvider) IsResourceSupported(resource api.AtlasCustomResourc
9797
return false
9898
case *akov2.AtlasDeployment:
9999
hasSearchNodes := atlasResource.Spec.DeploymentSpec != nil && len(atlasResource.Spec.DeploymentSpec.SearchNodes) > 0
100-
isServerless := atlasResource.Spec.ServerlessSpec != nil
101100

102-
return !(isServerless || hasSearchNodes)
101+
return !(atlasResource.IsServerless() || atlasResource.IsFlex() || hasSearchNodes)
103102
}
104103

105104
return false

internal/controller/atlas/provider_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ func TestProvider_IsResourceSupported(t *testing.T) {
135135
},
136136
expectation: false,
137137
},
138+
"should return false when it's Atlas Gov and resource is Flex Deployment": {
139+
domain: "https://cloud.mongodbgov.com",
140+
resource: &akov2.AtlasDeployment{
141+
Spec: akov2.AtlasDeploymentSpec{
142+
FlexSpec: &akov2.FlexSpec{},
143+
},
144+
},
145+
expectation: false,
146+
},
138147
"should return false when it's Atlas Gov and resource is a Deployment with search nodes": {
139148
domain: "https://cloud.mongodbgov.com",
140149
resource: &akov2.AtlasDeployment{

internal/controller/atlasdatabaseuser/databaseuser.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ func (r *AtlasDatabaseUserReconciler) handleDatabaseUser(ctx *workflow.Context,
5252
if err != nil {
5353
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
5454
}
55+
sdkClientSet, _, err := r.AtlasProvider.SdkClientSet(ctx.Context, credentials, r.Log)
56+
if err != nil {
57+
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
58+
}
5559
dbUserService := dbuser.NewAtlasUsers(sdkClient.DatabaseUsersApi)
56-
deploymentService := deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, r.AtlasProvider.IsCloudGov())
60+
deploymentService := deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, sdkClientSet.SdkClient20241113001.FlexClustersApi, r.AtlasProvider.IsCloudGov())
5761
atlasProject, err := r.ResolveProject(ctx.Context, sdkClient, atlasDatabaseUser, orgID)
5862
if err != nil {
5963
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)

internal/controller/atlasdatabaseuser/databaseuser_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/stretchr/testify/mock"
1313
"go.mongodb.org/atlas-sdk/v20231115008/admin"
1414
"go.mongodb.org/atlas-sdk/v20231115008/mockadmin"
15+
adminv20241113001 "go.mongodb.org/atlas-sdk/v20241113001/admin"
16+
1517
"go.uber.org/zap"
1618
"go.uber.org/zap/zaptest"
1719
corev1 "k8s.io/api/core/v1"
@@ -191,6 +193,9 @@ func TestHandleDatabaseUser(t *testing.T) {
191193

192194
return &admin.APIClient{ProjectsApi: projectAPI, ClustersApi: clusterAPI, DatabaseUsersApi: userAPI}, "", nil
193195
},
196+
SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) {
197+
return &atlas.ClientSet{SdkClient20241113001: &adminv20241113001.APIClient{}}, "", nil
198+
},
194199
},
195200
expectedResult: ctrl.Result{RequeueAfter: workflow.DefaultRetry},
196201
expectedConditions: []api.Condition{
@@ -2247,5 +2252,8 @@ func DefaultTestProvider(t *testing.T) *atlasmock.TestProvider {
22472252
DatabaseUsersApi: userAPI,
22482253
}, "", nil
22492254
},
2255+
SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) {
2256+
return &atlas.ClientSet{SdkClient20241113001: &adminv20241113001.APIClient{}}, "", nil
2257+
},
22502258
}
22512259
}

0 commit comments

Comments
 (0)