some api changes

This commit is contained in:
ston1th 2021-10-19 21:24:14 +02:00
commit 70fd3f71e5
11 changed files with 118 additions and 76 deletions

View file

@ -3,6 +3,7 @@ package main
import ( import (
"errors" "errors"
"flag" "flag"
"fmt"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -14,10 +15,12 @@ import (
"git.giftfish.de/ston1th/haproxy-lb/pkg/controller" "git.giftfish.de/ston1th/haproxy-lb/pkg/controller"
"git.giftfish.de/ston1th/haproxy-lb/pkg/etcd" "git.giftfish.de/ston1th/haproxy-lb/pkg/etcd"
"git.giftfish.de/ston1th/haproxy-lb/pkg/raft" "git.giftfish.de/ston1th/haproxy-lb/pkg/raft"
"github.com/go-logr/logr"
//"github.com/iand/logfmtr" //"github.com/iand/logfmtr"
"git.giftfish.de/ston1th/logfmtr" //"git.giftfish.de/ston1th/logfmtr"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
) )
var ( var (
@ -34,42 +37,52 @@ func fatal(err error, msg string) {
} }
func main() { func main() {
logopts := logfmtr.DefaultOptions() //logopts := logfmtr.DefaultOptions()
logopts.TimestampFormat = "2006-01-02T15:04:05.000Z-0700" //logopts.TimestampFormat = "2006-01-02T15:04:05.000Z-0700"
logopts.AddCaller = true //logopts.AddCaller = true
logopts.CallerSkip = 1 //logopts.CallerSkip = 1
logopts.Timezone = true //logopts.Timezone = true
logfmtr.UseOptions(logopts) //logfmtr.UseOptions(logopts)
flag.IntVar(&verbosity, "v", verbosity, "number for the log level verbosity") flag.IntVar(&verbosity, "v", verbosity, "number for the log level verbosity")
flag.StringVar(&configFile, "config", "haproxy-lb.yaml", "haproxy-lb config file") flag.StringVar(&configFile, "config", "haproxy-lb.yaml", "haproxy-lb config file")
flag.Parse() flag.Parse()
logfmtr.SetVerbosity(verbosity) //logfmtr.SetVerbosity(verbosity)
log = logfmtr.New().WithName("main") //log = logfmtr.New()
log.Info("starting haproxy-lb", "version", version) log = funcr.New(
func(pfx, args string) { fmt.Println("bla", pfx, args) },
funcr.Options{
LogCaller: funcr.All,
LogTimestamp: true,
},
)
l := log.WithName("main")
l.Info("starting haproxy-lb", "version", version)
cfg, err := config.ParseFile(configFile) cfg, err := config.ParseFile(configFile)
if err != nil { if err != nil {
fatal(err, "init failed") fatal(err, "config init failed")
} }
var c cluster.Cluster var c cluster.Cluster
if cfg.Cluster.Raft != nil { if cfg.Cluster.Raft != nil {
log.Info("creating cluster", "mode", "raft") l.Info("creating cluster", "mode", "raft")
c = raft.NewCluster(logfmtr.New().WithName("raft")) c = raft.NewCluster(log.WithName("raft"))
} else if cfg.Cluster.Etcd != nil { } else if cfg.Cluster.Etcd != nil {
log.Info("creating cluster", "mode", "etcd") l.Info("creating cluster", "mode", "etcd")
c = etcd.NewCluster(logfmtr.New().WithName("etcd")) c = etcd.NewCluster(log.WithName("etcd"))
} }
log.Info("starting api server", "address", cfg.APIServer.Listen, "tls", cfg.APIServer.TLS != nil, "auth", !cfg.APIServer.DisableAuth) l.Info("starting api server", "address", cfg.APIServer.Listen, "tls", cfg.APIServer.TLS != nil, "auth", !cfg.APIServer.DisableAuth)
srv, err := api.NewServer(cfg, logfmtr.New().WithName("api")) srv, err := api.NewServer(cfg, log.WithName("api"))
if err != nil { if err != nil {
fatal(err, "init failed") fatal(err, "init failed")
} }
log.Info("starting vip-controller") l.Info("starting vip-controller")
callbacks, err := controller.NewLBController(cfg, srv, logfmtr.New().WithName("vip-controller")) callbacks, err := controller.NewLBController(cfg, srv, log.WithName("vip-controller"))
if err != nil { if err != nil {
fatal(err, "init failed") fatal(err, "init failed")
} }
@ -81,7 +94,7 @@ func main() {
for { for {
err = c.Start(cfg) err = c.Start(cfg)
if err == cluster.ErrRestart { if err == cluster.ErrRestart {
log.Error(err, "received restart signal. restarting cluster controller.") l.Error(err, "received restart signal. restarting cluster controller.")
c.Reset() c.Reset()
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
@ -102,16 +115,16 @@ func main() {
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
s := <-sigs s := <-sigs
log.Info("haproxy-lb shutdown", "signal", s.String()) l.Info("haproxy-lb shutdown", "signal", s.String())
go func() { go func() {
select { select {
case <-time.After(time.Second * 20): case <-time.After(time.Second * 20):
log.Error(errors.New("shutdown took longer than 20 seconds"), "shutdown forced") l.Error(errors.New("shutdown took longer than 20 seconds"), "shutdown forced")
case <-sigs: case <-sigs:
log.Error(errors.New("force shutdown triggerd"), "shutdown forced") l.Error(errors.New("force shutdown triggerd"), "shutdown forced")
} }
os.Exit(1) os.Exit(1)
}() }()
c.Stop() c.Stop()
log.Info("haproxy-lb stopped") l.Info("haproxy-lb stopped")
} }

View file

@ -23,6 +23,7 @@ type Client struct {
endpoint string endpoint string
username string username string
password string password string
auth bool
httpClient *http.Client httpClient *http.Client
debugWriter io.Writer debugWriter io.Writer
insecure bool insecure bool
@ -62,6 +63,7 @@ func WithCredentials(username, password string) ClientOption {
return func(client *Client) { return func(client *Client) {
client.username = username client.username = username
client.password = password client.password = password
client.auth = true
} }
} }
@ -77,6 +79,7 @@ var (
type Config struct { type Config struct {
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
NoAuth bool `json:"no_auth"`
User string `json:"user"` User string `json:"user"`
Password string `json:"password"` Password string `json:"password"`
Insecure bool `json:"insecure"` Insecure bool `json:"insecure"`
@ -87,6 +90,13 @@ func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) {
err = ErrMissingEndpoint err = ErrMissingEndpoint
return return
} }
options = []ClientOption{
WithEndpoint(cfg.Endpoint),
WithInsecureClient(cfg.Insecure),
}
if cfg.NoAuth {
return
}
if cfg.User == "" { if cfg.User == "" {
err = ErrMissingUser err = ErrMissingUser
return return
@ -95,11 +105,7 @@ func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) {
err = ErrMissingPassword err = ErrMissingPassword
return return
} }
options = []ClientOption{ options = append(options, WithCredentials(cfg.User, cfg.Password))
WithEndpoint(cfg.Endpoint),
WithCredentials(cfg.User, cfg.Password),
WithInsecureClient(cfg.Insecure),
}
return return
} }
@ -109,6 +115,17 @@ func ClientOptionsFromEnv() (options []ClientOption, err error) {
err = ErrMissingEndpointEnv err = ErrMissingEndpointEnv
return return
} }
insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE"))
options = []ClientOption{
WithEndpoint(endpoint),
WithInsecureClient(insecure),
}
noAuth, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_NO_AUTH"))
if noAuth {
return
}
user := os.Getenv("HAPROXY_LB_USER") user := os.Getenv("HAPROXY_LB_USER")
if user == "" { if user == "" {
err = ErrMissingUserEnv err = ErrMissingUserEnv
@ -121,13 +138,7 @@ func ClientOptionsFromEnv() (options []ClientOption, err error) {
return return
} }
insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE")) options = append(options, WithCredentials(user, pass))
options = []ClientOption{
WithEndpoint(endpoint),
WithCredentials(user, pass),
WithInsecureClient(insecure),
}
return return
} }
@ -163,7 +174,9 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
if err != nil { if err != nil {
return nil, err return nil, err
} }
if c.auth {
req.SetBasicAuth(c.username, c.password) req.SetBasicAuth(c.username, c.password)
}
req = req.WithContext(ctx) req = req.WithContext(ctx)
return req, nil return req, nil
} }

View file

@ -2,15 +2,18 @@ package client
import ( import (
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
) )
type Error struct { type Error struct {
Message string `json:"message"` Err string `json:"error"`
Status int `json:"status"` Status int `json:"status"`
} }
func (e Error) Error() string {
return e.Err
}
func decodeResp(resp *http.Response, v interface{}) error { func decodeResp(resp *http.Response, v interface{}) error {
if resp.Body == nil { if resp.Body == nil {
return nil return nil
@ -22,16 +25,13 @@ func decodeResp(resp *http.Response, v interface{}) error {
} }
return json.NewDecoder(resp.Body).Decode(&v) return json.NewDecoder(resp.Body).Decode(&v)
} }
if resp.StatusCode == 404 {
return ErrNotFound
}
var e Error var e Error
err := json.NewDecoder(resp.Body).Decode(&e) err := json.NewDecoder(resp.Body).Decode(&e)
if err != nil { if err != nil {
return err return err
} }
if resp.StatusCode >= 400 && resp.StatusCode <= 599 { if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
return errors.New(e.Message) return e
} }
return nil return nil
} }

View file

@ -43,7 +43,7 @@ type notFoundHandler struct {
} }
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
types.NewContext(w, r, nf.s.Data, nf.s.log).NotFound() types.NewContext(w, r, nf.s.Data, nf.s.log).Err(types.ErrInvalidAPIRoute)
} }
// NewHTTPServer returns a new HTTPServer // NewHTTPServer returns a new HTTPServer

View file

@ -86,7 +86,7 @@ func (c *Context) log() {
c.Log.Info("access", c.Log.Info("access",
"addr", c.Request.RemoteAddr, "addr", c.Request.RemoteAddr,
"method", c.Request.Method, "method", c.Request.Method,
"url", c.Request.URL.Path, "path", c.Request.URL.Path,
"status", c.Status, "status", c.Status,
) )
} }

View file

@ -19,7 +19,7 @@ type Err struct {
// Error returns the error string // Error returns the error string
func (a Err) Error() string { func (a Err) Error() string {
return `{"message":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}` return `{"error":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}`
} }
// Status returns the HTTP status code // Status returns the HTTP status code
@ -35,13 +35,17 @@ func newError(err string, status int) Error {
} }
var ( var (
ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound)
ErrBadreq = newError("bad request", http.StatusBadRequest) ErrBadreq = newError("bad request", http.StatusBadRequest)
ErrInvalid = newError("invalid data", http.StatusBadRequest) ErrInvalid = newError("invalid data", http.StatusBadRequest)
ErrUnauthorized = newError("unauthorized", http.StatusUnauthorized) ErrUnauthorized = newError("unauthorized", http.StatusUnauthorized)
ErrForbidden = newError("forbidden", http.StatusForbidden) ErrForbidden = newError("forbidden", http.StatusForbidden)
ErrNotFound = newError("not found", http.StatusNotFound) ErrNotFound = newError("not found", http.StatusNotFound)
ErrLBNotFound = newError("loadbalancer not found", http.StatusNotFound)
ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed) ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed)
ErrExists = newError("resource already exists", http.StatusConflict) ErrExists = newError("resource already exists", http.StatusConflict)
ErrISE = newError("internal server error", http.StatusInternalServerError) ErrISE = newError("internal server error", http.StatusInternalServerError)
ErrFollower = newError("follower write forbidden", http.StatusForbidden) ErrFollower = newError("follower write forbidden", http.StatusForbidden)
ErrClusterNotFound = newError("cluster not found", http.StatusNotFound)
ErrLBsNotFound = newError("loadbalancers not found", http.StatusNotFound)
) )

View file

@ -13,7 +13,7 @@ import (
var ( var (
ErrClientIsNil = errors.New("client is nil") ErrClientIsNil = errors.New("client is nil")
base = "/" + schema.Version base = schema.LBPath
) )
type Client struct { type Client struct {
@ -32,7 +32,7 @@ func (c *Client) GetLoadBalancers(ctx context.Context) (lbs map[string]schema.Lo
} }
func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string) (lbs map[string]schema.LoadBalancer, err error) { func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string) (lbs map[string]schema.LoadBalancer, err error) {
path := base + "/lb" path := base
if cluster != "" { if cluster != "" {
path += "/" + cluster path += "/" + cluster
} }
@ -46,7 +46,7 @@ func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string
} }
func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb schema.LoadBalancer, err error) { func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb schema.LoadBalancer, err error) {
path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
req, err := c.c.NewRequest(ctx, "GET", path, nil) req, err := c.c.NewRequest(ctx, "GET", path, nil)
if err != nil { if err != nil {
return return
@ -57,7 +57,7 @@ func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb
func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, lb schema.LoadBalancer) (err error) { func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, lb schema.LoadBalancer) (err error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
err = lb.ValidateClient() err = lb.ValidateClient()
if err != nil { if err != nil {
return return
@ -75,7 +75,7 @@ func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, l
} }
func (c *Client) DeleteLoadBalancer(ctx context.Context, cluster, name string) (err error) { func (c *Client) DeleteLoadBalancer(ctx context.Context, cluster, name string) (err error) {
path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
req, err := c.c.NewRequest(ctx, "DELETE", path, nil) req, err := c.c.NewRequest(ctx, "DELETE", path, nil)
if err != nil { if err != nil {
return return
@ -88,7 +88,7 @@ func (c *Client) DeleteLoadBalancersWithCluster(ctx context.Context, cluster str
if cluster == "" { if cluster == "" {
return errors.New("cluster value can not be empty") return errors.New("cluster value can not be empty")
} }
path := base + "/lb/" + cluster path := base + "/" + cluster
req, err := c.c.NewRequest(ctx, "DELETE", path, nil) req, err := c.c.NewRequest(ctx, "DELETE", path, nil)
if err != nil { if err != nil {
return return

View file

@ -12,7 +12,11 @@ healthCheckNodePort
*/ */
const Version = "v1" const (
Version = "v1"
LBName = "lb"
LBPath = "/" + Version + "/" + LBName
)
type ProxyProtocol string type ProxyProtocol string

View file

@ -62,7 +62,7 @@ func lbHandler(ctx *types.Context) {
if err != nil { if err != nil {
ctx.Log.Error(err, "error reading loadbalancer", "cluster", cl, "name", n) ctx.Log.Error(err, "error reading loadbalancer", "cluster", cl, "name", n)
if err == cluster.ErrKeyNotFound { if err == cluster.ErrKeyNotFound {
ctx.Err(types.ErrNotFound) ctx.Err(types.ErrLBNotFound)
return return
} }
ctx.Err(types.ErrISE) ctx.Err(types.ErrISE)
@ -139,7 +139,7 @@ func lbHandler(ctx *types.Context) {
return return
} }
if !ctx.Data.DB.LBExists(name) { if !ctx.Data.DB.LBExists(name) {
ctx.Err(types.ErrNotFound) ctx.Err(types.ErrLBNotFound)
return return
} }
cidr, err := ctx.Data.DB.GetCIDR(name) cidr, err := ctx.Data.DB.GetCIDR(name)
@ -175,6 +175,10 @@ func lbList(lbs map[string][]byte) (m map[string]json.RawMessage) {
func lbClusterHandler(ctx *types.Context) { func lbClusterHandler(ctx *types.Context) {
cl := ctx.Var("cluster") cl := ctx.Var("cluster")
m, err := ctx.Data.DB.GetLBs(cl) m, err := ctx.Data.DB.GetLBs(cl)
if err == cluster.ErrPrefixNotFound {
ctx.Err(types.ErrClusterNotFound)
return
}
if err != nil { if err != nil {
ctx.Log.Error(err, "error reading loadbalancers", "cluster", cl) ctx.Log.Error(err, "error reading loadbalancers", "cluster", cl)
ctx.Err(types.ErrISE) ctx.Err(types.ErrISE)
@ -214,6 +218,10 @@ func lbClusterHandler(ctx *types.Context) {
} }
func lbListHandler(ctx *types.Context) { func lbListHandler(ctx *types.Context) {
m, err := ctx.Data.DB.GetLBs("") m, err := ctx.Data.DB.GetLBs("")
if err == cluster.ErrPrefixNotFound {
ctx.Err(types.ErrLBsNotFound)
return
}
if err != nil { if err != nil {
ctx.Log.Error(err, "error reading loadbalancer clusters") ctx.Log.Error(err, "error reading loadbalancer clusters")
ctx.Err(types.ErrISE) ctx.Err(types.ErrISE)

View file

@ -2,29 +2,33 @@ package server
import ( import (
"git.giftfish.de/ston1th/haproxy-lb/pkg/api/types" "git.giftfish.de/ston1th/haproxy-lb/pkg/api/types"
schemav1 "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema" "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema"
) )
const v1 = "/" + schemav1.Version const (
healthz = "/healthz"
lbcluster = schema.LBPath + "/{cluster:[a-zA-Z0-9-]+$}"
lbclustername = schema.LBPath + "/{cluster:[a-zA-Z0-9-]+}/{name:[a-zA-Z0-9-_]+$}"
)
var Routes = []types.Route{ var Routes = []types.Route{
{ {
"/healthz", healthz,
healthzHandler, healthzHandler,
[]string{"GET"}, []string{"GET"},
}, },
{ {
v1 + "/lb", schema.LBPath,
authHandler(lbListHandler), authHandler(lbListHandler),
[]string{"GET"}, []string{"GET"},
}, },
{ {
v1 + "/lb/{cluster:[a-zA-Z0-9-]+$}", lbcluster,
authHandler(lbClusterHandler), authHandler(lbClusterHandler),
[]string{"GET", "DELETE"}, []string{"GET", "DELETE"},
}, },
{ {
v1 + "/lb/{cluster:[a-zA-Z0-9-]+}/{name:[a-zA-Z0-9-_]+$}", lbclustername,
authHandler(lbHandler), authHandler(lbHandler),
[]string{"GET", "POST", "DELETE"}, []string{"GET", "POST", "DELETE"},
}, },

View file

@ -86,10 +86,6 @@ func (db *DB) GetLBs(cl string) (lbs map[string][]byte, err error) {
cl = cl + "/" cl = cl + "/"
} }
m, err := db.kv.GetPrefix(lbPrefix + cl) m, err := db.kv.GetPrefix(lbPrefix + cl)
if err == cluster.ErrPrefixNotFound {
err = nil
return
}
if err != nil { if err != nil {
return return
} }