github.com/m3db/m3@v1.5.0/src/cluster/services/heartbeat/etcd/store_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package etcd
    22  
    23  import (
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/m3db/m3/src/cluster/mocks"
    28  	"github.com/m3db/m3/src/cluster/placement"
    29  	"github.com/m3db/m3/src/cluster/services"
    30  
    31  	"github.com/stretchr/testify/require"
    32  	clientv3 "go.etcd.io/etcd/client/v3"
    33  	"go.etcd.io/etcd/tests/v3/framework/integration"
    34  )
    35  
    36  func TestKeys(t *testing.T) {
    37  	sid := services.NewServiceID().SetName("service")
    38  	id := "instance"
    39  
    40  	require.Equal(t, "_hb/service", servicePrefix(sid))
    41  	require.Equal(t, "_hb/service/instance", heartbeatKey(sid, id))
    42  
    43  	sid = sid.SetEnvironment("test")
    44  	require.Equal(t, "_hb/test/service/instance", heartbeatKey(sid, id))
    45  	require.Equal(t, "_hb/test/service", servicePrefix(sid))
    46  	require.Equal(t, "instance", instanceFromKey(heartbeatKey(sid, id), servicePrefix(sid)))
    47  }
    48  
    49  func TestReuseLeaseID(t *testing.T) {
    50  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
    51  	ec, opts, closeFn := testStore(t, sid)
    52  	defer closeFn()
    53  
    54  	i1 := placement.NewInstance().SetID("i1")
    55  
    56  	c, err := NewStore(ec, opts)
    57  	require.NoError(t, err)
    58  	store := c.(*client)
    59  
    60  	err = store.Heartbeat(i1, time.Minute)
    61  	require.NoError(t, err)
    62  
    63  	store.RLock()
    64  	require.Equal(t, 1, len(store.cache.leases))
    65  	var leaseID clientv3.LeaseID
    66  	for _, leases := range store.cache.leases {
    67  		for _, v := range leases {
    68  			leaseID = v
    69  		}
    70  	}
    71  	store.RUnlock()
    72  
    73  	err = store.Heartbeat(i1, time.Minute)
    74  	require.NoError(t, err)
    75  
    76  	store.RLock()
    77  	require.Equal(t, 1, len(store.cache.leases))
    78  	for _, leases := range store.cache.leases {
    79  		for _, v := range leases {
    80  			require.Equal(t, leaseID, v)
    81  		}
    82  	}
    83  	store.RUnlock()
    84  }
    85  
    86  func TestHeartbeat(t *testing.T) {
    87  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
    88  	ec, opts, closeFn := testStore(t, sid)
    89  	defer closeFn()
    90  
    91  	i1 := placement.NewInstance().SetID("i1")
    92  	i2 := placement.NewInstance().SetID("i2")
    93  
    94  	c, err := NewStore(ec, opts)
    95  	require.NoError(t, err)
    96  	store := c.(*client)
    97  
    98  	ids, err := store.Get()
    99  	require.Equal(t, 0, len(ids))
   100  	require.NoError(t, err)
   101  
   102  	err = store.Heartbeat(i1, 2*time.Second)
   103  	require.NoError(t, err)
   104  	err = store.Heartbeat(i2, time.Minute)
   105  	require.NoError(t, err)
   106  
   107  	ids, err = store.Get()
   108  	require.NoError(t, err)
   109  	require.Equal(t, 2, len(ids))
   110  	require.Contains(t, ids, "i1")
   111  	require.Contains(t, ids, "i2")
   112  
   113  	// ensure that both Get and GetInstances return the same instances
   114  	// with their respective serialization methods
   115  	instances, err := store.GetInstances()
   116  	require.NoError(t, err)
   117  	require.Equal(t, 2, len(instances))
   118  	require.Contains(t, instances, i1)
   119  	require.Contains(t, instances, i2)
   120  
   121  	for {
   122  		ids, err = store.Get()
   123  		require.NoError(t, err)
   124  		instances, err2 := store.GetInstances()
   125  		require.NoError(t, err)
   126  		require.NoError(t, err2)
   127  		if len(ids) == 1 && len(instances) == 1 {
   128  			break
   129  		}
   130  		time.Sleep(10 * time.Millisecond)
   131  	}
   132  	require.Equal(t, 1, len(ids))
   133  	require.NotContains(t, ids, "i1")
   134  	require.Contains(t, ids, "i2")
   135  
   136  	err = store.Delete(i2.ID())
   137  	require.NoError(t, err)
   138  
   139  	ids, err = store.Get()
   140  	require.NoError(t, err)
   141  	require.Equal(t, 0, len(ids))
   142  	require.NotContains(t, ids, "i1")
   143  	require.NotContains(t, ids, "i2")
   144  }
   145  
   146  func TestDelete(t *testing.T) {
   147  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
   148  	ec, opts, closeFn := testStore(t, sid)
   149  	defer closeFn()
   150  
   151  	i1 := placement.NewInstance().SetID("i1")
   152  	i2 := placement.NewInstance().SetID("i2")
   153  
   154  	c, err := NewStore(ec, opts)
   155  	require.NoError(t, err)
   156  	store := c.(*client)
   157  
   158  	err = store.Heartbeat(i1, time.Hour)
   159  	require.NoError(t, err)
   160  
   161  	err = store.Heartbeat(i2, time.Hour)
   162  	require.NoError(t, err)
   163  
   164  	ids, err := store.Get()
   165  	require.NoError(t, err)
   166  	require.Equal(t, 2, len(ids))
   167  	require.Contains(t, ids, "i1")
   168  	require.Contains(t, ids, "i2")
   169  
   170  	instances, err := store.GetInstances()
   171  	require.NoError(t, err)
   172  	require.Equal(t, 2, len(instances))
   173  	require.Contains(t, instances, i1)
   174  	require.Contains(t, instances, i2)
   175  
   176  	err = store.Delete(i1.ID())
   177  	require.NoError(t, err)
   178  
   179  	err = store.Delete(i1.ID())
   180  	require.Error(t, err)
   181  
   182  	ids, err = store.Get()
   183  	require.NoError(t, err)
   184  	require.Equal(t, 1, len(ids))
   185  	require.Contains(t, ids, "i2")
   186  
   187  	instances, err = store.GetInstances()
   188  	require.NoError(t, err)
   189  	require.Equal(t, 1, len(instances))
   190  	require.Contains(t, instances, i2)
   191  
   192  	err = store.Heartbeat(i1, time.Hour)
   193  	require.NoError(t, err)
   194  
   195  	for {
   196  		ids, _ = store.Get()
   197  		instances, _ = store.GetInstances()
   198  		if len(ids) == 2 && len(instances) == 2 {
   199  			break
   200  		}
   201  	}
   202  }
   203  
   204  func TestWatch(t *testing.T) {
   205  	sid := services.NewServiceID().SetName("s2").SetEnvironment("e2")
   206  	ec, opts, closeFn := testStore(t, sid)
   207  	defer closeFn()
   208  
   209  	store, err := NewStore(ec, opts)
   210  	require.NoError(t, err)
   211  
   212  	i1 := placement.NewInstance().SetID("i1")
   213  	i2 := placement.NewInstance().SetID("i2")
   214  
   215  	w1, err := store.Watch()
   216  	require.NoError(t, err)
   217  	<-w1.C()
   218  	require.Empty(t, w1.Get())
   219  
   220  	err = store.Heartbeat(i1, 2*time.Second)
   221  	require.NoError(t, err)
   222  
   223  	for range w1.C() {
   224  		if len(w1.Get().([]string)) == 1 {
   225  			break
   226  		}
   227  	}
   228  	require.Equal(t, []string{"i1"}, w1.Get())
   229  
   230  	err = store.Heartbeat(i2, 2*time.Second)
   231  	require.NoError(t, err)
   232  
   233  	for range w1.C() {
   234  		if len(w1.Get().([]string)) == 2 {
   235  			break
   236  		}
   237  	}
   238  	require.Equal(t, []string{"i1", "i2"}, w1.Get())
   239  
   240  	for range w1.C() {
   241  		if len(w1.Get().([]string)) == 0 {
   242  			break
   243  		}
   244  	}
   245  
   246  	err = store.Heartbeat(i2, time.Second)
   247  	require.NoError(t, err)
   248  
   249  	for range w1.C() {
   250  		val := w1.Get().([]string)
   251  		if len(val) == 1 && val[0] == "i2" {
   252  			break
   253  		}
   254  	}
   255  
   256  	w1.Close()
   257  }
   258  
   259  func TestWatchClose(t *testing.T) {
   260  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
   261  	ec, opts, closeFn := testStore(t, sid)
   262  	defer closeFn()
   263  
   264  	store, err := NewStore(ec, opts.SetWatchChanCheckInterval(10*time.Millisecond))
   265  	require.NoError(t, err)
   266  
   267  	i1 := placement.NewInstance().SetID("i1")
   268  	i2 := placement.NewInstance().SetID("i2")
   269  
   270  	err = store.Heartbeat(i1, 100*time.Second)
   271  	require.NoError(t, err)
   272  
   273  	w1, err := store.Watch()
   274  	require.NoError(t, err)
   275  	<-w1.C()
   276  	require.Equal(t, []string{"i1"}, w1.Get())
   277  
   278  	c := store.(*client)
   279  	_, ok := c.watchables["_hb/e1/s1"]
   280  	require.True(t, ok)
   281  
   282  	// closing w1 will close the go routine for the watch updates
   283  	w1.Close()
   284  
   285  	// waits until the original watchable is cleaned up
   286  	for {
   287  		c.RLock()
   288  		_, ok = c.watchables["_hb/e1/s1"]
   289  		c.RUnlock()
   290  		if !ok {
   291  			break
   292  		}
   293  	}
   294  
   295  	// getting a new watch will create a new watchale and thread to watch for updates
   296  	w2, err := store.Watch()
   297  	require.NoError(t, err)
   298  	<-w2.C()
   299  	require.Equal(t, []string{"i1"}, w2.Get())
   300  
   301  	// verify that w1 will no longer be updated because the original watchable is closed
   302  	err = store.Heartbeat(i2, 100*time.Second)
   303  	require.NoError(t, err)
   304  	<-w2.C()
   305  	require.Equal(t, []string{"i1", "i2"}, w2.Get())
   306  	require.Equal(t, []string{"i1"}, w1.Get())
   307  
   308  	w1.Close()
   309  	w2.Close()
   310  }
   311  
   312  func TestMultipleWatchesFromNotExist(t *testing.T) {
   313  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
   314  	ec, opts, closeFn := testStore(t, sid)
   315  	defer closeFn()
   316  
   317  	store, err := NewStore(ec, opts)
   318  	require.NoError(t, err)
   319  
   320  	i1 := placement.NewInstance().SetID("i1")
   321  	i2 := placement.NewInstance().SetID("i2")
   322  
   323  	w1, err := store.Watch()
   324  	require.NoError(t, err)
   325  	<-w1.C()
   326  	require.Empty(t, w1.Get())
   327  
   328  	w2, err := store.Watch()
   329  	require.NoError(t, err)
   330  	<-w2.C()
   331  	require.Empty(t, w2.Get())
   332  
   333  	err = store.Heartbeat(i1, 2*time.Second)
   334  	require.NoError(t, err)
   335  
   336  	for {
   337  		g := w1.Get()
   338  		if g == nil {
   339  			continue
   340  		}
   341  		if len(g.([]string)) == 1 {
   342  			break
   343  		}
   344  	}
   345  
   346  	<-w1.C()
   347  	require.Equal(t, 0, len(w1.C()))
   348  	require.Equal(t, []string{"i1"}, w1.Get())
   349  
   350  	<-w2.C()
   351  	require.Equal(t, 0, len(w2.C()))
   352  	require.Equal(t, []string{"i1"}, w2.Get())
   353  
   354  	err = store.Heartbeat(i2, 4*time.Second)
   355  	require.NoError(t, err)
   356  
   357  	for {
   358  		if len(w1.Get().([]string)) == 2 {
   359  			break
   360  		}
   361  	}
   362  	<-w1.C()
   363  	require.Equal(t, []string{"i1", "i2"}, w1.Get())
   364  	<-w2.C()
   365  	require.Equal(t, []string{"i1", "i2"}, w2.Get())
   366  
   367  	for {
   368  		if len(w1.Get().([]string)) == 1 {
   369  			break
   370  		}
   371  	}
   372  	<-w1.C()
   373  	require.Equal(t, []string{"i2"}, w1.Get())
   374  	<-w2.C()
   375  	require.Equal(t, []string{"i2"}, w2.Get())
   376  
   377  	for {
   378  		if len(w1.Get().([]string)) == 0 {
   379  			break
   380  		}
   381  	}
   382  	<-w1.C()
   383  	require.Equal(t, []string{}, w1.Get())
   384  	<-w2.C()
   385  	require.Equal(t, []string{}, w2.Get())
   386  
   387  	w1.Close()
   388  	w2.Close()
   389  }
   390  
   391  func TestWatchNonBlocking(t *testing.T) {
   392  	sid := services.NewServiceID().SetName("s1").SetEnvironment("e1")
   393  	ec, opts, closeFn := testStore(t, sid)
   394  	defer closeFn()
   395  
   396  	opts = opts.SetWatchChanResetInterval(200 * time.Millisecond).SetWatchChanInitTimeout(200 * time.Millisecond)
   397  
   398  	i1 := placement.NewInstance().SetID("i1")
   399  	i2 := placement.NewInstance().SetID("i2")
   400  
   401  	store, err := NewStore(ec, opts)
   402  	require.NoError(t, err)
   403  	c := store.(*client)
   404  
   405  	err = store.Heartbeat(i1, 100*time.Second)
   406  	require.NoError(t, err)
   407  
   408  	failTotal := 1
   409  	mw := mocks.NewBlackholeWatcher(ec, failTotal, func() { time.Sleep(time.Minute) })
   410  	c.watcher = mw
   411  
   412  	before := time.Now()
   413  	w1, err := c.Watch()
   414  	require.WithinDuration(t, time.Now(), before, 100*time.Millisecond)
   415  	require.NoError(t, err)
   416  
   417  	// watch channel will error out, but Get() will be tried
   418  	select {
   419  	case <-w1.C():
   420  	case <-time.After(200 * time.Millisecond):
   421  		require.Fail(t, "notification came too late")
   422  	}
   423  	require.Equal(t, []string{"i1"}, w1.Get())
   424  
   425  	time.Sleep(5 * (opts.WatchChanResetInterval() + opts.WatchChanInitTimeout()))
   426  
   427  	err = store.Heartbeat(i2, 100*time.Second)
   428  	require.NoError(t, err)
   429  
   430  	for {
   431  		if len(w1.Get().([]string)) == 2 {
   432  			break
   433  		}
   434  	}
   435  	require.Equal(t, []string{"i1", "i2"}, w1.Get())
   436  
   437  	w1.Close()
   438  }
   439  
   440  func testStore(t *testing.T, sid services.ServiceID) (*clientv3.Client, Options, func()) {
   441  	integration.BeforeTestExternal(t)
   442  	ecluster := integration.NewCluster(t, &integration.ClusterConfig{Size: 1})
   443  	ec := ecluster.RandClient()
   444  
   445  	closer := func() {
   446  		ecluster.Terminate(t)
   447  		ec.Close()
   448  	}
   449  	return ec, NewOptions().SetServiceID(sid).SetRequestTimeout(20 * time.Second), closer
   450  }