github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/servermaster/serverutil/watch_executors_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 serverutil
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tiflow/engine/model"
    23  	"github.com/pingcap/tiflow/engine/pkg/notifier"
    24  	"github.com/pingcap/tiflow/pkg/errors"
    25  	"github.com/stretchr/testify/mock"
    26  	"github.com/stretchr/testify/require"
    27  	"go.uber.org/atomic"
    28  )
    29  
    30  type mockExecutorWatcher struct {
    31  	mock.Mock
    32  }
    33  
    34  func newMockExecutorWatcher() *mockExecutorWatcher {
    35  	return &mockExecutorWatcher{}
    36  }
    37  
    38  func (w *mockExecutorWatcher) WatchExecutors(ctx context.Context) (
    39  	snap map[model.ExecutorID]string,
    40  	stream *notifier.Receiver[model.ExecutorStatusChange],
    41  	err error,
    42  ) {
    43  	args := w.Called(ctx)
    44  	return args.Get(0).(map[model.ExecutorID]string),
    45  		args.Get(1).(*notifier.Receiver[model.ExecutorStatusChange]),
    46  		args.Error(2)
    47  }
    48  
    49  type mockExecutorInfoUser struct {
    50  	mock.Mock
    51  }
    52  
    53  func newMockExecutorInfoUser() *mockExecutorInfoUser {
    54  	return &mockExecutorInfoUser{}
    55  }
    56  
    57  func (u *mockExecutorInfoUser) UpdateExecutorList(executors map[model.ExecutorID]string) error {
    58  	args := u.Called(executors)
    59  	return args.Error(0)
    60  }
    61  
    62  func (u *mockExecutorInfoUser) AddExecutor(executorID model.ExecutorID, addr string) error {
    63  	args := u.Called(executorID, addr)
    64  	return args.Error(0)
    65  }
    66  
    67  func (u *mockExecutorInfoUser) RemoveExecutor(executorID model.ExecutorID) error {
    68  	args := u.Called(executorID)
    69  	return args.Error(0)
    70  }
    71  
    72  func TestWatchExecutors(t *testing.T) {
    73  	t.Parallel()
    74  
    75  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    76  	defer cancel()
    77  
    78  	watcher := newMockExecutorWatcher()
    79  	user := newMockExecutorInfoUser()
    80  
    81  	snap := map[model.ExecutorID]string{
    82  		"executor-1": "127.0.0.1:1111",
    83  		"executor-2": "127.0.0.1:2222",
    84  	}
    85  
    86  	evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]()
    87  	defer evNotifier.Close()
    88  
    89  	watcher.On("WatchExecutors", mock.Anything).
    90  		Return(snap, evNotifier.NewReceiver(), nil).Times(1)
    91  	user.On("UpdateExecutorList", snap).Return(nil).Times(1)
    92  
    93  	events := []model.ExecutorStatusChange{
    94  		{
    95  			Tp:   model.EventExecutorOnline,
    96  			ID:   "executor-3",
    97  			Addr: "127.0.0.1:3333",
    98  		},
    99  		{
   100  			Tp:   model.EventExecutorOffline,
   101  			ID:   "executor-2",
   102  			Addr: "127.0.0.1:2222",
   103  		},
   104  		{
   105  			Tp:   model.EventExecutorOffline,
   106  			ID:   "executor-3",
   107  			Addr: "127.0.0.1:3333",
   108  		},
   109  	}
   110  
   111  	var eventCount atomic.Int64
   112  	for _, event := range events {
   113  		if event.Tp == model.EventExecutorOnline {
   114  			user.On("AddExecutor", event.ID, event.Addr).
   115  				Return(nil).Times(1).
   116  				Run(func(_ mock.Arguments) {
   117  					eventCount.Add(1)
   118  				})
   119  		} else if event.Tp == model.EventExecutorOffline {
   120  			user.On("RemoveExecutor", event.ID).
   121  				Return(nil).Times(1).
   122  				Run(func(_ mock.Arguments) {
   123  					eventCount.Add(1)
   124  				})
   125  		} else {
   126  			require.FailNow(t, "unexpected event type")
   127  		}
   128  	}
   129  
   130  	var wg sync.WaitGroup
   131  	wg.Add(1)
   132  	go func() {
   133  		defer wg.Done()
   134  
   135  		err := WatchExecutors(ctx, watcher, user)
   136  		require.ErrorIs(t, err, context.Canceled)
   137  	}()
   138  
   139  	for _, event := range events {
   140  		evNotifier.Notify(event)
   141  	}
   142  
   143  	require.Eventually(t, func() bool {
   144  		return eventCount.Load() == int64(len(events))
   145  	}, 1*time.Second, 10*time.Millisecond)
   146  
   147  	cancel()
   148  	wg.Wait()
   149  }
   150  
   151  func TestWatchExecutorFailed(t *testing.T) {
   152  	t.Parallel()
   153  
   154  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   155  	defer cancel()
   156  
   157  	watcher := newMockExecutorWatcher()
   158  	user := newMockExecutorInfoUser()
   159  
   160  	evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]()
   161  	defer evNotifier.Close()
   162  
   163  	watcher.On("WatchExecutors", mock.Anything).
   164  		Return(map[model.ExecutorID]string(nil),
   165  			(*notifier.Receiver[model.ExecutorStatusChange])(nil),
   166  			errors.New("test error"),
   167  		).Times(1)
   168  
   169  	err := WatchExecutors(ctx, watcher, user)
   170  	require.ErrorContains(t, err, "test error")
   171  }
   172  
   173  func TestCloseWatchExecutors(t *testing.T) {
   174  	t.Parallel()
   175  
   176  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   177  	defer cancel()
   178  
   179  	watcher := newMockExecutorWatcher()
   180  	user := newMockExecutorInfoUser()
   181  
   182  	evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]()
   183  
   184  	snap := map[model.ExecutorID]string{}
   185  	watcher.On("WatchExecutors", mock.Anything).
   186  		Return(snap, evNotifier.NewReceiver(), nil).Times(1)
   187  	user.On("UpdateExecutorList", snap).Return(nil).Times(1)
   188  
   189  	var wg sync.WaitGroup
   190  	wg.Add(1)
   191  	go func() {
   192  		defer wg.Done()
   193  		err := WatchExecutors(ctx, watcher, user)
   194  		require.ErrorIs(t, err, errors.ErrExecutorWatcherClosed)
   195  	}()
   196  
   197  	evNotifier.Close()
   198  	wg.Wait()
   199  }