github.com/matrixorigin/matrixone@v1.2.0/pkg/taskservice/daemon_task_test.go (about) 1 // Copyright 2021 - 2023 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 taskservice 16 17 import ( 18 "context" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/pb/task" 24 "github.com/stretchr/testify/assert" 25 ) 26 27 type mockActiveRoutine struct { 28 pauseC chan struct{} 29 resumeC chan struct{} 30 cancelC chan struct{} 31 } 32 33 func newMockActiveRoutine() *mockActiveRoutine { 34 return &mockActiveRoutine{ 35 pauseC: make(chan struct{}, 1), 36 resumeC: make(chan struct{}, 1), 37 cancelC: make(chan struct{}, 1), 38 } 39 } 40 41 func (r *mockActiveRoutine) Pause() error { 42 r.pauseC <- struct{}{} 43 return nil 44 } 45 46 func (r *mockActiveRoutine) Resume() error { 47 r.resumeC <- struct{}{} 48 return nil 49 } 50 51 func (r *mockActiveRoutine) Cancel() error { 52 r.cancelC <- struct{}{} 53 return nil 54 } 55 56 func daemonTaskMetadata() task.TaskMetadata { 57 return task.TaskMetadata{ 58 ID: "-", 59 Executor: task.TaskCode_ConnectorKafkaSink, 60 Options: task.TaskOptions{ 61 MaxRetryTimes: 0, 62 RetryInterval: 0, 63 DelayDuration: 0, 64 Concurrency: 0, 65 }, 66 } 67 } 68 69 func newDaemonTaskForTest(id uint64, status task.TaskStatus, runner string) task.DaemonTask { 70 nowTime := time.Now() 71 t := task.DaemonTask{ 72 ID: id, 73 Metadata: daemonTaskMetadata(), 74 TaskStatus: status, 75 TaskRunner: runner, 76 CreateAt: nowTime, 77 UpdateAt: nowTime, 78 Details: &task.Details{ 79 AccountID: 0, 80 Account: "sys", 81 Username: "dump", 82 Details: &task.Details_Connector{ 83 Connector: &task.ConnectorDetails{ 84 TableName: "d1.t1", 85 }, 86 }, 87 }, 88 } 89 return t 90 } 91 92 func TestRunDaemonTask(t *testing.T) { 93 runTaskRunnerTest(t, func(r *taskRunner, s TaskService, store TaskStorage) { 94 c := make(chan struct{}) 95 r.RegisterExecutor(task.TaskCode_ConnectorKafkaSink, func(ctx context.Context, task task.Task) error { 96 defer close(c) 97 return nil 98 }) 99 mustAddTestDaemonTask(t, store, 1, newDaemonTaskForTest(1, task.TaskStatus_Created, r.runnerID)) 100 <-c 101 tasks := mustGetTestDaemonTask(t, store, 1) 102 assert.Equal(t, 1, len(tasks)) 103 tk := tasks[0] 104 assert.Equal(t, task.TaskStatus_Running, tk.TaskStatus) 105 assert.False(t, tk.CreateAt.IsZero()) 106 assert.False(t, tk.UpdateAt.IsZero()) 107 assert.Equal(t, r.runnerID, tk.TaskRunner) 108 assert.False(t, tk.LastRun.IsZero()) 109 }, WithRunnerParallelism(1), 110 WithRunnerFetchInterval(time.Millisecond)) 111 } 112 113 func (r *taskRunner) testRegisterExecutor(t *testing.T, code task.TaskCode, started *atomic.Bool) { 114 r.RegisterExecutor(code, func(ctx context.Context, task task.Task) error { 115 started.Store(true) 116 ar := newMockActiveRoutine() 117 assert.NoError(t, r.Attach(context.Background(), 1, ar)) 118 for { 119 select { 120 case <-ar.cancelC: 121 return nil 122 123 case <-ar.pauseC: 124 select { 125 case <-ctx.Done(): 126 return nil 127 case <-ar.cancelC: 128 return nil 129 case <-ar.resumeC: 130 } 131 132 case <-ctx.Done(): 133 return nil 134 } 135 } 136 }) 137 } 138 139 func expectTaskStatus( 140 t *testing.T, store TaskStorage, dt task.DaemonTask, before task.TaskStatus, after task.TaskStatus, 141 ) { 142 dt.TaskStatus = before 143 mustUpdateTestDaemonTask(t, store, 1, []task.DaemonTask{dt}) 144 timer := time.NewTimer(time.Second * 5) 145 defer timer.Stop() 146 ticker := time.NewTicker(time.Millisecond * 10) 147 defer ticker.Stop() 148 FOR: 149 for { 150 select { 151 case <-timer.C: 152 panic("daemon task update timeout") 153 case <-ticker.C: 154 tasks := mustGetTestDaemonTask(t, store, 1, WithTaskIDCond(EQ, 1)) 155 assert.Equal(t, 1, len(tasks)) 156 tk := tasks[0] 157 if tk.TaskStatus == after { 158 break FOR 159 } 160 } 161 } 162 } 163 164 func waitStarted(started *atomic.Bool, timeout time.Duration) { 165 timer := time.NewTimer(timeout) 166 defer timer.Stop() 167 ticker := time.NewTicker(time.Millisecond * 10) 168 defer ticker.Stop() 169 for { 170 select { 171 case <-timer.C: 172 panic("start executor timeout") 173 case <-ticker.C: 174 if started.Load() { 175 return 176 } 177 } 178 } 179 } 180 181 func TestPauseResumeDaemonTask(t *testing.T) { 182 runTaskRunnerTest(t, func(r *taskRunner, s TaskService, store TaskStorage) { 183 dt := newDaemonTaskForTest(1, task.TaskStatus_Created, r.runnerID) 184 mustAddTestDaemonTask(t, store, 1, dt) 185 var started atomic.Bool 186 r.testRegisterExecutor(t, task.TaskCode_ConnectorKafkaSink, &started) 187 waitStarted(&started, time.Second*5) 188 189 expectTaskStatus(t, store, dt, task.TaskStatus_PauseRequested, task.TaskStatus_Paused) 190 expectTaskStatus(t, store, dt, task.TaskStatus_ResumeRequested, task.TaskStatus_Running) 191 }, WithRunnerParallelism(1), 192 WithRunnerFetchInterval(time.Millisecond)) 193 } 194 195 func TestCancelDaemonTask(t *testing.T) { 196 runTaskRunnerTest(t, func(r *taskRunner, s TaskService, store TaskStorage) { 197 dt := newDaemonTaskForTest(1, task.TaskStatus_Created, r.runnerID) 198 mustAddTestDaemonTask(t, store, 1, dt) 199 var started atomic.Bool 200 r.testRegisterExecutor(t, task.TaskCode_ConnectorKafkaSink, &started) 201 waitStarted(&started, time.Second*5) 202 203 expectTaskStatus(t, store, dt, task.TaskStatus_CancelRequested, task.TaskStatus_Canceled) 204 }, WithRunnerParallelism(1), 205 WithRunnerFetchInterval(time.Millisecond)) 206 }