github.com/gophercloud/gophercloud@v1.11.0/internal/acceptance/openstack/objectstorage/v1/objects_test.go (about)

     1  //go:build acceptance
     2  // +build acceptance
     3  
     4  package v1
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/gophercloud/gophercloud/internal/acceptance/clients"
    15  	"github.com/gophercloud/gophercloud/internal/acceptance/tools"
    16  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
    17  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
    18  	th "github.com/gophercloud/gophercloud/testhelper"
    19  )
    20  
    21  // numObjects is the number of objects to create for testing.
    22  var numObjects = 2
    23  
    24  func TestObjects(t *testing.T) {
    25  	numObjects := numObjects + 1
    26  	client, err := clients.NewObjectStorageV1Client()
    27  	if err != nil {
    28  		t.Fatalf("Unable to create client: %v", err)
    29  	}
    30  
    31  	// Make a slice of length numObjects to hold the random object names.
    32  	oNames := make([]string, numObjects)
    33  	for i := 0; i < len(oNames)-1; i++ {
    34  		oNames[i] = tools.RandomString("test-object-", 8)
    35  	}
    36  	oNames[len(oNames)-1] = "test-object-with-/v1/-in-the-name"
    37  
    38  	// Create a container to hold the test objects.
    39  	cName := tools.RandomString("test-container-", 8)
    40  	opts := containers.CreateOpts{
    41  		TempURLKey: "super-secret",
    42  	}
    43  	header, err := containers.Create(client, cName, opts).Extract()
    44  	th.AssertNoErr(t, err)
    45  	t.Logf("Create object headers: %+v\n", header)
    46  
    47  	// Defer deletion of the container until after testing.
    48  	defer func() {
    49  		res := containers.Delete(client, cName)
    50  		th.AssertNoErr(t, res.Err)
    51  	}()
    52  
    53  	// Create a slice of buffers to hold the test object content.
    54  	oContents := make([]string, numObjects)
    55  	for i := 0; i < numObjects; i++ {
    56  		oContents[i] = tools.RandomString("", 10)
    57  		createOpts := objects.CreateOpts{
    58  			Content: strings.NewReader(oContents[i]),
    59  		}
    60  		res := objects.Create(client, cName, oNames[i], createOpts)
    61  		th.AssertNoErr(t, res.Err)
    62  	}
    63  	// Delete the objects after testing.
    64  	defer func() {
    65  		for i := 0; i < numObjects; i++ {
    66  			res := objects.Delete(client, cName, oNames[i], nil)
    67  			th.AssertNoErr(t, res.Err)
    68  		}
    69  	}()
    70  
    71  	// List all created objects
    72  	listOpts := objects.ListOpts{
    73  		Full:   true,
    74  		Prefix: "test-object-",
    75  	}
    76  
    77  	allPages, err := objects.List(client, cName, listOpts).AllPages()
    78  	if err != nil {
    79  		t.Fatalf("Unable to list objects: %v", err)
    80  	}
    81  
    82  	ons, err := objects.ExtractNames(allPages)
    83  	if err != nil {
    84  		t.Fatalf("Unable to extract objects: %v", err)
    85  	}
    86  	th.AssertEquals(t, len(ons), len(oNames))
    87  
    88  	ois, err := objects.ExtractInfo(allPages)
    89  	if err != nil {
    90  		t.Fatalf("Unable to extract object info: %v", err)
    91  	}
    92  	th.AssertEquals(t, len(ois), len(oNames))
    93  
    94  	// Create temporary URL, download its contents and compare with what was originally created.
    95  	// Downloading the URL validates it (this cannot be done in unit tests).
    96  	objURLs := make([]string, numObjects)
    97  	for i := 0; i < numObjects; i++ {
    98  		objURLs[i], err = objects.CreateTempURL(client, cName, oNames[i], objects.CreateTempURLOpts{
    99  			Method: http.MethodGet,
   100  			TTL:    180,
   101  		})
   102  		th.AssertNoErr(t, err)
   103  
   104  		resp, err := client.ProviderClient.HTTPClient.Get(objURLs[i])
   105  		th.AssertNoErr(t, err)
   106  		if resp.StatusCode != http.StatusOK {
   107  			resp.Body.Close()
   108  			th.AssertNoErr(t, fmt.Errorf("unexpected response code: %d", resp.StatusCode))
   109  		}
   110  
   111  		body, err := ioutil.ReadAll(resp.Body)
   112  		th.AssertNoErr(t, err)
   113  		th.AssertDeepEquals(t, oContents[i], string(body))
   114  		resp.Body.Close()
   115  
   116  		// custom Temp URL key with a sha256 digest and exact timestamp
   117  		objURLs[i], err = objects.CreateTempURL(client, cName, oNames[i], objects.CreateTempURLOpts{
   118  			Method:     http.MethodGet,
   119  			Timestamp:  time.Now().UTC().Add(180 * time.Second),
   120  			Digest:     "sha256",
   121  			TempURLKey: opts.TempURLKey,
   122  		})
   123  		th.AssertNoErr(t, err)
   124  
   125  		resp, err = client.ProviderClient.HTTPClient.Get(objURLs[i])
   126  		th.AssertNoErr(t, err)
   127  		if resp.StatusCode != http.StatusOK {
   128  			resp.Body.Close()
   129  			th.AssertNoErr(t, fmt.Errorf("unexpected response code: %d", resp.StatusCode))
   130  		}
   131  
   132  		body, err = ioutil.ReadAll(resp.Body)
   133  		th.AssertNoErr(t, err)
   134  		th.AssertDeepEquals(t, oContents[i], string(body))
   135  		resp.Body.Close()
   136  	}
   137  
   138  	// Copy the contents of one object to another.
   139  	copyOpts := objects.CopyOpts{
   140  		Destination: cName + "/" + oNames[1],
   141  	}
   142  	copyres := objects.Copy(client, cName, oNames[0], copyOpts)
   143  	th.AssertNoErr(t, copyres.Err)
   144  
   145  	// Download one of the objects that was created above.
   146  	downloadres := objects.Download(client, cName, oNames[0], nil)
   147  	th.AssertNoErr(t, downloadres.Err)
   148  
   149  	o1Content, err := downloadres.ExtractContent()
   150  	th.AssertNoErr(t, err)
   151  
   152  	// Download the another object that was create above.
   153  	downloadOpts := objects.DownloadOpts{
   154  		Newest: true,
   155  	}
   156  	downloadres = objects.Download(client, cName, oNames[1], downloadOpts)
   157  	th.AssertNoErr(t, downloadres.Err)
   158  	o2Content, err := downloadres.ExtractContent()
   159  	th.AssertNoErr(t, err)
   160  
   161  	// Compare the two object's contents to test that the copy worked.
   162  	th.AssertEquals(t, string(o2Content), string(o1Content))
   163  
   164  	// Update an object's metadata.
   165  	metadata := map[string]string{
   166  		"Gophercloud-Test": "objects",
   167  	}
   168  
   169  	disposition := "inline"
   170  	cType := "text/plain"
   171  	updateOpts := &objects.UpdateOpts{
   172  		Metadata:           metadata,
   173  		ContentDisposition: &disposition,
   174  		ContentType:        &cType,
   175  	}
   176  	updateres := objects.Update(client, cName, oNames[0], updateOpts)
   177  	th.AssertNoErr(t, updateres.Err)
   178  
   179  	// Delete the object's metadata after testing.
   180  	defer func() {
   181  		temp := make([]string, len(metadata))
   182  		i := 0
   183  		for k := range metadata {
   184  			temp[i] = k
   185  			i++
   186  		}
   187  		empty := ""
   188  		cType := "application/octet-stream"
   189  		iTrue := true
   190  		updateOpts = &objects.UpdateOpts{
   191  			RemoveMetadata:     temp,
   192  			ContentDisposition: &empty,
   193  			ContentType:        &cType,
   194  			DetectContentType:  &iTrue,
   195  		}
   196  		res := objects.Update(client, cName, oNames[0], updateOpts)
   197  		th.AssertNoErr(t, res.Err)
   198  
   199  		// Retrieve an object's metadata.
   200  		getOpts := objects.GetOpts{
   201  			Newest: true,
   202  		}
   203  		resp := objects.Get(client, cName, oNames[0], getOpts)
   204  		om, err := resp.ExtractMetadata()
   205  		th.AssertNoErr(t, err)
   206  		if len(om) > 0 {
   207  			t.Errorf("Expected custom metadata to be empty, found: %v", metadata)
   208  		}
   209  		object, err := resp.Extract()
   210  		th.AssertNoErr(t, err)
   211  		th.AssertEquals(t, empty, object.ContentDisposition)
   212  		th.AssertEquals(t, cType, object.ContentType)
   213  	}()
   214  
   215  	// Retrieve an object's metadata.
   216  	getOpts := objects.GetOpts{
   217  		Newest: true,
   218  	}
   219  	resp := objects.Get(client, cName, oNames[0], getOpts)
   220  	om, err := resp.ExtractMetadata()
   221  	th.AssertNoErr(t, err)
   222  	for k := range metadata {
   223  		if om[k] != metadata[strings.Title(k)] {
   224  			t.Errorf("Expected custom metadata with key: %s", k)
   225  			return
   226  		}
   227  	}
   228  
   229  	object, err := resp.Extract()
   230  	th.AssertNoErr(t, err)
   231  	th.AssertEquals(t, disposition, object.ContentDisposition)
   232  	th.AssertEquals(t, cType, object.ContentType)
   233  }
   234  
   235  func TestObjectsListSubdir(t *testing.T) {
   236  	client, err := clients.NewObjectStorageV1Client()
   237  	if err != nil {
   238  		t.Fatalf("Unable to create client: %v", err)
   239  	}
   240  
   241  	// Create a random subdirectory name.
   242  	cSubdir1 := tools.RandomString("test-subdir-", 8)
   243  	cSubdir2 := tools.RandomString("test-subdir-", 8)
   244  
   245  	// Make a slice of length numObjects to hold the random object names.
   246  	oNames1 := make([]string, numObjects)
   247  	for i := 0; i < len(oNames1); i++ {
   248  		oNames1[i] = cSubdir1 + "/" + tools.RandomString("test-object-", 8)
   249  	}
   250  
   251  	oNames2 := make([]string, numObjects)
   252  	for i := 0; i < len(oNames2); i++ {
   253  		oNames2[i] = cSubdir2 + "/" + tools.RandomString("test-object-", 8)
   254  	}
   255  
   256  	// Create a container to hold the test objects.
   257  	cName := tools.RandomString("test-container-", 8)
   258  	_, err = containers.Create(client, cName, nil).Extract()
   259  	th.AssertNoErr(t, err)
   260  
   261  	// Defer deletion of the container until after testing.
   262  	defer func() {
   263  		t.Logf("Deleting container %s", cName)
   264  		res := containers.Delete(client, cName)
   265  		th.AssertNoErr(t, res.Err)
   266  	}()
   267  
   268  	// Create a slice of buffers to hold the test object content.
   269  	oContents1 := make([]string, numObjects)
   270  	for i := 0; i < numObjects; i++ {
   271  		oContents1[i] = tools.RandomString("", 10)
   272  		createOpts := objects.CreateOpts{
   273  			Content: strings.NewReader(oContents1[i]),
   274  		}
   275  		res := objects.Create(client, cName, oNames1[i], createOpts)
   276  		th.AssertNoErr(t, res.Err)
   277  	}
   278  	// Delete the objects after testing.
   279  	defer func() {
   280  		for i := 0; i < numObjects; i++ {
   281  			t.Logf("Deleting object %s", oNames1[i])
   282  			res := objects.Delete(client, cName, oNames1[i], nil)
   283  			th.AssertNoErr(t, res.Err)
   284  		}
   285  	}()
   286  
   287  	oContents2 := make([]string, numObjects)
   288  	for i := 0; i < numObjects; i++ {
   289  		oContents2[i] = tools.RandomString("", 10)
   290  		createOpts := objects.CreateOpts{
   291  			Content: strings.NewReader(oContents2[i]),
   292  		}
   293  		res := objects.Create(client, cName, oNames2[i], createOpts)
   294  		th.AssertNoErr(t, res.Err)
   295  	}
   296  	// Delete the objects after testing.
   297  	defer func() {
   298  		for i := 0; i < numObjects; i++ {
   299  			t.Logf("Deleting object %s", oNames2[i])
   300  			res := objects.Delete(client, cName, oNames2[i], nil)
   301  			th.AssertNoErr(t, res.Err)
   302  		}
   303  	}()
   304  
   305  	listOpts := objects.ListOpts{
   306  		Full:      true,
   307  		Delimiter: "/",
   308  	}
   309  
   310  	allPages, err := objects.List(client, cName, listOpts).AllPages()
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  
   315  	allObjects, err := objects.ExtractNames(allPages)
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  
   320  	t.Logf("%#v\n", allObjects)
   321  	expected := []string{cSubdir1, cSubdir2}
   322  	for _, e := range expected {
   323  		var valid bool
   324  		for _, a := range allObjects {
   325  			if e+"/" == a {
   326  				valid = true
   327  			}
   328  		}
   329  		if !valid {
   330  			t.Fatalf("could not find %s in results", e)
   331  		}
   332  	}
   333  
   334  	listOpts = objects.ListOpts{
   335  		Full:      true,
   336  		Delimiter: "/",
   337  		Prefix:    cSubdir2,
   338  	}
   339  
   340  	allPages, err = objects.List(client, cName, listOpts).AllPages()
   341  	if err != nil {
   342  		t.Fatal(err)
   343  	}
   344  
   345  	allObjects, err = objects.ExtractNames(allPages)
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  
   350  	th.AssertEquals(t, allObjects[0], cSubdir2+"/")
   351  	t.Logf("%#v\n", allObjects)
   352  }
   353  
   354  func TestObjectsBulkDelete(t *testing.T) {
   355  	client, err := clients.NewObjectStorageV1Client()
   356  	if err != nil {
   357  		t.Fatalf("Unable to create client: %v", err)
   358  	}
   359  
   360  	// Create a random subdirectory name.
   361  	cSubdir1 := tools.RandomString("test-subdir-", 8)
   362  	cSubdir2 := tools.RandomString("test-subdir-", 8)
   363  
   364  	// Make a slice of length numObjects to hold the random object names.
   365  	oNames1 := make([]string, numObjects)
   366  	for i := 0; i < len(oNames1); i++ {
   367  		oNames1[i] = cSubdir1 + "/" + tools.RandomString("test-object-", 8)
   368  	}
   369  
   370  	oNames2 := make([]string, numObjects)
   371  	for i := 0; i < len(oNames2); i++ {
   372  		oNames2[i] = cSubdir2 + "/" + tools.RandomString("test-object-", 8)
   373  	}
   374  
   375  	// Create a container to hold the test objects.
   376  	cName := tools.RandomString("test-container-", 8)
   377  	_, err = containers.Create(client, cName, nil).Extract()
   378  	th.AssertNoErr(t, err)
   379  
   380  	// Defer deletion of the container until after testing.
   381  	defer func() {
   382  		t.Logf("Deleting container %s", cName)
   383  		res := containers.Delete(client, cName)
   384  		th.AssertNoErr(t, res.Err)
   385  	}()
   386  
   387  	// Create a slice of buffers to hold the test object content.
   388  	oContents1 := make([]string, numObjects)
   389  	for i := 0; i < numObjects; i++ {
   390  		oContents1[i] = tools.RandomString("", 10)
   391  		createOpts := objects.CreateOpts{
   392  			Content: strings.NewReader(oContents1[i]),
   393  		}
   394  		res := objects.Create(client, cName, oNames1[i], createOpts)
   395  		th.AssertNoErr(t, res.Err)
   396  	}
   397  
   398  	oContents2 := make([]string, numObjects)
   399  	for i := 0; i < numObjects; i++ {
   400  		oContents2[i] = tools.RandomString("", 10)
   401  		createOpts := objects.CreateOpts{
   402  			Content: strings.NewReader(oContents2[i]),
   403  		}
   404  		res := objects.Create(client, cName, oNames2[i], createOpts)
   405  		th.AssertNoErr(t, res.Err)
   406  	}
   407  
   408  	// Delete the objects after testing.
   409  	expectedResp := objects.BulkDeleteResponse{
   410  		ResponseStatus: "200 OK",
   411  		Errors:         [][]string{},
   412  		NumberDeleted:  numObjects * 2,
   413  	}
   414  
   415  	resp, err := objects.BulkDelete(client, cName, append(oNames1, oNames2...)).Extract()
   416  	th.AssertNoErr(t, err)
   417  	th.AssertDeepEquals(t, *resp, expectedResp)
   418  
   419  	// Verify deletion
   420  	listOpts := objects.ListOpts{
   421  		Full:      true,
   422  		Delimiter: "/",
   423  	}
   424  
   425  	allPages, err := objects.List(client, cName, listOpts).AllPages()
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  
   430  	allObjects, err := objects.ExtractNames(allPages)
   431  	if err != nil {
   432  		t.Fatal(err)
   433  	}
   434  
   435  	th.AssertEquals(t, len(allObjects), 0)
   436  }