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
1=== modified file 'storage_base.go'
2--- storage_base.go 2013-06-11 16:31:49 +0000
3+++ storage_base.go 2013-06-18 09:20:32 +0000
4@@ -216,6 +216,7 @@
5 URL string // Resource locator, e.g. "http://example.com/my/resource".
6 Body io.Reader // Optional request body.
7 APIVersion string // Expected Azure API version, e.g. "2012-02-12".
8+ ExtraHeaders http.Header // Optional extra request headers.
9 Result Deserializer // Optional object to parse API response into.
10 ExpectedStatus HTTPStatus // Expected response status, e.g. http.StatusOK.
11 }
12@@ -250,6 +251,12 @@
13 if err != nil {
14 return nil, err
15 }
16+ // net/http has no way of adding headers en-masse, hence this abomination.
17+ for header, values := range params.ExtraHeaders {
18+ for _, value := range values {
19+ req.Header.Add(header, value)
20+ }
21+ }
22 addVersionHeader(req, params.APIVersion)
23 addDateHeader(req)
24 addContentHeaders(req)
25@@ -408,18 +415,37 @@
26 return nil
27 }
28
29+type PutBlobRequest struct {
30+ Container string // Container name in the storage account
31+ BlobType string // Pass "page" or "block"
32+ Filename string // Filename for the new blob
33+}
34+
35 // Send a request to create a space to upload a blob. Note that this does not
36 // do the uploading, it just makes an empty file.
37-func (context *StorageContext) PutBlob(container, filename string) error {
38+func (context *StorageContext) PutBlob(req *PutBlobRequest) error {
39+ var blobType string
40+ switch req.BlobType {
41+ case "page":
42+ blobType = "PageBlob"
43+ case "block":
44+ blobType = "BlockBlob"
45+ default:
46+ panic("block_type must be 'page' or 'block'")
47+ }
48+
49+ extraHeaders := http.Header{}
50+ extraHeaders.Add("x-ms-blob-type", blobType)
51+
52 _, err := context.performRequest(requestParams{
53- Method: "PUT",
54- // TODO: Add blobtype header.
55- URL: context.getFileURL(container, filename),
56+ Method: "PUT",
57+ URL: context.getFileURL(req.Container, req.Filename),
58 APIVersion: "2012-02-12",
59+ ExtraHeaders: extraHeaders,
60 ExpectedStatus: http.StatusCreated,
61 })
62 if err != nil {
63- return fmt.Errorf("failed to create blob %s: %v", filename, err)
64+ return fmt.Errorf("failed to create blob %s: %v", req.Filename, err)
65 }
66 return nil
67 }
68
69=== modified file 'storage_base_test.go'
70--- storage_base_test.go 2013-06-11 16:34:02 +0000
71+++ storage_base_test.go 2013-06-18 09:20:32 +0000
72@@ -671,8 +671,8 @@
73
74 var _ = Suite(&TestPutBlob{})
75
76-// The ListBlobs Storage API call returns 201 CREATED on success.
77-func (suite *TestPutBlob) Test(c *C) {
78+// Test basic PutBlob happy path functionality.
79+func (suite *TestPutBlob) TestPutBlockBlob(c *C) {
80 context := makeStorageContext()
81 response := &http.Response{
82 Status: fmt.Sprintf("%d", http.StatusCreated),
83@@ -680,10 +680,43 @@
84 }
85 transport := &TestTransport{Response: response}
86 context.client = &http.Client{Transport: transport}
87- err := context.PutBlob("container", "blobname")
88+ err := context.PutBlob(&PutBlobRequest{
89+ Container: "container", BlobType: "block", Filename: "blobname"})
90 c.Assert(err, IsNil)
91+ // Ensure that container was set right.
92 c.Check(transport.Request.URL.String(), Equals, context.getFileURL("container", "blobname"))
93+ // Ensure that the Authorization header is set.
94 c.Check(transport.Request.Header.Get("Authorization"), Not(Equals), "")
95+ // The blob type should be a block.
96+ c.Check(transport.Request.Header.Get("x-ms-blob-type"), Equals, "BlockBlob")
97+}
98+
99+// PutBlob should set x-ms-blob-type to PageBlob for Page Blobs.
100+func (suite *TestPutBlob) TestPutPageBlob(c *C) {
101+ context := makeStorageContext()
102+ response := &http.Response{
103+ Status: fmt.Sprintf("%d", http.StatusCreated),
104+ StatusCode: http.StatusCreated,
105+ }
106+ transport := &TestTransport{Response: response}
107+ context.client = &http.Client{Transport: transport}
108+ err := context.PutBlob(&PutBlobRequest{
109+ Container: "container", BlobType: "page", Filename: "blobname"})
110+ c.Assert(err, IsNil)
111+ c.Check(transport.Request.Header.Get("x-ms-blob-type"), Equals, "PageBlob")
112+}
113+
114+// Passing a BlobType other than page or block results in a panic.
115+func (suite *TestPutBlob) TestBlobType(c *C) {
116+ defer func() {
117+ err := recover()
118+ c.Assert(err, Equals, "block_type must be 'page' or 'block'")
119+ }()
120+ context := makeStorageContext()
121+ context.PutBlob(&PutBlobRequest{
122+ Container: "container", BlobType: "invalid-blob-type",
123+ Filename: "blobname"})
124+ c.Assert("This should have panicked", Equals, "But it didn't.")
125 }
126
127 // Client-side errors from the HTTP client are propagated back to the caller.
128@@ -692,7 +725,8 @@
129 transport := &TestTransport{Error: error}
130 context := makeStorageContext()
131 context.client = &http.Client{Transport: transport}
132- err := context.PutBlob("container", "blobname")
133+ err := context.PutBlob(&PutBlobRequest{
134+ Container: "container", BlobType: "block", Filename: "blobname"})
135 c.Assert(err, NotNil)
136 }
137
138@@ -706,7 +740,8 @@
139 transport := &TestTransport{Response: response}
140 context := makeStorageContext()
141 context.client = &http.Client{Transport: transport}
142- err := context.PutBlob("container", "blobname")
143+ err := context.PutBlob(&PutBlobRequest{
144+ Container: "container", BlobType: "block", Filename: "blobname"})
145 c.Assert(err, NotNil)
146 c.Check(err, ErrorMatches, ".*102.*")
147 c.Check(err, ErrorMatches, ".*Frotzed.*")

Subscribers

People subscribed via source and target branches

to all changes: