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

     1  // Package watch implements better watch semantics on top of etcd.
     2  // See this issue for the reasoning behind the package:
     3  // https://github.com/coreos/etcd/issues/7362
     4  package watch
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"reflect"
    10  
    11  	etcd "github.com/coreos/etcd/clientv3"
    12  	"github.com/gogo/protobuf/proto"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    14  )
    15  
    16  // EventType is the type of event
    17  type EventType int
    18  
    19  const (
    20  	// EventPut happens when an item is added
    21  	EventPut EventType = iota
    22  	// EventDelete happens when an item is removed
    23  	EventDelete
    24  	// EventError happens when an error occurred
    25  	EventError
    26  )
    27  
    28  // Event is an event that occurred to an item in etcd.
    29  type Event struct {
    30  	Key      []byte
    31  	Value    []byte
    32  	Type     EventType
    33  	Rev      int64
    34  	Ver      int64
    35  	Err      error
    36  	Template proto.Message
    37  }
    38  
    39  // Unmarshal unmarshals the item in an event into a protobuf message.
    40  func (e *Event) Unmarshal(key *string, val proto.Message) error {
    41  	if err := CheckType(e.Template, val); err != nil {
    42  		return err
    43  	}
    44  	*key = string(e.Key)
    45  	return proto.Unmarshal(e.Value, val)
    46  }
    47  
    48  // Watcher ...
    49  type Watcher interface {
    50  	// Watch returns a channel that delivers events
    51  	Watch() <-chan *Event
    52  	// Close this channel when you are done receiving events
    53  	Close()
    54  }
    55  
    56  type watcher struct {
    57  	eventCh chan *Event
    58  	done    chan struct{}
    59  }
    60  
    61  func (w *watcher) Watch() <-chan *Event {
    62  	return w.eventCh
    63  }
    64  
    65  func (w *watcher) Close() {
    66  	close(w.done)
    67  }
    68  
    69  // NewWatcher watches a given etcd prefix for events.
    70  func NewWatcher(ctx context.Context, client *etcd.Client, trimPrefix, prefix string, template proto.Message, opts ...OpOption) (Watcher, error) {
    71  	eventCh := make(chan *Event)
    72  	done := make(chan struct{})
    73  	// First list the collection to get the current items
    74  	// Sort by mod revision--how the items would have been returned if we watched
    75  	// them from the beginning.
    76  	getOptions := []etcd.OpOption{etcd.WithPrefix(), etcd.WithSort(etcd.SortByModRevision, etcd.SortAscend)}
    77  	for _, opt := range opts {
    78  		if opt.Get != nil {
    79  			getOptions = append(getOptions, etcd.OpOption(opt.Get))
    80  		}
    81  	}
    82  	resp, err := client.Get(ctx, prefix, getOptions...)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	nextRevision := resp.Header.Revision + 1
    87  	watchOptions := []etcd.OpOption{etcd.WithPrefix(), etcd.WithRev(nextRevision)}
    88  	for _, opt := range opts {
    89  		if opt.Watch != nil {
    90  			watchOptions = append(watchOptions, etcd.OpOption(opt.Watch))
    91  		}
    92  	}
    93  
    94  	etcdWatcher := etcd.NewWatcher(client)
    95  	// Issue a watch that uses the revision timestamp returned by the
    96  	// Get request earlier.  That way even if some items are added between
    97  	// when we list the collection and when we start watching the collection,
    98  	// we won't miss any items.
    99  
   100  	rch := etcdWatcher.Watch(ctx, prefix, watchOptions...)
   101  
   102  	go func() (retErr error) {
   103  		defer func() {
   104  			if retErr != nil {
   105  				select {
   106  				case eventCh <- &Event{
   107  					Err:  retErr,
   108  					Type: EventError,
   109  				}:
   110  				case <-done:
   111  				}
   112  			}
   113  			close(eventCh)
   114  			etcdWatcher.Close()
   115  		}()
   116  		for _, etcdKv := range resp.Kvs {
   117  			eventCh <- &Event{
   118  				Key:      bytes.TrimPrefix(etcdKv.Key, []byte(trimPrefix)),
   119  				Value:    etcdKv.Value,
   120  				Type:     EventPut,
   121  				Rev:      etcdKv.ModRevision,
   122  				Ver:      etcdKv.Version,
   123  				Template: template,
   124  			}
   125  		}
   126  		for {
   127  			var resp etcd.WatchResponse
   128  			var ok bool
   129  			select {
   130  			case resp, ok = <-rch:
   131  			case <-done:
   132  				return nil
   133  			}
   134  			if !ok {
   135  				if err := etcdWatcher.Close(); err != nil {
   136  					return err
   137  				}
   138  				etcdWatcher = etcd.NewWatcher(client)
   139  				// use new "nextRevision"
   140  				options := []etcd.OpOption{etcd.WithPrefix(), etcd.WithRev(nextRevision)}
   141  				for _, opt := range opts {
   142  					if opt.Watch != nil {
   143  						options = append(options, etcd.OpOption(opt.Watch))
   144  					}
   145  				}
   146  				rch = etcdWatcher.Watch(ctx, prefix, options...)
   147  				continue
   148  			}
   149  			if err := resp.Err(); err != nil {
   150  				return err
   151  			}
   152  			for _, etcdEv := range resp.Events {
   153  				ev := &Event{
   154  					Key:      bytes.TrimPrefix(etcdEv.Kv.Key, []byte(trimPrefix)),
   155  					Value:    etcdEv.Kv.Value,
   156  					Rev:      etcdEv.Kv.ModRevision,
   157  					Ver:      etcdEv.Kv.Version,
   158  					Template: template,
   159  				}
   160  				if etcdEv.Type == etcd.EventTypePut {
   161  					ev.Type = EventPut
   162  				} else {
   163  					ev.Type = EventDelete
   164  				}
   165  				select {
   166  				case eventCh <- ev:
   167  				case <-done:
   168  					return nil
   169  				}
   170  			}
   171  			nextRevision = resp.Header.Revision + 1
   172  		}
   173  	}()
   174  
   175  	return &watcher{
   176  		eventCh: eventCh,
   177  		done:    done,
   178  	}, nil
   179  }
   180  
   181  // MakeWatcher returns a Watcher that uses the given event channel and done
   182  // channel internally to deliver events and signal closure, respectively.
   183  func MakeWatcher(eventCh chan *Event, done chan struct{}) Watcher {
   184  	return &watcher{
   185  		eventCh: eventCh,
   186  		done:    done,
   187  	}
   188  }
   189  
   190  // CheckType checks to make sure val has the same type as template, unless
   191  // template is nil in which case it always returns nil.
   192  func CheckType(template proto.Message, val interface{}) error {
   193  	if template != nil {
   194  		valType, templateType := reflect.TypeOf(val), reflect.TypeOf(template)
   195  		if valType != templateType {
   196  			return errors.Errorf("invalid type, got: %s, expected: %s", valType, templateType)
   197  		}
   198  	}
   199  	return nil
   200  }