github.com/cilium/cilium@v1.16.2/pkg/kvstore/consul.go (about)

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