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  }