tailetc

package module
v0.0.0-...-b2fa539 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2021 License: BSD-3-Clause Imports: 21 Imported by: 1

Documentation

Overview

Package tailetc implements an total-memory-cache etcd v3 client implemented by tailing (watching) an entire etcd.

The client maintains a complete copy of the etcd database in memory, with values decoded into Go objects.

Transaction Model

It presents a simplified transaction model that assumes that multiple writers will not be contending on keys. When you call tx.Get or tx.Put, your Tx records the current etcd global revision. When you tx.Commit, if some newer revision of any key touched by Tx is in etcd then the commit will fail.

Contention failures are reported as ErrTxStale from tx.Commit. Failures may be reported sooner as tx.Get or tx.Put errors, but the tx error is sticky, that is, if you ignore those errors the eventual error from tx.Commit will have ErrTxStale in its error chain.

The Tx.Commit method waits before successfully returning until DB has caught up with the etcd global revision of the transaction. This ensures that sequential happen in the strict database sequence. So if you serve a REST API from a single *etcd.DB instance, then the API will behave as users expect it to.

If you know only one thing about this etcd client, know this:

Do not have writer contention on individual keys.

Everything else should follow a programmer's intuition for an in-memory map guarded by a RWMutex.

Caching

We take advantage of etcd's watch model to maintain a *decoded* value cache of the entire database in memory. This means that querying a value is extremely cheap. Issuing tx.Get(key) involves no more work than holding a mutex read lock, reading from a map, and cloning the value.

The etcd watch is then exposed to the user of *etcd.DB via the WatchFunc. This lets users maintain in-memory higher-level indexes into the etcd database that respond to external commits. WatchFunc is called while the database lock is held, so a WatchFunc implementation cannot synchronously issue transactions.

Object Ownership

The cache in etcd.DB is very careful not to copy objects both into and out of the cache so that users of the etcd.DB cannot get pointers directly into the memory inside the cache. This means over-copying. Users can control precisely how much copying is done, and how, by providing a CloneFunc implementation.

The general ownership semantics are: objects are copied as soon as they are passed to etcd, and any memory returned by etcd is owned by the caller.

Index

Constants

This section is empty.

Variables

View Source
var ErrTxClosed = errors.New("tx closed")

ErrTxClosed is reported when a method is called on a committed or canceled Tx.

View Source
var ErrTxStale = errors.New("tx stale")

ErrTxStale is reported when another transaction has modified a key referenced by this transaction, so it can no longer be applied to the database.

Functions

This section is empty.

Types

type DB

type DB struct {

	// Mu is the database lock.
	//
	// Reads are guarded by read locks.
	// Transaction commits and background watch updates
	// are guarded by write lcoks.
	//
	// The mutex is exported so that higher-level wrappers that want to
	// keep indexes in sync with background updates without problematic
	// lock ordering.
	Mu sync.RWMutex
	// contains filtered or unexported fields
}

DB is a read-write datastore backed by etcd.

func New

func New(ctx context.Context, urls string, opts Options) (db *DB, err error)

New loads the contents of an etcd prefix range and creates a *DB for reading and writing from the prefix range.

The urls parameter is a comma-separated list of etcd HTTP endpoint, e.g. "http://1.1.1.1:2379,http://2.2.2.2:2379".

There are two special case values of urls:

If urls is "memory://", the DB does not connect to any etcd server, instead all operations are performed on the in-memory cache.

If urls starts with the prefix "file://" then an embedded copy of etcd is started and creates the database in a "tailscale.etcd" directory under the referenced path. For example, the urls value "file:///data" uses an etcd database stored in "/data/tailscale.etcd". If only the prefix is provided, that is urls equals "file://", then an embedded etcd is started in the current working directory.

func (*DB) Close

func (db *DB) Close() error

Close cancels all transactions and releases all DB resources.

func (*DB) GetRange

func (db *DB) GetRange(keyPrefix string, fn func([]KV) error, finalFn func()) (err error)

GetRange gets a range of KV-pairs from the etcd cache.

The parameter fn is called with batches of matching KV-pairs. The passed slice and all the memory it references is owned by fn. If fn returns an error then GetRange aborts early and returns the error.

While fn is called GetRange holds either the DB read or write lock, so no transactions can be committed from inside the fn callback.

It is possible for the same key to be sent to fn more than once in a GetRange call. If this happens, the later key-value pair is a newer version that replaces the old value.

When all keys have been read, finalFn is called holding the DB write lock. This gives the caller a chance to do something knowing that no key updates can happen between reading the range and executing finalFn.

func (*DB) PanicOnWrite

func (db *DB) PanicOnWrite(enable bool)

PanicOnWrite sets whether the db should panic when a write is requested. It is used in tests to ensure that particular actions do not create db writes. Calls to PanicOnWrite may be nested.

func (*DB) ReadTx

func (db *DB) ReadTx() *Tx

ReadTx create a new read-only transaction.

func (*DB) Tx

func (db *DB) Tx(ctx context.Context) *Tx

Tx creates a new database transaction.

func (*DB) UnsafeClient

func (db *DB) UnsafeClient() *clientv3.Client

UnsafeClient exposes the raw underlying etcd client used by the database. Use with extreme care.

func (*DB) WatchKey

func (db *DB) WatchKey(ctx context.Context, key string, fn func(old, new interface{})) error

WatchKey registers fn to be called every time a change is made to a key.

If there is an existing value of the key it is played through fn before WatchKey returns.

The fn function is called holding either the read or write lock. Do not do any operation in fn that tries to take the etcd read or write lock.

The watch is de-registered when ctx is Done.

func (*DB) WatchPrefix

func (db *DB) WatchPrefix(ctx context.Context, keyPrefix string, fn func(kvs []KV)) error

WatchPrefix registers fn to be called every time a change is made to a key-value with the prefix keyPrefix.

All existing key-values matching keyPrefix are played through fn before WatchPrefix returns.

The fn function is called holding either the read or write lock. Do not do any operation in fn that tries to take the etcd read or write lock.

The watch is de-registered when ctx is Done.

The data structure storing prefix watchers is relatively inefficient at present, so adding large numbers of prefix watchers is expensive.

type KV

type KV struct {
	Key      string
	OldValue interface{} // nil if there is no old value
	Value    interface{} // nil if the key has been deleted
}

KV is a value change for an etcd key. Both the old value being replaced and the new value are provided, decoded.

type Options

type Options struct {
	Logf  func(format string, args ...interface{})
	HTTPC *http.Client
	// KeyPrefix is a prefix on all etcd keys accessed through this client.
	// The value "" means "/", because etcd keys are file-system-like.
	KeyPrefix string
	// AuthHeader is passed as the "Authorization" header to etcd.
	AuthHeader string
	// EncodeFunc encodes values for storage.
	// If nil, the default encoder produces []byte.
	EncodeFunc func(key string, value interface{}) ([]byte, error)
	// DecodeFunc decodes values from storage.
	// If nil, the default decoder produces []byte.
	DecodeFunc func(key string, data []byte) (interface{}, error)
	// CloneFunc clones src into dst with no aliased mutable memory.
	// The definition of "aliased mutable memory" is left to the user.
	// For example, if a user is certain that no values ever passed to or
	// read from the etcd package are ever modified, use a no-op CloneFunc.
	// If nil, the default requires all values to be []byte.
	CloneFunc func(dst interface{}, key string, src interface{}) error
	// WatchFunc is called when key-value pairs change in the DB.
	//
	// When the update is a Tx from this DB, WatchFunc is called after
	// the transaction has been successfully applied by the etcd server
	// but before the Commit method returns.
	//
	// The DB.Mu write lock is held for the call, so no transactions
	// can be issued from inside WatchFunc.
	//
	// Entire etcd transactions are single calls to WatchFunc.
	//
	// The called WatchFunc owns the values passed to it.
	WatchFunc func([]KV)
	// DeleteAllOnStart deletes all keys when the client is created.
	// Used for testing.
	DeleteAllOnStart bool
}

Options are optional settings for a DB.

If one of EncodeFunc, DecodeFunc, and CloneFunc are set they must all be set.

type Tx

type Tx struct {
	// PendingUpdate, if not nil, is called on each Put.
	// It can be used by higher-level objects to keep an index
	// up-to-date on a transaction state.
	//
	// The memory passed to PendingUpdate are only valid for the
	// duration of the call and must not be modified.
	//
	// A nil new value means the key has been deleted.
	PendingUpdate func(key string, old, new interface{})

	// Err is any error reported by the transaction during use.
	// This can be set used externally to ensure Commit does not fire.
	//
	// Once Err is set all future calls to Tx methods will return Err.
	// On Commit, if Err is not set, it is set to ErrTxClosed
	Err error
	// contains filtered or unexported fields
}

A Tx is an etcd transaction.

A Tx holds no resources besides some private memory, so there is no notion of closing a transaction or rolling back a transaction. A cheap etcd read can be done with:

found, err := db.ReadTx().Get(key, &val)

Tx is not safe for concurrent access. For concurrency, create more transactions.

func (*Tx) Commit

func (tx *Tx) Commit() (err error)

Commit commits the transaction to etcd. It is an error to call Commit on a read-only transaction.

func (*Tx) Get

func (tx *Tx) Get(key string, value interface{}) (found bool, err error)

Get retrieves a key-value from the etcd cache into value.

The value must be a pointer to the decoded type of the key, or nil. The caller owns the returned value.

No network events are generated.

The first call to Get in a Tx will pin the global revision number to the current etcd revision. If a subsequent Get finds a value with a greater revision then get will return ErrTxStale. This ensures that a Tx has a consistent view of the values it fetches.

func (*Tx) Put

func (tx *Tx) Put(key string, value interface{}) error

Put adds or replaces a KV-pair in the transaction. If a newer value for the key is in the DB this will return ErrTxStale. A nil value deletes the key.

func (*Tx) UnsafePeek

func (tx *Tx) UnsafePeek(key string, peekFunc func(v interface{})) (found bool, err error)

UnsafePeek lets the caller see the cached value for a key.

It is vital that the caller does not modify the value, or the DB will be corrupted.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL