HAProxy Data Plane API
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

415 lines
13 KiB

// Copyright 2019 HAProxy Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package handlers
import (
"fmt"
"strings"
"github.com/go-openapi/runtime/middleware"
client_native "github.com/haproxytech/client-native/v6"
native_errors "github.com/haproxytech/client-native/v6/errors"
"github.com/haproxytech/client-native/v6/models"
"github.com/haproxytech/dataplaneapi/misc"
"github.com/haproxytech/dataplaneapi/operations/server"
)
// GetRuntimeServerHandlerImpl implementation of the GetRuntimeServerHandler interface using client-native client
type GetRuntimeServerHandlerImpl struct {
Client client_native.HAProxyClient
}
// GetRuntimeServersHandlerImpl implementation of the GetRuntimeServersHandler interface using client-native client
type GetRuntimeServersHandlerImpl struct {
Client client_native.HAProxyClient
}
// ReplaceRuntimeServerHandlerImpl implementation of the ReplaceRuntimeServerHandler interface using client-native client
type ReplaceRuntimeServerHandlerImpl struct {
Client client_native.HAProxyClient
}
// AddRuntimeServerHandlerImpl implementation of the ServerAddRuntimeServerHandler interface using client-native client
type AddRuntimeServerHandlerImpl struct {
Client client_native.HAProxyClient
}
// DeleteRuntimeServerHandlerImpl implementation of the ServerDeleteRuntimeServerHandler interface using client-native client
type DeleteRuntimeServerHandlerImpl struct {
Client client_native.HAProxyClient
}
// Handle executing the request and returning a response
func (h *GetRuntimeServerHandlerImpl) Handle(params server.GetRuntimeServerParams, principal interface{}) middleware.Responder {
rn, err := h.Client.Runtime()
if err != nil {
e := misc.HandleError(err)
return server.NewGetRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
rs, err := rn.GetServerState(params.Backend, params.Name)
if err != nil {
if isNotFoundError(err) {
code := int64(404)
msg := err.Error()
return server.NewGetRuntimeServerNotFound().WithPayload(&models.Error{Code: &code, Message: &msg})
}
e := misc.HandleError(err)
return server.NewGetRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
if rs == nil {
code := int64(404)
msg := fmt.Sprintf("Runtime server %s not found in backend %s", params.Name, params.Backend)
return server.NewGetRuntimeServerNotFound().WithPayload(&models.Error{Code: &code, Message: &msg})
}
return server.NewGetRuntimeServerOK().WithPayload(rs)
}
// Handle executing the request and returning a response
func (h *GetRuntimeServersHandlerImpl) Handle(params server.GetRuntimeServersParams, principal interface{}) middleware.Responder {
runtime, err := h.Client.Runtime()
if err != nil {
e := misc.HandleError(err)
return server.NewGetRuntimeServersDefault(int(*e.Code)).WithPayload(e)
}
rs, err := runtime.GetServersState(params.Backend)
if err != nil {
e := misc.HandleContainerGetError(err)
if *e.Code == misc.ErrHTTPOk {
return server.NewGetRuntimeServersOK().WithPayload(models.RuntimeServers{})
}
return server.NewGetRuntimeServersDefault(int(*e.Code)).WithPayload(e)
}
return server.NewGetRuntimeServersOK().WithPayload(rs)
}
// Handle executing the request and returning a response
func (h *ReplaceRuntimeServerHandlerImpl) Handle(params server.ReplaceRuntimeServerParams, principal interface{}) middleware.Responder {
runtime, err := h.Client.Runtime()
if err != nil {
e := misc.HandleError(err)
return server.NewReplaceRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
rs, err := runtime.GetServerState(params.Backend, params.Name)
if err != nil {
e := misc.HandleError(err)
return server.NewReplaceRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
if rs == nil {
code := int64(404)
msg := fmt.Sprintf("Runtime server %s not found in backend %s", params.Name, params.Backend)
return server.NewReplaceRuntimeServerNotFound().WithPayload(&models.Error{Code: &code, Message: &msg})
}
// change operational state
if params.Data.OperationalState != "" && rs.OperationalState != params.Data.OperationalState {
err = runtime.SetServerHealth(params.Backend, params.Name, params.Data.OperationalState)
if err != nil {
e := misc.HandleError(err)
return server.NewReplaceRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
}
// change admin state
if params.Data.AdminState != "" && rs.AdminState != params.Data.AdminState {
err = runtime.SetServerState(params.Backend, params.Name, params.Data.AdminState)
if err != nil {
e := misc.HandleError(err)
// try to revert operational state and fall silently
//nolint:errcheck
runtime.SetServerHealth(params.Backend, params.Name, rs.OperationalState)
return server.NewReplaceRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
}
rs, err = runtime.GetServerState(params.Backend, params.Name)
if err != nil {
e := misc.HandleError(err)
return server.NewReplaceRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
return server.NewReplaceRuntimeServerOK().WithPayload(rs)
}
// Adds a new server dynamically without modifying the configuration.
// Warning: this only works if you have not defined a `default_server` in the defaults
// or in the current `backend` section.
func (h *AddRuntimeServerHandlerImpl) Handle(params server.AddRuntimeServerParams, principal interface{}) middleware.Responder {
runtime, err := h.Client.Runtime()
if err != nil {
e := misc.HandleError(err)
return server.NewAddRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
if params.Data.Name == "" {
code := int64(400)
msg := "the new server must have a name"
return server.NewAddRuntimeServerBadRequest().WithPayload(&models.Error{Code: &code, Message: &msg})
}
err = runtime.AddServer(params.Backend, params.Data.Name, SerializeRuntimeAddServer(params.Data))
if err != nil {
msg := err.Error()
switch {
case strings.Contains(msg, "No such backend"):
code := int64(404)
return server.NewAddRuntimeServerNotFound().WithPayload(&models.Error{Code: &code, Message: &msg})
case strings.Contains(msg, "Already exists"):
code := int64(409)
return server.NewAddRuntimeServerConflict().WithPayload(&models.Error{Code: &code, Message: &msg})
default:
e := misc.HandleError(err)
return server.NewAddRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
}
return server.NewAddRuntimeServerCreated().WithPayload(params.Data)
}
func isNotFoundError(err error) bool {
msg := err.Error()
return strings.Contains(msg, "No such backend") ||
strings.Contains(msg, native_errors.ErrNotFound.Error())
}
// Deletes a server from a backend immediately, without waiting for connections to drain.
func (h *DeleteRuntimeServerHandlerImpl) Handle(params server.DeleteRuntimeServerParams, principal interface{}) middleware.Responder {
runtime, err := h.Client.Runtime()
if err != nil {
e := misc.HandleError(err)
return server.NewDeleteRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
// Check if this server exists.
rs, err := runtime.GetServerState(params.Backend, params.Name)
if err != nil {
if isNotFoundError(err) {
code := int64(404)
msg := err.Error()
return server.NewDeleteRuntimeServerNotFound().WithPayload(&models.Error{Code: &code, Message: &msg})
}
e := misc.HandleError(err)
return server.NewDeleteRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
// Put the server in maintenance state before deleting it.
if rs.AdminState != "maint" {
err = runtime.DisableServer(params.Backend, params.Name)
if err != nil {
e := misc.HandleError(err)
return server.NewDeleteRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
}
// TODO: wait for connections to drain. This is not yet possible with HAProxy 2.6.
err = runtime.DeleteServer(params.Backend, params.Name)
if err != nil {
e := misc.HandleError(err)
return server.NewDeleteRuntimeServerDefault(int(*e.Code)).WithPayload(e)
}
return server.NewDeleteRuntimeServerNoContent()
}
// SerializeRuntimeAddServer returns a string in the HAProxy config format, suitable
// for the "add server" operation over the control socket.
// Not all the Server attributes are available in this case.
func SerializeRuntimeAddServer(srv *models.RuntimeAddServer) string { //nolint:cyclop,maintidx
b := &strings.Builder{}
push := func(s string) {
b.WriteByte(' ')
b.WriteString(s)
}
pushi := func(key string, val *int64) {
fmt.Fprintf(b, " %s %d", key, *val)
}
// push a quoted string
pushq := func(key, val string) {
fmt.Fprintf(b, ` %s "%s"`, key, val)
}
enabled := func(s string) bool {
return s == "enabled"
}
// Address is mandatory and must come first, with an optional port number.
addr := srv.Address
if srv.Port != nil {
addr += fmt.Sprintf(":%d", *srv.Port)
}
push(addr)
switch {
case enabled(srv.AgentCheck):
push("agent-check")
case srv.AgentAddr != "":
pushq("agent-addr", srv.AgentAddr)
case srv.AgentPort != nil:
pushi("agent-port", srv.AgentPort)
case srv.AgentInter != nil:
pushi("agent-inter", srv.AgentInter)
case srv.AgentSend != "":
pushq("agent-send", srv.AgentSend)
case srv.Allow0rtt:
push("allow-0rtt")
case srv.Alpn != "":
pushq("alpn", srv.Alpn)
case enabled(srv.Backup):
push("backup")
case srv.SslCafile != "":
pushq("ca-file", srv.SslCafile)
case enabled(srv.Check):
push("check")
case srv.CheckAlpn != "":
pushq("check-alpn", srv.CheckAlpn)
case srv.HealthCheckAddress != "":
pushq("addr", srv.HealthCheckAddress)
case srv.HealthCheckPort != nil:
pushi("port", srv.HealthCheckPort)
case srv.CheckProto != "":
pushq("check-proto", srv.CheckProto)
case enabled(srv.CheckSendProxy):
push("check-send-proxy")
case srv.CheckSni != "":
pushq("check-sni", srv.CheckSni)
case enabled(srv.CheckSsl):
push("check-ssl")
case enabled(srv.CheckViaSocks4):
push("check-via-socks4")
case srv.Ciphers != "":
pushq("ciphers", srv.Ciphers)
case srv.Ciphersuites != "":
pushq("ciphersuites", srv.Ciphersuites)
case srv.CrlFile != "":
pushq("crl-file", srv.CrlFile)
case srv.SslCertificate != "":
pushq("crt", srv.SslCertificate)
case enabled(srv.Maintenance):
push("disabled")
case srv.Downinter != nil:
pushi("downinter", srv.Downinter)
case !enabled(srv.Maintenance):
push("enabled")
case srv.ErrorLimit != nil:
pushi("error-limit", srv.ErrorLimit)
case srv.Fall != nil:
pushi("fall", srv.Fall)
case srv.Fastinter != nil:
pushi("fastinter", srv.Fastinter)
case enabled(srv.ForceSslv3):
push("force-sslv3")
case enabled(srv.ForceTlsv10):
push("force-tlsv10")
case enabled(srv.ForceTlsv11):
push("force-tlsv11")
case enabled(srv.ForceTlsv12):
push("force-tlsv12")
case enabled(srv.ForceTlsv13):
push("force-tlsv13")
case srv.ID != "":
pushq("id", srv.ID)
case srv.Inter != nil:
pushi("inter", srv.Inter)
case srv.Maxconn != nil:
pushi("maxconn", srv.Maxconn)
case srv.Maxqueue != nil:
pushi("maxqueue", srv.Maxqueue)
case srv.Minconn != nil:
pushi("minconn", srv.Minconn)
case !enabled(srv.SslReuse):
push("no-ssl-reuse")
case enabled(srv.NoSslv3):
push("no-sslv3")
case enabled(srv.NoTlsv10):
push("no-tlsv10")
case enabled(srv.NoTlsv11):
push("no-tlsv11")
case enabled(srv.NoTlsv12):
push("no-tlsv12")
case enabled(srv.NoTlsv13):
push("no-tlsv13")
case !enabled(srv.TLSTickets):
push("no-tls-tickets")
case srv.Npn != "":
pushq("npm", srv.Npn)
case srv.Observe != "":
pushq("observe", srv.Observe)
case srv.OnError != "":
pushq("on-error", srv.OnError)
case srv.OnMarkedDown != "":
pushq("on-marked-down", srv.OnMarkedDown)
case srv.OnMarkedUp != "":
pushq("on-marked-up", srv.OnMarkedUp)
case srv.PoolLowConn != nil:
pushi("pool-low-conn", srv.PoolLowConn)
case srv.PoolMaxConn != nil:
pushi("pool-max-conn", srv.PoolMaxConn)
case srv.PoolPurgeDelay != nil:
pushi("pool-purge-delay", srv.PoolPurgeDelay)
case srv.Proto != "":
pushq("proto", srv.Proto)
case len(srv.ProxyV2Options) > 0:
pushq("proxy-v2-options", strings.Join(srv.ProxyV2Options, ","))
case srv.Rise != nil:
pushi("rise", srv.Rise)
case enabled(srv.SendProxy):
push("send-proxy")
case enabled(srv.SendProxyV2):
push("send-proxy-v2")
case enabled(srv.SendProxyV2Ssl):
push("send-proxy-v2-ssl")
case enabled(srv.SendProxyV2SslCn):
push("send-proxy-v2-ssl-cn")
case srv.Slowstart != nil:
pushi("slowstart", srv.Slowstart)
case srv.Sni != "":
pushq("sni", srv.Sni)
case srv.Source != "":
pushq("source", srv.Source)
case enabled(srv.Ssl):
push("ssl")
case srv.SslMaxVer != "":
pushq("ssl-max-ver", srv.SslMaxVer)
case srv.SslMinVer != "":
pushq("ssl-min-ver", srv.SslMinVer)
case enabled(srv.Tfo):
push("tfo")
case enabled(srv.TLSTickets):
push("tls-tickets")
case srv.Track != "":
pushq("track", srv.Track)
/* XXX usesrc is not supported */
case srv.Verify != "":
pushq("verify", srv.Verify)
case srv.Verifyhost != "":
pushq("verifyhost", srv.Verifyhost)
case srv.Weight != nil:
pushi("weight", srv.Weight)
case srv.Ws != "":
pushq("ws", srv.Ws)
}
return b.String()
}