Skip to content

Commit

Permalink
CLOUDP-292365: Support Flex Clusters (#2030)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
roothorp and s-urbaniak authored Jan 23, 2025
1 parent 7225b1e commit 802db8c
Show file tree
Hide file tree
Showing 35 changed files with 3,363 additions and 543 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ jobs:
"reconcile-one",
"reconcile-two",
"backup-compliance",
"flex",
]
steps:
- name: Get repo files from cache
Expand Down
74 changes: 74 additions & 0 deletions api/v1/atlasdeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ type AtlasDeploymentSpec struct {
// ProcessArgs allows to modify Advanced Configuration Options
// +optional
ProcessArgs *ProcessArgs `json:"processArgs,omitempty"`

// Configuration for the Flex cluster API. https://www.mongodb.com/docs/atlas/reference/api-resources-spec/v2/#tag/Flex-Clusters
// +optional
FlexSpec *FlexSpec `json:"flexSpec,omitempty"`
}

type SearchNode struct {
Expand Down Expand Up @@ -465,6 +469,9 @@ func (c *AtlasDeployment) GetDeploymentName() string {
if c.IsServerless() {
return c.Spec.ServerlessSpec.Name
}
if c.IsFlex() {
return c.Spec.FlexSpec.Name
}
if c.IsAdvancedDeployment() {
return c.Spec.DeploymentSpec.Name
}
Expand All @@ -482,6 +489,10 @@ func (c *AtlasDeployment) IsAdvancedDeployment() bool {
return c.Spec.DeploymentSpec != nil
}

func (c *AtlasDeployment) IsFlex() bool {
return c.Spec.FlexSpec != nil
}

func (c *AtlasDeployment) GetReplicationSetID() string {
if len(c.Status.ReplicaSets) > 0 {
return c.Status.ReplicaSets[0].ID
Expand Down Expand Up @@ -530,6 +541,41 @@ func (c *AtlasDeployment) ProjectDualRef() *ProjectDualReference {
return &c.Spec.ProjectDualReference
}

type FlexSpec struct {
// Human-readable label that identifies the instance.
// +required
Name string `json:"name"`

// List that contains key-value pairs between 1 to 255 characters in length for tagging and categorizing the instance.
// +kubebuilder:validation:MaxItems=50
// +optional
Tags []*TagSpec `json:"tags,omitempty"`

// Flag that indicates whether termination protection is enabled on the cluster.
// If set to true, MongoDB Cloud won't delete the cluster. If set to false, MongoDB Cloud will delete the cluster.
// +kubebuilder:default:=false
// +optional
TerminationProtectionEnabled bool `json:"terminationProtectionEnabled,omitempty"`

// Group of cloud provider settings that configure the provisioned MongoDB flex cluster.
// +required
ProviderSettings *FlexProviderSettings `json:"providerSettings"`
}

type FlexProviderSettings struct {
// Cloud service provider on which MongoDB Atlas provisions the flex cluster.
// +kubebuilder:validation:Enum=AWS;GCP;AZURE
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Backing Provider cannot be modified after cluster creation"
// +required
BackingProviderName string `json:"backingProviderName,omitempty"`

// Human-readable label that identifies the geographic location of your MongoDB flex cluster.
// The region you choose can affect network latency for clients accessing your databases.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Region Name cannot be modified after cluster creation"
// +required
RegionName string `json:"regionName,omitempty"`
}

// ************************************ Builder methods *************************************************

func NewDeployment(namespace, name, nameInAtlas string) *AtlasDeployment {
Expand Down Expand Up @@ -583,6 +629,24 @@ func newServerlessInstance(namespace, name, nameInAtlas, backingProviderName, re
}
}

func newFlexInstance(namespace, name, nameInAtlas, backingProviderName, regionName string) *AtlasDeployment {
return &AtlasDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: AtlasDeploymentSpec{
FlexSpec: &FlexSpec{
Name: nameInAtlas,
ProviderSettings: &FlexProviderSettings{
BackingProviderName: backingProviderName,
RegionName: regionName,
},
},
},
}
}

func addReplicaIfNotAdded(deployment *AtlasDeployment) {
if deployment == nil {
return
Expand Down Expand Up @@ -767,6 +831,16 @@ func NewDefaultAWSServerlessInstance(namespace, projectName string) *AtlasDeploy
).WithProjectName(projectName)
}

func NewDefaultAWSFlexInstance(namespace, projectName string) *AtlasDeployment {
return newFlexInstance(
namespace,
"test-flex-instance-k8s",
"test-flex-instance",
"AWS",
"US_EAST_1",
).WithProjectName(projectName)
}

func (c *AtlasDeployment) AtlasName() string {
if c.Spec.DeploymentSpec != nil {
return c.Spec.DeploymentSpec.Name
Expand Down
51 changes: 51 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,73 @@ spec:
required:
- id
type: object
flexSpec:
description: Configuration for the Flex cluster API. https://www.mongodb.com/docs/atlas/reference/api-resources-spec/v2/#tag/Flex-Clusters
properties:
name:
description: Human-readable label that identifies the instance.
type: string
providerSettings:
description: Group of cloud provider settings that configure the
provisioned MongoDB flex cluster.
properties:
backingProviderName:
description: Cloud service provider on which MongoDB Atlas
provisions the flex cluster.
enum:
- AWS
- GCP
- AZURE
type: string
x-kubernetes-validations:
- message: Backing Provider cannot be modified after cluster
creation
rule: self == oldSelf
regionName:
description: |-
Human-readable label that identifies the geographic location of your MongoDB flex cluster.
The region you choose can affect network latency for clients accessing your databases.
type: string
x-kubernetes-validations:
- message: Region Name cannot be modified after cluster creation
rule: self == oldSelf
required:
- backingProviderName
- regionName
type: object
tags:
description: List that contains key-value pairs between 1 to 255
characters in length for tagging and categorizing the instance.
items:
description: TagSpec holds a key-value pair for resource tagging
on this deployment.
properties:
key:
maxLength: 255
minLength: 1
pattern: ^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$
type: string
value:
maxLength: 255
minLength: 1
pattern: ^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$
type: string
required:
- key
- value
type: object
maxItems: 50
type: array
terminationProtectionEnabled:
default: false
description: |-
Flag that indicates whether termination protection is enabled on the cluster.
If set to true, MongoDB Cloud won't delete the cluster. If set to false, MongoDB Cloud will delete the cluster.
type: boolean
required:
- name
- providerSettings
type: object
processArgs:
description: ProcessArgs allows to modify Advanced Configuration Options
properties:
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/atlas/api_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
// a serverless instance from the cluster API, which is not allowed
ServerlessInstanceFromClusterAPI = "CANNOT_USE_SERVERLESS_INSTANCE_IN_CLUSTER_API"

ClusterInstanceFromServerlessAPI = "CANNOT_USE_CLUSTER_IN_SERVERLESS_INSTANCE_API"

// Resource not found
ResourceNotFound = "RESOURCE_NOT_FOUND"

Expand All @@ -37,4 +39,9 @@ const (
BackupComplianceNotMet = "BACKUP_POLICIES_NOT_MEETING_BACKUP_COMPLIANCE_POLICY_REQUIREMENTS"

ProviderUnsupported = "PROVIDER_UNSUPPORTED"

// Cannot use the Flex API to interact with non-Flex clusters
NonFlexInFlexAPI = "CANNOT_USE_NON_FLEX_CLUSTER_IN_FLEX_API"

FeatureUnsupported = "FEATURE_UNSUPPORTED"
)
3 changes: 1 addition & 2 deletions internal/controller/atlas/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,8 @@ func (p *ProductionProvider) IsResourceSupported(resource api.AtlasCustomResourc
return false
case *akov2.AtlasDeployment:
hasSearchNodes := atlasResource.Spec.DeploymentSpec != nil && len(atlasResource.Spec.DeploymentSpec.SearchNodes) > 0
isServerless := atlasResource.Spec.ServerlessSpec != nil

return !(isServerless || hasSearchNodes)
return !(atlasResource.IsServerless() || atlasResource.IsFlex() || hasSearchNodes)
}

return false
Expand Down
9 changes: 9 additions & 0 deletions internal/controller/atlas/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ func TestProvider_IsResourceSupported(t *testing.T) {
},
expectation: false,
},
"should return false when it's Atlas Gov and resource is Flex Deployment": {
domain: "https://cloud.mongodbgov.com",
resource: &akov2.AtlasDeployment{
Spec: akov2.AtlasDeploymentSpec{
FlexSpec: &akov2.FlexSpec{},
},
},
expectation: false,
},
"should return false when it's Atlas Gov and resource is a Deployment with search nodes": {
domain: "https://cloud.mongodbgov.com",
resource: &akov2.AtlasDeployment{
Expand Down
6 changes: 5 additions & 1 deletion internal/controller/atlasdatabaseuser/databaseuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ func (r *AtlasDatabaseUserReconciler) handleDatabaseUser(ctx *workflow.Context,
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
}
sdkClientSet, _, err := r.AtlasProvider.SdkClientSet(ctx.Context, credentials, r.Log)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
}
dbUserService := dbuser.NewAtlasUsers(sdkClient.DatabaseUsersApi)
deploymentService := deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, r.AtlasProvider.IsCloudGov())
deploymentService := deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, sdkClientSet.SdkClient20241113001.FlexClustersApi, r.AtlasProvider.IsCloudGov())
atlasProject, err := r.ResolveProject(ctx.Context, sdkClient, atlasDatabaseUser, orgID)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
Expand Down
8 changes: 8 additions & 0 deletions internal/controller/atlasdatabaseuser/databaseuser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/stretchr/testify/mock"
"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.mongodb.org/atlas-sdk/v20231115008/mockadmin"
adminv20241113001 "go.mongodb.org/atlas-sdk/v20241113001/admin"

"go.uber.org/zap"
"go.uber.org/zap/zaptest"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -191,6 +193,9 @@ func TestHandleDatabaseUser(t *testing.T) {

return &admin.APIClient{ProjectsApi: projectAPI, ClustersApi: clusterAPI, DatabaseUsersApi: userAPI}, "", nil
},
SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) {
return &atlas.ClientSet{SdkClient20241113001: &adminv20241113001.APIClient{}}, "", nil
},
},
expectedResult: ctrl.Result{RequeueAfter: workflow.DefaultRetry},
expectedConditions: []api.Condition{
Expand Down Expand Up @@ -2247,5 +2252,8 @@ func DefaultTestProvider(t *testing.T) *atlasmock.TestProvider {
DatabaseUsersApi: userAPI,
}, "", nil
},
SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) {
return &atlas.ClientSet{SdkClient20241113001: &adminv20241113001.APIClient{}}, "", nil
},
}
}
Loading

0 comments on commit 802db8c

Please sign in to comment.