github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/collection/collection.go (about)

     1  package collection
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"path"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    16  	"github.com/pachyderm/pachyderm/src/client/pkg/tracing"
    17  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    18  	"github.com/pachyderm/pachyderm/src/server/pkg/watch"
    19  
    20  	etcd "github.com/coreos/etcd/clientv3"
    21  	"github.com/coreos/etcd/mvcc/mvccpb"
    22  	"github.com/gogo/protobuf/proto"
    23  )
    24  
    25  // defaultLimit was experimentally determined to be the highest value that could work
    26  // (It gets scaled down for specific collections if they trip the max-message size.)
    27  const (
    28  	defaultLimit    int64  = 262144
    29  	DefaultPrefix   string = "pachyderm/1.7.0"
    30  	indexIdentifier string = "__index_"
    31  )
    32  
    33  var (
    34  	// ErrNotClaimed is an error used to indicate that a different requester beat
    35  	// the current requester to a key claim.
    36  	ErrNotClaimed = errors.Errorf("NOT_CLAIMED")
    37  	ttl           = int64(30)
    38  )
    39  
    40  type collection struct {
    41  	etcdClient *etcd.Client
    42  	prefix     string
    43  	indexes    []*Index
    44  	// The limit used when listing the collection. This gets automatically
    45  	// tuned when requests fail so it's stored per collection.
    46  	limit int64
    47  	// We need this to figure out the concrete type of the objects
    48  	// that this collection is storing. It's pretty retarded, but
    49  	// not sure what else we can do since types in Go are not first-class
    50  	// objects.
    51  	// To be clear, this is only necessary because of `Delete`, where we
    52  	// need to know the type in order to properly remove secondary indexes.
    53  	template proto.Message
    54  	// keyCheck is a function that checks if a key is valid.  Invalid keys
    55  	// cannot be created.
    56  	keyCheck func(string) error
    57  
    58  	// valCheck is a function that checks if a value is valid.
    59  	valCheck func(proto.Message) error
    60  }
    61  
    62  // NewCollection creates a new collection.
    63  func NewCollection(etcdClient *etcd.Client, prefix string, indexes []*Index, template proto.Message, keyCheck func(string) error, valCheck func(proto.Message) error) Collection {
    64  	// We want to ensure that the prefix always ends with a trailing
    65  	// slash.  Otherwise, when you list the items under a collection
    66  	// such as `foo`, you might end up listing items under `foobar`
    67  	// as well.
    68  	if len(prefix) > 0 && prefix[len(prefix)-1] != '/' {
    69  		prefix = prefix + "/"
    70  	}
    71  
    72  	return &collection{
    73  		prefix:     prefix,
    74  		etcdClient: etcdClient,
    75  		indexes:    indexes,
    76  		limit:      defaultLimit,
    77  		template:   template,
    78  		keyCheck:   keyCheck,
    79  		valCheck:   valCheck,
    80  	}
    81  }
    82  
    83  func (c *collection) ReadWrite(stm STM) ReadWriteCollection {
    84  	return &readWriteCollection{
    85  		collection: c,
    86  		stm:        stm,
    87  	}
    88  }
    89  
    90  func (c *collection) ReadWriteInt(stm STM) ReadWriteIntCollection {
    91  	return &readWriteIntCollection{
    92  		collection: c,
    93  		stm:        stm,
    94  	}
    95  }
    96  
    97  func (c *collection) ReadOnly(ctx context.Context) ReadonlyCollection {
    98  	return &readonlyCollection{
    99  		collection: c,
   100  		ctx:        ctx,
   101  	}
   102  }
   103  
   104  func (c *collection) Claim(ctx context.Context, key string, val proto.Message, f func(context.Context) error) error {
   105  	var claimed bool
   106  	if _, err := NewSTM(ctx, c.etcdClient, func(stm STM) error {
   107  		readWriteC := c.ReadWrite(stm)
   108  		if err := readWriteC.Get(key, val); err != nil {
   109  			if !IsErrNotFound(err) {
   110  				return err
   111  			}
   112  			claimed = true
   113  			return readWriteC.PutTTL(key, val, ttl)
   114  		}
   115  		claimed = false
   116  		return nil
   117  	}); err != nil {
   118  		return err
   119  	}
   120  	if !claimed {
   121  		return ErrNotClaimed
   122  	}
   123  	claimCtx, cancel := context.WithCancel(ctx)
   124  	defer cancel()
   125  	go func() {
   126  		for {
   127  			select {
   128  			case <-time.After((time.Second * time.Duration(ttl)) / 2):
   129  				// (bryce) potential race condition, goroutine does PutTTL after Put for completion which deletes work
   130  				// potential way around this is to have this only update the lease and not do a put (maybe through keepalive?)
   131  				if _, err := NewSTM(claimCtx, c.etcdClient, func(stm STM) error {
   132  					readWriteC := c.ReadWrite(stm)
   133  					if err := readWriteC.Get(key, val); err != nil {
   134  						return err
   135  					}
   136  					return readWriteC.PutTTL(key, val, ttl)
   137  				}); err != nil {
   138  					cancel()
   139  					return
   140  				}
   141  			case <-claimCtx.Done():
   142  				return
   143  			}
   144  		}
   145  	}()
   146  	return f(claimCtx)
   147  }
   148  
   149  // Path returns the full path of a key in the etcd namespace
   150  func (c *collection) Path(key string) string {
   151  	return path.Join(c.prefix, key)
   152  }
   153  
   154  func (c *collection) indexRoot(index *Index) string {
   155  	// remove trailing slash from c.prefix
   156  	return fmt.Sprintf("%s%s%s/",
   157  		strings.TrimRight(c.prefix, "/"), indexIdentifier, index.Field)
   158  }
   159  
   160  // See the documentation for `Index` for details.
   161  func (c *collection) indexDir(index *Index, indexVal interface{}) string {
   162  	var indexValStr string
   163  	if marshaller, ok := indexVal.(proto.Marshaler); ok {
   164  		if indexValBytes, err := marshaller.Marshal(); err == nil {
   165  			// use marshalled proto as index. This way we can rename fields without
   166  			// breaking our index.
   167  			indexValStr = string(indexValBytes)
   168  		} else {
   169  			// log error but keep going (this used to be the only codepath)
   170  			log.Printf("ERROR trying to marshal index value: %v", err)
   171  			indexValStr = fmt.Sprintf("%v", indexVal)
   172  		}
   173  	} else {
   174  		indexValStr = fmt.Sprintf("%v", indexVal)
   175  	}
   176  	return path.Join(c.indexRoot(index), indexValStr)
   177  }
   178  
   179  // See the documentation for `Index` for details.
   180  func (c *collection) indexPath(index *Index, indexVal interface{}, key string) string {
   181  	return path.Join(c.indexDir(index, indexVal), key)
   182  }
   183  
   184  type readWriteCollection struct {
   185  	*collection
   186  	stm STM
   187  }
   188  
   189  func (c *readWriteCollection) Get(key string, val proto.Message) (retErr error) {
   190  	span, _ := tracing.AddSpanToAnyExisting(c.stm.Context(), "/etcd.RW/Get",
   191  		"col", c.prefix, "key", strings.TrimPrefix(key, c.prefix))
   192  	defer func() {
   193  		tracing.TagAnySpan(span, "err", retErr)
   194  		tracing.FinishAnySpan(span)
   195  	}()
   196  	if err := watch.CheckType(c.template, val); err != nil {
   197  		return err
   198  	}
   199  	valStr, err := c.stm.Get(c.Path(key))
   200  	if err != nil {
   201  		if IsErrNotFound(err) {
   202  			return ErrNotFound{c.prefix, key}
   203  		}
   204  		return err
   205  	}
   206  	c.stm.SetSafePutCheck(c.Path(key), reflect.ValueOf(val).Pointer())
   207  	return proto.Unmarshal([]byte(valStr), val)
   208  }
   209  
   210  func cloneProtoMsg(original proto.Message) proto.Message {
   211  	val := reflect.ValueOf(original)
   212  	if val.Kind() == reflect.Ptr {
   213  		val = reflect.Indirect(val)
   214  	}
   215  	return reflect.New(val.Type()).Interface().(proto.Message)
   216  }
   217  
   218  // Giving a value, an index, and the key of the item, return the path
   219  // under which the new index item should be stored.
   220  func (c *readWriteCollection) getIndexPath(val interface{}, index *Index, key string) string {
   221  	reflVal := reflect.ValueOf(val)
   222  	field := reflect.Indirect(reflVal).FieldByName(index.Field).Interface()
   223  	return c.indexPath(index, field, key)
   224  }
   225  
   226  // Giving a value, a multi-index, and the key of the item, return the
   227  // paths under which the multi-index items should be stored.
   228  func (c *readWriteCollection) getMultiIndexPaths(val interface{}, index *Index, key string) []string {
   229  	var indexPaths []string
   230  	field := reflect.Indirect(reflect.ValueOf(val)).FieldByName(index.Field)
   231  	for i := 0; i < field.Len(); i++ {
   232  		indexPaths = append(indexPaths, c.indexPath(index, field.Index(i).Interface(), key))
   233  	}
   234  	return indexPaths
   235  }
   236  
   237  func (c *readWriteCollection) Put(key string, val proto.Message) error {
   238  	return c.PutTTL(key, val, 0)
   239  }
   240  
   241  func (c *readWriteCollection) TTL(key string) (int64, error) {
   242  	ttl, err := c.stm.TTL(c.Path(key))
   243  	if IsErrNotFound(err) {
   244  		return ttl, ErrNotFound{c.prefix, key}
   245  	}
   246  	return ttl, err
   247  }
   248  
   249  func (c *readWriteCollection) PutTTL(key string, val proto.Message, ttl int64) error {
   250  	if strings.Contains(key, indexIdentifier) {
   251  		return errors.Errorf("cannot put key %q which contains reserved string %q", key, indexIdentifier)
   252  	}
   253  	if err := watch.CheckType(c.template, val); err != nil {
   254  		return err
   255  	}
   256  
   257  	if c.collection.keyCheck != nil {
   258  		if err := c.collection.keyCheck(key); err != nil {
   259  			return err
   260  		}
   261  	}
   262  
   263  	if c.collection.valCheck != nil {
   264  		if err := c.collection.valCheck(val); err != nil {
   265  			return err
   266  		}
   267  	}
   268  
   269  	ptr := reflect.ValueOf(val).Pointer()
   270  	if !c.stm.IsSafePut(c.Path(key), ptr) {
   271  		return errors.Errorf("unsafe put for key %v (passed ptr did not receive updated value)", key)
   272  	}
   273  
   274  	if c.indexes != nil {
   275  		clone := cloneProtoMsg(val)
   276  
   277  		// Put the appropriate record in any of c's secondary indexes
   278  		for _, index := range c.indexes {
   279  			if index.Multi {
   280  				indexPaths := c.getMultiIndexPaths(val, index, key)
   281  				for _, indexPath := range indexPaths {
   282  					// Only put the index if it doesn't already exist; otherwise
   283  					// we might trigger an unnecessary event if someone is
   284  					// watching the index
   285  					if _, err := c.stm.Get(indexPath); err != nil && IsErrNotFound(err) {
   286  						if err := c.stm.Put(indexPath, key, ttl, 0); err != nil {
   287  							return err
   288  						}
   289  					}
   290  				}
   291  				// If we can get the original value, we remove the original indexes
   292  				if err := c.Get(key, clone); err == nil {
   293  					for _, originalIndexPath := range c.getMultiIndexPaths(clone, index, key) {
   294  						var found bool
   295  						for _, indexPath := range indexPaths {
   296  							if originalIndexPath == indexPath {
   297  								found = true
   298  							}
   299  						}
   300  						if !found {
   301  							c.stm.Del(originalIndexPath)
   302  						}
   303  					}
   304  				}
   305  			} else {
   306  				indexPath := c.getIndexPath(val, index, key)
   307  				// If we can get the original value, we remove the original indexes
   308  				if err := c.Get(key, clone); err == nil {
   309  					originalIndexPath := c.getIndexPath(clone, index, key)
   310  					if originalIndexPath != indexPath {
   311  						c.stm.Del(originalIndexPath)
   312  					}
   313  				}
   314  				// Only put the index if it doesn't already exist; otherwise
   315  				// we might trigger an unnecessary event if someone is
   316  				// watching the index
   317  				if _, err := c.stm.Get(indexPath); err != nil && IsErrNotFound(err) {
   318  					if err := c.stm.Put(indexPath, key, ttl, 0); err != nil {
   319  						return err
   320  					}
   321  				}
   322  			}
   323  		}
   324  	}
   325  	bytes, err := proto.Marshal(val)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	return c.stm.Put(c.Path(key), string(bytes), ttl, ptr)
   330  }
   331  
   332  // Update reads the current value associated with 'key', calls 'f' to update
   333  // the value, and writes the new value back to the collection. 'key' must be
   334  // present in the collection, or a 'Not Found' error is returned
   335  func (c *readWriteCollection) Update(key string, val proto.Message, f func() error) error {
   336  	if err := watch.CheckType(c.template, val); err != nil {
   337  		return err
   338  	}
   339  	if err := c.Get(key, val); err != nil {
   340  		if IsErrNotFound(err) {
   341  			return ErrNotFound{c.prefix, key}
   342  		}
   343  		return err
   344  	}
   345  	if err := f(); err != nil {
   346  		return err
   347  	}
   348  	return c.Put(key, val)
   349  }
   350  
   351  // Upsert is like Update but 'key' is not required to be present
   352  func (c *readWriteCollection) Upsert(key string, val proto.Message, f func() error) error {
   353  	if err := watch.CheckType(c.template, val); err != nil {
   354  		return err
   355  	}
   356  	if err := c.Get(key, val); err != nil && !IsErrNotFound(err) {
   357  		return err
   358  	}
   359  	if err := f(); err != nil {
   360  		return err
   361  	}
   362  	return c.Put(key, val)
   363  }
   364  
   365  func (c *readWriteCollection) Create(key string, val proto.Message) error {
   366  	if err := watch.CheckType(c.template, val); err != nil {
   367  		return err
   368  	}
   369  	fullKey := c.Path(key)
   370  	_, err := c.stm.Get(fullKey)
   371  	if err != nil && !IsErrNotFound(err) {
   372  		return err
   373  	}
   374  	if err == nil {
   375  		return ErrExists{c.prefix, key}
   376  	}
   377  	return c.Put(key, val)
   378  }
   379  
   380  func (c *readWriteCollection) Delete(key string) error {
   381  	fullKey := c.Path(key)
   382  	if _, err := c.stm.Get(fullKey); err != nil {
   383  		return err
   384  	}
   385  	if c.indexes != nil && c.template != nil {
   386  		val := proto.Clone(c.template)
   387  		for _, index := range c.indexes {
   388  			if err := c.Get(key, val); err == nil {
   389  				if index.Multi {
   390  					indexPaths := c.getMultiIndexPaths(val, index, key)
   391  					for _, indexPath := range indexPaths {
   392  						c.stm.Del(indexPath)
   393  					}
   394  				} else {
   395  					indexPath := c.getIndexPath(val, index, key)
   396  					c.stm.Del(indexPath)
   397  				}
   398  			}
   399  		}
   400  	}
   401  	c.stm.Del(fullKey)
   402  	return nil
   403  }
   404  
   405  func (c *readWriteCollection) DeleteAll() {
   406  	// Delete indexes
   407  	for _, index := range c.indexes {
   408  		c.stm.DelAll(c.indexRoot(index))
   409  	}
   410  	c.stm.DelAll(c.prefix)
   411  }
   412  
   413  func (c *readWriteCollection) DeleteAllPrefix(prefix string) {
   414  	c.stm.DelAll(path.Join(c.prefix, prefix) + "/")
   415  }
   416  
   417  type readWriteIntCollection struct {
   418  	*collection
   419  	stm STM
   420  }
   421  
   422  func (c *readWriteIntCollection) Create(key string, val int) error {
   423  	fullKey := c.Path(key)
   424  	_, err := c.stm.Get(fullKey)
   425  	if err != nil && !IsErrNotFound(err) {
   426  		return err
   427  	}
   428  	if err == nil {
   429  		return ErrExists{c.prefix, key}
   430  	}
   431  	return c.stm.Put(fullKey, strconv.Itoa(val), 0, 0)
   432  }
   433  
   434  func (c *readWriteIntCollection) Get(key string) (int, error) {
   435  	valStr, err := c.stm.Get(c.Path(key))
   436  	if err != nil {
   437  		return 0, err
   438  	}
   439  	return strconv.Atoi(valStr)
   440  }
   441  
   442  func (c *readWriteIntCollection) Increment(key string) error {
   443  	return c.IncrementBy(key, 1)
   444  }
   445  
   446  func (c *readWriteIntCollection) IncrementBy(key string, n int) error {
   447  	fullKey := c.Path(key)
   448  	valStr, err := c.stm.Get(fullKey)
   449  	if err != nil {
   450  		return err
   451  	}
   452  	val, err := strconv.Atoi(valStr)
   453  	if err != nil {
   454  		return ErrMalformedValue{c.prefix, key, valStr}
   455  	}
   456  	return c.stm.Put(fullKey, strconv.Itoa(val+n), 0, 0)
   457  }
   458  
   459  func (c *readWriteIntCollection) Decrement(key string) error {
   460  	return c.DecrementBy(key, 1)
   461  }
   462  
   463  func (c *readWriteIntCollection) DecrementBy(key string, n int) error {
   464  	fullKey := c.Path(key)
   465  	valStr, err := c.stm.Get(fullKey)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	val, err := strconv.Atoi(valStr)
   470  	if err != nil {
   471  		return ErrMalformedValue{c.prefix, key, valStr}
   472  	}
   473  	return c.stm.Put(fullKey, strconv.Itoa(val-n), 0, 0)
   474  }
   475  
   476  func (c *readWriteIntCollection) Delete(key string) error {
   477  	fullKey := c.Path(key)
   478  	if _, err := c.stm.Get(fullKey); err != nil {
   479  		return err
   480  	}
   481  	c.stm.Del(fullKey)
   482  	return nil
   483  }
   484  
   485  type readonlyCollection struct {
   486  	*collection
   487  	ctx context.Context
   488  }
   489  
   490  // get is an internal wrapper around etcdClient.Get that wraps the call in a
   491  // trace
   492  func (c *readonlyCollection) get(key string, opts ...etcd.OpOption) (resp *etcd.GetResponse, retErr error) {
   493  	span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/Get",
   494  		"col", c.prefix, "key", strings.TrimPrefix(key, c.prefix))
   495  	defer func() {
   496  		tracing.TagAnySpan(span, "err", retErr)
   497  		tracing.FinishAnySpan(span)
   498  	}()
   499  	resp, err := c.etcdClient.Get(ctx, key, opts...)
   500  	return resp, err
   501  }
   502  
   503  func (c *readonlyCollection) Get(key string, val proto.Message) error {
   504  	if err := watch.CheckType(c.template, val); err != nil {
   505  		return err
   506  	}
   507  	resp, err := c.get(c.Path(key))
   508  	if err != nil {
   509  		return err
   510  	}
   511  
   512  	if len(resp.Kvs) == 0 {
   513  		return ErrNotFound{c.prefix, key}
   514  	}
   515  
   516  	return proto.Unmarshal(resp.Kvs[0].Value, val)
   517  }
   518  
   519  func (c *readonlyCollection) GetByIndex(index *Index, indexVal interface{}, val proto.Message, opts *Options, f func(key string) error) error {
   520  	span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/GetByIndex", "col", c.prefix, "index", index, "indexVal", indexVal)
   521  	defer tracing.FinishAnySpan(span)
   522  	if atomic.LoadInt64(&index.limit) == 0 {
   523  		atomic.CompareAndSwapInt64(&index.limit, 0, defaultLimit)
   524  	}
   525  	return c.list(c.indexDir(index, indexVal), &index.limit, opts, func(kv *mvccpb.KeyValue) error {
   526  		key := path.Base(string(kv.Key))
   527  		if err := c.Get(key, val); err != nil {
   528  			if IsErrNotFound(err) {
   529  				// In cases where we changed how certain objects are
   530  				// indexed, we could end up in a situation where the
   531  				// object is deleted but the old indexes still exist.
   532  				return nil
   533  			}
   534  			return err
   535  		}
   536  		return f(key)
   537  	})
   538  }
   539  
   540  func (c *readonlyCollection) GetBlock(key string, val proto.Message) error {
   541  	span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/GetBlock",
   542  		"col", c.prefix, "key", strings.TrimPrefix(key, c.prefix))
   543  	defer tracing.FinishAnySpan(span)
   544  	if err := watch.CheckType(c.template, val); err != nil {
   545  		return err
   546  	}
   547  	watcher, err := watch.NewWatcher(ctx, c.etcdClient, c.prefix, c.Path(key), c.template)
   548  	if err != nil {
   549  		return err
   550  	}
   551  	defer watcher.Close()
   552  	e := <-watcher.Watch()
   553  	if e.Err != nil {
   554  		return e.Err
   555  	}
   556  	return e.Unmarshal(&key, val)
   557  }
   558  
   559  func (c *readonlyCollection) TTL(key string) (int64, error) {
   560  	resp, err := c.get(c.Path(key))
   561  	if err != nil {
   562  		return 0, err
   563  	}
   564  	if len(resp.Kvs) == 0 {
   565  		return 0, ErrNotFound{c.prefix, key}
   566  	}
   567  	leaseID := etcd.LeaseID(resp.Kvs[0].Lease)
   568  
   569  	span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/TimeToLive")
   570  	defer tracing.FinishAnySpan(span)
   571  	leaseTTLResp, err := c.etcdClient.TimeToLive(ctx, leaseID)
   572  	if err != nil {
   573  		return 0, errors.Wrapf(err, "could not fetch lease TTL")
   574  	}
   575  	return leaseTTLResp.TTL, nil
   576  }
   577  
   578  // ListPrefix returns keys (and values) that begin with prefix, f will be
   579  // called with each key, val will contain the value for the key.
   580  // You can break out of iteration by returning errutil.ErrBreak.
   581  func (c *readonlyCollection) ListPrefix(prefix string, val proto.Message, opts *Options, f func(string) error) error {
   582  	span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/ListPrefix", "col", c.prefix, "prefix", prefix)
   583  	defer tracing.FinishAnySpan(span)
   584  	queryPrefix := c.prefix
   585  	if prefix != "" {
   586  		// If we always call join, we'll get rid of the trailing slash we need
   587  		// on the root c.prefix
   588  		queryPrefix = filepath.Join(c.prefix, prefix)
   589  	}
   590  	return c.list(queryPrefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error {
   591  		if err := proto.Unmarshal(kv.Value, val); err != nil {
   592  			return err
   593  		}
   594  		return f(strings.TrimPrefix(string(kv.Key), queryPrefix))
   595  	})
   596  }
   597  
   598  // List returns objects sorted based on the options passed in. f will be called with each key, val will contain the
   599  // corresponding value. Val is not an argument to f because that would require
   600  // f to perform a cast before it could be used.
   601  // You can break out of iteration by returning errutil.ErrBreak.
   602  func (c *readonlyCollection) List(val proto.Message, opts *Options, f func(key string) error) error {
   603  	span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/List", "col", c.prefix)
   604  	defer tracing.FinishAnySpan(span)
   605  	if err := watch.CheckType(c.template, val); err != nil {
   606  		return err
   607  	}
   608  	return c.list(c.prefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error {
   609  		if err := proto.Unmarshal(kv.Value, val); err != nil {
   610  			return err
   611  		}
   612  		return f(strings.TrimPrefix(string(kv.Key), c.prefix))
   613  	})
   614  }
   615  
   616  // ListRev returns objects sorted based on the options passed in. f will be called
   617  // with each key and the create-revision of the key, val will contain the
   618  // corresponding value. Val is not an argument to f because that would require
   619  // f to perform a cast before it could be used.  You can break out of iteration
   620  // by returning errutil.ErrBreak.
   621  func (c *readonlyCollection) ListRev(val proto.Message, opts *Options, f func(key string, createRev int64) error) error {
   622  	span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/List", "col", c.prefix)
   623  	defer tracing.FinishAnySpan(span)
   624  	if err := watch.CheckType(c.template, val); err != nil {
   625  		return err
   626  	}
   627  	return c.list(c.prefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error {
   628  		if err := proto.Unmarshal(kv.Value, val); err != nil {
   629  			return err
   630  		}
   631  		return f(strings.TrimPrefix(string(kv.Key), c.prefix), kv.CreateRevision)
   632  	})
   633  }
   634  
   635  func (c *readonlyCollection) list(prefix string, limitPtr *int64, opts *Options, f func(*mvccpb.KeyValue) error) error {
   636  	if opts.SelfSort {
   637  		return listSelfSortRevision(c, prefix, limitPtr, opts, f)
   638  	}
   639  
   640  	return listRevision(c, prefix, limitPtr, opts, f)
   641  }
   642  
   643  var (
   644  	countOpts = []etcd.OpOption{etcd.WithPrefix(), etcd.WithCountOnly()}
   645  )
   646  
   647  func (c *readonlyCollection) Count() (int64, error) {
   648  	resp, err := c.get(c.prefix, countOpts...)
   649  	if err != nil {
   650  		return 0, err
   651  	}
   652  	return resp.Count, err
   653  }
   654  
   655  func (c *readonlyCollection) CountRev(rev int64) (int64, int64, error) {
   656  	resp, err := c.get(c.prefix, append(countOpts, etcd.WithRev(rev))...)
   657  	if err != nil {
   658  		return 0, 0, err
   659  	}
   660  	return resp.Count, resp.Header.Revision, err
   661  }
   662  
   663  // Watch a collection, returning the current content of the collection as
   664  // well as any future additions.
   665  func (c *readonlyCollection) Watch(opts ...watch.OpOption) (watch.Watcher, error) {
   666  	return watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.prefix, c.template, opts...)
   667  }
   668  
   669  // WatchF watches a collection and executes a callback function each time an event occurs.
   670  func (c *readonlyCollection) WatchF(f func(e *watch.Event) error, opts ...watch.OpOption) error {
   671  	watcher, err := c.Watch(opts...)
   672  	if err != nil {
   673  		return err
   674  	}
   675  	defer watcher.Close()
   676  	return c.watchF(watcher, f)
   677  }
   678  
   679  func (c *readonlyCollection) watchF(watcher watch.Watcher, f func(e *watch.Event) error) error {
   680  	for {
   681  		select {
   682  		case e, ok := <-watcher.Watch():
   683  			if !ok {
   684  				return nil
   685  			}
   686  			if e.Type == watch.EventError {
   687  				return e.Err
   688  			}
   689  			if err := f(e); err != nil {
   690  				if errors.Is(err, errutil.ErrBreak) {
   691  					return nil
   692  				}
   693  				return err
   694  			}
   695  		case <-c.ctx.Done():
   696  			return c.ctx.Err()
   697  		}
   698  	}
   699  }
   700  
   701  // WatchByIndex watches items in a collection that match a particular index
   702  func (c *readonlyCollection) WatchByIndex(index *Index, val interface{}) (watch.Watcher, error) {
   703  	eventCh := make(chan *watch.Event)
   704  	done := make(chan struct{})
   705  	watcher, err := watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.indexDir(index, val), c.template)
   706  	if err != nil {
   707  		return nil, err
   708  	}
   709  	go func() (retErr error) {
   710  		defer func() {
   711  			if retErr != nil {
   712  				eventCh <- &watch.Event{
   713  					Type: watch.EventError,
   714  					Err:  retErr,
   715  				}
   716  				watcher.Close()
   717  			}
   718  			close(eventCh)
   719  		}()
   720  		for {
   721  			var ev *watch.Event
   722  			var ok bool
   723  			select {
   724  			case ev, ok = <-watcher.Watch():
   725  			case <-done:
   726  				watcher.Close()
   727  				return nil
   728  			}
   729  			if !ok {
   730  				watcher.Close()
   731  				return nil
   732  			}
   733  
   734  			var directEv *watch.Event
   735  			switch ev.Type {
   736  			case watch.EventError:
   737  				// pass along the error
   738  				return ev.Err
   739  			case watch.EventPut:
   740  				resp, err := c.get(c.Path(path.Base(string(ev.Key))))
   741  				if err != nil {
   742  					return err
   743  				}
   744  				if len(resp.Kvs) == 0 {
   745  					// this happens only if the item was deleted shortly after
   746  					// we receive this event.
   747  					continue
   748  				}
   749  				directEv = &watch.Event{
   750  					Key:      []byte(path.Base(string(ev.Key))),
   751  					Value:    resp.Kvs[0].Value,
   752  					Type:     ev.Type,
   753  					Template: c.template,
   754  				}
   755  			case watch.EventDelete:
   756  				directEv = &watch.Event{
   757  					Key:      []byte(path.Base(string(ev.Key))),
   758  					Type:     ev.Type,
   759  					Template: c.template,
   760  				}
   761  			}
   762  			eventCh <- directEv
   763  		}
   764  	}()
   765  	return watch.MakeWatcher(eventCh, done), nil
   766  }
   767  
   768  // WatchOne watches a given item.  The first value returned from the watch
   769  // will be the current value of the item.
   770  func (c *readonlyCollection) WatchOne(key string, opts ...watch.OpOption) (watch.Watcher, error) {
   771  	return watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.Path(key), c.template, opts...)
   772  }
   773  
   774  // WatchOneF watches a given item and executes a callback function each time an event occurs.
   775  // The first value returned from the watch will be the current value of the item.
   776  func (c *readonlyCollection) WatchOneF(key string, f func(e *watch.Event) error, opts ...watch.OpOption) error {
   777  	watcher, err := watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.Path(key), c.template, opts...)
   778  	if err != nil {
   779  		return err
   780  	}
   781  	defer watcher.Close()
   782  	return c.watchF(watcher, f)
   783  }