open-nomad/nomad/structs/event.go
Drew Bailey 86080e25a9
Send events to EventSinks (#9171)
* Process to send events to configured sinks

This PR adds a SinkManager to a server which is responsible for managing
managed sinks. Managed sinks subscribe to the event broker and send
events to a sink writer (webhook). When changes to the eventstore are
made the sinkmanager and managed sink are responsible for reloading or
starting a new managed sink.

* periodically check in sink progress to raft

Save progress on the last successfully sent index to raft. This allows a
managed sink to resume close to where it left off in the event of a lost
server or leadership change

dereference eventsink so we can accurately use the watchch

When using a pointer to eventsink struct it was updated immediately and our reload logic would not trigger
2020-10-26 17:27:54 -04:00

161 lines
3.3 KiB
Go

package structs
import (
"fmt"
"net/url"
"strings"
multierror "github.com/hashicorp/go-multierror"
)
// EventStreamRequest is used to stream events from a servers EventBroker
type EventStreamRequest struct {
Topics map[Topic][]string
Index int
QueryOptions
}
type EventStreamWrapper struct {
Error *RpcError
Event *EventJson
}
type Topic string
const (
TopicDeployment Topic = "Deployment"
TopicEval Topic = "Eval"
TopicAlloc Topic = "Alloc"
TopicJob Topic = "Job"
TopicNode Topic = "Node"
TopicAll Topic = "*"
)
// Event represents a change in Nomads state.
type Event struct {
// Topic represeents the primary object for the event
Topic Topic
// Type is a short string representing the reason for the event
Type string
// Key is the primary identifier of the Event, The involved objects ID
Key string
// Namespace is the namespace of the object, If the object is not namespace
// aware (Node) it is left blank
Namespace string
// FilterKeys are a set of additional related keys that are used to include
// events during filtering.
FilterKeys []string
// Index is the raft index that corresponds to the event
Index uint64
// Payload is the Event itself see state/events.go for a list of events
Payload interface{}
}
// Events is a wrapper that contains a set of events for a given index.
type Events struct {
Index uint64
Events []Event
}
// EventJson is a wrapper for a JSON object
type EventJson struct {
Data []byte
}
func (j *EventJson) Copy() *EventJson {
n := new(EventJson)
*n = *j
n.Data = make([]byte, len(j.Data))
copy(n.Data, j.Data)
return n
}
type EventSinkProgressRequest struct {
Sinks []*EventSink
WriteRequest
}
type EventSinkUpsertRequest struct {
Sink *EventSink
WriteRequest
}
type EventSinkSpecificRequest struct {
ID string
QueryOptions
}
type EventSinkResponse struct {
Sink *EventSink
QueryMeta
}
type EventSinkDeleteRequest struct {
IDs []string
WriteRequest
}
type EventSinkListRequest struct {
QueryOptions
}
type EventSinkListResponse struct {
Sinks []*EventSink
QueryMeta
}
type SinkType string
const (
SinkWebhook SinkType = "webhook"
)
type EventSink struct {
ID string
Type SinkType
Topics map[Topic][]string
Address string
// LatestIndex is the latest reported index that was successfully sent.
// MangedSinks periodically check in to update the LatestIndex so that a
// minimal amount of events are resent when reestablishing an event sink
LatestIndex uint64
CreateIndex uint64
ModifyIndex uint64
}
func (e *EventSink) Validate() error {
var mErr multierror.Error
if e.ID == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Missing sink ID"))
} else if strings.Contains(e.ID, " ") {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Sink ID contains a space"))
} else if strings.Contains(e.ID, "\000") {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Sink ID contains a null character"))
}
switch e.Type {
case SinkWebhook:
if e.Address == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Webhook sink requires a valid Address"))
} else if _, err := url.Parse(e.Address); err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Webhook sink Address must be a valid url: %w", err))
}
default:
mErr.Errors = append(mErr.Errors, fmt.Errorf("Sink type invalid"))
}
return mErr.ErrorOrNil()
}