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

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: 118
Merged at revision: 114
Proposed branch: lp:~julian-edwards/gwacl/putpage
Merge into: lp:gwacl
Diff against target: 126 lines (+105/-0)
2 files modified
storage_base.go (+35/-0)
storage_base_test.go (+70/-0)
To merge this branch: bzr merge lp:~julian-edwards/gwacl/putpage
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+170247@code.launchpad.net

Commit message

Add the PutPage storage API call

Description of the change

Add the PutPage storage API call.

There's nothing particularly noteworthy here, other than that a lot of the tests are taking on a familiar sheen. My next branch will be to refactor a lot of the test setup into better factory helpers, in particular we need a factory for the storage context with a fake transport. The one that was added the other day isn't helpful and also overlaps with the non-generic one already in storage_base_test.go.

To post a comment you must log in.
lp:~julian-edwards/gwacl/putpage updated
118. By Julian Edwards

Add reference to API page on web

Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good.

[0]

8 +type PutPageRequest struct {
9 + Container string // Container name in the storage account
10 + Filename string // The blob's file name
11 + StartRange int // Must be modulo 512, or an error is returned.
12 + EndRange int // Must be (modulo 512)-1, or an error is returned.
13 + Data io.Reader // The data to upload to the page.
14 +}
15 +
16 +// Send a request to add a range of data into a page blob.
17 +// See http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
18 +func (context *StorageContext) PutPage(req *PutPageRequest) error {

As discussed this morning, I think this is a good idea to have all our API method take structures as opposed to passing parameters. But I wonder if the container should not be excluded from PutPageRequest, and the signature of the method be something like: "PutPage(container string, req *PutPageRequest)"; this way the PutPageRequest will only capture things related to that request and could potentially be reused across different containers. What do you think?

[1]

// Check the range is set.

I know one ayatollah who would say that you don't need to say "Check" but me, I'm a good lad and I'm happy there is a comment at all ;).

[2]

Maybe this is something that belongs to a follow-up branch but I'd be happy to see this working in the field and thus I'd recommend calling this from example/storage/run.go.

[3]

106 +func (suite *TestPutPage) TestErrorResponse(c *C) {
107 + response := &http.Response{
108 + Status: "102 Frotzed",
109 + StatusCode: 102,
110 + Body: makeResponseBody("<Error><Code>Frotzed</Code><Message>failed to put blob</Message></Error>"),
111 + }
112 + transport := &TestTransport{Response: response}
113 + context := makeStorageContext()
114 + context.client = &http.Client{Transport: transport}
115 + err := context.PutPage(&PutPageRequest{
116 + Container: "container", Filename: "filename", StartRange: 0,
117 + EndRange: 512, Data: nil})
118 + c.Assert(err, NotNil)
119 + c.Check(err, ErrorMatches, ".*102.*")
120 + c.Check(err, ErrorMatches, ".*Frotzed.*")
121 + c.Check(err, ErrorMatches, ".*failed to put blob.*")

This is really a detail but I'd advocate in favor of defining variables with the error status, the error body, etc and do the checks at the end of the methods against these variables instead of duplicating things like "failed to put blob".

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

On 19/06/13 19:08, Raphaël Badin wrote:
> Review: Approve
>
> Looks good.

Thank you for reviewing.

>
> [0]
>
> 8 +type PutPageRequest struct {
> 9 + Container string // Container name in the storage account
> 10 + Filename string // The blob's file name
> 11 + StartRange int // Must be modulo 512, or an error is returned.
> 12 + EndRange int // Must be (modulo 512)-1, or an error is returned.
> 13 + Data io.Reader // The data to upload to the page.
> 14 +}
> 15 +
> 16 +// Send a request to add a range of data into a page blob.
> 17 +// See http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
> 18 +func (context *StorageContext) PutPage(req *PutPageRequest) error {
>
> As discussed this morning, I think this is a good idea to have all our API method take structures as opposed to passing parameters. But I wonder if the container should not be excluded from PutPageRequest, and the signature of the method be something like: "PutPage(container string, req *PutPageRequest)"; this way the PutPageRequest will only capture things related to that request and could potentially be reused across different containers. What do you think?

I used to think this but Gavin talked me out of it here:
https://code.launchpad.net/~allenap/gwacl/list-files-by-prefix/+merge/168479

>
> [1]
>
> // Check the range is set.
>
> I know one ayatollah who would say that you don't need to say "Check" but me, I'm a good lad and I'm happy there is a comment at all ;).

ayatollah - lol

>
> [2]
>
> Maybe this is something that belongs to a follow-up branch but I'd be happy to see this working in the field and thus I'd recommend calling this from example/storage/run.go.

Yes, totally, that is my plan. I just didn't want to mix it in here.

>
> [3]
>
> 106 +func (suite *TestPutPage) TestErrorResponse(c *C) {
> 107 + response := &http.Response{
> 108 + Status: "102 Frotzed",
> 109 + StatusCode: 102,
> 110 + Body: makeResponseBody("<Error><Code>Frotzed</Code><Message>failed to put blob</Message></Error>"),
> 111 + }
> 112 + transport := &TestTransport{Response: response}
> 113 + context := makeStorageContext()
> 114 + context.client = &http.Client{Transport: transport}
> 115 + err := context.PutPage(&PutPageRequest{
> 116 + Container: "container", Filename: "filename", StartRange: 0,
> 117 + EndRange: 512, Data: nil})
> 118 + c.Assert(err, NotNil)
> 119 + c.Check(err, ErrorMatches, ".*102.*")
> 120 + c.Check(err, ErrorMatches, ".*Frotzed.*")
> 121 + c.Check(err, ErrorMatches, ".*failed to put blob.*")
>
> This is really a detail but I'd advocate in favor of defining variables with the error status, the error body, etc and do the checks at the end of the methods against these variables instead of duplicating things like "failed to put blob".

This is a copy and paste of some other code. I would like to refactor
all of this in one fell swoop in a separate branch if you don't mind.

Cheers.

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-18 09:18:06 +0000
3+++ storage_base.go 2013-06-19 06:58:27 +0000
4@@ -450,6 +450,41 @@
5 return nil
6 }
7
8+type PutPageRequest struct {
9+ Container string // Container name in the storage account
10+ Filename string // The blob's file name
11+ StartRange int // Must be modulo 512, or an error is returned.
12+ EndRange int // Must be (modulo 512)-1, or an error is returned.
13+ Data io.Reader // The data to upload to the page.
14+}
15+
16+// Send a request to add a range of data into a page blob.
17+// See http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
18+func (context *StorageContext) PutPage(req *PutPageRequest) error {
19+ uri := addURLQueryParams(
20+ context.getFileURL(req.Container, req.Filename),
21+ "comp", "page")
22+
23+ extraHeaders := http.Header{}
24+
25+ rangeData := fmt.Sprintf("bytes=%d-%d", req.StartRange, req.EndRange)
26+ extraHeaders.Add("x-ms-range", rangeData)
27+ extraHeaders.Add("x-ms-page-write", "update")
28+
29+ _, err := context.performRequest(requestParams{
30+ Method: "PUT",
31+ URL: uri,
32+ Body: req.Data,
33+ APIVersion: "2012-02-12",
34+ ExtraHeaders: extraHeaders,
35+ ExpectedStatus: http.StatusCreated,
36+ })
37+ if err != nil {
38+ return fmt.Errorf("failed to put page for file %s: %v", req.Filename, err)
39+ }
40+ return nil
41+}
42+
43 // Send a request to fetch the list of blocks that have been uploaded as part
44 // of a block blob.
45 func (context *StorageContext) GetBlockList(container, filename string) (*GetBlockList, error) {
46
47=== modified file 'storage_base_test.go'
48--- storage_base_test.go 2013-06-18 09:18:06 +0000
49+++ storage_base_test.go 2013-06-19 06:58:27 +0000
50@@ -667,6 +667,76 @@
51 c.Assert(err, NotNil)
52 }
53
54+type TestPutPage struct{}
55+
56+var _ = Suite(&TestPutPage{})
57+
58+// Basic happy path testing.
59+func (suite *TestPutPage) TestHappyPath(c *C) {
60+ context := makeStorageContext()
61+ response := &http.Response{
62+ Status: fmt.Sprintf("%d", http.StatusCreated),
63+ StatusCode: http.StatusCreated,
64+ }
65+ transport := &TestTransport{Response: response}
66+ context.client = &http.Client{Transport: transport}
67+ randomData := MakeRandomByteSlice(10)
68+ dataReader := bytes.NewReader(randomData)
69+
70+ err := context.PutPage(&PutPageRequest{
71+ Container: "container", Filename: "filename", StartRange: 0,
72+ EndRange: 512, Data: dataReader})
73+ c.Assert(err, IsNil)
74+
75+ // Ensure that container was set right.
76+ c.Check(transport.Request.URL.String(), Matches, context.getFileURL("container", "filename")+"?.*")
77+ // Ensure that the Authorization header is set.
78+ c.Check(transport.Request.Header.Get("Authorization"), Not(Equals), "")
79+ // Check the range is set.
80+ c.Check(transport.Request.Header.Get("x-ms-range"), Equals, "bytes=0-512")
81+ // Check special page write header.
82+ c.Check(transport.Request.Header.Get("x-ms-page-write"), Equals, "update")
83+ // "?comp=page" should be part of the URL.
84+ c.Check(transport.Request.URL.Query(), DeepEquals, url.Values{
85+ "comp": {"page"},
86+ })
87+ // Check the data payload.
88+ data, err := ioutil.ReadAll(transport.Request.Body)
89+ c.Assert(err, IsNil)
90+ c.Check(data, DeepEquals, randomData)
91+}
92+
93+// Client-side errors from the HTTP client are propagated back to the caller.
94+func (suite *TestPutPage) TestError(c *C) {
95+ cannedError := fmt.Errorf("canned-error")
96+ transport := &TestTransport{Error: cannedError}
97+ context := makeStorageContext()
98+ context.client = &http.Client{Transport: transport}
99+ err := context.PutPage(&PutPageRequest{
100+ Container: "container", Filename: "filename", StartRange: 0,
101+ EndRange: 512, Data: nil})
102+ c.Assert(err, NotNil)
103+}
104+
105+// Server-side errors are propagated back to the caller.
106+func (suite *TestPutPage) TestErrorResponse(c *C) {
107+ response := &http.Response{
108+ Status: "102 Frotzed",
109+ StatusCode: 102,
110+ Body: makeResponseBody("<Error><Code>Frotzed</Code><Message>failed to put blob</Message></Error>"),
111+ }
112+ transport := &TestTransport{Response: response}
113+ context := makeStorageContext()
114+ context.client = &http.Client{Transport: transport}
115+ err := context.PutPage(&PutPageRequest{
116+ Container: "container", Filename: "filename", StartRange: 0,
117+ EndRange: 512, Data: nil})
118+ c.Assert(err, NotNil)
119+ c.Check(err, ErrorMatches, ".*102.*")
120+ c.Check(err, ErrorMatches, ".*Frotzed.*")
121+ c.Check(err, ErrorMatches, ".*failed to put blob.*")
122+}
123+
124 type TestPutBlob struct{}
125
126 var _ = Suite(&TestPutBlob{})

Subscribers

People subscribed via source and target branches

to all changes: