Skip to content

Commit

Permalink
Add option to configure multiple PostgreSQL instances
Browse files Browse the repository at this point in the history
  • Loading branch information
glrf committed Jul 21, 2023
1 parent bdf54f9 commit 7c5aba6
Show file tree
Hide file tree
Showing 7 changed files with 501 additions and 0 deletions.
34 changes: 34 additions & 0 deletions apis/vshn/v1/dbaas_vshn_postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,40 @@ type VSHNPostgreSQLParameters struct {

// UpdateStrategy indicates when updates to the instance spec will be applied.
UpdateStrategy VSHNPostgreSQLUpdateStrategy `json:"updateStrategy,omitempty"`

// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=3

// Instances configures the number of PostgreSQL instances for the cluster.
// Each instance contains one Postgres server.
// Out of all of the Postgres servers, one is elected as the primary, the rest remain as read-only replicas.
Instances int `json:"instances,omitempty"`

// This section allows to configure Postgres replication mode and HA roles groups.
//
// The main replication group is implicit and contains the total number of instances less the sum of all instances in other replication groups.
Replication VSHNPostgreSQLReplicationStrategy `json:"replication,omitempty"`
}

type VSHNPostgreSQLReplicationStrategy struct {
// +kubebuilder:validation:Enum="async";"sync";"strict-sync"

// Mode defines the replication mode applied to the whole cluster. Possible values are: "async"(default), "sync", and "strict-sync"
//
// "async": When in asynchronous mode the cluster is allowed to lose some committed transactions.
// When the primary server fails or becomes unavailable for any other reason a sufficiently healthy standby will automatically be promoted to primary.
// Any transactions that have not been replicated to that standby remain in a “forked timeline” on the primary, and are effectively unrecoverable
//
// "sync": When in synchronous mode a standby will not be promoted unless it is certain that the standby contains all transactions that may have returned a successful commit status to client.
// This means that the system may be unavailable for writes even though some servers are available.
//
// "strict-sync": When it is absolutely necessary to guarantee that each write is stored durably on at least two nodes, use the strict synchronous mode.
// This mode prevents synchronous replication to be switched off on the primary when no synchronous standby candidates are available.
// As a downside, the primary will not be available for writes, blocking all client write requests until at least one synchronous replica comes up.
//
// NOTE: We recommend to always use three intances when setting the mode to "strict-sync".
Mode string `json:"mode,omitempty"`
}

const VSHNPostgreSQLUpdateStrategyTypeImmediate = "Immediate"
Expand Down
16 changes: 16 additions & 0 deletions apis/vshn/v1/zz_generated.deepcopy.go

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

4 changes: 4 additions & 0 deletions cmd/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ var images = map[string][]runtime.Transform{
Name: "extensions",
TransformFunc: vpf.AddExtensions,
},
{
Name: "replication",
TransformFunc: vpf.ConfigureReplication,
},
},
"redis": {
{
Expand Down
17 changes: 17 additions & 0 deletions crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ spec:
description: Enabled specifies if the instance should use encrypted storage for the instance.
type: boolean
type: object
instances:
default: 1
description: Instances configures the number of PostgreSQL instances for the cluster. Each instance contains one Postgres server. Out of all of the Postgres servers, one is elected as the primary, the rest remain as read-only replicas.
maximum: 3
minimum: 1
type: integer
maintenance:
description: Maintenance contains settings to control the maintenance of an instance.
properties:
Expand Down Expand Up @@ -3493,6 +3499,17 @@ spec:
type: string
type: array
type: object
replication:
description: "This section allows to configure Postgres replication mode and HA roles groups. \n The main replication group is implicit and contains the total number of instances less the sum of all instances in other replication groups."
properties:
mode:
description: "Mode defines the replication mode applied to the whole cluster. Possible values are: \"async\"(default), \"sync\", and \"strict-sync\" \n \"async\": When in asynchronous mode the cluster is allowed to lose some committed transactions. When the primary server fails or becomes unavailable for any other reason a sufficiently healthy standby will automatically be promoted to primary. Any transactions that have not been replicated to that standby remain in a “forked timeline” on the primary, and are effectively unrecoverable \n \"sync\": When in synchronous mode a standby will not be promoted unless it is certain that the standby contains all transactions that may have returned a successful commit status to client. This means that the system may be unavailable for writes even though some servers are available. \n \"strict-sync\": When it is absolutely necessary to guarantee that each write is stored durably on at least two nodes, use the strict synchronous mode. This mode prevents synchronous replication to be switched off on the primary when no synchronous standby candidates are available. As a downside, the primary will not be available for writes, blocking all client write requests until at least one synchronous replica comes up. \n NOTE: We recommend to always use three intances when setting the mode to \"strict-sync\"."
enum:
- async
- sync
- strict-sync
type: string
type: object
restore:
description: Restore contains settings to control the restore of an instance.
properties:
Expand Down
45 changes: 45 additions & 0 deletions pkg/comp-functions/functions/vshn-postgres-func/replication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package vshnpostgres

import (
"context"

stackgresv1 "github.com/vshn/appcat/apis/stackgres/v1"
vshnv1 "github.com/vshn/appcat/apis/vshn/v1"
"github.com/vshn/appcat/pkg/comp-functions/runtime"
"k8s.io/utils/pointer"
)

// ConfigureReplication configures the stackgres replication based on the claim
func ConfigureReplication(ctx context.Context, iof *runtime.Runtime) runtime.Result {
comp := &vshnv1.VSHNPostgreSQL{}
err := iof.Desired.GetComposite(ctx, comp)
if err != nil {
return runtime.NewFatalErr(ctx, "Cannot get composite from function io", err)
}
cluster := &stackgresv1.SGCluster{}
err = iof.Desired.GetFromObject(ctx, cluster, "cluster")
if err != nil {
return runtime.NewFatalErr(ctx, "not able to get cluster", err)
}

cluster = configureReplication(ctx, comp, cluster)

err = iof.Desired.PutIntoObject(ctx, cluster, "cluster")
if err != nil {
return runtime.NewFatalErr(ctx, "cannot save cluster to functionIO", err)
}
return runtime.NewNormal()
}

func configureReplication(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, cluster *stackgresv1.SGCluster) *stackgresv1.SGCluster {
cluster.Spec.Replication = &stackgresv1.SGClusterSpecReplication{
Mode: pointer.String("async"),
SyncInstances: pointer.Int(1),
}
cluster.Spec.Instances = comp.Spec.Parameters.Instances
if comp.Spec.Parameters.Instances > 1 && comp.Spec.Parameters.Replication.Mode != "async" && comp.Spec.Parameters.Replication.Mode != "" {
cluster.Spec.Replication.Mode = pointer.String(comp.Spec.Parameters.Replication.Mode)
cluster.Spec.Replication.SyncInstances = pointer.Int(comp.Spec.Parameters.Instances - 1)
}
return cluster
}
143 changes: 143 additions & 0 deletions pkg/comp-functions/functions/vshn-postgres-func/replication_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package vshnpostgres

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
stackgresv1 "github.com/vshn/appcat/apis/stackgres/v1"
vshnv1 "github.com/vshn/appcat/apis/vshn/v1"
"github.com/vshn/appcat/pkg/comp-functions/functions/commontest"
"github.com/vshn/appcat/pkg/comp-functions/runtime"
)

func Test_ConfigureReplication(t *testing.T) {
ctx := context.TODO()
iof := commontest.LoadRuntimeFromFile(t, "vshn-postgres/replication/01-GivenMulitInstance.yaml")

res := ConfigureReplication(ctx, iof)
assert.Equal(t, runtime.NewNormal(), res)

cluster := &stackgresv1.SGCluster{}
assert.NoError(t, iof.Desired.GetFromObject(ctx, cluster, "cluster"))
assert.Equal(t, 3, cluster.Spec.Instances)
assert.Equal(t, "sync", *cluster.Spec.Replication.Mode)
assert.Equal(t, 2, *cluster.Spec.Replication.SyncInstances)
}

func Test_configureReplication_SingleInstance(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 1,
Replication: vshnv1.VSHNPostgreSQLReplicationStrategy{},
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 1, cluster.Spec.Instances)
assert.Equal(t, "async", *cluster.Spec.Replication.Mode)
}

func Test_configureReplication_SingleInstance_Sync(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 1,
Replication: vshnv1.VSHNPostgreSQLReplicationStrategy{
Mode: "sync",
},
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 1, cluster.Spec.Instances)
assert.Equal(t, "async", *cluster.Spec.Replication.Mode)
}

func Test_configureReplication_MultiInstance_Async(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 2,
Replication: vshnv1.VSHNPostgreSQLReplicationStrategy{
Mode: "async",
},
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 2, cluster.Spec.Instances)
assert.Equal(t, "async", *cluster.Spec.Replication.Mode)
}

func Test_configureReplication_MultiInstance_Sync(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 2,
Replication: vshnv1.VSHNPostgreSQLReplicationStrategy{
Mode: "sync",
},
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 2, cluster.Spec.Instances)
assert.Equal(t, "sync", *cluster.Spec.Replication.Mode)
assert.Equal(t, 1, *cluster.Spec.Replication.SyncInstances)
}

func Test_configureReplication_MultiInstance_StrictSync(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 3,
Replication: vshnv1.VSHNPostgreSQLReplicationStrategy{
Mode: "strict-sync",
},
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 3, cluster.Spec.Instances)
assert.Equal(t, "strict-sync", *cluster.Spec.Replication.Mode)
assert.Equal(t, 2, *cluster.Spec.Replication.SyncInstances)
}

func Test_configureReplication_MultiInstance_Default(t *testing.T) {
ctx := context.Background()
comp := &vshnv1.VSHNPostgreSQL{
Spec: vshnv1.VSHNPostgreSQLSpec{
Parameters: vshnv1.VSHNPostgreSQLParameters{
Instances: 3,
},
},
}
cluster := &stackgresv1.SGCluster{}

cluster = configureReplication(ctx, comp, cluster)

assert.Equal(t, 3, cluster.Spec.Instances)
assert.Equal(t, "async", *cluster.Spec.Replication.Mode)
}
Loading

0 comments on commit 7c5aba6

Please sign in to comment.