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 }