k3s/vendor/github.com/k3s-io/kine/pkg/endpoint/endpoint.go

266 lines
7.5 KiB
Go
Raw Normal View History

2019-08-22 05:12:46 +00:00
package endpoint
import (
"context"
"fmt"
"net"
"os"
"strings"
"github.com/k3s-io/kine/pkg/drivers/dqlite"
"github.com/k3s-io/kine/pkg/drivers/generic"
"github.com/k3s-io/kine/pkg/drivers/mysql"
"github.com/k3s-io/kine/pkg/drivers/pgsql"
"github.com/k3s-io/kine/pkg/drivers/sqlite"
"github.com/k3s-io/kine/pkg/server"
"github.com/k3s-io/kine/pkg/tls"
2019-12-12 01:27:03 +00:00
"github.com/pkg/errors"
2019-08-22 05:12:46 +00:00
"github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"go.etcd.io/etcd/server/v3/embed"
2019-08-22 05:12:46 +00:00
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
2019-08-22 05:12:46 +00:00
)
const (
KineSocket = "unix://kine.sock"
SQLiteBackend = "sqlite"
2019-11-08 21:45:10 +00:00
DQLiteBackend = "dqlite"
2019-08-22 05:12:46 +00:00
ETCDBackend = "etcd3"
MySQLBackend = "mysql"
PostgresBackend = "postgres"
)
type Config struct {
GRPCServer *grpc.Server
Listener string
Endpoint string
ConnectionPoolConfig generic.ConnectionPoolConfig
ServerTLSConfig tls.Config
BackendTLSConfig tls.Config
2019-08-22 05:12:46 +00:00
}
type ETCDConfig struct {
Endpoints []string
TLSConfig tls.Config
LeaderElect bool
}
func Listen(ctx context.Context, config Config) (ETCDConfig, error) {
driver, dsn := ParseStorageEndpoint(config.Endpoint)
if driver == ETCDBackend {
return ETCDConfig{
Endpoints: strings.Split(config.Endpoint, ","),
TLSConfig: config.BackendTLSConfig,
2019-08-22 05:12:46 +00:00
LeaderElect: true,
}, nil
}
2019-11-08 21:45:10 +00:00
leaderelect, backend, err := getKineStorageBackend(ctx, driver, dsn, config)
2019-08-22 05:12:46 +00:00
if err != nil {
2019-12-12 01:27:03 +00:00
return ETCDConfig{}, errors.Wrap(err, "building kine")
2019-08-22 05:12:46 +00:00
}
if err := backend.Start(ctx); err != nil {
2019-12-12 01:27:03 +00:00
return ETCDConfig{}, errors.Wrap(err, "starting kine backend")
2019-08-22 05:12:46 +00:00
}
// set up GRPC server and register services
b := server.New(backend, endpointScheme(config))
grpcServer, err := grpcServer(config)
if err != nil {
return ETCDConfig{}, errors.Wrap(err, "creating GRPC server")
2019-08-22 05:12:46 +00:00
}
b.Register(grpcServer)
// set up HTTP server with basic mux
httpServer := httpServer()
// Create raw listener and wrap in cmux for protocol switching
listener, err := createListener(config)
2019-08-22 05:12:46 +00:00
if err != nil {
return ETCDConfig{}, errors.Wrap(err, "creating listener")
}
m := cmux.New(listener)
if config.ServerTLSConfig.CertFile != "" && config.ServerTLSConfig.KeyFile != "" {
// If using TLS, wrap handler in GRPC/HTTP switching handler and serve TLS
httpServer.Handler = grpcHandlerFunc(grpcServer, httpServer.Handler)
anyl := m.Match(cmux.Any())
go func() {
if err := httpServer.ServeTLS(anyl, config.ServerTLSConfig.CertFile, config.ServerTLSConfig.KeyFile); err != nil {
logrus.Errorf("Kine TLS server shutdown: %v", err)
}
}()
} else {
// If using plaintext, use cmux matching for GRPC/HTTP switching
grpcl := m.Match(cmux.HTTP2())
go func() {
if err := grpcServer.Serve(grpcl); err != nil {
logrus.Errorf("Kine GRPC server shutdown: %v", err)
}
}()
httpl := m.Match(cmux.HTTP1())
go func() {
if err := httpServer.Serve(httpl); err != nil {
logrus.Errorf("Kine HTTP server shutdown: %v", err)
}
}()
2019-08-22 05:12:46 +00:00
}
go func() {
if err := m.Serve(); err != nil {
logrus.Errorf("Kine listener shutdown: %v", err)
grpcServer.Stop()
2019-08-22 05:12:46 +00:00
}
}()
endpoint := endpointURL(config, listener)
logrus.Infof("Kine available at %s", endpoint)
2019-08-22 05:12:46 +00:00
return ETCDConfig{
LeaderElect: leaderelect,
Endpoints: []string{endpoint},
2019-08-22 05:12:46 +00:00
TLSConfig: tls.Config{},
}, nil
}
// endpointURL returns a URI string suitable for use as a local etcd endpoint.
// For TCP sockets, it is assumed that the port can be reached via the loopback address.
func endpointURL(config Config, listener net.Listener) string {
scheme := endpointScheme(config)
address := listener.Addr().String()
if !strings.HasPrefix(scheme, "unix") {
_, port, err := net.SplitHostPort(address)
if err != nil {
logrus.Warnf("failed to get listener port: %v", err)
port = "2379"
}
address = "127.0.0.1:" + port
}
return scheme + "://" + address
}
// endpointScheme returns the URI scheme for the listener specified by the configuration.
func endpointScheme(config Config) string {
if config.Listener == "" {
config.Listener = KineSocket
}
network, _ := networkAndAddress(config.Listener)
if network != "unix" {
network = "http"
}
if config.ServerTLSConfig.CertFile != "" && config.ServerTLSConfig.KeyFile != "" {
// yes, etcd supports the "unixs" scheme for TLS over unix sockets
network += "s"
}
return network
}
// createListener returns a listener bound to the requested protocol and address.
func createListener(config Config) (ret net.Listener, rerr error) {
if config.Listener == "" {
config.Listener = KineSocket
}
network, address := networkAndAddress(config.Listener)
2019-08-22 05:12:46 +00:00
if network == "unix" {
if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
logrus.Warnf("failed to remove socket %s: %v", address, err)
}
defer func() {
if err := os.Chmod(address, 0600); err != nil {
rerr = err
}
}()
} else {
network = "tcp"
2019-08-22 05:12:46 +00:00
}
return net.Listen(network, address)
}
// grpcServer returns either a preconfigured GRPC server, or builds a new GRPC
// server using upstream keepalive defaults plus the local Server TLS configuration.
func grpcServer(config Config) (*grpc.Server, error) {
2019-08-22 05:12:46 +00:00
if config.GRPCServer != nil {
return config.GRPCServer, nil
2019-08-22 05:12:46 +00:00
}
gopts := []grpc.ServerOption{
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: embed.DefaultGRPCKeepAliveMinTime,
PermitWithoutStream: false,
}),
grpc.KeepaliveParams(keepalive.ServerParameters{
Time: embed.DefaultGRPCKeepAliveInterval,
Timeout: embed.DefaultGRPCKeepAliveTimeout,
}),
}
if config.ServerTLSConfig.CertFile != "" && config.ServerTLSConfig.KeyFile != "" {
creds, err := credentials.NewServerTLSFromFile(config.ServerTLSConfig.CertFile, config.ServerTLSConfig.KeyFile)
if err != nil {
return nil, err
}
gopts = append(gopts, grpc.Creds(creds))
}
return grpc.NewServer(gopts...), nil
2019-08-22 05:12:46 +00:00
}
// getKineStorageBackend parses the driver string, and returns a bool
// indicating whether the backend requires leader election, and a suitable
// backend datastore connection.
2019-11-08 21:45:10 +00:00
func getKineStorageBackend(ctx context.Context, driver, dsn string, cfg Config) (bool, server.Backend, error) {
2019-08-22 05:12:46 +00:00
var (
backend server.Backend
leaderElect = true
err error
)
switch driver {
case SQLiteBackend:
leaderElect = false
backend, err = sqlite.New(ctx, dsn, cfg.ConnectionPoolConfig)
2019-11-08 21:45:10 +00:00
case DQLiteBackend:
backend, err = dqlite.New(ctx, dsn, cfg.ConnectionPoolConfig)
2019-08-22 05:12:46 +00:00
case PostgresBackend:
backend, err = pgsql.New(ctx, dsn, cfg.BackendTLSConfig, cfg.ConnectionPoolConfig)
2019-08-22 05:12:46 +00:00
case MySQLBackend:
backend, err = mysql.New(ctx, dsn, cfg.BackendTLSConfig, cfg.ConnectionPoolConfig)
2019-08-22 05:12:46 +00:00
default:
return false, nil, fmt.Errorf("storage backend is not defined")
}
return leaderElect, backend, err
}
// ParseStorageEndpoint returns the driver name and endpoint string from a datastore endpoint URL.
2019-08-22 05:12:46 +00:00
func ParseStorageEndpoint(storageEndpoint string) (string, string) {
network, address := networkAndAddress(storageEndpoint)
switch network {
case "":
return SQLiteBackend, ""
case "http":
fallthrough
case "https":
return ETCDBackend, address
}
return network, address
}
// networkAndAddress crudely splits a URL string into network (scheme) and address,
// where the address includes everything after the scheme/authority separator.
2019-08-22 05:12:46 +00:00
func networkAndAddress(str string) (string, string) {
parts := strings.SplitN(str, "://", 2)
if len(parts) > 1 {
return parts[0], parts[1]
}
return "", parts[0]
}