Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,12 @@ spec:
name: "Clone-Instance-Name"
# The description of the clone instance
description: Database Description
# Cluster id of the cluster where the Database has to be provisioned
# Can be fetched from the GET /clusters endpoint
clusterId: "Nutanix Cluster Id"
# Cluster id or name of the cluster where the Database has to be provisioned
# Either clusterId or clusterName must be provided
# clusterId can be fetched from the GET /clusters endpoint
# clusterName is the name of the cluster (more developer-friendly)
clusterId: "Nutanix Cluster Id" # Optional if clusterName is provided
# clusterName: "Cluster-Name" # Optional if clusterId is provided
# You can specify any (or none) of these types of profiles: compute, software, network, dbParam
# If not specified, the corresponding Out-of-Box (OOB) profile will be used wherever applicable
# Name is case-sensitive. ID is the UUID of the profile. Profile should be in the "READY" state
Expand All @@ -240,10 +243,14 @@ spec:
# data: password, ssh_public_key
credentialSecret: clone-instance-secret-name
timezone: "UTC"
# ID of the database to clone from, can be fetched from NDB REST API Explorer
sourceDatabaseId: source-database-id
# ID of the snapshot to clone from, can be fetched from NDB REST API Explorer
snapshotId: snapshot-id
# ID or name of the database to clone from
# Either sourceDatabaseId or sourceDatabaseName must be provided
sourceDatabaseId: source-database-id # Optional if sourceDatabaseName is provided
# sourceDatabaseName: "source-database-name" # Optional if sourceDatabaseId is provided
# ID or name of the snapshot to clone from
# Either snapshotId or snapshotName must be provided
snapshotId: snapshot-id # Optional if snapshotName is provided
# snapshotName: "snapshot-name" # Optional if snapshotId is provided
additionalArguments: # Optional block, can specify additional arguments that are unique to database engines.
expireInDays: 3

Expand Down
32 changes: 28 additions & 4 deletions api/v1alpha1/database_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ type Instance struct {
// Description of the database instance
Description string `json:"description"`
// Id of the cluster to provision the database on
ClusterId string `json:"clusterId"`
// Either clusterId or clusterName must be provided
// +optional
ClusterId string `json:"clusterId,omitempty"`
// Name of the cluster to provision the database on
// Either clusterId or clusterName must be provided
// +optional
ClusterName string `json:"clusterName,omitempty"`
// +optional
Profiles *Profiles `json:"profiles"`
// Name of the secret holding the credentials for the database instance (password and ssh key)
Expand Down Expand Up @@ -118,7 +124,13 @@ type Clone struct {
// Type of parent clone
Type string `json:"type"`
// Id of the cluster to clone the database on
ClusterId string `json:"clusterId"`
// Either clusterId or clusterName must be provided
// +optional
ClusterId string `json:"clusterId,omitempty"`
// Name of the cluster to clone the database on
// Either clusterId or clusterName must be provided
// +optional
ClusterName string `json:"clusterName,omitempty"`
// +optional
Profiles *Profiles `json:"profiles"`
// Name of the secret holding the credentials for the database instance (password and ssh key)
Expand All @@ -127,9 +139,21 @@ type Clone struct {
// default UTC
TimeZone string `json:"timezone"`
// Id of the source database on NDB to clone from
SourceDatabaseId string `json:"sourceDatabaseId"`
// Either sourceDatabaseId or sourceDatabaseName must be provided
// +optional
SourceDatabaseId string `json:"sourceDatabaseId,omitempty"`
// Name of the source database on NDB to clone from
// Either sourceDatabaseId or sourceDatabaseName must be provided
// +optional
SourceDatabaseName string `json:"sourceDatabaseName,omitempty"`
// Id of the snapshot to create a clone from
SnapshotId string `json:"snapshotId"`
// Either snapshotId or snapshotName must be provided
// +optional
SnapshotId string `json:"snapshotId,omitempty"`
// Name of the snapshot to create a clone from
// Either snapshotId or snapshotName must be provided
// +optional
SnapshotName string `json:"snapshotName,omitempty"`
// +optional
// Additional database engine specific arguments
AdditionalArguments map[string]string `json:"additionalArguments"`
Expand Down
32 changes: 20 additions & 12 deletions api/v1alpha1/webhook_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)

// validateUUIDOrName validates that either UUID or name is provided, and validates UUID if provided
func validateUUIDOrName(uuid, name string, uuidPath, namePath *field.Path, uuidFieldName, nameFieldName string, errors *field.ErrorList) {
if uuid == "" && name == "" {
*errors = append(*errors, field.Invalid(uuidPath, uuid, fmt.Sprintf("Either %s or %s must be provided", uuidFieldName, nameFieldName)))
} else if uuid != "" {
// If UUID is provided, validate it's a valid UUID
if err := util.ValidateUUID(uuid); err != nil {
*errors = append(*errors, field.Invalid(uuidPath, uuid, fmt.Sprintf("%s field must be a valid UUID", uuidFieldName)))
}
}
}

// Get specific implementation of the DBProvisionRequestAppender interface based on the provided databaseType
func getDatabaseWebhookHandler(database *Database) DatabaseWebhookHandler {
if database.Spec.IsClone {
Expand Down Expand Up @@ -59,9 +71,8 @@ func (v *CloningWebhookHandler) validateCreate(spec *DatabaseSpec, errors *field
*errors = append(*errors, field.Invalid(clonePath.Child("name"), clone.Name, "A valid Clone Name must be specified"))
}

if err := util.ValidateUUID(clone.ClusterId); err != nil {
*errors = append(*errors, field.Invalid(clonePath.Child("clusterId"), clone.ClusterId, "ClusterId field must be a valid UUID"))
}
// Validate clusterId or clusterName - at least one must be provided
validateUUIDOrName(clone.ClusterId, clone.ClusterName, clonePath.Child("clusterId"), clonePath.Child("clusterName"), "clusterId", "clusterName", errors)

if clone.CredentialSecret == "" {
*errors = append(*errors, field.Invalid(clonePath.Child("credentialSecret"), clone.CredentialSecret, "CredentialSecret must be provided in the Clone Spec"))
Expand All @@ -71,13 +82,11 @@ func (v *CloningWebhookHandler) validateCreate(spec *DatabaseSpec, errors *field
*errors = append(*errors, field.Invalid(clonePath.Child("timeZone"), clone.TimeZone, "TimeZone must be provided in Clone Spec"))
}

if err := util.ValidateUUID(clone.SourceDatabaseId); err != nil {
*errors = append(*errors, field.Invalid(clonePath.Child("sourceDatabaseId"), clone.SourceDatabaseId, "sourceDatabaseId must be a valid UUID"))
}
// Validate sourceDatabaseId or sourceDatabaseName - at least one must be provided
validateUUIDOrName(clone.SourceDatabaseId, clone.SourceDatabaseName, clonePath.Child("sourceDatabaseId"), clonePath.Child("sourceDatabaseName"), "sourceDatabaseId", "sourceDatabaseName", errors)

if err := util.ValidateUUID(clone.SnapshotId); err != nil {
*errors = append(*errors, field.Invalid(clonePath.Child("snapshotId"), clone.SnapshotId, "snapshotId must be a valid UUID"))
}
// Validate snapshotId or snapshotName - at least one must be provided
validateUUIDOrName(clone.SnapshotId, clone.SnapshotName, clonePath.Child("snapshotId"), clonePath.Child("snapshotName"), "snapshotId", "snapshotName", errors)

if _, isPresent := api.AllowedDatabaseTypes[clone.Type]; !isPresent {
*errors = append(*errors, field.Invalid(clonePath.Child("type"), clone.Type,
Expand Down Expand Up @@ -174,9 +183,8 @@ func (v *ProvisioningWebhookHandler) validateCreate(spec *DatabaseSpec, errors *
*errors = append(*errors, field.Invalid(instancePath.Child("name"), instance.Name, "A valid Database Instance Name must be specified"))
}

if err := util.ValidateUUID(instance.ClusterId); err != nil {
*errors = append(*errors, field.Invalid(instancePath.Child("clusterId"), instance.ClusterId, "ClusterId field must be a valid UUID"))
}
// Validate clusterId or clusterName - at least one must be provided
validateUUIDOrName(instance.ClusterId, instance.ClusterName, instancePath.Child("clusterId"), instancePath.Child("clusterName"), "clusterId", "clusterName", errors)

if instance.Size < 10 {
*errors = append(*errors, field.Invalid(instancePath.Child("size"), instance.Size, "Initial Database size must be specified with a value 10 GBs or more"))
Expand Down
8 changes: 4 additions & 4 deletions api/v1alpha1/webhook_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ var _ = Describe("Webhook Tests", func() {
err := k8sClient.Create(context.Background(), database)
Expect(err).To(HaveOccurred())
errMsg := err.(*errors.StatusError).ErrStatus.Message
Expect(errMsg).To(ContainSubstring("ClusterId field must be a valid UUID"))
Expect(errMsg).To(ContainSubstring("Either clusterId or clusterName must be provided"))
})

It("Should check for missing CredentialSecret", func() {
Expand Down Expand Up @@ -381,7 +381,7 @@ var _ = Describe("Webhook Tests", func() {
err := k8sClient.Create(context.Background(), clone)
Expect(err).To(HaveOccurred())
errMsg := err.(*errors.StatusError).ErrStatus.Message
Expect(errMsg).To(ContainSubstring("ClusterId field must be a valid UUID"))
Expect(errMsg).To(ContainSubstring("Either clusterId or clusterName must be provided"))
})

It("Should check for missing CredentialSecret", func() {
Expand Down Expand Up @@ -421,7 +421,7 @@ var _ = Describe("Webhook Tests", func() {
err := k8sClient.Create(context.Background(), clone)
Expect(err).To(HaveOccurred())
errMsg := err.(*errors.StatusError).ErrStatus.Message
Expect(errMsg).To(ContainSubstring("sourceDatabaseId must be a valid UUID"))
Expect(errMsg).To(ContainSubstring("Either sourceDatabaseId or sourceDatabaseName must be provided"))
})

It("Should check for snapshotId", func() {
Expand All @@ -431,7 +431,7 @@ var _ = Describe("Webhook Tests", func() {
err := k8sClient.Create(context.Background(), clone)
Expect(err).To(HaveOccurred())
errMsg := err.(*errors.StatusError).ErrStatus.Message
Expect(errMsg).To(ContainSubstring("snapshotId must be a valid UUID"))
Expect(errMsg).To(ContainSubstring("Either snapshotId or snapshotName must be provided"))
})

It("Should check for invalid Type'", func() {
Expand Down
1 change: 1 addition & 0 deletions automation/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
NDB_SECRET_PASSWORD_ENV = "NDB_SECRET_PASSWORD"
NDB_SERVER_ENV = "NDB_SERVER"
NX_CLUSTER_ID_ENV = "NX_CLUSTER_ID"
NX_CLUSTER_NAME_ENV = "NX_CLUSTER_NAME"

MONGO_SI_CLONING_NAME_ENV = "MONGO_SI_CLONING_NAME"
MSSQL_SI_CLONING_NAME_ENV = "MSSQL_SI_CLONING_NAME"
Expand Down
2 changes: 1 addition & 1 deletion automation/tests/.env.bak
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
KUBECONFIG='/Users/shivaprasad.mb/.kube/config'
KUBECONFIG='/Users/sasikanth.masini/.kube/config'
DB_SECRET_PASSWORD='Nutanix.1'
NDB_SECRET_USERNAME='admin'
NDB_SECRET_PASSWORD='Nutanix.1'
Expand Down
3 changes: 3 additions & 0 deletions automation/tests/cloning/mongo-si_test/config/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ spec:
type: "mongodb"
description: Cloning mongoDB single instance testing
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
profiles: {}
credentialSecret: clone-db-secret-mongo-si
timezone: America/Los_Angeles
sourceDatabaseId: <source-database-id>
# sourceDatabaseName: <source-database-name> # Optional: use sourceDatabaseName instead of sourceDatabaseId
snapshotId: <snapshot-id>
# snapshotName: <snapshot-name> # Optional: use snapshotName instead of snapshotId
additionalArguments: {}
3 changes: 3 additions & 0 deletions automation/tests/cloning/mssql-si_test/config/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ spec:
type: mssql
description: Cloning mssql single instance testing
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
profiles:
software:
name: MSSQL-MAZIN2
credentialSecret: clone-db-secret-mssql-si
timezone: UTC
sourceDatabaseId: <source-database-id>
# sourceDatabaseName: <source-database-name> # Optional: use sourceDatabaseName instead of sourceDatabaseId
snapshotId: <snapshot-id>
# snapshotName: <snapshot-name> # Optional: use snapshotName instead of snapshotId
additionalArguments:
"authentication_mode": "mixed"

3 changes: 3 additions & 0 deletions automation/tests/cloning/mysql-si_test/config/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ spec:
type: mysql
description: Cloning mysql single instance testing
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
profiles: {}
credentialSecret: clone-db-secret-mysql-si
timezone: UTC
sourceDatabaseId: <source-database-id>
# sourceDatabaseName: <source-database-name> # Optional: use sourceDatabaseName instead of sourceDatabaseId
snapshotId: <snapshot-id>
# snapshotName: <snapshot-name> # Optional: use snapshotName instead of snapshotId
additionalArguments: {}

3 changes: 3 additions & 0 deletions automation/tests/cloning/pg-si_test/config/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ spec:
type: postgres
description: Cloning pg single instance testing
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
profiles: {}
credentialSecret: clone-db-secret-pg-si
timezone: UTC
sourceDatabaseId: <source-database-id>
# sourceDatabaseName: <source-database-name> # Optional: use sourceDatabaseName instead of sourceDatabaseId
snapshotId: <snapshot-id>
# snapshotName: <snapshot-name> # Optional: use snapshotName instead of snapshotId
additionalArguments: {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ spec:
databaseNames:
- database_one
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
credentialSecret: db-secret-mongo-si
size: 10
timezone: "UTC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ spec:
databaseNames:
- database_one
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
credentialSecret: db-secret-mssql-si
size: 10
timezone: "UTC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ spec:
databaseNames:
- database_one
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
credentialSecret: db-secret-mysql-si
size: 10
timezone: "UTC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ spec:
- database_two
- database_three
clusterId: <cluster-id>
# clusterName: <cluster-name> # Optional: use clusterName instead of clusterId
credentialSecret: db-secret-pg-si
size: 10
timezone: "UTC"
Expand Down
15 changes: 11 additions & 4 deletions automation/util/cloning_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,17 @@ func updateClone(ctx context.Context, database *ndbv1alpha1.Database, ndbServer
logger.Printf("Retrieved snapshot: %s for clusterId: %s", snapshotId, nxClusterId)
}

// Update sourceDatabaseId, nxClusterId, and snapshotId
database.Spec.Clone.SourceDatabaseId = sourceDatabaseId
database.Spec.Clone.ClusterId = nxClusterId
database.Spec.Clone.SnapshotId = snapshotId
// Update sourceDatabaseId, nxClusterId, and snapshotId only if names are not provided
// If names are provided, the controller will resolve them to IDs
if database.Spec.Clone.SourceDatabaseName == "" {
database.Spec.Clone.SourceDatabaseId = sourceDatabaseId
}
if database.Spec.Clone.ClusterName == "" {
database.Spec.Clone.ClusterId = nxClusterId
}
if database.Spec.Clone.SnapshotName == "" {
database.Spec.Clone.SnapshotId = snapshotId
}

return
}
7 changes: 6 additions & 1 deletion automation/util/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,19 @@ func CheckRequiredEnv(ctx context.Context) (err error) {
automation.NDB_SECRET_USERNAME_ENV,
automation.NDB_SECRET_PASSWORD_ENV,
automation.NDB_SERVER_ENV,
automation.NX_CLUSTER_ID_ENV,
}
missingRequiredEnvs := []string{}
for _, env := range requiredEnvs {
if _, ok := os.LookupEnv(env); !ok {
missingRequiredEnvs = append(missingRequiredEnvs, env)
}
}
// Check that at least one of NX_CLUSTER_ID or NX_CLUSTER_NAME is provided
nxClusterId := os.Getenv(automation.NX_CLUSTER_ID_ENV)
nxClusterName := os.Getenv(automation.NX_CLUSTER_NAME_ENV)
if nxClusterId == "" && nxClusterName == "" {
missingRequiredEnvs = append(missingRequiredEnvs, fmt.Sprintf("%s or %s", automation.NX_CLUSTER_ID_ENV, automation.NX_CLUSTER_NAME_ENV))
}
if len(missingRequiredEnvs) != 0 {
return fmt.Errorf("error: loadEnv() ended! Missing the following required env variables: %s", missingRequiredEnvs)
} else {
Expand Down
10 changes: 9 additions & 1 deletion automation/util/test_suite_manager_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,21 @@ func provisionOrClone(ctx context.Context, st *SetupTypes, clientset *kubernetes

// Create Database or Clone
if st.Database != nil {
// Check if clusterName is provided, otherwise use clusterId
nxClusterName := os.Getenv(automation.NX_CLUSTER_NAME_ENV)
nxClusterId := os.Getenv(automation.NX_CLUSTER_ID_ENV)
if st.Database.Spec.IsClone {
if err = updateClone(ctx, st.Database, st.NdbServer, st.NdbSecret); err != nil {
return
}
} else {
st.Database.Spec.Instance.ClusterId = nxClusterId
if nxClusterName != "" {
st.Database.Spec.Instance.ClusterName = nxClusterName
// Clear clusterId if clusterName is provided
st.Database.Spec.Instance.ClusterId = ""
} else if nxClusterId != "" {
st.Database.Spec.Instance.ClusterId = nxClusterId
}
}
st.Database, err = v1alpha1ClientSet.Databases(st.Database.Namespace).Create(st.Database)
if err != nil {
Expand Down
Loading
Loading