Merge lp:~allenap/gwacl/list-files-by-prefix into lp:gwacl

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: 112
Merged at revision: 106
Proposed branch: lp:~allenap/gwacl/list-files-by-prefix
Merge into: lp:gwacl
Diff against target: 467 lines (+250/-94)
5 files modified
example/storage/run.go (+5/-1)
storage.go (+35/-0)
storage_base.go (+17/-41)
storage_base_test.go (+52/-52)
storage_test.go (+141/-0)
To merge this branch: bzr merge lp:~allenap/gwacl/list-files-by-prefix
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+168479@code.launchpad.net

Commit message

List blobs matching a prefix.

Description of the change

This branch's mission - and it's chosen to accept it - is to add
prefix support to the List Blobs API call via GWACL, where `prefix` is
defined as:

  Optional. Filters the results to return only blobs whose names begin
  with the specified prefix.

As far as I am aware, it's not possible to have "proper" optional
arguments with Go, so I've created the ListBlobsRequest struct to
provide this behaviour. It'll also make it easy to add support for
maxresults, include, timeout, etc. down the road.

However, first I had some refactoring to do: the List Blobs API call
was implemented in private getListBlobsBatch function, and ListBlobs
was a wrapper that was repeatedly calling getListBlobsBatch to get all
blobs. I think the layering is good, but masking the foundational API
call is limiting, and premature.

To address this I've:

- moved ListBlobs from storage_base.go to storage.go and renamed it
  ListAllBlobs.

- getListBlobsBatch is now ListBlobs.

- Both functions take the new ListBlobsRequest struct as their only
  argument.

A similar problem exists in ListContainers and getListContainersBatch,
but that can be addressed in a separate branch if the approach taken
here is acceptable.

I've also updated the storage example so that a prefix can be passed
in, and verified that it works.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

(You gotta wonder why Go doesn't have default args :/)

The first thing that popped into my head was, do you think that we should put mandatory parameters as separate args to the ListAllBlobs function, and reserve ListBlobsRequest for optional params only? It seems as though you don't check for the mandatory params anyway, but it would obviate that param checking and is more explicit.

Second thing: the tests nearly all have the same set-up code for the fake transport. I think we need a factored test helper for this as all the setup detracts from the test code's intention. I don't think it should be done in this branch though, a follow-up one is better.

Also - I am wondering if we need some factory methods to create all these huge blobs of XML we're passing around? It's not entirely clear what we need though.

Everything else looks pretty good to me, thanks for all the extra test cases!

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

> (You gotta wonder why Go doesn't have default args :/)
>
> The first thing that popped into my head was, do you think that we should put
> mandatory parameters as separate args to the ListAllBlobs function, and
> reserve ListBlobsRequest for optional params only?  It seems as though you
> don't check for the mandatory params anyway, but it would obviate that param
> checking and is more explicit.

Now that we have a struct to represent the request, I think it should
be the place that it's represented. We could add a Validate() method
to the struct to check always-required parameters or something, but
I'm more inclined to just throw at Azure whatever is passed in, and
let Azure do the checking. Let's make the fewest assumptions about
Azure possible. It's less work too.

>
> Second thing: the tests nearly all have the same set-up code for the fake
> transport.  I think we need a factored test helper for this as all the setup
> detracts from the test code's intention.  I don't think it should be done in
> this branch though, a follow-up one is better.

Yeah, it's ugly. We can extract some of that stuff, but it may come
with a loss of clarity. I've seen enough of this code for now. Given
time I may come back to it and see a good way to clean this up; the
same goes for any of us.

>
> Also - I am wondering if we need some factory methods to create all these huge
> blobs of XML we're passing around? It's not entirely clear what we need
> though.

Yeah :-/ Same goes as for the set-up.

>
> Everything else looks pretty good to me, thanks for all the extra test cases!

Thanks for the review!

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 11 Jun 2013 13:30:38 you wrote:
> > (You gotta wonder why Go doesn't have default args :/)
> >
> > The first thing that popped into my head was, do you think that we should
> > put mandatory parameters as separate args to the ListAllBlobs function,
> > and reserve ListBlobsRequest for optional params only? It seems as
> > though you don't check for the mandatory params anyway, but it would
> > obviate that param checking and is more explicit.
>
> Now that we have a struct to represent the request, I think it should
> be the place that it's represented. We could add a Validate() method
> to the struct to check always-required parameters or something, but
> I'm more inclined to just throw at Azure whatever is passed in, and
> let Azure do the checking. Let's make the fewest assumptions about
> Azure possible. It's less work too.

My only worry with that is that it break the "fail as early as possible"
paradigm. The later you fail, the harder it is to work out the source of the
problem. Especially so with Go.

Please seriously consider adding some validation. It's tied to the API
version used anyway so it's not going to suddenly break at the other end.

>
> > Second thing: the tests nearly all have the same set-up code for the fake
> > transport. I think we need a factored test helper for this as all the
> > setup detracts from the test code's intention. I don't think it should
> > be done in this branch though, a follow-up one is better.
>
> Yeah, it's ugly. We can extract some of that stuff, but it may come
> with a loss of clarity.

I don't think clarity can get much worse from what it is now :)

> I've seen enough of this code for now. Given
> time I may come back to it and see a good way to clean this up; the
> same goes for any of us.

Ok, please file a tech debt bug.

>
> > Also - I am wondering if we need some factory methods to create all these
> > huge blobs of XML we're passing around? It's not entirely clear what we
> > need though.
>
> Yeah :-/ Same goes as for the set-up.
>
> > Everything else looks pretty good to me, thanks for all the extra test
> > cases!
> Thanks for the review!

I'd like to say it was a pleasure looking at all this Go. I'd really like to.

:^)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'example/storage/run.go'
--- example/storage/run.go 2013-04-18 15:03:42 +0000
+++ example/storage/run.go 2013-06-10 15:57:40 +0000
@@ -21,6 +21,7 @@
21var key string21var key string
22var filename string22var filename string
23var container string23var container string
24var prefix string
2425
25func checkContainerAndFilename() error {26func checkContainerAndFilename() error {
26 if container == "" || filename == "" {27 if container == "" || filename == "" {
@@ -38,6 +39,7 @@
38 flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded)")39 flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded)")
39 flag.StringVar(&container, "container", "", "Name of the container to use")40 flag.StringVar(&container, "container", "", "Name of the container to use")
40 flag.StringVar(&filename, "file", "", "File containing blob to upload/download")41 flag.StringVar(&filename, "file", "", "File containing blob to upload/download")
42 flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs")
41 flag.Parse()43 flag.Parse()
4244
43 if account == "" || key == "" {45 if account == "" || key == "" {
@@ -102,7 +104,9 @@
102}104}
103105
104func list(storage gwacl.StorageContext) {106func list(storage gwacl.StorageContext) {
105 res, err := storage.ListBlobs(container)107 request := &gwacl.ListBlobsRequest{
108 Container: container, Prefix: prefix}
109 res, err := storage.ListAllBlobs(request)
106 if err != nil {110 if err != nil {
107 dumpError(err)111 dumpError(err)
108 return112 return
109113
=== modified file 'storage.go'
--- storage.go 2013-04-22 16:17:23 +0000
+++ storage.go 2013-06-10 15:57:40 +0000
@@ -11,6 +11,7 @@
11 "io"11 "io"
12 . "launchpad.net/gwacl/logging"12 . "launchpad.net/gwacl/logging"
13 "strconv"13 "strconv"
14 "strings"
14)15)
1516
16// UploadBlockBlob uses PutBlock and PutBlockList API operations to upload17// UploadBlockBlob uses PutBlock and PutBlockList API operations to upload
@@ -45,3 +46,37 @@
45 Debugf("Committing %d blocks.\n", len(blockList.Items))46 Debugf("Committing %d blocks.\n", len(blockList.Items))
46 return context.PutBlockList(container, filename, blockList)47 return context.PutBlockList(container, filename, blockList)
47}48}
49
50// ListAllBlobs requests from the API a list of blobs in a container.
51func (context *StorageContext) ListAllBlobs(request *ListBlobsRequest) (*BlobEnumerationResults, error) {
52 blobs := make([]Blob, 0)
53 var batch *BlobEnumerationResults
54
55 // Request the initial result, using the empty marker. Then, for as long
56 // as the result has a nonempty NextMarker, request the next batch using
57 // that marker.
58 // This loop is very similar to the one in ListContainers().
59 for marker, nextMarker := "", "x"; nextMarker != ""; marker = nextMarker {
60 var err error
61 // Don't use := here or you'll shadow variables from the function's
62 // outer scopes.
63 request.Marker = marker
64 batch, err = context.ListBlobs(request)
65 if err != nil {
66 return nil, err
67 }
68 // The response may contain a NextMarker field, to let us request a
69 // subsequent batch of results. The XML parser won't trim whitespace out
70 // of the marker tag, so we do that here.
71 nextMarker = strings.TrimSpace(batch.NextMarker)
72 blobs = append(blobs, batch.Blobs...)
73 }
74
75 // There's more in a BlobsEnumerationResults than just the blobs.
76 // Return the latest batch, but give it the full cumulative blobs list
77 // instead of just the last batch.
78 // To the caller, this will look like they made one call to Azure's
79 // List Blobs method, but batch size was unlimited.
80 batch.Blobs = blobs
81 return batch, nil
82}
4883
=== modified file 'storage_base.go'
--- storage_base.go 2013-05-01 05:16:08 +0000
+++ storage_base.go 2013-06-10 15:57:40 +0000
@@ -383,18 +383,27 @@
383 return batch, nil383 return batch, nil
384}384}
385385
386// getListBlobsBatch calls the "List Blobs" operation on the storage API, and386type ListBlobsRequest struct {
387// returns a single batch of results.387 Container string
388// The marker argument should be empty for a new List Blobs request. For388 Marker string
389// subsequent calls to get additional batches of the same result, pass the389 Prefix string
390}
391
392// ListBlobs calls the "List Blobs" operation on the storage API, and returns
393// a single batch of results.
394// The request.Marker argument should be empty for a new List Blobs request.
395// For subsequent calls to get additional batches of the same result, pass the
390// NextMarker from the previous call's result.396// NextMarker from the previous call's result.
391func (context *StorageContext) getListBlobsBatch(container, marker string) (*BlobEnumerationResults, error) {397func (context *StorageContext) ListBlobs(request *ListBlobsRequest) (*BlobEnumerationResults, error) {
392 uri := addURLQueryParams(398 uri := addURLQueryParams(
393 context.getContainerURL(container),399 context.getContainerURL(request.Container),
394 "restype", "container",400 "restype", "container",
395 "comp", "list")401 "comp", "list")
396 if marker != "" {402 if request.Marker != "" {
397 uri = addURLQueryParams(uri, "marker", marker)403 uri = addURLQueryParams(uri, "marker", request.Marker)
404 }
405 if request.Prefix != "" {
406 uri = addURLQueryParams(uri, "prefix", request.Prefix)
398 }407 }
399 blobs := BlobEnumerationResults{}408 blobs := BlobEnumerationResults{}
400 _, err := context.performRequest(requestParams{409 _, err := context.performRequest(requestParams{
@@ -410,39 +419,6 @@
410 return &blobs, err419 return &blobs, err
411}420}
412421
413// ListBlobs requests from the API a list of blobs in a container.
414func (context *StorageContext) ListBlobs(container string) (*BlobEnumerationResults, error) {
415 blobs := make([]Blob, 0)
416 var batch *BlobEnumerationResults
417
418 // Request the initial result, using the empty marker. Then, for as long
419 // as the result has a nonempty NextMarker, request the next batch using
420 // that marker.
421 // This loop is very similar to the one in ListContainers().
422 for marker, nextMarker := "", "x"; nextMarker != ""; marker = nextMarker {
423 var err error
424 // Don't use := here or you'll shadow variables from the function's
425 // outer scopes.
426 batch, err = context.getListBlobsBatch(container, marker)
427 if err != nil {
428 return nil, err
429 }
430 // The response may contain a NextMarker field, to let us request a
431 // subsequent batch of results. The XML parser won't trim whitespace out
432 // of the marker tag, so we do that here.
433 nextMarker = strings.TrimSpace(batch.NextMarker)
434 blobs = append(blobs, batch.Blobs...)
435 }
436
437 // There's more in a BlobsEnumerationResults than just the blobs.
438 // Return the latest batch, but give it the full cumulative blobs list
439 // instead of just the last batch.
440 // To the caller, this will look like they made one call to Azure's
441 // List Blobs method, but batch size was unlimited.
442 batch.Blobs = blobs
443 return batch, nil
444}
445
446// Send a request to the storage service to create a new container. If the422// Send a request to the storage service to create a new container. If the
447// request fails, error is non-nil.423// request fails, error is non-nil.
448func (context *StorageContext) CreateContainer(container string) error {424func (context *StorageContext) CreateContainer(container string) error {
449425
=== modified file 'storage_base_test.go'
--- storage_base_test.go 2013-04-30 14:48:05 +0000
+++ storage_base_test.go 2013-06-10 15:57:40 +0000
@@ -533,7 +533,8 @@
533 }533 }
534 transport := &TestTransport{Response: response}534 transport := &TestTransport{Response: response}
535 context.client = &http.Client{Transport: transport}535 context.client = &http.Client{Transport: transport}
536 results, err := context.ListBlobs("container")536 request := &ListBlobsRequest{Container: "container"}
537 results, err := context.ListBlobs(request)
537 c.Assert(err, IsNil)538 c.Assert(err, IsNil)
538 c.Check(transport.Request.URL.String(), Matches, context.getContainerURL("container")+"?.*")539 c.Check(transport.Request.URL.String(), Matches, context.getContainerURL("container")+"?.*")
539 c.Check(transport.Request.URL.Query(), DeepEquals, url.Values{540 c.Check(transport.Request.URL.Query(), DeepEquals, url.Values{
@@ -550,7 +551,8 @@
550 transport := &TestTransport{Error: error}551 transport := &TestTransport{Error: error}
551 context := makeStorageContext()552 context := makeStorageContext()
552 context.client = &http.Client{Transport: transport}553 context.client = &http.Client{Transport: transport}
553 _, err := context.ListBlobs("container")554 request := &ListBlobsRequest{Container: "container"}
555 _, err := context.ListBlobs(request)
554 c.Assert(err, NotNil)556 c.Assert(err, NotNil)
555}557}
556558
@@ -563,63 +565,21 @@
563 transport := &TestTransport{Response: response}565 transport := &TestTransport{Response: response}
564 context := makeStorageContext()566 context := makeStorageContext()
565 context.client = &http.Client{Transport: transport}567 context.client = &http.Client{Transport: transport}
566 _, err := context.ListBlobs("container")568 request := &ListBlobsRequest{Container: "container"}
569 _, err := context.ListBlobs(request)
567 c.Assert(err, NotNil)570 c.Assert(err, NotNil)
568}571}
569572
570// ListBlobs combines multiple batches of output.573func (suite *TestListBlobs) TestListBlobsPassesMarker(c *C) {
571func (suite *TestListBlobs) TestBatchedResult(c *C) {
572 firstBlob := "blob1"
573 lastBlob := "blob2"
574 marker := "moreplease"
575 firstBatch := http.Response{
576 StatusCode: http.StatusOK,
577 Body: makeResponseBody(fmt.Sprintf(`
578 <EnumerationResults>
579 <Blobs>
580 <Blob>
581 <Name>%s</Name>
582 </Blob>
583 </Blobs>
584 <NextMarker>%s</NextMarker>
585 </EnumerationResults>
586 `, firstBlob, marker)),
587 }
588 lastBatch := http.Response{
589 StatusCode: http.StatusOK,
590 Body: makeResponseBody(fmt.Sprintf(`
591 <EnumerationResults>
592 <Blobs>
593 <Blob>
594 <Name>%s</Name>
595 </Blob>
596 </Blobs>
597 </EnumerationResults>
598 `, lastBlob)),
599 }
600 transport := TestTransport2{}
601 transport.AddExchange(&firstBatch, nil)
602 transport.AddExchange(&lastBatch, nil)
603 context := makeStorageContext()
604 context.client = &http.Client{Transport: &transport}
605
606 blobs, err := context.ListBlobs("mycontainer")
607 c.Assert(err, IsNil)
608
609 c.Check(len(blobs.Blobs), Equals, 2)
610 c.Check(blobs.Blobs[0].Name, Equals, firstBlob)
611 c.Check(blobs.Blobs[1].Name, Equals, lastBlob)
612}
613
614func (suite *TestListBlobs) TestGetListBlobsBatchPassesMarker(c *C) {
615 transport := TestTransport2{}574 transport := TestTransport2{}
616 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)575 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
617 context := makeStorageContext()576 context := makeStorageContext()
618 context.client = &http.Client{Transport: &transport}577 context.client = &http.Client{Transport: &transport}
619578
620 // Call getListBlobsBatch. This will fail because of the empty579 // Call ListBlobs. This will fail because of the empty
621 // response, but no matter. We only care about the request.580 // response, but no matter. We only care about the request.
622 _, err := context.getListBlobsBatch("mycontainer", "thismarkerhere")581 request := &ListBlobsRequest{Container: "mycontainer", Marker: "thismarkerhere"}
582 _, err := context.ListBlobs(request)
623 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")583 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
624 c.Assert(transport.ExchangeCount, Equals, 1)584 c.Assert(transport.ExchangeCount, Equals, 1)
625585
@@ -629,14 +589,15 @@
629 c.Check(values["marker"], DeepEquals, []string{"thismarkerhere"})589 c.Check(values["marker"], DeepEquals, []string{"thismarkerhere"})
630}590}
631591
632func (suite *TestListBlobs) TestGetListBlobsBatchDoesNotPassEmptyMarker(c *C) {592func (suite *TestListBlobs) TestListBlobsDoesNotPassEmptyMarker(c *C) {
633 transport := TestTransport2{}593 transport := TestTransport2{}
634 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)594 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
635 context := makeStorageContext()595 context := makeStorageContext()
636 context.client = &http.Client{Transport: &transport}596 context.client = &http.Client{Transport: &transport}
637597
638 // The error is OK. We only care about the request.598 // The error is OK. We only care about the request.
639 _, err := context.getListBlobsBatch("mycontainer", "")599 request := &ListBlobsRequest{Container: "mycontainer"}
600 _, err := context.ListBlobs(request)
640 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")601 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
641 c.Assert(transport.ExchangeCount, Equals, 1)602 c.Assert(transport.ExchangeCount, Equals, 1)
642603
@@ -648,6 +609,45 @@
648 c.Check(marker, DeepEquals, []string(nil))609 c.Check(marker, DeepEquals, []string(nil))
649}610}
650611
612func (suite *TestListBlobs) TestListBlobsPassesPrefix(c *C) {
613 transport := TestTransport2{}
614 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
615 context := makeStorageContext()
616 context.client = &http.Client{Transport: &transport}
617
618 // Call ListBlobs. This will fail because of the empty
619 // response, but no matter. We only care about the request.
620 request := &ListBlobsRequest{Container: "mycontainer", Prefix: "thisprefixhere"}
621 _, err := context.ListBlobs(request)
622 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
623 c.Assert(transport.ExchangeCount, Equals, 1)
624
625 query := transport.Exchanges[0].Request.URL.RawQuery
626 values, err := url.ParseQuery(query)
627 c.Assert(err, IsNil)
628 c.Check(values["prefix"], DeepEquals, []string{"thisprefixhere"})
629}
630
631func (suite *TestListBlobs) TestListBlobsDoesNotPassEmptyPrefix(c *C) {
632 transport := TestTransport2{}
633 transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: Empty}, nil)
634 context := makeStorageContext()
635 context.client = &http.Client{Transport: &transport}
636
637 // The error is OK. We only care about the request.
638 request := &ListBlobsRequest{Container: "mycontainer"}
639 _, err := context.ListBlobs(request)
640 c.Assert(err, ErrorMatches, ".*Failed to deserialize data.*")
641 c.Assert(transport.ExchangeCount, Equals, 1)
642
643 query := transport.Exchanges[0].Request.URL.RawQuery
644 values, err := url.ParseQuery(query)
645 c.Assert(err, IsNil)
646 prefix, present := values["prefix"]
647 c.Check(present, Equals, false)
648 c.Check(prefix, DeepEquals, []string(nil))
649}
650
651type TestCreateContainer struct{}651type TestCreateContainer struct{}
652652
653var _ = Suite(&TestCreateContainer{})653var _ = Suite(&TestCreateContainer{})
654654
=== modified file 'storage_test.go'
--- storage_test.go 2013-04-29 11:52:04 +0000
+++ storage_test.go 2013-06-10 15:57:40 +0000
@@ -91,3 +91,144 @@
91 expected += "</BlockList>"91 expected += "</BlockList>"
92 c.Check(string(body), Equals, expected)92 c.Check(string(body), Equals, expected)
93}93}
94
95type testListAllBlobs struct{}
96
97var _ = Suite(&testListAllBlobs{})
98
99// The ListAllBlobs Storage API call returns a BlobEnumerationResults struct
100// on success.
101func (suite *testListAllBlobs) Test(c *C) {
102 response_body := `
103 <?xml version="1.0" encoding="utf-8"?>
104 <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer">
105 <Prefix>prefix</Prefix>
106 <Marker>marker</Marker>
107 <MaxResults>maxresults</MaxResults>
108 <Delimiter>delimiter</Delimiter>
109 <Blobs>
110 <Blob>
111 <Name>blob-name</Name>
112 <Snapshot>snapshot-date-time</Snapshot>
113 <Url>blob-address</Url>
114 <Properties>
115 <Last-Modified>last-modified</Last-Modified>
116 <Etag>etag</Etag>
117 <Content-Length>size-in-bytes</Content-Length>
118 <Content-Type>blob-content-type</Content-Type>
119 <Content-Encoding />
120 <Content-Language />
121 <Content-MD5 />
122 <Cache-Control />
123 <x-ms-blob-sequence-number>sequence-number</x-ms-blob-sequence-number>
124 <BlobType>blobtype</BlobType>
125 <LeaseStatus>leasestatus</LeaseStatus>
126 <LeaseState>leasestate</LeaseState>
127 <LeaseDuration>leasesduration</LeaseDuration>
128 <CopyId>id</CopyId>
129 <CopyStatus>copystatus</CopyStatus>
130 <CopySource>copysource</CopySource>
131 <CopyProgress>copyprogress</CopyProgress>
132 <CopyCompletionTime>copycompletiontime</CopyCompletionTime>
133 <CopyStatusDescription>copydesc</CopyStatusDescription>
134 </Properties>
135 <Metadata>
136 <MetaName1>metadataname1</MetaName1>
137 <MetaName2>metadataname2</MetaName2>
138 </Metadata>
139 </Blob>
140 <BlobPrefix>
141 <Name>blob-prefix</Name>
142 </BlobPrefix>
143 </Blobs>
144 <NextMarker />
145 </EnumerationResults>`
146 context := makeStorageContext()
147 response := &http.Response{
148 Status: fmt.Sprintf("%d", http.StatusOK),
149 StatusCode: http.StatusOK,
150 Body: makeResponseBody(response_body),
151 }
152 transport := &TestTransport{Response: response}
153 context.client = &http.Client{Transport: transport}
154 request := &ListBlobsRequest{Container: "container"}
155 results, err := context.ListAllBlobs(request)
156 c.Assert(err, IsNil)
157 c.Check(transport.Request.URL.String(), Matches, context.getContainerURL("container")+"?.*")
158 c.Check(transport.Request.URL.Query(), DeepEquals, url.Values{
159 "restype": {"container"},
160 "comp": {"list"},
161 })
162 c.Check(transport.Request.Header.Get("Authorization"), Not(Equals), "")
163 c.Assert(results, NotNil)
164}
165
166// Client-side errors from the HTTP client are propagated back to the caller.
167func (suite *testListAllBlobs) TestError(c *C) {
168 error := fmt.Errorf("canned-error")
169 transport := &TestTransport{Error: error}
170 context := makeStorageContext()
171 context.client = &http.Client{Transport: transport}
172 request := &ListBlobsRequest{Container: "container"}
173 _, err := context.ListAllBlobs(request)
174 c.Assert(err, NotNil)
175}
176
177// Server-side errors are propagated back to the caller.
178func (suite *testListAllBlobs) TestErrorResponse(c *C) {
179 response := &http.Response{
180 Status: fmt.Sprintf("%d", http.StatusNotFound),
181 StatusCode: http.StatusNotFound,
182 }
183 transport := &TestTransport{Response: response}
184 context := makeStorageContext()
185 context.client = &http.Client{Transport: transport}
186 request := &ListBlobsRequest{Container: "container"}
187 _, err := context.ListAllBlobs(request)
188 c.Assert(err, NotNil)
189}
190
191// ListAllBlobs combines multiple batches of output.
192func (suite *testListAllBlobs) TestBatchedResult(c *C) {
193 firstBlob := "blob1"
194 lastBlob := "blob2"
195 marker := "moreplease"
196 firstBatch := http.Response{
197 StatusCode: http.StatusOK,
198 Body: makeResponseBody(fmt.Sprintf(`
199 <EnumerationResults>
200 <Blobs>
201 <Blob>
202 <Name>%s</Name>
203 </Blob>
204 </Blobs>
205 <NextMarker>%s</NextMarker>
206 </EnumerationResults>
207 `, firstBlob, marker)),
208 }
209 lastBatch := http.Response{
210 StatusCode: http.StatusOK,
211 Body: makeResponseBody(fmt.Sprintf(`
212 <EnumerationResults>
213 <Blobs>
214 <Blob>
215 <Name>%s</Name>
216 </Blob>
217 </Blobs>
218 </EnumerationResults>
219 `, lastBlob)),
220 }
221 transport := TestTransport2{}
222 transport.AddExchange(&firstBatch, nil)
223 transport.AddExchange(&lastBatch, nil)
224 context := makeStorageContext()
225 context.client = &http.Client{Transport: &transport}
226
227 request := &ListBlobsRequest{Container: "mycontainer"}
228 blobs, err := context.ListAllBlobs(request)
229 c.Assert(err, IsNil)
230
231 c.Check(len(blobs.Blobs), Equals, 2)
232 c.Check(blobs.Blobs[0].Name, Equals, firstBlob)
233 c.Check(blobs.Blobs[1].Name, Equals, lastBlob)
234}

Subscribers

People subscribed via source and target branches

to all changes: