github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/pdutil/api_client_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 pdutil
    15  
    16  import (
    17  	"context"
    18  	"encoding/hex"
    19  	"encoding/json"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"strconv"
    23  	"testing"
    24  
    25  	"github.com/pingcap/tidb/pkg/tablecodec"
    26  	"github.com/pingcap/tidb/pkg/util/codec"
    27  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    28  	cerror "github.com/pingcap/tiflow/pkg/errors"
    29  	"github.com/pingcap/tiflow/pkg/httputil"
    30  	"github.com/pingcap/tiflow/pkg/spanz"
    31  	"github.com/stretchr/testify/require"
    32  	pd "github.com/tikv/pd/client"
    33  )
    34  
    35  type mockPDClient struct {
    36  	pd.Client
    37  	testServer *httptest.Server
    38  	url        string
    39  }
    40  
    41  func (m *mockPDClient) GetLeaderURL() string {
    42  	return m.url
    43  }
    44  
    45  func newMockPDClient(normal bool) *mockPDClient {
    46  	mock := &mockPDClient{}
    47  	status := http.StatusOK
    48  	if !normal {
    49  		status = http.StatusNotFound
    50  	}
    51  	mock.testServer = httptest.NewServer(http.HandlerFunc(
    52  		func(w http.ResponseWriter, r *http.Request) {
    53  			w.WriteHeader(status)
    54  			_, _ = w.Write([]byte("{}"))
    55  		},
    56  	))
    57  	mock.url = mock.testServer.URL
    58  
    59  	return mock
    60  }
    61  
    62  func TestMetaLabelNormal(t *testing.T) {
    63  	t.Parallel()
    64  
    65  	mockClient := newMockPDClient(true)
    66  
    67  	pc, err := NewPDAPIClient(mockClient, nil)
    68  	require.NoError(t, err)
    69  	defer pc.Close()
    70  
    71  	ctx, cancel := context.WithCancel(context.Background())
    72  	defer cancel()
    73  	err = pc.UpdateMetaLabel(ctx)
    74  	require.NoError(t, err)
    75  	mockClient.testServer.Close()
    76  }
    77  
    78  func TestMetaLabelFail(t *testing.T) {
    79  	t.Parallel()
    80  
    81  	mockClient := newMockPDClient(false)
    82  	pc, err := NewPDAPIClient(mockClient, nil)
    83  	require.NoError(t, err)
    84  	defer pc.Close()
    85  	mockClient.url = "http://127.0.1.1:2345"
    86  
    87  	ctx, cancel := context.WithCancel(context.Background())
    88  	defer cancel()
    89  	// test url error
    90  	err = pc.(*pdAPIClient).patchMetaLabel(ctx)
    91  	require.Error(t, err)
    92  
    93  	// test 404
    94  	mockClient.url = mockClient.testServer.URL
    95  	err = pc.(*pdAPIClient).patchMetaLabel(ctx)
    96  	require.Regexp(t, ".*404.*", err)
    97  
    98  	err = pc.UpdateMetaLabel(ctx)
    99  	require.ErrorIs(t, err, cerror.ErrReachMaxTry)
   100  	mockClient.testServer.Close()
   101  }
   102  
   103  func TestListGcServiceSafePoint(t *testing.T) {
   104  	t.Parallel()
   105  
   106  	mockClient := newMockPDClient(true)
   107  
   108  	ctx, cancel := context.WithCancel(context.Background())
   109  	defer cancel()
   110  
   111  	pc, err := NewPDAPIClient(mockClient, nil)
   112  	require.NoError(t, err)
   113  	defer pc.Close()
   114  	_, err = pc.ListGcServiceSafePoint(ctx)
   115  	require.NoError(t, err)
   116  	mockClient.testServer.Close()
   117  }
   118  
   119  // LabelRulePatch is the patch to update the label rules.
   120  // NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
   121  // Copied from github.com/tikv/pd/server/schedule/labeler
   122  type LabelRulePatch struct {
   123  	SetRules    []*LabelRule `json:"sets"`
   124  	DeleteRules []string     `json:"deletes"`
   125  }
   126  
   127  // LabelRule is the rule to assign labels to a region.
   128  // NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
   129  // Copied from github.com/tikv/pd/server/schedule/labeler
   130  type LabelRule struct {
   131  	ID       string        `json:"id"`
   132  	Index    int           `json:"index"`
   133  	Labels   []RegionLabel `json:"labels"`
   134  	RuleType string        `json:"rule_type"`
   135  	Data     interface{}   `json:"data"`
   136  }
   137  
   138  // RegionLabel is the label of a region.
   139  // NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
   140  // Copied from github.com/tikv/pd/server/schedule/labeler
   141  type RegionLabel struct {
   142  	Key   string `json:"key"`
   143  	Value string `json:"value"`
   144  }
   145  
   146  func TestMetaLabelDecodeJSON(t *testing.T) {
   147  	t.Parallel()
   148  
   149  	meta := LabelRulePatch{}
   150  	require.Nil(t, json.Unmarshal([]byte(addMetaJSON), &meta))
   151  	require.Len(t, meta.SetRules, 2)
   152  	keys := meta.SetRules[1].Data.([]interface{})[0].(map[string]interface{})
   153  	startKey, err := hex.DecodeString(keys["start_key"].(string))
   154  	require.NoError(t, err)
   155  	endKey, err := hex.DecodeString(keys["end_key"].(string))
   156  	require.NoError(t, err)
   157  
   158  	_, startKey, err = codec.DecodeBytes(startKey, nil)
   159  	require.NoError(t, err)
   160  	require.EqualValues(
   161  		t, spanz.JobTableID, tablecodec.DecodeTableID(startKey), keys["start_key"].(string))
   162  
   163  	_, endKey, err = codec.DecodeBytes(endKey, nil)
   164  	require.NoError(t, err)
   165  	require.EqualValues(
   166  		t, spanz.JobTableID+1, tablecodec.DecodeTableID(endKey), keys["end_key"].(string))
   167  }
   168  
   169  func TestScanRegions(t *testing.T) {
   170  	t.Parallel()
   171  
   172  	regions := []RegionInfo{
   173  		NewTestRegionInfo(2, []byte(""), []byte{0, 1}, 0),
   174  		NewTestRegionInfo(3, []byte{0, 1}, []byte{0, 2}, 1),
   175  		NewTestRegionInfo(4, []byte{0, 2}, []byte{0, 3}, 2),
   176  		NewTestRegionInfo(5, []byte{0, 2}, []byte{0, 4}, 3), // a merged region.
   177  		NewTestRegionInfo(6, []byte{0, 4}, []byte{1, 0}, 4),
   178  		NewTestRegionInfo(7, []byte{1, 0}, []byte{1, 1}, 5),
   179  		NewTestRegionInfo(8, []byte{1, 1}, []byte(""), 6),
   180  	}
   181  	var handler func() RegionsInfo
   182  	mockPDServer := httptest.NewServer(http.HandlerFunc(
   183  		func(w http.ResponseWriter, r *http.Request) {
   184  			w.WriteHeader(http.StatusOK)
   185  			startKey, _ := hex.DecodeString(r.URL.Query()["key"][0])
   186  			endKey, _ := hex.DecodeString(r.URL.Query()["end_key"][0])
   187  			limit, _ := strconv.Atoi(r.URL.Query()["limit"][0])
   188  			t.Log(startKey, endKey, limit)
   189  			info := handler()
   190  			info.Count = len(info.Regions)
   191  			data, _ := json.Marshal(info)
   192  			t.Logf("%s", string(data))
   193  			_, _ = w.Write(data)
   194  		},
   195  	))
   196  	defer mockPDServer.Close()
   197  
   198  	httpcli, _ := httputil.NewClient(nil)
   199  	pc := pdAPIClient{httpClient: httpcli}
   200  
   201  	i := 0
   202  	handler = func() RegionsInfo {
   203  		start := i
   204  		end := i + 1
   205  		i++
   206  		if end > len(regions) {
   207  			return RegionsInfo{Regions: regions[start:]}
   208  		}
   209  		return RegionsInfo{Regions: regions[start:end]}
   210  	}
   211  	rs, err := pc.scanRegions(context.Background(), tablepb.Span{}, []string{mockPDServer.URL}, 1)
   212  	require.NoError(t, err)
   213  	require.Equal(t, 7, len(rs))
   214  
   215  	handler = func() RegionsInfo {
   216  		return RegionsInfo{Regions: regions}
   217  	}
   218  	rs, err = pc.scanRegions(context.Background(), tablepb.Span{}, []string{mockPDServer.URL}, 1024)
   219  	require.NoError(t, err)
   220  	require.Equal(t, 7, len(rs))
   221  
   222  	i = 0
   223  	handler = func() RegionsInfo {
   224  		if i != 0 {
   225  			require.FailNow(t, "must only request once")
   226  		}
   227  		i++
   228  		return RegionsInfo{Regions: regions[2:3]}
   229  	}
   230  	rs, err = pc.scanRegions(
   231  		context.Background(),
   232  		tablepb.Span{StartKey: []byte{0, 2, 0}, EndKey: []byte{0, 3}},
   233  		[]string{mockPDServer.URL}, 1)
   234  	require.NoError(t, err)
   235  	require.Equal(t, 1, len(rs))
   236  
   237  	i = 0
   238  	handler = func() RegionsInfo {
   239  		if i == 0 {
   240  			i++
   241  			return RegionsInfo{Regions: regions[2:3]}
   242  		} else if i == 1 {
   243  			i++
   244  			return RegionsInfo{Regions: regions[3:4]}
   245  		} else if i == 2 {
   246  			i++
   247  			return RegionsInfo{Regions: regions[4:5]}
   248  		} else {
   249  			require.FailNow(t, "must only request once")
   250  			return RegionsInfo{}
   251  		}
   252  	}
   253  	rs, err = pc.scanRegions(
   254  		context.Background(),
   255  		tablepb.Span{StartKey: []byte{0, 2, 0}, EndKey: []byte{0, 4, 0}},
   256  		[]string{mockPDServer.URL}, 1)
   257  	require.NoError(t, err)
   258  	require.Equal(t, 3, len(rs))
   259  }