Merge branch 'master' into sketch-e2e-tests-update-client
This commit is contained in:
commit
42f5ac292f
|
|
@ -13,6 +13,7 @@ rules:
|
|||
- acid.zalan.do
|
||||
resources:
|
||||
- postgresqls
|
||||
- postgresqls/status
|
||||
- operatorconfigurations
|
||||
verbs:
|
||||
- "*"
|
||||
|
|
|
|||
|
|
@ -32,16 +32,19 @@ kubectl create -f manifests/postgres-operator.yaml # deployment
|
|||
|
||||
## Helm chart
|
||||
|
||||
Another possibility is using a provided [Helm](https://helm.sh/) chart which
|
||||
saves you these steps. Therefore, you would need to install the helm CLI on your
|
||||
machine. After initializing helm (and its server component Tiller) in your local
|
||||
cluster you can install the operator chart.
|
||||
Alternatively, the operator can be installed by using the provided [Helm](https://helm.sh/)
|
||||
chart which saves you the manual steps. Therefore, you would need to install
|
||||
the helm CLI on your machine. After initializing helm (and its server
|
||||
component Tiller) in your local cluster you can install the operator chart.
|
||||
You can define a release name that is prepended to the operator resource's
|
||||
names. Use `--name zalando` to match with the default service account name
|
||||
as older operator versions do not support custom names for service accounts.
|
||||
|
||||
```bash
|
||||
# 1) initialize helm
|
||||
helm init
|
||||
# 2) install postgres-operator chart
|
||||
helm install --name postgres-operator ./charts/postgres-operator
|
||||
helm install --name zalando ./charts/postgres-operator
|
||||
```
|
||||
|
||||
## Create a Postgres cluster
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ rules:
|
|||
- acid.zalan.do
|
||||
resources:
|
||||
- postgresqls
|
||||
- postgresqls/status
|
||||
- operatorconfigurations
|
||||
verbs:
|
||||
- "*"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ package v1
|
|||
|
||||
// ClusterStatusUnknown etc : status of a Postgres cluster known to the operator
|
||||
const (
|
||||
ClusterStatusUnknown PostgresStatus = ""
|
||||
ClusterStatusCreating PostgresStatus = "Creating"
|
||||
ClusterStatusUpdating PostgresStatus = "Updating"
|
||||
ClusterStatusUpdateFailed PostgresStatus = "UpdateFailed"
|
||||
ClusterStatusSyncFailed PostgresStatus = "SyncFailed"
|
||||
ClusterStatusAddFailed PostgresStatus = "CreateFailed"
|
||||
ClusterStatusRunning PostgresStatus = "Running"
|
||||
ClusterStatusInvalid PostgresStatus = "Invalid"
|
||||
ClusterStatusUnknown = ""
|
||||
ClusterStatusCreating = "Creating"
|
||||
ClusterStatusUpdating = "Updating"
|
||||
ClusterStatusUpdateFailed = "UpdateFailed"
|
||||
ClusterStatusSyncFailed = "SyncFailed"
|
||||
ClusterStatusAddFailed = "CreateFailed"
|
||||
ClusterStatusRunning = "Running"
|
||||
ClusterStatusInvalid = "Invalid"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
type postgresqlCopy Postgresql
|
||||
type postgresStatusCopy PostgresStatus
|
||||
|
||||
// MarshalJSON converts a maintenance window definition to JSON.
|
||||
func (m *MaintenanceWindow) MarshalJSON() ([]byte, error) {
|
||||
|
|
@ -69,6 +70,26 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON to the status subresource definition.
|
||||
func (ps *PostgresStatus) UnmarshalJSON(data []byte) error {
|
||||
var (
|
||||
tmp postgresStatusCopy
|
||||
status string
|
||||
)
|
||||
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
metaErr := json.Unmarshal(data, &status)
|
||||
if metaErr != nil {
|
||||
return fmt.Errorf("Could not parse status: %v; err %v", string(data), metaErr)
|
||||
}
|
||||
tmp.PostgresClusterStatus = status
|
||||
}
|
||||
*ps = PostgresStatus(tmp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON into the PostgreSQL object.
|
||||
func (p *Postgresql) UnmarshalJSON(data []byte) error {
|
||||
var tmp postgresqlCopy
|
||||
|
|
@ -81,7 +102,7 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
tmp.Error = err.Error()
|
||||
tmp.Status = ClusterStatusInvalid
|
||||
tmp.Status = PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid}
|
||||
|
||||
*p = Postgresql(tmp)
|
||||
|
||||
|
|
@ -91,10 +112,10 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error {
|
|||
|
||||
if clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID); err != nil {
|
||||
tmp2.Error = err.Error()
|
||||
tmp2.Status = ClusterStatusInvalid
|
||||
tmp2.Status = PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid}
|
||||
} else if err := validateCloneClusterDescription(&tmp2.Spec.Clone); err != nil {
|
||||
tmp2.Error = err.Error()
|
||||
tmp2.Status = ClusterStatusInvalid
|
||||
tmp2.Status = PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid}
|
||||
} else {
|
||||
tmp2.Spec.ClusterName = clusterName
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type Postgresql struct {
|
|||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec PostgresSpec `json:"spec"`
|
||||
Status PostgresStatus `json:"status,omitempty"`
|
||||
Status PostgresStatus `json:"status"`
|
||||
Error string `json:"-"`
|
||||
}
|
||||
|
||||
|
|
@ -129,4 +129,6 @@ type Sidecar struct {
|
|||
type UserFlags []string
|
||||
|
||||
// PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
|
||||
type PostgresStatus string
|
||||
type PostgresStatus struct {
|
||||
PostgresClusterStatus string `json:"PostgresClusterStatus"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,12 +85,22 @@ func validateCloneClusterDescription(clone *CloneDescription) error {
|
|||
}
|
||||
|
||||
// Success of the current Status
|
||||
func (status PostgresStatus) Success() bool {
|
||||
return status != ClusterStatusAddFailed &&
|
||||
status != ClusterStatusUpdateFailed &&
|
||||
status != ClusterStatusSyncFailed
|
||||
func (postgresStatus PostgresStatus) Success() bool {
|
||||
return postgresStatus.PostgresClusterStatus != ClusterStatusAddFailed &&
|
||||
postgresStatus.PostgresClusterStatus != ClusterStatusUpdateFailed &&
|
||||
postgresStatus.PostgresClusterStatus != ClusterStatusSyncFailed
|
||||
}
|
||||
|
||||
func (status PostgresStatus) String() string {
|
||||
return string(status)
|
||||
// Running status of cluster
|
||||
func (postgresStatus PostgresStatus) Running() bool {
|
||||
return postgresStatus.PostgresClusterStatus == ClusterStatusRunning
|
||||
}
|
||||
|
||||
// Creating status of cluster
|
||||
func (postgresStatus PostgresStatus) Creating() bool {
|
||||
return postgresStatus.PostgresClusterStatus == ClusterStatusCreating
|
||||
}
|
||||
|
||||
func (postgresStatus PostgresStatus) String() string {
|
||||
return postgresStatus.PostgresClusterStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,101 +111,139 @@ var maintenanceWindows = []struct {
|
|||
{[]byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")},
|
||||
{[]byte(`"Mon:00:00-00:00:00"`), MaintenanceWindow{}, errors.New("could not parse end time: incorrect time format")}}
|
||||
|
||||
var postgresStatus = []struct {
|
||||
in []byte
|
||||
out PostgresStatus
|
||||
err error
|
||||
}{
|
||||
{[]byte(`{"PostgresClusterStatus":"Running"}`),
|
||||
PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
|
||||
{[]byte(`{"PostgresClusterStatus":""}`),
|
||||
PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil},
|
||||
{[]byte(`"Running"`),
|
||||
PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
|
||||
{[]byte(`""`),
|
||||
PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil}}
|
||||
|
||||
var unmarshalCluster = []struct {
|
||||
in []byte
|
||||
out Postgresql
|
||||
marshal []byte
|
||||
err error
|
||||
}{{
|
||||
[]byte(`{
|
||||
"kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
|
||||
Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
}{
|
||||
// example with simple status field
|
||||
{
|
||||
in: []byte(`{
|
||||
"kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
|
||||
out: Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acid-testcluster1",
|
||||
},
|
||||
Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
|
||||
// This error message can vary between Go versions, so compute it for the current version.
|
||||
Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acid-testcluster1",
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`),
|
||||
err: nil},
|
||||
// example with /status subresource
|
||||
{
|
||||
in: []byte(`{
|
||||
"kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
|
||||
out: Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acid-testcluster1",
|
||||
},
|
||||
Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
|
||||
// This error message can vary between Go versions, so compute it for the current version.
|
||||
Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(),
|
||||
},
|
||||
Status: ClusterStatusInvalid,
|
||||
// This error message can vary between Go versions, so compute it for the current version.
|
||||
Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(),
|
||||
},
|
||||
[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), nil},
|
||||
{[]byte(`{
|
||||
"kind": "Postgresql",
|
||||
"apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {
|
||||
"name": "acid-testcluster1"
|
||||
},
|
||||
"spec": {
|
||||
"teamId": "ACID",
|
||||
"volume": {
|
||||
"size": "5Gi",
|
||||
"storageClass": "SSD"
|
||||
},
|
||||
"numberOfInstances": 2,
|
||||
"users": {
|
||||
"zalando": [
|
||||
"superuser",
|
||||
"createdb"
|
||||
]
|
||||
},
|
||||
"allowedSourceRanges": [
|
||||
"127.0.0.1/32"
|
||||
],
|
||||
"postgresql": {
|
||||
"version": "9.6",
|
||||
"parameters": {
|
||||
"shared_buffers": "32MB",
|
||||
"max_connections": "10",
|
||||
"log_statement": "all"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "10m",
|
||||
"memory": "50Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "300m",
|
||||
"memory": "3000Mi"
|
||||
}
|
||||
},
|
||||
"clone" : {
|
||||
"cluster": "acid-batman"
|
||||
},
|
||||
"patroni": {
|
||||
"initdb": {
|
||||
"encoding": "UTF8",
|
||||
"locale": "en_US.UTF-8",
|
||||
"data-checksums": "true"
|
||||
},
|
||||
"pg_hba": [
|
||||
"hostssl all all 0.0.0.0/0 md5",
|
||||
"host all all 0.0.0.0/0 md5"
|
||||
],
|
||||
"ttl": 30,
|
||||
"loop_wait": 10,
|
||||
"retry_timeout": 10,
|
||||
"maximum_lag_on_failover": 33554432,
|
||||
"slots" : {
|
||||
"permanent_logical_1" : {
|
||||
"type" : "logical",
|
||||
"database" : "foo",
|
||||
"plugin" : "pgoutput"
|
||||
}
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
||||
err: nil},
|
||||
// example with detailed input manifest
|
||||
{
|
||||
in: []byte(`{
|
||||
"kind": "Postgresql",
|
||||
"apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {
|
||||
"name": "acid-testcluster1"
|
||||
},
|
||||
"spec": {
|
||||
"teamId": "ACID",
|
||||
"volume": {
|
||||
"size": "5Gi",
|
||||
"storageClass": "SSD"
|
||||
},
|
||||
"numberOfInstances": 2,
|
||||
"users": {
|
||||
"zalando": [
|
||||
"superuser",
|
||||
"createdb"
|
||||
]
|
||||
},
|
||||
"allowedSourceRanges": [
|
||||
"127.0.0.1/32"
|
||||
],
|
||||
"postgresql": {
|
||||
"version": "9.6",
|
||||
"parameters": {
|
||||
"shared_buffers": "32MB",
|
||||
"max_connections": "10",
|
||||
"log_statement": "all"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "10m",
|
||||
"memory": "50Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "300m",
|
||||
"memory": "3000Mi"
|
||||
}
|
||||
},
|
||||
"clone" : {
|
||||
"cluster": "acid-batman"
|
||||
},
|
||||
"patroni": {
|
||||
"initdb": {
|
||||
"encoding": "UTF8",
|
||||
"locale": "en_US.UTF-8",
|
||||
"data-checksums": "true"
|
||||
},
|
||||
"pg_hba": [
|
||||
"hostssl all all 0.0.0.0/0 md5",
|
||||
"host all all 0.0.0.0/0 md5"
|
||||
],
|
||||
"ttl": 30,
|
||||
"loop_wait": 10,
|
||||
"retry_timeout": 10,
|
||||
"maximum_lag_on_failover": 33554432,
|
||||
"slots" : {
|
||||
"permanent_logical_1" : {
|
||||
"type" : "logical",
|
||||
"database" : "foo",
|
||||
"plugin" : "pgoutput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maintenanceWindows": [
|
||||
"Mon:01:00-06:00",
|
||||
"Sat:00:00-04:00",
|
||||
"05:00-05:15"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maintenanceWindows": [
|
||||
"Mon:01:00-06:00",
|
||||
"Sat:00:00-04:00",
|
||||
"05:00-05:15"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
Postgresql{
|
||||
}`),
|
||||
out: Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
|
|
@ -273,10 +311,12 @@ var unmarshalCluster = []struct {
|
|||
},
|
||||
Error: "",
|
||||
},
|
||||
[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}}}`), nil},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
||||
err: nil},
|
||||
// example with teamId set in input
|
||||
{
|
||||
[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`),
|
||||
Postgresql{
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`),
|
||||
out: Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
|
|
@ -285,10 +325,12 @@ var unmarshalCluster = []struct {
|
|||
Name: "teapot-testcluster1",
|
||||
},
|
||||
Spec: PostgresSpec{TeamID: "acid"},
|
||||
Status: ClusterStatusInvalid,
|
||||
Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
|
||||
Error: errors.New("name must match {TEAM}-{NAME} format").Error(),
|
||||
},
|
||||
[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), nil},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
||||
err: nil},
|
||||
// clone example
|
||||
{
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
|
||||
out: Postgresql{
|
||||
|
|
@ -308,22 +350,26 @@ var unmarshalCluster = []struct {
|
|||
},
|
||||
Error: "",
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}}}`), err: nil},
|
||||
{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
|
||||
Postgresql{},
|
||||
[]byte{},
|
||||
errors.New("unexpected end of JSON input")},
|
||||
{[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`),
|
||||
Postgresql{},
|
||||
[]byte{},
|
||||
errors.New("invalid character 'q' looking for beginning of value")}}
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
||||
err: nil},
|
||||
// erroneous examples
|
||||
{
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
|
||||
out: Postgresql{},
|
||||
marshal: []byte{},
|
||||
err: errors.New("unexpected end of JSON input")},
|
||||
{
|
||||
in: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
||||
out: Postgresql{},
|
||||
marshal: []byte{},
|
||||
err: errors.New("invalid character 'q' looking for beginning of value")}}
|
||||
|
||||
var postgresqlList = []struct {
|
||||
in []byte
|
||||
out PostgresqlList
|
||||
err error
|
||||
}{
|
||||
{[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"9.6"},"teamId":"acid","volume":{"size":"10Gi"}},"status":"Running"}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
|
||||
{[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"9.6"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
|
||||
PostgresqlList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
|
|
@ -350,8 +396,10 @@ var postgresqlList = []struct {
|
|||
AllowedSourceRanges: []string{"185.85.220.0/22"},
|
||||
NumberOfInstances: 1,
|
||||
},
|
||||
Status: ClusterStatusRunning,
|
||||
Error: "",
|
||||
Status: PostgresStatus{
|
||||
PostgresClusterStatus: ClusterStatusRunning,
|
||||
},
|
||||
Error: "",
|
||||
}},
|
||||
},
|
||||
nil},
|
||||
|
|
@ -469,6 +517,25 @@ func TestMarshalMaintenanceWindow(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalPostgresStatus(t *testing.T) {
|
||||
for _, tt := range postgresStatus {
|
||||
var ps PostgresStatus
|
||||
err := ps.UnmarshalJSON(tt.in)
|
||||
if err != nil {
|
||||
if tt.err == nil || err.Error() != tt.err.Error() {
|
||||
t.Errorf("CR status unmarshal expected error: %v, got %v", tt.err, err)
|
||||
}
|
||||
continue
|
||||
//} else if tt.err != nil {
|
||||
//t.Errorf("Expected error: %v", tt.err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ps, tt.out) {
|
||||
t.Errorf("Expected status: %#v, got: %#v", tt.out, ps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresUnmarshal(t *testing.T) {
|
||||
for _, tt := range unmarshalCluster {
|
||||
var cluster Postgresql
|
||||
|
|
@ -494,12 +561,26 @@ func TestMarshal(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Unmarshal and marshal example to capture api changes
|
||||
var cluster Postgresql
|
||||
err := cluster.UnmarshalJSON(tt.marshal)
|
||||
if err != nil {
|
||||
if tt.err == nil || err.Error() != tt.err.Error() {
|
||||
t.Errorf("Backwards compatibility unmarshal expected error: %v, got: %v", tt.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
expected, err := json.Marshal(cluster)
|
||||
if err != nil {
|
||||
t.Errorf("Backwards compatibility marshal error: %v", err)
|
||||
}
|
||||
|
||||
m, err := json.Marshal(tt.out)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(m, tt.marshal) {
|
||||
t.Errorf("Marshal Postgresql \nexpected: %q, \ngot: %q", string(tt.marshal), string(m))
|
||||
if !bytes.Equal(m, expected) {
|
||||
t.Errorf("Marshal Postgresql \nexpected: %q, \ngot: %q", string(expected), string(m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -479,6 +479,22 @@ func (in *PostgresSpec) DeepCopy() *PostgresSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgresStatus) DeepCopyInto(out *PostgresStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresStatus.
|
||||
func (in *PostgresStatus) DeepCopy() *PostgresStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgresStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgresUsersConfiguration) DeepCopyInto(out *PostgresUsersConfiguration) {
|
||||
*out = *in
|
||||
|
|
@ -501,6 +517,7 @@ func (in *Postgresql) DeepCopyInto(out *Postgresql) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package cluster
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
|
@ -19,8 +20,6 @@ import (
|
|||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
"github.com/zalando/postgres-operator/pkg/spec"
|
||||
"github.com/zalando/postgres-operator/pkg/util"
|
||||
|
|
@ -149,21 +148,24 @@ func (c *Cluster) setProcessName(procName string, args ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) setStatus(status acidv1.PostgresStatus) {
|
||||
// TODO: eventually switch to updateStatus() for kubernetes 1.11 and above
|
||||
var (
|
||||
err error
|
||||
b []byte
|
||||
)
|
||||
if b, err = json.Marshal(status); err != nil {
|
||||
// SetStatus of Postgres cluster
|
||||
// TODO: eventually switch to updateStatus() for kubernetes 1.11 and above
|
||||
func (c *Cluster) setStatus(status string) {
|
||||
var pgStatus acidv1.PostgresStatus
|
||||
pgStatus.PostgresClusterStatus = status
|
||||
|
||||
patch, err := json.Marshal(struct {
|
||||
PgStatus interface{} `json:"status"`
|
||||
}{&pgStatus})
|
||||
|
||||
if err != nil {
|
||||
c.logger.Errorf("could not marshal status: %v", err)
|
||||
}
|
||||
|
||||
patch := []byte(fmt.Sprintf(`{"status": %s}`, string(b)))
|
||||
// we cannot do a full scale update here without fetching the previous manifest (as the resourceVersion may differ),
|
||||
// however, we could do patch without it. In the future, once /status subresource is there (starting Kubernets 1.11)
|
||||
// we should take advantage of it.
|
||||
newspec, err := c.KubeClient.AcidV1ClientSet.AcidV1().Postgresqls(c.clusterNamespace()).Patch(c.Name, types.MergePatchType, patch)
|
||||
newspec, err := c.KubeClient.AcidV1ClientSet.AcidV1().Postgresqls(c.clusterNamespace()).Patch(c.Name, types.MergePatchType, patch, "status")
|
||||
if err != nil {
|
||||
c.logger.Errorf("could not update status: %v", err)
|
||||
}
|
||||
|
|
@ -172,7 +174,7 @@ func (c *Cluster) setStatus(status acidv1.PostgresStatus) {
|
|||
}
|
||||
|
||||
func (c *Cluster) isNewCluster() bool {
|
||||
return c.Status == acidv1.ClusterStatusCreating
|
||||
return c.Status.Creating()
|
||||
}
|
||||
|
||||
// initUsers populates c.systemUsers and c.pgUsers maps.
|
||||
|
|
|
|||
|
|
@ -20,10 +20,20 @@ const (
|
|||
)
|
||||
|
||||
var logger = logrus.New().WithField("test", "cluster")
|
||||
var cl = New(Config{OpConfig: config.Config{ProtectedRoles: []string{"admin"},
|
||||
Auth: config.Auth{SuperUsername: superUserName,
|
||||
ReplicationUsername: replicationUserName}}},
|
||||
k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger)
|
||||
var cl = New(
|
||||
Config{
|
||||
OpConfig: config.Config{
|
||||
ProtectedRoles: []string{"admin"},
|
||||
Auth: config.Auth{
|
||||
SuperUsername: superUserName,
|
||||
ReplicationUsername: replicationUserName,
|
||||
},
|
||||
},
|
||||
},
|
||||
k8sutil.NewMockKubernetesClient(),
|
||||
acidv1.Postgresql{},
|
||||
logger,
|
||||
)
|
||||
|
||||
func TestInitRobotUsers(t *testing.T) {
|
||||
testName := "TestInitRobotUsers"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
|||
if err != nil {
|
||||
c.logger.Warningf("error while syncing cluster state: %v", err)
|
||||
c.setStatus(acidv1.ClusterStatusSyncFailed)
|
||||
} else if c.Status != acidv1.ClusterStatusRunning {
|
||||
} else if !c.Status.Running() {
|
||||
c.setStatus(acidv1.ClusterStatusRunning)
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -6,82 +6,24 @@ import (
|
|||
"testing"
|
||||
|
||||
b64 "encoding/base64"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/spec"
|
||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
testInfrastructureRolesSecretName = "infrastructureroles-test"
|
||||
)
|
||||
|
||||
type mockSecret struct {
|
||||
v1core.SecretInterface
|
||||
}
|
||||
|
||||
type mockConfigMap struct {
|
||||
v1core.ConfigMapInterface
|
||||
}
|
||||
|
||||
func (c *mockSecret) Get(name string, options metav1.GetOptions) (*v1.Secret, error) {
|
||||
if name != testInfrastructureRolesSecretName {
|
||||
return nil, fmt.Errorf("NotFound")
|
||||
}
|
||||
secret := &v1.Secret{}
|
||||
secret.Name = mockController.opConfig.ClusterNameLabel
|
||||
secret.Data = map[string][]byte{
|
||||
"user1": []byte("testrole"),
|
||||
"password1": []byte("testpassword"),
|
||||
"inrole1": []byte("testinrole"),
|
||||
"foobar": []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
|
||||
}
|
||||
return secret, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *mockConfigMap) Get(name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
|
||||
if name != testInfrastructureRolesSecretName {
|
||||
return nil, fmt.Errorf("NotFound")
|
||||
}
|
||||
configmap := &v1.ConfigMap{}
|
||||
configmap.Name = mockController.opConfig.ClusterNameLabel
|
||||
configmap.Data = map[string]string{
|
||||
"foobar": "{}",
|
||||
}
|
||||
return configmap, nil
|
||||
}
|
||||
|
||||
type MockSecretGetter struct {
|
||||
}
|
||||
|
||||
type MockConfigMapsGetter struct {
|
||||
}
|
||||
|
||||
func (c *MockSecretGetter) Secrets(namespace string) v1core.SecretInterface {
|
||||
return &mockSecret{}
|
||||
}
|
||||
|
||||
func (c *MockConfigMapsGetter) ConfigMaps(namespace string) v1core.ConfigMapInterface {
|
||||
return &mockConfigMap{}
|
||||
}
|
||||
|
||||
func newMockKubernetesClient() k8sutil.KubernetesClient {
|
||||
return k8sutil.KubernetesClient{
|
||||
SecretsGetter: &MockSecretGetter{},
|
||||
ConfigMapsGetter: &MockConfigMapsGetter{},
|
||||
}
|
||||
}
|
||||
|
||||
func newMockController() *Controller {
|
||||
controller := NewController(&spec.ControllerConfig{})
|
||||
controller.opConfig.ClusterNameLabel = "cluster-name"
|
||||
controller.opConfig.InfrastructureRolesSecretName =
|
||||
spec.NamespacedName{Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesSecretName}
|
||||
controller.opConfig.Workers = 4
|
||||
controller.KubeClient = newMockKubernetesClient()
|
||||
controller.KubeClient = k8sutil.NewMockKubernetesClient()
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ package k8sutil
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
b64 "encoding/base64"
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||
"k8s.io/api/core/v1"
|
||||
policybeta1 "k8s.io/api/policy/v1beta1"
|
||||
|
|
@ -15,9 +19,9 @@ import (
|
|||
rbacv1beta1 "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"reflect"
|
||||
|
||||
acidv1client "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// KubernetesClient describes getters for Kubernetes objects
|
||||
|
|
@ -41,6 +45,20 @@ type KubernetesClient struct {
|
|||
AcidV1ClientSet *acidv1client.Clientset
|
||||
}
|
||||
|
||||
type mockSecret struct {
|
||||
v1core.SecretInterface
|
||||
}
|
||||
|
||||
type MockSecretGetter struct {
|
||||
}
|
||||
|
||||
type mockConfigMap struct {
|
||||
v1core.ConfigMapInterface
|
||||
}
|
||||
|
||||
type MockConfigMapsGetter struct {
|
||||
}
|
||||
|
||||
// RestConfig creates REST config
|
||||
func RestConfig(kubeConfig string, outOfCluster bool) (*rest.Config, error) {
|
||||
if outOfCluster {
|
||||
|
|
@ -140,3 +158,49 @@ func SamePDB(cur, new *policybeta1.PodDisruptionBudget) (match bool, reason stri
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *mockSecret) Get(name string, options metav1.GetOptions) (*v1.Secret, error) {
|
||||
if name != "infrastructureroles-test" {
|
||||
return nil, fmt.Errorf("NotFound")
|
||||
}
|
||||
secret := &v1.Secret{}
|
||||
secret.Name = "testcluster"
|
||||
secret.Data = map[string][]byte{
|
||||
"user1": []byte("testrole"),
|
||||
"password1": []byte("testpassword"),
|
||||
"inrole1": []byte("testinrole"),
|
||||
"foobar": []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
|
||||
}
|
||||
return secret, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *mockConfigMap) Get(name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
|
||||
if name != "infrastructureroles-test" {
|
||||
return nil, fmt.Errorf("NotFound")
|
||||
}
|
||||
configmap := &v1.ConfigMap{}
|
||||
configmap.Name = "testcluster"
|
||||
configmap.Data = map[string]string{
|
||||
"foobar": "{}",
|
||||
}
|
||||
return configmap, nil
|
||||
}
|
||||
|
||||
// Secrets to be mocked
|
||||
func (c *MockSecretGetter) Secrets(namespace string) v1core.SecretInterface {
|
||||
return &mockSecret{}
|
||||
}
|
||||
|
||||
// ConfigMaps to be mocked
|
||||
func (c *MockConfigMapsGetter) ConfigMaps(namespace string) v1core.ConfigMapInterface {
|
||||
return &mockConfigMap{}
|
||||
}
|
||||
|
||||
// NewMockKubernetesClient for other tests
|
||||
func NewMockKubernetesClient() KubernetesClient {
|
||||
return KubernetesClient{
|
||||
SecretsGetter: &MockSecretGetter{},
|
||||
ConfigMapsGetter: &MockConfigMapsGetter{},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue