Skip to content
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

Extend Operator to PSQL-HA #199

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
a182070
Initial commit with work from previous semester
irion4686 Mar 23, 2024
b18fc0e
Fixed ReadMe formatting
irion4686 Mar 24, 2024
04b6e17
Added back optional tags on IsHighAvailability field
irion4686 Mar 25, 2024
917228a
Added node struct to allow setting of properties for individual nodes
irion4686 Apr 2, 2024
b937355
Added implementation for the getInstanceNodes method
irion4686 Apr 4, 2024
5ba62b8
Implemented dynamically setting of nodes and basic validation of node…
irion4686 Apr 8, 2024
0d43f84
Dynamically setting all arguments
kpatel0923 Apr 9, 2024
7ed55c9
Changed Node struct to have new NodeProperties struct
irion4686 Apr 11, 2024
77d9f67
Fixed unit tests and added default nodes settings
irion4686 Apr 12, 2024
6cc94b7
cleaning up
irion4686 Apr 16, 2024
d2d16a0
cleaning up
irion4686 Apr 16, 2024
af1bce2
Updated dynamic nodes format
irion4686 Apr 16, 2024
5134ca5
Merge pull request #4 from irion4686/dynamic-nodes
justinorringer Apr 16, 2024
c326980
Added omitempty to nodes to fix existing unit tests
irion4686 Apr 17, 2024
a1b6dd7
Merge pull request #5 from irion4686/dynamic-nodes
irion4686 Apr 18, 2024
ec0daa8
Moved default logic to map and new function
justinorringer Apr 18, 2024
83a0bce
Changed clone to dynamically build Nodes
justinorringer Apr 18, 2024
f3d3d62
Set NodeCount based on actual nodes
justinorringer Apr 18, 2024
16caf1a
Add changes for end-to-end high availability tests
ArcZz Apr 18, 2024
ddd0d6e
edit folder name
ArcZz Apr 18, 2024
ff1f6d9
Update automation/tests/provisioning/pg-ha_test_2/pg-ha_test.go
justinorringer Apr 18, 2024
383ad7b
Added default cluster_name when none given
irion4686 Apr 18, 2024
d4057bf
fixed unit test
irion4686 Apr 18, 2024
108572d
fixed unit test
irion4686 Apr 18, 2024
933a405
Separated postgres HA additional arguments and fixed isHighAvailabili…
irion4686 Apr 21, 2024
ce9e102
moved the appendProvisioningRequest for PostgresHA to be near others
irion4686 Apr 21, 2024
9238fcb
Merge branch 'dev' into ete_ha_test
justinorringer Apr 21, 2024
731a1de
Moving folder, cleanup
justinorringer Apr 21, 2024
ee127e5
Uncommit spacing change
justinorringer Apr 21, 2024
707cb29
Newline
justinorringer Apr 21, 2024
835a1ac
webhook test updates
kpatel0923 Apr 22, 2024
233de04
syntax errors
kpatel0923 Apr 22, 2024
667a9f2
more syntax errors
kpatel0923 Apr 22, 2024
ee2a1e1
Naming issue
kpatel0923 Apr 22, 2024
708b4cf
VmName
kpatel0923 Apr 22, 2024
5886293
properties name
kpatel0923 Apr 22, 2024
c81b5aa
properties
kpatel0923 Apr 22, 2024
95857e4
Node change
kpatel0923 Apr 22, 2024
9f24ced
testing a new change for creating nodes
kpatel0923 Apr 22, 2024
de3903b
Validation for clone and provision
justinorringer Apr 22, 2024
c71d1a0
Correct variable declaration
justinorringer Apr 22, 2024
5879c22
Type errors
justinorringer Apr 22, 2024
5032fbc
Default should run prior to Validate
justinorringer Apr 22, 2024
a087217
Validation for clone and provision
justinorringer Apr 22, 2024
6e753d0
Correct variable declaration
justinorringer Apr 22, 2024
6b108e6
Type errors
justinorringer Apr 22, 2024
666abfd
Default should run prior to Validate
justinorringer Apr 22, 2024
66c9069
Updated tests
irion4686 Apr 22, 2024
20a725d
Updated required nodes to 3 database nodes
irion4686 Apr 22, 2024
31b0234
Merge pull request #12 from irion4686/update-nodes
justinorringer Apr 22, 2024
760ddb2
Adding back the Make changes
justinorringer Apr 23, 2024
1da557a
Merge branch 'dev' into ete_ha_test
justinorringer Apr 23, 2024
4db5552
Merge pull request #8 from irion4686/ete_ha_test
ArcZz Apr 23, 2024
3dcbcd1
Revert "end to end test with custom test case"
justinorringer Apr 23, 2024
348e60c
Merge pull request #13 from irion4686/revert-8-ete_ha_test
justinorringer Apr 23, 2024
f541e13
Removing cloning functionality
justinorringer Apr 23, 2024
7bc5b15
Removed the wrong struct params
justinorringer Apr 23, 2024
cbfc971
Adding back the appendCloningRequest hook needed
justinorringer Apr 23, 2024
f4b05c3
GetInstanceIsHA on clone
justinorringer Apr 23, 2024
bf09f04
Fix test, no IsHighAvailability field on clone
justinorringer Apr 23, 2024
bf3dd2d
Reflect cloning removed in readme
justinorringer Apr 23, 2024
2002a96
Removed the clone tests and added db tests
kpatel0923 Apr 24, 2024
33e9ecd
Merge branch 'dev' into webhook-test-update
justinorringer Apr 24, 2024
497a918
Release v0.5.1 (#198)
mazin-s Apr 22, 2024
eb00f13
Fixed test case
kpatel0923 Apr 24, 2024
1babd0d
Merge branch 'webhook-test-update' of https://github.com/irion4686/nd…
kpatel0923 Apr 24, 2024
bbc9598
Added check to the end to end test to assert that more than 1 node wa…
irion4686 Apr 24, 2024
7188d25
Merge pull request #17 from irion4686/webhook-test-update
justinorringer Apr 24, 2024
6af152c
Merge branch 'dev' into ete_ha_test
justinorringer Apr 25, 2024
4fc1804
Merge pull request #8 from irion4686/ete_ha_test
ArcZz Apr 23, 2024
71688cc
Added check to the end to end test to assert that more than 1 node wa…
irion4686 Apr 24, 2024
02bb089
Merge pull request #18 from irion4686/ete_ha_test
ArcZz Apr 25, 2024
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
19 changes: 14 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.8)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.8)
VERSION ?= 0.5.0
VERSION ?= 0.5.1

# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
Expand Down Expand Up @@ -299,13 +299,22 @@ run-automation-cloning:
fi; \
go test $(DEFAULT_CLONING_ROOT)$$folders -v -timeout 90m

.PHONY: run-automation-provisioning
.PHONY: run-automation-provisioning-si
DEFAULT_PROVISIONING_ROOT := ./automation/tests/provisioning/
PROVISIONING_FOLDERS := ...
run-automation-provisioning:
# change this list to every folder but the pg-ha_test one
SI_PROVISIONING_FOLDERS := ...
run-automation-provisioning-si:
@read -p "Enter the test directories with spacing to run (mongo-si_test mssql-si_test mysql-si_test pg-si_test). Else all directories will be run: " folders; \
if [ -z "$$folders" ]; then \
folders="$(PROVISIONING_FOLDERS)"; \
folders="$(SI_PROVISIONING_FOLDERS)"; \
fi; \
go test $(DEFAULT_PROVISIONING_ROOT)$$folders -v -timeout 90m

.PHONY: run-automation-provisioning-ha
HA_PROVISIONING_FOLDERS := pg-ha_test
run-automation-provisioning-ha:
@read -p "Enter the test directories with spacing to run (pg-ha_test). Else all directories will be run: " folders; \
if [ -z "$$folders" ]; then \
folders="$(HA_PROVISIONING_FOLDERS)"; \
fi; \
go test $(DEFAULT_PROVISIONING_ROOT)$$folders -v -timeout 90m
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ 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
# Name is case-sensitive. ID is the UUID of the profile. Profile should be in the "READY" state
Expand Down Expand Up @@ -214,6 +216,9 @@ 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 @@ -285,6 +290,26 @@ additionalArguments:
windows_domain_profile_id: <domain-profile-id> # NO Default. Must specify vm_db_server_user.
vm_db_server_user: <vm-db-server-use> # NO Default. Must specify windows_domain_profile_id.
vm_win_license_key: <licenseKey> # NO Default.

# Postgres High Availability
additionalArguments:
listener_port: "1111" # Default: "5432"
failover_mode: "Manual" # Default: "Automatic"
proxy_read_port: "1111" # Default: "5001"
listener_port: "1111" # Default: "5432"
proxy_write_port: "1111" # Default: "5000",
enable_synchronous_mode: "true" # Default: "true",
auto_tune_staging_drive: "false" # Default: true",
backup_policy: "primary_only" # Default: "primary_only"
provision_virtual_ip": "true" # Default: "true"
deploy_haproxy: "true" # Default: "true"
node_type: "haproxy" # Default: "database"
allocate_pg_hugepage: "false" # Default: "true"
cluster_database: "false" # Default: "true"
archive_wal_expire_days: "7" # Default: "-1"
enable_peer_auth: "false" # Default: "true"
cluster_name: "<cluster-name>"
patroni_cluster_name: "<patroni>"
```

Cloning Additional Arguments:
Expand Down
18 changes: 18 additions & 0 deletions api/v1alpha1/database_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ type Instance struct {
// +optional
// Additional database engine specific arguments
AdditionalArguments map[string]string `json:"additionalArguments"`
// +optional
IsHighAvailability bool `json:"isHighAvailability"`
// +optional
Nodes []*Node `json:"nodes,omitempty"`
}

type Node struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to validate this in web hooks. e.g validate vmNames being unique, properties correctly defined, etc.

// +optional
VmName string `json:"vmName"`
Properties NodeProperties `json:"properties"`
}

type NodeProperties struct {
justinorringer marked this conversation as resolved.
Show resolved Hide resolved
NodeType string `json:"node_type"`
// +optional
Role string `json:"role"`
// +optional
FailoverMode string `json:"failover_mode"`
}

type Clone struct {
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/database_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func (r *Database) ValidateDelete() (admission.Warnings, error) {
}

/* Checks if configured additional arguments are valid or not and returns the corresponding additional arguments. If error is nil valid, else invalid */
func additionalArgumentsValidationCheck(isClone bool, dbType string, specifiedAdditionalArguments map[string]string) error {
func additionalArgumentsValidationCheck(isClone bool, dbType string, isHA bool, specifiedAdditionalArguments map[string]string) error {
// Empty additionalArguments is always valid
if specifiedAdditionalArguments == nil {
return nil
}

allowedAdditionalArguments, err := util.GetAllowedAdditionalArguments(isClone, dbType)
allowedAdditionalArguments, err := util.GetAllowedAdditionalArguments(isClone, dbType, isHA)

// Invalid type returns error
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions api/v1alpha1/node_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package v1alpha1

// validate the Node and NodeProperties passed are valid
// e.g validate vmNames being unique, properties correctly defined, etc.
// one day move to common/util

import (
"fmt"
)

var (
typeOptions = map[string]bool{"database": true, "haproxy": true}
roleOptions = map[string]bool{"primary": true, "secondary": true}
failoverOptions = map[string]bool{"Automatic": true, "Manual": true}
)

func ValidateNodes(nodes []*Node, isHighAvailability bool) error {
if !isHighAvailability {
return nil
}

databaseNodeCount := 0
vmNames := make(map[string]bool) // for validating that vmnames are unique
for _, node := range nodes {
if node.Properties.NodeType == "database" {
databaseNodeCount++
}

if err := ValidateNodeProperties(node.Properties); err != nil {
return err
}

if _, ok := vmNames[node.VmName]; ok {
return fmt.Errorf("vmName %s is already specified", node.VmName)
}
vmNames[node.VmName] = true
}

if databaseNodeCount < 3 {
return fmt.Errorf("high Availability requires at least 3 nodes database nodes")
}
return nil
}

func ValidateNodeProperties(np NodeProperties) error {
if !typeOptions[np.NodeType] {
return fmt.Errorf("invalid NodeType in Node Properties: %s", np.NodeType)
}

if !roleOptions[np.Role] {
return fmt.Errorf("invalid Role in Node Properties: %s", np.Role)
}

if !failoverOptions[np.FailoverMode] {
return fmt.Errorf("invalid FailoverMode in Node Properties: %s", np.FailoverMode)
}

return nil
}
12 changes: 10 additions & 2 deletions api/v1alpha1/webhook_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ func (v *CloningWebhookHandler) validateCreate(spec *DatabaseSpec, errors *field
}
}

if err := additionalArgumentsValidationCheck(spec.IsClone, clone.Type, clone.AdditionalArguments); err != nil {
// HA is not supported when cloning
isHighAvailability := false
if err := additionalArgumentsValidationCheck(spec.IsClone, clone.Type, isHighAvailability, clone.AdditionalArguments); err != nil {
*errors = append(*errors, field.Invalid(clonePath.Child("additionalArguments"), clone.AdditionalArguments, err.Error()))
}

databaselog.Info("Exiting validateCreate for clone")
}

Expand Down Expand Up @@ -230,10 +233,15 @@ func (v *ProvisioningWebhookHandler) validateCreate(spec *DatabaseSpec, errors *
))
}

if err := additionalArgumentsValidationCheck(spec.IsClone, instance.Type, instance.AdditionalArguments); err != nil {
if err := additionalArgumentsValidationCheck(spec.IsClone, instance.Type, instance.IsHighAvailability, instance.AdditionalArguments); err != nil {
*errors = append(*errors, field.Invalid(instancePath.Child("additionalArguments"), instance.AdditionalArguments, err.Error()))
}

// Validate nodes for HA
if err := ValidateNodes(instance.Nodes, instance.IsHighAvailability); err != nil {
*errors = append(*errors, field.Invalid(instancePath.Child("nodes"), instance.Nodes, err.Error()))
}

databaselog.Info("Exiting validateCreate for provisioning")
}

Expand Down
144 changes: 144 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) {
justinorringer marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -353,6 +354,139 @@ var _ = Describe("Webhook Tests", func() {
Expect(errMsg).To(ContainSubstring(fmt.Sprintf("additional arguments validation for type: %s failed!", common.DATABASE_TYPE_MSSQL)))
})
})

When("Postgres specified with IsHighAvailability", func() {
It("Should have zero nodes and IsHighAvailability set to true", func() {
db := createDefaultDatabase("db15")
db.Spec.Instance.AdditionalArguments = map[string]string{
"failover_mode": "Automatic",
"proxy_read_port": "5001",
"listener_port": "5432",
"proxy_write_port": "5000",
"enable_synchronous_mode": "true",
"auto_tune_staging_drive": "true",
"backup_policy": "primary_only",
"provision_virtual_ip": "true",
"deploy_haproxy": "true",
"node_type": "database",
"allocate_pg_hugepage": "false",
"cluster_database": "false",
"archive_wal_expire_days": "-1",
"enable_peer_auth": "false",
"cluster_name": "psqlcluster",
"patroni_cluster_name": "patroni",
}
db.Spec.Instance.IsHighAvailability = true
db.Spec.Instance.Nodes = nil

err := k8sClient.Create(context.Background(), db)
Expect(err).To(HaveOccurred())
})

It("Should have 5 nodes and IsHighAvailability set to true", func() {
db := createDefaultDatabase("db16")
db.Spec.Instance.AdditionalArguments = map[string]string{
"failover_mode": "Automatic",
"proxy_read_port": "5001",
"listener_port": "5432",
"proxy_write_port": "5000",
"enable_synchronous_mode": "true",
"auto_tune_staging_drive": "true",
"backup_policy": "primary_only",
"provision_virtual_ip": "true",
"deploy_haproxy": "true",
"node_type": "database",
"allocate_pg_hugepage": "false",
"cluster_database": "false",
"archive_wal_expire_days": "-1",
"enable_peer_auth": "false",
"cluster_name": "psqlcluster",
"patroni_cluster_name": "patroni",
}
primaryProp := createDefaultNodeProperties("database", "primary")
secondaryProp := createDefaultNodeProperties("database", "secondary")
proxyProp := createDefaultNodeProperties("haproxy", "secondary")
db.Spec.Instance.IsHighAvailability = true
db.Spec.Instance.Nodes = []*Node{
{
VmName: "VM1",
Properties: *primaryProp,
},
{
VmName: "VM2",
Properties: *secondaryProp,
},
{
VmName: "VM3",
Properties: *secondaryProp,
},
{
VmName: "VM4",
Properties: *proxyProp,
},
{
VmName: "VM5",
Properties: *proxyProp,
},
}

err := k8sClient.Create(context.Background(), db)
Expect(err).ToNot(HaveOccurred())
})

It("Should throw error when given 2 nodes", func() {
db := createDefaultDatabase("db17")
db.Spec.Instance.AdditionalArguments = map[string]string{
"failover_mode": "Automatic",
"proxy_read_port": "5001",
"listener_port": "5432",
"proxy_write_port": "5000",
"enable_synchronous_mode": "true",
"auto_tune_staging_drive": "true",
"backup_policy": "primary_only",
"provision_virtual_ip": "true",
"deploy_haproxy": "true",
"node_type": "database",
"allocate_pg_hugepage": "false",
"cluster_database": "false",
"archive_wal_expire_days": "-1",
"enable_peer_auth": "false",
"cluster_name": "psqlcluster",
"patroni_cluster_name": "patroni",
}
primaryProp := createDefaultNodeProperties("database", "primary")
secondaryProp := createDefaultNodeProperties("database", "secondary")
db.Spec.Instance.IsHighAvailability = true
db.Spec.Instance.Nodes = []*Node{
{
VmName: "VM1",
Properties: *primaryProp,
},
{
VmName: "VM2",
Properties: *secondaryProp,
},
}

err := k8sClient.Create(context.Background(), db)
Expect(err).To(HaveOccurred())
})

It("Should error out for invalid Postgres additionalArguments and IsHighAvailability set to true", func() {
db := createDefaultDatabase("db18")
db.Spec.Instance.AdditionalArguments = map[string]string{
"listener_port": "5432",
"invalid": "invalid",
}
db.Spec.Instance.IsHighAvailability = true

err := k8sClient.Create(context.Background(), db)
Expect(err).To(HaveOccurred())
errMsg := err.(*errors.StatusError).ErrStatus.Message
Expect(errMsg).To(ContainSubstring(fmt.Sprintf("additional arguments validation for type: %s failed!", common.DATABASE_TYPE_POSTGRES)))
})
})

})

Context("Clone checks", func() {
Expand Down Expand Up @@ -595,6 +729,7 @@ var _ = Describe("Webhook Tests", func() {
Expect(errMsg).To(ContainSubstring(fmt.Sprintf("additional arguments validation for type: %s failed!", common.DATABASE_TYPE_MSSQL)))
})
})

})
})

Expand All @@ -615,6 +750,7 @@ func createDefaultDatabase(metadataName string) *Database {
Type: common.DATABASE_TYPE_POSTGRES,
Profiles: &(Profiles{}),
AdditionalArguments: map[string]string{},
IsHighAvailability: HA,
},
},
}
Expand Down Expand Up @@ -643,3 +779,11 @@ func createDefaultClone(metadataName string) *Database {
},
}
}

func createDefaultNodeProperties(node_type, role string) *NodeProperties {
return &NodeProperties{
NodeType: node_type,
Role: role,
FailoverMode: "Automatic",
}
}
Loading