From 8aa11ecee2f5b073e3f8b53240ec1555ca1663c4 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 30 Aug 2017 16:01:18 +0200 Subject: [PATCH] Add patroni api client --- pkg/cluster/cluster.go | 3 ++ pkg/util/patroni/patroni.go | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 pkg/util/patroni/patroni.go diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index f48b94fb1..b5882448f 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -23,6 +23,7 @@ import ( "github.com/zalando-incubator/postgres-operator/pkg/util/config" "github.com/zalando-incubator/postgres-operator/pkg/util/constants" "github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil" + "github.com/zalando-incubator/postgres-operator/pkg/util/patroni" "github.com/zalando-incubator/postgres-operator/pkg/util/teams" "github.com/zalando-incubator/postgres-operator/pkg/util/users" "github.com/zalando-incubator/postgres-operator/pkg/util/volumes" @@ -55,6 +56,7 @@ type Cluster struct { spec.Postgresql Config logger *logrus.Entry + patroni patroni.Interface pgUsers map[string]spec.PgUser systemUsers map[string]spec.PgUser podSubscribers map[spec.NamespacedName]chan spec.PodEvent @@ -105,6 +107,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec spec.Postgresql teamsAPIClient: teams.NewTeamsAPI(cfg.OpConfig.TeamsAPIUrl, logger), } cluster.logger = logger.WithField("pkg", "cluster").WithField("cluster-name", cluster.clusterName()) + cluster.patroni = patroni.New(cluster.logger) return cluster } diff --git a/pkg/util/patroni/patroni.go b/pkg/util/patroni/patroni.go new file mode 100644 index 000000000..6c254b41a --- /dev/null +++ b/pkg/util/patroni/patroni.go @@ -0,0 +1,80 @@ +package patroni + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/Sirupsen/logrus" + "k8s.io/client-go/pkg/api/v1" +) + +const ( + failoverPath = "/failover" + apiPort = 8008 + timeout = 30 * time.Second +) + +// Interface describe patroni methods +type Interface interface { + Failover(master *v1.Pod, candidate string) error +} + +// Patroni API client +type Patroni struct { + httpClient *http.Client + logger *logrus.Entry +} + +// New create patroni +func New(logger *logrus.Entry) *Patroni { + cl := http.Client{ + Timeout: timeout, + } + + return &Patroni{ + logger: logger, + httpClient: &cl, + } +} + +func (p *Patroni) apiURL(masterPod *v1.Pod) string { + return fmt.Sprintf("http://%s:%d", masterPod.Status.PodIP, apiPort) +} + +// Failover does manual failover via patroni api +func (p *Patroni) Failover(master *v1.Pod, candidate string) error { + buf := &bytes.Buffer{} + + err := json.NewEncoder(buf).Encode(map[string]string{"leader": master.Name, "member": candidate}) + if err != nil { + return fmt.Errorf("could not encode json: %v", err) + } + + request, err := http.NewRequest(http.MethodPost, p.apiURL(master)+failoverPath, buf) + if err != nil { + return fmt.Errorf("could not create request: %v", err) + } + + p.logger.Debugf("making http request: %s", request.URL.String()) + + resp, err := p.httpClient.Do(request) + if err != nil { + return fmt.Errorf("could not make request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("could not read response: %v", err) + } + + return fmt.Errorf("patroni returned '%s'", string(bodyBytes)) + } + + return nil +}