github.com/decred/dcrlnd@v0.7.6/kvdb/etcd/embed.go (about) 1 //go:build kvdb_etcd 2 // +build kvdb_etcd 3 4 package etcd 5 6 import ( 7 "fmt" 8 "net" 9 "net/url" 10 "sync/atomic" 11 "time" 12 13 "go.etcd.io/etcd/server/v3/embed" 14 ) 15 16 const ( 17 // readyTimeout is the time until the embedded etcd instance should start. 18 readyTimeout = 10 * time.Second 19 20 // defaultEtcdPort is the start of the range for listening ports of 21 // embedded etcd servers. Ports are monotonically increasing starting 22 // from this number and are determined by the results of getFreePort(). 23 defaultEtcdPort = 2379 24 25 // defaultNamespace is the namespace we'll use in our embedded etcd 26 // instance. Since it is only used for testing, we'll use the namespace 27 // name "test/" for this. Note that the namespace can be any string, 28 // the trailing / is not required. 29 defaultNamespace = "test/" 30 ) 31 32 var ( 33 // lastPort is the last port determined to be free for use by a new 34 // embedded etcd server. It should be used atomically. 35 lastPort uint32 = defaultEtcdPort 36 ) 37 38 // getFreePort returns the first port that is available for listening by a new 39 // embedded etcd server. It panics if no port is found and the maximum available 40 // TCP port is reached. 41 func getFreePort() int { 42 port := atomic.AddUint32(&lastPort, 1) 43 for port < 65535 { 44 // If there are no errors while attempting to listen on this 45 // port, close the socket and return it as available. 46 addr := fmt.Sprintf("127.0.0.1:%d", port) 47 l, err := net.Listen("tcp4", addr) 48 if err == nil { 49 err := l.Close() 50 if err == nil { 51 return int(port) 52 } 53 } 54 port = atomic.AddUint32(&lastPort, 1) 55 } 56 57 // No ports available? Must be a mistake. 58 panic("no ports available for listening") 59 } 60 61 // NewEmbeddedEtcdInstance creates an embedded etcd instance for testing, 62 // listening on random open ports. Returns the backend config and a cleanup 63 // func that will stop the etcd instance. 64 func NewEmbeddedEtcdInstance(path string, clientPort, peerPort uint16, 65 logFile string) (*Config, func(), error) { 66 67 cfg := embed.NewConfig() 68 cfg.Dir = path 69 70 // To ensure that we can submit large transactions. 71 cfg.MaxTxnOps = 8192 72 cfg.MaxRequestBytes = 16384 * 1024 73 cfg.Logger = "zap" 74 if logFile != "" { 75 cfg.LogLevel = "info" 76 cfg.LogOutputs = []string{logFile} 77 } else { 78 cfg.LogLevel = "error" 79 } 80 81 // Listen on random free ports if no ports were specified. 82 if clientPort == 0 { 83 clientPort = uint16(getFreePort()) 84 } 85 86 if peerPort == 0 { 87 peerPort = uint16(getFreePort()) 88 } 89 90 clientURL := fmt.Sprintf("127.0.0.1:%d", clientPort) 91 peerURL := fmt.Sprintf("127.0.0.1:%d", peerPort) 92 cfg.LCUrls = []url.URL{{Host: clientURL}} 93 cfg.LPUrls = []url.URL{{Host: peerURL}} 94 95 etcd, err := embed.StartEtcd(cfg) 96 if err != nil { 97 return nil, nil, err 98 } 99 100 select { 101 case <-etcd.Server.ReadyNotify(): 102 case <-time.After(readyTimeout): 103 etcd.Close() 104 return nil, nil, 105 fmt.Errorf("etcd failed to start after: %v", readyTimeout) 106 } 107 108 connConfig := &Config{ 109 Host: "http://" + clientURL, 110 InsecureSkipVerify: true, 111 Namespace: defaultNamespace, 112 } 113 114 return connConfig, func() { 115 etcd.Close() 116 }, nil 117 }