github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kvstore/consul.go (about)

     1  // Copyright 2016-2020 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kvstore
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/base64"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"time"
    25  
    26  	"github.com/cilium/cilium/pkg/backoff"
    27  	"github.com/cilium/cilium/pkg/controller"
    28  	"github.com/cilium/cilium/pkg/lock"
    29  	"github.com/cilium/cilium/pkg/logging/logfields"
    30  	"github.com/cilium/cilium/pkg/option"
    31  	"github.com/cilium/cilium/pkg/spanstat"
    32  
    33  	consulAPI "github.com/hashicorp/consul/api"
    34  	"github.com/sirupsen/logrus"
    35  	"gopkg.in/yaml.v2"
    36  )
    37  
    38  const (
    39  	consulName = "consul"
    40  
    41  	// ConsulAddrOption is the string representing the key mapping to the value of the
    42  	// address for Consul.
    43  	ConsulAddrOption   = "consul.address"
    44  	ConsulOptionConfig = "consul.tlsconfig"
    45  
    46  	// maxLockRetries is the number of retries attempted when acquiring a lock
    47  	maxLockRetries = 10
    48  )
    49  
    50  type consulModule struct {
    51  	opts   backendOptions
    52  	config *consulAPI.Config
    53  }
    54  
    55  var (
    56  	//consulDummyAddress can be overwritten from test invokers using ldflags
    57  	consulDummyAddress = "https://127.0.0.1:8501"
    58  	//consulDummyConfigFile can be overwritten from test invokers using ldflags
    59  	consulDummyConfigFile = "/tmp/cilium-consul-certs/cilium-consul.yaml"
    60  
    61  	module = newConsulModule()
    62  )
    63  
    64  func init() {
    65  	// register consul module for use
    66  	registerBackend(consulName, module)
    67  }
    68  
    69  func newConsulModule() backendModule {
    70  	return &consulModule{
    71  		opts: backendOptions{
    72  			ConsulAddrOption: &backendOption{
    73  				description: "Addresses of consul cluster",
    74  			},
    75  			ConsulOptionConfig: &backendOption{
    76  				description: "Path to consul tls configuration file",
    77  			},
    78  		},
    79  	}
    80  }
    81  
    82  func ConsulDummyAddress() string {
    83  	return consulDummyAddress
    84  }
    85  
    86  func ConsulDummyConfigFile() string {
    87  	return consulDummyConfigFile
    88  }
    89  
    90  func (c *consulModule) createInstance() backendModule {
    91  	return newConsulModule()
    92  }
    93  
    94  func (c *consulModule) getName() string {
    95  	return consulName
    96  }
    97  
    98  func (c *consulModule) setConfigDummy() {
    99  	c.config = consulAPI.DefaultConfig()
   100  	c.config.Address = consulDummyAddress
   101  	yc := consulAPI.TLSConfig{}
   102  	b, err := ioutil.ReadFile(consulDummyConfigFile)
   103  	if err != nil {
   104  		log.WithError(err).Warnf("unable to read consul tls configuration file %s", consulDummyConfigFile)
   105  	}
   106  
   107  	err = yaml.Unmarshal(b, &yc)
   108  	if err != nil {
   109  		log.WithError(err).Warnf("invalid consul tls configuration in %s", consulDummyConfigFile)
   110  	}
   111  
   112  	c.config.TLSConfig = yc
   113  }
   114  
   115  func (c *consulModule) setConfig(opts map[string]string) error {
   116  	return setOpts(opts, c.opts)
   117  }
   118  
   119  func (c *consulModule) setExtraConfig(opts *ExtraOptions) error {
   120  	return nil
   121  }
   122  
   123  func (c *consulModule) getConfig() map[string]string {
   124  	return getOpts(c.opts)
   125  }
   126  
   127  func (c *consulModule) newClient(opts *ExtraOptions) (BackendOperations, chan error) {
   128  	errChan := make(chan error, 1)
   129  	backend, err := c.connectConsulClient(opts)
   130  	if err != nil {
   131  		errChan <- err
   132  	}
   133  	close(errChan)
   134  	return backend, errChan
   135  }
   136  
   137  func (c *consulModule) connectConsulClient(opts *ExtraOptions) (BackendOperations, error) {
   138  	if c.config == nil {
   139  		consulAddr, consulAddrSet := c.opts[ConsulAddrOption]
   140  		configPathOpt, configPathOptSet := c.opts[ConsulOptionConfig]
   141  		if !consulAddrSet {
   142  			return nil, fmt.Errorf("invalid consul configuration, please specify %s option", ConsulAddrOption)
   143  		}
   144  
   145  		if consulAddr.value == "" {
   146  			return nil, fmt.Errorf("invalid consul configuration, please specify %s option", ConsulAddrOption)
   147  		}
   148  
   149  		addr := consulAddr.value
   150  		c.config = consulAPI.DefaultConfig()
   151  		if configPathOptSet && configPathOpt.value != "" {
   152  			b, err := ioutil.ReadFile(configPathOpt.value)
   153  			if err != nil {
   154  				return nil, fmt.Errorf("unable to read consul tls configuration file %s: %s", configPathOpt.value, err)
   155  			}
   156  			yc := consulAPI.TLSConfig{}
   157  			err = yaml.Unmarshal(b, &yc)
   158  			if err != nil {
   159  				return nil, fmt.Errorf("invalid consul tls configuration in %s: %s", configPathOpt.value, err)
   160  			}
   161  			c.config.TLSConfig = yc
   162  		}
   163  
   164  		c.config.Address = addr
   165  
   166  	}
   167  	client, err := newConsulClient(c.config, opts)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	return client, nil
   173  }
   174  
   175  var (
   176  	maxRetries = 30
   177  )
   178  
   179  type consulClient struct {
   180  	*consulAPI.Client
   181  	lease          string
   182  	controllers    *controller.Manager
   183  	extraOptions   *ExtraOptions
   184  	disconnectedMu lock.RWMutex
   185  	disconnected   chan struct{}
   186  }
   187  
   188  func newConsulClient(config *consulAPI.Config, opts *ExtraOptions) (BackendOperations, error) {
   189  	var (
   190  		c   *consulAPI.Client
   191  		err error
   192  	)
   193  	if config != nil {
   194  		c, err = consulAPI.NewClient(config)
   195  	} else {
   196  		c, err = consulAPI.NewClient(consulAPI.DefaultConfig())
   197  	}
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	boff := backoff.Exponential{Min: time.Duration(100) * time.Millisecond}
   203  	log.Info("Waiting for consul to elect a leader")
   204  
   205  	for i := 0; i < maxRetries; i++ {
   206  		var leader string
   207  		leader, err = c.Status().Leader()
   208  
   209  		if err == nil {
   210  			if leader != "" {
   211  				// happy path
   212  				break
   213  			} else {
   214  				err = errors.New("timeout while waiting for leader to be elected")
   215  			}
   216  		}
   217  		boff.Wait(context.TODO())
   218  	}
   219  
   220  	if err != nil {
   221  		log.WithError(err).Fatal("Unable to contact consul server")
   222  	}
   223  
   224  	entry := &consulAPI.SessionEntry{
   225  		TTL:      fmt.Sprintf("%ds", int(option.Config.KVstoreLeaseTTL.Seconds())),
   226  		Behavior: consulAPI.SessionBehaviorDelete,
   227  	}
   228  
   229  	lease, _, err := c.Session().Create(entry, nil)
   230  	if err != nil {
   231  		return nil, fmt.Errorf("unable to create default lease: %s", err)
   232  	}
   233  
   234  	client := &consulClient{
   235  		Client:       c,
   236  		lease:        lease,
   237  		controllers:  controller.NewManager(),
   238  		extraOptions: opts,
   239  		disconnected: make(chan struct{}),
   240  	}
   241  
   242  	client.controllers.UpdateController(fmt.Sprintf("consul-lease-keepalive-%p", c),
   243  		controller.ControllerParams{
   244  			DoFunc: func(ctx context.Context) error {
   245  				_, _, err := c.Session().Renew(lease, nil)
   246  				if err != nil {
   247  					// consider disconnected!
   248  					client.disconnectedMu.Lock()
   249  					close(client.disconnected)
   250  					client.disconnected = make(chan struct{})
   251  					client.disconnectedMu.Unlock()
   252  				}
   253  				return err
   254  			},
   255  			RunInterval: option.Config.KVstoreKeepAliveInterval,
   256  		},
   257  	)
   258  
   259  	return client, nil
   260  }
   261  
   262  type ConsulLocker struct {
   263  	*consulAPI.Lock
   264  }
   265  
   266  func (cl *ConsulLocker) Comparator() interface{} {
   267  	return nil
   268  }
   269  
   270  func (c *consulClient) LockPath(ctx context.Context, path string) (KVLocker, error) {
   271  	lockKey, err := c.LockOpts(&consulAPI.LockOptions{Key: getLockPath(path)})
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	for retries := 0; retries < maxLockRetries; retries++ {
   277  		ch, err := lockKey.Lock(nil)
   278  		switch {
   279  		case err != nil:
   280  			return nil, err
   281  		case ch == nil && err == nil:
   282  			Trace("Acquiring lock timed out, retrying", nil, logrus.Fields{fieldKey: path, logfields.Attempt: retries})
   283  		default:
   284  			return &ConsulLocker{Lock: lockKey}, err
   285  		}
   286  
   287  		select {
   288  		case <-ctx.Done():
   289  			return nil, fmt.Errorf("lock cancelled via context: %s", ctx.Err())
   290  		default:
   291  		}
   292  	}
   293  
   294  	return nil, fmt.Errorf("maximum retries (%d) reached", maxLockRetries)
   295  }
   296  
   297  // Watch starts watching for changes in a prefix
   298  func (c *consulClient) Watch(w *Watcher) {
   299  	// Last known state of all KVPairs matching the prefix
   300  	localState := map[string]consulAPI.KVPair{}
   301  	nextIndex := uint64(0)
   302  
   303  	qo := consulAPI.QueryOptions{
   304  		WaitTime: time.Second,
   305  	}
   306  
   307  	for {
   308  		// Initialize sleep time to a millisecond as we don't
   309  		// want to sleep in between successful watch cycles
   310  		sleepTime := 1 * time.Millisecond
   311  
   312  		qo.WaitIndex = nextIndex
   313  		pairs, q, err := c.KV().List(w.prefix, &qo)
   314  		if err != nil {
   315  			sleepTime = 5 * time.Second
   316  			Trace("List of Watch failed", err, logrus.Fields{fieldPrefix: w.prefix, fieldWatcher: w.name})
   317  		}
   318  
   319  		if q != nil {
   320  			nextIndex = q.LastIndex
   321  		}
   322  
   323  		// timeout while watching for changes, re-schedule
   324  		if qo.WaitIndex != 0 && (q == nil || q.LastIndex == qo.WaitIndex) {
   325  			goto wait
   326  		}
   327  
   328  		for _, newPair := range pairs {
   329  			oldPair, ok := localState[newPair.Key]
   330  
   331  			// Keys reported for the first time must be new
   332  			if !ok {
   333  				if newPair.CreateIndex != newPair.ModifyIndex {
   334  					log.Debugf("consul: Previously unknown key %s received with CreateIndex(%d) != ModifyIndex(%d)",
   335  						newPair.Key, newPair.CreateIndex, newPair.ModifyIndex)
   336  				}
   337  
   338  				queueStart := spanstat.Start()
   339  				w.Events <- KeyValueEvent{
   340  					Typ:   EventTypeCreate,
   341  					Key:   newPair.Key,
   342  					Value: newPair.Value,
   343  				}
   344  				trackEventQueued(newPair.Key, EventTypeCreate, queueStart.End(true).Total())
   345  			} else if oldPair.ModifyIndex != newPair.ModifyIndex {
   346  				queueStart := spanstat.Start()
   347  				w.Events <- KeyValueEvent{
   348  					Typ:   EventTypeModify,
   349  					Key:   newPair.Key,
   350  					Value: newPair.Value,
   351  				}
   352  				trackEventQueued(newPair.Key, EventTypeModify, queueStart.End(true).Total())
   353  			}
   354  
   355  			// Everything left on localState will be assumed to
   356  			// have been deleted, therefore remove all keys in
   357  			// localState that still exist in the kvstore
   358  			delete(localState, newPair.Key)
   359  		}
   360  
   361  		for k, deletedPair := range localState {
   362  			queueStart := spanstat.Start()
   363  			w.Events <- KeyValueEvent{
   364  				Typ:   EventTypeDelete,
   365  				Key:   deletedPair.Key,
   366  				Value: deletedPair.Value,
   367  			}
   368  			trackEventQueued(deletedPair.Key, EventTypeDelete, queueStart.End(true).Total())
   369  			delete(localState, k)
   370  		}
   371  
   372  		for _, newPair := range pairs {
   373  			localState[newPair.Key] = *newPair
   374  
   375  		}
   376  
   377  		// Initial list operation has been completed, signal this
   378  		if qo.WaitIndex == 0 {
   379  			w.Events <- KeyValueEvent{Typ: EventTypeListDone}
   380  		}
   381  
   382  	wait:
   383  		select {
   384  		case <-time.After(sleepTime):
   385  		case <-w.stopWatch:
   386  			close(w.Events)
   387  			w.stopWait.Done()
   388  			return
   389  		}
   390  	}
   391  }
   392  
   393  func (c *consulClient) waitForInitLock() <-chan struct{} {
   394  	initLockSucceeded := make(chan struct{})
   395  
   396  	go func() {
   397  		for {
   398  			locker, err := c.LockPath(context.TODO(), InitLockPath)
   399  			if err == nil {
   400  				close(initLockSucceeded)
   401  				locker.Unlock()
   402  				log.Info("Distributed lock successful, consul has quorum")
   403  				return
   404  			}
   405  
   406  			time.Sleep(100 * time.Millisecond)
   407  		}
   408  	}()
   409  
   410  	return initLockSucceeded
   411  }
   412  
   413  // Connected closes the returned channel when the etcd client is connected.
   414  func (c *consulClient) Connected() <-chan struct{} {
   415  	ch := make(chan struct{})
   416  	go func() {
   417  		for {
   418  			// TODO find out if there's a better way to do this for consul
   419  			_, _, err := c.Session().Info(c.lease, &consulAPI.QueryOptions{})
   420  			if err == nil {
   421  				break
   422  			}
   423  			time.Sleep(100 * time.Millisecond)
   424  		}
   425  		<-c.waitForInitLock()
   426  		close(ch)
   427  	}()
   428  	return ch
   429  }
   430  
   431  // Disconnected closes the returned channel when consul detects the client
   432  // is disconnected from the server.
   433  func (c *consulClient) Disconnected() <-chan struct{} {
   434  	c.disconnectedMu.RLock()
   435  	ch := c.disconnected
   436  	c.disconnectedMu.RUnlock()
   437  	return ch
   438  }
   439  
   440  func (c *consulClient) Status() (string, error) {
   441  	leader, err := c.Client.Status().Leader()
   442  	return "Consul: " + leader, err
   443  }
   444  
   445  func (c *consulClient) DeletePrefix(path string) (err error) {
   446  	defer func() { Trace("DeletePrefix", err, logrus.Fields{fieldPrefix: path}) }()
   447  	duration := spanstat.Start()
   448  	_, err = c.Client.KV().DeleteTree(path, nil)
   449  	increaseMetric(path, metricDelete, "DeletePrefix", duration.EndError(err).Total(), err)
   450  	return err
   451  }
   452  
   453  // Set sets value of key
   454  func (c *consulClient) Set(key string, value []byte) (err error) {
   455  	defer func() { Trace("Set", err, logrus.Fields{fieldKey: key, fieldValue: string(value)}) }()
   456  	duration := spanstat.Start()
   457  	_, err = c.KV().Put(&consulAPI.KVPair{Key: key, Value: value}, nil)
   458  	increaseMetric(key, metricSet, "Set", duration.EndError(err).Total(), err)
   459  	return err
   460  }
   461  
   462  // DeleteIfLocked deletes a key if the client is still holding the given lock.
   463  func (c *consulClient) DeleteIfLocked(key string, lock KVLocker) (err error) {
   464  	defer func() { Trace("DeleteIfLocked", err, logrus.Fields{fieldKey: key}) }()
   465  	err = c.Delete(key)
   466  	return err
   467  }
   468  
   469  // Delete deletes a key
   470  func (c *consulClient) Delete(key string) (err error) {
   471  	defer func() { Trace("Delete", err, logrus.Fields{fieldKey: key}) }()
   472  	duration := spanstat.Start()
   473  	_, err = c.KV().Delete(key, nil)
   474  	increaseMetric(key, metricDelete, "Delete", duration.EndError(err).Total(), err)
   475  	return err
   476  }
   477  
   478  // GetIfLocked returns value of key if the client is still holding the given lock.
   479  func (c *consulClient) GetIfLocked(key string, lock KVLocker) (bv []byte, err error) {
   480  	defer func() { Trace("GetIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: string(bv)}) }()
   481  	bv, err = c.Get(key)
   482  	return bv, err
   483  }
   484  
   485  // Get returns value of key
   486  func (c *consulClient) Get(key string) (bv []byte, err error) {
   487  	defer func() { Trace("Get", err, logrus.Fields{fieldKey: key, fieldValue: string(bv)}) }()
   488  	duration := spanstat.Start()
   489  	var pair *consulAPI.KVPair
   490  	pair, _, err = c.KV().Get(key, nil)
   491  	increaseMetric(key, metricRead, "Get", duration.EndError(err).Total(), err)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  	if pair == nil {
   496  		return nil, nil
   497  	}
   498  	return pair.Value, nil
   499  }
   500  
   501  // GetPrefixIfLocked returns the first key which matches the prefix and its value if the client is still holding the given lock.
   502  func (c *consulClient) GetPrefixIfLocked(ctx context.Context, prefix string, lock KVLocker) (k string, bv []byte, err error) {
   503  	defer func() {
   504  		Trace("GetPrefixIfLocked", err, logrus.Fields{fieldPrefix: prefix, fieldKey: k, fieldValue: string(bv)})
   505  	}()
   506  	k, bv, err = c.getPrefix(ctx, prefix)
   507  	return k, bv, err
   508  }
   509  
   510  // GetPrefix returns the first key which matches the prefix and its value
   511  func (c *consulClient) GetPrefix(ctx context.Context, prefix string) (k string, bv []byte, err error) {
   512  	defer func() {
   513  		Trace("GetPrefix", err, logrus.Fields{fieldPrefix: prefix, fieldKey: k, fieldValue: string(bv)})
   514  	}()
   515  	k, bv, err = c.getPrefix(ctx, prefix)
   516  	return k, bv, err
   517  }
   518  
   519  func (c *consulClient) getPrefix(ctx context.Context, prefix string) (k string, bv []byte, err error) {
   520  	duration := spanstat.Start()
   521  	opts := &consulAPI.QueryOptions{}
   522  	pairs, _, err := c.KV().List(prefix, opts.WithContext(ctx))
   523  	increaseMetric(prefix, metricRead, "GetPrefix", duration.EndError(err).Total(), err)
   524  	if err != nil {
   525  		return "", nil, err
   526  	}
   527  
   528  	if len(pairs) == 0 {
   529  		return "", nil, nil
   530  	}
   531  
   532  	return pairs[0].Key, pairs[0].Value, nil
   533  }
   534  
   535  // UpdateIfLocked atomically creates a key or fails if it already exists if the client is still holding the given lock.
   536  func (c *consulClient) UpdateIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) error {
   537  	return c.Update(ctx, key, value, lease)
   538  }
   539  
   540  // Update creates or updates a key with the value
   541  func (c *consulClient) Update(ctx context.Context, key string, value []byte, lease bool) (err error) {
   542  	defer func() {
   543  		Trace("Update", err, logrus.Fields{fieldKey: key, fieldValue: string(value), fieldAttachLease: lease})
   544  	}()
   545  
   546  	k := &consulAPI.KVPair{Key: key, Value: value}
   547  
   548  	if lease {
   549  		k.Session = c.lease
   550  	}
   551  
   552  	opts := &consulAPI.WriteOptions{}
   553  
   554  	duration := spanstat.Start()
   555  	_, err = c.KV().Put(k, opts.WithContext(ctx))
   556  	increaseMetric(key, metricSet, "Update", duration.EndError(err).Total(), err)
   557  	return err
   558  }
   559  
   560  // UpdateIfDifferentIfLocked updates a key if the value is different and if the client is still holding the given lock.
   561  func (c *consulClient) UpdateIfDifferentIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) (recreated bool, err error) {
   562  	defer func() {
   563  		Trace("UpdateIfDifferentIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "recreated": recreated})
   564  	}()
   565  
   566  	return c.updateIfDifferent(ctx, key, value, lease)
   567  }
   568  
   569  // UpdateIfDifferent updates a key if the value is different
   570  func (c *consulClient) UpdateIfDifferent(ctx context.Context, key string, value []byte, lease bool) (recreated bool, err error) {
   571  	defer func() {
   572  		Trace("UpdateIfDifferent", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "recreated": recreated})
   573  	}()
   574  
   575  	return c.updateIfDifferent(ctx, key, value, lease)
   576  }
   577  
   578  func (c *consulClient) updateIfDifferent(ctx context.Context, key string, value []byte, lease bool) (bool, error) {
   579  	duration := spanstat.Start()
   580  	getR, _, err := c.KV().Get(key, nil)
   581  	increaseMetric(key, metricRead, "Get", duration.EndError(err).Total(), err)
   582  	// On error, attempt update blindly
   583  	if err != nil || getR == nil {
   584  		return true, c.Update(ctx, key, value, lease)
   585  	}
   586  
   587  	if lease && getR.Session != c.lease {
   588  		return true, c.Update(ctx, key, value, lease)
   589  	}
   590  
   591  	// if lease is different and value is not equal then update.
   592  	if !bytes.Equal(getR.Value, value) {
   593  		return true, c.Update(ctx, key, value, lease)
   594  	}
   595  
   596  	return false, nil
   597  }
   598  
   599  // CreateOnlyIfLocked atomically creates a key if the client is still holding the given lock or fails if it already exists
   600  func (c *consulClient) CreateOnlyIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) (success bool, err error) {
   601  	defer func() {
   602  		Trace("CreateOnlyIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "success": success})
   603  	}()
   604  	return c.createOnly(ctx, key, value, lease)
   605  }
   606  
   607  // CreateOnly creates a key with the value and will fail if the key already exists
   608  func (c *consulClient) CreateOnly(ctx context.Context, key string, value []byte, lease bool) (success bool, err error) {
   609  	defer func() {
   610  		Trace("CreateOnly", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "success": success})
   611  	}()
   612  
   613  	return c.createOnly(ctx, key, value, lease)
   614  }
   615  
   616  func (c *consulClient) createOnly(ctx context.Context, key string, value []byte, lease bool) (bool, error) {
   617  	k := &consulAPI.KVPair{
   618  		Key:         key,
   619  		Value:       value,
   620  		CreateIndex: 0,
   621  	}
   622  
   623  	if lease {
   624  		k.Session = c.lease
   625  	}
   626  	opts := &consulAPI.WriteOptions{}
   627  
   628  	duration := spanstat.Start()
   629  	success, _, err := c.KV().CAS(k, opts.WithContext(ctx))
   630  	increaseMetric(key, metricSet, "CreateOnly", duration.EndError(err).Total(), err)
   631  	if err != nil {
   632  		return false, fmt.Errorf("unable to compare-and-swap: %s", err)
   633  	}
   634  	return success, nil
   635  }
   636  
   637  // createIfExists creates a key with the value only if key condKey exists
   638  func (c *consulClient) createIfExists(condKey, key string, value []byte, lease bool) error {
   639  	// Consul does not support transactions which would allow to check for
   640  	// the presence of a conditional key if the key is not the key being
   641  	// manipulated
   642  	//
   643  	// Lock the conditional key to serialize all CreateIfExists() calls
   644  
   645  	l, err := LockPath(context.Background(), c, condKey)
   646  	if err != nil {
   647  		return fmt.Errorf("unable to lock condKey for CreateIfExists: %s", err)
   648  	}
   649  
   650  	defer l.Unlock()
   651  
   652  	// Create the key if it does not exist
   653  	if _, err := c.CreateOnly(context.TODO(), key, value, lease); err != nil {
   654  		return err
   655  	}
   656  
   657  	// Consul does not support transactions which would allow to check for
   658  	// the presence of another key
   659  	masterKey, err := c.Get(condKey)
   660  	if err != nil || masterKey == nil {
   661  		c.Delete(key)
   662  		return fmt.Errorf("conditional key not present")
   663  	}
   664  
   665  	return nil
   666  }
   667  
   668  // CreateIfExists creates a key with the value only if key condKey exists
   669  func (c *consulClient) CreateIfExists(condKey, key string, value []byte, lease bool) (err error) {
   670  	defer func() {
   671  		Trace("CreateIfExists", err, logrus.Fields{fieldKey: key, fieldValue: string(value), fieldCondition: condKey, fieldAttachLease: lease})
   672  	}()
   673  	duration := spanstat.Start()
   674  	err = c.createIfExists(condKey, key, value, lease)
   675  	increaseMetric(key, metricSet, "CreateIfExists", duration.EndError(err).Total(), err)
   676  	return err
   677  }
   678  
   679  // ListPrefixIfLocked returns a list of keys matching the prefix only if the client is still holding the given lock.
   680  func (c *consulClient) ListPrefixIfLocked(prefix string, lock KVLocker) (v KeyValuePairs, err error) {
   681  	defer func() { Trace("ListPrefixIfLocked", err, logrus.Fields{fieldPrefix: prefix, fieldNumEntries: len(v)}) }()
   682  	v, err = c.ListPrefix(prefix)
   683  	return v, err
   684  }
   685  
   686  // ListPrefix returns a map of matching keys
   687  func (c *consulClient) ListPrefix(prefix string) (v KeyValuePairs, err error) {
   688  	defer func() { Trace("ListPrefix", err, logrus.Fields{fieldPrefix: prefix, fieldNumEntries: len(v)}) }()
   689  	duration := spanstat.Start()
   690  
   691  	var pairs consulAPI.KVPairs
   692  	pairs, _, err = c.KV().List(prefix, nil)
   693  	increaseMetric(prefix, metricRead, "ListPrefix", duration.EndError(err).Total(), err)
   694  	if err != nil {
   695  		return nil, err
   696  	}
   697  
   698  	p := KeyValuePairs(make(map[string]Value, len(pairs)))
   699  	for i := 0; i < len(pairs); i++ {
   700  		p[pairs[i].Key] = Value{
   701  			Data:        pairs[i].Value,
   702  			ModRevision: pairs[i].ModifyIndex,
   703  			SessionID:   pairs[i].Session,
   704  		}
   705  	}
   706  
   707  	return p, nil
   708  }
   709  
   710  // Close closes the consul session
   711  func (c *consulClient) Close() {
   712  	if c.controllers != nil {
   713  		c.controllers.RemoveAll()
   714  	}
   715  	if c.lease != "" {
   716  		c.Session().Destroy(c.lease, nil)
   717  	}
   718  }
   719  
   720  // GetCapabilities returns the capabilities of the backend
   721  func (c *consulClient) GetCapabilities() Capabilities {
   722  	return Capabilities(0)
   723  }
   724  
   725  // Encode encodes a binary slice into a character set that the backend supports
   726  func (c *consulClient) Encode(in []byte) (out string) {
   727  	defer func() { Trace("Encode", nil, logrus.Fields{"in": in, "out": out}) }()
   728  	return base64.URLEncoding.EncodeToString([]byte(in))
   729  }
   730  
   731  // Decode decodes a key previously encoded back into the original binary slice
   732  func (c *consulClient) Decode(in string) (out []byte, err error) {
   733  	defer func() { Trace("Decode", err, logrus.Fields{"in": in, "out": out}) }()
   734  	return base64.URLEncoding.DecodeString(in)
   735  }
   736  
   737  // ListAndWatch implements the BackendOperations.ListAndWatch using consul
   738  func (c *consulClient) ListAndWatch(name, prefix string, chanSize int) *Watcher {
   739  	w := newWatcher(name, prefix, chanSize)
   740  
   741  	log.WithField(fieldWatcher, w).Debug("Starting watcher...")
   742  
   743  	go c.Watch(w)
   744  
   745  	return w
   746  }