diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 2c432a082..cb4b7a335 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -79,6 +79,9 @@ spec: enable_lazy_spilo_upgrade: type: boolean default: false + enable_maintenance_windows: + type: boolean + default: true enable_pgversion_env_var: type: boolean default: true diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 6d0161bfb..dfec76b6b 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -27,6 +27,8 @@ configGeneral: - "all" # update only the statefulsets without immediately doing the rolling update enable_lazy_spilo_upgrade: false + # toogle to use maintenance windows feature + enable_maintenance_windows: true # set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION enable_pgversion_env_var: true # start any new database pod without limitations on shm memory diff --git a/docs/administrator.md b/docs/administrator.md index d18bb5349..b7880b183 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -95,11 +95,11 @@ Thus, the `full` mode can create drift between desired and actual state. ### Upgrade during maintenance windows -When `maintenanceWindows` are defined in the Postgres manifest the operator -will trigger major-version-related pod rotation and the major version upgrade -only during these periods. Make sure they are at least twice as long as your -configured `resync_period` to guarantee -that operator actions can be triggered. +When `maintenanceWindows` are defined in the Postgres manifest or in the global +config the operator will trigger major-version-related pod rotation and the +major version upgrade only during these periods. Make sure they are at least +twice as long as your configured `resync_period` to guarantee that operator +actions can be triggered. ### Upgrade annotations diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index ae23dabb9..fd0660f57 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -118,7 +118,9 @@ These parameters are grouped directly under the `spec` key in the manifest. a list which defines specific time frames when certain maintenance operations such as automatic major upgrades or master pod migration are allowed to happen. Accepted formats are "01:00-06:00" for daily maintenance windows or - "Sat:00:00-04:00" for specific days, with all times in UTC. + "Sat:00:00-04:00" for specific days, with all times in UTC. Note, when the + global config option `enable_maintenance_windows` is false, the specified + windows will be ignored. * **users** a map of usernames to user flags for the users that should be created in the diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index a62f67dfb..6dd775069 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -173,6 +173,9 @@ Those are top-level keys, containing both leaf keys and groups. the thresholds. The value must be `"true"` to be effective. The default is empty which means the feature is disabled. +* **enable_maintenance_windows** + toggle for using the maintenance windows feature. Default is `"true"`. + * **maintenance_windows** a list which defines specific time frames when certain maintenance operations such as automatic major upgrades or master pod migration are diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 1cf455e57..571a4171b 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -46,6 +46,7 @@ data: enable_ebs_gp3_migration_max_size: "1000" enable_init_containers: "true" enable_lazy_spilo_upgrade: "false" + enable_maintenance_windows: "true" enable_master_load_balancer: "false" enable_master_pooler_load_balancer: "false" enable_password_rotation: "false" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 03534cefb..3be545b65 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -77,6 +77,9 @@ spec: enable_lazy_spilo_upgrade: type: boolean default: false + enable_maintenance_windows: + type: boolean + default: true enable_pgversion_env_var: type: boolean default: true diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 9c54e4379..1c6a0e34a 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -8,6 +8,7 @@ configuration: # crd_categories: # - all # enable_lazy_spilo_upgrade: false + enable_maintenance_windows: true enable_pgversion_env_var: true # enable_shm_volume: true enable_spilo_wal_path_compat: false diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 46739e46d..3175f152a 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -105,6 +105,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "enable_lazy_spilo_upgrade": { Type: "boolean", }, + "enable_maintenance_windows": { + Type: "boolean", + }, "enable_shm_volume": { Type: "boolean", }, diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index bfad24b0d..453d618d3 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -266,6 +266,7 @@ type OperatorConfigurationData struct { Workers uint32 `json:"workers,omitempty"` ResyncPeriod Duration `json:"resync_period,omitempty"` RepairPeriod Duration `json:"repair_period,omitempty"` + EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"` MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows,omitempty"` SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"` ShmVolume *bool `json:"enable_shm_volume,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 159a87f35..0fa4b1037 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -433,6 +433,11 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData *out = make([]string, len(*in)) copy(*out, *in) } + if in.EnableMaintenanceWindows != nil { + in, out := &in.EnableMaintenanceWindows, &out.EnableMaintenanceWindows + *out = new(bool) + **out = **in + } if in.MaintenanceWindows != nil { in, out := &in.MaintenanceWindows, &out.MaintenanceWindows *out = make([]MaintenanceWindow, len(*in)) diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index 81e927d94..9c830129d 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -675,7 +675,9 @@ func isStandbyCluster(spec *acidv1.PostgresSpec) bool { } func (c *Cluster) isInMaintenanceWindow(specMaintenanceWindows []acidv1.MaintenanceWindow) bool { - if len(specMaintenanceWindows) == 0 && len(c.OpConfig.MaintenanceWindows) == 0 { + ignoreMaintenanceWindows := c.OpConfig.EnableMaintenanceWindows != nil && !*c.OpConfig.EnableMaintenanceWindows + noWindowsDefined := len(specMaintenanceWindows) == 0 && len(c.OpConfig.MaintenanceWindows) == 0 + if noWindowsDefined || ignoreMaintenanceWindows { return true } now := time.Now() diff --git a/pkg/cluster/util_test.go b/pkg/cluster/util_test.go index ea3e81e89..8413ca396 100644 --- a/pkg/cluster/util_test.go +++ b/pkg/cluster/util_test.go @@ -660,6 +660,7 @@ func TestIsInMaintenanceWindow(t *testing.T) { cluster := New( Config{ OpConfig: config.Config{ + EnableMaintenanceWindows: util.True(), Resources: config.Resources{ ClusterLabels: map[string]string{"application": "spilo"}, ClusterNameLabel: "cluster-name", @@ -683,12 +684,27 @@ func TestIsInMaintenanceWindow(t *testing.T) { name string windows []acidv1.MaintenanceWindow configWindows []string + windowsFlag bool expected bool }{ { name: "no maintenance windows", windows: nil, configWindows: nil, + windowsFlag: true, + expected: true, + }, + { + name: "maintenance windows diabled", + windows: []acidv1.MaintenanceWindow{ + { + Everyday: true, + StartTime: mustParseTime("00:00"), + EndTime: mustParseTime("23:59"), + }, + }, + configWindows: nil, + windowsFlag: false, expected: true, }, { @@ -701,6 +717,7 @@ func TestIsInMaintenanceWindow(t *testing.T) { }, }, configWindows: nil, + windowsFlag: true, expected: true, }, { @@ -713,6 +730,7 @@ func TestIsInMaintenanceWindow(t *testing.T) { }, }, configWindows: nil, + windowsFlag: true, expected: true, }, { @@ -724,24 +742,35 @@ func TestIsInMaintenanceWindow(t *testing.T) { EndTime: mustParseTime(futureTimeEndFormatted), }, }, - expected: false, + windowsFlag: true, + expected: false, }, { name: "global maintenance windows with future interval time", windows: nil, configWindows: []string{fmt.Sprintf("%s-%s", futureTimeStartFormatted, futureTimeEndFormatted)}, + windowsFlag: true, expected: false, }, { name: "global maintenance windows all day", windows: nil, configWindows: []string{"00:00-02:00", "02:00-23:59"}, + windowsFlag: true, + expected: true, + }, + { + name: "global maintenance windows ignored", + windows: nil, + configWindows: []string{"00:00-02:00", "02:00-23:59"}, + windowsFlag: false, expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + cluster.OpConfig.EnableMaintenanceWindows = &tt.windowsFlag cluster.OpConfig.MaintenanceWindows = tt.configWindows cluster.Spec.MaintenanceWindows = tt.windows if cluster.isInMaintenanceWindow(cluster.Spec.MaintenanceWindows) != tt.expected { diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 7719a2939..0a458618b 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -51,6 +51,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.ShmVolume = util.CoalesceBool(fromCRD.ShmVolume, util.True()) result.SidecarImages = fromCRD.SidecarImages result.SidecarContainers = fromCRD.SidecarContainers + result.EnableMaintenanceWindows = util.CoalesceBool(fromCRD.EnableMaintenanceWindows, util.True()) if len(fromCRD.MaintenanceWindows) > 0 { result.MaintenanceWindows = make([]string, 0, len(fromCRD.MaintenanceWindows)) for _, window := range fromCRD.MaintenanceWindows { diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 468cf9328..914d7a180 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -173,14 +173,15 @@ type Config struct { LogicalBackup ConnectionPooler - WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to' - KubernetesUseConfigMaps bool `name:"kubernetes_use_configmaps" default:"false"` - EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS - MaintenanceWindows []string `name:"maintenance_windows"` - DockerImage string `name:"docker_image" default:"ghcr.io/zalando/spilo-18:4.1-p1"` - SidecarImages map[string]string `name:"sidecar_docker_images"` // deprecated in favour of SidecarContainers - SidecarContainers []v1.Container `name:"sidecars"` - PodServiceAccountName string `name:"pod_service_account_name" default:"postgres-pod"` + WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to' + KubernetesUseConfigMaps bool `name:"kubernetes_use_configmaps" default:"false"` + EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS + EnableMaintenanceWindows *bool `name:"enable_maintenance_windows" default:"true"` + MaintenanceWindows []string `name:"maintenance_windows"` + DockerImage string `name:"docker_image" default:"ghcr.io/zalando/spilo-18:4.1-p1"` + SidecarImages map[string]string `name:"sidecar_docker_images"` // deprecated in favour of SidecarContainers + SidecarContainers []v1.Container `name:"sidecars"` + PodServiceAccountName string `name:"pod_service_account_name" default:"postgres-pod"` // value of this string must be valid JSON or YAML; see initPodServiceAccount PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""` PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""`