2019-08-22 05:12:46 +00:00
|
|
|
package endpoint
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
2020-11-30 23:45:22 +00:00
|
|
|
"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"
|
2021-08-30 20:43:25 +00:00
|
|
|
"github.com/soheilhy/cmux"
|
2021-07-02 22:06:02 +00:00
|
|
|
"go.etcd.io/etcd/server/v3/embed"
|
2019-08-22 05:12:46 +00:00
|
|
|
"google.golang.org/grpc"
|
2021-08-30 20:43:25 +00:00
|
|
|
"google.golang.org/grpc/credentials"
|
2021-07-02 22:06:02 +00:00
|
|
|
"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 {
|
2020-10-15 17:34:24 +00:00
|
|
|
GRPCServer *grpc.Server
|
|
|
|
Listener string
|
|
|
|
Endpoint string
|
|
|
|
ConnectionPoolConfig generic.ConnectionPoolConfig
|
2021-08-30 20:43:25 +00:00
|
|
|
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, ","),
|
2021-08-30 20:43:25 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +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)
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
// 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 {
|
2021-08-30 20:43:25 +00:00
|
|
|
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() {
|
2021-08-30 20:43:25 +00:00
|
|
|
if err := m.Serve(); err != nil {
|
|
|
|
logrus.Errorf("Kine listener shutdown: %v", err)
|
|
|
|
grpcServer.Stop()
|
2019-08-22 05:12:46 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-08-30 20:43:25 +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,
|
2021-08-30 20:43:25 +00:00
|
|
|
Endpoints: []string{endpoint},
|
2019-08-22 05:12:46 +00:00
|
|
|
TLSConfig: tls.Config{},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}()
|
2021-08-30 20:43:25 +00:00
|
|
|
} else {
|
|
|
|
network = "tcp"
|
2019-08-22 05:12:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return net.Listen(network, address)
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
// 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 {
|
2021-08-30 20:43:25 +00:00
|
|
|
return config.GRPCServer, nil
|
2019-08-22 05:12:46 +00:00
|
|
|
}
|
2021-08-30 20:43:25 +00:00
|
|
|
|
2021-07-02 22:06:02 +00:00
|
|
|
gopts := []grpc.ServerOption{
|
|
|
|
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
|
|
|
MinTime: embed.DefaultGRPCKeepAliveMinTime,
|
|
|
|
PermitWithoutStream: false,
|
|
|
|
}),
|
|
|
|
grpc.KeepaliveParams(keepalive.ServerParameters{
|
|
|
|
Time: embed.DefaultGRPCKeepAliveInterval,
|
|
|
|
Timeout: embed.DefaultGRPCKeepAliveTimeout,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +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
|
2020-10-15 17:34:24 +00:00
|
|
|
backend, err = sqlite.New(ctx, dsn, cfg.ConnectionPoolConfig)
|
2019-11-08 21:45:10 +00:00
|
|
|
case DQLiteBackend:
|
2020-10-15 17:34:24 +00:00
|
|
|
backend, err = dqlite.New(ctx, dsn, cfg.ConnectionPoolConfig)
|
2019-08-22 05:12:46 +00:00
|
|
|
case PostgresBackend:
|
2021-08-30 20:43:25 +00:00
|
|
|
backend, err = pgsql.New(ctx, dsn, cfg.BackendTLSConfig, cfg.ConnectionPoolConfig)
|
2019-08-22 05:12:46 +00:00
|
|
|
case MySQLBackend:
|
2021-08-30 20:43:25 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:43:25 +00:00
|
|
|
// 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]
|
|
|
|
}
|