github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/externalresource/manager/gc_coordinator_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 manager 15 16 import ( 17 "context" 18 "sync" 19 "testing" 20 "time" 21 22 frameModel "github.com/pingcap/tiflow/engine/framework/model" 23 "github.com/pingcap/tiflow/engine/model" 24 "github.com/pingcap/tiflow/engine/pkg/externalresource/internal/bucket" 25 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 26 pkgOrm "github.com/pingcap/tiflow/engine/pkg/orm" 27 "github.com/stretchr/testify/require" 28 ) 29 30 type gcTestHelper struct { 31 ExecInfo *MockExecutorInfoProvider 32 JobInfo *MockJobStatusProvider 33 GCRunner *MockGCRunner 34 Meta pkgOrm.Client 35 Coord *DefaultGCCoordinator 36 37 wg sync.WaitGroup 38 ctx context.Context 39 cancel context.CancelFunc 40 errCh chan error 41 } 42 43 func newGCTestHelper() *gcTestHelper { 44 execInfo := NewMockExecutorInfoProvider() 45 jobInfo := NewMockJobStatusProvider() 46 meta, err := pkgOrm.NewMockClient() 47 if err != nil { 48 panic(err) 49 } 50 gcRunner := NewMockGCRunner(meta) 51 coord := NewGCCoordinator(execInfo, jobInfo, meta, gcRunner) 52 53 ctx, cancel := context.WithCancel(context.Background()) 54 ret := &gcTestHelper{ 55 ExecInfo: execInfo, 56 JobInfo: jobInfo, 57 Meta: meta, 58 GCRunner: gcRunner, 59 Coord: coord, 60 61 ctx: ctx, 62 cancel: cancel, 63 errCh: make(chan error, 1), 64 } 65 66 return ret 67 } 68 69 func (h *gcTestHelper) Start() { 70 h.wg.Add(1) 71 go func() { 72 defer h.wg.Done() 73 74 h.errCh <- h.Coord.Run(h.ctx) 75 }() 76 } 77 78 func (h *gcTestHelper) Close() { 79 h.cancel() 80 h.wg.Wait() 81 } 82 83 func (h *gcTestHelper) GetError() error { 84 return <-h.errCh 85 } 86 87 func (h *gcTestHelper) IsGCPending(t *testing.T, resourceKey pkgOrm.ResourceKey) bool { 88 meta, err := h.Meta.GetResourceByID(context.Background(), resourceKey) 89 if err != nil { 90 require.NoError(t, err) 91 } 92 93 return meta.GCPending 94 } 95 96 func (h *gcTestHelper) IsRemoved(t *testing.T, resourceKey pkgOrm.ResourceKey) bool { 97 _, err := h.Meta.GetResourceByID(context.Background(), resourceKey) 98 if pkgOrm.IsNotFoundError(err) { 99 return true 100 } 101 require.NoError(t, err) 102 return false 103 } 104 105 func (h *gcTestHelper) LoadDefaultMockData(t *testing.T) { 106 h.ExecInfo.AddExecutor("executor-1", "addr-1:8080") 107 h.ExecInfo.AddExecutor("executor-2", "addr-2:8080") 108 h.ExecInfo.AddExecutor("executor-3", "addr-3:8080") 109 110 h.JobInfo.SetJobStatus("job-1", frameModel.MasterStateInit) 111 h.JobInfo.SetJobStatus("job-2", frameModel.MasterStateInit) 112 h.JobInfo.SetJobStatus("job-3", frameModel.MasterStateInit) 113 114 err := h.Meta.CreateResource(context.Background(), &resModel.ResourceMeta{ 115 ID: "/local/resource-1", 116 Job: "job-1", 117 Worker: "worker-1", 118 Executor: "executor-1", 119 }) 120 require.NoError(t, err) 121 122 err = h.Meta.CreateResource(context.Background(), &resModel.ResourceMeta{ 123 ID: "/local/resource-2", 124 Job: "job-2", 125 Worker: "worker-2", 126 Executor: "executor-2", 127 }) 128 require.NoError(t, err) 129 130 err = h.Meta.CreateResource(context.Background(), &resModel.ResourceMeta{ 131 ID: "/local/resource-3", 132 Job: "job-3", 133 Worker: "worker-3", 134 Executor: "executor-3", 135 }) 136 require.NoError(t, err) 137 138 executors := []string{"executor-1", "executor-2", "executor-3"} 139 for _, executor := range executors { 140 id := model.ExecutorID(executor) 141 err = h.Meta.CreateResource(context.Background(), &resModel.ResourceMeta{ 142 ID: bucket.DummyResourceID, 143 Job: bucket.GetDummyJobID(id), 144 Worker: bucket.DummyWorkerID, 145 Executor: id, 146 }) 147 require.NoError(t, err) 148 } 149 } 150 151 func TestGCCoordinatorRemoveExecutors(t *testing.T) { 152 t.Parallel() 153 helper := newGCTestHelper() 154 helper.LoadDefaultMockData(t) 155 helper.Start() 156 157 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"})) 158 helper.ExecInfo.RemoveExecutor("executor-1") 159 require.Eventually(t, func() bool { 160 return helper.IsRemoved(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"}) 161 }, 1*time.Second, 10*time.Millisecond) 162 163 time.Sleep(20 * time.Millisecond) 164 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"})) 165 helper.ExecInfo.RemoveExecutor("executor-2") 166 require.Eventually(t, func() bool { 167 return helper.IsRemoved(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"}) 168 }, 1*time.Second, 10*time.Millisecond) 169 170 time.Sleep(20 * time.Millisecond) 171 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-3", ID: "/local/resource-3"})) 172 helper.ExecInfo.RemoveExecutor("executor-3") 173 require.Eventually(t, func() bool { 174 return helper.IsRemoved(t, pkgOrm.ResourceKey{JobID: "job-3", ID: "/local/resource-3"}) 175 }, 1*time.Second, 10*time.Millisecond) 176 177 executors := []string{"executor-1", "executor-2", "executor-3"} 178 for _, executor := range executors { 179 require.Eventually(t, func() bool { 180 return helper.IsRemoved(t, pkgOrm.ResourceKey{ 181 JobID: bucket.GetDummyJobID(model.ExecutorID(executor)), 182 ID: bucket.DummyResourceID, 183 }) 184 }, 1*time.Second, 10*time.Millisecond) 185 } 186 helper.Close() 187 } 188 189 func TestGCCoordinatorRemoveJobs(t *testing.T) { 190 t.Parallel() 191 helper := newGCTestHelper() 192 helper.LoadDefaultMockData(t) 193 helper.Start() 194 195 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"})) 196 helper.JobInfo.RemoveJob("job-1") 197 helper.GCRunner.WaitNotify(t, 1*time.Second) 198 require.Eventually(t, func() bool { 199 return helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"}) 200 }, 1*time.Second, 10*time.Millisecond) 201 202 time.Sleep(20 * time.Millisecond) 203 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"})) 204 helper.JobInfo.RemoveJob("job-2") 205 helper.GCRunner.WaitNotify(t, 1*time.Second) 206 require.Eventually(t, func() bool { 207 return helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"}) 208 }, 1*time.Second, 10*time.Millisecond) 209 210 time.Sleep(20 * time.Millisecond) 211 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-3", ID: "/local/resource-3"})) 212 helper.JobInfo.RemoveJob("job-3") 213 helper.GCRunner.WaitNotify(t, 1*time.Second) 214 require.Eventually(t, func() bool { 215 return helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-3", ID: "/local/resource-3"}) 216 }, 1*time.Second, 10*time.Millisecond) 217 218 helper.Close() 219 } 220 221 func TestGCCoordinatorRemoveJobAndExecutor(t *testing.T) { 222 t.Parallel() 223 helper := newGCTestHelper() 224 helper.LoadDefaultMockData(t) 225 helper.Start() 226 227 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"})) 228 require.False(t, helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"})) 229 230 helper.JobInfo.RemoveJob("job-1") 231 helper.ExecInfo.RemoveExecutor("executor-2") 232 233 helper.GCRunner.WaitNotify(t, 1*time.Second) 234 require.Eventually(t, func() bool { 235 return helper.IsGCPending(t, pkgOrm.ResourceKey{JobID: "job-1", ID: "/local/resource-1"}) 236 }, 1*time.Second, 10*time.Millisecond) 237 require.Eventually(t, func() bool { 238 return helper.IsRemoved(t, pkgOrm.ResourceKey{JobID: "job-2", ID: "/local/resource-2"}) 239 }, 1*time.Second, 10*time.Millisecond) 240 241 helper.Close() 242 } 243 244 func TestDummyBucketResource(t *testing.T) { 245 t.Parallel() 246 require.True(t, isDummyBucketResource(resModel.ResourceTypeS3, bucket.GetDummyResourceName())) 247 require.False(t, isDummyBucketResource(resModel.ResourceTypeS3, "xxx")) 248 require.True(t, isDummyBucketResource(resModel.ResourceTypeGCS, bucket.GetDummyResourceName())) 249 require.False(t, isDummyBucketResource(resModel.ResourceTypeGCS, "xxx")) 250 require.False(t, isDummyBucketResource(resModel.ResourceTypeLocalFile, bucket.GetDummyResourceName())) 251 }