Merge branch 'master' into bump-v1.5.0
This commit is contained in:
		
						commit
						2a38091cc3
					
				|  | @ -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 | ||||||
|  | @ -106,7 +109,7 @@ configKubernetes: | ||||||
|   # Postgres pods are terminated forcefully after this timeout |   # Postgres pods are terminated forcefully after this timeout | ||||||
|   pod_terminate_grace_period: 5m |   pod_terminate_grace_period: 5m | ||||||
|   # template for database user secrets generated by the operator |   # template for database user secrets generated by the operator | ||||||
|   secret_name_template: '{username}.{cluster}.credentials' |   secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" | ||||||
|   # group ID with write-access to volumes (required to run Spilo as non-root process) |   # group ID with write-access to volumes (required to run Spilo as non-root process) | ||||||
|   # spilo_fsgroup: "103" |   # spilo_fsgroup: "103" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  | @ -49,16 +50,16 @@ data: | ||||||
|   # inherited_labels: application,environment |   # inherited_labels: application,environment | ||||||
|   # kube_iam_role: "" |   # kube_iam_role: "" | ||||||
|   # log_s3_bucket: "" |   # log_s3_bucket: "" | ||||||
|   # logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup" |   logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup" | ||||||
|   # logical_backup_s3_access_key_id: "" |   # logical_backup_s3_access_key_id: "" | ||||||
|   # logical_backup_s3_bucket: "my-bucket-url" |   logical_backup_s3_bucket: "my-bucket-url" | ||||||
|   # logical_backup_s3_region: "" |   # logical_backup_s3_region: "" | ||||||
|   # logical_backup_s3_endpoint: "" |   # logical_backup_s3_endpoint: "" | ||||||
|   # logical_backup_s3_secret_access_key: "" |   # logical_backup_s3_secret_access_key: "" | ||||||
|   # logical_backup_s3_sse: "AES256" |   logical_backup_s3_sse: "AES256" | ||||||
|   # logical_backup_schedule: "30 00 * * *" |   logical_backup_schedule: "30 00 * * *" | ||||||
|   master_dns_name_format: "{cluster}.{team}.{hostedzone}" |   master_dns_name_format: "{cluster}.{team}.{hostedzone}" | ||||||
|   # master_pod_move_timeout: 10m |   # master_pod_move_timeout: 20m | ||||||
|   # max_instances: "-1" |   # max_instances: "-1" | ||||||
|   # min_instances: "-1" |   # min_instances: "-1" | ||||||
|   # min_cpu_limit: 250m |   # min_cpu_limit: 250m | ||||||
|  |  | ||||||
|  | @ -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{}) | ||||||
|  |  | ||||||
|  | @ -33,28 +33,28 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur | ||||||
| 	result := &config.Config{} | 	result := &config.Config{} | ||||||
| 
 | 
 | ||||||
| 	// general config
 | 	// general config
 | ||||||
| 	result.EnableCRDValidation = fromCRD.EnableCRDValidation | 	result.EnableCRDValidation = util.CoalesceBool(fromCRD.EnableCRDValidation, util.True()) | ||||||
| 	result.EnableLazySpiloUpgrade = fromCRD.EnableLazySpiloUpgrade | 	result.EnableLazySpiloUpgrade = fromCRD.EnableLazySpiloUpgrade | ||||||
| 	result.EtcdHost = fromCRD.EtcdHost | 	result.EtcdHost = fromCRD.EtcdHost | ||||||
| 	result.KubernetesUseConfigMaps = fromCRD.KubernetesUseConfigMaps | 	result.KubernetesUseConfigMaps = fromCRD.KubernetesUseConfigMaps | ||||||
| 	result.DockerImage = fromCRD.DockerImage | 	result.DockerImage = util.Coalesce(fromCRD.DockerImage, "registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p115") | ||||||
| 	result.Workers = fromCRD.Workers | 	result.Workers = fromCRD.Workers | ||||||
| 	result.MinInstances = fromCRD.MinInstances | 	result.MinInstances = fromCRD.MinInstances | ||||||
| 	result.MaxInstances = fromCRD.MaxInstances | 	result.MaxInstances = fromCRD.MaxInstances | ||||||
| 	result.ResyncPeriod = time.Duration(fromCRD.ResyncPeriod) | 	result.ResyncPeriod = time.Duration(fromCRD.ResyncPeriod) | ||||||
| 	result.RepairPeriod = time.Duration(fromCRD.RepairPeriod) | 	result.RepairPeriod = time.Duration(fromCRD.RepairPeriod) | ||||||
| 	result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit | 	result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit | ||||||
| 	result.ShmVolume = fromCRD.ShmVolume | 	result.ShmVolume = util.CoalesceBool(fromCRD.ShmVolume, util.True()) | ||||||
| 	result.SidecarImages = fromCRD.SidecarImages | 	result.SidecarImages = fromCRD.SidecarImages | ||||||
| 	result.SidecarContainers = fromCRD.SidecarContainers | 	result.SidecarContainers = fromCRD.SidecarContainers | ||||||
| 
 | 
 | ||||||
| 	// user config
 | 	// user config
 | ||||||
| 	result.SuperUsername = fromCRD.PostgresUsersConfiguration.SuperUsername | 	result.SuperUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.SuperUsername, "postgres") | ||||||
| 	result.ReplicationUsername = fromCRD.PostgresUsersConfiguration.ReplicationUsername | 	result.ReplicationUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.ReplicationUsername, "standby") | ||||||
| 
 | 
 | ||||||
| 	// kubernetes config
 | 	// kubernetes config
 | ||||||
| 	result.CustomPodAnnotations = fromCRD.Kubernetes.CustomPodAnnotations | 	result.CustomPodAnnotations = fromCRD.Kubernetes.CustomPodAnnotations | ||||||
| 	result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName | 	result.PodServiceAccountName = util.Coalesce(fromCRD.Kubernetes.PodServiceAccountName, "postgres-pod") | ||||||
| 	result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition | 	result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition | ||||||
| 	result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition | 	result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition | ||||||
| 	result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap | 	result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap | ||||||
|  | @ -64,30 +64,31 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur | ||||||
| 	result.ClusterDomain = util.Coalesce(fromCRD.Kubernetes.ClusterDomain, "cluster.local") | 	result.ClusterDomain = util.Coalesce(fromCRD.Kubernetes.ClusterDomain, "cluster.local") | ||||||
| 	result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace | 	result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace | ||||||
| 	result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat | 	result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat | ||||||
| 	result.EnablePodDisruptionBudget = fromCRD.Kubernetes.EnablePodDisruptionBudget | 	result.EnablePodDisruptionBudget = util.CoalesceBool(fromCRD.Kubernetes.EnablePodDisruptionBudget, util.True()) | ||||||
| 	result.EnableInitContainers = fromCRD.Kubernetes.EnableInitContainers | 	result.EnableInitContainers = util.CoalesceBool(fromCRD.Kubernetes.EnableInitContainers, util.True()) | ||||||
| 	result.EnableSidecars = fromCRD.Kubernetes.EnableSidecars | 	result.EnableSidecars = util.CoalesceBool(fromCRD.Kubernetes.EnableSidecars, util.True()) | ||||||
| 	result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate | 	result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate | ||||||
| 	result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName | 	result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName | ||||||
| 	result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName | 	result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName | ||||||
| 	result.PodRoleLabel = fromCRD.Kubernetes.PodRoleLabel | 	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.ClusterNameLabel = fromCRD.Kubernetes.ClusterNameLabel | 	result.DownscalerAnnotations = fromCRD.Kubernetes.DownscalerAnnotations | ||||||
|  | 	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 | ||||||
| 	result.PodManagementPolicy = fromCRD.Kubernetes.PodManagementPolicy | 	result.PodManagementPolicy = util.Coalesce(fromCRD.Kubernetes.PodManagementPolicy, "ordered_ready") | ||||||
| 	result.MasterPodMoveTimeout = time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout) | 	result.MasterPodMoveTimeout = time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout) | ||||||
| 	result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity | 	result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity | ||||||
| 	result.PodAntiAffinityTopologyKey = fromCRD.Kubernetes.PodAntiAffinityTopologyKey | 	result.PodAntiAffinityTopologyKey = util.Coalesce(fromCRD.Kubernetes.PodAntiAffinityTopologyKey, "kubernetes.io/hostname") | ||||||
| 
 | 
 | ||||||
| 	// Postgres Pod resources
 | 	// Postgres Pod resources
 | ||||||
| 	result.DefaultCPURequest = fromCRD.PostgresPodResources.DefaultCPURequest | 	result.DefaultCPURequest = util.Coalesce(fromCRD.PostgresPodResources.DefaultCPURequest, "100m") | ||||||
| 	result.DefaultMemoryRequest = fromCRD.PostgresPodResources.DefaultMemoryRequest | 	result.DefaultMemoryRequest = util.Coalesce(fromCRD.PostgresPodResources.DefaultMemoryRequest, "100Mi") | ||||||
| 	result.DefaultCPULimit = fromCRD.PostgresPodResources.DefaultCPULimit | 	result.DefaultCPULimit = util.Coalesce(fromCRD.PostgresPodResources.DefaultCPULimit, "1") | ||||||
| 	result.DefaultMemoryLimit = fromCRD.PostgresPodResources.DefaultMemoryLimit | 	result.DefaultMemoryLimit = util.Coalesce(fromCRD.PostgresPodResources.DefaultMemoryLimit, "500Mi") | ||||||
| 	result.MinCPULimit = fromCRD.PostgresPodResources.MinCPULimit | 	result.MinCPULimit = util.Coalesce(fromCRD.PostgresPodResources.MinCPULimit, "250m") | ||||||
| 	result.MinMemoryLimit = fromCRD.PostgresPodResources.MinMemoryLimit | 	result.MinMemoryLimit = util.Coalesce(fromCRD.PostgresPodResources.MinMemoryLimit, "250Mi") | ||||||
| 
 | 
 | ||||||
| 	// timeout config
 | 	// timeout config
 | ||||||
| 	result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval) | 	result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval) | ||||||
|  | @ -114,8 +115,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur | ||||||
| 	result.AdditionalSecretMountPath = fromCRD.AWSGCP.AdditionalSecretMountPath | 	result.AdditionalSecretMountPath = fromCRD.AWSGCP.AdditionalSecretMountPath | ||||||
| 
 | 
 | ||||||
| 	// logical backup config
 | 	// logical backup config
 | ||||||
| 	result.LogicalBackupSchedule = fromCRD.LogicalBackup.Schedule | 	result.LogicalBackupSchedule = util.Coalesce(fromCRD.LogicalBackup.Schedule, "30 00 * * *") | ||||||
| 	result.LogicalBackupDockerImage = fromCRD.LogicalBackup.DockerImage | 	result.LogicalBackupDockerImage = util.Coalesce(fromCRD.LogicalBackup.DockerImage, "registry.opensource.zalan.do/acid/logical-backup") | ||||||
| 	result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket | 	result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket | ||||||
| 	result.LogicalBackupS3Region = fromCRD.LogicalBackup.S3Region | 	result.LogicalBackupS3Region = fromCRD.LogicalBackup.S3Region | ||||||
| 	result.LogicalBackupS3Endpoint = fromCRD.LogicalBackup.S3Endpoint | 	result.LogicalBackupS3Endpoint = fromCRD.LogicalBackup.S3Endpoint | ||||||
|  |  | ||||||
|  | @ -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:""` | ||||||
|  | @ -82,7 +83,7 @@ type LogicalBackup struct { | ||||||
| 	LogicalBackupS3Endpoint        string `name:"logical_backup_s3_endpoint" default:""` | 	LogicalBackupS3Endpoint        string `name:"logical_backup_s3_endpoint" default:""` | ||||||
| 	LogicalBackupS3AccessKeyID     string `name:"logical_backup_s3_access_key_id" default:""` | 	LogicalBackupS3AccessKeyID     string `name:"logical_backup_s3_access_key_id" default:""` | ||||||
| 	LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""` | 	LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""` | ||||||
| 	LogicalBackupS3SSE             string `name:"logical_backup_s3_sse" default:"AES256"` | 	LogicalBackupS3SSE             string `name:"logical_backup_s3_sse" default:""` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Operator options for connection pooler
 | // Operator options for connection pooler
 | ||||||
|  |  | ||||||
|  | @ -147,7 +147,7 @@ func Coalesce(val, defaultVal string) string { | ||||||
| 	return val | 	return val | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Yeah, golang
 | // CoalesceInt32 works like coalesce but for *int32
 | ||||||
| func CoalesceInt32(val, defaultVal *int32) *int32 { | func CoalesceInt32(val, defaultVal *int32) *int32 { | ||||||
| 	if val == nil { | 	if val == nil { | ||||||
| 		return defaultVal | 		return defaultVal | ||||||
|  | @ -155,6 +155,14 @@ func CoalesceInt32(val, defaultVal *int32) *int32 { | ||||||
| 	return val | 	return val | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CoalesceBool works like coalesce but for *bool
 | ||||||
|  | func CoalesceBool(val, defaultVal *bool) *bool { | ||||||
|  | 	if val == nil { | ||||||
|  | 		return defaultVal | ||||||
|  | 	} | ||||||
|  | 	return val | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Test if any of the values is nil
 | // Test if any of the values is nil
 | ||||||
| func testNil(values ...*int32) bool { | func testNil(values ...*int32) bool { | ||||||
| 	for _, v := range values { | 	for _, v := range values { | ||||||
|  | @ -166,8 +174,8 @@ func testNil(values ...*int32) bool { | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Return maximum of two integers provided via pointers. If one value is not
 | // MaxInt32 : Return maximum of two integers provided via pointers. If one value
 | ||||||
| // defined, return the other one. If both are not defined, result is also
 | // is not defined, return the other one. If both are not defined, result is also
 | ||||||
| // undefined, caller needs to check for that.
 | // undefined, caller needs to check for that.
 | ||||||
| func MaxInt32(a, b *int32) *int32 { | func MaxInt32(a, b *int32) *int32 { | ||||||
| 	if testNil(a, b) { | 	if testNil(a, b) { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue