github.com/portworx/kvdb@v0.0.0-20241107215734-a185a966f535/kvdb.go (about)

     1  package kvdb
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	_ "github.com/golang/mock/mockgen/model"
     8  	"github.com/sirupsen/logrus"
     9  )
    10  
    11  const (
    12  	// KVSet signifies the KV was modified.
    13  	KVSet KVAction = 1 << iota
    14  	// KVCreate set if the KV pair was created.
    15  	KVCreate
    16  	// KVGet set when the key is fetched from the KV store
    17  	KVGet
    18  	// KVDelete set when the key is deleted from the KV store
    19  	KVDelete
    20  	// KVExpire set when the key expires
    21  	KVExpire
    22  	// KVUknown operation on KV pair
    23  	KVUknown
    24  )
    25  
    26  const (
    27  	// KVCapabilityOrderedUpdates support requires watch to send an watch update
    28  	// for every put - instead of coalescing multiple puts in one update.
    29  	KVCapabilityOrderedUpdates = 1 << iota
    30  )
    31  
    32  const (
    33  	// KVPrevExists flag to check key already exists
    34  	KVPrevExists KVFlags = 1 << iota
    35  	// KVCreatedIndex flag compares with passed in index (possibly in KVPair)
    36  	KVCreatedIndex
    37  	// KVModifiedIndex flag compares with passed in index (possibly in KVPair)
    38  	KVModifiedIndex
    39  	// KVTTL uses TTL val from KVPair.
    40  	KVTTL
    41  )
    42  
    43  const (
    44  	// ReadPermission for read only access
    45  	ReadPermission = iota
    46  	// WritePermission for write only access
    47  	WritePermission
    48  	// ReadWritePermission for read-write access
    49  	ReadWritePermission
    50  )
    51  const (
    52  	// UsernameKey for an authenticated kvdb endpoint
    53  	UsernameKey = "Username"
    54  	// PasswordKey for an authenticated kvdb endpoint
    55  	PasswordKey = "Password"
    56  	// CAFileKey is the CA file path for an authenticated kvdb endpoint
    57  	CAFileKey = "CAFile"
    58  	// CertFileKey is the certificate file path for an authenticated kvdb endpoint
    59  	CertFileKey = "CertFile"
    60  	// CertKeyFileKey is the key to the certificate
    61  	CertKeyFileKey = "CertKeyFile"
    62  	// TrustedCAFileKey is the key for the trusted CA.
    63  	TrustedCAFileKey = "TrustedCAFile"
    64  	// ClientCertAuthKey is the boolean value indicating client authenticated certificate.
    65  	ClientCertAuthKey = "ClientCertAuth"
    66  	// RetryCountKey is the integer value indicating the retry count of etcd operations
    67  	RetryCountKey = "RetryCount"
    68  	// ACLTokenKey is the token value for ACL based KV stores
    69  	ACLTokenKey = "ACLToken"
    70  	// CAAuthAddress is the address of CA signing authority (required in consul TLS config)
    71  	CAAuthAddress = "CAAuthAddress"
    72  	// InsecureSkipVerify has a value true or false (required in consul TLS config)
    73  	InsecureSkipVerify = "InsecureSkipVerify"
    74  	// TransportScheme points to http transport being either http or https.
    75  	TransportScheme = "TransportScheme"
    76  	// LogPathOption is the name of the option which specified the log path location
    77  	LogPathOption = "LogPathOption"
    78  )
    79  
    80  // List of kvdb endpoints supported versions
    81  const (
    82  	// ConsulVersion1 key
    83  	ConsulVersion1 = "consulv1"
    84  	// EtcdBaseVersion key
    85  	EtcdBaseVersion = "etcd"
    86  	// EtcdVersion3 key
    87  	EtcdVersion3 = "etcdv3"
    88  	// MemVersion1 key
    89  	MemVersion1 = "memv1"
    90  	// BoltVersion1 key
    91  	BoltVersion1 = "boltv1"
    92  	// ZookeeperVersion1 key
    93  	ZookeeperVersion1 = "zookeeperv1"
    94  )
    95  
    96  const (
    97  	// DefaultLockTryDuration is the maximum time spent trying to acquire lock
    98  	DefaultLockTryDuration = 300 * time.Second
    99  	// DefaultSeparator separate key components
   100  	DefaultSeparator = "/"
   101  )
   102  
   103  type WrapperName string
   104  
   105  const (
   106  	Wrapper_None     = WrapperName("Wrapper_None")
   107  	Wrapper_Log      = WrapperName("Wrapper_Log")
   108  	Wrapper_NoQuorum = WrapperName("Wrapper_NoQuorum")
   109  )
   110  
   111  type KvdbWrapper interface {
   112  	// WrapperName is the name of this wrapper
   113  	WrapperName() WrapperName
   114  	// WrappedKvdb is the Kvdb wrapped by this wrapper
   115  	WrappedKvdb() Kvdb
   116  	// Removed is called when wrapper is removed
   117  	Removed()
   118  	// WrappedKvdb is the Kvdb wrapped by this wrapper
   119  	SetWrappedKvdb(kvdb Kvdb) error
   120  }
   121  
   122  var (
   123  	// ErrNotSupported implemenation of a specific function is not supported.
   124  	ErrNotSupported = errors.New("implementation not supported")
   125  	// ErrWatchStopped is raised when user stops watch.
   126  	ErrWatchStopped = errors.New("Watch Stopped")
   127  	// ErrNotFound raised if Key is not found
   128  	ErrNotFound = errors.New("Key not found")
   129  	// ErrExist raised if key already exists
   130  	ErrExist = errors.New("Key already exists")
   131  	// ErrUnmarshal raised if Get fails to unmarshal value.
   132  	ErrUnmarshal = errors.New("Failed to unmarshal value")
   133  	// ErrIllegal raised if object is not valid.
   134  	ErrIllegal = errors.New("Illegal operation")
   135  	// ErrValueMismatch raised if existing KVDB value mismatches with user provided value
   136  	ErrValueMismatch = errors.New("Value mismatch")
   137  	// ErrEmptyValue raised if the value is empty
   138  	ErrEmptyValue = errors.New("Value cannot be empty")
   139  	// ErrModified raised during an atomic operation if the index does not match the one in the store
   140  	ErrModified = errors.New("Key Index mismatch")
   141  	// ErrSetTTLFailed raised if unable to set ttl value for a key create/put/update action
   142  	ErrSetTTLFailed = errors.New("Unable to set ttl value")
   143  	// ErrTTLNotSupported if kvdb implementation doesn't support TTL
   144  	ErrTTLNotSupported = errors.New("TTL value not supported")
   145  	// ErrInvalidLock Lock and unlock operations don't match.
   146  	ErrInvalidLock = errors.New("Invalid lock/unlock operation")
   147  	// ErrNoPassword provided
   148  	ErrNoPassword = errors.New("Username provided without any password")
   149  	// ErrAuthNotSupported authentication not supported for this kvdb implementation
   150  	ErrAuthNotSupported = errors.New("Kvdb authentication not supported")
   151  	// ErrNoCertificate no certificate provided for authentication
   152  	ErrNoCertificate = errors.New("Certificate File Path not provided")
   153  	// ErrUnknownPermission raised if unknown permission type
   154  	ErrUnknownPermission = errors.New("Unknown Permission Type")
   155  	// ErrMemberDoesNotExist returned when an operation fails for a member
   156  	// which does not exist
   157  	ErrMemberDoesNotExist = errors.New("Kvdb member does not exist")
   158  	// ErrWatchRevisionCompacted requested watch version has been compacted
   159  	ErrWatchRevisionCompacted = errors.New("Kvdb watch revision compacted")
   160  	// ErrLockRefreshFailed could not refresh lock key so exclusive access to lock may be lost
   161  	ErrLockRefreshFailed = errors.New("Failed to refresh lock")
   162  	// ErrLockHoldTimeoutTriggered triggers if lock is held beyond configured timeout
   163  	ErrLockHoldTimeoutTriggered = errors.New("Lock held beyond configured timeout")
   164  	// ErrNoConnection no connection to server
   165  	ErrNoConnection = errors.New("No server connection")
   166  	// ErrNoQuorum kvdb has lost quorum
   167  	ErrNoQuorum = errors.New("KVDB connection failed, either node has " +
   168  		"networking issues or KVDB members are down or KVDB cluster is unhealthy. " +
   169  		"All operations (get/update/delete) are unavailable.")
   170  )
   171  
   172  // KVAction specifies the action on a KV pair. This is useful to make decisions
   173  // from the results of  a Watch.
   174  type KVAction int
   175  
   176  // KVFlags options for operations on KVDB
   177  type KVFlags uint64
   178  
   179  // PermissionType for user access
   180  type PermissionType int
   181  
   182  // WatchCB is called when a watched key or tree is modified. If the callback
   183  // returns an error, then watch stops and the cb is called one last time
   184  // with ErrWatchStopped.
   185  type WatchCB func(prefix string, opaque interface{}, kvp *KVPair, err error) error
   186  
   187  // FatalErrorCB callback is invoked incase of fatal errors
   188  type FatalErrorCB func(err error, format string, args ...interface{})
   189  
   190  // DatastoreInit is called to activate a backend KV store.
   191  type DatastoreInit func(domain string, machines []string, options map[string]string,
   192  	cb FatalErrorCB) (Kvdb, error)
   193  
   194  // DatastoreVersion is called to get the version of a backend KV store
   195  type DatastoreVersion func(url string, kvdbOptions map[string]string) (string, error)
   196  
   197  // WrapperInit is called to activate a backend KV store.
   198  type WrapperInit func(kv Kvdb, options map[string]string) (Kvdb, error)
   199  
   200  // EnumerateSelect function is a callback function provided to EnumerateWithSelect API
   201  // This fn is executed over all the keys and only those values are returned by Enumerate for which
   202  // this function return true.
   203  type EnumerateSelect func(val interface{}) bool
   204  
   205  // CopySelect function is a callback function provided to EnumerateWithSelect API
   206  // This fn should perform a deep copy of the input interface and return the copy
   207  type CopySelect func(val interface{}) interface{}
   208  
   209  // EnumerateKVPSelect function is a callback function provided to EnumerateKVPWithSelect API
   210  // This fn is executed over all the keys and only those values are returned by Enumerate for which
   211  // this function return true.
   212  type EnumerateKVPSelect func(kvp *KVPair, val interface{}) bool
   213  
   214  // CopyKVPSelect function is a callback function provided to EnumerateKVPWithSelect API
   215  // This fn should perform a deep copy of the input KVPair and return the copy
   216  type CopyKVPSelect func(kvp *KVPair, val interface{}) *KVPair
   217  
   218  // KVPair represents the results of an operation on KVDB.
   219  type KVPair struct {
   220  	// Key for this kv pair.
   221  	Key string
   222  	// Value for this kv pair
   223  	Value []byte
   224  	// Action the last action on this KVPair.
   225  	Action KVAction
   226  	// TTL value after which this key will expire from KVDB
   227  	TTL int64
   228  	// KVDBIndex A Monotonically index updated at each modification operation.
   229  	KVDBIndex uint64
   230  	// CreatedIndex for this kv pair
   231  	CreatedIndex uint64
   232  	// ModifiedIndex for this kv pair
   233  	ModifiedIndex uint64
   234  	// Lock is a generic interface to represent a lock held on a key.
   235  	Lock interface{}
   236  }
   237  
   238  // KVPairs list of KVPairs
   239  type KVPairs []*KVPair
   240  
   241  // Tx Interface to transactionally apply updates to a set of keys.
   242  type Tx interface {
   243  	// Put specified key value pair in TX.
   244  	Put(key string, value interface{}, ttl uint64) (*KVPair, error)
   245  	// Get returns KVPair in this TXs view. If not found, returns value from
   246  	// backing KVDB.
   247  	Get(key string) (*KVPair, error)
   248  	// Get same as get except that value has the unmarshalled value.
   249  	GetVal(key string, value interface{}) (*KVPair, error)
   250  	// Prepare returns an error it transaction cannot be logged.
   251  	Prepare() error
   252  	// Commit propagates updates to the KVDB. No operations on this Tx are
   253  	// allowed after commit.
   254  	Commit() error
   255  	// Abort aborts this transaction.  No operations on this Tx are allowed
   256  	// afer commit.
   257  	Abort() error
   258  }
   259  
   260  // Kvdb interface implemented by backing datastores.
   261  //
   262  //go:generate mockgen -package=mock -destination=mock/mock_kvdb.go . Kvdb
   263  type Kvdb interface {
   264  	Controller
   265  	// String representation of backend datastore.
   266  	String() string
   267  	// Capbilities - see KVCapabilityXXX
   268  	Capabilities() int
   269  	// Get returns KVPair that maps to specified key or ErrNotFound.
   270  	Get(key string) (*KVPair, error)
   271  	// Get returns KVPair that maps to specified key or ErrNotFound. If found
   272  	// value contains the unmarshalled result or error is ErrUnmarshal
   273  	GetVal(key string, value interface{}) (*KVPair, error)
   274  	// GetWithCopy returns a copy of the value as an interface for the specified key
   275  	GetWithCopy(key string, copySelect CopySelect) (interface{}, error)
   276  	// Put inserts value at key in kvdb. If value is a runtime.Object, it is
   277  	// marshalled. If Value is []byte it is set directly. If Value is a string,
   278  	// its byte representation is stored.
   279  	Put(key string, value interface{}, ttl uint64) (*KVPair, error)
   280  	// Create is the same as Put except that ErrExist is returned if the key exists.
   281  	Create(key string, value interface{}, ttl uint64) (*KVPair, error)
   282  	// Update is the same as Put except that ErrNotFound is returned if the key
   283  	// does not exist.
   284  	Update(key string, value interface{}, ttl uint64) (*KVPair, error)
   285  	// Enumerate returns a list of KVPair for all keys that share the specified prefix.
   286  	Enumerate(prefix string) (KVPairs, error)
   287  	// EnumerateWithSelect returns a copy of all values under the prefix that satisfy the select
   288  	// function in the provided output array of interfaces
   289  	EnumerateWithSelect(prefix string, enumerateSelect EnumerateSelect, copySelect CopySelect) ([]interface{}, error)
   290  	// EnumerateKVPWithSelect returns a copy of all the KVPairs under the prefix that satisfy the select
   291  	// function in the provided output array of key-value pairs
   292  	EnumerateKVPWithSelect(prefix string, enumerateSelect EnumerateKVPSelect, copySelect CopyKVPSelect) (KVPairs, error)
   293  	// Delete deletes the KVPair specified by the key. ErrNotFound is returned
   294  	// if the key is not found. The old KVPair is returned if successful.
   295  	Delete(key string) (*KVPair, error)
   296  	// DeleteTree same as Delete execpt that all keys sharing the prefix are
   297  	// deleted.
   298  	DeleteTree(prefix string) error
   299  	// Keys returns an array of keys that share specified prefix (ie. "1st level directory").
   300  	// sep parameter defines a key-separator, and if not provided the "/" is assumed.
   301  	Keys(prefix, sep string) ([]string, error)
   302  	// CompareAndSet updates value at kvp.Key if the previous resident
   303  	// satisfies conditions set in flags and optional prevValue.
   304  	CompareAndSet(kvp *KVPair, flags KVFlags, prevValue []byte) (*KVPair, error)
   305  	// CompareAndDelete deletes value at kvp.Key if the previous resident matches
   306  	// satisfies conditions set in flags.
   307  	CompareAndDelete(kvp *KVPair, flags KVFlags) (*KVPair, error)
   308  	// WatchKey calls watchCB everytime a value at key is updated. waitIndex
   309  	// is the oldest ModifiedIndex of a KVPair for which updates are requestd.
   310  	WatchKey(key string, waitIndex uint64, opaque interface{}, watchCB WatchCB) error
   311  	// WatchTree is the same as WatchKey except that watchCB is triggered
   312  	// for updates on all keys that share the prefix.
   313  	WatchTree(prefix string, waitIndex uint64, opaque interface{}, watchCB WatchCB) error
   314  	// Snapshot returns a kvdb snapshot of the provided list of prefixes and the last updated index.
   315  	// If no prefixes are provided, then the whole kvdb tree is snapshotted and could be potentially an expensive operation
   316  	// If consistent is true, then snapshot is going to return all the updates happening during the snapshot operation and the last
   317  	// updated index from the snapshot
   318  	Snapshot(prefixes []string, consistent bool) (Kvdb, uint64, error)
   319  	// SnapPut records the key value pair including the index.
   320  	SnapPut(kvp *KVPair) (*KVPair, error)
   321  	// LockWithID locks the specified key and associates a lockerID with it, probably to identify
   322  	// who acquired the lock. The KVPair returned should be used to unlock.
   323  	LockWithID(key string, lockerID string) (*KVPair, error)
   324  	// Lock locks the specified key. The KVPair returned should be used to unlock.
   325  	Lock(key string) (*KVPair, error)
   326  	// LockWithTimeout locks with specified key and associates a lockerID with it.
   327  	// lockTryDuration is the maximum time that can be spent trying to acquire
   328  	// lock, else return error.
   329  	// lockHoldDuration is the maximum time the lock can be held, after which
   330  	// FatalCb is invoked.
   331  	// The KVPair returned should be used to unlock if successful.
   332  	LockWithTimeout(key string, lockerID string, lockTryDuration time.Duration,
   333  		lockHoldDuration time.Duration) (*KVPair, error)
   334  	// IsKeyLocked returns a boolean if the lock is held or not. If held, returns the owner.
   335  	IsKeyLocked(key string) (bool, string, error)
   336  	// Unlock kvp previously acquired through a call to lock.
   337  	Unlock(kvp *KVPair) error
   338  	// TxNew returns a new Tx coordinator object or ErrNotSupported
   339  	TxNew() (Tx, error)
   340  	// AddUser adds a new user to kvdb
   341  	AddUser(username string, password string) error
   342  	// RemoveUser removes a user from kvdb
   343  	RemoveUser(username string) error
   344  	// GrantUserAccess grants user access to a subtree/prefix based on the permission
   345  	GrantUserAccess(username string, permType PermissionType, subtree string) error
   346  	// RevokeUsersAccess revokes user's access to a subtree/prefix based on the permission
   347  	RevokeUsersAccess(username string, permType PermissionType, subtree string) error
   348  	// SetFatalCb sets the function to be called in case of fatal errors
   349  	SetFatalCb(f FatalErrorCB)
   350  	// SetLockHoldDuration sets maximum time a lock may be held
   351  	SetLockHoldDuration(timeout time.Duration)
   352  	// GetLockTryDuration gets the maximum time to attempt to get a lock.
   353  	GetLockTryDuration() time.Duration
   354  	// GetLockHoldDuration gets the currently set lock hold timeout
   355  	GetLockHoldDuration() time.Duration
   356  	// Serialize serializes all the keys under the domain and returns a byte array
   357  	Serialize() ([]byte, error)
   358  	// Deserialize deserializes the given byte array into a list of kv pairs
   359  	Deserialize([]byte) (KVPairs, error)
   360  	// Compact removes the history before the specified index/revision to reduce the space and memory usage
   361  	Compact(index uint64) error
   362  	KvdbWrapper
   363  }
   364  
   365  // ReplayCb provides info required for replay
   366  type ReplayCb struct {
   367  	// Prefix is the watch key/tree prefix
   368  	Prefix string
   369  	// WaitIndex is the index after which updates must be returned
   370  	WaitIndex uint64
   371  	// Opaque is a hint returned by the caller
   372  	Opaque interface{}
   373  	// WatchCB is the watch callback
   374  	WatchCB WatchCB
   375  }
   376  
   377  // UpdatesCollector collects updates from kvdb.
   378  type UpdatesCollector interface {
   379  	// Stop collecting updates
   380  	Stop()
   381  	// ReplayUpdates replays the collected updates.
   382  	// Returns the version until the replay's were done
   383  	// and any errors it encountered.
   384  	ReplayUpdates(updateCb []ReplayCb) (uint64, error)
   385  }
   386  
   387  // NewUpdatesCollector creates new Kvdb collector that collects updates
   388  // starting at startIndex + 1 index.
   389  func NewUpdatesCollector(
   390  	db Kvdb,
   391  	prefix string,
   392  	startIndex uint64,
   393  ) (UpdatesCollector, error) {
   394  	collector := &updatesCollectorImpl{updates: make([]*kvdbUpdate, 0),
   395  		startIndex: startIndex}
   396  	logrus.Infof("Starting collector watch on %v at %v", prefix, startIndex)
   397  	if err := db.WatchTree(prefix, startIndex, nil, collector.watchCb); err != nil {
   398  		return nil, err
   399  	}
   400  	return collector, nil
   401  }
   402  
   403  // List of kvdb controller ports
   404  const (
   405  	// PeerPort is the port on which peers identify themselves
   406  	PeerPort = "2380"
   407  	// ClientPort is the port on which clients send requests to kvdb.
   408  	ClientPort = "2379"
   409  )
   410  
   411  // MemberInfo represents a member of the kvdb cluster
   412  type MemberInfo struct {
   413  	// PeerUrls is this member's URL on which it talks to its peers
   414  	PeerUrls []string
   415  	// ClientUrls is this member's URL on which clients can reach this member.
   416  	ClientUrls []string
   417  	// Leader indicates if this member is the leader of this cluster.
   418  	Leader bool
   419  	// DbSize is the current DB size as seen by this member.
   420  	DbSize int64
   421  	// IsHealthy indicates the health of the member.
   422  	IsHealthy bool
   423  	// ID is the string representation of member's ID
   424  	ID string
   425  	// Name of the member. A member which has not started has an empty Name.
   426  	Name string
   427  	// HasStarted indicates if this member has successfully started kvdb.
   428  	HasStarted bool
   429  	// IsLearner indicates if this member is a learner (i.e. not yet promoted to a full voting member).
   430  	IsLearner bool
   431  }
   432  
   433  // Controller interface provides APIs to manage Kvdb Cluster and Kvdb Clients.
   434  type Controller interface {
   435  	// AddMember adds a new member to an existing kvdb cluster. Add API should be
   436  	// invoked on an existing kvdb node where kvdb is already running. It should be
   437  	// followed by a Setup call on the node which is being added.
   438  	// Returns: map of nodeID to peerUrls of all members in the initial cluster or error.
   439  	AddMember(nodeIP, nodePeerPort, nodeName string) (map[string][]string, error)
   440  
   441  	// AddLearner is same as AddMember except that the new member is added as a learner.
   442  	// It is caller's responsibility to promote it to a full voting member.
   443  	AddLearner(nodeIP, nodePeerPort, nodeName string) (map[string][]string, error)
   444  
   445  	// RemoveMember removes a member based on its Name from an existing kvdb cluster.
   446  	// Returns: error if it fails to remove a member
   447  	RemoveMember(nodeName, nodeIP string) error
   448  
   449  	// RemoveMemberByID removes a member based on its ID from an existing kvdb cluster.
   450  	// Returns: error if it fails to remove a member
   451  	RemoveMemberByID(memberID uint64) error
   452  
   453  	// UpdateMember updates the IP for the given node in an existing kvdb cluster
   454  	// Returns: map of nodeID to peerUrls of all members from the existing cluster
   455  	UpdateMember(nodeIP, nodePeerPort, nodeName string) (map[string][]string, error)
   456  
   457  	// ListMembers enumerates the members of the kvdb cluster. It includes both the
   458  	// started and unstarted members.
   459  	// Returns: the member's ID  to MemberInfo mappings for all the members
   460  	ListMembers() (map[uint64]*MemberInfo, error)
   461  
   462  	// SetEndpoints set the kvdb endpoints for the client
   463  	SetEndpoints(endpoints []string) error
   464  
   465  	// GetEndpoints returns the kvdb endpoints for the client
   466  	GetEndpoints() []string
   467  
   468  	// Defragment defrags the underlying database for the given endpoint
   469  	// with a timeout specified in seconds
   470  	Defragment(endpoint string, timeout int) error
   471  }
   472  
   473  func LogFatalErrorCB(err error, format string, args ...interface{}) {
   474  	logrus.Errorf("encountered fatal error: %v", err)
   475  	logrus.Panicf(format, args...)
   476  }