From fe465cf49273e55a4ac76361b2b54e55e91b03bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mu=C3=9Fler?= Date: Thu, 18 Feb 2021 22:01:08 +0100 Subject: [PATCH] Making progress on automated upgrade support via manifest and global upgrades. --- pkg/cluster/cluster.go | 23 +++++---- pkg/cluster/majorversionupgrade.go | 82 ++++++++++++++++++++++++++++++ pkg/controller/util_test.go | 42 +++++++++++++++ pkg/util/config/config.go | 4 ++ pkg/util/patroni/patroni.go | 54 ++++++++++++++++++++ 5 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 pkg/cluster/majorversionupgrade.go diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 1055b795d..a2eeccf03 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -83,15 +83,16 @@ type Cluster struct { deleteOptions metav1.DeleteOptions podEventsQueue *cache.FIFO - teamsAPIClient teams.Interface - oauthTokenGetter OAuthTokenGetter - KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place? - currentProcess Process - processMu sync.RWMutex // protects the current operation for reporting, no need to hold the master mutex - specMu sync.RWMutex // protects the spec for reporting, no need to hold the master mutex - ConnectionPooler map[PostgresRole]*ConnectionPoolerObjects - EBSVolumes map[string]volumes.VolumeProperties - VolumeResizer volumes.VolumeResizer + teamsAPIClient teams.Interface + oauthTokenGetter OAuthTokenGetter + KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place? + currentProcess Process + processMu sync.RWMutex // protects the current operation for reporting, no need to hold the master mutex + specMu sync.RWMutex // protects the spec for reporting, no need to hold the master mutex + ConnectionPooler map[PostgresRole]*ConnectionPoolerObjects + EBSVolumes map[string]volumes.VolumeProperties + VolumeResizer volumes.VolumeResizer + currentMajorVersion int } type compareStatefulsetResult struct { @@ -781,6 +782,10 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error { updateFailed = true } + if err := c.majorVersionUpgrade(); err != nil { + + } + return nil } diff --git a/pkg/cluster/majorversionupgrade.go b/pkg/cluster/majorversionupgrade.go new file mode 100644 index 000000000..b6402a80d --- /dev/null +++ b/pkg/cluster/majorversionupgrade.go @@ -0,0 +1,82 @@ +package cluster + +import ( + "fmt" + + "github.com/zalando/postgres-operator/pkg/spec" + v1 "k8s.io/api/core/v1" +) + +// VersionMap Map of version numbers +var VersionMap = map[string]int{ + "9.5": 9500, + "9.6": 9600, + "10": 10000, + "11": 11000, + "12": 12000, + "13": 13000, +} + +// IsBiggerPostgresVersion Compare two Postgres version numbers +func IsBiggerPostgresVersion(old string, new string) bool { + oldN, _ := VersionMap[old] + newN, _ := VersionMap[new] + return newN > oldN +} + +// GetDesiredMajorVersionAsInt Convert string to comparable integer of PG version +func (c *Cluster) GetDesiredMajorVersionAsInt() int { + return VersionMap[c.GetDesiredMajorVersion()] +} + +// GetDesiredMajorVersion returns major version to use, incl. potential auto upgrade +func (c *Cluster) GetDesiredMajorVersion() string { + + if c.Config.OpConfig.MajorVersionUpgradeMode == "full" { + if IsBiggerPostgresVersion(c.Spec.PgVersion, c.Config.OpConfig.TargetMajorVersion) { + c.logger.Infof("Overwriting configured major version %s to %s", c.Spec.PgVersion, c.Config.OpConfig.TargetMajorVersion) + return c.Config.OpConfig.TargetMajorVersion + } + } + + return c.Spec.PgVersion +} + +func (c *Cluster) majorVersionUpgrade() error { + + if c.OpConfig.MajorVersionUpgradeMode == "off" { + return nil + } + + pods, _ := c.listPods() + allRunning := true + + var masterPod *v1.Pod + + for _, pod := range pods { + ps, _ := c.patroni.GetMemberData(&pod) + + if ps.State != "running" { + allRunning = false + } + + if ps.Role == "master" { + masterPod = &pod + c.currentMajorVersion = ps.ServerVersion + } + } + + numberOfPods := len(pods) + if allRunning && masterPod != nil { + if c.currentMajorVersion < c.GetDesiredMajorVersionAsInt() { + podName := &spec.NamespacedName{Namespace: masterPod.Namespace, Name: masterPod.Name} + c.ExecCommand(podName, fmt.Sprintf("python3 /scripts/inplace_upgrade.py %d", numberOfPods)) + } + } + + return nil +} + +func (c *Cluster) getCurrentMajorVersion() error { + return nil +} diff --git a/pkg/controller/util_test.go b/pkg/controller/util_test.go index edc05d67e..d8e4c3782 100644 --- a/pkg/controller/util_test.go +++ b/pkg/controller/util_test.go @@ -480,3 +480,45 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { } } } + +type SubConfig struct { + teammap map[string]string +} + +type SuperConfig struct { + sub SubConfig +} + +func TestUnderstandingMapsAndReferences(t *testing.T) { + teams := map[string]string{"acid": "Felix"} + + sc := SubConfig{ + teammap: teams, + } + + ssc := SuperConfig{ + sub: sc, + } + + teams["24x7"] = "alex" + + if len(ssc.sub.teammap) != 2 { + t.Errorf("Team Map does not contain 2 elements") + } + + ssc.sub.teammap["teapot"] = "Mikkel" + + if len(teams) != 3 { + t.Errorf("Team Map does not contain 3 elements") + } + + teams = make(map[string]string) + + if len(ssc.sub.teammap) != 3 { + t.Errorf("Team Map does not contain 0 elements") + } + + if &teams == &(ssc.sub.teammap) { + t.Errorf("Identical maps") + } +} diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 93fceff01..2969441c6 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -206,6 +206,10 @@ type Config struct { EnableLazySpiloUpgrade bool `name:"enable_lazy_spilo_upgrade" default:"false"` EnablePgVersionEnvVar bool `name:"enable_pgversion_env_var" default:"true"` EnableSpiloWalPathCompat bool `name:"enable_spilo_wal_path_compat" default:"false"` + MajorVersionUpgradeMode string `name:"major_version_upgrade_mode" default:"off"` // off - no actions, manual - manifest triggers action, full - manifest and minimal version violation trigger upgrade + MinimalMajorVersion string `name:"minimal_major_version" default:"9.5"` + TargetMajorVersion string `name:"target_major_version" default:"13"` + AllowedMajorUpgradeVersions []string `name:"allowed_major_upgrade_versions" default:"12,13"` } // MustMarshal marshals the config or panics diff --git a/pkg/util/patroni/patroni.go b/pkg/util/patroni/patroni.go index 53065e599..05f152c5a 100644 --- a/pkg/util/patroni/patroni.go +++ b/pkg/util/patroni/patroni.go @@ -27,6 +27,7 @@ type Interface interface { Switchover(master *v1.Pod, candidate string) error SetPostgresParameters(server *v1.Pod, options map[string]string) error GetPatroniMemberState(pod *v1.Pod) (string, error) + GetMemberData(server *v1.Pod) (MemberData, error) } // Patroni API client @@ -158,3 +159,56 @@ func (p *Patroni) GetPatroniMemberState(server *v1.Pod) (string, error) { return state, nil } + +// MemberData Patroni member data from Patroni API +type MemberData struct { + State string + Role string + ServerVersion int + Scope string + PatroniVersion string +} + +// GetMemberData read member data from patroni API +func (p *Patroni) GetMemberData(server *v1.Pod) (MemberData, error) { + + apiURLString, err := apiURL(server) + if err != nil { + return MemberData{}, err + } + response, err := p.httpClient.Get(apiURLString) + if err != nil { + return MemberData{}, fmt.Errorf("could not perform Get request: %v", err) + } + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return MemberData{}, fmt.Errorf("could not read response: %v", err) + } + + data := make(map[string]interface{}) + err = json.Unmarshal(body, &data) + if err != nil { + return MemberData{}, err + } + + memberData := MemberData{} + + var ok, r bool + + memberData.state, r = data["state"].(string) + ok = ok && r + memberData.serverVersion, r = data["server_version"].(int) + ok = ok && r + memberData.role, r = data["role"].(string) + ok = ok && r + memberData.role, r = data["scope"].(string) + ok = ok && r + + if !ok { + return MemberData{}, errors.New("Patroni member data could not be read") + } + + return memberData, nil +}