Skip to content
Closed
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
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ The NDB operator brings automated and simplified database administration, provis
4. A clone of the source code ([this](https://github.com/nutanix-cloud-native/ndb-operator) repository).
5. Cert-manager (only when running in non OpenShift clusters). Follow the instructions [here](https://cert-manager.io/docs/installation/).

With the pre-requisites completed, the NDB Operator can be deployed in one of the following ways:
With the pre-requisites completed, the NDB Operator can be deployed in one of the following ways:

### Outside Kubernetes
Runs the controller outside the Kubernetes cluster as a process, but installs the CRDs, services and RBAC entities within the Kubernetes cluster. Generally used while development (without running webhooks):
```sh
make install run
```

### Within Kubernetes
### Within Kubernetes
Runs the controller pod, installs the CRDs, services and RBAC entities within the Kubernetes cluster. Used to run the operator from the container image defined in the Makefile. Make sure that the cert-manager is installed if not using OpenShift.

```sh
Expand Down Expand Up @@ -110,12 +110,12 @@ metadata:
app.kubernetes.io/created-by: ndb-operator
name: ndb
spec:
# Name of the secret that holds the credentials for NDB: username, password and ca_certificate created earlier
credentialSecret: ndb-secret-name
# NDB Server's API URL
server: https://[NDB IP]:8443/era/v0.9
# Set to true to skip SSL certificate validation, should be false if ca_certificate is provided in the credential secret.
skipCertificateVerification: true
# Name of the secret that holds the credentials for NDB: username, password and ca_certificate created earlier
credentialSecret: ndb-secret-name
# NDB Server's API URL
server: https://[NDB IP]:8443/era/v0.9
# Set to true to skip SSL certificate validation, should be false if ca_certificate is provided in the credential secret.
skipCertificateVerification: true

```
Create the NDBServer resource using:
Expand Down Expand Up @@ -156,6 +156,8 @@ spec:
size: 10
timezone: "UTC"
type: postgres
# isHighAvailability is an optional parameter. In case nothing is specified, it is set to false
isHighAvailability: false

# 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
Expand Down Expand Up @@ -214,6 +216,8 @@ spec:
# Cluster id of the cluster where the Database has to be provisioned
# Can be fetched from the GET /clusters endpoint
clusterId: "Nutanix Cluster Id"
# isHighAvailability is an optional parameter. In case nothing is specified, it is set to false
isHighAvailability: false
# 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 Down Expand Up @@ -257,7 +261,7 @@ kubectl apply -f <path/to/database-manifest.yaml>
### Additional Arguments for Databases
Below are the various optional addtionalArguments you can specify along with examples of their corresponding values. Arguments that have defaults will be indicated.

Provisioning Additional Arguments:
Provisioning Additional Arguments:
```yaml
# PostGres
additionalArguments:
Expand Down Expand Up @@ -287,7 +291,7 @@ additionalArguments:
vm_win_license_key: <licenseKey> # NO Default.
```

Cloning Additional Arguments:
Cloning Additional Arguments:
```yaml
MSSQL:
windows_domain_profile_id
Expand Down Expand Up @@ -360,7 +364,7 @@ Run your controller locally (this will run in the foreground, so switch to a new
make run
```

**NOTES:**
**NOTES:**
1. You can also run this in one step by running: `make install run`
2. Run `make --help` for more information on all potential `make` targets

Expand Down Expand Up @@ -439,4 +443,4 @@ This code is developed in the open with input from the community through issues
## License
Copyright 2022-2023 Nutanix, Inc.

The project is released under version 2.0 of the [Apache license](http://www.apache.org/licenses/LICENSE-2.0).
The project is released under version 2.0 of the [Apache license](http://www.apache.org/licenses/LICENSE-2.0).
2 changes: 2 additions & 0 deletions api/v1alpha1/database_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type Instance struct {
// +optional
// Additional database engine specific arguments
AdditionalArguments map[string]string `json:"additionalArguments"`
IsHighAvailability bool `json:"isHighAvailability"`
}

type Clone struct {
Expand All @@ -133,6 +134,7 @@ type Clone struct {
// +optional
// Additional database engine specific arguments
AdditionalArguments map[string]string `json:"additionalArguments"`
IsHighAvailability bool `json:"isHighAvailability"`
}

// Time Machine details
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/webhook_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const (
CREDENTIAL_SECRET = "database-secret"
TIMEZONE = "UTC"
SIZE = 10
HA = false
)

func TestAPIs(t *testing.T) {
Expand Down Expand Up @@ -615,6 +616,7 @@ func createDefaultDatabase(metadataName string) *Database {
Type: common.DATABASE_TYPE_POSTGRES,
Profiles: &(Profiles{}),
AdditionalArguments: map[string]string{},
IsHighAvailability: HA,
},
},
}
Expand All @@ -639,6 +641,7 @@ func createDefaultClone(metadataName string) *Database {
SnapshotId: DEFAULT_UUID,
Profiles: &(Profiles{}),
AdditionalArguments: map[string]string{},
IsHighAvailability: HA,
},
},
}
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/ndb.nutanix.com_databases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ spec:
description:
description: Description of the clone instance
type: string
isHighAvailability:
type: boolean
name:
description: Name of the clone instance
type: string
Expand Down Expand Up @@ -151,6 +153,8 @@ spec:
description:
description: Description of the database instance
type: string
isHighAvailability:
type: boolean
name:
description: Name of the database instance
type: string
Expand Down
4 changes: 4 additions & 0 deletions controller_adapters/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ func (d *Database) GetInstanceSize() int {
return d.Spec.Instance.Size
}

func (d *Database) GetInstanceIsHighAvailability() bool {
return d.Spec.Instance.IsHighAvailability
}

// Returns basic details about the Time Machine if provided in the
// underlying database, else returns defaults like:
// TM Name: <db_instance_name>_TM
Expand Down
24 changes: 24 additions & 0 deletions controller_adapters/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,30 @@ func TestDatabase_GetInstanceSize(t *testing.T) {
})
}

// Tests the GetInstanceIsHighAvailability() function retrieves Size correctly:
func TestDatabase_GetInstanceIsHighAvailability(t *testing.T) {

name := "Contains IsHighAvailability"
database := Database{
Database: v1alpha1.Database{
Spec: v1alpha1.DatabaseSpec{
Instance: &v1alpha1.Instance{
IsHighAvailability: true,
},
},
},
}
wantIsHighAvailability := true

t.Run(name, func(t *testing.T) {

gotIsHighAvailability := database.GetInstanceIsHighAvailability()
if gotIsHighAvailability != wantIsHighAvailability {
t.Errorf("Database.GetInstanceIsHighAvailability() gotIsHighAvailability= %v, want %v", gotIsHighAvailability, wantIsHighAvailability)
}
})
}

// Tests the GetClusterId() function retrieves ClusterId correctly:
func TestDatabase_GetClusterId(t *testing.T) {

Expand Down
99 changes: 97 additions & 2 deletions ndb_api/clone_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package ndb_api
import (
"context"
"fmt"
"strconv"

"github.com/nutanix-cloud-native/ndb-operator/common"
"github.com/nutanix-cloud-native/ndb-operator/ndb_client"
Expand Down Expand Up @@ -79,7 +80,7 @@ func GenerateCloningRequest(ctx context.Context, ndb_client *ndb_client.NDBClien
NetworkProfileId: profilesMap[common.PROFILE_TYPE_NETWORK].Id,
NewDbServerTimeZone: "",
NxClusterId: database.GetClusterId(),
Properties: make([]string, 0),
Properties: make([]map[string]string, 0),
},
},
// Added by request appenders as per the engine
Expand All @@ -90,8 +91,11 @@ func GenerateCloningRequest(ctx context.Context, ndb_client *ndb_client.NDBClien
NetworkProfileId: profilesMap[common.PROFILE_TYPE_NETWORK].Id,
DatabaseParameterProfileId: profilesMap[common.PROFILE_TYPE_DATABASE_PARAMETER].Id,
}
// boolean for high availability
isHighAvailability := false

// Appending request body based on database type
appender, err := GetRequestAppender(databaseType)
appender, err := GetRequestAppender(databaseType, isHighAvailability)
if err != nil {
log.Error(err, "Error while getting a request appender")
return
Expand Down Expand Up @@ -204,6 +208,97 @@ func (a *PostgresRequestAppender) appendCloningRequest(req *DatabaseCloneRequest
return req, nil
}

func setCloneNodesParameters(req *DatabaseCloneRequest, database DatabaseInterface) {
// Extract values of ComputeProfileId and NetworkProfileId
computeProfileId := req.Nodes[0].ComputeProfileId
networkProfileId := req.Nodes[0].NetworkProfileId
serverTimeZone := req.Nodes[0].NewDbServerTimeZone

// Clear the original req.Nodes array
req.Nodes = []Node{}

// Create node object for HA Proxy
for i := 0; i < 2; i++ {
// Hard coding the HA Proxy properties
props := make([]map[string]string, 1)
props[0] = map[string]string{
"name": "node_type",
"value": "haproxy",
}
req.Nodes = append(req.Nodes, Node{
Properties: props,
VmName: database.GetName() + "_haproxy" + strconv.Itoa(i),
NxClusterId: database.GetClusterId(),
})
}

// Create node object for Database Instances
for i := 0; i < 3; i++ {
// Hard coding the DB properties
props := make([]map[string]string, 4)
props[0] = map[string]string{
"name": "role",
"value": "Secondary",
}
// 1st node will be the primary node
if i == 0 {
props[0]["value"] = "Primary"
}
props[1] = map[string]string{
"name": "failover_mode",
"value": "Automatic",
}
props[2] = map[string]string{
"name": "node_type",
"value": "database",
}
props[3] = map[string]string{
"name": "remote_archive_destination",
"value": "",
}
req.Nodes = append(req.Nodes, Node{
ComputeProfileId: computeProfileId,
NetworkProfileId: networkProfileId,
NewDbServerTimeZone: serverTimeZone,
Properties: props,
VmName: database.GetName() + "-" + strconv.Itoa(i),
NxClusterId: database.GetClusterId(),
})
}
}

func (a *PostgresHARequestAppender) appendCloningRequest(req *DatabaseCloneRequest, database DatabaseInterface, reqData map[string]interface{}) (*DatabaseCloneRequest, error) {
req.SSHPublicKey = reqData[common.NDB_PARAM_SSH_PUBLIC_KEY].(string)
dbPassword := reqData[common.NDB_PARAM_PASSWORD].(string)

// Set the number of nodes to 5, 3 Postgres nodes + 2 HA Proxy nodes
req.NodeCount = 5
setCloneNodesParameters(req, database)

// Default action arguments
actionArguments := map[string]string{
/* Non-Configurable from additionalArguments*/
"vm_name": database.GetName(),
"dbserver_description": "DB Server VM for " + database.GetName(),
"db_password": dbPassword,
}

// Appending/overwriting database actionArguments to actionArguments
if err := setConfiguredActionArguments(database, actionArguments); err != nil {
return nil, err
}

// Converting action arguments map to list and appending to req.ActionArguments
req.ActionArguments = append(req.ActionArguments, convertMapToActionArguments(actionArguments)...)

// Appending LCMConfig Details if specified
if err := appendLCMConfigDetailsToRequest(req, database.GetAdditionalArguments()); err != nil {
return nil, err
}

return req, nil
}

func (a *MySqlRequestAppender) appendCloningRequest(req *DatabaseCloneRequest, database DatabaseInterface, reqData map[string]interface{}) (*DatabaseCloneRequest, error) {
req.SSHPublicKey = reqData[common.NDB_PARAM_SSH_PUBLIC_KEY].(string)
dbPassword := reqData[common.NDB_PARAM_PASSWORD].(string)
Expand Down
8 changes: 6 additions & 2 deletions ndb_api/common_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ func GetDatabasePortByType(dbType string) int32 {
}

// Get specific implementation of the DBProvisionRequestAppender interface based on the provided databaseType
func GetRequestAppender(databaseType string) (requestAppender RequestAppender, err error) {
func GetRequestAppender(databaseType string, isHighAvailability bool) (requestAppender RequestAppender, err error) {
switch databaseType {
case common.DATABASE_TYPE_MYSQL:
requestAppender = &MySqlRequestAppender{}
case common.DATABASE_TYPE_POSTGRES:
requestAppender = &PostgresRequestAppender{}
if isHighAvailability {
requestAppender = &PostgresHARequestAppender{}
} else {
requestAppender = &PostgresRequestAppender{}
}
case common.DATABASE_TYPE_MONGODB:
requestAppender = &MongoDbRequestAppender{}
case common.DATABASE_TYPE_MSSQL:
Expand Down
12 changes: 6 additions & 6 deletions ndb_api/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ type ActionArgument struct {
}

type Node struct {
VmName string `json:"vmName"`
ComputeProfileId string `json:"computeProfileId,omitempty"`
NetworkProfileId string `json:"networkProfileId,omitempty"`
NewDbServerTimeZone string `json:"newDbServerTimeZone,omitempty"`
NxClusterId string `json:"nxClusterId,omitempty"`
Properties []string `json:"properties"`
VmName string `json:"vmName"`
ComputeProfileId string `json:"computeProfileId,omitempty"`
NetworkProfileId string `json:"networkProfileId,omitempty"`
NewDbServerTimeZone string `json:"newDbServerTimeZone,omitempty"`
NxClusterId string `json:"nxClusterId,omitempty"`
Properties []map[string]string `json:"properties"`
}

type Property struct {
Expand Down
Loading