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  }