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 }