mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-18 19:02:52 +02:00
Support major version upgrade via manifest and global upgrades via min version (#1372)
Support major version upgrade trigger via manifest. There is `off` `manual` and `full`. Manual is what you expect, and full will auto upgrade clusters below a certain threshold.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package httpclient
|
||||
|
||||
//go:generate mockgen -package mocks -destination=$PWD/mocks/$GOFILE -source=$GOFILE -build_flags=-mod=vendor
|
||||
|
||||
import "net/http"
|
||||
|
||||
// HTTPClient interface
|
||||
type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
Get(url string) (resp *http.Response, err error)
|
||||
}
|
||||
+39
-22
@@ -3,7 +3,6 @@ package patroni
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -11,6 +10,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
httpclient "github.com/zalando/postgres-operator/pkg/util/httpclient"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
@@ -26,24 +27,28 @@ const (
|
||||
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
|
||||
type Patroni struct {
|
||||
httpClient *http.Client
|
||||
httpClient httpclient.HTTPClient
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
// New create patroni
|
||||
func New(logger *logrus.Entry) *Patroni {
|
||||
cl := http.Client{
|
||||
Timeout: timeout,
|
||||
func New(logger *logrus.Entry, client httpclient.HTTPClient) *Patroni {
|
||||
if client == nil {
|
||||
|
||||
client = &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &Patroni{
|
||||
logger: logger,
|
||||
httpClient: &cl,
|
||||
httpClient: client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +73,9 @@ func (p *Patroni) httpPostOrPatch(method string, url string, body *bytes.Buffer)
|
||||
return fmt.Errorf("could not create request: %v", err)
|
||||
}
|
||||
|
||||
p.logger.Debugf("making %s http request: %s", method, request.URL.String())
|
||||
if p.logger != nil {
|
||||
p.logger.Debugf("making %s http request: %s", method, request.URL.String())
|
||||
}
|
||||
|
||||
resp, err := p.httpClient.Do(request)
|
||||
if err != nil {
|
||||
@@ -126,35 +133,45 @@ func (p *Patroni) SetPostgresParameters(server *v1.Pod, parameters map[string]st
|
||||
return p.httpPostOrPatch(http.MethodPatch, apiURLString+configPath, buf)
|
||||
}
|
||||
|
||||
//GetPatroniMemberState returns a state of member of a Patroni cluster
|
||||
func (p *Patroni) GetPatroniMemberState(server *v1.Pod) (string, error) {
|
||||
// MemberDataPatroni child element
|
||||
type MemberDataPatroni struct {
|
||||
Version string `json:"version"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// MemberData Patroni member data from Patroni API
|
||||
type MemberData struct {
|
||||
State string `json:"state"`
|
||||
Role string `json:"role"`
|
||||
ServerVersion int `json:"server_version"`
|
||||
PendingRestart bool `json:"pending_restart"`
|
||||
ClusterUnlocked bool `json:"cluster_unlocked"`
|
||||
Patroni MemberDataPatroni `json:"patroni"`
|
||||
}
|
||||
|
||||
// GetMemberData read member data from patroni API
|
||||
func (p *Patroni) GetMemberData(server *v1.Pod) (MemberData, error) {
|
||||
|
||||
apiURLString, err := apiURL(server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return MemberData{}, err
|
||||
}
|
||||
response, err := p.httpClient.Get(apiURLString)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not perform Get request: %v", err)
|
||||
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 "", fmt.Errorf("could not read response: %v", err)
|
||||
return MemberData{}, fmt.Errorf("could not read response: %v", err)
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data := MemberData{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return MemberData{}, err
|
||||
}
|
||||
|
||||
state, ok := data["state"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("Patroni Get call response contains wrong type for 'state' field")
|
||||
}
|
||||
|
||||
return state, nil
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package patroni
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"k8s.io/api/core/v1"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/zalando/postgres-operator/mocks"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func newMockPod(ip string) *v1.Pod {
|
||||
@@ -72,3 +79,32 @@ func TestApiURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatroniAPI(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
json := `{"state": "running", "postmaster_start_time": "2021-02-19 14:31:50.053 CET", "role": "master", "server_version": 90621, "cluster_unlocked": false, "xlog": {"location": 55978296057856}, "timeline": 6, "database_system_identifier": "6462555844314089962", "pending_restart": true, "patroni": {"version": "2.0.1", "scope": "acid-rest92-standby"}}`
|
||||
r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
|
||||
|
||||
response := http.Response{
|
||||
Status: "200",
|
||||
Body: r,
|
||||
}
|
||||
|
||||
mockClient := mocks.NewMockHTTPClient(ctrl)
|
||||
mockClient.EXPECT().Get(gomock.Any()).Return(&response, nil)
|
||||
|
||||
p := New(nil, mockClient)
|
||||
|
||||
pod := v1.Pod{
|
||||
Status: v1.PodStatus{
|
||||
PodIP: "192.168.100.1",
|
||||
},
|
||||
}
|
||||
_, err := p.GetMemberData(&pod)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Could not read Patroni data: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user