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

     1  // Package zookeeper implements the KVDB interface based for zookeeper.
     2  // Code from docker/libkv was leveraged to build parts of this module.
     3  
     4  package zookeeper
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/portworx/kvdb"
    16  	"github.com/portworx/kvdb/common"
    17  	"github.com/samuel/go-zookeeper/zk"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  const (
    22  	// Name is the name of this kvdb implementation
    23  	Name = "zookeeper-kv"
    24  	// SOH control character
    25  	SOH = "\x01"
    26  	// defaultSessionTimeout is the client session timeout. If the session times out,
    27  	// then all ephemeral zk nodes are deleted by the zk server. As all lock keys are
    28  	// ephemeral nodes, all locks will be released after this timeout when a node
    29  	// dies or closes the zookeeper connection.
    30  	defaultSessionTimeout = 16 * time.Second
    31  	defaultRetryCount     = 10
    32  )
    33  
    34  var (
    35  	defaultServers = []string{"127.0.0.1:2181"}
    36  )
    37  
    38  type zookeeperKV struct {
    39  	common.BaseKvdb
    40  	client  *zk.Conn
    41  	options map[string]string
    42  	domain  string
    43  	kvdb.Controller
    44  }
    45  
    46  // zookeeperLock combines Mutex and channel
    47  type zookeeperLock struct {
    48  	Done     chan struct{}
    49  	Unlocked bool
    50  	sync.Mutex
    51  }
    52  
    53  // LockerIDInfo id of locker
    54  type LockerIDInfo struct {
    55  	LockerID string
    56  }
    57  
    58  // New constructs a new kvdb.Kvdb with zookeeper implementation
    59  func New(
    60  	domain string,
    61  	servers []string,
    62  	options map[string]string,
    63  	fatalErrorCb kvdb.FatalErrorCB,
    64  ) (kvdb.Kvdb, error) {
    65  	return newClient(domain, servers, options, fatalErrorCb)
    66  }
    67  
    68  // Version returns the supported version of the zookeeper implementation
    69  func Version(url string, kvdbOptions map[string]string) (string, error) {
    70  	return kvdb.ZookeeperVersion1, nil
    71  }
    72  
    73  // Used to create a zookeeper client for testing
    74  func newClient(
    75  	domain string,
    76  	servers []string,
    77  	options map[string]string,
    78  	fatalErrorCb kvdb.FatalErrorCB,
    79  ) (*zookeeperKV, error) {
    80  	if len(servers) == 0 {
    81  		servers = defaultServers
    82  	}
    83  
    84  	if domain != "" {
    85  		domain = normalize(domain)
    86  	}
    87  
    88  	zkClient, _, err := zk.Connect(servers, defaultSessionTimeout)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	return &zookeeperKV{
    94  		BaseKvdb: common.BaseKvdb{
    95  			FatalCb:         fatalErrorCb,
    96  			LockTryDuration: kvdb.DefaultLockTryDuration,
    97  		},
    98  		client:     zkClient,
    99  		domain:     domain,
   100  		Controller: kvdb.ControllerNotSupported,
   101  	}, nil
   102  }
   103  
   104  func (z *zookeeperKV) closeClient() {
   105  	z.client.Close()
   106  }
   107  
   108  func (z *zookeeperKV) String() string {
   109  	return Name
   110  }
   111  
   112  func (z *zookeeperKV) Capabilities() int {
   113  	return 0
   114  }
   115  
   116  func (z *zookeeperKV) Get(key string) (*kvdb.KVPair, error) {
   117  	var (
   118  		err  error
   119  		resp []byte
   120  		meta *zk.Stat
   121  	)
   122  
   123  	key = z.domain + normalize(key)
   124  
   125  	for i := 0; i < z.getRetryCount(); i++ {
   126  		resp, meta, err = z.client.Get(key)
   127  		if err == nil && resp != nil {
   128  			// Rare case where Get returns SOH control character
   129  			// instead of the actual value
   130  			if string(resp) == SOH {
   131  				continue
   132  			}
   133  			if len(resp) == 0 {
   134  				return nil, kvdb.ErrNotFound
   135  			}
   136  			return z.resultToKvPair(key, resp, "get", meta), nil
   137  		}
   138  		if err == zk.ErrNoNode {
   139  			return nil, kvdb.ErrNotFound
   140  		}
   141  		return nil, err
   142  	}
   143  
   144  	return nil, err
   145  }
   146  
   147  func (z *zookeeperKV) GetVal(key string, val interface{}) (*kvdb.KVPair, error) {
   148  	kvp, err := z.Get(key)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	if err := json.Unmarshal(kvp.Value, val); err != nil {
   153  		return kvp, kvdb.ErrUnmarshal
   154  	}
   155  	return kvp, nil
   156  }
   157  
   158  func (z *zookeeperKV) GetWithCopy(
   159  	key string,
   160  	copySelect kvdb.CopySelect,
   161  ) (interface{}, error) {
   162  	return nil, kvdb.ErrNotSupported
   163  }
   164  
   165  // Put creates a node if it does not exist and sets the given value
   166  func (z *zookeeperKV) Put(
   167  	key string,
   168  	val interface{},
   169  	ttl uint64,
   170  ) (*kvdb.KVPair, error) {
   171  	if ttl != 0 {
   172  		return nil, kvdb.ErrTTLNotSupported
   173  	}
   174  
   175  	bval, err := common.ToBytes(val)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	if len(bval) == 0 {
   180  		return nil, kvdb.ErrEmptyValue
   181  	}
   182  
   183  	err = z.createFullPath(key, false)
   184  	if err != nil && err != zk.ErrNodeExists {
   185  		return nil, err
   186  	}
   187  
   188  	key = z.domain + normalize(key)
   189  	meta, err := z.client.Set(key, bval, -1)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	return z.resultToKvPair(key, bval, "set", meta), nil
   194  }
   195  
   196  // Creates a zk node only if it does not exist.
   197  func (z *zookeeperKV) Create(
   198  	key string,
   199  	val interface{},
   200  	ttl uint64,
   201  ) (*kvdb.KVPair, error) {
   202  	if ttl != 0 {
   203  		return nil, kvdb.ErrTTLNotSupported
   204  	}
   205  
   206  	bval, err := common.ToBytes(val)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	if len(bval) == 0 {
   211  		return nil, kvdb.ErrEmptyValue
   212  	}
   213  
   214  	err = z.createFullPath(key, false)
   215  	if err == zk.ErrNodeExists {
   216  		return nil, kvdb.ErrExist
   217  	} else if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	key = z.domain + normalize(key)
   222  	meta, err := z.client.Set(key, bval, -1)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	return z.resultToKvPair(key, bval, "create", meta), nil
   227  }
   228  
   229  func (z *zookeeperKV) createEphemeral(
   230  	key string,
   231  	val interface{},
   232  ) (*kvdb.KVPair, error) {
   233  	bval, err := common.ToBytes(val)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	if len(bval) == 0 {
   238  		return nil, kvdb.ErrEmptyValue
   239  	}
   240  
   241  	err = z.createFullPath(key, true)
   242  	if err == zk.ErrNodeExists {
   243  		return nil, kvdb.ErrExist
   244  	} else if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	key = z.domain + normalize(key)
   249  	meta, err := z.client.Set(key, bval, -1)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  	return z.resultToKvPair(key, bval, "create", meta), nil
   254  }
   255  
   256  func (z *zookeeperKV) Update(
   257  	key string,
   258  	val interface{},
   259  	ttl uint64,
   260  ) (*kvdb.KVPair, error) {
   261  	exists, err := z.exists(key)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	if !exists {
   266  		return nil, kvdb.ErrNotFound
   267  	}
   268  	return z.Put(key, val, ttl)
   269  }
   270  
   271  func (z *zookeeperKV) Enumerate(prefix string) (kvdb.KVPairs, error) {
   272  	prefix = normalize(prefix)
   273  	fullPrefix := z.domain + prefix
   274  	keys, _, err := z.client.Children(fullPrefix)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	kvs := []*kvdb.KVPair{}
   280  	for _, key := range keys {
   281  		kvp, err := z.Get(prefix + normalize(key))
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		kvs = append(kvs, kvp)
   286  	}
   287  	return kvs, nil
   288  }
   289  
   290  func (z *zookeeperKV) EnumerateWithSelect(
   291  	prefix string,
   292  	enumerateSelect kvdb.EnumerateSelect,
   293  	copySelect kvdb.CopySelect,
   294  ) ([]interface{}, error) {
   295  	return nil, kvdb.ErrNotSupported
   296  }
   297  
   298  func (z *zookeeperKV) EnumerateKVPWithSelect(
   299  	prefix string,
   300  	enumerateSelect kvdb.EnumerateKVPSelect,
   301  	copySelect kvdb.CopyKVPSelect,
   302  ) (kvdb.KVPairs, error) {
   303  	return nil, kvdb.ErrNotSupported
   304  }
   305  
   306  func (z *zookeeperKV) Delete(key string) (*kvdb.KVPair, error) {
   307  	kvp, err := z.Get(key)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	key = z.domain + normalize(key)
   313  	err = z.client.Delete(key, -1)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	kvp.Action = kvdb.KVDelete
   319  	return kvp, nil
   320  }
   321  
   322  func (z *zookeeperKV) DeleteTree(prefix string) error {
   323  	fullPrefix := z.domain + normalize(prefix)
   324  	keys, _, err := z.client.Children(fullPrefix)
   325  	if err != nil {
   326  		return err
   327  	}
   328  
   329  	var requests []interface{}
   330  	for _, key := range keys {
   331  		requests = append(requests, &zk.DeleteRequest{
   332  			Path:    fullPrefix + normalize(key),
   333  			Version: -1,
   334  		})
   335  	}
   336  
   337  	_, err = z.client.Multi(requests...)
   338  	return err
   339  }
   340  
   341  func (z *zookeeperKV) Keys(prefix, sep string) ([]string, error) {
   342  	prefix = normalize(prefix)
   343  	fullPrefix := z.domain + prefix
   344  	keys, _, err := z.client.Children(fullPrefix)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	return keys, nil
   349  }
   350  
   351  func (z *zookeeperKV) CompareAndSet(
   352  	kvp *kvdb.KVPair,
   353  	flags kvdb.KVFlags,
   354  	prevValue []byte,
   355  ) (*kvdb.KVPair, error) {
   356  	action := "create"
   357  	modifiedIndex := int32(-1)
   358  
   359  	prevPair, err := z.Get(kvp.Key)
   360  	if err == nil {
   361  		if (flags & kvdb.KVModifiedIndex) != 0 {
   362  			if kvp.ModifiedIndex != prevPair.ModifiedIndex {
   363  				return nil, kvdb.ErrModified
   364  			}
   365  		} else {
   366  			if bytes.Compare(prevValue, prevPair.Value) != 0 {
   367  				return nil, kvdb.ErrValueMismatch
   368  			}
   369  		}
   370  		action = "set"
   371  		modifiedIndex = int32(prevPair.ModifiedIndex)
   372  	} else if err != kvdb.ErrNotFound {
   373  		return nil, err
   374  	}
   375  
   376  	err = z.createFullPath(kvp.Key, false)
   377  	if err != nil && err != zk.ErrNodeExists {
   378  		return nil, err
   379  	}
   380  
   381  	key := z.domain + normalize(kvp.Key)
   382  	// In case of two nodes trying to call CAS at the same time, setting
   383  	// modified index in Set() will ensure that only one update succeeds.
   384  	// Zookeeper will reject an update if the ModifiedIndex does not
   385  	// match the previous version, unless it is -1.
   386  	meta, err := z.client.Set(key, kvp.Value, modifiedIndex)
   387  	if err == zk.ErrBadVersion {
   388  		return nil, kvdb.ErrModified
   389  	} else if err != nil {
   390  		return nil, err
   391  	}
   392  	return z.resultToKvPair(kvp.Key, kvp.Value, action, meta), nil
   393  }
   394  
   395  func (z *zookeeperKV) CompareAndDelete(
   396  	kvp *kvdb.KVPair,
   397  	flags kvdb.KVFlags,
   398  ) (*kvdb.KVPair, error) {
   399  	prevPair, err := z.Get(kvp.Key)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	if (flags & kvdb.KVModifiedIndex) != 0 {
   405  		if kvp.ModifiedIndex != prevPair.ModifiedIndex {
   406  			return nil, kvdb.ErrModified
   407  		}
   408  	} else {
   409  		if bytes.Compare(kvp.Value, prevPair.Value) != 0 {
   410  			return nil, kvdb.ErrValueMismatch
   411  		}
   412  	}
   413  
   414  	key := z.domain + normalize(kvp.Key)
   415  	// In case of two nodes trying to call CompareAndDelete at the same time,
   416  	// setting modified index in Delete() will ensure that only one call succeeds.
   417  	// Zookeeper will reject an update if the ModifiedIndex does not match the
   418  	// previous version, unless it is -1.
   419  	err = z.client.Delete(key, int32(prevPair.ModifiedIndex))
   420  	if err == zk.ErrNoNode {
   421  		return nil, kvdb.ErrNotFound
   422  	} else if err == zk.ErrBadVersion {
   423  		return nil, kvdb.ErrModified
   424  	} else if err != nil {
   425  		return nil, err
   426  	}
   427  	prevPair.Action = kvdb.KVDelete
   428  	return prevPair, nil
   429  }
   430  
   431  func (z *zookeeperKV) WatchKey(
   432  	key string,
   433  	waitIndex uint64,
   434  	opaque interface{},
   435  	watchCB kvdb.WatchCB,
   436  ) error {
   437  	return kvdb.ErrNotSupported
   438  }
   439  
   440  func (z *zookeeperKV) WatchTree(
   441  	prefix string,
   442  	waitIndex uint64,
   443  	opaque interface{},
   444  	watchCB kvdb.WatchCB,
   445  ) error {
   446  	return kvdb.ErrNotSupported
   447  }
   448  
   449  func (z *zookeeperKV) Compact(
   450  	index uint64,
   451  ) error {
   452  	return kvdb.ErrNotSupported
   453  }
   454  
   455  func (z *zookeeperKV) Snapshot(prefixes []string, consistent bool) (kvdb.Kvdb, uint64, error) {
   456  	return nil, 0, kvdb.ErrNotSupported
   457  }
   458  
   459  func (z *zookeeperKV) SnapPut(kvp *kvdb.KVPair) (*kvdb.KVPair, error) {
   460  	return nil, kvdb.ErrNotSupported
   461  }
   462  
   463  func (z *zookeeperKV) Lock(key string) (*kvdb.KVPair, error) {
   464  	return z.LockWithID(key, "locked")
   465  }
   466  
   467  func (z *zookeeperKV) LockWithID(key, lockerID string) (*kvdb.KVPair, error) {
   468  	return z.LockWithTimeout(key, lockerID, z.LockTryDuration, z.GetLockHoldDuration())
   469  }
   470  
   471  func (z *zookeeperKV) LockWithTimeout(
   472  	key string,
   473  	lockerID string,
   474  	lockTryDuration time.Duration,
   475  	lockHoldDuration time.Duration,
   476  ) (*kvdb.KVPair, error) {
   477  	key = normalize(key)
   478  	lockTag := LockerIDInfo{LockerID: lockerID}
   479  
   480  	kvPair, err := z.createEphemeral(key, lockTag)
   481  	startTime := time.Now()
   482  
   483  	for count := 0; err != nil; count++ {
   484  		time.Sleep(time.Second)
   485  		kvPair, err = z.createEphemeral(key, lockTag)
   486  		if count > 0 && count%15 == 0 && err != nil {
   487  			currLockerTag := LockerIDInfo{}
   488  			if _, errGet := z.GetVal(key, &currLockerTag); errGet == nil {
   489  				logrus.Warnf("Lock %v locked for %v seconds, tag: %v, err: %v",
   490  					key, count, currLockerTag, err)
   491  			}
   492  		}
   493  		if err != nil && time.Since(startTime) > lockTryDuration {
   494  			return nil, err
   495  		}
   496  	}
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  
   501  	kvPair.Lock = &zookeeperLock{Done: make(chan struct{})}
   502  	go z.waitForUnlock(kvPair, lockerID, lockHoldDuration)
   503  	return kvPair, err
   504  }
   505  
   506  func (z *zookeeperKV) IsKeyLocked(key string) (bool, string, error) {
   507  	return false, "", kvdb.ErrNotSupported
   508  }
   509  
   510  func (z *zookeeperKV) waitForUnlock(
   511  	kvp *kvdb.KVPair,
   512  	lockerID string,
   513  	lockHoldDuration time.Duration,
   514  ) {
   515  	l := kvp.Lock.(*zookeeperLock)
   516  	timer := time.NewTimer(lockHoldDuration)
   517  	defer timer.Stop()
   518  
   519  	lockMsgString := kvp.Key + ",tag=" + lockerID
   520  
   521  	for {
   522  		select {
   523  		case <-timer.C:
   524  			if lockHoldDuration > 0 {
   525  				l.Lock()
   526  				defer l.Unlock()
   527  				if !l.Unlocked {
   528  					if _, err := z.Delete(kvp.Key); err != nil {
   529  						logrus.Errorf("Error deleting lock %s after timeout. %v",
   530  							lockMsgString, err)
   531  					}
   532  					l.Unlocked = true
   533  				}
   534  				z.LockTimedout(lockMsgString, lockHoldDuration)
   535  				return
   536  			}
   537  		case <-l.Done:
   538  			return
   539  		}
   540  	}
   541  }
   542  
   543  func (z *zookeeperKV) Unlock(kvp *kvdb.KVPair) error {
   544  	l, ok := kvp.Lock.(*zookeeperLock)
   545  	if !ok {
   546  		return fmt.Errorf("Invalid lock structure for key: %s", kvp.Key)
   547  	}
   548  	l.Lock()
   549  	if _, err := z.Delete(kvp.Key); err != nil {
   550  		l.Unlock()
   551  		return err
   552  	}
   553  	l.Unlocked = true
   554  	l.Unlock()
   555  	l.Done <- struct{}{}
   556  	return nil
   557  }
   558  
   559  func (z *zookeeperKV) TxNew() (kvdb.Tx, error) {
   560  	return nil, kvdb.ErrNotSupported
   561  }
   562  
   563  func (z *zookeeperKV) AddUser(username string, password string) error {
   564  	return kvdb.ErrNotSupported
   565  }
   566  
   567  func (z *zookeeperKV) RemoveUser(username string) error {
   568  	return kvdb.ErrNotSupported
   569  }
   570  
   571  func (z *zookeeperKV) GrantUserAccess(
   572  	username string,
   573  	permType kvdb.PermissionType,
   574  	subtree string,
   575  ) error {
   576  	return kvdb.ErrNotSupported
   577  }
   578  
   579  func (z *zookeeperKV) RevokeUsersAccess(
   580  	username string,
   581  	permType kvdb.PermissionType,
   582  	subtree string,
   583  ) error {
   584  	return kvdb.ErrNotSupported
   585  }
   586  
   587  func (z *zookeeperKV) Serialize() ([]byte, error) {
   588  	return nil, kvdb.ErrNotSupported
   589  }
   590  
   591  func (z *zookeeperKV) Deserialize(b []byte) (kvdb.KVPairs, error) {
   592  	return nil, kvdb.ErrNotSupported
   593  }
   594  
   595  // normalize converts a given path to the form /a/b/c
   596  func normalize(key string) string {
   597  	if key == "" {
   598  		return ""
   599  	}
   600  	path := strings.Split(strings.Trim(key, "/"), "/")
   601  	return "/" + strings.Join(path, "/")
   602  }
   603  
   604  // createFullPath creates the entire path for a directory
   605  func (z *zookeeperKV) createFullPath(key string, ephemeral bool) error {
   606  	key = z.domain + normalize(key)
   607  	path := strings.Split(strings.TrimPrefix(key, "/"), "/")
   608  
   609  	for i := 1; i <= len(path); i++ {
   610  		newPath := "/" + strings.Join(path[:i], "/")
   611  
   612  		if i == len(path) {
   613  			flags := int32(0)
   614  			if ephemeral {
   615  				flags = zk.FlagEphemeral
   616  			}
   617  			_, err := z.client.Create(newPath, []byte{},
   618  				flags, zk.WorldACL(zk.PermAll))
   619  			return err
   620  		}
   621  
   622  		_, err := z.client.Create(newPath, []byte{},
   623  			0, zk.WorldACL(zk.PermAll))
   624  		if err != nil && err != zk.ErrNodeExists {
   625  			return err
   626  		}
   627  	}
   628  	return nil
   629  }
   630  
   631  // exists checks if the key exists
   632  func (z *zookeeperKV) exists(key string) (bool, error) {
   633  	key = z.domain + normalize(key)
   634  	exists, _, err := z.client.Exists(key)
   635  	if err != nil {
   636  		return false, err
   637  	}
   638  	return exists, nil
   639  }
   640  
   641  func (z *zookeeperKV) getRetryCount() int {
   642  	retryCount, ok := z.options[kvdb.RetryCountKey]
   643  	if !ok {
   644  		return defaultRetryCount
   645  	}
   646  	retry, err := strconv.ParseInt(retryCount, 10, 0)
   647  	if err != nil {
   648  		return defaultRetryCount
   649  	}
   650  	return int(retry)
   651  }
   652  
   653  func (z *zookeeperKV) getAction(action string) kvdb.KVAction {
   654  	switch action {
   655  	case "set":
   656  		return kvdb.KVSet
   657  	case "create":
   658  		return kvdb.KVCreate
   659  	case "get":
   660  		return kvdb.KVGet
   661  	default:
   662  		return kvdb.KVUknown
   663  	}
   664  }
   665  
   666  func (z *zookeeperKV) resultToKvPair(
   667  	key string,
   668  	value []byte,
   669  	action string,
   670  	stat *zk.Stat,
   671  ) *kvdb.KVPair {
   672  	return &kvdb.KVPair{
   673  		Key:           strings.TrimPrefix(key, z.domain+"/"),
   674  		Value:         value,
   675  		Action:        z.getAction(action),
   676  		ModifiedIndex: uint64(stat.Version),
   677  		CreatedIndex:  uint64(stat.Version),
   678  	}
   679  }
   680  
   681  func init() {
   682  	if err := kvdb.Register(Name, New, Version); err != nil {
   683  		panic(err.Error())
   684  	}
   685  }