diff --git a/README.md b/README.md index b463d6b..efccba1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # vip-manager -Manages a virtual IP based on state kept in `etcd` or `Consul` +Manages a virtual IP based on state kept in `etcd`, `Consul` or using `Patroni` REST API ## Table of Contents - [Prerequisites](#prerequisites) @@ -159,4 +159,4 @@ Either: ## Author -Cybertec Schönig & Schönig GmbH, https://www.cybertec-postgresql.com +CYBERTEC PostgreSQL International GmbH, https://www.cybertec-postgresql.com diff --git a/checker/leader_checker.go b/checker/leader_checker.go index 715126e..892fa27 100644 --- a/checker/leader_checker.go +++ b/checker/leader_checker.go @@ -25,6 +25,8 @@ func NewLeaderChecker(con *vipconfig.Config) (LeaderChecker, error) { lc, err = NewConsulLeaderChecker(con) case "etcd", "etcd3": lc, err = NewEtcdLeaderChecker(con) + case "patroni": + lc, err = NewPatroniLeaderChecker(con) default: err = ErrUnsupportedEndpointType } diff --git a/checker/patroni_leader_checker.go b/checker/patroni_leader_checker.go new file mode 100644 index 0000000..24dfbd8 --- /dev/null +++ b/checker/patroni_leader_checker.go @@ -0,0 +1,41 @@ +package checker + +import ( + "context" + "log" + "strconv" + "time" + + "net/http" + + "github.com/cybertec-postgresql/vip-manager/vipconfig" +) + +// PatroniLeaderChecker will use Patroni REST API to check the leader. +// --trigger-key is used to specify the endpoint to check, e.g. /leader. +// --trigger-value is used to specify the HTTP code to expect, e.g. 200. +type PatroniLeaderChecker struct { + *vipconfig.Config +} + +// NewPatroniLeaderChecker returns a new instance +func NewPatroniLeaderChecker(conf *vipconfig.Config) (*PatroniLeaderChecker, error) { + return &PatroniLeaderChecker{conf}, nil +} + +// GetChangeNotificationStream checks the status in the loop +func (c *PatroniLeaderChecker) GetChangeNotificationStream(ctx context.Context, out chan<- bool) error { + for { + select { + case <-ctx.Done(): + return nil + case <-time.After(time.Duration(cConf.Interval) * time.Millisecond): + r, err := http.Get(c.Endpoints[0] + c.Key) + if err != nil { + log.Printf("patroni REST API error: %s", err) + continue + } + out <- strconv.Itoa(r.StatusCode) == c.Nodename + } + } +} diff --git a/vipconfig/config.go b/vipconfig/config.go index 0229f85..67bc01d 100644 --- a/vipconfig/config.go +++ b/vipconfig/config.go @@ -55,9 +55,9 @@ func defineFlags() { pflag.String("trigger-key", "", "Key in the DCS to monitor, e.g. \"/service/batman/leader\".") pflag.String("trigger-value", "", "Value to monitor for.") - pflag.String("dcs-type", "etcd", "Type of endpoint used for key storage. Supported values: etcd, consul.") + pflag.String("dcs-type", "etcd", "Type of endpoint used for key storage. Supported values: etcd, consul, patroni.") // note: can't put a default value into dcs-endpoints as that would mess with applying default localhost when using consul - pflag.String("dcs-endpoints", "", "DCS endpoint(s), separate multiple endpoints using commas. (default \"http://127.0.0.1:2379\" or \"http://127.0.0.1:8500\" depending on dcs-type.)") + pflag.String("dcs-endpoints", "", "DCS endpoint(s), separate multiple endpoints using commas. (default \"http://127.0.0.1:2379\", \"http://127.0.0.1:8500\" or \"http://127.0.0.1:8008/\" depending on dcs-type.)") pflag.String("etcd-user", "", "Username for etcd DCS endpoints.") pflag.String("etcd-password", "", "Password for etcd DCS endpoints.") pflag.String("etcd-ca-file", "", "Trusted CA certificate for the etcd server.") @@ -302,12 +302,18 @@ func NewConfig() (*Config, error) { viper.Set("dcs-endpoints", []string{"http://127.0.0.1:8500"}) case "etcd", "etcd3": viper.Set("dcs-endpoints", []string{"http://127.0.0.1:2379"}) + case "patroni": + viper.Set("dcs-endpoints", []string{"http://127.0.0.1:8008/"}) } } - // set trigger-value to hostname if nothing is specified - if len(viper.GetString("trigger-value")) == 0 { - triggerValue, err := os.Hostname() + // set trigger-value to default value if nothing is specified + if triggerValue := viper.GetString("trigger-value"); len(triggerValue) == 0 { + if viper.GetString("dcs-type") == "patroni" { + triggerValue = "200" + } else { + triggerValue, err = os.Hostname() + } if err != nil { log.Printf("No trigger-value specified, hostname could not be retrieved: %s", err) } else { diff --git a/vipconfig/vip-manager.yml b/vipconfig/vip-manager.yml index a99177e..f9ca52b 100644 --- a/vipconfig/vip-manager.yml +++ b/vipconfig/vip-manager.yml @@ -15,13 +15,13 @@ interface: enp0s3 #interface to which the virtual ip will be added # how the virtual ip should be managed. we currently support "ip addr add/remove" through shell commands or the Hetzner api hosting-type: basic # possible values: basic, or hetzner. -dcs-type: etcd # etcd or consul +dcs-type: etcd # etcd, consul or patroni # a list that contains all DCS endpoints to which vip-manager could talk. dcs-endpoints: - http://127.0.0.1:2379 - https://192.168.0.42:2379 # A single list-item is also fine. - # consul will always only use the first entry from this list. + # consul and patroni will always only use the first entry from this list. # For consul, you'll obviously need to change the port to 8500. Unless you're using a different one. Maybe you're a rebel and are running consul on port 2379? Just to confuse people? Why would you do that? Oh, I get it. etcd-user: "patroni"