github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/objects/batch_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package objects 5 6 import ( 7 "context" 8 "encoding/base64" 9 "encoding/json" 10 "fmt" 11 "net/http" 12 "net/http/httptest" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/qiniu/go-sdk/v7/storagev2/apis" 18 "github.com/qiniu/go-sdk/v7/storagev2/apis/batch_ops" 19 "github.com/qiniu/go-sdk/v7/storagev2/credentials" 20 "github.com/qiniu/go-sdk/v7/storagev2/http_client" 21 "github.com/qiniu/go-sdk/v7/storagev2/region" 22 ) 23 24 func TestTopoSort(t *testing.T) { 25 objectsManager := NewObjectsManager(nil) 26 object1 := objectsManager.Bucket("bucket1").Object("object1") 27 object2 := objectsManager.Bucket("bucket2").Object("object2") 28 object3 := objectsManager.Bucket("bucket3").Object("object3") 29 object4 := objectsManager.Bucket("bucket4").Object("object4") 30 operations := []Operation{ 31 object4.Stat(), 32 object1.Stat(), 33 object2.Stat(), 34 object3.Stat(), 35 } 36 assertTopoSort(t, operations, [][]Operation{{operations[0]}, {operations[1]}, {operations[2]}, {operations[3]}}) 37 operations = []Operation{ 38 object4.Stat(), 39 object1.Stat(), 40 object2.Stat(), 41 object3.Stat(), 42 object1.SetLifeCycle().DeleteAfterDays(1), 43 object2.SetLifeCycle().DeleteAfterDays(1), 44 object1.CopyTo("bucket2", "object2"), 45 object2.CopyTo("bucket3", "object3"), 46 } 47 assertTopoSort(t, operations, [][]Operation{{operations[0]}, operations[1:]}) 48 operations = []Operation{ 49 object1.Stat(), 50 object2.Stat(), 51 object3.Stat(), 52 object1.SetLifeCycle().DeleteAfterDays(1), 53 object2.SetLifeCycle().DeleteAfterDays(2), 54 object4.CopyTo("bucket2", "object2"), 55 object2.CopyTo("bucket3", "object3"), 56 } 57 assertTopoSort(t, operations, 58 [][]Operation{ 59 {operations[0], operations[3]}, 60 {operations[1], operations[2], operations[4], operations[5], operations[6]}, 61 }) 62 operations = []Operation{ 63 object4.Stat(), 64 object1.Stat(), 65 object2.Stat(), 66 object3.Stat(), 67 object1.SetLifeCycle().DeleteAfterDays(1), 68 object2.SetLifeCycle().DeleteAfterDays(1), 69 object1.CopyTo("bucket2", "object2"), 70 object2.CopyTo("bucket3", "object3"), 71 object4.CopyTo("bucket3", "object3"), 72 } 73 assertTopoSort(t, operations, [][]Operation{operations}) 74 } 75 76 func assertTopoSort(t *testing.T, operations []Operation, operationsGroups [][]Operation) { 77 sortedGroups, err := topoSort(operations) 78 if err != nil { 79 t.Fatal(err) 80 } 81 next: 82 for _, actual := range filterOperations(sortedGroups) { 83 for _, expected := range operationsGroups { 84 if isOperationsEqual(actual, expected) { 85 continue next 86 } 87 } 88 t.Fatalf("failed to match topo sort result") 89 } 90 } 91 92 func isOperationsEqual(operations1, operations2 []Operation) bool { 93 m := make(map[string]struct{}, len(operations1)) 94 for _, operation := range operations1 { 95 m[operation.String()] = struct{}{} 96 } 97 for _, operation := range operations2 { 98 if _, ok := m[operation.String()]; ok { 99 delete(m, operation.String()) 100 } else { 101 return false 102 } 103 } 104 return true 105 } 106 107 func TestDoOperations(t *testing.T) { 108 objectNamesDeleteCounts := make(map[string]uint) 109 objectNamesResponsedCounts := make(map[string]uint) 110 mux := http.NewServeMux() 111 mux.HandleFunc("/batch", func(w http.ResponseWriter, r *http.Request) { 112 if r.Method != http.MethodPost { 113 t.Fatalf("unexpected method") 114 } 115 if err := r.ParseForm(); err != nil { 116 t.Fatal(err) 117 } 118 responses := make([]batch_ops.OperationResponse, 0, len(r.PostForm["op"])) 119 for _, op := range r.PostForm["op"] { 120 if !strings.HasPrefix(op, "delete/") { 121 t.Fatalf("unexpected op: %s", op) 122 } 123 op = strings.TrimPrefix(op, "delete/") 124 entryBytes, err := base64.URLEncoding.DecodeString(op) 125 if err != nil { 126 t.Fatal(err) 127 } 128 if !strings.HasPrefix(string(entryBytes), "bucket1:") { 129 t.Fatalf("unexpected op entry: %s", entryBytes) 130 } 131 objectName := strings.TrimPrefix(string(entryBytes), "bucket1:") 132 objectNamesDeleteCounts[objectName] += 1 133 responses = append(responses, batch_ops.OperationResponse{Code: 200}) 134 } 135 respBody, err := json.Marshal(&batch_ops.Response{ 136 OperationResponses: responses, 137 }) 138 if err != nil { 139 t.Fatal(err) 140 } 141 w.Header().Add("X-ReqId", "fakereqid") 142 w.Write(respBody) 143 }) 144 server := httptest.NewServer(mux) 145 defer server.Close() 146 147 objectsManager := NewObjectsManager(nil) 148 bucket1 := objectsManager.Bucket("bucket1") 149 150 operations := make([]*operation, 20) 151 for i := 0; i < 20; i++ { 152 objectName := fmt.Sprintf("object_%02d", i) 153 operations[i] = &operation{Operation: bucket1.Object(objectName).Delete().OnResponse(func() { 154 objectNamesResponsedCounts[objectName] += 1 155 }).OnError(func(err error) { 156 t.Fatal(err) 157 })} 158 } 159 160 operations, err := doOperations(context.Background(), operations, apis.NewStorage(&http_client.Options{ 161 Credentials: credentials.NewCredentials("testak", "testsk"), 162 Regions: ®ion.Region{Rs: region.Endpoints{Preferred: []string{server.URL}}}, 163 }), 10, 3) 164 if err != nil { 165 t.Fatal(err) 166 } else if len(operations) > 0 { 167 t.Fatalf("unexpected operations returned") 168 } 169 if len(objectNamesDeleteCounts) != 20 { 170 t.Fatalf("unexpected object names deleted count map") 171 } 172 for _, count := range objectNamesDeleteCounts { 173 if count != 1 { 174 t.Fatalf("unexpected objects deleted count") 175 } 176 } 177 if len(objectNamesResponsedCounts) != 20 { 178 t.Fatalf("unexpected object names responsed map") 179 } 180 for _, count := range objectNamesResponsedCounts { 181 if count != 1 { 182 t.Fatalf("unexpected objects responsed count") 183 } 184 } 185 } 186 187 func TestDoOperationsRetries(t *testing.T) { 188 objectNames := make([]string, 0, 20) 189 for i := 0; i < 20; i++ { 190 objectNames = append(objectNames, fmt.Sprintf("object%02d", i)) 191 } 192 objectNamesDeleteCounts := make(map[string]uint) 193 objectNamesResponsedCounts := make(map[string]uint) 194 mux := http.NewServeMux() 195 mux.HandleFunc("/batch", func(w http.ResponseWriter, r *http.Request) { 196 if r.Method != http.MethodPost { 197 t.Fatalf("unexpected method") 198 } 199 if err := r.ParseForm(); err != nil { 200 t.Fatal(err) 201 } 202 responses := make([]batch_ops.OperationResponse, 0, len(r.PostForm["op"])) 203 for _, op := range r.PostForm["op"] { 204 if !strings.HasPrefix(op, "delete/") { 205 t.Fatalf("unexpected op: %s", op) 206 } 207 op = strings.TrimPrefix(op, "delete/") 208 entryBytes, err := base64.URLEncoding.DecodeString(op) 209 if err != nil { 210 t.Fatal(err) 211 } 212 if !strings.HasPrefix(string(entryBytes), "bucket1:") { 213 t.Fatalf("unexpected op entry: %s", entryBytes) 214 } 215 objectName := strings.TrimPrefix(string(entryBytes), "bucket1:") 216 objectNamesDeleteCounts[objectName] += 1 217 responses = append(responses, batch_ops.OperationResponse{Code: 599, Data: batch_ops.OperationResponseData{Error: "test error"}}) 218 } 219 respBody, err := json.Marshal(&batch_ops.Response{ 220 OperationResponses: responses, 221 }) 222 if err != nil { 223 t.Fatal(err) 224 } 225 w.Header().Add("X-ReqId", "fakereqid") 226 w.Write(respBody) 227 }) 228 server := httptest.NewServer(mux) 229 defer server.Close() 230 231 objectsManager := NewObjectsManager(nil) 232 bucket1 := objectsManager.Bucket("bucket1") 233 234 operations := make([]*operation, len(objectNames)) 235 for i, objectName := range objectNames { 236 thisObjectName := objectName 237 operations[i] = &operation{Operation: bucket1.Object(thisObjectName).Delete().OnResponse(func() { 238 t.Fatalf("unexpected response") 239 }).OnError(func(err error) { 240 objectNamesResponsedCounts[thisObjectName] += 1 241 })} 242 } 243 244 operations, err := doOperations(context.Background(), operations, apis.NewStorage(&http_client.Options{ 245 Credentials: credentials.NewCredentials("testak", "testsk"), 246 Regions: ®ion.Region{Rs: region.Endpoints{Preferred: []string{server.URL}}}, 247 }), 10, 3) 248 if err != nil { 249 t.Fatal(err) 250 } else if len(operations) > 0 { 251 t.Fatalf("unexpected operations returned") 252 } 253 if len(objectNamesDeleteCounts) != 20 { 254 t.Fatalf("unexpected object names delete count map") 255 } 256 for _, count := range objectNamesDeleteCounts { 257 if count != 3 { 258 t.Fatalf("unexpected objects delete count") 259 } 260 } 261 if len(objectNamesResponsedCounts) != 20 { 262 t.Fatalf("unexpected object names responsed count map") 263 } 264 for _, count := range objectNamesResponsedCounts { 265 if count != 3 { 266 t.Fatalf("unexpected objects responsed count") 267 } 268 } 269 } 270 271 func TestDoOperationsDontRetry(t *testing.T) { 272 objectNames := make([]string, 0, 20) 273 for i := 0; i < 20; i++ { 274 objectNames = append(objectNames, fmt.Sprintf("object%02d", i)) 275 } 276 objectNamesDeleteCounts := make(map[string]uint) 277 objectNamesResponsedCounts := make(map[string]uint) 278 mux := http.NewServeMux() 279 mux.HandleFunc("/batch", func(w http.ResponseWriter, r *http.Request) { 280 if r.Method != http.MethodPost { 281 t.Fatalf("unexpected method") 282 } 283 if err := r.ParseForm(); err != nil { 284 t.Fatal(err) 285 } 286 responses := make([]batch_ops.OperationResponse, 0, len(r.PostForm["op"])) 287 for _, op := range r.PostForm["op"] { 288 if !strings.HasPrefix(op, "delete/") { 289 t.Fatalf("unexpected op: %s", op) 290 } 291 op = strings.TrimPrefix(op, "delete/") 292 entryBytes, err := base64.URLEncoding.DecodeString(op) 293 if err != nil { 294 t.Fatal(err) 295 } 296 if !strings.HasPrefix(string(entryBytes), "bucket1:") { 297 t.Fatalf("unexpected op entry: %s", entryBytes) 298 } 299 objectName := strings.TrimPrefix(string(entryBytes), "bucket1:") 300 objectNamesDeleteCounts[objectName] += 1 301 responses = append(responses, batch_ops.OperationResponse{Code: 614}) 302 } 303 respBody, err := json.Marshal(&batch_ops.Response{ 304 OperationResponses: responses, 305 }) 306 if err != nil { 307 t.Fatal(err) 308 } 309 w.Header().Add("X-ReqId", "fakereqid") 310 w.Write(respBody) 311 }) 312 server := httptest.NewServer(mux) 313 defer server.Close() 314 315 objectsManager := NewObjectsManager(nil) 316 bucket1 := objectsManager.Bucket("bucket1") 317 318 operations := make([]*operation, len(objectNames)) 319 for i, objectName := range objectNames { 320 thisObjectName := objectName 321 operations[i] = &operation{Operation: bucket1.Object(thisObjectName).Delete().OnResponse(func() { 322 t.Fatalf("unexpected responsed") 323 }).OnError(func(err error) { 324 objectNamesResponsedCounts[thisObjectName] += 1 325 })} 326 } 327 328 operations, err := doOperations(context.Background(), operations, apis.NewStorage(&http_client.Options{ 329 Credentials: credentials.NewCredentials("testak", "testsk"), 330 Regions: ®ion.Region{Rs: region.Endpoints{Preferred: []string{server.URL}}}, 331 }), 10, 3) 332 if err != nil { 333 t.Fatal(err) 334 } else if len(operations) > 0 { 335 t.Fatalf("unexpected operations returned") 336 } 337 if len(objectNamesDeleteCounts) != 20 { 338 t.Fatalf("unexpected object names deleted count map") 339 } 340 for _, count := range objectNamesDeleteCounts { 341 if count != 1 { 342 t.Fatalf("unexpected objects deleted count") 343 } 344 } 345 if len(objectNamesResponsedCounts) != 20 { 346 t.Fatalf("unexpected object names responsed count map") 347 } 348 for _, count := range objectNamesResponsedCounts { 349 if count != 1 { 350 t.Fatalf("unexpected objects responsed count") 351 } 352 } 353 } 354 355 func TestRequestManagerGetOperations(t *testing.T) { 356 objectsManager := NewObjectsManager(nil) 357 bucket1 := objectsManager.Bucket("bucket1") 358 object1 := bucket1.Object("object1") 359 object2 := bucket1.Object("object2") 360 object3 := bucket1.Object("object3") 361 362 requestsManager, err := newRequestsManager(apis.NewStorage(nil), 4, 4, 4, 2, 1, 1*time.Minute, []Operation{ 363 object1.Stat(), 364 object1.Stat(), 365 object1.Stat(), 366 object1.Stat(), 367 object1.Stat(), 368 object2.Stat(), 369 object2.Stat(), 370 object2.Stat(), 371 }) 372 if err != nil { 373 t.Fatal(err) 374 } 375 376 if operations := requestsManager.takeOperations(); len(operations) != 3 { 377 t.Fatalf("unexpected got operations") 378 } 379 if operations := requestsManager.takeOperations(); len(operations) != 5 { 380 t.Fatalf("unexpected got operations") 381 } 382 383 requestsManager, err = newRequestsManager(apis.NewStorage(nil), 4, 4, 4, 2, 1, 1*time.Minute, []Operation{ 384 object1.Stat(), 385 object1.Stat(), 386 object1.Stat(), 387 object1.Stat(), 388 object1.Stat(), 389 object2.Stat(), 390 object2.Stat(), 391 object2.Stat(), 392 object3.Stat(), 393 }) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 if operations := requestsManager.takeOperations(); len(operations) != 4 { 399 t.Fatalf("unexpected got operations") 400 } 401 if operations := requestsManager.takeOperations(); len(operations) != 5 { 402 t.Fatalf("unexpected got operations") 403 } 404 } 405 406 func TestRequestManagerBatchSize(t *testing.T) { 407 objectsManager := NewObjectsManager(nil) 408 bucket1 := objectsManager.Bucket("bucket1") 409 operations := make([]Operation, 0, 10000) 410 for i := 0; i < 10000; i++ { 411 operations = append(operations, bucket1.Object(fmt.Sprintf("object_%04d", i)).Stat()) 412 } 413 414 const interval = 1 * time.Second 415 requestsManager, err := newRequestsManager(apis.NewStorage(nil), 256, 1, 1000, 2, 1, interval, operations) 416 if err != nil { 417 t.Fatal(err) 418 } 419 defer requestsManager.done() 420 time.Sleep(100 * time.Millisecond) 421 422 ticker := time.NewTicker(interval) 423 defer ticker.Stop() 424 if size := len(requestsManager.takeOperations()); size != 256 { 425 t.Fatalf("unexpected operations got, actual: %d, expected: %d", size, 256) 426 } 427 <-ticker.C 428 requestsManager.decreaseBatchSize() 429 ticker = time.NewTicker(interval) 430 if size := len(requestsManager.takeOperations()); size != 256 { 431 t.Fatalf("unexpected operations got, actual: %d, expected: %d", size, 256) 432 } 433 for i := 0; i < 3; i++ { 434 <-ticker.C 435 } 436 if size := len(requestsManager.takeOperations()); size != 1000 { 437 t.Fatalf("unexpected operations got, actual: %d, expected: %d", size, 1000) 438 } 439 for i := 0; i < 10; i++ { 440 requestsManager.decreaseBatchSize() 441 } 442 ticker = time.NewTicker(interval) 443 if size := len(requestsManager.takeOperations()); size != 1 { 444 t.Fatalf("unexpected operations got, actual: %d, expected: %d", size, 1) 445 } 446 for i := 0; i < 11; i++ { 447 <-ticker.C 448 } 449 if size := len(requestsManager.takeOperations()); size != 1000 { 450 t.Fatalf("unexpected operations got, actual: %d, expected: %d", size, 1000) 451 } 452 } 453 454 func TestWorkersManagerDoOperations573(t *testing.T) { 455 objectNames := make([]string, 100000) 456 for i := 0; i < 100000; i++ { 457 objectNames[i] = fmt.Sprintf("object%05d", i) 458 } 459 mux := http.NewServeMux() 460 mux.HandleFunc("/batch", func(w http.ResponseWriter, r *http.Request) { 461 if r.Method != http.MethodPost { 462 t.Fatalf("unexpected method") 463 } 464 if err := r.ParseForm(); err != nil { 465 t.Fatal(err) 466 } 467 w.Header().Add("X-ReqId", "fakereqid") 468 w.WriteHeader(573) 469 }) 470 server := httptest.NewServer(mux) 471 defer server.Close() 472 473 objectsManager := NewObjectsManager(&ObjectsManagerOptions{}) 474 bucket1 := objectsManager.Bucket("bucket1") 475 476 operations := make([]Operation, 100000) 477 for i := 0; i < 100000; i++ { 478 objectName := fmt.Sprintf("object_%05d", i) 479 operations[i] = bucket1.Object(objectName).Delete() 480 } 481 482 requestsManager, err := newRequestsManager(apis.NewStorage(&http_client.Options{ 483 Credentials: credentials.NewCredentials("ak", "sk"), 484 Regions: ®ion.Region{Rs: region.Endpoints{Preferred: []string{server.URL}}}, 485 }), 100, 100, 100, 2, 1, 1*time.Minute, operations) 486 if err != nil { 487 t.Fatal(err) 488 } 489 defer requestsManager.done() 490 491 workersManager := newWorkersManager(context.Background(), 10, 10, 10, 1*time.Minute, requestsManager) 492 workersManager.wait() 493 }