github.com/portworx/kvdb@v0.0.0-20241107215734-a185a966f535/etcd/common/common.go (about) 1 package common 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "os/exec" 13 "strconv" 14 "sync" 15 "time" 16 17 "github.com/portworx/kvdb" 18 "go.etcd.io/etcd/pkg/transport" 19 "go.etcd.io/etcd/version" 20 ) 21 22 const ( 23 // DefaultRetryCount for etcd operations 24 DefaultRetryCount = 60 25 // DefaultIntervalBetweenRetries for etcd failed operations 26 DefaultIntervalBetweenRetries = time.Millisecond * 500 27 // Bootstrap key 28 Bootstrap = "kvdb/bootstrap" 29 // DefaultDialTimeout in etcd http requests 30 // the maximum amount of time a dial will wait for a connection to setup. 31 // 30s is long enough for most of the network conditions. 32 DefaultDialTimeout = 30 * time.Second 33 // DefaultLockTTL is the ttl for an etcd lock 34 DefaultLockTTL = 16 35 // DefaultLockRefreshDuration is the time interval for refreshing an etcd lock 36 DefaultLockRefreshDuration = 2 * time.Second 37 ) 38 39 // EtcdCommon defined the common functions between v2 and v3 etcd implementations. 40 type EtcdCommon interface { 41 // GetAuthInfoFromOptions 42 GetAuthInfoFromOptions() (transport.TLSInfo, string, string, error) 43 44 // GetRetryCount 45 GetRetryCount() int 46 47 // IsTLSEnabled returns TRUE if SSL is enabled in options 48 IsTLSEnabled() bool 49 } 50 51 // EtcdLock combines Mutex and channel 52 type EtcdLock struct { 53 Done chan struct{} 54 Unlocked bool 55 Err error 56 Tag string 57 AcquisitionTime time.Time 58 sync.Mutex 59 } 60 61 // LockerIDInfo id of locker 62 type LockerIDInfo struct { 63 LockerID string 64 } 65 66 type etcdCommon struct { 67 options map[string]string 68 } 69 70 var ( 71 cmd *exec.Cmd 72 ) 73 74 // NewEtcdCommon returns the EtcdCommon interface 75 func NewEtcdCommon(options map[string]string) EtcdCommon { 76 return &etcdCommon{ 77 options: options, 78 } 79 } 80 81 func (ec *etcdCommon) GetRetryCount() int { 82 retryCount, ok := ec.options[kvdb.RetryCountKey] 83 if !ok { 84 return DefaultRetryCount 85 } 86 retry, err := strconv.ParseInt(retryCount, 10, 0) 87 if err != nil { 88 // use default value 89 return DefaultRetryCount 90 } 91 return int(retry) 92 } 93 94 func (ec *etcdCommon) IsTLSEnabled() bool { 95 // use `TransportScheme` hint if available 96 if ts, has := ec.options[kvdb.TransportScheme]; has { 97 return ts == "https" 98 } 99 return ec.options[kvdb.CertFileKey] != "" && ec.options[kvdb.CertKeyFileKey] != "" 100 } 101 102 func (ec *etcdCommon) GetAuthInfoFromOptions() (transport.TLSInfo, string, string, error) { 103 var ( 104 username string 105 password string 106 caFile string 107 certFile string 108 keyFile string 109 trustedCAFile string 110 clientCertAuth bool 111 err error 112 ) 113 // options provided. Probably auth options 114 if ec.options != nil || len(ec.options) > 0 { 115 // Check if username provided 116 username = ec.options[kvdb.UsernameKey] 117 // Check if password provided 118 password = ec.options[kvdb.PasswordKey] 119 // Check if CA file provided 120 caFile = ec.options[kvdb.CAFileKey] 121 // Check if certificate file provided 122 certFile = ec.options[kvdb.CertFileKey] 123 // Check if certificate key is provided 124 keyFile = ec.options[kvdb.CertKeyFileKey] 125 // Check if trusted ca file is provided 126 trustedCAFile = ec.options[kvdb.TrustedCAFileKey] 127 // Check if client cert auth is provided 128 clientCertAuthStr, ok := ec.options[kvdb.ClientCertAuthKey] 129 if !ok { 130 clientCertAuth = false 131 } else { 132 clientCertAuth, err = strconv.ParseBool(clientCertAuthStr) 133 if err != nil { 134 clientCertAuth = false 135 } 136 } 137 } 138 if trustedCAFile == "" && caFile != "" { 139 trustedCAFile = caFile 140 } 141 t := transport.TLSInfo{ 142 CertFile: certFile, 143 KeyFile: keyFile, 144 TrustedCAFile: trustedCAFile, 145 ClientCertAuth: clientCertAuth, 146 } 147 148 return t, username, password, nil 149 } 150 151 // Version returns the version of the provided etcd server 152 func Version(uri string, options map[string]string) (string, error) { 153 useTLS := false 154 tlsConfig := &tls.Config{} 155 // Check if CA file provided 156 caFile, ok := options[kvdb.CAFileKey] 157 if ok && caFile != "" { 158 useTLS = true 159 // Load CA cert 160 caCert, err := ioutil.ReadFile(caFile) 161 if err != nil { 162 return "", err 163 } 164 caCertPool := x509.NewCertPool() 165 caCertPool.AppendCertsFromPEM(caCert) 166 tlsConfig.RootCAs = caCertPool 167 } 168 // Check if certificate file provided 169 certFile, certOk := options[kvdb.CertFileKey] 170 // Check if certificate key is provided 171 keyFile, keyOk := options[kvdb.CertKeyFileKey] 172 if certOk && keyOk && certFile != "" && keyFile != "" { 173 useTLS = true 174 // Load client cert 175 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 176 if err != nil { 177 return "", err 178 } 179 tlsConfig.Certificates = []tls.Certificate{cert} 180 } 181 182 var client *http.Client 183 if useTLS { 184 tlsConfig.BuildNameToCertificate() 185 client = &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} 186 } else { 187 tempURL, _ := url.Parse(uri) 188 if tempURL.Scheme == "https" { 189 transport := &http.Transport{TLSClientConfig: &tls.Config{}} 190 client = &http.Client{Transport: transport} 191 } else { 192 client = &http.Client{} 193 } 194 } 195 196 // Do GET something 197 resp, err := client.Get(uri + "/version") 198 if err != nil { 199 return "", fmt.Errorf("error in obtaining etcd version: %v", err) 200 } 201 defer resp.Body.Close() 202 203 // Dump response 204 data, err := ioutil.ReadAll(resp.Body) 205 if err != nil { 206 return "", fmt.Errorf("error in obtaining etcd version: %v", err) 207 } 208 209 var ver version.Versions 210 err = json.Unmarshal(data, &ver) 211 if err != nil { 212 // Probably a version less than 2.3. Default to using v2 apis 213 return kvdb.EtcdBaseVersion, nil 214 } 215 if ver.Server == "" { 216 // This should never happen in an ideal scenario unless 217 // etcd messes up. To avoid a crash further in this code 218 // we return an error 219 return "", fmt.Errorf("unable to determine etcd version (empty response from etcd)") 220 } 221 if ver.Server[0] == '2' || ver.Server[0] == '1' { 222 return kvdb.EtcdBaseVersion, nil 223 } else if ver.Server[0] == '3' { 224 return kvdb.EtcdVersion3, nil 225 } else { 226 return "", fmt.Errorf("unsupported etcd version: %v", ver.Server) 227 } 228 } 229 230 // TestStart starts test 231 func TestStart(removeData bool) error { 232 dataDir := "/tmp/etcd" 233 if removeData { 234 os.RemoveAll(dataDir) 235 } 236 cmd = exec.Command("etcd", "--enable-v2=true", "--advertise-client-urls", "http://127.0.0.1:2379", "--data-dir", dataDir) 237 err := cmd.Start() 238 time.Sleep(5 * time.Second) 239 return err 240 } 241 242 // TestStop stops test 243 func TestStop() error { 244 err := cmd.Process.Kill() 245 time.Sleep(5 * time.Second) 246 return err 247 }