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

     1  // Copyright (c) 2019 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  	"errors"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/golang/mock/gomock"
    29  	"github.com/stretchr/testify/suite"
    30  
    31  	"github.com/m3db/m3/src/dbnode/environment"
    32  	"github.com/m3db/m3/src/dbnode/topology"
    33  	"github.com/m3db/m3/src/m3ninx/doc"
    34  	"github.com/m3db/m3/src/x/ident"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  	xsync "github.com/m3db/m3/src/x/sync"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  )
    39  
    40  type replicatedSessionTestSuite struct {
    41  	suite.Suite
    42  	mockCtrl          *gomock.Controller
    43  	replicatedSession *replicatedSession
    44  }
    45  
    46  func newSessionFnWithSession(s clientSession) newSessionFn {
    47  	return func(Options) (clientSession, error) {
    48  		return s, nil
    49  	}
    50  }
    51  
    52  func newTopologyInitializer() topology.Initializer {
    53  	return topology.NewStaticInitializer(topology.NewStaticOptions())
    54  }
    55  
    56  func optionsWithAsyncSessions(hasSync bool, asyncCount int) Options {
    57  	topoInits := make([]topology.Initializer, 0, asyncCount)
    58  	for i := 0; i < asyncCount; i++ {
    59  		topoInits = append(topoInits, newTopologyInitializer())
    60  	}
    61  	options := NewAdminOptions().
    62  		SetAsyncTopologyInitializers(topoInits)
    63  	if asyncCount > 0 {
    64  		workerPool, err := xsync.NewPooledWorkerPool(10,
    65  			xsync.NewPooledWorkerPoolOptions())
    66  		if err != nil {
    67  			panic(err)
    68  		}
    69  		workerPool.Init()
    70  		options = options.SetAsyncWriteWorkerPool(workerPool)
    71  	}
    72  
    73  	if hasSync {
    74  		options = options.SetTopologyInitializer(newTopologyInitializer())
    75  	}
    76  	return options
    77  }
    78  
    79  func (s *replicatedSessionTestSuite) initReplicatedSession(opts Options, newSessionFunc newSessionFn) {
    80  	topoInits := opts.AsyncTopologyInitializers()
    81  	overrides := make([]environment.ClientOverrides, len(topoInits))
    82  	session, err := newReplicatedSession(
    83  		opts,
    84  		NewOptionsForAsyncClusters(opts, topoInits, overrides),
    85  		withNewSessionFn(newSessionFunc),
    86  	)
    87  	s.replicatedSession = session.(*replicatedSession)
    88  	s.NoError(err)
    89  }
    90  
    91  func (s *replicatedSessionTestSuite) SetupTest() {
    92  	s.mockCtrl = gomock.NewController(s.T())
    93  }
    94  
    95  func (s *replicatedSessionTestSuite) TearDownTest() {
    96  	s.mockCtrl.Finish()
    97  }
    98  
    99  func TestReplicatedSessionTestSuite(t *testing.T) {
   100  	suite.Run(t, new(replicatedSessionTestSuite))
   101  }
   102  
   103  var constructorCreatesSessionsTests = []struct {
   104  	options       Options
   105  	expectedCount int
   106  }{
   107  	{
   108  		options:       optionsWithAsyncSessions(false, 0),
   109  		expectedCount: 0,
   110  	},
   111  	{
   112  		options:       optionsWithAsyncSessions(false, 2),
   113  		expectedCount: 2,
   114  	},
   115  	{
   116  		options:       optionsWithAsyncSessions(true, 3),
   117  		expectedCount: 4,
   118  	},
   119  	{
   120  		options:       optionsWithAsyncSessions(true, 0),
   121  		expectedCount: 1,
   122  	},
   123  }
   124  
   125  func (s *replicatedSessionTestSuite) TestConstructorCreatesSessions() {
   126  	for _, tt := range constructorCreatesSessionsTests {
   127  		count := 0
   128  		var newSessionFunc = func(opts Options) (clientSession, error) {
   129  			count = count + 1
   130  			return NewMockclientSession(s.mockCtrl), nil
   131  		}
   132  
   133  		s.initReplicatedSession(tt.options, newSessionFunc)
   134  		s.Equal(tt.expectedCount, count)
   135  	}
   136  }
   137  
   138  func (s *replicatedSessionTestSuite) TestSetSession() {
   139  	opts := optionsWithAsyncSessions(false, 0)
   140  	session := NewMockclientSession(s.mockCtrl)
   141  	newSessionFunc := newSessionFnWithSession(session)
   142  	s.initReplicatedSession(opts, newSessionFunc)
   143  
   144  	topoInit := newTopologyInitializer()
   145  	sessionOpts := NewMockAdminOptions(s.mockCtrl)
   146  	sessionOpts.EXPECT().TopologyInitializer().Return(topoInit)
   147  
   148  	s.Nil(s.replicatedSession.session)
   149  	err := s.replicatedSession.setSession(sessionOpts)
   150  	s.NoError(err)
   151  	s.Equal(session, s.replicatedSession.session)
   152  }
   153  
   154  func (s *replicatedSessionTestSuite) TestSetAsyncSessions() {
   155  	opts := optionsWithAsyncSessions(false, 0)
   156  	session := NewMockclientSession(s.mockCtrl)
   157  	newSessionFunc := newSessionFnWithSession(session)
   158  
   159  	s.initReplicatedSession(opts, newSessionFunc)
   160  
   161  	sessionOpts := []Options{}
   162  	for i := 0; i < 3; i++ {
   163  		o := NewMockAdminOptions(s.mockCtrl)
   164  		o.EXPECT().InstrumentOptions().AnyTimes().Return(instrument.NewOptions())
   165  		o.EXPECT().SetInstrumentOptions(gomock.Any()).Return(o)
   166  		sessionOpts = append(sessionOpts, o)
   167  	}
   168  
   169  	s.Len(s.replicatedSession.asyncSessions, 0)
   170  	err := s.replicatedSession.setAsyncSessions(sessionOpts)
   171  	s.NoError(err)
   172  	s.Len(s.replicatedSession.asyncSessions, 3)
   173  }
   174  
   175  func (s *replicatedSessionTestSuite) TestReplicate() {
   176  	var (
   177  		asyncCount = 2
   178  		namespace  = ident.StringID("foo")
   179  		id         = ident.StringID("bar")
   180  		now        = xtime.Now()
   181  		value      = float64(123)
   182  		unit       = xtime.Nanosecond
   183  		annotation = []byte("annotation")
   184  	)
   185  
   186  	newSessionFunc := func(opts Options) (clientSession, error) {
   187  		s := NewMockclientSession(s.mockCtrl)
   188  		s.EXPECT().Write(
   189  			ident.NewIDMatcher(namespace.String()),
   190  			ident.NewIDMatcher(id.String()),
   191  			now, value, unit, annotation,
   192  		).Return(nil)
   193  		return s, nil
   194  	}
   195  
   196  	opts := optionsWithAsyncSessions(true, asyncCount)
   197  	s.initReplicatedSession(opts, newSessionFunc)
   198  	s.replicatedSession.outCh = make(chan error)
   199  
   200  	err := s.replicatedSession.Write(namespace, id, now, value, unit, annotation)
   201  	s.NoError(err)
   202  
   203  	s.waitForAsyncSessions(asyncCount)
   204  }
   205  
   206  func (s *replicatedSessionTestSuite) TestReplicateTagged() {
   207  	var (
   208  		asyncCount = 2
   209  		namespace  = ident.StringID("foo")
   210  		id         = ident.StringID("bar")
   211  		tags       = ident.NewFieldsTagsIterator([]doc.Field{{Name: []byte("k"), Value: []byte("v")}})
   212  		now        = xtime.Now()
   213  		value      = float64(123)
   214  		unit       = xtime.Nanosecond
   215  		annotation = []byte("annotation")
   216  	)
   217  
   218  	newSessionFunc := func(opts Options) (clientSession, error) {
   219  		s := NewMockclientSession(s.mockCtrl)
   220  		s.EXPECT().WriteTagged(
   221  			ident.NewIDMatcher(namespace.String()),
   222  			ident.NewIDMatcher(id.String()),
   223  			ident.NewTagIterMatcher(tags),
   224  			now, value, unit, annotation,
   225  		).Return(nil)
   226  		return s, nil
   227  	}
   228  
   229  	opts := optionsWithAsyncSessions(true, asyncCount)
   230  	s.initReplicatedSession(opts, newSessionFunc)
   231  	s.replicatedSession.outCh = make(chan error)
   232  
   233  	err := s.replicatedSession.WriteTagged(namespace, id, tags, now, value, unit, annotation)
   234  	s.NoError(err)
   235  
   236  	s.waitForAsyncSessions(asyncCount)
   237  }
   238  
   239  func (s *replicatedSessionTestSuite) TestOpenReplicatedSession() {
   240  	var newSessionFunc = func(opts Options) (clientSession, error) {
   241  		s := NewMockclientSession(s.mockCtrl)
   242  		s.EXPECT().Open().Return(nil)
   243  		return s, nil
   244  	}
   245  
   246  	opts := optionsWithAsyncSessions(true, 2)
   247  	s.initReplicatedSession(opts, newSessionFunc)
   248  	s.replicatedSession.Open()
   249  }
   250  
   251  func (s *replicatedSessionTestSuite) TestOpenReplicatedSessionSyncError() {
   252  	sessions := []*MockclientSession{}
   253  	var newSessionFunc = func(opts Options) (clientSession, error) {
   254  		s := NewMockclientSession(s.mockCtrl)
   255  		sessions = append(sessions, s)
   256  		return s, nil
   257  	}
   258  
   259  	opts := optionsWithAsyncSessions(true, 2)
   260  	s.initReplicatedSession(opts, newSessionFunc)
   261  
   262  	// Early exit if sync session Open() returns an error
   263  	sessions[0].EXPECT().Open().Return(errors.New("an error"))
   264  	s.replicatedSession.Open()
   265  }
   266  
   267  func (s *replicatedSessionTestSuite) TestOpenReplicatedSessionAsyncError() {
   268  	sessions := []*MockclientSession{}
   269  	var newSessionFunc = func(opts Options) (clientSession, error) {
   270  		s := NewMockclientSession(s.mockCtrl)
   271  		sessions = append(sessions, s)
   272  		return s, nil
   273  	}
   274  
   275  	opts := optionsWithAsyncSessions(true, 2)
   276  	s.initReplicatedSession(opts, newSessionFunc)
   277  
   278  	// No early exit if async session Open() returns an error
   279  	sessions[0].EXPECT().Open().Return(nil)
   280  	sessions[1].EXPECT().Open().Return(errors.New("an error"))
   281  	sessions[2].EXPECT().Open().Return(nil)
   282  	s.replicatedSession.Open()
   283  }
   284  
   285  func (s *replicatedSessionTestSuite) waitForAsyncSessions(asyncCount int) {
   286  	t := time.NewTimer(1 * time.Second)
   287  
   288  	// Allow async expectations to occur before ending test.
   289  	for i := 0; i < asyncCount; i++ {
   290  		select {
   291  		case err := <-s.replicatedSession.outCh:
   292  			s.NoError(err)
   293  		case <-t.C:
   294  			return
   295  		}
   296  	}
   297  }