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 }