github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/session_topology_test.go (about)

     1  // Copyright (c) 2017 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 client
    22  
    23  import (
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/cluster/services"
    29  	"github.com/m3db/m3/src/cluster/shard"
    30  	"github.com/m3db/m3/src/dbnode/integration/fake"
    31  	"github.com/m3db/m3/src/dbnode/topology"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  	"github.com/uber-go/tally"
    37  )
    38  
    39  type testHostQueues struct {
    40  	sync.RWMutex
    41  	queues map[string][]*MockhostQueue
    42  }
    43  
    44  func newTestHostQueues() *testHostQueues {
    45  	return &testHostQueues{
    46  		queues: make(map[string][]*MockhostQueue),
    47  	}
    48  }
    49  
    50  func (q *testHostQueues) add(id string, value *MockhostQueue) {
    51  	q.Lock()
    52  	defer q.Unlock()
    53  	q.queues[id] = append(q.queues[id], value)
    54  }
    55  
    56  func (q *testHostQueues) get(id string) []*MockhostQueue {
    57  	q.RLock()
    58  	defer q.RUnlock()
    59  	return q.queues[id]
    60  }
    61  
    62  func (q *testHostQueues) numUnique() int {
    63  	q.RLock()
    64  	defer q.RUnlock()
    65  	return len(q.queues)
    66  }
    67  
    68  func TestSessionTopologyChangeCreatesNewClosesOldHostQueues(t *testing.T) {
    69  	ctrl := gomock.NewController(t)
    70  	defer ctrl.Finish()
    71  
    72  	avail := shard.Available
    73  
    74  	node := func(id string, shards []uint32) services.ServiceInstance {
    75  		result := services.NewServiceInstance().SetInstanceID(id)
    76  		resultShards := make([]shard.Shard, len(shards))
    77  		for i, id := range shards {
    78  			resultShards[i] = shard.NewShard(id).SetState(avail)
    79  		}
    80  		return result.SetShards(shard.NewShards(resultShards))
    81  	}
    82  
    83  	svc := fake.NewM3ClusterService().
    84  		SetInstances([]services.ServiceInstance{
    85  			node("testhost0", []uint32{0, 1}),
    86  			node("testhost1", []uint32{2, 3}),
    87  		}).
    88  		SetReplication(services.NewServiceReplication().SetReplicas(1)).
    89  		SetSharding(services.NewServiceSharding().SetNumShards(4))
    90  
    91  	svcs := fake.NewM3ClusterServices()
    92  	svcs.RegisterService("m3db", svc)
    93  
    94  	topoOpts := topology.NewDynamicOptions().
    95  		SetConfigServiceClient(fake.NewM3ClusterClient(svcs, nil))
    96  	topoInit := topology.NewDynamicInitializer(topoOpts)
    97  
    98  	var testScopeTags map[string]string
    99  	scope := tally.NewTestScope("", testScopeTags)
   100  
   101  	opts := newSessionTestOptions().
   102  		SetTopologyInitializer(topoInit)
   103  	opts = opts.SetInstrumentOptions(opts.InstrumentOptions().
   104  		SetMetricsScope(scope))
   105  
   106  	s, err := newSession(opts)
   107  	require.NoError(t, err)
   108  
   109  	createdQueues := newTestHostQueues()
   110  	closedQueues := newTestHostQueues()
   111  
   112  	session := s.(*session)
   113  	session.newHostQueueFn = func(
   114  		host topology.Host,
   115  		opts hostQueueOpts,
   116  	) (hostQueue, error) {
   117  		queue := NewMockhostQueue(ctrl)
   118  		createdQueues.add(host.ID(), queue)
   119  
   120  		queue.EXPECT().Open()
   121  		queue.EXPECT().Host().Return(host).AnyTimes()
   122  		queue.EXPECT().ConnectionCount().Return(opts.opts.MinConnectionCount()).AnyTimes()
   123  		queue.EXPECT().Close().Do(func() {
   124  			closedQueues.add(host.ID(), queue)
   125  		})
   126  		return queue, nil
   127  	}
   128  
   129  	require.NoError(t, session.Open())
   130  	defer func() {
   131  		assert.NoError(t, session.Close())
   132  	}()
   133  
   134  	// Assert created two
   135  	require.Equal(t, 2, createdQueues.numUnique())
   136  	require.Equal(t, 0, closedQueues.numUnique())
   137  	require.Equal(t, 1, len(createdQueues.get("testhost0")))
   138  	require.Equal(t, 1, len(createdQueues.get("testhost1")))
   139  
   140  	// Change topology with one new instance and close one instance
   141  	svc.SetInstances([]services.ServiceInstance{
   142  		node("testhost1", []uint32{2, 3}),
   143  		node("testhost2", []uint32{0, 1}),
   144  	})
   145  	svcs.NotifyServiceUpdate("m3db")
   146  
   147  	// Wait for topology to be processed
   148  	testScopeCounterKey := tally.KeyForPrefixedStringMap("topology.updated-success", testScopeTags)
   149  	for {
   150  		updated, ok := scope.Snapshot().Counters()[testScopeCounterKey]
   151  		if ok && updated.Value() > 0 {
   152  			break
   153  		}
   154  		time.Sleep(10 * time.Millisecond)
   155  	}
   156  
   157  	// Wait for the close to occur
   158  	for closedQueues.numUnique() < 1 {
   159  		time.Sleep(10 * time.Millisecond)
   160  	}
   161  
   162  	// Assert create third and closed first
   163  	require.Equal(t, 3, createdQueues.numUnique())
   164  	require.Equal(t, 1, closedQueues.numUnique())
   165  	require.Equal(t, 1, len(createdQueues.get("testhost0")))
   166  	require.Equal(t, 1, len(createdQueues.get("testhost1")))
   167  	require.Equal(t, 1, len(createdQueues.get("testhost2")))
   168  	require.Equal(t, 1, len(closedQueues.get("testhost0")))
   169  }