github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/stream/subscription.go (about)

     1  package stream
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync/atomic"
     7  
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  const (
    12  	// subscriptionStateOpen is the default state of a subscription. An open
    13  	// subscription may receive new events.
    14  	subscriptionStateOpen uint32 = 0
    15  
    16  	// subscriptionStateClosed indicates that the subscription was closed, possibly
    17  	// as a result of a change to an ACL token, and will not receive new events.
    18  	// The subscriber must issue a new Subscribe request.
    19  	subscriptionStateClosed uint32 = 1
    20  )
    21  
    22  // ErrSubscriptionClosed is a error signalling the subscription has been
    23  // closed. The client should Unsubscribe, then re-Subscribe.
    24  var ErrSubscriptionClosed = errors.New("subscription closed by server, client should resubscribe")
    25  var ErrACLInvalid = errors.New("Provided ACL token is invalid for requested topics")
    26  
    27  type Subscription struct {
    28  	// state must be accessed atomically 0 means open, 1 means closed with reload
    29  	state uint32
    30  
    31  	req *SubscribeRequest
    32  
    33  	// currentItem stores the current buffer item we are on. It
    34  	// is mutated by calls to Next.
    35  	currentItem *bufferItem
    36  
    37  	// forceClosed is closed when forceClose is called. It is used by
    38  	// EventBroker to cancel Next().
    39  	forceClosed chan struct{}
    40  
    41  	// unsub is a function set by EventBroker that is called to free resources
    42  	// when the subscription is no longer needed.
    43  	// It must be safe to call the function from multiple goroutines and the function
    44  	// must be idempotent.
    45  	unsub func()
    46  }
    47  
    48  type SubscribeRequest struct {
    49  	Token     string
    50  	Index     uint64
    51  	Namespace string
    52  
    53  	Topics map[structs.Topic][]string
    54  
    55  	// StartExactlyAtIndex specifies if a subscription needs to
    56  	// start exactly at the requested Index. If set to false,
    57  	// the closest index in the buffer will be returned if there is not
    58  	// an exact match
    59  	StartExactlyAtIndex bool
    60  }
    61  
    62  func newSubscription(req *SubscribeRequest, item *bufferItem, unsub func()) *Subscription {
    63  	return &Subscription{
    64  		forceClosed: make(chan struct{}),
    65  		req:         req,
    66  		currentItem: item,
    67  		unsub:       unsub,
    68  	}
    69  }
    70  
    71  func (s *Subscription) Next(ctx context.Context) (structs.Events, error) {
    72  	if atomic.LoadUint32(&s.state) == subscriptionStateClosed {
    73  		return structs.Events{}, ErrSubscriptionClosed
    74  	}
    75  
    76  	for {
    77  		next, err := s.currentItem.Next(ctx, s.forceClosed)
    78  		switch {
    79  		case err != nil && atomic.LoadUint32(&s.state) == subscriptionStateClosed:
    80  			return structs.Events{}, ErrSubscriptionClosed
    81  		case err != nil:
    82  			return structs.Events{}, err
    83  		}
    84  		s.currentItem = next
    85  
    86  		events := filter(s.req, next.Events.Events)
    87  		if len(events) == 0 {
    88  			continue
    89  		}
    90  		return structs.Events{Index: next.Events.Index, Events: events}, nil
    91  	}
    92  }
    93  
    94  func (s *Subscription) NextNoBlock() ([]structs.Event, error) {
    95  	if atomic.LoadUint32(&s.state) == subscriptionStateClosed {
    96  		return nil, ErrSubscriptionClosed
    97  	}
    98  
    99  	for {
   100  		next := s.currentItem.NextNoBlock()
   101  		if next == nil {
   102  			return nil, nil
   103  		}
   104  		s.currentItem = next
   105  
   106  		events := filter(s.req, next.Events.Events)
   107  		if len(events) == 0 {
   108  			continue
   109  		}
   110  		return events, nil
   111  	}
   112  }
   113  
   114  func (s *Subscription) Unsubscribe() {
   115  	s.unsub()
   116  }
   117  
   118  // filter events to only those that match a subscriptions topic/keys/namespace
   119  func filter(req *SubscribeRequest, events []structs.Event) []structs.Event {
   120  	if len(events) == 0 {
   121  		return nil
   122  	}
   123  
   124  	allTopicKeys := req.Topics[structs.TopicAll]
   125  
   126  	if req.Namespace == "" && len(allTopicKeys) == 1 && allTopicKeys[0] == string(structs.TopicAll) {
   127  		return events
   128  	}
   129  
   130  	var result []structs.Event
   131  
   132  	for _, event := range events {
   133  		if req.Namespace != "" && event.Namespace != "" && event.Namespace != req.Namespace {
   134  			continue
   135  		}
   136  
   137  		// *[*] always matches
   138  		if len(allTopicKeys) == 1 && allTopicKeys[0] == string(structs.TopicAll) {
   139  			result = append(result, event)
   140  			continue
   141  		}
   142  
   143  		keys := allTopicKeys
   144  
   145  		if topicKeys, ok := req.Topics[event.Topic]; ok {
   146  			keys = append(keys, topicKeys...)
   147  		}
   148  
   149  		if len(keys) == 1 && keys[0] == string(structs.TopicAll) {
   150  			result = append(result, event)
   151  			continue
   152  		}
   153  
   154  		for _, key := range keys {
   155  			if eventMatchesKey(event, key) {
   156  				result = append(result, event)
   157  				continue
   158  			}
   159  		}
   160  	}
   161  
   162  	return result
   163  }
   164  
   165  func eventMatchesKey(event structs.Event, key string) bool {
   166  	if event.Key == key {
   167  		return true
   168  	}
   169  
   170  	for _, fk := range event.FilterKeys {
   171  		if fk == key {
   172  			return true
   173  		}
   174  	}
   175  
   176  	return false
   177  }