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  }