github.com/matrixorigin/matrixone@v1.2.0/pkg/hakeeper/task/task_scheduler_test.go (about) 1 // Copyright 2022 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package task 16 17 import ( 18 "context" 19 "github.com/matrixorigin/matrixone/pkg/common/runtime" 20 "github.com/matrixorigin/matrixone/pkg/hakeeper" 21 "github.com/matrixorigin/matrixone/pkg/logutil" 22 pb "github.com/matrixorigin/matrixone/pkg/pb/logservice" 23 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 24 "github.com/matrixorigin/matrixone/pkg/pb/task" 25 "github.com/matrixorigin/matrixone/pkg/taskservice" 26 "github.com/stretchr/testify/assert" 27 "testing" 28 "time" 29 ) 30 31 func TestMain(m *testing.M) { 32 logutil.SetupMOLogger(&logutil.LogConfig{ 33 Level: "debug", 34 Format: "console", 35 }) 36 37 runtime.SetupProcessLevelRuntime(runtime.NewRuntime(metadata.ServiceType_LOG, "test", logutil.GetGlobalLogger())) 38 m.Run() 39 } 40 41 func TestGetExpiredTasks(t *testing.T) { 42 cases := []struct { 43 tasks []task.AsyncTask 44 workingCN pb.CNState 45 46 expected map[uint64]struct{} 47 }{ 48 { 49 tasks: nil, 50 workingCN: pb.CNState{}, 51 52 expected: nil, 53 }, 54 { 55 // CN running task 1 is expired. 56 tasks: []task.AsyncTask{ 57 {ID: 1, TaskRunner: "a", LastHeartbeat: time.Now().UnixMilli()}, 58 {ID: 2, TaskRunner: "b", LastHeartbeat: time.Now().UnixMilli()}, 59 }, 60 workingCN: pb.CNState{Stores: map[string]pb.CNStoreInfo{"b": {}}}, 61 62 expected: map[uint64]struct{}{1: {}}, 63 }, 64 { 65 // Heartbeat of task 1 is expired. 66 tasks: []task.AsyncTask{ 67 {ID: 1, TaskRunner: "a", LastHeartbeat: time.Now().Add(-taskSchedulerDefaultTimeout - 1).UnixMilli()}, 68 {ID: 2, TaskRunner: "b", LastHeartbeat: time.Now().UnixMilli()}, 69 }, 70 workingCN: pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}, "b": {}}}, 71 72 expected: map[uint64]struct{}{1: {}}, 73 }, 74 } 75 76 for _, c := range cases { 77 results := getExpiredTasks(c.tasks, newCNPoolWithCNState(c.workingCN)) 78 for _, asyncTask := range results { 79 _, ok := c.expected[asyncTask.ID] 80 assert.True(t, ok) 81 } 82 } 83 } 84 85 func TestGetCNOrderedMap(t *testing.T) { 86 cases := []struct { 87 tasks []task.AsyncTask 88 workingCN pb.CNState 89 90 expected *cnPool 91 }{ 92 { 93 tasks: nil, 94 workingCN: pb.CNState{}, 95 96 expected: newCNPool(), 97 }, 98 { 99 tasks: []task.AsyncTask{ 100 {TaskRunner: "a", LastHeartbeat: time.Now().UnixMilli()}, 101 {TaskRunner: "b", LastHeartbeat: time.Now().UnixMilli()}, 102 {TaskRunner: "b", LastHeartbeat: time.Now().UnixMilli()}}, 103 workingCN: pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}, "b": {}}}, 104 105 expected: &cnPool{ 106 freq: map[string]uint32{"a": 1, "b": 2}, 107 sortedCN: []cnStore{{uuid: "a", info: pb.CNStoreInfo{}}, {uuid: "b", info: pb.CNStoreInfo{}}}, 108 }, 109 }, 110 { 111 tasks: []task.AsyncTask{ 112 {TaskRunner: "a", LastHeartbeat: time.Now().UnixMilli()}, 113 {TaskRunner: "b", LastHeartbeat: time.Now().UnixMilli()}, 114 {TaskRunner: "a", LastHeartbeat: time.Now().UnixMilli()}, 115 {TaskRunner: "a", LastHeartbeat: time.Now().UnixMilli()}}, 116 workingCN: pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}, "b": {}}}, 117 118 expected: &cnPool{ 119 freq: map[string]uint32{"a": 3, "b": 1}, 120 sortedCN: []cnStore{{uuid: "b", info: pb.CNStoreInfo{}}, {uuid: "a", info: pb.CNStoreInfo{}}}, 121 }, 122 }, 123 } 124 125 for _, c := range cases { 126 pool := newCNPoolWithCNState(c.workingCN) 127 getExpiredTasks(c.tasks, pool) 128 assert.Equal(t, c.expected, pool) 129 } 130 } 131 132 func TestScheduleCreatedTasks(t *testing.T) { 133 service := taskservice.NewTaskService(runtime.DefaultRuntime(), taskservice.NewMemTaskStorage()) 134 scheduler := NewScheduler(func() taskservice.TaskService { return service }, hakeeper.Config{}) 135 cnState := pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}}} 136 currentTick := uint64(0) 137 138 // Schedule empty task 139 scheduler.Schedule(cnState, currentTick) 140 141 // Create Task 1 142 assert.NoError(t, service.CreateAsyncTask(context.Background(), task.TaskMetadata{ID: "1"})) 143 query, err := service.QueryAsyncTask(context.Background()) 144 assert.NoError(t, err) 145 assert.Equal(t, task.TaskStatus_Created, query[0].Status) 146 147 // Schedule Task 1 148 scheduler.Schedule(cnState, currentTick) 149 150 query, err = service.QueryAsyncTask(context.Background()) 151 assert.NoError(t, err) 152 assert.Equal(t, "a", query[0].TaskRunner) 153 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 154 155 // Create Task 2 156 assert.NoError(t, service.CreateAsyncTask(context.Background(), task.TaskMetadata{ID: "2"})) 157 query, err = service.QueryAsyncTask(context.Background(), 158 taskservice.WithTaskStatusCond(task.TaskStatus_Created)) 159 assert.NoError(t, err) 160 assert.Equal(t, 1, len(query)) 161 assert.NotNil(t, query[0].Status) 162 163 // Add CNStore "b" 164 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}, "b": {}}} 165 166 // Schedule Task 2 167 scheduler.Schedule(cnState, currentTick) 168 169 query, err = service.QueryAsyncTask(context.Background(), taskservice.WithTaskRunnerCond(taskservice.EQ, "b")) 170 assert.NoError(t, err) 171 assert.NotNil(t, query) 172 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 173 } 174 175 func TestReallocateExpiredTasks(t *testing.T) { 176 service := taskservice.NewTaskService(runtime.DefaultRuntime(), taskservice.NewMemTaskStorage()) 177 scheduler := NewScheduler(func() taskservice.TaskService { return service }, hakeeper.Config{}) 178 cnState := pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}}} 179 currentTick := expiredTick - 1 180 181 // Create Task 1 182 assert.NoError(t, service.CreateAsyncTask(context.Background(), task.TaskMetadata{ID: "1"})) 183 query, err := service.QueryAsyncTask(context.Background()) 184 assert.NoError(t, err) 185 assert.Equal(t, task.TaskStatus_Created, query[0].Status) 186 187 // Schedule Task 1 on "a" 188 scheduler.Schedule(cnState, currentTick) 189 190 query, err = service.QueryAsyncTask(context.Background()) 191 assert.NoError(t, err) 192 assert.Equal(t, 1, len(query)) 193 assert.Equal(t, "a", query[0].TaskRunner) 194 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 195 196 // Make CNStore "a" expired 197 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}}} 198 currentTick = expiredTick + 1 199 200 // Re-schedule Task 1 201 // Since no other CN available, task 1 remains on CN "a" 202 scheduler.Schedule(cnState, currentTick) 203 204 query, err = service.QueryAsyncTask(context.Background()) 205 assert.NoError(t, err) 206 assert.Equal(t, 1, len(query)) 207 assert.Equal(t, "a", query[0].TaskRunner) 208 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 209 210 // Add CNStore "b" 211 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}, "b": {Tick: expiredTick}}} 212 213 // Re-schedule Task 1 214 // "b" available 215 scheduler.Schedule(cnState, currentTick) 216 217 query, err = service.QueryAsyncTask(context.Background()) 218 assert.NoError(t, err) 219 assert.Equal(t, 1, len(query)) 220 assert.Equal(t, "b", query[0].TaskRunner) 221 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 222 } 223 224 func TestAllocTasksWithLabels(t *testing.T) { 225 service := taskservice.NewTaskService(runtime.DefaultRuntime(), taskservice.NewMemTaskStorage()) 226 scheduler := NewScheduler(func() taskservice.TaskService { return service }, hakeeper.Config{}) 227 cnState := pb.CNState{Stores: map[string]pb.CNStoreInfo{ 228 "a": {Labels: map[string]metadata.LabelList{"k1": {Labels: []string{"v1"}}}}, 229 "b": {Labels: map[string]metadata.LabelList{"k1": {Labels: []string{"v2"}}}}, 230 }} 231 currentTick := expiredTick - 1 232 233 // Create Task 1 234 assert.NoError(t, service.CreateAsyncTask(context.Background(), task.TaskMetadata{ID: "1", Options: task.TaskOptions{Labels: map[string]string{"k1": "v1"}}})) 235 query, err := service.QueryAsyncTask(context.Background()) 236 assert.NoError(t, err) 237 assert.Equal(t, task.TaskStatus_Created, query[0].Status) 238 239 // Schedule Task 1 on "a" 240 scheduler.Schedule(cnState, currentTick) 241 242 query, err = service.QueryAsyncTask(context.Background()) 243 assert.NoError(t, err) 244 assert.Equal(t, 1, len(query)) 245 assert.Equal(t, "a", query[0].TaskRunner) 246 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 247 248 // Make CNStore "a" expired 249 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}}} 250 currentTick = expiredTick + 1 251 252 // Re-schedule Task 1 253 // Since no other CN available, task 1 remains on CN "a" 254 scheduler.Schedule(cnState, currentTick) 255 256 query, err = service.QueryAsyncTask(context.Background()) 257 assert.NoError(t, err) 258 assert.Equal(t, 1, len(query)) 259 assert.Equal(t, "a", query[0].TaskRunner) 260 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 261 262 // Add CNStore "c" 263 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{ 264 "a": {}, 265 "b": {Labels: map[string]metadata.LabelList{"k1": {Labels: []string{"v2"}}}}, 266 "c": { 267 Tick: expiredTick, 268 Labels: map[string]metadata.LabelList{"k1": {Labels: []string{"v1"}}}, 269 }, 270 }} 271 272 // Re-schedule Task 1 273 // "c" available 274 scheduler.Schedule(cnState, currentTick) 275 276 query, err = service.QueryAsyncTask(context.Background()) 277 assert.NoError(t, err) 278 assert.Equal(t, 1, len(query)) 279 assert.Equal(t, "c", query[0].TaskRunner) 280 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 281 } 282 283 func TestAllocTasksWithMemoryOrCPU(t *testing.T) { 284 service := taskservice.NewTaskService(runtime.DefaultRuntime(), taskservice.NewMemTaskStorage()) 285 scheduler := NewScheduler(func() taskservice.TaskService { return service }, hakeeper.Config{}) 286 cnState := pb.CNState{Stores: map[string]pb.CNStoreInfo{ 287 "a": {Resource: pb.Resource{CPUTotal: 1, MemTotal: 200}}, 288 "b": {Resource: pb.Resource{CPUTotal: 1, MemTotal: 100}}, 289 }} 290 currentTick := expiredTick - 1 291 292 // Create Task 1 293 assert.NoError(t, service.CreateAsyncTask(context.Background(), task.TaskMetadata{ID: "1", 294 Options: task.TaskOptions{Resource: &task.Resource{CPU: 1, Memory: 150}}})) 295 query, err := service.QueryAsyncTask(context.Background()) 296 assert.NoError(t, err) 297 assert.Equal(t, task.TaskStatus_Created, query[0].Status) 298 299 // Schedule Task 1 on "a" 300 scheduler.Schedule(cnState, currentTick) 301 302 query, err = service.QueryAsyncTask(context.Background()) 303 assert.NoError(t, err) 304 assert.Equal(t, 1, len(query)) 305 assert.Equal(t, "a", query[0].TaskRunner) 306 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 307 308 // Make CNStore "a" expired 309 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{"a": {}}} 310 currentTick = expiredTick + 1 311 312 // Re-schedule Task 1 313 // Since no other CN available, task 1 remains on CN "a" 314 scheduler.Schedule(cnState, currentTick) 315 316 query, err = service.QueryAsyncTask(context.Background()) 317 assert.NoError(t, err) 318 assert.Equal(t, 1, len(query)) 319 assert.Equal(t, "a", query[0].TaskRunner) 320 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 321 322 // Add CNStore "c" 323 cnState = pb.CNState{Stores: map[string]pb.CNStoreInfo{ 324 "a": {}, 325 "b": {}, 326 "c": {Tick: expiredTick, Resource: pb.Resource{CPUTotal: 2, MemTotal: 200}}, 327 }} 328 329 // Re-schedule Task 1 330 // "c" available 331 scheduler.Schedule(cnState, currentTick) 332 333 query, err = service.QueryAsyncTask(context.Background()) 334 assert.NoError(t, err) 335 assert.Equal(t, 1, len(query)) 336 assert.Equal(t, "c", query[0].TaskRunner) 337 assert.Equal(t, task.TaskStatus_Running, query[0].Status) 338 }