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:     &region.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:     &region.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:     &region.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:     &region.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  }