github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/m3em/node/node_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 node
    22  
    23  import (
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"math/rand"
    28  	"os"
    29  	"testing"
    30  
    31  	"github.com/m3db/m3/src/cluster/placement"
    32  	"github.com/m3db/m3/src/m3em/build"
    33  	"github.com/m3db/m3/src/m3em/generated/proto/m3em"
    34  	mockfs "github.com/m3db/m3/src/m3em/os/fs/mocks"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/require"
    38  	"google.golang.org/grpc"
    39  )
    40  
    41  const defaultRandSeed = 1234567890
    42  
    43  var (
    44  	defaultRandomVar = rand.New(rand.NewSource(int64(defaultRandSeed)))
    45  )
    46  
    47  func newMockPlacementInstance(ctrl *gomock.Controller) placement.Instance {
    48  	r := defaultRandomVar
    49  	node := placement.NewMockInstance(ctrl)
    50  	node.EXPECT().ID().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    51  	node.EXPECT().Endpoint().AnyTimes().Return(fmt.Sprintf("%d:%d", r.Int(), r.Int()))
    52  	node.EXPECT().IsolationGroup().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    53  	node.EXPECT().Zone().AnyTimes().Return(fmt.Sprintf("%d", r.Int()))
    54  	node.EXPECT().Weight().AnyTimes().Return(r.Uint32())
    55  	node.EXPECT().Shards().AnyTimes().Return(nil)
    56  	return node
    57  }
    58  
    59  func newTestOptions(c *m3em.MockOperatorClient) Options {
    60  	return NewOptions(nil).
    61  		SetOperatorClientFn(func() (*grpc.ClientConn, m3em.OperatorClient, error) {
    62  			return nil, c, nil
    63  		})
    64  }
    65  
    66  func TestNodePropertyInitialization(t *testing.T) {
    67  	ctrl := gomock.NewController(t)
    68  	defer ctrl.Finish()
    69  
    70  	opts := newTestOptions(nil)
    71  	mockInstance := newMockPlacementInstance(ctrl)
    72  	serviceNode, err := New(mockInstance, opts)
    73  	require.NoError(t, err)
    74  	require.Equal(t, mockInstance.ID(), serviceNode.ID())
    75  	require.Equal(t, mockInstance.Endpoint(), serviceNode.Endpoint())
    76  	require.Equal(t, mockInstance.IsolationGroup(), serviceNode.IsolationGroup())
    77  	require.Equal(t, mockInstance.Zone(), serviceNode.Zone())
    78  	require.Equal(t, mockInstance.Weight(), serviceNode.Weight())
    79  	require.Equal(t, mockInstance.Shards(), serviceNode.Shards())
    80  }
    81  
    82  func TestNodeErrorStatusIllegalTransitions(t *testing.T) {
    83  	ctrl := gomock.NewController(t)
    84  	defer ctrl.Finish()
    85  	mockClient := m3em.NewMockOperatorClient(ctrl)
    86  	opts := newTestOptions(mockClient)
    87  	mockInstance := newMockPlacementInstance(ctrl)
    88  	node, err := New(mockInstance, opts)
    89  	require.NoError(t, err)
    90  	serviceNode := node.(*svcNode)
    91  	require.Equal(t, StatusUninitialized, serviceNode.Status())
    92  	serviceNode.status = StatusError
    93  	require.Error(t, serviceNode.Start())
    94  	require.Error(t, serviceNode.Stop())
    95  	require.Error(t, serviceNode.Setup(nil, nil, "", false))
    96  }
    97  
    98  func TestNodeErrorStatusToTeardownTransition(t *testing.T) {
    99  	ctrl := gomock.NewController(t)
   100  	defer ctrl.Finish()
   101  	mockClient := m3em.NewMockOperatorClient(ctrl)
   102  	opts := newTestOptions(mockClient)
   103  	mockInstance := newMockPlacementInstance(ctrl)
   104  	node, err := New(mockInstance, opts)
   105  	require.NoError(t, err)
   106  	serviceNode := node.(*svcNode)
   107  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   108  	serviceNode.status = StatusError
   109  	mockClient.EXPECT().Teardown(gomock.Any(), gomock.Any())
   110  	require.NoError(t, serviceNode.Teardown())
   111  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   112  }
   113  
   114  func TestNodeUninitializedStatusIllegalTransitions(t *testing.T) {
   115  	ctrl := gomock.NewController(t)
   116  	defer ctrl.Finish()
   117  	mockClient := m3em.NewMockOperatorClient(ctrl)
   118  	opts := newTestOptions(mockClient)
   119  	mockInstance := newMockPlacementInstance(ctrl)
   120  	node, err := New(mockInstance, opts)
   121  	require.NoError(t, err)
   122  	serviceNode := node.(*svcNode)
   123  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   124  	require.Error(t, serviceNode.Start())
   125  	require.Error(t, serviceNode.Stop())
   126  	require.Error(t, serviceNode.Teardown())
   127  }
   128  
   129  func TestNodeUninitializedStatusToSetupTransition(t *testing.T) {
   130  	ctrl := gomock.NewController(t)
   131  	defer ctrl.Finish()
   132  	mockClient := m3em.NewMockOperatorClient(ctrl)
   133  	opts := newTestOptions(mockClient)
   134  	mb := build.NewMockServiceBuild(ctrl)
   135  	mc := build.NewMockServiceConfiguration(ctrl)
   136  	mockInstance := newMockPlacementInstance(ctrl)
   137  	node, err := New(mockInstance, opts)
   138  	require.NoError(t, err)
   139  	serviceNode := node.(*svcNode)
   140  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   141  
   142  	forceSetup := false
   143  	buildChecksum := uint32(123)
   144  	configChecksum := uint32(321)
   145  
   146  	dummyBytes := []byte(`some long string`)
   147  	dummyBuildIter := mockfs.NewMockFileReaderIter(ctrl)
   148  	gomock.InOrder(
   149  		dummyBuildIter.EXPECT().Next().Return(true),
   150  		dummyBuildIter.EXPECT().Current().Return(dummyBytes),
   151  		dummyBuildIter.EXPECT().Next().Return(true),
   152  		dummyBuildIter.EXPECT().Current().Return(dummyBytes),
   153  		dummyBuildIter.EXPECT().Next().Return(false),
   154  		dummyBuildIter.EXPECT().Err().Return(nil),
   155  		dummyBuildIter.EXPECT().Checksum().Return(buildChecksum),
   156  		dummyBuildIter.EXPECT().Close(),
   157  	)
   158  	mb.EXPECT().ID().Return("build-id")
   159  	mb.EXPECT().Iter(gomock.Any()).Return(dummyBuildIter, nil)
   160  	dummyConfIter := mockfs.NewMockFileReaderIter(ctrl)
   161  	gomock.InOrder(
   162  		dummyConfIter.EXPECT().Next().Return(true),
   163  		dummyConfIter.EXPECT().Current().Return(dummyBytes),
   164  		dummyConfIter.EXPECT().Next().Return(false),
   165  		dummyConfIter.EXPECT().Err().Return(nil),
   166  		dummyConfIter.EXPECT().Checksum().Return(configChecksum),
   167  		dummyConfIter.EXPECT().Close(),
   168  	)
   169  	mc.EXPECT().ID().Return("config-id")
   170  	mc.EXPECT().Iter(gomock.Any()).Return(dummyConfIter, nil)
   171  
   172  	buildTransferClient := m3em.NewMockOperator_PushFileClient(ctrl)
   173  	gomock.InOrder(
   174  		buildTransferClient.EXPECT().Send(&m3em.PushFileRequest{
   175  			Type:        m3em.PushFileType_PUSH_FILE_TYPE_SERVICE_BINARY,
   176  			TargetPaths: []string{"build-id"},
   177  			Overwrite:   forceSetup,
   178  			Data: &m3em.DataChunk{
   179  				Bytes: dummyBytes,
   180  				Idx:   0,
   181  			},
   182  		}).Return(nil),
   183  		buildTransferClient.EXPECT().Send(&m3em.PushFileRequest{
   184  			Type:        m3em.PushFileType_PUSH_FILE_TYPE_SERVICE_BINARY,
   185  			TargetPaths: []string{"build-id"},
   186  			Overwrite:   forceSetup,
   187  			Data: &m3em.DataChunk{
   188  				Bytes: dummyBytes,
   189  				Idx:   1,
   190  			},
   191  		}).Return(nil),
   192  		buildTransferClient.EXPECT().CloseAndRecv().Return(
   193  			&m3em.PushFileResponse{
   194  				FileChecksum:   buildChecksum,
   195  				NumChunksRecvd: 2,
   196  			}, nil,
   197  		),
   198  	)
   199  	configTransferClient := m3em.NewMockOperator_PushFileClient(ctrl)
   200  	gomock.InOrder(
   201  		configTransferClient.EXPECT().Send(&m3em.PushFileRequest{
   202  			Type:        m3em.PushFileType_PUSH_FILE_TYPE_SERVICE_CONFIG,
   203  			TargetPaths: []string{"config-id"},
   204  			Overwrite:   forceSetup,
   205  			Data: &m3em.DataChunk{
   206  				Bytes: dummyBytes,
   207  				Idx:   0,
   208  			},
   209  		}).Return(nil),
   210  		configTransferClient.EXPECT().CloseAndRecv().Return(
   211  			&m3em.PushFileResponse{
   212  				FileChecksum:   configChecksum,
   213  				NumChunksRecvd: 1,
   214  			}, nil,
   215  		),
   216  	)
   217  	gomock.InOrder(
   218  		mockClient.EXPECT().Setup(gomock.Any(), gomock.Any()),
   219  		mockClient.EXPECT().PushFile(gomock.Any()).Return(buildTransferClient, nil),
   220  		mockClient.EXPECT().PushFile(gomock.Any()).Return(configTransferClient, nil),
   221  	)
   222  
   223  	require.NoError(t, serviceNode.Setup(mb, mc, "", forceSetup))
   224  	require.Equal(t, StatusSetup, serviceNode.Status())
   225  	require.Equal(t, mb, serviceNode.currentBuild)
   226  	require.Equal(t, mc, serviceNode.currentConf)
   227  }
   228  
   229  func TestNodeSetupStatusIllegalTransitions(t *testing.T) {
   230  	ctrl := gomock.NewController(t)
   231  	defer ctrl.Finish()
   232  	mockClient := m3em.NewMockOperatorClient(ctrl)
   233  	opts := newTestOptions(mockClient)
   234  	mockInstance := newMockPlacementInstance(ctrl)
   235  	node, err := New(mockInstance, opts)
   236  	require.NoError(t, err)
   237  	serviceNode := node.(*svcNode)
   238  	serviceNode.status = StatusSetup
   239  	require.Error(t, serviceNode.Stop())
   240  }
   241  
   242  func TestNodeSetupStatusToStartTransition(t *testing.T) {
   243  	ctrl := gomock.NewController(t)
   244  	defer ctrl.Finish()
   245  	mockClient := m3em.NewMockOperatorClient(ctrl)
   246  	opts := newTestOptions(mockClient)
   247  	mockInstance := newMockPlacementInstance(ctrl)
   248  	node, err := New(mockInstance, opts)
   249  	require.NoError(t, err)
   250  	serviceNode := node.(*svcNode)
   251  	serviceNode.status = StatusSetup
   252  	mockClient.EXPECT().Start(gomock.Any(), gomock.Any())
   253  	require.NoError(t, serviceNode.Start())
   254  	require.Equal(t, StatusRunning, serviceNode.Status())
   255  }
   256  
   257  func TestNodeSetupStatusToTeardownTransition(t *testing.T) {
   258  	ctrl := gomock.NewController(t)
   259  	defer ctrl.Finish()
   260  	mockClient := m3em.NewMockOperatorClient(ctrl)
   261  	opts := newTestOptions(mockClient)
   262  	mockInstance := newMockPlacementInstance(ctrl)
   263  	node, err := New(mockInstance, opts)
   264  	require.NoError(t, err)
   265  	serviceNode := node.(*svcNode)
   266  	serviceNode.status = StatusSetup
   267  	mockClient.EXPECT().Teardown(gomock.Any(), gomock.Any())
   268  	require.NoError(t, serviceNode.Teardown())
   269  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   270  }
   271  
   272  func TestNodeRunningStatusIllegalTransitions(t *testing.T) {
   273  	ctrl := gomock.NewController(t)
   274  	defer ctrl.Finish()
   275  	mockClient := m3em.NewMockOperatorClient(ctrl)
   276  	opts := newTestOptions(mockClient)
   277  	mockInstance := newMockPlacementInstance(ctrl)
   278  	node, err := New(mockInstance, opts)
   279  	require.NoError(t, err)
   280  	serviceNode := node.(*svcNode)
   281  	serviceNode.status = StatusRunning
   282  	require.Error(t, serviceNode.Start())
   283  	require.Error(t, serviceNode.Setup(nil, nil, "", false))
   284  }
   285  
   286  func TestNodeRunningStatusToStopTransition(t *testing.T) {
   287  	ctrl := gomock.NewController(t)
   288  	defer ctrl.Finish()
   289  	mockClient := m3em.NewMockOperatorClient(ctrl)
   290  	opts := newTestOptions(mockClient)
   291  	mockInstance := newMockPlacementInstance(ctrl)
   292  	node, err := New(mockInstance, opts)
   293  	require.NoError(t, err)
   294  	serviceNode := node.(*svcNode)
   295  	serviceNode.status = StatusRunning
   296  	mockClient.EXPECT().Stop(gomock.Any(), gomock.Any())
   297  	require.NoError(t, serviceNode.Stop())
   298  	require.Equal(t, StatusSetup, serviceNode.Status())
   299  }
   300  
   301  func TestNodeRunningStatusToTeardownTransition(t *testing.T) {
   302  	ctrl := gomock.NewController(t)
   303  	defer ctrl.Finish()
   304  	mockClient := m3em.NewMockOperatorClient(ctrl)
   305  	opts := newTestOptions(mockClient)
   306  	mockInstance := newMockPlacementInstance(ctrl)
   307  	node, err := New(mockInstance, opts)
   308  	require.NoError(t, err)
   309  	serviceNode := node.(*svcNode)
   310  	serviceNode.status = StatusRunning
   311  	mockClient.EXPECT().Teardown(gomock.Any(), gomock.Any())
   312  	require.NoError(t, serviceNode.Teardown())
   313  	require.Equal(t, StatusUninitialized, serviceNode.Status())
   314  
   315  }
   316  
   317  func TestNodeGetRemoteOutput(t *testing.T) {
   318  	ctrl := gomock.NewController(t)
   319  	defer ctrl.Finish()
   320  
   321  	tempDir, err := ioutil.TempDir("", "remote-output")
   322  	require.NoError(t, err)
   323  	defer os.RemoveAll(tempDir)
   324  
   325  	mockClient := m3em.NewMockOperatorClient(ctrl)
   326  	opts := newTestOptions(mockClient)
   327  
   328  	mockInstance := newMockPlacementInstance(ctrl)
   329  	node, err := New(mockInstance, opts)
   330  	require.NoError(t, err)
   331  	serviceNode := node.(*svcNode)
   332  	serviceNode.status = StatusSetup
   333  
   334  	dummyBytes := []byte(`some long string`)
   335  	testLocalDestPath := fmt.Sprintf("%s/someLocalPath", tempDir)
   336  	pullClient := m3em.NewMockOperator_PullFileClient(ctrl)
   337  
   338  	gomock.InOrder(
   339  		mockClient.EXPECT().
   340  			PullFile(gomock.Any(), &m3em.PullFileRequest{
   341  				FileType:  m3em.PullFileType_PULL_FILE_TYPE_SERVICE_STDOUT,
   342  				ChunkSize: int64(opts.TransferBufferSize()),
   343  				MaxSize:   opts.MaxPullSize(),
   344  			}).
   345  			Return(pullClient, nil),
   346  		pullClient.EXPECT().Recv().Return(&m3em.PullFileResponse{
   347  			Data: &m3em.DataChunk{
   348  				Bytes: dummyBytes,
   349  				Idx:   0,
   350  			},
   351  			Truncated: true,
   352  		}, nil),
   353  		pullClient.EXPECT().Recv().Return(&m3em.PullFileResponse{
   354  			Data: &m3em.DataChunk{
   355  				Bytes: dummyBytes,
   356  				Idx:   1,
   357  			},
   358  			Truncated: true,
   359  		}, nil),
   360  		pullClient.EXPECT().Recv().Return(nil, io.EOF),
   361  	)
   362  
   363  	trunc, err := serviceNode.GetRemoteOutput(RemoteProcessStdout, testLocalDestPath)
   364  	require.NoError(t, err)
   365  	require.True(t, trunc)
   366  
   367  	expectedBytes := append(dummyBytes, dummyBytes...)
   368  	transferredBytes, err := ioutil.ReadFile(testLocalDestPath)
   369  	require.NoError(t, err)
   370  	require.Equal(t, expectedBytes, transferredBytes)
   371  }