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  }