github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/renameworker_test.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "mime" 10 "mime/multipart" 11 "net/http" 12 "strconv" 13 "strings" 14 "sync" 15 "testing" 16 17 "github.com/0chain/errors" 18 "github.com/0chain/gosdk/core/zcncrypto" 19 "github.com/0chain/gosdk/dev" 20 devMock "github.com/0chain/gosdk/dev/mock" 21 "github.com/0chain/gosdk/sdks/blobber" 22 "github.com/0chain/gosdk/zboxcore/blockchain" 23 zclient "github.com/0chain/gosdk/zboxcore/client" 24 "github.com/0chain/gosdk/zboxcore/fileref" 25 "github.com/0chain/gosdk/zboxcore/mocks" 26 "github.com/0chain/gosdk/zboxcore/zboxutil" 27 "github.com/stretchr/testify/mock" 28 "github.com/stretchr/testify/require" 29 ) 30 31 func TestRenameRequest_renameBlobberObject(t *testing.T) { 32 const ( 33 mockAllocationTxId = "mock transaction id" 34 mockClientId = "mock client id" 35 mockClientKey = "mock client key" 36 mockRemoteFilePath = "mock/remote/file/path" 37 mockAllocationId = "mock allocation id" 38 mockBlobberId = "mock blobber id" 39 mockType = "f" 40 mockConnectionId = "1234567890" 41 mockNewName = "mock new name" 42 ) 43 44 var mockClient = mocks.HttpClient{} 45 rawClient := zboxutil.Client 46 zboxutil.Client = &mockClient 47 defer func() { 48 zboxutil.Client = rawClient 49 }() 50 51 client := zclient.GetClient() 52 client.Wallet = &zcncrypto.Wallet{ 53 ClientID: mockClientId, 54 ClientKey: mockClientKey, 55 } 56 57 type parameters struct { 58 referencePathToRetrieve fileref.ReferencePath 59 requestFields map[string]string 60 } 61 62 tests := []struct { 63 name string 64 parameters parameters 65 setup func(*testing.T, string, parameters) 66 wantErr bool 67 errMsg string 68 wantFunc func(*require.Assertions, *RenameRequest) 69 }{ 70 { 71 name: "Test_Error_New_HTTP_Failed_By_Containing_" + string([]byte{0x7f, 0, 0}), 72 setup: func(t *testing.T, testName string, p parameters) {}, 73 wantErr: true, 74 errMsg: `net/url: invalid control character in URL`, 75 }, 76 { 77 name: "Test_Error_Get_Object_Tree_From_Blobber_Failed", 78 setup: func(t *testing.T, testName string, p parameters) { 79 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 80 return strings.HasPrefix(req.URL.Path, testName) 81 })).Return(&http.Response{ 82 StatusCode: http.StatusBadRequest, 83 Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 84 }, nil) 85 }, 86 wantErr: true, 87 errMsg: "400: Object tree error response: Body:", 88 }, 89 { 90 name: "Test_Rename_Blobber_Object_Failed", 91 parameters: parameters{ 92 referencePathToRetrieve: fileref.ReferencePath{ 93 Meta: map[string]interface{}{ 94 "type": mockType, 95 }, 96 }, 97 }, 98 setup: func(t *testing.T, testName string, p parameters) { 99 100 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 101 return strings.Contains(req.URL.Path, "latestwritemarker") && 102 req.Method == "GET" 103 })).Return(&http.Response{ 104 StatusCode: http.StatusOK, 105 Body: func() io.ReadCloser { 106 jsonFR := `{"latest_write_marker":null,"prev_write_marker":null}` 107 return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR))) 108 }(), 109 }, nil) 110 111 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 112 return strings.HasPrefix(req.URL.Path, testName) && 113 req.Method == "GET" && 114 req.Header.Get("X-App-Client-ID") == mockClientId && 115 req.Header.Get("X-App-Client-Key") == mockClientKey 116 })).Return(&http.Response{ 117 StatusCode: http.StatusOK, 118 Body: func() io.ReadCloser { 119 jsonFR, err := json.Marshal(p.referencePathToRetrieve) 120 require.NoError(t, err) 121 return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR))) 122 }(), 123 }, nil) 124 125 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 126 return strings.HasPrefix(req.URL.Path, testName) && 127 req.Method == "POST" && 128 req.Header.Get("X-App-Client-ID") == mockClientId && 129 req.Header.Get("X-App-Client-Key") == mockClientKey 130 })).Return(&http.Response{ 131 StatusCode: http.StatusBadRequest, 132 Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 133 }, nil) 134 }, 135 wantErr: true, 136 errMsg: "response_error: ", 137 wantFunc: func(require *require.Assertions, req *RenameRequest) { 138 require.NotNil(req) 139 require.Equal(0, req.renameMask.CountOnes()) 140 require.Equal(0, req.consensus.consensus) 141 }, 142 }, 143 { 144 name: "Test_Rename_Blobber_Object_Success", 145 parameters: parameters{ 146 referencePathToRetrieve: fileref.ReferencePath{ 147 Meta: map[string]interface{}{ 148 "type": mockType, 149 }, 150 }, 151 requestFields: map[string]string{ 152 "connection_id": mockConnectionId, 153 "path": mockRemoteFilePath, 154 "new_name": mockNewName, 155 }, 156 }, 157 setup: func(t *testing.T, testName string, p parameters) { 158 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 159 return strings.Contains(req.URL.Path, "latestwritemarker") && 160 req.Method == "GET" 161 })).Return(&http.Response{ 162 StatusCode: http.StatusOK, 163 Body: func() io.ReadCloser { 164 jsonFR := `{"latest_write_marker":null,"prev_write_marker":null}` 165 return io.NopCloser(bytes.NewReader([]byte(jsonFR))) 166 }(), 167 }, nil) 168 169 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 170 return strings.HasPrefix(req.URL.Path, testName) && 171 req.Method == "GET" && 172 req.Header.Get("X-App-Client-ID") == mockClientId && 173 req.Header.Get("X-App-Client-Key") == mockClientKey 174 })).Return(&http.Response{ 175 StatusCode: http.StatusOK, 176 Body: func() io.ReadCloser { 177 jsonFR, err := json.Marshal(p.referencePathToRetrieve) 178 require.NoError(t, err) 179 return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR))) 180 }(), 181 }, nil) 182 183 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 184 mediaType, params, err := mime.ParseMediaType(req.Header.Get("Content-Type")) 185 require.NoError(t, err) 186 require.True(t, strings.HasPrefix(mediaType, "multipart/")) 187 reader := multipart.NewReader(req.Body, params["boundary"]) 188 189 err = nil 190 for { 191 var part *multipart.Part 192 part, err = reader.NextPart() 193 if err != nil { 194 break 195 } 196 expected, ok := p.requestFields[part.FormName()] 197 require.True(t, ok) 198 actual, err := ioutil.ReadAll(part) 199 require.NoError(t, err) 200 require.EqualValues(t, expected, string(actual)) 201 } 202 require.Error(t, err) 203 require.EqualValues(t, "EOF", errors.Top(err)) 204 205 return strings.HasPrefix(req.URL.Path, testName) && 206 req.Method == "POST" && 207 req.Header.Get("X-App-Client-ID") == mockClientId && 208 req.Header.Get("X-App-Client-Key") == mockClientKey 209 })).Return(&http.Response{ 210 StatusCode: http.StatusOK, 211 Body: func() io.ReadCloser { 212 jsonFR, err := json.Marshal(p.referencePathToRetrieve) 213 require.NoError(t, err) 214 return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR))) 215 }(), 216 }, nil) 217 }, 218 wantFunc: func(require *require.Assertions, req *RenameRequest) { 219 require.NotNil(req) 220 require.Equal(4, req.renameMask.CountOnes()) 221 require.Equal(1, req.consensus.consensus) 222 }, 223 }, 224 } 225 for _, tt := range tests { 226 t.Run(tt.name, func(t *testing.T) { 227 require := require.New(t) 228 tt.setup(t, tt.name, tt.parameters) 229 req := &RenameRequest{ 230 allocationID: mockAllocationId, 231 allocationTx: mockAllocationTxId, 232 remotefilepath: mockRemoteFilePath, 233 consensus: Consensus{ 234 RWMutex: &sync.RWMutex{}, 235 consensusThresh: 2, 236 fullconsensus: 4, 237 }, 238 ctx: context.TODO(), 239 renameMask: zboxutil.NewUint128(1).Lsh(uint64(4)).Sub64(1), 240 maskMU: &sync.Mutex{}, 241 connectionID: mockConnectionId, 242 newName: mockNewName, 243 } 244 req.blobbers = append(req.blobbers, &blockchain.StorageNode{ 245 Baseurl: tt.name, 246 }) 247 _, err := req.renameBlobberObject(req.blobbers[0], 0) 248 require.EqualValues(tt.wantErr, err != nil, "Error: ", err) 249 if err != nil { 250 require.Contains(errors.Top(err), tt.errMsg) 251 return 252 } 253 require.NoErrorf(err, "expected no error but got %v", err) 254 if tt.wantFunc != nil { 255 tt.wantFunc(require, req) 256 } 257 }) 258 } 259 } 260 261 func TestRenameRequest_ProcessRename(t *testing.T) { 262 const ( 263 mockAllocationTxId = "mock transaction id" 264 mockClientId = "mock client id" 265 mockClientKey = "mock client key" 266 mockRemoteFilePath = "mock/remote/file/path" 267 mockAllocationId = "mock allocation id" 268 mockBlobberId = "mock blobber id" 269 mockBlobberUrl = "mockblobberurl" 270 mockType = "f" 271 mockConnectionId = "1234567890" 272 mockNewName = "mock new name" 273 ) 274 275 var mockClient = mocks.HttpClient{} 276 rawClient := zboxutil.Client 277 zboxutil.Client = &mockClient 278 defer func() { 279 zboxutil.Client = rawClient 280 }() 281 282 client := zclient.GetClient() 283 client.Wallet = &zcncrypto.Wallet{ 284 ClientID: mockClientId, 285 ClientKey: mockClientKey, 286 } 287 288 setupHttpResponses := func(t *testing.T, testName string, numBlobbers int, numCorrect int, req *RenameRequest) { 289 290 for i := 0; i < numBlobbers; i++ { 291 url := mockBlobberUrl + strconv.Itoa(i) 292 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 293 return req.Method == "GET" && 294 strings.Contains(req.URL.String(), testName+url) && !strings.Contains(req.URL.String(), "latestwritemarker") 295 })).Return(&http.Response{ 296 StatusCode: http.StatusOK, 297 Body: func() io.ReadCloser { 298 jsonFR, err := json.Marshal(&fileref.ReferencePath{ 299 Meta: map[string]interface{}{ 300 "type": mockType, 301 }, 302 }) 303 require.NoError(t, err) 304 return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR))) 305 }(), 306 }, nil) 307 308 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 309 return req.Method == "POST" && 310 strings.Contains(req.URL.String(), zboxutil.RENAME_ENDPOINT) && 311 strings.Contains(req.URL.String(), testName+url) 312 })).Return(&http.Response{ 313 StatusCode: func() int { 314 if i < numCorrect { 315 return http.StatusOK 316 } 317 return http.StatusBadRequest 318 }(), 319 Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 320 }, nil) 321 322 if i < numCorrect { 323 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 324 return req.Method == "POST" && 325 strings.Contains(req.URL.String(), zboxutil.WM_LOCK_ENDPOINT) && 326 strings.Contains(req.URL.String(), testName+url) 327 })).Return(&http.Response{ 328 StatusCode: func() int { 329 if i < numCorrect { 330 return http.StatusOK 331 } 332 return http.StatusBadRequest 333 }(), 334 Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":2}`))), 335 }, nil) 336 337 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 338 return req.Method == "POST" && 339 strings.Contains(req.URL.String(), zboxutil.COMMIT_ENDPOINT) && 340 strings.Contains(req.URL.String(), testName+url) 341 })).Return(&http.Response{ 342 StatusCode: func() int { 343 if i < numCorrect { 344 return http.StatusOK 345 } 346 return http.StatusBadRequest 347 }(), 348 Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 349 }, nil) 350 } 351 352 mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { 353 return req.Method == "DELETE" && 354 strings.Contains(req.URL.String(), testName+url) 355 })).Return(&http.Response{ 356 StatusCode: http.StatusOK, 357 }, nil) 358 } 359 360 commitChan = make(map[string]chan *CommitRequest) 361 for _, blobber := range req.blobbers { 362 if _, ok := commitChan[blobber.ID]; !ok { 363 commitChan[blobber.ID] = make(chan *CommitRequest, 1) 364 } 365 } 366 blobberChan := commitChan 367 go func() { 368 cm0 := <-blobberChan[req.blobbers[0].ID] 369 require.EqualValues(t, cm0.blobber.ID, testName+mockBlobberId+strconv.Itoa(0)) 370 cm0.result = &CommitResult{ 371 Success: true, 372 } 373 if cm0.wg != nil { 374 cm0.wg.Done() 375 } 376 }() 377 go func() { 378 cm1 := <-blobberChan[req.blobbers[1].ID] 379 require.EqualValues(t, cm1.blobber.ID, testName+mockBlobberId+strconv.Itoa(1)) 380 cm1.result = &CommitResult{ 381 Success: true, 382 } 383 if cm1.wg != nil { 384 cm1.wg.Done() 385 } 386 }() 387 go func() { 388 cm2 := <-blobberChan[req.blobbers[2].ID] 389 require.EqualValues(t, cm2.blobber.ID, testName+mockBlobberId+strconv.Itoa(2)) 390 cm2.result = &CommitResult{ 391 Success: true, 392 } 393 if cm2.wg != nil { 394 cm2.wg.Done() 395 } 396 }() 397 go func() { 398 cm3 := <-blobberChan[req.blobbers[3].ID] 399 require.EqualValues(t, cm3.blobber.ID, testName+mockBlobberId+strconv.Itoa(3)) 400 cm3.result = &CommitResult{ 401 Success: true, 402 } 403 if cm3.wg != nil { 404 cm3.wg.Done() 405 } 406 }() 407 } 408 409 tests := []struct { 410 name string 411 numBlobbers int 412 numCorrect int 413 setup func(*testing.T, string, int, int, *RenameRequest) 414 wantErr bool 415 errMsg string 416 wantFunc func(require *require.Assertions, req *RenameRequest) 417 }{ 418 { 419 name: "Test_All_Blobber_Rename_Success", 420 numBlobbers: 4, 421 numCorrect: 4, 422 setup: setupHttpResponses, 423 wantErr: false, 424 wantFunc: func(require *require.Assertions, req *RenameRequest) { 425 require.NotNil(req) 426 require.Equal(4, req.renameMask.CountOnes()) 427 require.Equal(4, req.consensus.consensus) 428 }, 429 }, 430 { 431 name: "Test_Blobber_Index_3_Error_On_Rename_Success", 432 numBlobbers: 4, 433 numCorrect: 3, 434 setup: setupHttpResponses, 435 wantErr: false, 436 wantFunc: func(require *require.Assertions, req *RenameRequest) { 437 require.NotNil(req) 438 require.Equal(3, req.renameMask.CountOnes()) 439 require.Equal(3, req.consensus.consensus) 440 }, 441 }, 442 { 443 name: "Test_Blobber_Index_2_3_Error_On_Rename_Failure", 444 numBlobbers: 4, 445 numCorrect: 2, 446 setup: setupHttpResponses, 447 wantErr: true, 448 errMsg: "rename_failed", 449 }, 450 { 451 name: "Test_All_Blobber_Error_On_Rename_Failure", 452 numBlobbers: 4, 453 numCorrect: 0, 454 setup: setupHttpResponses, 455 wantErr: true, 456 errMsg: "rename_failed", 457 }, 458 } 459 for _, tt := range tests { 460 t.Run(tt.name, func(t *testing.T) { 461 require := require.New(t) 462 463 a := &Allocation{ 464 Tx: "TestRenameRequest_ProcessRename", 465 DataShards: numBlobbers, 466 } 467 468 setupMockAllocation(t, a) 469 470 resp := &WMLockResult{ 471 Status: WMLockStatusOK, 472 } 473 474 respBuf, _ := json.Marshal(resp) 475 m := make(devMock.ResponseMap) 476 477 server := dev.NewBlobberServer(m) 478 defer server.Close() 479 480 for i := 0; i < numBlobbers; i++ { 481 path := "/TestRenameRequest_ProcessRename" + tt.name + mockBlobberUrl + strconv.Itoa(i) 482 483 m[http.MethodPost+":"+path+blobber.EndpointWriteMarkerLock+a.ID] = devMock.Response{ 484 StatusCode: http.StatusOK, 485 Body: respBuf, 486 } 487 488 a.Blobbers = append(a.Blobbers, &blockchain.StorageNode{ 489 ID: tt.name + mockBlobberId + strconv.Itoa(i), 490 Baseurl: server.URL + path, 491 }) 492 } 493 494 setupMockRollback(a, &mockClient) 495 496 req := &RenameRequest{ 497 allocationObj: a, 498 blobbers: a.Blobbers, 499 allocationID: mockAllocationId, 500 allocationTx: mockAllocationTxId, 501 remotefilepath: mockRemoteFilePath, 502 consensus: Consensus{ 503 RWMutex: &sync.RWMutex{}, 504 consensusThresh: 3, 505 fullconsensus: 4, 506 }, 507 renameMask: zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1), 508 maskMU: &sync.Mutex{}, 509 connectionID: mockConnectionId, 510 newName: mockNewName, 511 } 512 req.ctx, req.ctxCncl = context.WithCancel(context.TODO()) 513 tt.setup(t, tt.name, tt.numBlobbers, tt.numCorrect, req) 514 err := req.ProcessRename() 515 require.EqualValues(tt.wantErr, err != nil, err) 516 if err != nil { 517 require.Contains(errors.Top(err), tt.errMsg) 518 return 519 } 520 if tt.wantFunc != nil { 521 tt.wantFunc(require, req) 522 } 523 }) 524 } 525 }