github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/topology/dynamic_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 topology
    22  
    23  import (
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/cluster/client"
    29  	"github.com/m3db/m3/src/cluster/services"
    30  	"github.com/m3db/m3/src/cluster/shard"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func testSetup(ctrl *gomock.Controller) (DynamicOptions, *testWatch) {
    38  	opts := NewDynamicOptions()
    39  
    40  	watch := newTestWatch(ctrl, time.Millisecond, time.Millisecond, 100, 100)
    41  	mockCSServices := services.NewMockServices(ctrl)
    42  	mockCSServices.EXPECT().Watch(opts.ServiceID(), opts.QueryOptions()).Return(watch, nil)
    43  
    44  	mockCSClient := client.NewMockClient(ctrl)
    45  	mockCSClient.EXPECT().Services(gomock.Any()).Return(mockCSServices, nil)
    46  	opts = opts.SetConfigServiceClient(mockCSClient)
    47  	return opts, watch
    48  }
    49  
    50  func testFinish(ctrl *gomock.Controller, watch *testWatch) {
    51  	watch.Lock()
    52  	defer watch.Unlock()
    53  	// Ensure only single writers to gomock.Controller
    54  	ctrl.Finish()
    55  }
    56  
    57  func TestInitNoTimeout(t *testing.T) {
    58  	ctrl := gomock.NewController(t)
    59  	opts, w := testSetup(ctrl)
    60  	defer testFinish(ctrl, w)
    61  
    62  	go w.run()
    63  	topo, err := newDynamicTopology(opts)
    64  
    65  	assert.NoError(t, err)
    66  	assert.NotNil(t, topo)
    67  	topo.Close()
    68  	// safe to close again
    69  	topo.Close()
    70  }
    71  
    72  func TestBack(t *testing.T) {
    73  	ctrl := gomock.NewController(t)
    74  	opts, w := testSetup(ctrl)
    75  	defer testFinish(ctrl, w)
    76  
    77  	go w.run()
    78  	topo, err := newDynamicTopology(opts)
    79  	assert.NoError(t, err)
    80  	mw, err := topo.Watch()
    81  	assert.NoError(t, err)
    82  	assert.Equal(t, 2, mw.Get().Replicas())
    83  	assert.Equal(t, 3, mw.Get().HostsLen())
    84  
    85  	opts, w = testSetup(ctrl)
    86  	close(w.ch)
    87  	topo, err = newDynamicTopology(opts)
    88  	assert.Error(t, err)
    89  }
    90  
    91  func TestGet(t *testing.T) {
    92  	ctrl := gomock.NewController(t)
    93  	opts, w := testSetup(ctrl)
    94  	defer testFinish(ctrl, w)
    95  
    96  	go w.run()
    97  	topo, err := newDynamicTopology(opts)
    98  	assert.NoError(t, err)
    99  
   100  	m := topo.Get()
   101  	assert.Equal(t, 2, m.Replicas())
   102  }
   103  
   104  func TestWatch(t *testing.T) {
   105  	ctrl := gomock.NewController(t)
   106  	opts, watch := testSetup(ctrl)
   107  	defer testFinish(ctrl, watch)
   108  
   109  	go watch.run()
   110  	topo, err := newDynamicTopology(opts)
   111  	assert.NoError(t, err)
   112  
   113  	w, err := topo.Watch()
   114  	require.NoError(t, err)
   115  	<-w.C()
   116  	m := w.Get()
   117  	assert.Equal(t, 2, m.Replicas())
   118  	assert.Equal(t, 2, w.Get().Replicas())
   119  
   120  	for range w.C() {
   121  		assert.Equal(t, 2, w.Get().Replicas())
   122  	}
   123  }
   124  
   125  func TestGetUniqueShardsAndReplicas(t *testing.T) {
   126  	goodInstances := goodInstances()
   127  
   128  	shards, err := validateInstances(goodInstances, 2, 3)
   129  	assert.NoError(t, err)
   130  	assert.Equal(t, 3, len(shards))
   131  
   132  	goodInstances[0].SetShards(nil)
   133  	_, err = validateInstances(goodInstances, 2, 3)
   134  	assert.Equal(t, errInstanceHasNoShardsAssignment, err)
   135  
   136  	goodInstances[0].SetShards(shard.NewShards(
   137  		[]shard.Shard{
   138  			shard.NewShard(0),
   139  			shard.NewShard(1),
   140  			shard.NewShard(3),
   141  		}))
   142  	_, err = validateInstances(goodInstances, 2, 3)
   143  	assert.Equal(t, errUnexpectedShard, err)
   144  
   145  	// got h1: 1, h2: 1, 2, h3 0,2, missing a replica for 1
   146  	goodInstances[0].SetShards(shard.NewShards(
   147  		[]shard.Shard{
   148  			shard.NewShard(1),
   149  		}))
   150  	_, err = validateInstances(goodInstances, 2, 3)
   151  	assert.Equal(t, errNotEnoughReplicasForShard, err)
   152  
   153  	goodInstances[0].SetShards(shard.NewShards(
   154  		[]shard.Shard{
   155  			shard.NewShard(0),
   156  		}))
   157  	goodInstances[1].SetShards(shard.NewShards(
   158  		[]shard.Shard{
   159  			shard.NewShard(2),
   160  		}))
   161  	_, err = validateInstances(goodInstances, 2, 3)
   162  	// got h1:0, h2: 2, h3 0,2, missing 1
   163  	assert.Equal(t, errMissingShard, err)
   164  }
   165  
   166  type testWatch struct {
   167  	sync.RWMutex
   168  
   169  	ctrl                  *gomock.Controller
   170  	data                  services.Service
   171  	firstDelay, nextDelay time.Duration
   172  	errAfter, closeAfter  int
   173  	currentCalled         int
   174  	ch                    chan struct{}
   175  }
   176  
   177  func newTestWatch(ctrl *gomock.Controller, firstDelay, nextDelay time.Duration, errAfter, closeAfter int) *testWatch {
   178  	w := testWatch{ctrl: ctrl, firstDelay: firstDelay, nextDelay: nextDelay, errAfter: errAfter, closeAfter: closeAfter}
   179  	w.ch = make(chan struct{})
   180  	return &w
   181  }
   182  
   183  func (w *testWatch) run() {
   184  	time.Sleep(w.firstDelay)
   185  	w.update()
   186  	for w.currentCalled < w.closeAfter {
   187  		time.Sleep(w.nextDelay)
   188  		w.update()
   189  	}
   190  	close(w.ch)
   191  }
   192  
   193  func (w *testWatch) update() {
   194  	w.Lock()
   195  	if w.currentCalled < w.errAfter {
   196  		w.data = getMockService(w.ctrl)
   197  	} else {
   198  		w.data = nil
   199  	}
   200  	w.currentCalled++
   201  	w.Unlock()
   202  	w.ch <- struct{}{}
   203  }
   204  
   205  func (w *testWatch) Close() {}
   206  
   207  func (w *testWatch) Get() services.Service {
   208  	w.RLock()
   209  	defer w.RUnlock()
   210  	return w.data
   211  }
   212  
   213  func (w *testWatch) C() <-chan struct{} {
   214  	return w.ch
   215  }
   216  
   217  func getMockService(ctrl *gomock.Controller) services.Service {
   218  	mockService := services.NewMockService(ctrl)
   219  
   220  	mockReplication := services.NewMockServiceReplication(ctrl)
   221  	mockReplication.EXPECT().Replicas().Return(2).AnyTimes()
   222  	mockService.EXPECT().Replication().Return(mockReplication).AnyTimes()
   223  
   224  	mockSharding := services.NewMockServiceSharding(ctrl)
   225  	mockSharding.EXPECT().NumShards().Return(3).AnyTimes()
   226  	mockService.EXPECT().Sharding().Return(mockSharding).AnyTimes()
   227  
   228  	mockService.EXPECT().Instances().Return(goodInstances()).AnyTimes()
   229  
   230  	return mockService
   231  }
   232  
   233  func goodInstances() []services.ServiceInstance {
   234  	i1 := services.NewServiceInstance().SetShards(shard.NewShards(
   235  		[]shard.Shard{
   236  			shard.NewShard(0),
   237  			shard.NewShard(1),
   238  		})).SetInstanceID("h1").SetEndpoint("h1:9000")
   239  
   240  	i2 := services.NewServiceInstance().SetShards(shard.NewShards(
   241  		[]shard.Shard{
   242  			shard.NewShard(1),
   243  			shard.NewShard(2),
   244  		})).SetInstanceID("h2").SetEndpoint("h2:9000")
   245  
   246  	i3 := services.NewServiceInstance().SetShards(shard.NewShards(
   247  		[]shard.Shard{
   248  			shard.NewShard(2),
   249  			shard.NewShard(0),
   250  		})).SetInstanceID("h3").SetEndpoint("h3:9000")
   251  
   252  	return []services.ServiceInstance{i1, i2, i3}
   253  }