1 package storage
2
3
4
5
6 import (
7 "bytes"
8 "encoding/xml"
9 "fmt"
10 "io/ioutil"
11 "net/http"
12 "strconv"
13 "strings"
14
15 chk "gopkg.in/check.v1"
16 )
17
18 type StorageBlobSuite struct{}
19
20 var _ = chk.Suite(&StorageBlobSuite{})
21
22 func getBlobClient(c *chk.C) BlobStorageClient {
23 return getBasicClient(c).GetBlobService()
24 }
25
26 func (s *StorageBlobSuite) Test_buildPath(c *chk.C) {
27 cli := getBlobClient(c)
28 cnt := cli.GetContainerReference("lol")
29 b := cnt.GetBlobReference("rofl")
30 c.Assert(b.buildPath(), chk.Equals, "/lol/rofl")
31 }
32
33 func (s *StorageBlobSuite) Test_pathForResource(c *chk.C) {
34 c.Assert(pathForResource("lol", ""), chk.Equals, "/lol")
35 c.Assert(pathForResource("lol", "blob"), chk.Equals, "/lol/blob")
36 }
37
38 func (s *StorageBlobSuite) TestBlobExists(c *chk.C) {
39 cli := getBlobClient(c)
40 rec := cli.client.appendRecorder(c)
41 defer rec.Stop()
42
43 cnt := cli.GetContainerReference(containerName(c))
44 c.Assert(cnt.Create(nil), chk.IsNil)
45 b := cnt.GetBlobReference(blobName(c))
46 defer cnt.Delete(nil)
47
48 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
49 defer b.Delete(nil)
50
51 ok, err := b.Exists()
52 c.Assert(err, chk.IsNil)
53 c.Assert(ok, chk.Equals, true)
54 b.Name += ".lol"
55 ok, err = b.Exists()
56 c.Assert(err, chk.IsNil)
57 c.Assert(ok, chk.Equals, false)
58
59 }
60
61 func (s *StorageBlobSuite) TestGetBlobURL(c *chk.C) {
62 cli, err := NewBasicClient(dummyStorageAccount, dummyMiniStorageKey)
63 c.Assert(err, chk.IsNil)
64 blobCli := cli.GetBlobService()
65
66 cnt := blobCli.GetContainerReference("c")
67 b := cnt.GetBlobReference("nested/blob")
68 c.Assert(b.GetURL(), chk.Equals, "https://golangrocksonazure.blob.core.windows.net/c/nested/blob")
69
70 cnt.Name = ""
71 c.Assert(b.GetURL(), chk.Equals, "https://golangrocksonazure.blob.core.windows.net/$root/nested/blob")
72
73 b.Name = "blob"
74 c.Assert(b.GetURL(), chk.Equals, "https://golangrocksonazure.blob.core.windows.net/$root/blob")
75
76 }
77
78 func (s *StorageBlobSuite) TestGetBlobContainerURL(c *chk.C) {
79 cli, err := NewBasicClient(dummyStorageAccount, dummyMiniStorageKey)
80 c.Assert(err, chk.IsNil)
81 blobCli := cli.GetBlobService()
82
83 cnt := blobCli.GetContainerReference("c")
84 b := cnt.GetBlobReference("")
85 c.Assert(b.GetURL(), chk.Equals, "https://golangrocksonazure.blob.core.windows.net/c")
86
87 cnt.Name = ""
88 c.Assert(b.GetURL(), chk.Equals, "https://golangrocksonazure.blob.core.windows.net/$root")
89 }
90
91 func (s *StorageBlobSuite) TestDeleteBlobIfExists(c *chk.C) {
92 cli := getBlobClient(c)
93 rec := cli.client.appendRecorder(c)
94 defer rec.Stop()
95
96 cnt := cli.GetContainerReference(containerName(c))
97 b := cnt.GetBlobReference(blobName(c))
98 c.Assert(cnt.Create(nil), chk.IsNil)
99 defer cnt.Delete(nil)
100
101 c.Assert(b.Delete(nil), chk.NotNil)
102
103 ok, err := b.DeleteIfExists(nil)
104 c.Assert(err, chk.IsNil)
105 c.Assert(ok, chk.Equals, false)
106 }
107
108 func (s *StorageBlobSuite) TestDeleteBlobWithConditions(c *chk.C) {
109 cli := getBlobClient(c)
110 rec := cli.client.appendRecorder(c)
111 defer rec.Stop()
112
113 cnt := cli.GetContainerReference(containerName(c))
114 b := cnt.GetBlobReference(blobName(c))
115 c.Assert(cnt.Create(nil), chk.IsNil)
116 defer cnt.Delete(nil)
117
118 c.Assert(b.CreateBlockBlob(nil), chk.IsNil)
119 err := b.GetProperties(nil)
120 c.Assert(err, chk.IsNil)
121 etag := b.Properties.Etag
122
123
124 options := DeleteBlobOptions{
125 IfMatch: "GolangRocksOnAzure",
126 }
127 err = b.Delete(&options)
128 c.Assert(err, chk.FitsTypeOf, AzureStorageServiceError{})
129 c.Assert(err.(AzureStorageServiceError).StatusCode, chk.Equals, http.StatusPreconditionFailed)
130 ok, err := b.Exists()
131 c.Assert(err, chk.IsNil)
132 c.Assert(ok, chk.Equals, true)
133
134
135 options.IfMatch = etag
136 ok, err = b.DeleteIfExists(&options)
137 c.Assert(err, chk.IsNil)
138 c.Assert(ok, chk.Equals, true)
139 }
140
141 func (s *StorageBlobSuite) TestGetBlobProperties(c *chk.C) {
142 cli := getBlobClient(c)
143 rec := cli.client.appendRecorder(c)
144 defer rec.Stop()
145
146 cnt := cli.GetContainerReference(containerName(c))
147 c.Assert(cnt.Create(nil), chk.IsNil)
148 defer cnt.Delete(nil)
149
150
151 blob1 := cnt.GetBlobReference(blobName(c, "1"))
152 err := blob1.GetProperties(nil)
153 c.Assert(err, chk.NotNil)
154
155
156 blob2 := cnt.GetBlobReference(blobName(c, "2"))
157 contents := content(64)
158 c.Assert(blob2.putSingleBlockBlob(contents), chk.IsNil)
159
160
161 err = blob2.GetProperties(nil)
162 c.Assert(err, chk.IsNil)
163
164 c.Assert(blob2.Properties.ContentLength, chk.Equals, int64(len(contents)))
165 c.Assert(blob2.Properties.ContentType, chk.Equals, "application/octet-stream")
166 c.Assert(blob2.Properties.BlobType, chk.Equals, BlobTypeBlock)
167 }
168
169
170
171 func (s *StorageBlobSuite) TestMarshalBlobMetadata(c *chk.C) {
172 buf, err := xml.Marshal(Blob{
173 Name: blobName(c),
174 Properties: BlobProperties{},
175 Metadata: map[string]string{
176 "lol": "baz < waz",
177 },
178 })
179 c.Assert(err, chk.IsNil)
180 c.Assert(string(buf), chk.Matches, `.*<Metadata><Lol>baz < waz</Lol></Metadata>.*`)
181 }
182
183 func (s *StorageBlobSuite) TestGetAndSetBlobMetadata(c *chk.C) {
184 cli := getBlobClient(c)
185 rec := cli.client.appendRecorder(c)
186 defer rec.Stop()
187
188 cnt := cli.GetContainerReference(containerName(c))
189 c.Assert(cnt.Create(nil), chk.IsNil)
190 defer cnt.Delete(nil)
191
192
193 blob1 := cnt.GetBlobReference(blobName(c, "1"))
194 c.Assert(blob1.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
195
196 err := blob1.GetMetadata(nil)
197 c.Assert(err, chk.IsNil)
198 c.Assert(blob1.Metadata, chk.HasLen, 0)
199
200
201 blob2 := cnt.GetBlobReference(blobName(c, "2"))
202 c.Assert(blob2.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
203 metaPut := BlobMetadata{
204 "lol": "rofl",
205 "rofl_baz": "waz qux",
206 }
207 blob2.Metadata = metaPut
208
209 err = blob2.SetMetadata(nil)
210 c.Assert(err, chk.IsNil)
211
212 err = blob2.GetMetadata(nil)
213 c.Assert(err, chk.IsNil)
214 c.Check(blob2.Metadata, chk.DeepEquals, metaPut)
215 }
216
217 func (s *StorageBlobSuite) TestMetadataCaseMunging(c *chk.C) {
218 cli := getBlobClient(c)
219 rec := cli.client.appendRecorder(c)
220 defer rec.Stop()
221
222 cnt := cli.GetContainerReference(containerName(c))
223 c.Assert(cnt.Create(nil), chk.IsNil)
224 defer cnt.Delete(nil)
225
226 b := cnt.GetBlobReference(blobName(c))
227 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
228
229
230 metaPutUpper := BlobMetadata{
231 "Lol": "different rofl",
232 "rofl_BAZ": "different waz qux",
233 }
234 metaExpectLower := BlobMetadata{
235 "lol": "different rofl",
236 "rofl_baz": "different waz qux",
237 }
238
239 b.Metadata = metaPutUpper
240 err := b.SetMetadata(nil)
241 c.Assert(err, chk.IsNil)
242
243 err = b.GetMetadata(nil)
244 c.Assert(err, chk.IsNil)
245 c.Check(b.Metadata, chk.DeepEquals, metaExpectLower)
246 }
247
248 func (s *StorageBlobSuite) TestSetMetadataWithExtraHeaders(c *chk.C) {
249 cli := getBlobClient(c)
250 rec := cli.client.appendRecorder(c)
251 defer rec.Stop()
252
253 cnt := cli.GetContainerReference(containerName(c))
254 b := cnt.GetBlobReference(blobName(c))
255 c.Assert(cnt.Create(nil), chk.IsNil)
256 defer cnt.Delete(nil)
257
258 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
259
260 meta := BlobMetadata{
261 "lol": "rofl",
262 "rofl_baz": "waz qux",
263 }
264 b.Metadata = meta
265
266 options := SetBlobMetadataOptions{
267 IfMatch: "incorrect-etag",
268 }
269
270
271 err := b.SetMetadata(&options)
272 c.Assert(err, chk.NotNil)
273
274 err = b.GetProperties(nil)
275 c.Assert(err, chk.IsNil)
276
277
278 options.IfMatch = b.Properties.Etag
279 b.Metadata = meta
280 err = b.SetMetadata(&options)
281 c.Assert(err, chk.IsNil)
282 }
283
284 func (s *StorageBlobSuite) TestSetBlobProperties(c *chk.C) {
285 cli := getBlobClient(c)
286 rec := cli.client.appendRecorder(c)
287 defer rec.Stop()
288
289 cnt := cli.GetContainerReference(containerName(c))
290 b := cnt.GetBlobReference(blobName(c))
291 c.Assert(cnt.Create(nil), chk.IsNil)
292 defer cnt.Delete(nil)
293
294 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
295
296 input := BlobProperties{
297 CacheControl: "private, max-age=0, no-cache",
298 ContentMD5: "oBATU+oaDduHWbVZLuzIJw==",
299 ContentType: "application/json",
300 ContentEncoding: "gzip",
301 ContentLanguage: "de-DE",
302 }
303 b.Properties = input
304
305 err := b.SetProperties(nil)
306 c.Assert(err, chk.IsNil)
307
308 err = b.GetProperties(nil)
309 c.Assert(err, chk.IsNil)
310
311 c.Check(b.Properties.CacheControl, chk.Equals, input.CacheControl)
312 c.Check(b.Properties.ContentType, chk.Equals, input.ContentType)
313 c.Check(b.Properties.ContentMD5, chk.Equals, input.ContentMD5)
314 c.Check(b.Properties.ContentEncoding, chk.Equals, input.ContentEncoding)
315 c.Check(b.Properties.ContentLanguage, chk.Equals, input.ContentLanguage)
316 }
317
318 func (s *StorageBlobSuite) TestSetPageBlobProperties(c *chk.C) {
319 cli := getBlobClient(c)
320 rec := cli.client.appendRecorder(c)
321 defer rec.Stop()
322
323 cnt := cli.GetContainerReference(containerName(c))
324 b := cnt.GetBlobReference(blobName(c))
325 c.Assert(cnt.Create(nil), chk.IsNil)
326 defer cnt.Delete(nil)
327
328 size := int64(1024)
329 b.Properties.ContentLength = size
330 c.Assert(b.PutPageBlob(nil), chk.IsNil)
331
332 b.Properties.ContentLength = int64(512)
333 options := SetBlobPropertiesOptions{Timeout: 30}
334 err := b.SetProperties(&options)
335 c.Assert(err, chk.IsNil)
336 }
337
338 func (s *StorageBlobSuite) TestSnapshotBlob(c *chk.C) {
339 cli := getBlobClient(c)
340 rec := cli.client.appendRecorder(c)
341 defer rec.Stop()
342
343 cnt := cli.GetContainerReference(containerName(c))
344 b := cnt.GetBlobReference(blobName(c))
345 c.Assert(cnt.Create(nil), chk.IsNil)
346 defer cnt.Delete(nil)
347
348 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
349
350 snapshotTime, err := b.CreateSnapshot(nil)
351 c.Assert(err, chk.IsNil)
352 c.Assert(snapshotTime, chk.NotNil)
353 }
354
355 func (s *StorageBlobSuite) TestSnapshotBlobWithTimeout(c *chk.C) {
356 cli := getBlobClient(c)
357 rec := cli.client.appendRecorder(c)
358 defer rec.Stop()
359
360 cnt := cli.GetContainerReference(containerName(c))
361 b := cnt.GetBlobReference(blobName(c))
362 c.Assert(cnt.Create(nil), chk.IsNil)
363 defer cnt.Delete(nil)
364
365 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
366
367 options := SnapshotOptions{
368 Timeout: 0,
369 }
370 snapshotTime, err := b.CreateSnapshot(&options)
371 c.Assert(err, chk.IsNil)
372 c.Assert(snapshotTime, chk.NotNil)
373 }
374
375 func (s *StorageBlobSuite) TestSnapshotBlobWithValidLease(c *chk.C) {
376 cli := getBlobClient(c)
377 rec := cli.client.appendRecorder(c)
378 defer rec.Stop()
379
380 cnt := cli.GetContainerReference(containerName(c))
381 b := cnt.GetBlobReference(blobName(c))
382 c.Assert(cnt.Create(nil), chk.IsNil)
383 defer cnt.Delete(nil)
384
385 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
386
387
388 currentLeaseID, err := b.AcquireLease(30, "", nil)
389 c.Assert(err, chk.IsNil)
390
391 options := SnapshotOptions{
392 LeaseID: currentLeaseID,
393 }
394 snapshotTime, err := b.CreateSnapshot(&options)
395 c.Assert(err, chk.IsNil)
396 c.Assert(snapshotTime, chk.NotNil)
397 }
398
399 func (s *StorageBlobSuite) TestSnapshotBlobWithInvalidLease(c *chk.C) {
400 cli := getBlobClient(c)
401 rec := cli.client.appendRecorder(c)
402 defer rec.Stop()
403
404 cnt := cli.GetContainerReference(containerName(c))
405 b := cnt.GetBlobReference(blobName(c))
406 c.Assert(cnt.Create(nil), chk.IsNil)
407 defer cnt.Delete(nil)
408
409 c.Assert(b.putSingleBlockBlob([]byte("Hello!")), chk.IsNil)
410
411
412 leaseID, err := b.AcquireLease(30, "", nil)
413 c.Assert(err, chk.IsNil)
414 c.Assert(leaseID, chk.Not(chk.Equals), "")
415
416 options := SnapshotOptions{
417 LeaseID: "GolangRocksOnAzure",
418 }
419 snapshotTime, err := b.CreateSnapshot(&options)
420 c.Assert(err, chk.NotNil)
421 c.Assert(snapshotTime, chk.IsNil)
422 }
423
424 func (s *StorageBlobSuite) TestGetBlobRange(c *chk.C) {
425 cli := getBlobClient(c)
426 rec := cli.client.appendRecorder(c)
427 defer rec.Stop()
428
429 cnt := cli.GetContainerReference(containerName(c))
430 b := cnt.GetBlobReference(blobName(c))
431 c.Assert(cnt.Create(nil), chk.IsNil)
432 defer cnt.Delete(nil)
433
434 body := "0123456789"
435 c.Assert(b.putSingleBlockBlob([]byte(body)), chk.IsNil)
436 defer b.Delete(nil)
437
438 cases := []struct {
439 options GetBlobRangeOptions
440 expected string
441 }{
442 {
443 options: GetBlobRangeOptions{
444 Range: &BlobRange{
445 Start: 0,
446 End: uint64(len(body)),
447 },
448 },
449 expected: body,
450 },
451 {
452 options: GetBlobRangeOptions{
453 Range: &BlobRange{
454 Start: 0,
455 End: 0,
456 },
457 },
458 expected: body,
459 },
460 {
461 options: GetBlobRangeOptions{
462 Range: &BlobRange{
463 Start: 1,
464 End: 3,
465 },
466 },
467 expected: body[1 : 3+1],
468 },
469 {
470 options: GetBlobRangeOptions{
471 Range: &BlobRange{
472 Start: 3,
473 End: uint64(len(body)),
474 },
475 },
476 expected: body[3:],
477 },
478 {
479 options: GetBlobRangeOptions{
480 Range: &BlobRange{
481 Start: 3,
482 End: 0,
483 },
484 },
485 expected: body[3:],
486 },
487 }
488
489 err := b.GetProperties(nil)
490 c.Assert(err, chk.IsNil)
491
492
493 for _, r := range cases {
494 resp, err := b.GetRange(&(r.options))
495 c.Assert(err, chk.IsNil)
496 blobBody, err := ioutil.ReadAll(resp)
497 c.Assert(err, chk.IsNil)
498
499 str := string(blobBody)
500 c.Assert(str, chk.Equals, r.expected)
501
502
503 c.Assert(b.Properties.ContentLength, chk.Equals, int64(len(body)))
504 }
505 }
506
507 func (b *Blob) putSingleBlockBlob(chunk []byte) error {
508 if len(chunk) > MaxBlobBlockSize {
509 return fmt.Errorf("storage: provided chunk (%d bytes) cannot fit into single-block blob (max %d bytes)", len(chunk), MaxBlobBlockSize)
510 }
511
512 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), nil)
513 headers := b.Container.bsc.client.getStandardHeaders()
514 b.Properties.BlobType = BlobTypeBlock
515 headers["x-ms-blob-type"] = string(BlobTypeBlock)
516 headers["Content-Length"] = strconv.Itoa(len(chunk))
517
518 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, bytes.NewReader(chunk), b.Container.bsc.auth)
519 if err != nil {
520 return err
521 }
522 return checkRespCode(resp, []int{http.StatusCreated})
523 }
524
525 func blobName(c *chk.C, extras ...string) string {
526 return nameGenerator(1024, "blob/", alphanum, c, extras)
527
528 }
529
530 func contentWithSpecialChars(n int) string {
531 name := string(content(n)) + "/" + string(content(n)) + "-._~:?#[]@!$&'()*,;+= " + string(content(n))
532 return name
533 }
534
535 func nameGenerator(maxLen int, prefix, valid string, c *chk.C, extras []string) string {
536 extra := strings.Join(extras, "")
537 name := prefix + extra + removeInvalidCharacters(c.TestName(), valid)
538 if len(name) > maxLen {
539 return name[:maxLen]
540 }
541 return name
542 }
543
544 func removeInvalidCharacters(unformatted string, valid string) string {
545 unformatted = strings.ToLower(unformatted)
546 buffer := bytes.NewBufferString(strconv.Itoa((len(unformatted))))
547 runes := []rune(unformatted)
548 for _, r := range runes {
549 if strings.ContainsRune(valid, r) {
550 buffer.WriteRune(r)
551 }
552 }
553 return string(buffer.Bytes())
554 }
555
556 func content(n int) []byte {
557 buffer := bytes.NewBufferString("")
558 rep := (n / len(veryLongString)) + 1
559 for i := 0; i < rep; i++ {
560 buffer.WriteString(veryLongString)
561 }
562 return buffer.Bytes()[:n]
563 }
564
565 const (
566 alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
567 alpha = "abcdefghijklmnopqrstuvwxyz"
568 veryLongString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer feugiat eleifend scelerisque. Phasellus tempor turpis eget magna pretium, et finibus massa convallis. Donec eget lacinia nibh. Ut ut cursus odio. Quisque id justo interdum, maximus ex a, dapibus leo. Nullam mattis arcu nec justo vehicula pretium. Curabitur fermentum quam ac dolor venenatis, vitae scelerisque ex posuere. Donec ut ante porttitor, ultricies ante ac, pulvinar metus. Nunc suscipit elit gravida dolor facilisis sollicitudin. Fusce ac ultrices libero. Donec erat lectus, hendrerit volutpat nisl quis, porta accumsan nibh. Pellentesque hendrerit nisi id mi porttitor maximus. Phasellus vitae venenatis velit. Quisque id felis nec lacus iaculis porttitor. Maecenas egestas tortor et nulla dapibus varius. In hac habitasse platea dictumst."
569 )
570
View as plain text