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 }