merge with master
This commit is contained in:
commit
a73e89e8b5
|
|
@ -117,6 +117,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
downscaler_annotations:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
enable_init_containers:
|
enable_init_containers:
|
||||||
type: boolean
|
type: boolean
|
||||||
enable_pod_antiaffinity:
|
enable_pod_antiaffinity:
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ configKubernetes:
|
||||||
# keya: valuea
|
# keya: valuea
|
||||||
# keyb: valueb
|
# keyb: valueb
|
||||||
|
|
||||||
|
# list of annotations propagated from cluster manifest to statefulset and deployment
|
||||||
|
# downscaler_annotations:
|
||||||
|
# - deployment-time
|
||||||
|
# - downscaler/*
|
||||||
|
|
||||||
# enables initContainers to run actions before Spilo is started
|
# enables initContainers to run actions before Spilo is started
|
||||||
enable_init_containers: true
|
enable_init_containers: true
|
||||||
# toggles pod anti affinity on the Postgres pods
|
# toggles pod anti affinity on the Postgres pods
|
||||||
|
|
@ -262,11 +267,11 @@ configConnectionPooler:
|
||||||
# docker image
|
# docker image
|
||||||
connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer"
|
connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer"
|
||||||
# max db connections the pooler should hold
|
# max db connections the pooler should hold
|
||||||
connection_pooler_max_db_connections: "60"
|
connection_pooler_max_db_connections: 60
|
||||||
# default pooling mode
|
# default pooling mode
|
||||||
connection_pooler_mode: "transaction"
|
connection_pooler_mode: "transaction"
|
||||||
# number of pooler instances
|
# number of pooler instances
|
||||||
connection_pooler_number_of_instances: "2"
|
connection_pooler_number_of_instances: 2
|
||||||
# default resources
|
# default resources
|
||||||
connection_pooler_default_cpu_request: 500m
|
connection_pooler_default_cpu_request: 500m
|
||||||
connection_pooler_default_memory_request: 100Mi
|
connection_pooler_default_memory_request: 100Mi
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ configKubernetes:
|
||||||
# annotations attached to each database pod
|
# annotations attached to each database pod
|
||||||
# custom_pod_annotations: "keya:valuea,keyb:valueb"
|
# custom_pod_annotations: "keya:valuea,keyb:valueb"
|
||||||
|
|
||||||
|
# list of annotations propagated from cluster manifest to statefulset and deployment
|
||||||
|
# downscaler_annotations: "deployment-time,downscaler/*"
|
||||||
|
|
||||||
# enables initContainers to run actions before Spilo is started
|
# enables initContainers to run actions before Spilo is started
|
||||||
enable_init_containers: "true"
|
enable_init_containers: "true"
|
||||||
# toggles pod anti affinity on the Postgres pods
|
# toggles pod anti affinity on the Postgres pods
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ These parameters are grouped directly under the `spec` key in the manifest.
|
||||||
If `targetContainers` is empty, additional volumes will be mounted only in the `postgres` container.
|
If `targetContainers` is empty, additional volumes will be mounted only in the `postgres` container.
|
||||||
If you set the `all` special item, it will be mounted in all containers (postgres + sidecars).
|
If you set the `all` special item, it will be mounted in all containers (postgres + sidecars).
|
||||||
Else you can set the list of target containers in which the additional volumes will be mounted (eg : postgres, telegraf)
|
Else you can set the list of target containers in which the additional volumes will be mounted (eg : postgres, telegraf)
|
||||||
|
|
||||||
## Postgres parameters
|
## Postgres parameters
|
||||||
|
|
||||||
Those parameters are grouped under the `postgresql` top-level key, which is
|
Those parameters are grouped under the `postgresql` top-level key, which is
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,12 @@ configuration they are grouped under the `kubernetes` key.
|
||||||
of a database created by the operator. If the annotation key is also provided
|
of a database created by the operator. If the annotation key is also provided
|
||||||
by the database definition, the database definition value is used.
|
by the database definition, the database definition value is used.
|
||||||
|
|
||||||
|
* **downscaler_annotations**
|
||||||
|
An array of annotations that should be passed from Postgres CRD on to the
|
||||||
|
statefulset and, if exists, to the connection pooler deployment as well.
|
||||||
|
Regular expressions like `downscaler/*` etc. are also accepted. Can be used
|
||||||
|
with [kube-downscaler](https://github.com/hjacobs/kube-downscaler).
|
||||||
|
|
||||||
* **watched_namespace**
|
* **watched_namespace**
|
||||||
The operator watches for Postgres objects in the given namespace. If not
|
The operator watches for Postgres objects in the given namespace. If not
|
||||||
specified, the value is taken from the operator namespace. A special `*`
|
specified, the value is taken from the operator namespace. A special `*`
|
||||||
|
|
|
||||||
|
|
@ -428,7 +428,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
k8s.api.core_v1.patch_node(current_master_node, patch_readiness_label)
|
k8s.api.core_v1.patch_node(current_master_node, patch_readiness_label)
|
||||||
|
|
||||||
# wait a little before proceeding with the pod distribution test
|
# wait a little before proceeding with the pod distribution test
|
||||||
time.sleep(k8s.RETRY_TIMEOUT_SEC)
|
time.sleep(30)
|
||||||
|
|
||||||
# toggle pod anti affinity to move replica away from master node
|
# toggle pod anti affinity to move replica away from master node
|
||||||
self.assert_distributed_pods(new_master_node, new_replica_nodes, cluster_label)
|
self.assert_distributed_pods(new_master_node, new_replica_nodes, cluster_label)
|
||||||
|
|
@ -465,21 +465,24 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
pg_patch_custom_annotations = {
|
pg_patch_custom_annotations = {
|
||||||
"spec": {
|
"spec": {
|
||||||
"serviceAnnotations": {
|
"serviceAnnotations": {
|
||||||
"annotation.key": "value"
|
"annotation.key": "value",
|
||||||
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_custom_annotations)
|
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_custom_annotations)
|
||||||
|
|
||||||
|
# wait a little before proceeding
|
||||||
|
time.sleep(30)
|
||||||
annotations = {
|
annotations = {
|
||||||
"annotation.key": "value",
|
"annotation.key": "value",
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
self.assertTrue(k8s.check_service_annotations(
|
self.assertTrue(k8s.check_service_annotations(
|
||||||
"cluster-name=acid-service-annotations,spilo-role=master", annotations))
|
"cluster-name=acid-minimal-cluster,spilo-role=master", annotations))
|
||||||
self.assertTrue(k8s.check_service_annotations(
|
self.assertTrue(k8s.check_service_annotations(
|
||||||
"cluster-name=acid-service-annotations,spilo-role=replica", annotations))
|
"cluster-name=acid-minimal-cluster,spilo-role=replica", annotations))
|
||||||
|
|
||||||
# clean up
|
# clean up
|
||||||
unpatch_custom_service_annotations = {
|
unpatch_custom_service_annotations = {
|
||||||
|
|
@ -489,6 +492,40 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
}
|
}
|
||||||
k8s.update_config(unpatch_custom_service_annotations)
|
k8s.update_config(unpatch_custom_service_annotations)
|
||||||
|
|
||||||
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
|
def test_statefulset_annotation_propagation(self):
|
||||||
|
'''
|
||||||
|
Inject annotation to Postgresql CRD and check it's propagation to stateful set
|
||||||
|
'''
|
||||||
|
k8s = self.k8s
|
||||||
|
cluster_label = 'application=spilo,cluster-name=acid-minimal-cluster'
|
||||||
|
|
||||||
|
patch_sset_propagate_annotations = {
|
||||||
|
"data": {
|
||||||
|
"downscaler_annotations": "deployment-time,downscaler/*",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k8s.update_config(patch_sset_propagate_annotations)
|
||||||
|
|
||||||
|
pg_crd_annotations = {
|
||||||
|
"metadata": {
|
||||||
|
"annotations": {
|
||||||
|
"deployment-time": "2020-04-30 12:00:00",
|
||||||
|
"downscaler/downtime_replicas": "0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
|
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_crd_annotations)
|
||||||
|
|
||||||
|
# wait a little before proceeding
|
||||||
|
time.sleep(60)
|
||||||
|
annotations = {
|
||||||
|
"deployment-time": "2020-04-30 12:00:00",
|
||||||
|
"downscaler/downtime_replicas": "0",
|
||||||
|
}
|
||||||
|
self.assertTrue(k8s.check_statefulset_annotations(cluster_label, annotations))
|
||||||
|
|
||||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
def test_taint_based_eviction(self):
|
def test_taint_based_eviction(self):
|
||||||
'''
|
'''
|
||||||
|
|
@ -528,7 +565,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
k8s.update_config(patch_toleration_config)
|
k8s.update_config(patch_toleration_config)
|
||||||
|
|
||||||
# wait a little before proceeding with the pod distribution test
|
# wait a little before proceeding with the pod distribution test
|
||||||
time.sleep(k8s.RETRY_TIMEOUT_SEC)
|
time.sleep(30)
|
||||||
|
|
||||||
# toggle pod anti affinity to move replica away from master node
|
# toggle pod anti affinity to move replica away from master node
|
||||||
self.assert_distributed_pods(new_master_node, new_replica_nodes, cluster_label)
|
self.assert_distributed_pods(new_master_node, new_replica_nodes, cluster_label)
|
||||||
|
|
@ -694,10 +731,18 @@ class K8s:
|
||||||
def check_service_annotations(self, svc_labels, annotations, namespace='default'):
|
def check_service_annotations(self, svc_labels, annotations, namespace='default'):
|
||||||
svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
|
svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
|
||||||
for svc in svcs:
|
for svc in svcs:
|
||||||
if len(svc.metadata.annotations) != len(annotations):
|
for key, value in annotations.items():
|
||||||
return False
|
if key not in svc.metadata.annotations or svc.metadata.annotations[key] != value:
|
||||||
for key in svc.metadata.annotations:
|
print("Expected key {} not found in annotations {}".format(key, svc.metadata.annotation))
|
||||||
if svc.metadata.annotations[key] != annotations[key]:
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_statefulset_annotations(self, sset_labels, annotations, namespace='default'):
|
||||||
|
ssets = self.api.apps_v1.list_namespaced_stateful_set(namespace, label_selector=sset_labels, limit=1).items
|
||||||
|
for sset in ssets:
|
||||||
|
for key, value in annotations.items():
|
||||||
|
if key not in sset.metadata.annotations or sset.metadata.annotations[key] != value:
|
||||||
|
print("Expected key {} not found in annotations {}".format(key, sset.metadata.annotation))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ data:
|
||||||
# default_memory_limit: 500Mi
|
# default_memory_limit: 500Mi
|
||||||
# default_memory_request: 100Mi
|
# default_memory_request: 100Mi
|
||||||
docker_image: registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p115
|
docker_image: registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p115
|
||||||
|
# downscaler_annotations: "deployment-time,downscaler/*"
|
||||||
# enable_admin_role_for_users: "true"
|
# enable_admin_role_for_users: "true"
|
||||||
# enable_crd_validation: "true"
|
# enable_crd_validation: "true"
|
||||||
# enable_database_access: "true"
|
# enable_database_access: "true"
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
downscaler_annotations:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
enable_init_containers:
|
enable_init_containers:
|
||||||
type: boolean
|
type: boolean
|
||||||
enable_pod_antiaffinity:
|
enable_pod_antiaffinity:
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ configuration:
|
||||||
# custom_pod_annotations:
|
# custom_pod_annotations:
|
||||||
# keya: valuea
|
# keya: valuea
|
||||||
# keyb: valueb
|
# keyb: valueb
|
||||||
|
# downscaler_annotations:
|
||||||
|
# - deployment-time
|
||||||
|
# - downscaler/*
|
||||||
enable_init_containers: true
|
enable_init_containers: true
|
||||||
enable_pod_antiaffinity: false
|
enable_pod_antiaffinity: false
|
||||||
enable_pod_disruption_budget: true
|
enable_pod_disruption_budget: true
|
||||||
|
|
|
||||||
|
|
@ -21,48 +21,48 @@ const (
|
||||||
|
|
||||||
// PostgresCRDResourceColumns definition of AdditionalPrinterColumns for postgresql CRD
|
// PostgresCRDResourceColumns definition of AdditionalPrinterColumns for postgresql CRD
|
||||||
var PostgresCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefinition{
|
var PostgresCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefinition{
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Team",
|
Name: "Team",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Team responsible for Postgres cluster",
|
Description: "Team responsible for Postgres cluster",
|
||||||
JSONPath: ".spec.teamId",
|
JSONPath: ".spec.teamId",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Version",
|
Name: "Version",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "PostgreSQL version",
|
Description: "PostgreSQL version",
|
||||||
JSONPath: ".spec.postgresql.version",
|
JSONPath: ".spec.postgresql.version",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Pods",
|
Name: "Pods",
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
Description: "Number of Pods per Postgres cluster",
|
Description: "Number of Pods per Postgres cluster",
|
||||||
JSONPath: ".spec.numberOfInstances",
|
JSONPath: ".spec.numberOfInstances",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Volume",
|
Name: "Volume",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Size of the bound volume",
|
Description: "Size of the bound volume",
|
||||||
JSONPath: ".spec.volume.size",
|
JSONPath: ".spec.volume.size",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "CPU-Request",
|
Name: "CPU-Request",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Requested CPU for Postgres containers",
|
Description: "Requested CPU for Postgres containers",
|
||||||
JSONPath: ".spec.resources.requests.cpu",
|
JSONPath: ".spec.resources.requests.cpu",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Memory-Request",
|
Name: "Memory-Request",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Requested memory for Postgres containers",
|
Description: "Requested memory for Postgres containers",
|
||||||
JSONPath: ".spec.resources.requests.memory",
|
JSONPath: ".spec.resources.requests.memory",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Age",
|
Name: "Age",
|
||||||
Type: "date",
|
Type: "date",
|
||||||
JSONPath: ".metadata.creationTimestamp",
|
JSONPath: ".metadata.creationTimestamp",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Status",
|
Name: "Status",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Current sync status of postgresql resource",
|
Description: "Current sync status of postgresql resource",
|
||||||
|
|
@ -72,31 +72,31 @@ var PostgresCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefinition{
|
||||||
|
|
||||||
// OperatorConfigCRDResourceColumns definition of AdditionalPrinterColumns for OperatorConfiguration CRD
|
// OperatorConfigCRDResourceColumns definition of AdditionalPrinterColumns for OperatorConfiguration CRD
|
||||||
var OperatorConfigCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefinition{
|
var OperatorConfigCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefinition{
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Image",
|
Name: "Image",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Spilo image to be used for Pods",
|
Description: "Spilo image to be used for Pods",
|
||||||
JSONPath: ".configuration.docker_image",
|
JSONPath: ".configuration.docker_image",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Cluster-Label",
|
Name: "Cluster-Label",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Label for K8s resources created by operator",
|
Description: "Label for K8s resources created by operator",
|
||||||
JSONPath: ".configuration.kubernetes.cluster_name_label",
|
JSONPath: ".configuration.kubernetes.cluster_name_label",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Service-Account",
|
Name: "Service-Account",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Name of service account to be used",
|
Description: "Name of service account to be used",
|
||||||
JSONPath: ".configuration.kubernetes.pod_service_account_name",
|
JSONPath: ".configuration.kubernetes.pod_service_account_name",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Min-Instances",
|
Name: "Min-Instances",
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
Description: "Minimum number of instances per Postgres cluster",
|
Description: "Minimum number of instances per Postgres cluster",
|
||||||
JSONPath: ".configuration.min_instances",
|
JSONPath: ".configuration.min_instances",
|
||||||
},
|
},
|
||||||
apiextv1beta1.CustomResourceColumnDefinition{
|
{
|
||||||
Name: "Age",
|
Name: "Age",
|
||||||
Type: "date",
|
Type: "date",
|
||||||
JSONPath: ".metadata.creationTimestamp",
|
JSONPath: ".metadata.creationTimestamp",
|
||||||
|
|
@ -888,6 +888,14 @@ var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"downscaler_annotations": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"enable_init_containers": {
|
"enable_init_containers": {
|
||||||
Type: "boolean",
|
Type: "boolean",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ type KubernetesMetaConfiguration struct {
|
||||||
PodRoleLabel string `json:"pod_role_label,omitempty"`
|
PodRoleLabel string `json:"pod_role_label,omitempty"`
|
||||||
ClusterLabels map[string]string `json:"cluster_labels,omitempty"`
|
ClusterLabels map[string]string `json:"cluster_labels,omitempty"`
|
||||||
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
||||||
|
DownscalerAnnotations []string `json:"downscaler_annotations,omitempty"`
|
||||||
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
||||||
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
||||||
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.DownscalerAnnotations != nil {
|
||||||
|
in, out := &in.DownscalerAnnotations, &out.DownscalerAnnotations
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
if in.NodeReadinessLabel != nil {
|
if in.NodeReadinessLabel != nil {
|
||||||
in, out := &in.NodeReadinessLabel, &out.NodeReadinessLabel
|
in, out := &in.NodeReadinessLabel, &out.NodeReadinessLabel
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
|
|
||||||
|
|
@ -711,8 +711,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
||||||
updateFailed = true
|
updateFailed = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(oldSs, newSs) || !reflect.DeepEqual(oldSpec.Annotations, newSpec.Annotations) {
|
||||||
if !reflect.DeepEqual(oldSs, newSs) {
|
|
||||||
c.logger.Debugf("syncing statefulsets")
|
c.logger.Debugf("syncing statefulsets")
|
||||||
// TODO: avoid generating the StatefulSet object twice by passing it to syncStatefulSet
|
// TODO: avoid generating the StatefulSet object twice by passing it to syncStatefulSet
|
||||||
if err := c.syncStatefulSet(); err != nil {
|
if err := c.syncStatefulSet(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,48 @@ var eventRecorder = record.NewFakeRecorder(1)
|
||||||
var cl = New(
|
var cl = New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
ProtectedRoles: []string{"admin"},
|
PodManagementPolicy: "ordered_ready",
|
||||||
|
ProtectedRoles: []string{"admin"},
|
||||||
Auth: config.Auth{
|
Auth: config.Auth{
|
||||||
SuperUsername: superUserName,
|
SuperUsername: superUserName,
|
||||||
ReplicationUsername: replicationUserName,
|
ReplicationUsername: replicationUserName,
|
||||||
},
|
},
|
||||||
|
Resources: config.Resources{
|
||||||
|
DownscalerAnnotations: []string{"downscaler/*"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
k8sutil.NewMockKubernetesClient(),
|
k8sutil.NewMockKubernetesClient(),
|
||||||
acidv1.Postgresql{ObjectMeta: metav1.ObjectMeta{Name: "acid-test", Namespace: "test"}},
|
acidv1.Postgresql{ObjectMeta: metav1.ObjectMeta{Name: "acid-test", Namespace: "test", Annotations: map[string]string{"downscaler/downtime_replicas": "0"}}},
|
||||||
logger,
|
logger,
|
||||||
eventRecorder,
|
eventRecorder,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestStatefulSetAnnotations(t *testing.T) {
|
||||||
|
testName := "CheckStatefulsetAnnotations"
|
||||||
|
spec := acidv1.PostgresSpec{
|
||||||
|
TeamID: "myapp", NumberOfInstances: 1,
|
||||||
|
Resources: acidv1.Resources{
|
||||||
|
ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "10"},
|
||||||
|
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "10"},
|
||||||
|
},
|
||||||
|
Volume: acidv1.Volume{
|
||||||
|
Size: "1G",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ss, err := cl.generateStatefulSet(&spec)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("in %s no statefulset created %v", testName, err)
|
||||||
|
}
|
||||||
|
if ss != nil {
|
||||||
|
annotation := ss.ObjectMeta.GetAnnotations()
|
||||||
|
if _, ok := annotation["downscaler/downtime_replicas"]; !ok {
|
||||||
|
t.Errorf("in %s respective annotation not found on sts", testName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestInitRobotUsers(t *testing.T) {
|
func TestInitRobotUsers(t *testing.T) {
|
||||||
testName := "TestInitRobotUsers"
|
testName := "TestInitRobotUsers"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
|
@ -1182,12 +1183,15 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||||
return nil, fmt.Errorf("could not set the pod management policy to the unknown value: %v", c.OpConfig.PodManagementPolicy)
|
return nil, fmt.Errorf("could not set the pod management policy to the unknown value: %v", c.OpConfig.PodManagementPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
annotations = make(map[string]string)
|
||||||
|
annotations[rollingUpdateStatefulsetAnnotationKey] = strconv.FormatBool(false)
|
||||||
|
|
||||||
statefulSet := &appsv1.StatefulSet{
|
statefulSet := &appsv1.StatefulSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: c.statefulSetName(),
|
Name: c.statefulSetName(),
|
||||||
Namespace: c.Namespace,
|
Namespace: c.Namespace,
|
||||||
Labels: c.labelsSet(true),
|
Labels: c.labelsSet(true),
|
||||||
Annotations: map[string]string{rollingUpdateStatefulsetAnnotationKey: "false"},
|
Annotations: c.AnnotationsToPropagate(annotations),
|
||||||
},
|
},
|
||||||
Spec: appsv1.StatefulSetSpec{
|
Spec: appsv1.StatefulSetSpec{
|
||||||
Replicas: &numberOfInstances,
|
Replicas: &numberOfInstances,
|
||||||
|
|
|
||||||
|
|
@ -853,3 +853,24 @@ func (c *Cluster) updateConnectionPoolerDeployment(oldDeploymentSpec, newDeploym
|
||||||
|
|
||||||
return deployment, nil
|
return deployment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//updateConnectionPoolerAnnotations updates the annotations of connection pooler deployment
|
||||||
|
func (c *Cluster) updateConnectionPoolerAnnotations(annotations map[string]string) (*appsv1.Deployment, error) {
|
||||||
|
c.logger.Debugf("updating connection pooler annotations")
|
||||||
|
patchData, err := metaAnnotationsPatch(annotations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not form patch for the deployment metadata: %v", err)
|
||||||
|
}
|
||||||
|
result, err := c.KubeClient.Deployments(c.ConnectionPooler.Deployment.Namespace).Patch(
|
||||||
|
context.TODO(),
|
||||||
|
c.ConnectionPooler.Deployment.Name,
|
||||||
|
types.MergePatchType,
|
||||||
|
[]byte(patchData),
|
||||||
|
metav1.PatchOptions{},
|
||||||
|
"")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not patch connection pooler annotations %q: %v", patchData, err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package cluster
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||||
|
|
@ -358,6 +359,8 @@ func (c *Cluster) syncStatefulSet() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
annotations := c.AnnotationsToPropagate(c.Statefulset.Annotations)
|
||||||
|
c.updateStatefulSetAnnotations(annotations)
|
||||||
|
|
||||||
if !podsRollingUpdateRequired && !c.OpConfig.EnableLazySpiloUpgrade {
|
if !podsRollingUpdateRequired && !c.OpConfig.EnableLazySpiloUpgrade {
|
||||||
// even if desired and actual statefulsets match
|
// even if desired and actual statefulsets match
|
||||||
|
|
@ -397,6 +400,30 @@ func (c *Cluster) syncStatefulSet() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnotationsToPropagate get the annotations to update if required
|
||||||
|
// based on the annotations in postgres CRD
|
||||||
|
func (c *Cluster) AnnotationsToPropagate(annotations map[string]string) map[string]string {
|
||||||
|
toPropagateAnnotations := c.OpConfig.DownscalerAnnotations
|
||||||
|
pgCRDAnnotations := c.Postgresql.ObjectMeta.GetAnnotations()
|
||||||
|
|
||||||
|
if toPropagateAnnotations != nil && pgCRDAnnotations != nil {
|
||||||
|
for _, anno := range toPropagateAnnotations {
|
||||||
|
for k, v := range pgCRDAnnotations {
|
||||||
|
matched, err := regexp.MatchString(anno, k)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Errorf("annotations matching issue: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
annotations[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations
|
||||||
|
}
|
||||||
|
|
||||||
// checkAndSetGlobalPostgreSQLConfiguration checks whether cluster-wide API parameters
|
// checkAndSetGlobalPostgreSQLConfiguration checks whether cluster-wide API parameters
|
||||||
// (like max_connections) has changed and if necessary sets it via the Patroni API
|
// (like max_connections) has changed and if necessary sets it via the Patroni API
|
||||||
func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration() error {
|
func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration() error {
|
||||||
|
|
@ -939,6 +966,11 @@ func (c *Cluster) syncConnectionPoolerWorker(oldSpec, newSpec *acidv1.Postgresql
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newAnnotations := c.AnnotationsToPropagate(c.ConnectionPooler.Deployment.Annotations)
|
||||||
|
if newAnnotations != nil {
|
||||||
|
c.updateConnectionPoolerAnnotations(newAnnotations)
|
||||||
|
}
|
||||||
|
|
||||||
service, err := c.KubeClient.
|
service, err := c.KubeClient.
|
||||||
Services(c.Namespace).
|
Services(c.Namespace).
|
||||||
Get(context.TODO(), c.connectionPoolerName(), metav1.GetOptions{})
|
Get(context.TODO(), c.connectionPoolerName(), metav1.GetOptions{})
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.PodRoleLabel = util.Coalesce(fromCRD.Kubernetes.PodRoleLabel, "spilo-role")
|
result.PodRoleLabel = util.Coalesce(fromCRD.Kubernetes.PodRoleLabel, "spilo-role")
|
||||||
result.ClusterLabels = fromCRD.Kubernetes.ClusterLabels
|
result.ClusterLabels = fromCRD.Kubernetes.ClusterLabels
|
||||||
result.InheritedLabels = fromCRD.Kubernetes.InheritedLabels
|
result.InheritedLabels = fromCRD.Kubernetes.InheritedLabels
|
||||||
|
result.DownscalerAnnotations = fromCRD.Kubernetes.DownscalerAnnotations
|
||||||
result.ClusterNameLabel = util.Coalesce(fromCRD.Kubernetes.ClusterNameLabel, "cluster-name")
|
result.ClusterNameLabel = util.Coalesce(fromCRD.Kubernetes.ClusterNameLabel, "cluster-name")
|
||||||
result.NodeReadinessLabel = fromCRD.Kubernetes.NodeReadinessLabel
|
result.NodeReadinessLabel = fromCRD.Kubernetes.NodeReadinessLabel
|
||||||
result.PodPriorityClassName = fromCRD.Kubernetes.PodPriorityClassName
|
result.PodPriorityClassName = fromCRD.Kubernetes.PodPriorityClassName
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,9 @@ func (c *Controller) postgresqlUpdate(prev, cur interface{}) {
|
||||||
if pgOld != nil && pgNew != nil {
|
if pgOld != nil && pgNew != nil {
|
||||||
// Avoid the inifinite recursion for status updates
|
// Avoid the inifinite recursion for status updates
|
||||||
if reflect.DeepEqual(pgOld.Spec, pgNew.Spec) {
|
if reflect.DeepEqual(pgOld.Spec, pgNew.Spec) {
|
||||||
return
|
if reflect.DeepEqual(pgNew.Annotations, pgOld.Annotations) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.queueClusterEvent(pgOld, pgNew, EventUpdate)
|
c.queueClusterEvent(pgOld, pgNew, EventUpdate)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ type Resources struct {
|
||||||
SpiloPrivileged bool `name:"spilo_privileged" default:"false"`
|
SpiloPrivileged bool `name:"spilo_privileged" default:"false"`
|
||||||
ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"`
|
ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"`
|
||||||
InheritedLabels []string `name:"inherited_labels" default:""`
|
InheritedLabels []string `name:"inherited_labels" default:""`
|
||||||
|
DownscalerAnnotations []string `name:"downscaler_annotations"`
|
||||||
ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"`
|
ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"`
|
||||||
PodRoleLabel string `name:"pod_role_label" default:"spilo-role"`
|
PodRoleLabel string `name:"pod_role_label" default:"spilo-role"`
|
||||||
PodToleration map[string]string `name:"toleration" default:""`
|
PodToleration map[string]string `name:"toleration" default:""`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue