github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/servermaster/executor_manager_test.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package servermaster
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/mock/gomock"
    23  	pb "github.com/pingcap/tiflow/engine/enginepb"
    24  	"github.com/pingcap/tiflow/engine/model"
    25  	"github.com/pingcap/tiflow/engine/pkg/orm/mock"
    26  	ormModel "github.com/pingcap/tiflow/engine/pkg/orm/model"
    27  	"github.com/pingcap/tiflow/pkg/errors"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func TestExecutorManager(t *testing.T) {
    32  	t.Parallel()
    33  
    34  	metaClient := mock.NewMockClient(gomock.NewController(t))
    35  	ctx, cancel := context.WithCancel(context.Background())
    36  	heartbeatTTL := time.Millisecond * 100
    37  	checkInterval := time.Millisecond * 10
    38  	mgr := NewExecutorManagerImpl(metaClient, heartbeatTTL, checkInterval)
    39  
    40  	// register an executor server
    41  	executorAddr := "127.0.0.1:10001"
    42  	registerReq := &pb.RegisterExecutorRequest{
    43  		Executor: &pb.Executor{Address: executorAddr},
    44  	}
    45  	metaClient.EXPECT().
    46  		CreateExecutor(gomock.Any(), gomock.Any()).Times(1).
    47  		DoAndReturn(func(ctx context.Context, executor *ormModel.Executor) error {
    48  			require.NotEmpty(t, executor.ID)
    49  			require.Equal(t, executorAddr, executor.Address)
    50  			return nil
    51  		})
    52  
    53  	executor, err := mgr.AllocateNewExec(ctx, registerReq)
    54  	require.Nil(t, err)
    55  
    56  	addr, ok := mgr.GetAddr(executor.ID)
    57  	require.True(t, ok)
    58  	require.Equal(t, "127.0.0.1:10001", addr)
    59  
    60  	require.Equal(t, 1, mgr.ExecutorCount(model.Initing))
    61  	require.Equal(t, 0, mgr.ExecutorCount(model.Running))
    62  	mgr.mu.Lock()
    63  	require.Contains(t, mgr.executors, executor.ID)
    64  	mgr.mu.Unlock()
    65  
    66  	newHeartbeatReq := func() *pb.HeartbeatRequest {
    67  		return &pb.HeartbeatRequest{
    68  			ExecutorId: string(executor.ID),
    69  			Timestamp:  uint64(time.Now().Unix()),
    70  			Ttl:        uint64(10), // 10ms ttl
    71  		}
    72  	}
    73  
    74  	// test executor heartbeat
    75  	_, err = mgr.HandleHeartbeat(newHeartbeatReq())
    76  	require.NoError(t, err)
    77  
    78  	metaClient.EXPECT().QueryExecutors(gomock.Any()).Times(1).Return([]*ormModel.Executor{}, nil)
    79  	metaClient.EXPECT().DeleteExecutor(gomock.Any(), executor.ID).Times(1).Return(nil)
    80  
    81  	var wg sync.WaitGroup
    82  	wg.Add(1)
    83  	go func() {
    84  		defer wg.Done()
    85  		mgr.Run(ctx)
    86  	}()
    87  
    88  	require.Eventually(t, func() bool {
    89  		return mgr.ExecutorCount(model.Running) == 0
    90  	}, time.Second*2, time.Millisecond*50)
    91  
    92  	// test late heartbeat request after executor is offline
    93  	_, err = mgr.HandleHeartbeat(newHeartbeatReq())
    94  	require.Error(t, err)
    95  	require.True(t, errors.Is(err, errors.ErrUnknownExecutor))
    96  
    97  	cancel()
    98  	wg.Wait()
    99  }
   100  
   101  func TestExecutorManagerWatch(t *testing.T) {
   102  	t.Parallel()
   103  
   104  	metaClient := mock.NewMockClient(gomock.NewController(t))
   105  
   106  	heartbeatTTL := time.Millisecond * 400
   107  	checkInterval := time.Millisecond * 50
   108  	ctx, cancel := context.WithCancel(context.Background())
   109  	mgr := NewExecutorManagerImpl(metaClient, heartbeatTTL, checkInterval)
   110  
   111  	// register an executor server
   112  	executorAddr := "127.0.0.1:10001"
   113  	registerReq := &pb.RegisterExecutorRequest{
   114  		Executor: &pb.Executor{Address: executorAddr},
   115  	}
   116  	metaClient.EXPECT().
   117  		CreateExecutor(gomock.Any(), gomock.Any()).Times(1).
   118  		DoAndReturn(func(ctx context.Context, executor *ormModel.Executor) error {
   119  			require.NotEmpty(t, executor.ID)
   120  			require.Equal(t, executorAddr, executor.Address)
   121  			return nil
   122  		})
   123  	executor, err := mgr.AllocateNewExec(ctx, registerReq)
   124  	require.Nil(t, err)
   125  
   126  	executorID1 := executor.ID
   127  	snap, stream, err := mgr.WatchExecutors(context.Background())
   128  	require.NoError(t, err)
   129  	require.Equal(t, map[model.ExecutorID]string{
   130  		executorID1: executor.Address,
   131  	}, snap)
   132  
   133  	// register another executor server
   134  	executorAddr = "127.0.0.1:10002"
   135  	registerReq = &pb.RegisterExecutorRequest{
   136  		Executor: &pb.Executor{Address: executorAddr},
   137  	}
   138  	metaClient.EXPECT().
   139  		CreateExecutor(gomock.Any(), gomock.Any()).Times(1).
   140  		DoAndReturn(func(ctx context.Context, executor *ormModel.Executor) error {
   141  			require.NotEmpty(t, executor.ID)
   142  			require.Equal(t, executorAddr, executor.Address)
   143  			return nil
   144  		})
   145  	executor, err = mgr.AllocateNewExec(ctx, registerReq)
   146  	require.Nil(t, err)
   147  
   148  	executorID2 := executor.ID
   149  	event := <-stream.C
   150  	require.Equal(t, model.ExecutorStatusChange{
   151  		ID:   executorID2,
   152  		Tp:   model.EventExecutorOnline,
   153  		Addr: "127.0.0.1:10002",
   154  	}, event)
   155  
   156  	newHeartbeatReq := func(executorID model.ExecutorID) *pb.HeartbeatRequest {
   157  		return &pb.HeartbeatRequest{
   158  			ExecutorId: string(executorID),
   159  			Timestamp:  uint64(time.Now().Unix()),
   160  			Ttl:        uint64(100), // 10ms ttl
   161  		}
   162  	}
   163  
   164  	bgExecutorHeartbeat := func(
   165  		ctx context.Context, wg *sync.WaitGroup, executorID model.DeployNodeID,
   166  	) context.CancelFunc {
   167  		// send a synchronous heartbeat first in order to ensure the online
   168  		// count of this executor takes effect immediately.
   169  		_, err := mgr.HandleHeartbeat(newHeartbeatReq(executorID))
   170  		require.NoError(t, err)
   171  
   172  		ctxIn, cancelIn := context.WithCancel(ctx)
   173  		wg.Add(1)
   174  		go func() {
   175  			defer wg.Done()
   176  			ticker := time.NewTicker(time.Millisecond)
   177  			defer ticker.Stop()
   178  			for {
   179  				select {
   180  				case <-ctxIn.Done():
   181  					return
   182  				case <-ticker.C:
   183  					_, err := mgr.HandleHeartbeat(newHeartbeatReq(executorID))
   184  					require.NoError(t, err)
   185  				}
   186  			}
   187  		}()
   188  		return cancelIn
   189  	}
   190  
   191  	metaClient.EXPECT().QueryExecutors(gomock.Any()).Times(1).
   192  		Return([]*ormModel.Executor{
   193  			{ID: executorID1, Address: "127.0.0.1:10001"},
   194  			{ID: executorID2, Address: "127.0.0.1:10002"},
   195  		}, nil)
   196  	metaClient.EXPECT().DeleteExecutor(gomock.Any(), executorID1).Times(1).Return(nil)
   197  	metaClient.EXPECT().DeleteExecutor(gomock.Any(), executorID2).Times(1).Return(nil)
   198  
   199  	var mgrWg sync.WaitGroup
   200  	mgrWg.Add(1)
   201  	go func() {
   202  		defer mgrWg.Done()
   203  		mgr.Run(ctx)
   204  	}()
   205  
   206  	// mgr.Start will reset executors first, so there will be two online events.
   207  	event = <-stream.C
   208  	require.Equal(t, model.ExecutorStatusChange{
   209  		ID:   executorID1,
   210  		Tp:   model.EventExecutorOnline,
   211  		Addr: "127.0.0.1:10001",
   212  	}, event)
   213  	event = <-stream.C
   214  	require.Equal(t, model.ExecutorStatusChange{
   215  		ID:   executorID2,
   216  		Tp:   model.EventExecutorOnline,
   217  		Addr: "127.0.0.1:10002",
   218  	}, event)
   219  
   220  	var wg sync.WaitGroup
   221  	require.Equal(t, 0, mgr.ExecutorCount(model.Running))
   222  	cancel1 := bgExecutorHeartbeat(ctx, &wg, executorID1)
   223  	cancel2 := bgExecutorHeartbeat(ctx, &wg, executorID2)
   224  	require.Equal(t, 2, mgr.ExecutorCount(model.Running))
   225  
   226  	// executor-1 will time out
   227  	cancel1()
   228  	require.Eventually(t, func() bool {
   229  		return mgr.ExecutorCount(model.Running) == 1
   230  	}, time.Second*2, time.Millisecond*5)
   231  
   232  	event = <-stream.C
   233  	require.Equal(t, model.ExecutorStatusChange{
   234  		ID:   executorID1,
   235  		Tp:   model.EventExecutorOffline,
   236  		Addr: "127.0.0.1:10001",
   237  	}, event)
   238  
   239  	// executor-2 will time out
   240  	cancel2()
   241  	wg.Wait()
   242  	event = <-stream.C
   243  	require.Equal(t, model.ExecutorStatusChange{
   244  		ID:   executorID2,
   245  		Tp:   model.EventExecutorOffline,
   246  		Addr: "127.0.0.1:10002",
   247  	}, event)
   248  
   249  	cancel()
   250  	mgrWg.Wait()
   251  }