diff --git a/pkg/apis/acid.zalan.do/v1/marshal.go b/pkg/apis/acid.zalan.do/v1/marshal.go index 9521082fc..f4167ce92 100644 --- a/pkg/apis/acid.zalan.do/v1/marshal.go +++ b/pkg/apis/acid.zalan.do/v1/marshal.go @@ -81,7 +81,7 @@ func (ps *PostgresStatus) UnmarshalJSON(data []byte) error { if err != nil { metaErr := json.Unmarshal(data, &status) if metaErr != nil { - return fmt.Errorf("Could not parse status: %v; err %v", string(data), metaErr) + return fmt.Errorf("could not parse status: %v; err %v", string(data), metaErr) } tmp.PostgresClusterStatus = status } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index e2484f05f..88bb066fe 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -2,7 +2,9 @@ package cluster import ( "context" + "encoding/json" "fmt" + "reflect" "regexp" "strings" "time" @@ -409,7 +411,7 @@ func (c *Cluster) syncStatefulSet() error { } if instanceRestartRequired { - c.logger.Debugln("restarting Postgres server within pods") + c.logger.Debugf("restarting Postgres server within pod %s", podName) ttl, ok := config["ttl"].(int32) if !ok { ttl = 30 @@ -494,29 +496,71 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, patroniC restartRequired bool ) - // we need to extract those options from the cluster manifest. - optionsToSet := make(map[string]string) - desiredConfig := c.Spec.Parameters - effectiveConfig := patroniConfig["postgresql"].(map[string]interface{}) - effectiveParameters := effectiveConfig["parameters"].(map[string]interface{}) + configToSet := make(map[string]interface{}) + parametersToSet := make(map[string]string) + effectivePgParameters := make(map[string]interface{}) - for desiredOption, desiredValue := range desiredConfig { - effectiveValue, exists := effectiveParameters[desiredOption] - if isBootstrapOnlyParameter(desiredOption) && (effectiveValue != desiredValue || !exists) { - optionsToSet[desiredOption] = desiredValue + // read effective Patroni config if set + if patroniConfig != nil { + effectivePostgresql := patroniConfig["postgresql"].(map[string]interface{}) + effectivePgParameters = effectivePostgresql[patroniPGParametersParameterName].(map[string]interface{}) + } + + // compare parameters under postgresql section with c.Spec.Postgresql.Parameters from manifest + desiredPgParameters := c.Spec.Parameters + for desiredOption, desiredValue := range desiredPgParameters { + effectiveValue := effectivePgParameters[desiredOption] + if isBootstrapOnlyParameter(desiredOption) && (effectiveValue != desiredValue) { + parametersToSet[desiredOption] = desiredValue } } - if len(optionsToSet) == 0 { + if len(parametersToSet) > 0 { + configToSet["postgresql"] = map[string]interface{}{patroniPGParametersParameterName: parametersToSet} + } + + // compare other options from config with c.Spec.Patroni from manifest + desiredPatroniConfig := c.Spec.Patroni + if desiredPatroniConfig.LoopWait > 0 && desiredPatroniConfig.LoopWait != uint32(patroniConfig["loop_wait"].(float64)) { + configToSet["loop_wait"] = desiredPatroniConfig.LoopWait + } + if desiredPatroniConfig.MaximumLagOnFailover > 0 && desiredPatroniConfig.MaximumLagOnFailover != float32(patroniConfig["maximum_lag_on_failover"].(float64)) { + configToSet["maximum_lag_on_failover"] = desiredPatroniConfig.MaximumLagOnFailover + } + if desiredPatroniConfig.PgHba != nil && !reflect.DeepEqual(desiredPatroniConfig.PgHba, (patroniConfig["pg_hba"])) { + configToSet["pg_hba"] = desiredPatroniConfig.PgHba + } + if desiredPatroniConfig.RetryTimeout > 0 && desiredPatroniConfig.RetryTimeout != uint32(patroniConfig["retry_timeout"].(float64)) { + configToSet["retry_timeout"] = desiredPatroniConfig.RetryTimeout + } + if desiredPatroniConfig.Slots != nil && !reflect.DeepEqual(desiredPatroniConfig.Slots, patroniConfig["slots"]) { + configToSet["slots"] = desiredPatroniConfig.Slots + } + if desiredPatroniConfig.SynchronousMode != patroniConfig["synchronous_mode"] { + configToSet["synchronous_mode"] = desiredPatroniConfig.SynchronousMode + } + if desiredPatroniConfig.SynchronousModeStrict != patroniConfig["synchronous_mode_strict"] { + configToSet["synchronous_mode_strict"] = desiredPatroniConfig.SynchronousModeStrict + } + if desiredPatroniConfig.TTL > 0 && desiredPatroniConfig.TTL != uint32(patroniConfig["ttl"].(float64)) { + configToSet["ttl"] = desiredPatroniConfig.TTL + } + + if len(configToSet) == 0 { return restartRequired, nil } + configToSetJson, err := json.Marshal(configToSet) + if err != nil { + c.logger.Debugf("could not convert config patch to JSON: %v", err) + } + // try all pods until the first one that is successful, as it doesn't matter which pod // carries the request to change configuration through podName := util.NameFromMeta(pod.ObjectMeta) - c.logger.Debugf("calling Patroni API on a pod %s to set the following Postgres options: %v", - podName, optionsToSet) - if err = c.patroni.SetPostgresParameters(pod, optionsToSet); err == nil { + c.logger.Debugf("calling Patroni API on a pod %s to set the following Postgres options: %s", + podName, configToSetJson) + if err = c.patroni.SetConfig(pod, configToSet); err == nil { restartRequired = true return restartRequired, nil } diff --git a/pkg/util/patroni/patroni.go b/pkg/util/patroni/patroni.go index 1f2c95552..a9cadafba 100644 --- a/pkg/util/patroni/patroni.go +++ b/pkg/util/patroni/patroni.go @@ -32,6 +32,7 @@ type Interface interface { GetMemberData(server *v1.Pod) (MemberData, error) Restart(server *v1.Pod) error GetConfig(server *v1.Pod) (map[string]interface{}, error) + SetConfig(server *v1.Pod, config map[string]interface{}) error } // Patroni API client @@ -163,6 +164,20 @@ func (p *Patroni) SetPostgresParameters(server *v1.Pod, parameters map[string]st return p.httpPostOrPatch(http.MethodPatch, apiURLString+configPath, buf) } +//SetConfig sets Patroni options via Patroni patch API call. +func (p *Patroni) SetConfig(server *v1.Pod, config map[string]interface{}) error { + buf := &bytes.Buffer{} + err := json.NewEncoder(buf).Encode(config) + if err != nil { + return fmt.Errorf("could not encode json: %v", err) + } + apiURLString, err := apiURL(server) + if err != nil { + return err + } + return p.httpPostOrPatch(http.MethodPatch, apiURLString+configPath, buf) +} + // MemberDataPatroni child element type MemberDataPatroni struct { Version string `json:"version"`