2020-07-06 21:29:45 +00:00
|
|
|
/*
|
|
|
|
Package stream provides a publish/subscribe system for events produced by changes
|
|
|
|
to the state store.
|
|
|
|
*/
|
2020-06-05 23:36:31 +00:00
|
|
|
package stream
|
|
|
|
|
2020-11-05 22:57:25 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
)
|
2020-06-05 23:36:31 +00:00
|
|
|
|
2020-07-07 00:04:24 +00:00
|
|
|
// Topic is an identifier that partitions events. A subscription will only receive
|
|
|
|
// events which match the Topic.
|
|
|
|
type Topic fmt.Stringer
|
2020-06-05 23:36:31 +00:00
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
// Subject identifies a portion of a topic for which a subscriber wishes to
|
|
|
|
// receive events (e.g. health events for a particular service) usually the
|
|
|
|
// normalized resource name (including partition and namespace if applicable).
|
|
|
|
type Subject string
|
|
|
|
|
2020-07-07 00:04:24 +00:00
|
|
|
// Event is a structure with identifiers and a payload. Events are Published to
|
|
|
|
// EventPublisher and returned to Subscribers.
|
2020-06-05 23:36:31 +00:00
|
|
|
type Event struct {
|
|
|
|
Topic Topic
|
|
|
|
Index uint64
|
2020-10-27 18:40:06 +00:00
|
|
|
Payload Payload
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:00:33 +00:00
|
|
|
// A Payload contains the topic-specific data in an event. The payload methods
|
|
|
|
// should not modify the state of the payload if the Event is being submitted to
|
|
|
|
// EventPublisher.Publish.
|
2020-10-27 18:40:06 +00:00
|
|
|
type Payload interface {
|
2020-11-05 22:57:25 +00:00
|
|
|
// HasReadPermission uses the acl.Authorizer to determine if the items in the
|
|
|
|
// Payload are visible to the request. It returns true if the payload is
|
|
|
|
// authorized for Read, otherwise returns false.
|
|
|
|
HasReadPermission(authz acl.Authorizer) bool
|
2022-01-28 12:27:00 +00:00
|
|
|
|
|
|
|
// Subject is used to identify which subscribers should be notified of this
|
|
|
|
// event - e.g. those subscribing to health events for a particular service.
|
|
|
|
// it is usually the normalized resource name (including the partition and
|
|
|
|
// namespace if applicable).
|
|
|
|
Subject() Subject
|
2020-06-05 23:36:31 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 18:00:33 +00:00
|
|
|
// PayloadEvents is a Payload that may be returned by Subscription.Next when
|
|
|
|
// there are multiple events at an index.
|
|
|
|
//
|
|
|
|
// Note that unlike most other Payload, PayloadEvents is mutable and it is NOT
|
|
|
|
// safe to send to EventPublisher.Publish.
|
2020-11-05 22:50:17 +00:00
|
|
|
type PayloadEvents struct {
|
|
|
|
Items []Event
|
2020-10-05 16:38:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 18:00:33 +00:00
|
|
|
func newPayloadEvents(items ...Event) *PayloadEvents {
|
2020-11-05 22:50:17 +00:00
|
|
|
return &PayloadEvents{Items: items}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PayloadEvents) filter(f func(Event) bool) bool {
|
|
|
|
items := p.Items
|
2020-10-05 16:38:38 +00:00
|
|
|
|
|
|
|
// To avoid extra allocations, iterate over the list of events first and
|
|
|
|
// get a count of the total desired size. This trades off some extra cpu
|
|
|
|
// time in the worse case (when not all items match the filter), for
|
|
|
|
// fewer memory allocations.
|
|
|
|
var size int
|
2020-11-05 22:50:17 +00:00
|
|
|
for idx := range items {
|
|
|
|
if f(items[idx]) {
|
2020-10-05 16:38:38 +00:00
|
|
|
size++
|
|
|
|
}
|
|
|
|
}
|
2020-11-05 22:50:17 +00:00
|
|
|
if len(items) == size || size == 0 {
|
|
|
|
return size != 0
|
2020-10-05 16:38:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 22:50:17 +00:00
|
|
|
filtered := make([]Event, 0, size)
|
|
|
|
for idx := range items {
|
|
|
|
event := items[idx]
|
2020-10-05 16:38:38 +00:00
|
|
|
if f(event) {
|
|
|
|
filtered = append(filtered, event)
|
|
|
|
}
|
|
|
|
}
|
2020-11-05 22:50:17 +00:00
|
|
|
p.Items = filtered
|
|
|
|
return true
|
2020-10-05 16:38:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 22:50:17 +00:00
|
|
|
func (p *PayloadEvents) Len() int {
|
|
|
|
return len(p.Items)
|
2020-10-27 18:40:06 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 18:00:33 +00:00
|
|
|
// HasReadPermission filters the PayloadEvents to those which are authorized
|
|
|
|
// for reading by authz.
|
2020-11-05 22:57:25 +00:00
|
|
|
func (p *PayloadEvents) HasReadPermission(authz acl.Authorizer) bool {
|
|
|
|
return p.filter(func(event Event) bool {
|
|
|
|
return event.Payload.HasReadPermission(authz)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
// Subject is required to satisfy the Payload interface but is not implemented
|
|
|
|
// by PayloadEvents. PayloadEvents structs are constructed by Subscription.Next
|
|
|
|
// *after* Subject has been used to dispatch the enclosed events to the correct
|
|
|
|
// buffer.
|
|
|
|
func (PayloadEvents) Subject() Subject {
|
|
|
|
panic("PayloadEvents does not implement Subject")
|
|
|
|
}
|
|
|
|
|
2020-07-08 18:45:18 +00:00
|
|
|
// IsEndOfSnapshot returns true if this is a framing event that indicates the
|
2020-10-01 17:51:55 +00:00
|
|
|
// snapshot has completed. Subsequent events from Subscription.Next will be
|
|
|
|
// streamed as they occur.
|
2020-06-15 22:49:00 +00:00
|
|
|
func (e Event) IsEndOfSnapshot() bool {
|
2020-06-05 23:36:31 +00:00
|
|
|
return e.Payload == endOfSnapshot{}
|
|
|
|
}
|
|
|
|
|
2020-10-01 17:51:55 +00:00
|
|
|
// IsNewSnapshotToFollow returns true if this is a framing event that indicates
|
|
|
|
// that the clients view is stale, and must be reset. Subsequent events from
|
|
|
|
// Subscription.Next will be a new snapshot, followed by an EndOfSnapshot event.
|
|
|
|
func (e Event) IsNewSnapshotToFollow() bool {
|
|
|
|
return e.Payload == newSnapshotToFollow{}
|
2020-06-15 22:49:00 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 22:57:25 +00:00
|
|
|
type framingEvent struct{}
|
2020-06-15 22:49:00 +00:00
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
func (framingEvent) HasReadPermission(acl.Authorizer) bool {
|
2020-10-27 18:40:06 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
// Subject is required by the Payload interface but is not implemented by
|
|
|
|
// framing events, as they are typically *manually* appended to the correct
|
|
|
|
// buffer and do not need to be routed using a Subject.
|
|
|
|
func (framingEvent) Subject() Subject {
|
|
|
|
panic("framing events do not implement Subject")
|
2020-10-27 18:40:06 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 22:57:25 +00:00
|
|
|
type endOfSnapshot struct {
|
|
|
|
framingEvent
|
|
|
|
}
|
|
|
|
|
|
|
|
type newSnapshotToFollow struct {
|
|
|
|
framingEvent
|
|
|
|
}
|
|
|
|
|
2020-07-06 21:29:45 +00:00
|
|
|
type closeSubscriptionPayload struct {
|
|
|
|
tokensSecretIDs []string
|
2020-07-06 18:34:58 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
func (closeSubscriptionPayload) HasReadPermission(acl.Authorizer) bool {
|
2020-11-05 22:57:25 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-01-28 12:27:00 +00:00
|
|
|
// Subject is required by the Payload interface but it is not implemented by
|
|
|
|
// closeSubscriptionPayload, as this event type is handled separately and not
|
|
|
|
// actually appended to the buffer.
|
|
|
|
func (closeSubscriptionPayload) Subject() Subject {
|
|
|
|
panic("closeSubscriptionPayload does not implement Subject")
|
2020-10-27 18:40:06 +00:00
|
|
|
}
|
|
|
|
|
2020-07-06 21:29:45 +00:00
|
|
|
// NewCloseSubscriptionEvent returns a special Event that is handled by the
|
|
|
|
// stream package, and is never sent to subscribers. EventProcessor handles
|
|
|
|
// these events, and closes any subscriptions which were created using a token
|
|
|
|
// which matches any of the tokenSecretIDs.
|
2020-07-08 18:45:18 +00:00
|
|
|
//
|
|
|
|
// tokenSecretIDs may contain duplicate IDs.
|
2020-07-06 21:29:45 +00:00
|
|
|
func NewCloseSubscriptionEvent(tokenSecretIDs []string) Event {
|
|
|
|
return Event{Payload: closeSubscriptionPayload{tokensSecretIDs: tokenSecretIDs}}
|
2020-07-06 18:34:58 +00:00
|
|
|
}
|