Merge lp:~julian-edwards/gwacl/pageblob into lp:gwacl

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: 114
Merged at revision: 111
Proposed branch: lp:~julian-edwards/gwacl/pageblob
Merge into: lp:gwacl
Diff against target: 147 lines (+71/-10)
2 files modified
storage_base.go (+31/-5)
storage_base_test.go (+40/-5)
To merge this branch: bzr merge lp:~julian-edwards/gwacl/pageblob
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+170009@code.launchpad.net

Commit message

Fix PutBlob by making sure it can supply x-ms-blob-type in the request.

Description of the change

The existing PutBlob implementation was half-finished. I think we originally started it with good intentions and then realised it wasn't needed.

This branch finishes the work by enabling the required x-ms-blob-type header. Some changes were needed in performRequest to support this.

We need to use PutBlob to initialise page blobs for the proposed user data solution.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

The capitalization of PutBlobRequest looks suspicious: the type is exported but its attributes are not. AFAICS its only use is in PutBlob, but how is anybody outside the module going to be able to create a PutBlobRequest to pass to PutBlob if they can't address its fields?

Is there any value to passing the address of a PutBlobRequest into PutBlob, as opposed to just giving it the struct itself? It just seems like extra weird syntax to live with: &PutBlockRequest{...} instead of just PutBlockRequest{...}. Then again, it's probably consistent with what we did in other places...

By the way, blob_type in Go style becomes blobType, and extra_headers becomes extraHeaders. Not pleasant, especially when it goes against vocal emphasis, but we'd better get used to it.

Once inside PutBlob, you return an error when an invalid blob type is selected. But this isn't variable data, so I'd say this was indicative of a static programming mistake. Might as well panic to teach them a lesson. And if that seems unfair, well, document the thing so they get a chance to do better!

The comment on TestPutBlob, about PutBlob returning Created on success, no longer belongs on top... It's now one of many things that the test looks at.

On TestPutPageBlob, the documenting comment incongruously starts with "Ensure that." Might as well drop that and just state the behaviour that the test verifies as fact.

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

The blob_type/blobType stuff could be done with a pseudo-enum:

type BlobType int

const (
    PageBlob BlobType = iota
    BlockBlob
)

type PutBlobRequest struct {
    Container string
    BlobType  BlobType  // not sure if it'll like the repeated name.
    Filename  string
}

There's nothing stopping someone from using an int instead of one of
the enum values though, which I think goes to show that I don't have a
full mental model of Go's type system yet.

lp:~julian-edwards/gwacl/pageblob updated
112. By Julian Edwards

jtv's review comments addressed

113. By Julian Edwards

Add request param comments

114. By Julian Edwards

go fmt

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

On 18/06/13 18:35, Gavin Panella wrote:
> The blob_type/blobType stuff could be done with a pseudo-enum:
>
> type BlobType int
>
> const (
> PageBlob BlobType = iota
> BlockBlob
> )
>
> type PutBlobRequest struct {
> Container string
> BlobType BlobType // not sure if it'll like the repeated name.
> Filename string
> }
>
> There's nothing stopping someone from using an int instead of one of
> the enum values though, which I think goes to show that I don't have a
> full mental model of Go's type system yet.
>

Does this give us any advantage over an obvious string? Other than the
warm fuzzy feeling of knowing that you're embedding into the Pikeverse a
bit more.

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

On 18/06/13 18:06, Jeroen T. Vermeulen wrote:
> The capitalization of PutBlobRequest looks suspicious: the type is exported but its attributes are not. AFAICS its only use is in PutBlob, but how is anybody outside the module going to be able to create a PutBlobRequest to pass to PutBlob if they can't address its fields?

I forgot about the capitalisation exporting crap. Bugger. Why.... why
does it have to be like that.

>
> Is there any value to passing the address of a PutBlobRequest into PutBlob, as opposed to just giving it the struct itself? It just seems like extra weird syntax to live with: &PutBlockRequest{...} instead of just PutBlockRequest{...}. Then again, it's probably consistent with what we did in other places...

Exactly. I considered passing by value but it would be inconsistent,
and consistency always wins.

>
> By the way, blob_type in Go style becomes blobType, and extra_headers becomes extraHeaders. Not pleasant, especially when it goes against vocal emphasis, but we'd better get used to it.

Grrrrrr!

>
> Once inside PutBlob, you return an error when an invalid blob type is selected. But this isn't variable data, so I'd say this was indicative of a static programming mistake. Might as well panic to teach them a lesson. And if that seems unfair, well, document the thing so they get a chance to do better!

Fair enough!

>
> The comment on TestPutBlob, about PutBlob returning Created on success, no longer belongs on top... It's now one of many things that the test looks at.

Changed.

>
> On TestPutPageBlob, the documenting comment incongruously starts with "Ensure that." Might as well drop that and just state the behaviour that the test verifies as fact.
>

Ok.

Better now? :)

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Much better, thanks. (Apart from the capitalization, which is merely more Go-like).

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

Cheers.

If Gavin wants the enum thing it can be done as a separate branch.

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

The other option is to remove the check entirely and let the api callsite pass the exact value that Azure needs. I think Gavin took this route on one of his storage-related changes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'storage_base.go'
--- storage_base.go 2013-06-11 16:31:49 +0000
+++ storage_base.go 2013-06-18 09:20:32 +0000
@@ -216,6 +216,7 @@
216 URL string // Resource locator, e.g. "http://example.com/my/resource".216 URL string // Resource locator, e.g. "http://example.com/my/resource".
217 Body io.Reader // Optional request body.217 Body io.Reader // Optional request body.
218 APIVersion string // Expected Azure API version, e.g. "2012-02-12".218 APIVersion string // Expected Azure API version, e.g. "2012-02-12".
219 ExtraHeaders http.Header // Optional extra request headers.
219 Result Deserializer // Optional object to parse API response into.220 Result Deserializer // Optional object to parse API response into.
220 ExpectedStatus HTTPStatus // Expected response status, e.g. http.StatusOK.221 ExpectedStatus HTTPStatus // Expected response status, e.g. http.StatusOK.
221}222}
@@ -250,6 +251,12 @@
250 if err != nil {251 if err != nil {
251 return nil, err252 return nil, err
252 }253 }
254 // net/http has no way of adding headers en-masse, hence this abomination.
255 for header, values := range params.ExtraHeaders {
256 for _, value := range values {
257 req.Header.Add(header, value)
258 }
259 }
253 addVersionHeader(req, params.APIVersion)260 addVersionHeader(req, params.APIVersion)
254 addDateHeader(req)261 addDateHeader(req)
255 addContentHeaders(req)262 addContentHeaders(req)
@@ -408,18 +415,37 @@
408 return nil415 return nil
409}416}
410417
418type PutBlobRequest struct {
419 Container string // Container name in the storage account
420 BlobType string // Pass "page" or "block"
421 Filename string // Filename for the new blob
422}
423
411// Send a request to create a space to upload a blob. Note that this does not424// Send a request to create a space to upload a blob. Note that this does not
412// do the uploading, it just makes an empty file.425// do the uploading, it just makes an empty file.
413func (context *StorageContext) PutBlob(container, filename string) error {426func (context *StorageContext) PutBlob(req *PutBlobRequest) error {
427 var blobType string
428 switch req.BlobType {
429 case "page":
430 blobType = "PageBlob"
431 case "block":
432 blobType = "BlockBlob"
433 default:
434 panic("block_type must be 'page' or 'block'")
435 }
436
437 extraHeaders := http.Header{}
438 extraHeaders.Add("x-ms-blob-type", blobType)
439
414 _, err := context.performRequest(requestParams{440 _, err := context.performRequest(requestParams{
415 Method: "PUT",441 Method: "PUT",
416 // TODO: Add blobtype header.442 URL: context.getFileURL(req.Container, req.Filename),
417 URL: context.getFileURL(container, filename),
418 APIVersion: "2012-02-12",443 APIVersion: "2012-02-12",
444 ExtraHeaders: extraHeaders,
419 ExpectedStatus: http.StatusCreated,445 ExpectedStatus: http.StatusCreated,
420 })446 })
421 if err != nil {447 if err != nil {
422 return fmt.Errorf("failed to create blob %s: %v", filename, err)448 return fmt.Errorf("failed to create blob %s: %v", req.Filename, err)
423 }449 }
424 return nil450 return nil
425}451}
426452
=== modified file 'storage_base_test.go'
--- storage_base_test.go 2013-06-11 16:34:02 +0000
+++ storage_base_test.go 2013-06-18 09:20:32 +0000
@@ -671,8 +671,8 @@
671671
672var _ = Suite(&TestPutBlob{})672var _ = Suite(&TestPutBlob{})
673673
674// The ListBlobs Storage API call returns 201 CREATED on success.674// Test basic PutBlob happy path functionality.
675func (suite *TestPutBlob) Test(c *C) {675func (suite *TestPutBlob) TestPutBlockBlob(c *C) {
676 context := makeStorageContext()676 context := makeStorageContext()
677 response := &http.Response{677 response := &http.Response{
678 Status: fmt.Sprintf("%d", http.StatusCreated),678 Status: fmt.Sprintf("%d", http.StatusCreated),
@@ -680,10 +680,43 @@
680 }680 }
681 transport := &TestTransport{Response: response}681 transport := &TestTransport{Response: response}
682 context.client = &http.Client{Transport: transport}682 context.client = &http.Client{Transport: transport}
683 err := context.PutBlob("container", "blobname")683 err := context.PutBlob(&PutBlobRequest{
684 Container: "container", BlobType: "block", Filename: "blobname"})
684 c.Assert(err, IsNil)685 c.Assert(err, IsNil)
686 // Ensure that container was set right.
685 c.Check(transport.Request.URL.String(), Equals, context.getFileURL("container", "blobname"))687 c.Check(transport.Request.URL.String(), Equals, context.getFileURL("container", "blobname"))
688 // Ensure that the Authorization header is set.
686 c.Check(transport.Request.Header.Get("Authorization"), Not(Equals), "")689 c.Check(transport.Request.Header.Get("Authorization"), Not(Equals), "")
690 // The blob type should be a block.
691 c.Check(transport.Request.Header.Get("x-ms-blob-type"), Equals, "BlockBlob")
692}
693
694// PutBlob should set x-ms-blob-type to PageBlob for Page Blobs.
695func (suite *TestPutBlob) TestPutPageBlob(c *C) {
696 context := makeStorageContext()
697 response := &http.Response{
698 Status: fmt.Sprintf("%d", http.StatusCreated),
699 StatusCode: http.StatusCreated,
700 }
701 transport := &TestTransport{Response: response}
702 context.client = &http.Client{Transport: transport}
703 err := context.PutBlob(&PutBlobRequest{
704 Container: "container", BlobType: "page", Filename: "blobname"})
705 c.Assert(err, IsNil)
706 c.Check(transport.Request.Header.Get("x-ms-blob-type"), Equals, "PageBlob")
707}
708
709// Passing a BlobType other than page or block results in a panic.
710func (suite *TestPutBlob) TestBlobType(c *C) {
711 defer func() {
712 err := recover()
713 c.Assert(err, Equals, "block_type must be 'page' or 'block'")
714 }()
715 context := makeStorageContext()
716 context.PutBlob(&PutBlobRequest{
717 Container: "container", BlobType: "invalid-blob-type",
718 Filename: "blobname"})
719 c.Assert("This should have panicked", Equals, "But it didn't.")
687}720}
688721
689// Client-side errors from the HTTP client are propagated back to the caller.722// Client-side errors from the HTTP client are propagated back to the caller.
@@ -692,7 +725,8 @@
692 transport := &TestTransport{Error: error}725 transport := &TestTransport{Error: error}
693 context := makeStorageContext()726 context := makeStorageContext()
694 context.client = &http.Client{Transport: transport}727 context.client = &http.Client{Transport: transport}
695 err := context.PutBlob("container", "blobname")728 err := context.PutBlob(&PutBlobRequest{
729 Container: "container", BlobType: "block", Filename: "blobname"})
696 c.Assert(err, NotNil)730 c.Assert(err, NotNil)
697}731}
698732
@@ -706,7 +740,8 @@
706 transport := &TestTransport{Response: response}740 transport := &TestTransport{Response: response}
707 context := makeStorageContext()741 context := makeStorageContext()
708 context.client = &http.Client{Transport: transport}742 context.client = &http.Client{Transport: transport}
709 err := context.PutBlob("container", "blobname")743 err := context.PutBlob(&PutBlobRequest{
744 Container: "container", BlobType: "block", Filename: "blobname"})
710 c.Assert(err, NotNil)745 c.Assert(err, NotNil)
711 c.Check(err, ErrorMatches, ".*102.*")746 c.Check(err, ErrorMatches, ".*102.*")
712 c.Check(err, ErrorMatches, ".*Frotzed.*")747 c.Check(err, ErrorMatches, ".*Frotzed.*")

Subscribers

People subscribed via source and target branches

to all changes: