github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kvstore/etcd_test.go (about)

     1  // Copyright 2016-2020 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // +build !privileged_tests
    16  
    17  package kvstore
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"path"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/cilium/cilium/pkg/checker"
    30  
    31  	etcdAPI "go.etcd.io/etcd/clientv3"
    32  	. "gopkg.in/check.v1"
    33  )
    34  
    35  type EtcdSuite struct {
    36  	BaseTests
    37  }
    38  
    39  var _ = Suite(&EtcdSuite{})
    40  
    41  func (e *EtcdSuite) SetUpTest(c *C) {
    42  	SetupDummy("etcd")
    43  }
    44  
    45  func (e *EtcdSuite) TearDownTest(c *C) {
    46  	Client().Close()
    47  }
    48  
    49  type MaintenanceMocker struct {
    50  	OnAlarmList   func(ctx context.Context) (*etcdAPI.AlarmResponse, error)
    51  	OnAlarmDisarm func(ctx context.Context, m *etcdAPI.AlarmMember) (*etcdAPI.AlarmResponse, error)
    52  	OnDefragment  func(ctx context.Context, endpoint string) (*etcdAPI.DefragmentResponse, error)
    53  	OnStatus      func(ctx context.Context, endpoint string) (*etcdAPI.StatusResponse, error)
    54  	OnSnapshot    func(ctx context.Context) (io.ReadCloser, error)
    55  	OnHashKV      func(ctx context.Context, endpoint string, rev int64) (*etcdAPI.HashKVResponse, error)
    56  	OnMoveLeader  func(ctx context.Context, transfereeID uint64) (*etcdAPI.MoveLeaderResponse, error)
    57  }
    58  
    59  func (m MaintenanceMocker) AlarmList(ctx context.Context) (*etcdAPI.AlarmResponse, error) {
    60  	if m.OnAlarmList != nil {
    61  		return m.OnAlarmList(ctx)
    62  	}
    63  	return nil, fmt.Errorf("Method AlarmList should not have been called")
    64  }
    65  
    66  func (m MaintenanceMocker) AlarmDisarm(ctx context.Context, am *etcdAPI.AlarmMember) (*etcdAPI.AlarmResponse, error) {
    67  	if m.OnAlarmDisarm != nil {
    68  		return m.OnAlarmDisarm(ctx, am)
    69  	}
    70  	return nil, fmt.Errorf("Method AlarmDisarm should not have been called")
    71  }
    72  
    73  func (m MaintenanceMocker) Defragment(ctx context.Context, endpoint string) (*etcdAPI.DefragmentResponse, error) {
    74  	if m.OnDefragment != nil {
    75  		return m.OnDefragment(ctx, endpoint)
    76  	}
    77  	return nil, fmt.Errorf("Method Defragment should not have been called")
    78  }
    79  
    80  func (m MaintenanceMocker) Status(ctx context.Context, endpoint string) (*etcdAPI.StatusResponse, error) {
    81  	if m.OnStatus != nil {
    82  		return m.OnStatus(ctx, endpoint)
    83  	}
    84  	return nil, fmt.Errorf("Method Status should not have been called")
    85  }
    86  
    87  func (m MaintenanceMocker) Snapshot(ctx context.Context) (io.ReadCloser, error) {
    88  	if m.OnSnapshot != nil {
    89  		return m.OnSnapshot(ctx)
    90  	}
    91  	return nil, fmt.Errorf("Method Snapshot should not have been called")
    92  }
    93  
    94  func (m MaintenanceMocker) HashKV(ctx context.Context, endpoint string, rev int64) (*etcdAPI.HashKVResponse, error) {
    95  	if m.OnSnapshot != nil {
    96  		return m.OnHashKV(ctx, endpoint, rev)
    97  	}
    98  	return nil, fmt.Errorf("Method HashKV should not have been called")
    99  }
   100  
   101  func (m MaintenanceMocker) MoveLeader(ctx context.Context, transfereeID uint64) (*etcdAPI.MoveLeaderResponse, error) {
   102  	if m.OnSnapshot != nil {
   103  		return m.OnMoveLeader(ctx, transfereeID)
   104  	}
   105  	return nil, fmt.Errorf("Method MoveLeader should not have been called")
   106  }
   107  
   108  func (s *EtcdSuite) TestHint(c *C) {
   109  	var err error
   110  
   111  	c.Assert(Hint(err), IsNil)
   112  
   113  	err = errors.New("foo bar")
   114  	c.Assert(Hint(err), ErrorMatches, "foo bar")
   115  
   116  	err = fmt.Errorf("ayy lmao")
   117  	c.Assert(Hint(err), ErrorMatches, "ayy lmao")
   118  
   119  	err = context.DeadlineExceeded
   120  	c.Assert(Hint(err), ErrorMatches, "etcd client timeout exceeded")
   121  
   122  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
   123  	defer cancel()
   124  
   125  	<-ctx.Done()
   126  	err = ctx.Err()
   127  
   128  	c.Assert(Hint(err), ErrorMatches, "etcd client timeout exceeded")
   129  }
   130  
   131  func (s *EtcdSuite) TestETCDVersionCheck(c *C) {
   132  	badVersionStr := "3.0.0"
   133  	goodVersion := "3.1.0"
   134  	mm := MaintenanceMocker{
   135  		OnStatus: func(ctx context.Context, endpoint string) (*etcdAPI.StatusResponse, error) {
   136  			switch endpoint {
   137  			case "http://127.0.0.1:4004":
   138  				return &etcdAPI.StatusResponse{
   139  					Version: badVersionStr,
   140  				}, nil
   141  			default:
   142  				return &etcdAPI.StatusResponse{
   143  					Version: goodVersion,
   144  				}, nil
   145  			}
   146  		},
   147  	}
   148  	// Check a good version
   149  	v, err := getEPVersion(mm, "http://127.0.0.1:4003", time.Second)
   150  	c.Assert(err, IsNil)
   151  	c.Assert(v.String(), Equals, goodVersion)
   152  
   153  	// Check a bad version
   154  	v, err = getEPVersion(mm, "http://127.0.0.1:4004", time.Second)
   155  	c.Assert(err, IsNil)
   156  	c.Assert(v.String(), Equals, badVersionStr)
   157  
   158  	// CheckMinVersion all good
   159  	cfg := etcdAPI.Config{}
   160  	cfg.Endpoints = []string{"http://127.0.0.1:4003", "http://127.0.0.1:4005"}
   161  	cli, err := etcdAPI.New(cfg)
   162  	c.Assert(err, IsNil)
   163  	cli.Maintenance = mm
   164  	client := etcdClient{
   165  		client: cli,
   166  	}
   167  
   168  	// short timeout for tests
   169  	versionCheckTimeout = time.Second
   170  
   171  	c.Assert(client.checkMinVersion(), IsNil)
   172  
   173  	// One endpoint has a bad version and should fail
   174  	cfg.Endpoints = []string{"http://127.0.0.1:4003", "http://127.0.0.1:4004", "http://127.0.0.1:4005"}
   175  	cli, err = etcdAPI.New(cfg)
   176  	c.Assert(err, IsNil)
   177  	cli.Maintenance = mm
   178  	client = etcdClient{
   179  		client: cli,
   180  	}
   181  
   182  	c.Assert(client.checkMinVersion(), Not(IsNil))
   183  }
   184  
   185  type EtcdHelpersSuite struct{}
   186  
   187  var _ = Suite(&EtcdHelpersSuite{})
   188  
   189  func (s *EtcdHelpersSuite) TestIsEtcdOperator(c *C) {
   190  	temp := c.MkDir()
   191  	etcdConfigByte := []byte(`---
   192  endpoints:
   193  - https://cilium-etcd-client.kube-system.svc:2379
   194  `)
   195  	etcdTempFile := path.Join(temp, "etcd-config.yaml")
   196  	err := ioutil.WriteFile(etcdTempFile, etcdConfigByte, 0600)
   197  	c.Assert(err, IsNil)
   198  	type args struct {
   199  		backend      string
   200  		opts         map[string]string
   201  		k8sNamespace string
   202  	}
   203  	tests := []struct {
   204  		args        args
   205  		wantSvcName string
   206  		wantBool    bool
   207  	}{
   208  		{
   209  			args: args{
   210  				backend: consulName,
   211  			},
   212  			// it is not etcd
   213  			wantBool: false,
   214  		},
   215  		{
   216  			args: args{
   217  				backend: EtcdBackendName,
   218  			},
   219  			// misses configuration
   220  			wantBool: false,
   221  		},
   222  		{
   223  			args: args{
   224  				backend: EtcdBackendName,
   225  				opts: map[string]string{
   226  					"etcd.address": "http://cilium-etcd-client.kube-system.svc",
   227  				},
   228  				k8sNamespace: "kube-system",
   229  			},
   230  			wantSvcName: "http://cilium-etcd-client.kube-system.svc",
   231  			// everything valid
   232  			wantBool: true,
   233  		},
   234  		{
   235  			args: args{
   236  				backend: EtcdBackendName,
   237  				opts: map[string]string{
   238  					"etcd.address": "cilium-etcd-client.kube-system.svc",
   239  				},
   240  				k8sNamespace: "kube-system",
   241  			},
   242  			// domain name misses protocol
   243  			wantBool: false,
   244  		},
   245  		{
   246  			args: args{
   247  				opts: map[string]string{
   248  					"etcd.address": "cilium-etcd-client.kube-system.svc",
   249  				},
   250  				k8sNamespace: "kube-system",
   251  			},
   252  			// backend not specified
   253  			wantBool: false,
   254  		},
   255  		{
   256  			args: args{
   257  				backend: EtcdBackendName,
   258  				opts: map[string]string{
   259  					"etcd.config": etcdTempFile,
   260  				},
   261  				k8sNamespace: "kube-system",
   262  			},
   263  			wantSvcName: "https://cilium-etcd-client.kube-system.svc:2379",
   264  			// config file with everything setup
   265  			wantBool: true,
   266  		},
   267  		{
   268  			args: args{
   269  				backend: EtcdBackendName,
   270  				opts: map[string]string{
   271  					"etcd.address":  "foo-bar.kube-system.svc",
   272  					"etcd.operator": "true",
   273  				},
   274  				k8sNamespace: "kube-system",
   275  			},
   276  			wantSvcName: "foo-bar.kube-system.svc",
   277  			wantBool:    true,
   278  		},
   279  		{
   280  			args: args{
   281  				backend: EtcdBackendName,
   282  				opts: map[string]string{
   283  					"etcd.address":  "foo-bar.kube-system.svc",
   284  					"etcd.operator": "false",
   285  				},
   286  				k8sNamespace: "kube-system",
   287  			},
   288  			wantBool: false,
   289  		},
   290  		{
   291  			args: args{
   292  				backend: EtcdBackendName,
   293  				opts: map[string]string{
   294  					"etcd.address": "foo-bar.kube-system.svc",
   295  				},
   296  				k8sNamespace: "kube-system",
   297  			},
   298  			wantBool: false,
   299  		},
   300  		{
   301  			args: args{
   302  				backend: EtcdBackendName,
   303  				opts: map[string]string{
   304  					"etcd.address":  "foo-bar.kube-system.svc",
   305  					"etcd.operator": "foo-bar",
   306  				},
   307  				k8sNamespace: "kube-system",
   308  			},
   309  			wantBool: false,
   310  		},
   311  		{
   312  			args: args{
   313  				backend: EtcdBackendName,
   314  				opts: map[string]string{
   315  					"etcd.address":  "https://cilium-etcd-client.kube-system.svc",
   316  					"etcd.operator": "foo-bar",
   317  				},
   318  				k8sNamespace: "kube-system",
   319  			},
   320  			wantSvcName: "https://cilium-etcd-client.kube-system.svc",
   321  			wantBool:    true,
   322  		},
   323  		{
   324  			args: args{
   325  				backend: EtcdBackendName,
   326  				opts: map[string]string{
   327  					"etcd.config":   etcdTempFile,
   328  					"etcd.operator": "foo-bar",
   329  				},
   330  				k8sNamespace: "kube-system",
   331  			},
   332  			wantSvcName: "https://cilium-etcd-client.kube-system.svc:2379",
   333  			// config file with everything setup
   334  			wantBool: true,
   335  		},
   336  	}
   337  	for i, tt := range tests {
   338  		gotSvcName, gotBool := IsEtcdOperator(tt.args.backend, tt.args.opts, tt.args.k8sNamespace)
   339  		c.Assert(gotBool, Equals, tt.wantBool, Commentf("Test %d", i))
   340  		c.Assert(gotSvcName, Equals, tt.wantSvcName, Commentf("Test %d", i))
   341  	}
   342  }
   343  
   344  type EtcdLockedSuite struct {
   345  	etcdClient *etcdAPI.Client
   346  }
   347  
   348  var _ = Suite(&EtcdLockedSuite{})
   349  
   350  func (e *EtcdLockedSuite) SetUpSuite(c *C) {
   351  	SetupDummy("etcd")
   352  
   353  	// setup client
   354  	cfg := etcdAPI.Config{}
   355  	cfg.Endpoints = []string{etcdDummyAddress}
   356  	cfg.DialTimeout = 0
   357  	cli, err := etcdAPI.New(cfg)
   358  	c.Assert(err, IsNil)
   359  	e.etcdClient = cli
   360  }
   361  
   362  func (e *EtcdLockedSuite) TearDownSuite(c *C) {
   363  	err := e.etcdClient.Close()
   364  	c.Assert(err, IsNil)
   365  	Client().Close()
   366  }
   367  
   368  func (e *EtcdLockedSuite) TestGetIfLocked(c *C) {
   369  	randomPath := c.MkDir()
   370  	type args struct {
   371  		key  string
   372  		lock KVLocker
   373  	}
   374  	type wanted struct {
   375  		err   error
   376  		value []byte
   377  	}
   378  	tests := []struct {
   379  		name        string
   380  		setupArgs   func() args
   381  		setupWanted func() wanted
   382  		cleanup     func(args args) error
   383  	}{
   384  		{
   385  			name: "getting locked path",
   386  			setupArgs: func() args {
   387  				key := randomPath + "foo"
   388  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   389  				c.Assert(err, IsNil)
   390  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   391  				c.Assert(err, IsNil)
   392  
   393  				return args{
   394  					key:  key,
   395  					lock: kvlocker,
   396  				}
   397  			},
   398  			setupWanted: func() wanted {
   399  				return wanted{
   400  					err:   nil,
   401  					value: []byte("bar"),
   402  				}
   403  			},
   404  			cleanup: func(args args) error {
   405  				_, err := e.etcdClient.Delete(context.Background(), args.key)
   406  				if err != nil {
   407  					return err
   408  				}
   409  				return args.lock.Unlock()
   410  			},
   411  		},
   412  		{
   413  			name: "getting locked path with no value",
   414  			setupArgs: func() args {
   415  				key := randomPath + "foo"
   416  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   417  				c.Assert(err, IsNil)
   418  				_, err = e.etcdClient.Delete(context.Background(), key)
   419  				c.Assert(err, IsNil)
   420  
   421  				return args{
   422  					key:  key,
   423  					lock: kvlocker,
   424  				}
   425  			},
   426  			setupWanted: func() wanted {
   427  				return wanted{
   428  					err:   nil,
   429  					value: nil,
   430  				}
   431  			},
   432  			cleanup: func(args args) error {
   433  				return args.lock.Unlock()
   434  			},
   435  		},
   436  		{
   437  			name: "getting locked path where lock was lost",
   438  			setupArgs: func() args {
   439  				key := randomPath + "foo"
   440  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   441  				c.Assert(err, IsNil)
   442  				err = kvlocker.Unlock()
   443  				c.Assert(err, IsNil)
   444  
   445  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   446  				c.Assert(err, IsNil)
   447  
   448  				return args{
   449  					key:  key,
   450  					lock: kvlocker,
   451  				}
   452  			},
   453  			setupWanted: func() wanted {
   454  				return wanted{
   455  					err:   ErrLockLeaseExpired,
   456  					value: nil,
   457  				}
   458  			},
   459  			cleanup: func(args args) error {
   460  				_, err := e.etcdClient.Delete(context.Background(), args.key)
   461  				return err
   462  			},
   463  		},
   464  	}
   465  	for _, tt := range tests {
   466  		c.Log(tt.name)
   467  		args := tt.setupArgs()
   468  		want := tt.setupWanted()
   469  		value, err := Client().GetIfLocked(args.key, args.lock)
   470  		c.Assert(err, Equals, want.err)
   471  		c.Assert(value, checker.DeepEquals, want.value)
   472  		err = tt.cleanup(args)
   473  		c.Assert(err, IsNil)
   474  	}
   475  }
   476  
   477  func (e *EtcdLockedSuite) TestGetPrefixIfLocked(c *C) {
   478  	randomPath := c.MkDir()
   479  	type args struct {
   480  		key  string
   481  		lock KVLocker
   482  	}
   483  	type wanted struct {
   484  		err   error
   485  		key   string
   486  		value []byte
   487  	}
   488  	tests := []struct {
   489  		name        string
   490  		setupArgs   func() args
   491  		setupWanted func() wanted
   492  		cleanup     func(args args) error
   493  	}{
   494  		{
   495  			name: "getting locked prefix path",
   496  			setupArgs: func() args {
   497  				key := randomPath + "foo"
   498  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   499  				c.Assert(err, IsNil)
   500  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   501  				c.Assert(err, IsNil)
   502  
   503  				return args{
   504  					key:  key,
   505  					lock: kvlocker,
   506  				}
   507  			},
   508  			setupWanted: func() wanted {
   509  				return wanted{
   510  					err:   nil,
   511  					key:   randomPath + "foo",
   512  					value: []byte("bar"),
   513  				}
   514  			},
   515  			cleanup: func(args args) error {
   516  				_, err := e.etcdClient.Delete(context.Background(), args.key)
   517  				if err != nil {
   518  					return err
   519  				}
   520  				return args.lock.Unlock()
   521  			},
   522  		},
   523  		{
   524  			name: "getting locked prefix path with no value",
   525  			setupArgs: func() args {
   526  				key := randomPath + "foo"
   527  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   528  				c.Assert(err, IsNil)
   529  
   530  				return args{
   531  					key:  key,
   532  					lock: kvlocker,
   533  				}
   534  			},
   535  			setupWanted: func() wanted {
   536  				return wanted{
   537  					err:   nil,
   538  					value: nil,
   539  				}
   540  			},
   541  			cleanup: func(args args) error {
   542  				return args.lock.Unlock()
   543  			},
   544  		},
   545  		{
   546  			name: "getting locked prefix path where lock was lost",
   547  			setupArgs: func() args {
   548  				key := randomPath + "foo"
   549  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   550  				c.Assert(err, IsNil)
   551  				err = kvlocker.Unlock()
   552  				c.Assert(err, IsNil)
   553  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   554  				c.Assert(err, IsNil)
   555  
   556  				return args{
   557  					key:  key,
   558  					lock: kvlocker,
   559  				}
   560  			},
   561  			setupWanted: func() wanted {
   562  				return wanted{
   563  					err:   ErrLockLeaseExpired,
   564  					value: nil,
   565  				}
   566  			},
   567  			cleanup: func(args args) error {
   568  				_, err := e.etcdClient.Delete(context.Background(), args.key)
   569  				return err
   570  			},
   571  		},
   572  	}
   573  	for _, tt := range tests {
   574  		c.Log(tt.name)
   575  		args := tt.setupArgs()
   576  		want := tt.setupWanted()
   577  		k, value, err := Client().GetPrefixIfLocked(context.Background(), args.key, args.lock)
   578  		c.Assert(err, Equals, want.err)
   579  		c.Assert(k, Equals, want.key)
   580  		c.Assert(value, checker.DeepEquals, want.value)
   581  		err = tt.cleanup(args)
   582  		c.Assert(err, IsNil)
   583  	}
   584  }
   585  
   586  func (e *EtcdLockedSuite) TestDeleteIfLocked(c *C) {
   587  	randomPath := c.MkDir()
   588  	type args struct {
   589  		key  string
   590  		lock KVLocker
   591  	}
   592  	type wanted struct {
   593  		err error
   594  	}
   595  	tests := []struct {
   596  		name        string
   597  		setupArgs   func() args
   598  		setupWanted func() wanted
   599  		cleanup     func(args args) error
   600  	}{
   601  		{
   602  			name: "deleting locked path",
   603  			setupArgs: func() args {
   604  				key := randomPath + "foo"
   605  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   606  				c.Assert(err, IsNil)
   607  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   608  				c.Assert(err, IsNil)
   609  
   610  				return args{
   611  					key:  key,
   612  					lock: kvlocker,
   613  				}
   614  			},
   615  			setupWanted: func() wanted {
   616  				return wanted{
   617  					err: nil,
   618  				}
   619  			},
   620  			cleanup: func(args args) error {
   621  				key := randomPath + "foo"
   622  				// verify that key was actually deleted
   623  				gr, err := e.etcdClient.Get(context.Background(), key)
   624  				c.Assert(err, IsNil)
   625  				c.Assert(gr.Count, Equals, int64(0))
   626  
   627  				return args.lock.Unlock()
   628  			},
   629  		},
   630  		{
   631  			name: "deleting locked path with no value",
   632  			setupArgs: func() args {
   633  				key := randomPath + "foo"
   634  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   635  				c.Assert(err, IsNil)
   636  
   637  				_, err = e.etcdClient.Delete(context.Background(), key)
   638  				c.Assert(err, IsNil)
   639  
   640  				return args{
   641  					key:  key,
   642  					lock: kvlocker,
   643  				}
   644  			},
   645  			setupWanted: func() wanted {
   646  				return wanted{
   647  					err: nil,
   648  				}
   649  			},
   650  			cleanup: func(args args) error {
   651  				key := randomPath + "foo"
   652  				// verify that key was actually deleted (this should not matter
   653  				// as the key was never in the kvstore but still)
   654  				gr, err := e.etcdClient.Get(context.Background(), key)
   655  				c.Assert(err, IsNil)
   656  				c.Assert(gr.Count, Equals, int64(0))
   657  
   658  				return args.lock.Unlock()
   659  			},
   660  		},
   661  		{
   662  			name: "deleting locked path where lock was lost",
   663  			setupArgs: func() args {
   664  				key := randomPath + "foo"
   665  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   666  				c.Assert(err, IsNil)
   667  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   668  				c.Assert(err, IsNil)
   669  				err = kvlocker.Unlock()
   670  				c.Assert(err, IsNil)
   671  
   672  				return args{
   673  					key:  key,
   674  					lock: kvlocker,
   675  				}
   676  			},
   677  			setupWanted: func() wanted {
   678  				return wanted{
   679  					err: ErrLockLeaseExpired,
   680  				}
   681  			},
   682  			cleanup: func(args args) error {
   683  				key := randomPath + "foo"
   684  				// If the lock was lost it means the value still exists
   685  				value, err := e.etcdClient.Get(context.Background(), key)
   686  				c.Assert(err, IsNil)
   687  				c.Assert(value.Count, Equals, int64(1))
   688  				c.Assert(value.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
   689  				return nil
   690  			},
   691  		},
   692  	}
   693  	for _, tt := range tests {
   694  		c.Log(tt.name)
   695  		args := tt.setupArgs()
   696  		want := tt.setupWanted()
   697  		err := Client().DeleteIfLocked(args.key, args.lock)
   698  		c.Assert(err, Equals, want.err)
   699  		err = tt.cleanup(args)
   700  		c.Assert(err, IsNil)
   701  	}
   702  }
   703  
   704  func (e *EtcdLockedSuite) TestUpdateIfLocked(c *C) {
   705  	randomPath := c.MkDir()
   706  	type args struct {
   707  		key      string
   708  		lock     KVLocker
   709  		newValue []byte
   710  		lease    bool
   711  	}
   712  	type wanted struct {
   713  		err error
   714  	}
   715  	tests := []struct {
   716  		name        string
   717  		setupArgs   func() args
   718  		setupWanted func() wanted
   719  		cleanup     func(args args) error
   720  	}{
   721  		{
   722  			name: "update locked path without lease",
   723  			setupArgs: func() args {
   724  				key := randomPath + "foo"
   725  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   726  				c.Assert(err, IsNil)
   727  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   728  				c.Assert(err, IsNil)
   729  
   730  				return args{
   731  					key:      key,
   732  					lock:     kvlocker,
   733  					newValue: []byte("newbar"),
   734  				}
   735  			},
   736  			setupWanted: func() wanted {
   737  				return wanted{
   738  					err: nil,
   739  				}
   740  			},
   741  			cleanup: func(args args) error {
   742  				key := randomPath + "foo"
   743  				// verify that key was actually updated
   744  				gr, err := e.etcdClient.Get(context.Background(), key)
   745  				c.Assert(err, IsNil)
   746  				c.Assert(gr.Count, Equals, int64(1))
   747  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
   748  
   749  				return args.lock.Unlock()
   750  			},
   751  		},
   752  		{
   753  			name: "update locked path with no value without lease",
   754  			setupArgs: func() args {
   755  				key := randomPath + "foo"
   756  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   757  				c.Assert(err, IsNil)
   758  
   759  				_, err = e.etcdClient.Delete(context.Background(), key)
   760  				c.Assert(err, IsNil)
   761  
   762  				return args{
   763  					key:      key,
   764  					lock:     kvlocker,
   765  					newValue: []byte("newbar"),
   766  				}
   767  			},
   768  			setupWanted: func() wanted {
   769  				return wanted{
   770  					err: nil,
   771  				}
   772  			},
   773  			cleanup: func(args args) error {
   774  				key := randomPath + "foo"
   775  				// a key that was updated with no value will create a new value
   776  				gr, err := e.etcdClient.Get(context.Background(), key)
   777  				c.Assert(err, IsNil)
   778  				c.Assert(gr.Count, Equals, int64(1))
   779  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
   780  
   781  				return args.lock.Unlock()
   782  			},
   783  		},
   784  		{
   785  			name: "update locked path where lock was lost without lease",
   786  			setupArgs: func() args {
   787  				key := randomPath + "foo"
   788  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   789  				c.Assert(err, IsNil)
   790  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   791  				c.Assert(err, IsNil)
   792  				err = kvlocker.Unlock()
   793  				c.Assert(err, IsNil)
   794  
   795  				return args{
   796  					key:  key,
   797  					lock: kvlocker,
   798  				}
   799  			},
   800  			setupWanted: func() wanted {
   801  				return wanted{
   802  					err: ErrLockLeaseExpired,
   803  				}
   804  			},
   805  			cleanup: func(args args) error {
   806  				key := randomPath + "foo"
   807  				// verify that key was actually updated
   808  				gr, err := e.etcdClient.Get(context.Background(), key)
   809  				c.Assert(err, IsNil)
   810  				c.Assert(gr.Count, Equals, int64(1))
   811  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
   812  				return nil
   813  			},
   814  		},
   815  		{
   816  			name: "update locked path with lease",
   817  			setupArgs: func() args {
   818  				key := randomPath + "foo"
   819  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   820  				c.Assert(err, IsNil)
   821  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   822  				c.Assert(err, IsNil)
   823  
   824  				return args{
   825  					key:      key,
   826  					lock:     kvlocker,
   827  					newValue: []byte("newbar"),
   828  					lease:    true,
   829  				}
   830  			},
   831  			setupWanted: func() wanted {
   832  				return wanted{
   833  					err: nil,
   834  				}
   835  			},
   836  			cleanup: func(args args) error {
   837  				key := randomPath + "foo"
   838  				// verify that key was actually updated
   839  				gr, err := e.etcdClient.Get(context.Background(), key)
   840  				c.Assert(err, IsNil)
   841  				c.Assert(gr.Count, Equals, int64(1))
   842  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
   843  
   844  				return args.lock.Unlock()
   845  			},
   846  		},
   847  		{
   848  			name: "update locked path with no value with lease",
   849  			setupArgs: func() args {
   850  				key := randomPath + "foo"
   851  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   852  				c.Assert(err, IsNil)
   853  
   854  				_, err = e.etcdClient.Delete(context.Background(), key)
   855  				c.Assert(err, IsNil)
   856  
   857  				return args{
   858  					key:      key,
   859  					lock:     kvlocker,
   860  					newValue: []byte("newbar"),
   861  					lease:    true,
   862  				}
   863  			},
   864  			setupWanted: func() wanted {
   865  				return wanted{
   866  					err: nil,
   867  				}
   868  			},
   869  			cleanup: func(args args) error {
   870  				key := randomPath + "foo"
   871  				// a key that was updated with no value will create a new value
   872  				gr, err := e.etcdClient.Get(context.Background(), key)
   873  				c.Assert(err, IsNil)
   874  				c.Assert(gr.Count, Equals, int64(1))
   875  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
   876  
   877  				return args.lock.Unlock()
   878  			},
   879  		},
   880  		{
   881  			name: "update locked path where lock was lost with lease",
   882  			setupArgs: func() args {
   883  				key := randomPath + "foo"
   884  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   885  				c.Assert(err, IsNil)
   886  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   887  				c.Assert(err, IsNil)
   888  				err = kvlocker.Unlock()
   889  				c.Assert(err, IsNil)
   890  
   891  				return args{
   892  					key:   key,
   893  					lock:  kvlocker,
   894  					lease: true,
   895  				}
   896  			},
   897  			setupWanted: func() wanted {
   898  				return wanted{
   899  					err: ErrLockLeaseExpired,
   900  				}
   901  			},
   902  			cleanup: func(args args) error {
   903  				key := randomPath + "foo"
   904  				// verify that key was actually updated
   905  				gr, err := e.etcdClient.Get(context.Background(), key)
   906  				c.Assert(err, IsNil)
   907  				c.Assert(gr.Count, Equals, int64(1))
   908  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
   909  				return nil
   910  			},
   911  		},
   912  	}
   913  	for _, tt := range tests {
   914  		c.Log(tt.name)
   915  		args := tt.setupArgs()
   916  		want := tt.setupWanted()
   917  		err := Client().UpdateIfLocked(context.Background(), args.key, args.newValue, args.lease, args.lock)
   918  		c.Assert(err, Equals, want.err)
   919  		err = tt.cleanup(args)
   920  		c.Assert(err, IsNil)
   921  	}
   922  }
   923  
   924  func (e *EtcdLockedSuite) TestUpdateIfDifferentIfLocked(c *C) {
   925  	randomPath := c.MkDir()
   926  	type args struct {
   927  		key      string
   928  		lock     KVLocker
   929  		newValue []byte
   930  		lease    bool
   931  	}
   932  	type wanted struct {
   933  		err     error
   934  		updated bool
   935  	}
   936  	tests := []struct {
   937  		name        string
   938  		setupArgs   func() args
   939  		setupWanted func() wanted
   940  		cleanup     func(args args) error
   941  	}{
   942  		{
   943  			name: "update locked path without lease",
   944  			setupArgs: func() args {
   945  				key := randomPath + "foo"
   946  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   947  				c.Assert(err, IsNil)
   948  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   949  				c.Assert(err, IsNil)
   950  
   951  				return args{
   952  					key:      key,
   953  					lock:     kvlocker,
   954  					newValue: []byte("newbar"),
   955  				}
   956  			},
   957  			setupWanted: func() wanted {
   958  				return wanted{
   959  					err:     nil,
   960  					updated: true,
   961  				}
   962  			},
   963  			cleanup: func(args args) error {
   964  				key := randomPath + "foo"
   965  				// verify that key was actually updated
   966  				gr, err := e.etcdClient.Get(context.Background(), key)
   967  				c.Assert(err, IsNil)
   968  				c.Assert(gr.Count, Equals, int64(1))
   969  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
   970  				_, err = e.etcdClient.Delete(context.Background(), key)
   971  				c.Assert(err, IsNil)
   972  				return args.lock.Unlock()
   973  			},
   974  		},
   975  		{
   976  			name: "update locked path without lease and with same value",
   977  			setupArgs: func() args {
   978  				key := randomPath + "foo"
   979  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
   980  				c.Assert(err, IsNil)
   981  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
   982  				c.Assert(err, IsNil)
   983  
   984  				return args{
   985  					key:      key,
   986  					lock:     kvlocker,
   987  					newValue: []byte("bar"),
   988  				}
   989  			},
   990  			setupWanted: func() wanted {
   991  				return wanted{
   992  					err: nil,
   993  				}
   994  			},
   995  			cleanup: func(args args) error {
   996  				key := randomPath + "foo"
   997  				// verify that key was actually updated
   998  				gr, err := e.etcdClient.Get(context.Background(), key)
   999  				c.Assert(err, IsNil)
  1000  				c.Assert(gr.Count, Equals, int64(1))
  1001  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1002  
  1003  				return args.lock.Unlock()
  1004  			},
  1005  		},
  1006  		{
  1007  			name: "update locked path with no value without lease",
  1008  			setupArgs: func() args {
  1009  				key := randomPath + "foo"
  1010  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1011  				c.Assert(err, IsNil)
  1012  
  1013  				_, err = e.etcdClient.Delete(context.Background(), key)
  1014  				c.Assert(err, IsNil)
  1015  
  1016  				return args{
  1017  					key:      key,
  1018  					lock:     kvlocker,
  1019  					newValue: []byte("newbar"),
  1020  				}
  1021  			},
  1022  			setupWanted: func() wanted {
  1023  				return wanted{
  1024  					err:     nil,
  1025  					updated: true,
  1026  				}
  1027  			},
  1028  			cleanup: func(args args) error {
  1029  				key := randomPath + "foo"
  1030  				// a key that was updated with no value will create a new value
  1031  				gr, err := e.etcdClient.Get(context.Background(), key)
  1032  				c.Assert(err, IsNil)
  1033  				c.Assert(gr.Count, Equals, int64(1))
  1034  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
  1035  				_, err = e.etcdClient.Delete(context.Background(), key)
  1036  				c.Assert(err, IsNil)
  1037  				return args.lock.Unlock()
  1038  			},
  1039  		},
  1040  		{
  1041  			name: "update locked path where lock was lost without lease",
  1042  			setupArgs: func() args {
  1043  				key := randomPath + "foo"
  1044  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1045  				c.Assert(err, IsNil)
  1046  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1047  				c.Assert(err, IsNil)
  1048  				err = kvlocker.Unlock()
  1049  				c.Assert(err, IsNil)
  1050  
  1051  				return args{
  1052  					key:      key,
  1053  					newValue: []byte("baz"),
  1054  					lock:     kvlocker,
  1055  				}
  1056  			},
  1057  			setupWanted: func() wanted {
  1058  				return wanted{
  1059  					err: ErrLockLeaseExpired,
  1060  				}
  1061  			},
  1062  			cleanup: func(args args) error {
  1063  				key := randomPath + "foo"
  1064  				// verify that key was actually updated
  1065  				gr, err := e.etcdClient.Get(context.Background(), key)
  1066  				c.Assert(err, IsNil)
  1067  				c.Assert(gr.Count, Equals, int64(1))
  1068  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1069  				_, err = e.etcdClient.Delete(context.Background(), key)
  1070  				c.Assert(err, IsNil)
  1071  				return nil
  1072  			},
  1073  		},
  1074  		{
  1075  			name: "update locked path with lease",
  1076  			setupArgs: func() args {
  1077  				key := randomPath + "foo"
  1078  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1079  				c.Assert(err, IsNil)
  1080  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1081  				c.Assert(err, IsNil)
  1082  
  1083  				return args{
  1084  					key:      key,
  1085  					lock:     kvlocker,
  1086  					newValue: []byte("newbar"),
  1087  					lease:    true,
  1088  				}
  1089  			},
  1090  			setupWanted: func() wanted {
  1091  				return wanted{
  1092  					err:     nil,
  1093  					updated: true,
  1094  				}
  1095  			},
  1096  			cleanup: func(args args) error {
  1097  				key := randomPath + "foo"
  1098  				// verify that key was actually updated
  1099  				gr, err := e.etcdClient.Get(context.Background(), key)
  1100  				c.Assert(err, IsNil)
  1101  				c.Assert(gr.Count, Equals, int64(1))
  1102  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
  1103  				_, err = e.etcdClient.Delete(context.Background(), key)
  1104  				c.Assert(err, IsNil)
  1105  				return args.lock.Unlock()
  1106  			},
  1107  		},
  1108  		{
  1109  			name: "update locked path with no value with lease",
  1110  			setupArgs: func() args {
  1111  				key := randomPath + "foo"
  1112  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1113  				c.Assert(err, IsNil)
  1114  
  1115  				_, err = e.etcdClient.Delete(context.Background(), key)
  1116  				c.Assert(err, IsNil)
  1117  
  1118  				return args{
  1119  					key:      key,
  1120  					lock:     kvlocker,
  1121  					newValue: []byte("newbar"),
  1122  					lease:    true,
  1123  				}
  1124  			},
  1125  			setupWanted: func() wanted {
  1126  				return wanted{
  1127  					err:     nil,
  1128  					updated: true,
  1129  				}
  1130  			},
  1131  			cleanup: func(args args) error {
  1132  				key := randomPath + "foo"
  1133  				// a key that was updated with no value will create a new value
  1134  				gr, err := e.etcdClient.Get(context.Background(), key)
  1135  				c.Assert(err, IsNil)
  1136  				c.Assert(gr.Count, Equals, int64(1))
  1137  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
  1138  
  1139  				_, err = e.etcdClient.Delete(context.Background(), key)
  1140  				c.Assert(err, IsNil)
  1141  
  1142  				return args.lock.Unlock()
  1143  			},
  1144  		},
  1145  		{
  1146  			name: "update locked path with lease and with same value",
  1147  			setupArgs: func() args {
  1148  				key := randomPath + "foo"
  1149  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1150  				c.Assert(err, IsNil)
  1151  				created, err := Client().CreateOnly(context.Background(), key, []byte("bar"), true)
  1152  				c.Assert(err, IsNil)
  1153  				c.Assert(created, Equals, true)
  1154  
  1155  				return args{
  1156  					key:      key,
  1157  					lock:     kvlocker,
  1158  					newValue: []byte("bar"),
  1159  					lease:    true,
  1160  				}
  1161  			},
  1162  			setupWanted: func() wanted {
  1163  				return wanted{
  1164  					err: nil,
  1165  				}
  1166  			},
  1167  			cleanup: func(args args) error {
  1168  				key := randomPath + "foo"
  1169  				// verify that key was actually updated
  1170  				gr, err := e.etcdClient.Get(context.Background(), key)
  1171  				c.Assert(err, IsNil)
  1172  				c.Assert(gr.Count, Equals, int64(1))
  1173  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1174  				_, err = e.etcdClient.Delete(context.Background(), key)
  1175  				c.Assert(err, IsNil)
  1176  				return args.lock.Unlock()
  1177  			},
  1178  		},
  1179  		{
  1180  			name: "update locked path where lock was lost with lease",
  1181  			setupArgs: func() args {
  1182  				key := randomPath + "foo"
  1183  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1184  				c.Assert(err, IsNil)
  1185  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1186  				c.Assert(err, IsNil)
  1187  				err = kvlocker.Unlock()
  1188  				c.Assert(err, IsNil)
  1189  
  1190  				return args{
  1191  					key:   key,
  1192  					lock:  kvlocker,
  1193  					lease: true,
  1194  				}
  1195  			},
  1196  			setupWanted: func() wanted {
  1197  				return wanted{
  1198  					err: ErrLockLeaseExpired,
  1199  				}
  1200  			},
  1201  			cleanup: func(args args) error {
  1202  				key := randomPath + "foo"
  1203  				// verify that key was actually updated
  1204  				gr, err := e.etcdClient.Get(context.Background(), key)
  1205  				c.Assert(err, IsNil)
  1206  				c.Assert(gr.Count, Equals, int64(1))
  1207  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1208  				return nil
  1209  			},
  1210  		},
  1211  	}
  1212  	for _, tt := range tests {
  1213  		c.Log(tt.name)
  1214  		args := tt.setupArgs()
  1215  		want := tt.setupWanted()
  1216  		updated, err := Client().UpdateIfDifferentIfLocked(context.Background(), args.key, args.newValue, args.lease, args.lock)
  1217  		c.Assert(err, Equals, want.err)
  1218  		c.Assert(updated, Equals, want.updated)
  1219  		err = tt.cleanup(args)
  1220  		c.Assert(err, IsNil)
  1221  	}
  1222  }
  1223  
  1224  func (e *EtcdLockedSuite) TestCreateOnlyIfLocked(c *C) {
  1225  	randomPath := c.MkDir()
  1226  	type args struct {
  1227  		key      string
  1228  		lock     KVLocker
  1229  		newValue []byte
  1230  		lease    bool
  1231  	}
  1232  	type wanted struct {
  1233  		err     error
  1234  		created bool
  1235  	}
  1236  	tests := []struct {
  1237  		name        string
  1238  		setupArgs   func() args
  1239  		setupWanted func() wanted
  1240  		cleanup     func(args args) error
  1241  	}{
  1242  		{
  1243  			name: "create only locked path without lease",
  1244  			setupArgs: func() args {
  1245  				key := randomPath + "foo"
  1246  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1247  				c.Assert(err, IsNil)
  1248  
  1249  				_, err = e.etcdClient.Delete(context.Background(), key)
  1250  				c.Assert(err, IsNil)
  1251  
  1252  				return args{
  1253  					key:      key,
  1254  					lock:     kvlocker,
  1255  					newValue: []byte("newbar"),
  1256  				}
  1257  			},
  1258  			setupWanted: func() wanted {
  1259  				return wanted{
  1260  					err:     nil,
  1261  					created: true,
  1262  				}
  1263  			},
  1264  			cleanup: func(args args) error {
  1265  				key := randomPath + "foo"
  1266  				// verify that key was actually created
  1267  				gr, err := e.etcdClient.Get(context.Background(), key)
  1268  				c.Assert(err, IsNil)
  1269  				c.Assert(gr.Count, Equals, int64(1))
  1270  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
  1271  
  1272  				return args.lock.Unlock()
  1273  			},
  1274  		},
  1275  		{
  1276  			name: "create only locked path with an existing value without lease",
  1277  			setupArgs: func() args {
  1278  				key := randomPath + "foo"
  1279  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1280  				c.Assert(err, IsNil)
  1281  
  1282  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1283  				c.Assert(err, IsNil)
  1284  
  1285  				return args{
  1286  					key:      key,
  1287  					lock:     kvlocker,
  1288  					newValue: []byte("newbar"),
  1289  				}
  1290  			},
  1291  			setupWanted: func() wanted {
  1292  				return wanted{
  1293  					err: nil,
  1294  				}
  1295  			},
  1296  			cleanup: func(args args) error {
  1297  				key := randomPath + "foo"
  1298  				// the key should not have been created and therefore the old
  1299  				// value is still there
  1300  				gr, err := e.etcdClient.Get(context.Background(), key)
  1301  				c.Assert(err, IsNil)
  1302  				c.Assert(gr.Count, Equals, int64(1))
  1303  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1304  
  1305  				return args.lock.Unlock()
  1306  			},
  1307  		},
  1308  		{
  1309  			name: "create only locked path where lock was lost without lease",
  1310  			setupArgs: func() args {
  1311  				key := randomPath + "foo"
  1312  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1313  				c.Assert(err, IsNil)
  1314  				_, err = e.etcdClient.Delete(context.Background(), key)
  1315  				c.Assert(err, IsNil)
  1316  				err = kvlocker.Unlock()
  1317  				c.Assert(err, IsNil)
  1318  
  1319  				return args{
  1320  					key:      key,
  1321  					lock:     kvlocker,
  1322  					newValue: []byte("bar"),
  1323  				}
  1324  			},
  1325  			setupWanted: func() wanted {
  1326  				return wanted{
  1327  					err: ErrLockLeaseExpired,
  1328  				}
  1329  			},
  1330  			cleanup: func(args args) error {
  1331  				key := randomPath + "foo"
  1332  				// verify that key was not created
  1333  				gr, err := e.etcdClient.Get(context.Background(), key)
  1334  				c.Assert(err, IsNil)
  1335  				c.Assert(gr.Count, Equals, int64(0))
  1336  				return nil
  1337  			},
  1338  		},
  1339  		{
  1340  			name: "create only locked path with lease",
  1341  			setupArgs: func() args {
  1342  				key := randomPath + "foo"
  1343  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1344  				c.Assert(err, IsNil)
  1345  
  1346  				_, err = e.etcdClient.Delete(context.Background(), key)
  1347  				c.Assert(err, IsNil)
  1348  
  1349  				return args{
  1350  					key:      key,
  1351  					lock:     kvlocker,
  1352  					newValue: []byte("newbar"),
  1353  					lease:    true,
  1354  				}
  1355  			},
  1356  			setupWanted: func() wanted {
  1357  				return wanted{
  1358  					err:     nil,
  1359  					created: true,
  1360  				}
  1361  			},
  1362  			cleanup: func(args args) error {
  1363  				key := randomPath + "foo"
  1364  				// verify that key was actually created
  1365  				gr, err := e.etcdClient.Get(context.Background(), key)
  1366  				c.Assert(err, IsNil)
  1367  				c.Assert(gr.Count, Equals, int64(1))
  1368  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("newbar"))
  1369  
  1370  				return args.lock.Unlock()
  1371  			},
  1372  		},
  1373  		{
  1374  			name: "create only locked path with an existing value with lease",
  1375  			setupArgs: func() args {
  1376  				key := randomPath + "foo"
  1377  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1378  				c.Assert(err, IsNil)
  1379  
  1380  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1381  				c.Assert(err, IsNil)
  1382  
  1383  				return args{
  1384  					key:      key,
  1385  					lock:     kvlocker,
  1386  					newValue: []byte("newbar"),
  1387  					lease:    true,
  1388  				}
  1389  			},
  1390  			setupWanted: func() wanted {
  1391  				return wanted{
  1392  					err: nil,
  1393  				}
  1394  			},
  1395  			cleanup: func(args args) error {
  1396  				key := randomPath + "foo"
  1397  				// the key should not have been created and therefore the old
  1398  				// value is still there
  1399  				gr, err := e.etcdClient.Get(context.Background(), key)
  1400  				c.Assert(err, IsNil)
  1401  				c.Assert(gr.Count, Equals, int64(1))
  1402  				c.Assert(gr.Kvs[0].Value, checker.DeepEquals, []byte("bar"))
  1403  
  1404  				return args.lock.Unlock()
  1405  			},
  1406  		},
  1407  		{
  1408  			name: "create only locked path where lock was lost with lease",
  1409  			setupArgs: func() args {
  1410  				key := randomPath + "foo"
  1411  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1412  				c.Assert(err, IsNil)
  1413  				_, err = e.etcdClient.Delete(context.Background(), key)
  1414  				c.Assert(err, IsNil)
  1415  				err = kvlocker.Unlock()
  1416  				c.Assert(err, IsNil)
  1417  
  1418  				return args{
  1419  					key:      key,
  1420  					lock:     kvlocker,
  1421  					newValue: []byte("bar"),
  1422  					lease:    true,
  1423  				}
  1424  			},
  1425  			setupWanted: func() wanted {
  1426  				return wanted{
  1427  					err: ErrLockLeaseExpired,
  1428  				}
  1429  			},
  1430  			cleanup: func(args args) error {
  1431  				key := randomPath + "foo"
  1432  				// verify that key was not created
  1433  				gr, err := e.etcdClient.Get(context.Background(), key)
  1434  				c.Assert(err, IsNil)
  1435  				c.Assert(gr.Count, Equals, int64(0))
  1436  				return nil
  1437  			},
  1438  		},
  1439  	}
  1440  	for _, tt := range tests {
  1441  		c.Log(tt.name)
  1442  		args := tt.setupArgs()
  1443  		want := tt.setupWanted()
  1444  		created, err := Client().CreateOnlyIfLocked(context.Background(), args.key, args.newValue, args.lease, args.lock)
  1445  		c.Assert(err, Equals, want.err)
  1446  		c.Assert(created, Equals, want.created)
  1447  		err = tt.cleanup(args)
  1448  		c.Assert(err, IsNil)
  1449  	}
  1450  }
  1451  
  1452  func (e *EtcdLockedSuite) TestListPrefixIfLocked(c *C) {
  1453  	randomPath := c.MkDir()
  1454  	type args struct {
  1455  		key  string
  1456  		lock KVLocker
  1457  	}
  1458  	type wanted struct {
  1459  		err     error
  1460  		kvPairs KeyValuePairs
  1461  	}
  1462  	tests := []struct {
  1463  		name        string
  1464  		setupArgs   func() args
  1465  		setupWanted func() wanted
  1466  		cleanup     func(args args) error
  1467  	}{
  1468  		{
  1469  			name: "list prefix locked",
  1470  			setupArgs: func() args {
  1471  				key := randomPath + "foo"
  1472  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1473  				c.Assert(err, IsNil)
  1474  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1475  				c.Assert(err, IsNil)
  1476  				_, err = e.etcdClient.Put(context.Background(), key+"1", "bar1")
  1477  				c.Assert(err, IsNil)
  1478  
  1479  				return args{
  1480  					key:  key,
  1481  					lock: kvlocker,
  1482  				}
  1483  			},
  1484  			setupWanted: func() wanted {
  1485  				key := randomPath + "foo"
  1486  				return wanted{
  1487  					err: nil,
  1488  					kvPairs: KeyValuePairs{
  1489  						key: Value{
  1490  							Data: []byte("bar"),
  1491  						},
  1492  						key + "1": Value{
  1493  							Data: []byte("bar1"),
  1494  						},
  1495  					},
  1496  				}
  1497  			},
  1498  			cleanup: func(args args) error {
  1499  				_, err := e.etcdClient.Delete(context.Background(), args.key, etcdAPI.WithPrefix())
  1500  				if err != nil {
  1501  					return err
  1502  				}
  1503  				return args.lock.Unlock()
  1504  			},
  1505  		},
  1506  		{
  1507  			name: "list prefix locked with no values",
  1508  			setupArgs: func() args {
  1509  				key := randomPath + "foo"
  1510  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1511  				c.Assert(err, IsNil)
  1512  				_, err = e.etcdClient.Delete(context.Background(), key, etcdAPI.WithPrefix())
  1513  				c.Assert(err, IsNil)
  1514  
  1515  				return args{
  1516  					key:  key,
  1517  					lock: kvlocker,
  1518  				}
  1519  			},
  1520  			setupWanted: func() wanted {
  1521  				return wanted{
  1522  					err: nil,
  1523  				}
  1524  			},
  1525  			cleanup: func(args args) error {
  1526  				return args.lock.Unlock()
  1527  			},
  1528  		},
  1529  		{
  1530  			name: "list prefix locked where lock was lost",
  1531  			setupArgs: func() args {
  1532  				key := randomPath + "foo"
  1533  				kvlocker, err := Client().LockPath(context.Background(), "locks/"+key+"/.lock")
  1534  				c.Assert(err, IsNil)
  1535  				_, err = e.etcdClient.Put(context.Background(), key, "bar")
  1536  				c.Assert(err, IsNil)
  1537  				_, err = e.etcdClient.Put(context.Background(), key+"1", "bar1")
  1538  				c.Assert(err, IsNil)
  1539  				err = kvlocker.Unlock()
  1540  				c.Assert(err, IsNil)
  1541  
  1542  				return args{
  1543  					key:  key,
  1544  					lock: kvlocker,
  1545  				}
  1546  			},
  1547  			setupWanted: func() wanted {
  1548  				return wanted{
  1549  					err: ErrLockLeaseExpired,
  1550  				}
  1551  			},
  1552  			cleanup: func(args args) error {
  1553  				_, err := e.etcdClient.Delete(context.Background(), args.key)
  1554  				return err
  1555  			},
  1556  		},
  1557  	}
  1558  	for _, tt := range tests {
  1559  		c.Log(tt.name)
  1560  		args := tt.setupArgs()
  1561  		want := tt.setupWanted()
  1562  		kvPairs, err := Client().ListPrefixIfLocked(args.key, args.lock)
  1563  		c.Assert(err, Equals, want.err)
  1564  		for k, v := range kvPairs {
  1565  			// We don't compare revision of the value because we can't predict
  1566  			// its value.
  1567  			v1, ok := want.kvPairs[k]
  1568  			c.Assert(ok, Equals, true)
  1569  			c.Assert(v.Data, checker.DeepEquals, v1.Data)
  1570  		}
  1571  		err = tt.cleanup(args)
  1572  		c.Assert(err, IsNil)
  1573  	}
  1574  }
  1575  
  1576  func TestGetSvcNamespace(t *testing.T) {
  1577  	type args struct {
  1578  		address string
  1579  	}
  1580  	tests := []struct {
  1581  		name          string
  1582  		args          args
  1583  		wantSvcName   string
  1584  		wantNamespace string
  1585  		wantErr       bool
  1586  	}{
  1587  		{
  1588  			name: "test-1",
  1589  			args: args{
  1590  				address: "http://foo.bar.something",
  1591  			},
  1592  			wantSvcName:   "foo",
  1593  			wantNamespace: "bar",
  1594  			wantErr:       false,
  1595  		},
  1596  		{
  1597  			name: "test-2",
  1598  			args: args{
  1599  				address: "http://foo.bar",
  1600  			},
  1601  			wantSvcName:   "foo",
  1602  			wantNamespace: "bar",
  1603  			wantErr:       false,
  1604  		},
  1605  		{
  1606  			name: "test-3",
  1607  			args: args{
  1608  				address: "http://foo",
  1609  			},
  1610  			wantErr: true,
  1611  		},
  1612  		{
  1613  			name: "test-4",
  1614  			args: args{
  1615  				address: "http://foo.bar:5679/",
  1616  			},
  1617  			wantSvcName:   "foo",
  1618  			wantNamespace: "bar",
  1619  			wantErr:       false,
  1620  		},
  1621  		{
  1622  			name: "test-5",
  1623  			args: args{
  1624  				address: "http://foo:2379",
  1625  			},
  1626  			wantErr: true,
  1627  		},
  1628  	}
  1629  	for _, tt := range tests {
  1630  		t.Run(tt.name, func(t *testing.T) {
  1631  			got, got1, err := SplitK8sServiceURL(tt.args.address)
  1632  			if (err != nil) != tt.wantErr {
  1633  				t.Errorf("SplitK8sServiceURL() error = %v, wantErr %v", err, tt.wantErr)
  1634  				return
  1635  			}
  1636  			if got != tt.wantSvcName {
  1637  				t.Errorf("SplitK8sServiceURL() got = %v, want %v", got, tt.wantSvcName)
  1638  			}
  1639  			if got1 != tt.wantNamespace {
  1640  				t.Errorf("SplitK8sServiceURL() got1 = %v, want %v", got1, tt.wantNamespace)
  1641  			}
  1642  		})
  1643  	}
  1644  }