Documentation ¶
Index ¶
- Variables
- func ErrInvalidMetadataType(key string) error
- func ErrInvalidValueType(key string, value proto.Message) error
- func IsDownstreamResync(ctx context.Context) bool
- func IsFullResync(ctx context.Context) bool
- func IsNonBlockingTxn(ctx context.Context) bool
- func IsWithDescription(ctx context.Context) (description string, withDescription bool)
- func IsWithRetry(ctx context.Context) (period time.Duration, expBackoff, withRetry bool)
- func IsWithRevert(ctx context.Context) bool
- func WithDescription(ctx context.Context, description string) context.Context
- func WithDownstreamResync(ctx context.Context) context.Context
- func WithFullResync(ctx context.Context) context.Context
- func WithRetry(ctx context.Context, period time.Duration, expBackoff bool) context.Context
- func WithRevert(ctx context.Context) context.Context
- func WithoutBlocking(ctx context.Context) context.Context
- type Dependency
- type KVDescriptor
- type KVScheduler
- type KVWithMetadata
- type KeySelector
- type KeyValuePair
- type KeyWithError
- type Metadata
- type MetadataMapFactory
- type Txn
- type TxnOperation
- type ValueOrigin
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCombinedDownstreamResyncWithChange is returned when transaction combines downstream-resync with data changes. ErrCombinedDownstreamResyncWithChange = errors.New("downstream resync combined with data changes in one transaction") // ErrRevertNotSupportedWithResync is returned when transaction combines resync with revert. ErrRevertNotSupportedWithResync = errors.New("it is not supported to combine resync with revert") // ErrClosedScheduler is returned when scheduler is closed during transaction execution. ErrClosedScheduler = errors.New("scheduler was closed") // ErrTxnWaitCanceled is returned when waiting for result of blocking transaction is canceled. ErrTxnWaitCanceled = errors.New("waiting for result of blocking transaction was canceled") // ErrTxnQueueFull is returned when the queue of pending transactions is full. ErrTxnQueueFull = errors.New("transaction queue is full") // ErrUnregisteredValueType is returned for non-derived values whose proto.Message type // is not registered. ErrUnregisteredValueType = errors.New("protobuf message type is not registered") // ErrUnimplementedKey is returned for non-derived values without provided descriptor. ErrUnimplementedKey = errors.New("unimplemented key") // ErrUnimplementedAdd is returned when NB transaction attempts to Add value // for which there is a descriptor, but Add operation is not implemented. ErrUnimplementedAdd = errors.New("Add operation is not implemented") // ErrUnimplementedDelete is returned when NB transaction attempts to Delete value // for which there is a descriptor, but Delete operation is not implemented. ErrUnimplementedDelete = errors.New("Delete operation is not implemented") // ErrUnimplementedModify is returned when NB transaction attempts to Modify value // for which there is a descriptor, but Modify operation is not implemented. ErrUnimplementedModify = errors.New("Modify operation is not implemented") )
Functions ¶
func ErrInvalidMetadataType ¶
ErrInvalidMetadataType is returned to scheduler by auto-generated descriptor adapter when value metadata does not match expected type.
func ErrInvalidValueType ¶
ErrInvalidValueType is returned to scheduler by auto-generated descriptor adapter when value does not match expected type.
func IsDownstreamResync ¶
IsDownstreamResync returns true if the transaction context is configured to trigger downstream-resync.
func IsFullResync ¶
IsFullResync returns true if the transaction context is configured to trigger full-resync.
func IsNonBlockingTxn ¶
IsNonBlockingTxn returns true if transaction context is configured for non-blocking Commit.
func IsWithDescription ¶
IsWithDescription returns true if the transaction context is configured to include transaction description.
func IsWithRetry ¶
IsWithRetry returns true if transaction context is configured to allow retry, including the option parameters, or zero values if retry is not enabled.
func IsWithRevert ¶
IsWithRevert returns true if the transaction context is configured to revert transaction if any of its operations fails.
func WithDescription ¶
WithDescription prepares context for transaction that will have description provided. By default, transactions are without description.
func WithDownstreamResync ¶
WithDownstreamResync prepares context for transaction that will trigger resync between scheduler and SB - i.e. without NB providing up-to-date snapshot of key-value pairs, hence "downstream" reconciliation. Transaction is thus expected to carry no key-value pairs.
func WithFullResync ¶
WithFullResync prepares context for transaction carrying up-to-date *full* snapshot of NB key-value pairs that SB should be reconciled against. Such transaction should only carry non-NIL values - existing NB values not included in the transaction are automatically removed.
func WithRetry ¶
WithRetry prepares context for transaction for which the scheduler will retry any (retriable) failed operations after given <period>. If <expBackoff> is enabled, every failed retry will double the next delay. Can be combined with revert - even failed revert operations will be re-tried. By default, the scheduler will not automatically retry failed operations.
func WithRevert ¶
WithRevert prepares context for transaction that will be reverted if any of its operations fails. By default, the scheduler executes transactions in a best-effort mode - even in the case of an error it will keep the effects of successful operations.
Types ¶
type Dependency ¶
type Dependency struct { // Label should be a short human-readable string labeling the dependency. // Must be unique in the list of dependencies for a value. Label string // Key of another kv pair that the associated value depends on. // If empty, AnyOf must be defined instead. Key string // AnyOf, if not nil, must return true for at least one of the already added // keys for the dependency to be considered satisfied. // Either Key or AnyOf should be defined, but not both at the same time. // Note: AnyOf comes with more overhead than a static key dependency, // so prefer to use the latter whenever possible. AnyOf KeySelector }
Dependency references another kv pair that must exist before the associated value can be added.
type KVDescriptor ¶
type KVDescriptor struct { // Name of the descriptor unique across all registered descriptors. Name string // KeySelector selects keys described by this descriptor. KeySelector KeySelector // ValueTypeName defines name of the proto.Message type used to represent // described values. This attribute is mandatory, otherwise LazyValue-s // received from NB (e.g. datasync package) cannot be un-marshalled. // Note: proto Messages are registered against this type name in the generated // code using proto.RegisterType(). ValueTypeName string // KeyLabel can be *optionally* defined to provide a *shorter* value // identifier, that, unlike the original key, only needs to be unique in the // key scope of the descriptor and not necessarily in the entire key space. // If defined, key label will be used as value identifier in the metadata map // and in the non-verbose logs. KeyLabel func(key string) string // ValueComparator can be *optionally* provided to customize comparision // of values for equality. // Scheduler compares values to determine if Modify operation is really // needed. // For NB values, <oldValue> was either previously set by NB or dumped, // whereas <newValue> is a new value to be applied by NB. ValueComparator func(key string, oldValue, newValue proto.Message) bool // NBKeyPrefix is a key prefix that the scheduler should watch // in NB to receive all NB-values described by this descriptor. // The key space defined by NBKeyPrefix may cover more than KeySelector // selects - the scheduler will filter the received values and pass // to the descriptor only those that are really chosen by KeySelector. // The opposite may be true as well - KeySelector may select some extra // SB-only values, which the scheduler will not watch for in NB. Furthermore, // the keys may already be requested for watching by another descriptor // within the same plugin and in such case it is not needed to mention the // same prefix again. NBKeyPrefix string // WithMetadata tells scheduler whether to enable metadata - run-time, // descriptor-owned, scheduler-opaque, data carried alongside a created // (non-derived) value. // If enabled, the scheduler will maintain a map between key (-label, if // KeyLabel is defined) and the associated metadata. // If <WithMetadata> is false, metadata returned by Add will be ignored and // other methods will receive nil metadata. WithMetadata bool // MetadataMapFactory can be used to provide a customized map implementation // for value metadata, possibly extended with secondary lookups. // If not defined, the scheduler will use the bare NamedMapping from // the idxmap package. MetadataMapFactory MetadataMapFactory // Add new value handler. // For non-derived values, descriptor may return metadata to associate with // the value. // For derived values, Add+Delete+Modify are optional. Typically, properties // of base values are implemented as derived (often empty) values without // attached SB operations, used as targets for dependencies. Add func(key string, value proto.Message) (metadata Metadata, err error) // Delete value handler. // If Add is defined, Delete handler must be provided as well. Delete func(key string, value proto.Message, metadata Metadata) error // Modify value handler. // <newMetadata> can re-use the <oldMetadata>. // If Add is defined, Modify handler must be provided as well. Modify func(key string, oldValue, newValue proto.Message, oldMetadata Metadata) (newMetadata Metadata, err error) // ModifyWithRecreate can be defined to tell the scheduler if going from // <oldValue> to <newValue> requires the value to be completely re-created // with Delete+Add handlers. // If not defined, it is assumed that value can be always modified without // re-creation. ModifyWithRecreate func(key string, oldValue, newValue proto.Message, metadata Metadata) bool // Update value handler (optional for any value). // Update is called every time the "context" of the value changes - whenever // a dependency is modified or the set of dependencies changes without // preventing the existence of this value. Update func(key string, value proto.Message, metadata Metadata) error // IsRetriableFailure tells scheduler if the given error, returned by one // of Add/Delete/Modify/Update handlers, will always be returned for the // the same value (non-retriable) or if the value can be theoretically // fixed merely by repeating the operation. // If the callback is not defined, every error will be considered retriable. IsRetriableFailure func(err error) bool // Dependencies are keys that must already exist for the value to be added. // Conversely, if a dependency is to be removed, all values that depend on it // are deleted first and cached for a potential future re-creation. // Dependencies returned in the list are AND-ed. // The callback is optional - if not defined, the kv-pairs of the descriptor // are assumed to have no dependencies. Dependencies func(key string, value proto.Message) []Dependency // DerivedValues returns ("derived") values solely inferred from the current // state of this ("base") value. Derived values cannot be changed by NB // transaction. // While their state and existence is bound to the state of their base value, // they are allowed to have their own descriptors. // // Typically, derived value represents the base value's properties (that // other kv pairs may depend on), or extra actions taken when additional // dependencies are met, but otherwise not blocking the base // value from being added. // // The callback is optional - if not defined, there will be no values derived // from kv-pairs of the descriptor. DerivedValues func(key string, value proto.Message) []KeyValuePair // Dump should return all non-derived values described by this descriptor // that *really* exist in the southbound plane (and not what the current // scheduler's view of SB is). Derived value will get automatically created // using the method DerivedValues(). If some non-derived value doesn't // actually exist, it shouldn't be returned by DerivedValues() for the dumped // value! // <correlate> represents the non-derived values currently created // as viewed from the northbound/scheduler point of view: // -> startup resync: <correlate> = values received from NB to be applied // -> run-time/downstream resync: <correlate> = values applied according to the // in-memory kv-store (scheduler's view of SB) // // The callback is optional - if not defined, it is assumed that descriptor // is not able to dump the current SB state and thus refresh cannot be // performed for its kv-pairs. Dump func(correlate []KVWithMetadata) ([]KVWithMetadata, error) // DumpDependencies is a list of descriptors that have to be dumped // before this descriptor. Values already dumped are available for reading // via scheduler methods GetValue(), GetValues() and runtime data using // GetMetadataMap(). DumpDependencies []string /* descriptor name */ }
KVDescriptor teaches KVScheduler how to add/delete/modify/update & dump values under keys matched by KeySelector().
Every SB component should define one or more descriptors to cover all (non-property) keys under its management. The descriptor is what in essence gives meaning to individual key-value pairs. The list of available keys and their purpose should be described in the API of SB components so that NB plane can use them correctly. The scheduler does not care what Add/Delete/... methods do, it only needs to call the right callbacks at the right time.
Every key-value pair must have at most one descriptor associated with it. NB base value without descriptor is considered unimplemented and will never be added (can only be pushed from SB as already created/executed). On the other hand, derived value is allowed to have no descriptor associated with it. Typically, properties of base values are implemented as derived (often empty) values without attached SB operations, used as targets for dependencies.
type KVScheduler ¶
type KVScheduler interface { // RegisterKVDescriptor registers descriptor for a set of selected // keys. It should be called in the Init phase of agent plugins. // Every key-value pair must have at most one descriptor associated with it // (none for derived values expressing properties). RegisterKVDescriptor(descriptor *KVDescriptor) // GetRegisteredNBKeyPrefixes returns a list of key prefixes from NB with values // described by registered descriptors and therefore managed by the scheduler. GetRegisteredNBKeyPrefixes() []string // StartNBTransaction starts a new transaction from NB to SB plane. // The enqueued actions are scheduled for execution by Txn.Commit(). StartNBTransaction() Txn // TransactionBarrier ensures that all notifications received prior to the call // are associated with transactions that have already finalized. TransactionBarrier() // PushSBNotification notifies about a spontaneous value change in the SB // plane (i.e. not triggered by NB transaction). // // Pass <value> as nil if the value was removed, non-nil otherwise. // // Values pushed from SB do not trigger Add/Modify/Delete operations // on the descriptors - the change has already happened in SB - only // dependencies and derived values are updated. // // Values pushed from SB are overwritten by those created via NB transactions, // however. For example, notifications for values already created by NB // are ignored. But otherwise, SB values (not managed by NB) are untouched // by reconciliation or any other operation of the scheduler/descriptor. PushSBNotification(key string, value proto.Message, metadata Metadata) error // GetValue currently set for the given key. // The function can be used from within a transaction. However, if update // of A uses the value of B, then A should be marked as dependent on B // so that the scheduler can ensure that B is updated before A is. GetValue(key string) proto.Message // GetValues returns a set of values matched by the given selector. GetValues(selector KeySelector) []KeyValuePair // GetMetadataMap returns (read-only) map associating value label with value // metadata of a given descriptor. // Returns nil if the descriptor does not expose metadata. GetMetadataMap(descriptor string) idxmap.NamedMapping // GetPendingValues returns list of values (possibly filtered by selector) // waiting for their dependencies to be met. GetPendingValues(keySelector KeySelector) []KeyValuePair // GetFailedValues returns a list of keys (possibly filtered by selector) // whose (base) values are in a failed state (i.e. possibly not in the state as set // by the last transaction). GetFailedValues(keySelector KeySelector) []KeyWithError // SubscribeForErrors allows to get notified about all failed (Error!=nil) // and restored (Error==nil) values (possibly filtered using the selector). SubscribeForErrors(channel chan<- KeyWithError, keySelector KeySelector) }
KVScheduler synchronizes the *desired* system state described by northbound (NB) components via transactions with the *actual* state of the southbound (SB). The system state is represented as a set of inter-dependent key-value pairs that can be added, modified, deleted from within NB transactions or be notified about via notifications from the SB plane. The scheduling basically implements "state reconciliation" - periodically and on any change the scheduler attempts to update every value which has satisfied dependencies but is out-of-sync with the desired state given by NB.
For the scheduler, the key-value pairs are just abstract items that need to be managed in a synchronized fashion according to the described relations. It is up to the SB components to assign actual meaning to the individual values (via methods Add, Delete, Modify & Update of the KVDescriptor).
The idea behind scheduler is based on the Mediator pattern - SB components do not communicate directly, but instead interact through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling.
The values are described for scheduler by registered KVDescriptor-s. The scheduler learns two kinds of relations between values that have to be respected by the scheduling algorithm:
-> A depends on B: - A cannot exist without B - request to add A without B existing must be postponed by storing A into the cache of values with unmet dependencies (a.k.a. pending) - if B is to be removed and A exists, A must be removed first and cached in case B is restored in the future - Note: values pushed from SB are not checked for dependencies -> B is derived from A: - value B is not added directly (by NB or SB) but gets derived from base value A (using the DerivedValues() method of the base value's descriptor) - derived value exists only as long as its base does and gets removed (without caching) once the base value goes away - derived value may be described by a different descriptor than the base and usually represents property of the base value (that other values may depend on) or an extra action to be taken when additional dependencies are met.
Every key-value pair must have at most one descriptor associated with it. Base NB value without descriptor is considered unimplemented and will never be added (can only be pushed from SB as already created/executed). On the other hand, derived value is allowed to have no descriptor associated with it. Typically, properties of base values are implemented as derived (often empty) values without attached SB operations, used as targets for dependencies.
For descriptors the values are mutable objects - Add, Modify, Delete and Update method should reflect the value content without changing it. To add and maintain extra (runtime) attributes alongside the value, scheduler allows descriptors to append metadata - of any type - to each created non-derived Object value. Descriptor can also use the metadata to define secondary lookups, exposed via MetadataMap.
Advantages of the centralized scheduling are:
- easy to add new descriptors and dependencies
- decreases the likelihood of race conditions and deadlocks in systems with complex dependencies
- allows to write loosely-coupled SB components (mediator pattern)
- descriptor API will force new SB components to follow the same code structure which will make them easier to familiarize with
- NB components should never worry about dependencies between requests - it is taken care of by the scheduler
- single cache for all (not only pending) values (exposed via REST, easier to debug)
Apart from scheduling and execution, KVScheduler also offers the following features:
- collecting and counting present and past errors individually for every key
- retry for previously failed actions
- transaction reverting
- exposing history of actions, errors and pending values over the REST interface
- clearly describing the sequence of actions to be executed and postponed in the log file
- transaction execution tracing (using "runtime/trace" package)
- TBD: consider exposing the current config as a plotted graph (returned via REST) with values as nodes (colored to distinguish cached from added ones, derived from base, etc.) and dependencies as edges (unsatisfied marked with red color).
type KVWithMetadata ¶
type KVWithMetadata struct { Key string Value proto.Message Metadata Metadata Origin ValueOrigin }
KVWithMetadata encapsulates key-value pair with metadata and the origin mark.
type KeyValuePair ¶
type KeyValuePair struct { // Key identifies value. Key string // Value may represent some object, action or property. // // Value can be added either via northbound transaction (NB-value, // ValueOrigin = FromNB) or pushed (as already created) through SB notification // (SB-value, ValueOrigin = FromSB). Values from NB take priority as they // overwrite existing SB values (via Modify operation), whereas notifications // for existing NB values are ignored. For values returned by Dump with unknown // origin the scheduler reviews the value's history to determine where it came // from. // // For descriptors the values are mutable objects - Add, Modify, Delete and // Update methods should reflect the value content without changing it. // To add and maintain extra (runtime) attributes alongside the value, descriptor // can use the value metadata. Value proto.Message }
KeyValuePair groups key with value.
type KeyWithError ¶
type KeyWithError struct { Key string TxnOperation TxnOperation Error error }
KeyWithError stores error for a key whose value failed to get updated.
type Metadata ¶
type Metadata interface{}
Metadata are extra information carried alongside non-derived (base) value that descriptor may use for runtime attributes, secondary lookups, etc. This data are opaque for the scheduler and fully owned by the descriptor. Descriptor is supposed to create/edit (and use) metadata inside the Add, Modify, Update methods and return the latest state in the dump. Metadata, however, should not be used to determine the list of derived values and dependencies for a value - this needs to be fixed for a given value (Modify is effectively replace) and known even before the value is added.
The only way how scheduler can learn anything from metadata, is if MetadataMap is enabled by descriptor (using WithMetadata method) and a custom NamedMapping implementation is provided that defines secondary indexes (over metadata). The scheduler exposes the current snapshot of secondary indexes, but otherwise is not familiar with their semantics.
type MetadataMapFactory ¶
type MetadataMapFactory func() idxmap.NamedMappingRW
MetadataMapFactory can be used by descriptor to define a custom map associating value labels with value metadata, potentially extending the basic in-memory implementation (memNamedMapping) with secondary indexes, type-safe watch, etc. If metadata are enabled (by WithMetadata method), the scheduler will create an instance of the map using the provided factory during the descriptor registration (RegisterKVDescriptor). Immediately afterwards, the mapping is available read-only via scheduler's method GetMetadataMap. The returned map can be then casted to the customized implementation, but it should remain read-only (i.e. define read-only interface for the customized implementation).
type Txn ¶
type Txn interface { // SetValue changes (non-derived) lazy value - un-marshalled during // transaction pre-processing using ValueTypeName given by descriptor. // If <value> is nil, the value will get deleted. SetValue(key string, value datasync.LazyValue) Txn // Commit orders scheduler to execute enqueued operations. // Operations with unmet dependencies will get postponed and possibly // executed later. // <ctx> allows to pass transaction options (see With* functions from // txn_options.go) or to cancel waiting for the end of a blocking transaction. // <txnError> covers validity of the transaction and the preparedness // of the scheduler to execute it. // <kvErrors> are related to operations from this transaction that // could be immediately executed or from previous transactions that have // got their dependencies satisfied by this txn. // Non-blocking transactions return immediately and always without errors. // Subscribe with KVScheduler.SubscribeForErrors() to get notified about all // errors, including those returned by action triggered later or asynchronously // by a SB notification. Commit(ctx context.Context) (kvErrors []KeyWithError, txnError error) }
Txn represent a single transaction. Scheduler starts to plan and execute actions only after Commit is called.
type TxnOperation ¶
type TxnOperation int
TxnOperation is one of: Pre-process, Add, Modify, Delete and Update.
const ( // UndefinedTxnOp represents undefined transaction operation. UndefinedTxnOp TxnOperation = iota // PreProcess key-value pair. PreProcess // Add new value. Add // Modify existing value. Modify // Delete existing value. Delete // Update (reflect modified dependencies) existing value. Update )
func (TxnOperation) String ¶
func (txnOpType TxnOperation) String() string
String returns human-readable string representation of transaction operation.
type ValueOrigin ¶
type ValueOrigin int
ValueOrigin is one of: FromNB, FromSB, UnknownOrigin.
const ( // UnknownOrigin is returned by Dump for a value when it cannot be determine // if the value was previously created by NB or not. // Scheduler will then look into its history to find out if the value was // ever managed by NB to determine the origin heuristically. UnknownOrigin ValueOrigin = iota // FromNB marks value created via NB transaction. FromNB // FromSB marks value not managed by NB - i.e. created automatically or // externally in SB. FromSB )
func (ValueOrigin) String ¶
func (vo ValueOrigin) String() string
String converts ValueOrigin to string.