github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/servermaster/jobop/operator_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 jobop 15 16 import ( 17 "context" 18 "testing" 19 20 frameModel "github.com/pingcap/tiflow/engine/framework/model" 21 pkgOrm "github.com/pingcap/tiflow/engine/pkg/orm" 22 ormModel "github.com/pingcap/tiflow/engine/pkg/orm/model" 23 "github.com/pingcap/tiflow/pkg/errors" 24 "github.com/stretchr/testify/require" 25 ) 26 27 type mockOperatorRouter struct { 28 onlineJobs map[string]struct{} 29 cancelCalls map[string]int 30 cli pkgOrm.Client 31 } 32 33 func newMockOperatorRouter(cli pkgOrm.Client) *mockOperatorRouter { 34 return &mockOperatorRouter{ 35 onlineJobs: make(map[string]struct{}), 36 cancelCalls: make(map[string]int), 37 cli: cli, 38 } 39 } 40 41 func (r *mockOperatorRouter) SendCancelJobMessage( 42 ctx context.Context, jobID string, 43 ) error { 44 if _, ok := r.onlineJobs[jobID]; !ok { 45 return errors.ErrMasterNotFound.GenWithStackByArgs(jobID) 46 } 47 r.cancelCalls[jobID]++ 48 return nil 49 } 50 51 func (r *mockOperatorRouter) checkCancelCalls(t *testing.T, jobID string, expected int) { 52 require.Contains(t, r.cancelCalls, jobID) 53 require.Equal(t, expected, r.cancelCalls[jobID]) 54 } 55 56 func (r *mockOperatorRouter) jobOnline( 57 ctx context.Context, jobID string, meta *frameModel.MasterMeta, 58 ) error { 59 r.onlineJobs[jobID] = struct{}{} 60 return r.cli.UpsertJob(ctx, meta) 61 } 62 63 func (r *mockOperatorRouter) jobOffline( 64 ctx context.Context, jobID string, meta *frameModel.MasterMeta, 65 ) error { 66 delete(r.onlineJobs, jobID) 67 return r.cli.UpsertJob(ctx, meta) 68 } 69 70 func checkJobOpWithStatus( 71 ctx context.Context, t *testing.T, metaCli pkgOrm.Client, 72 expectedStatus ormModel.JobOpStatus, expectedCount int, 73 ) { 74 ops, err := metaCli.QueryJobOpsByStatus(ctx, expectedStatus) 75 require.NoError(t, err) 76 require.Len(t, ops, expectedCount) 77 } 78 79 func TestJobOperator(t *testing.T) { 80 t.Parallel() 81 82 ctx := context.Background() 83 metaCli, err := pkgOrm.NewMockClient() 84 require.NoError(t, err) 85 router := newMockOperatorRouter(metaCli) 86 oper := NewJobOperatorImpl(metaCli, router) 87 88 jobID := "cancel-job-id" 89 meta := &frameModel.MasterMeta{ 90 ID: jobID, 91 Type: frameModel.CvsJobMaster, 92 State: frameModel.MasterStateInit, 93 } 94 err = router.jobOnline(ctx, jobID, meta) 95 require.NoError(t, err) 96 97 require.False(t, oper.IsJobCanceling(ctx, jobID)) 98 err = oper.MarkJobCanceling(ctx, jobID) 99 require.NoError(t, err) 100 // cancel job repeatly is ok 101 err = oper.MarkJobCanceling(ctx, jobID) 102 require.NoError(t, err) 103 104 for i := 0; i < 3; i++ { 105 err = oper.Tick(ctx) 106 require.NoError(t, err) 107 router.checkCancelCalls(t, jobID, i+1) 108 checkJobOpWithStatus(ctx, t, metaCli, ormModel.JobOpStatusCanceling, 1) 109 require.True(t, oper.IsJobCanceling(ctx, jobID)) 110 } 111 112 // mock job master is canceled and status persisted 113 meta.State = frameModel.MasterStateStopped 114 err = router.jobOffline(ctx, jobID, meta) 115 require.NoError(t, err) 116 117 err = oper.Tick(ctx) 118 require.NoError(t, err) 119 checkJobOpWithStatus(ctx, t, metaCli, ormModel.JobOpStatusCanceled, 1) 120 require.False(t, oper.IsJobCanceling(ctx, jobID)) 121 } 122 123 func TestJobOperatorMetOrphanJob(t *testing.T) { 124 t.Parallel() 125 126 ctx := context.Background() 127 metaCli, err := pkgOrm.NewMockClient() 128 require.NoError(t, err) 129 router := newMockOperatorRouter(metaCli) 130 oper := NewJobOperatorImpl(metaCli, router) 131 132 jobID := "cancel-orphan-job-id" 133 err = oper.MarkJobCanceling(ctx, jobID) 134 require.NoError(t, err) 135 136 err = oper.Tick(ctx) 137 require.NoError(t, err) 138 checkJobOpWithStatus(ctx, t, metaCli, ormModel.JobOpStatusNoop, 1) 139 }