package server import ( "encoding/json" "errors" "net" "strings" "git.giftfish.de/ston1th/haproxy-lb/pkg/api/types" "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema" "git.giftfish.de/ston1th/haproxy-lb/pkg/cluster" ) var errFollower = errors.New("follower write") func authHandler(h types.CtxHandler) types.CtxHandler { return func(ctx *types.Context) { if !ctx.Data.AuthDisabled { user, pass, ok := ctx.Request.BasicAuth() if !ok { ctx.Err(types.ErrUnauthorized) return } if !ctx.Data.Auth.Login(user, pass, ctx.Path()) { ctx.Err(types.ErrForbidden) return } } h(ctx) } } func healthzHandler(ctx *types.Context) { // TODO maybe report etcd/raft stats ctx.OK() } func name(c, n string) string { return c + "/" + n } const lbsep = "_" func lbname(c, n string) string { return c + lbsep + n } func convlbname(n string) string { a := strings.SplitN(n, lbsep, 1) if len(a) != 2 { return "" } return name(a[0], a[1]) } func getIP(cidr string) (string, error) { ip, _, err := net.ParseCIDR(cidr) if err != nil { return "", err } return ip.String(), nil } func lbHandler(ctx *types.Context) { cl := ctx.Var("cluster") n := ctx.Var("name") name := name(cl, n) switch ctx.Method() { case "GET": b, err := ctx.Data.DB.GetLB(name) if err != nil { ctx.Log.Error(err, "error reading loadbalancer", "cluster", cl, "name", n) if err == cluster.ErrKeyNotFound { ctx.Err(types.ErrLBNotFound) return } ctx.Err(types.ErrISE) return } ctx.Body(b) case "POST": if !ctx.Leader() { ctx.Log.Error(errFollower, "error writing config: i am a follower", "cluster", cl, "name", n) ctx.Err(types.ErrFollower) return } b, err := ctx.ReadBody() if err != nil { ctx.Log.Error(err, "error reading request body", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } var lb schema.LoadBalancer err = json.Unmarshal(b, &lb) if err != nil { ctx.Log.Error(err, "error decoding request body", "cluster", cl, "name", n) ctx.Err(types.ErrInvalid) return } lb.Name = lbname(cl, n) cidr, err := ctx.Data.DB.GetCIDR(name) if err != nil && err != cluster.ErrKeyNotFound { ctx.Log.Error(err, "error getting existing loadbalancer ip", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } if cidr != "" { lb.CIDR = cidr lb.IP, err = getIP(cidr) if err != nil { ctx.Log.Error(err, "error parsing existing ip", "cluster", cl, "name", n, "cidr", cidr) ctx.Err(types.ErrISE) return } } else { ip, err := ctx.Data.Net.Alloc() if err != nil { ctx.Log.Error(err, "error allocating ip for loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } lb.IP = ip.IP.String() lb.CIDR = ctx.Data.Net.CIDR(ip) } err = lb.ValidateServer() if err != nil { ctx.Log.Error(err, "error validating loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrInvalid) return } b, err = lb.JSON() if err != nil { ctx.Log.Error(err, "error encoding loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } err = ctx.Data.DB.SetLB(name, b) if err != nil { ctx.Log.Error(err, "error writing loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } ctx.OK() case "DELETE": if !ctx.Leader() { ctx.Log.Error(errFollower, "error writing config: i am a follower", "cluster", cl, "name", n) ctx.Err(types.ErrFollower) return } if !ctx.Data.DB.LBExists(name) { ctx.Err(types.ErrLBNotFound) return } cidr, err := ctx.Data.DB.GetCIDR(name) if err != nil && err != cluster.ErrKeyNotFound { ctx.Log.Error(err, "error reading IP of loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } err = ctx.Data.Net.FreeCIDR(cidr) if err != nil && err != cluster.ErrKeyNotFound { ctx.Log.Error(err, "error freeing IP of loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } err = ctx.Data.DB.DeleteLB(name) if err != nil { ctx.Log.Error(err, "error deleting loadbalancer", "cluster", cl, "name", n) ctx.Err(types.ErrISE) return } ctx.OK() } } func lbList(lbs map[string][]byte) (m map[string]json.RawMessage) { m = make(map[string]json.RawMessage) for k, v := range lbs { m[k] = json.RawMessage(v) } return } func lbClusterHandler(ctx *types.Context) { cl := ctx.Var("cluster") m, err := ctx.Data.DB.GetLBs(cl) if err == cluster.ErrPrefixNotFound { ctx.Err(types.ErrClusterNotFound) return } if err != nil { ctx.Log.Error(err, "error reading loadbalancers", "cluster", cl) ctx.Err(types.ErrISE) return } switch ctx.Method() { case "GET": ctx.JSON(lbList(m)) case "DELETE": if !ctx.Leader() { ctx.Log.Error(errFollower, "error writing config: i am a follower", "cluster", cl, "name", name) ctx.Err(types.ErrFollower) return } for n, _ := range m { name := convlbname(n) cidr, err := ctx.Data.DB.GetCIDR(name) if err != nil && err != cluster.ErrKeyNotFound { ctx.Log.Error(err, "error reading IP of loadbalancer", "cluster", cl, "name", name) ctx.Err(types.ErrISE) return } if err == cluster.ErrKeyNotFound { continue } err = ctx.Data.Net.FreeCIDR(cidr) if err != nil && err != cluster.ErrKeyNotFound { ctx.Log.Error(err, "error freeing IP of loadbalancer", "cluster", cl, "name", name) ctx.Err(types.ErrISE) return } err = ctx.Data.DB.DeleteLB(name) if err != nil { ctx.Log.Error(err, "error deleting loadbalancer", "cluster", cl, "name", name) ctx.Err(types.ErrISE) return } } ctx.OK() } } func lbListHandler(ctx *types.Context) { m, err := ctx.Data.DB.GetLBs("") if err == cluster.ErrPrefixNotFound { ctx.Err(types.ErrLBsNotFound) return } if err != nil { ctx.Log.Error(err, "error reading loadbalancer clusters") ctx.Err(types.ErrISE) return } ctx.JSON(lbList(m)) }