lnd/kvdb/etcd/embed.go
2022-02-28 12:03:05 +01:00

119 lines
3.0 KiB
Go

//go:build kvdb_etcd
// +build kvdb_etcd
package etcd
import (
"fmt"
"net"
"net/url"
"sync/atomic"
"time"
"go.etcd.io/etcd/server/v3/embed"
)
const (
// readyTimeout is the time until the embedded etcd instance should start.
readyTimeout = 10 * time.Second
// defaultEtcdPort is the start of the range for listening ports of
// embedded etcd servers. Ports are monotonically increasing starting
// from this number and are determined by the results of getFreePort().
defaultEtcdPort = 2379
// defaultNamespace is the namespace we'll use in our embedded etcd
// instance. Since it is only used for testing, we'll use the namespace
// name "test/" for this. Note that the namespace can be any string,
// the trailing / is not required.
defaultNamespace = "test/"
)
var (
// lastPort is the last port determined to be free for use by a new
// embedded etcd server. It should be used atomically.
lastPort uint32 = defaultEtcdPort
)
// getFreePort returns the first port that is available for listening by a new
// embedded etcd server. It panics if no port is found and the maximum available
// TCP port is reached.
func getFreePort() int {
port := atomic.AddUint32(&lastPort, 1)
for port < 65535 {
// If there are no errors while attempting to listen on this
// port, close the socket and return it as available.
addr := fmt.Sprintf("127.0.0.1:%d", port)
l, err := net.Listen("tcp4", addr)
if err == nil {
err := l.Close()
if err == nil {
return int(port)
}
}
port = atomic.AddUint32(&lastPort, 1)
}
// No ports available? Must be a mistake.
panic("no ports available for listening")
}
// NewEmbeddedEtcdInstance creates an embedded etcd instance for testing,
// listening on random open ports. Returns the backend config and a cleanup
// func that will stop the etcd instance.
func NewEmbeddedEtcdInstance(path string, clientPort, peerPort uint16,
logFile string) (*Config, func(), error) {
cfg := embed.NewConfig()
cfg.Dir = path
// To ensure that we can submit large transactions.
cfg.MaxTxnOps = 16384
cfg.MaxRequestBytes = 16384 * 1024
cfg.Logger = "zap"
if logFile != "" {
cfg.LogLevel = "info"
cfg.LogOutputs = []string{logFile}
} else {
cfg.LogLevel = "error"
}
// Listen on random free ports if no ports were specified.
if clientPort == 0 {
clientPort = uint16(getFreePort())
}
if peerPort == 0 {
peerPort = uint16(getFreePort())
}
clientURL := fmt.Sprintf("127.0.0.1:%d", clientPort)
peerURL := fmt.Sprintf("127.0.0.1:%d", peerPort)
cfg.LCUrls = []url.URL{{Host: clientURL}}
cfg.LPUrls = []url.URL{{Host: peerURL}}
etcd, err := embed.StartEtcd(cfg)
if err != nil {
return nil, nil, err
}
select {
case <-etcd.Server.ReadyNotify():
case <-time.After(readyTimeout):
etcd.Close()
return nil, nil,
fmt.Errorf("etcd failed to start after: %v", readyTimeout)
}
connConfig := &Config{
Host: "http://" + clientURL,
InsecureSkipVerify: true,
Namespace: defaultNamespace,
MaxMsgSize: int(cfg.MaxRequestBytes),
}
return connConfig, func() {
etcd.Close()
}, nil
}