Merge lp:~allenap/gwacl/chunky-blobs-operation into lp:gwacl
- chunky-blobs-operation
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | 118 |
Merged at revision: | 93 |
Proposed branch: | lp:~allenap/gwacl/chunky-blobs-operation |
Merge into: | lp:gwacl |
Diff against target: |
449 lines (+318/-41) 7 files modified
example/storage/run.go (+4/-32) logging/logging.go (+83/-0) logging/logging_test.go (+39/-0) storage.go (+47/-0) storage_test.go (+130/-0) test_helpers.go (+6/-0) x509dispatcher.go (+9/-9) |
To merge this branch: | bzr merge lp:~allenap/gwacl/chunky-blobs-operation |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+159512@code.launchpad.net |
Commit message
New Storage API meta-operation UploadBlockBlob that uses PutBlock and PutBlockList to upload larger files in chunks.
Description of the change
Julian Edwards (julian-edwards) wrote : | # |
Julian Edwards (julian-edwards) wrote : | # |
[sorry. hit the button too early]
91 + if err == io.EOF {
92 + break
93 + }
94 + if err != nil {
95 + return err
96 + }
I looked at this and thought, why not a switch. But then the break would break the wrong thing... gah :/
98 + blockID := strconv.
The documentation says that all block IDs should be the same length, and suggests base64 encoding this ID. Additionally the docs say "the pre-encoded string must be 64 bytes or less". It might be worth enforcing/checking that here.
99 + log.Printf(
109 + log.Printf(
I don't think you should be logging here :)
You don't have to do it here but one of the advantages of block uploads is that we can do them in parallel. As a side project you could add go routines to do that here, up to a configured maximum in parallel. It'll speed up the uploads considerably.
167 + Body: ioutil.
We've used this NopCloser thing in a few places now, do you think we should turn it into a test helper?
160 +func (suite *testUploadBloc
Did you copy this and forget to change the function name?
172 + Status: fmt.Sprintf("%d", http.StatusOK),
173 + StatusCode: http.StatusCreated,
Discrepancy between statuses here, is that intentional? If so, please comment why.
I don't know if it's how Go just is but I found it quite hard to discern the test intentions in this large test. I think we should break it up for readability, and create some re-usable test helpers at the same time.
If we had something along the lines of this, it would more clearly show test intent:
[in pseudo code]
insertFakeR
makeRandomD
uploadBlock
assertPutBl
assertBlock
This way we've abstracted out the boilerplate, but the test data is all contained here in the same function so it's easy to see input and expected output, and the test intention.
- 100. By Gavin Panella
-
Create Empty, an io.ReadCloser counterpart to ioutil.Discard.
- 101. By Gavin Panella
-
Fix copy-n-paste error.
- 102. By Gavin Panella
-
Fix typo.
Gavin Panella (allenap) wrote : | # |
> [sorry. hit the button too early]
>
> 91 + if err == io.EOF {
> 92 + break
> 93 + }
> 94 + if err != nil {
> 95 + return err
> 96 + }
>
> I looked at this and thought, why not a switch. But then the break would
> break the wrong thing... gah :/
Yeah, that was the same problem I encountered. I could use a label,
but they're just as ugly.
>
> 98 + blockID := strconv.
>
> The documentation says that all block IDs should be the same length, and
> suggests base64 encoding this ID. Additionally the docs say "the pre-encoded
> string must be 64 bytes or less". It might be worth enforcing/checking that
> here.
By my calculations we won't have a problem here until blockNum is a
little over 4e99. There's belt and braces, then there's girdle,
suspenders and nappies. I think it'll be fine :)
>
> 99 + log.Printf(
> 109 + log.Printf(
>
> I don't think you should be logging here :)
I think I should be logging here, but for Go's pretend log package,
which is not much more than a wrapper around print right now.
Crucially there's no way to suppress these messages. If you object
strongly I'll remove them, but they are useful when uploading bigger
files.
>
> You don't have to do it here but one of the advantages of block uploads is
> that we can do them in parallel. As a side project you could add go routines
> to do that here, up to a configured maximum in parallel. It'll speed up the
> uploads considerably.
Will it? If network utilisation is near 100% then doing more
concurrently won't speed it up. I guess it would mean that more TCP
slow-starts could happen together, instead of each time there's a
connection. Beyond that I'm not sure. I want to get my teeth into some
Go-style concurrency, but I'm not sure this warrants it.
>
> 167 + Body: ioutil.
>
> We've used this NopCloser thing in a few places now, do you think we should
> turn it into a test helper?
Done: I've created Empty in test_helpers.go, which is a io.ReadCloser
counterpart to ioutil.Discard.
>
> 160 +func (suite *testUploadBloc
>
> Did you copy this and forget to change the function name?
Oops, yes.
>
> 172 + Status: fmt.Sprintf("%d", http.StatusOK),
> 173 + StatusCode: http.StatusCreated,
>
> Discrepancy between statuses here, is that intentional? If so, please comment
> why.
It was a mistake. Fixed.
>
> I don't know if it's how Go just is but I found it quite hard to discern the
> test intentions in this large test. I think we should break it up for
> readability, and create some re-usable test helpers at the same time.
>
> If we had something along the lines of this, it would more clearly show test
> intent:
> [in pseudo code]
> insertFakeRespo
> makeRandomData()
> uploadBlockBlob()
> assertPutBlockS
> assertBlockList
>
> This way we've abstracted out the boilerplate, but the tes...
Gavin Panella (allenap) wrote : | # |
> 61 === added file 'storage2.go'
>
> Can you not think of a better name for this? In fact I'm not even sure we
> need another file. If you insist, we could have a new file called
> storage_blob.go and move all the blob stuff into it.
I tried first to refactor the storage stuff into a subpackage, but
that turned into a nightmare, and I backed out. The other file was
getting a bit long, and I wanted to put these higher-level operations
somewhere distinct from the lower-level API building blocks. As such,
I don't think storage_blob.go is the right name. Perhaps renaming
storage.go to storage_base.go, and storage2.go to storage.go?
- 103. By Gavin Panella
-
Extract method makeFakeCreated
Response( ). - 104. By Gavin Panella
-
Extract methods uploadRandomBlob(), assertBlockSent(), and assertBlockList
Sent(). - 105. By Gavin Panella
-
Move makeFakeCreated
Response( ) down. - 106. By Gavin Panella
-
Additional test for a large file that actually needs chunking.
Gavin Panella (allenap) wrote : | # |
I think this is ready for another look now. In addition to responding to your review comments, I've also added a second test for a larger file, one that needs to be chunked.
Julian Edwards (julian-edwards) wrote : | # |
On 20/04/13 00:50, Gavin Panella wrote:
> Yeah, that was the same problem I encountered. I could use a label,
> but they're just as ugly.
I reckon this is why Go still has a goto ...
>>
>> 98 + blockID := strconv.
>>
>> The documentation says that all block IDs should be the same length, and
>> suggests base64 encoding this ID. Additionally the docs say "the pre-encoded
>> string must be 64 bytes or less". It might be worth enforcing/checking that
>> here.
>
> By my calculations we won't have a problem here until blockNum is a
> little over 4e99. There's belt and braces, then there's girdle,
> suspenders and nappies. I think it'll be fine :)
I guess I am getting ahead of myself a bit, I was thinking of something
I know will happen at some point where people will want to supply their
own block IDs.
Does FormatInt guarantee the same length string for all numbers? That
does seem to be a hard requirement of the API.
>>
>> 99 + log.Printf(
>> 109 + log.Printf(
>>
>> I don't think you should be logging here :)
>
> I think I should be logging here, but for Go's pretend log package,
> which is not much more than a wrapper around print right now.
> Crucially there's no way to suppress these messages. If you object
> strongly I'll remove them, but they are useful when uploading bigger
> files.
I am strongly of the opinion that a library function should not
unconditionally log. At least change to a Debugf.
>> You don't have to do it here but one of the advantages of block uploads is
>> that we can do them in parallel. As a side project you could add go routines
>> to do that here, up to a configured maximum in parallel. It'll speed up the
>> uploads considerably.
>
> Will it?
Yes.
> If network utilisation is near 100% then doing more
You're not necessarily guaranteed 100% net utilisation from a single TCP
connection. Especially over international links. I can get much better
speeds here if I have more than one session at once, because handshaking
has such a high latency that the TCP window still fails to do anything
useful.
> concurrently won't speed it up. I guess it would mean that more TCP
> slow-starts could happen together, instead of each time there's a
> connection. Beyond that I'm not sure. I want to get my teeth into some
> Go-style concurrency, but I'm not sure this warrants it.
Trust me, it does warrant it. I do a lot of downloading from Australia
and I see real speed gains with multiple sessions.
Like I said, we don't need it right now in this branch but I think it
should be done. Besides, you want to play with goroutines, right? :)
>
>>
>> 167 + Body: ioutil.
>>
>> We've used this NopCloser thing in a few places now, do you think we should
>> turn it into a test helper?
>
> Done: I've created Empty in test_helpers.go, which is a io.ReadCloser
> counterpart to ioutil.Discard.
Coolio, thanks! (I can't help but pronounce Body in the manner of Fat
Bastard from Austin Powers)
> I like this idea. Fwiw, I did pass this by The Mi...
Julian Edwards (julian-edwards) wrote : | # |
On 20/04/13 00:55, Gavin Panella wrote:
>> 61 === added file 'storage2.go'
>>
>> Can you not think of a better name for this? In fact I'm not even sure we
>> need another file. If you insist, we could have a new file called
>> storage_blob.go and move all the blob stuff into it.
>
> I tried first to refactor the storage stuff into a subpackage, but
> that turned into a nightmare, and I backed out. The other file was
> getting a bit long, and I wanted to put these higher-level operations
> somewhere distinct from the lower-level API building blocks. As such,
> I don't think storage_blob.go is the right name. Perhaps renaming
> storage.go to storage_base.go, and storage2.go to storage.go?
>
On reflection I think it's early optimisation (if there is such a thing
for file organisation). Do you strongly object to just leaving it all
in the same file?
Otherwise your suggestion is fine.
Julian Edwards (julian-edwards) wrote : | # |
+ // block ID is a base64 encoding of the string "0".
+ assertBlockSent(c, context, data, "MA==", transport.
You could use:
base64.
like we do in the rest of the gwacl tests.
Everything else looks great, thanks.
Gavin Panella (allenap) wrote : | # |
> Does FormatInt guarantee the same length string for all numbers? That
> does seem to be a hard requirement of the API.
Ah, no, it doesn't. I haven't seen that requirement before... /me
investigates.
...
> I am strongly of the opinion that a library function should not
> unconditionally log. At least change to a Debugf.
Yeah, I agree with that. It's a rubbish logging package. I'll do
something to make sure it's quiet.
>
> >> You don't have to do it here but one of the advantages of block uploads is
> >> that we can do them in parallel. As a side project you could add go
> routines
> >> to do that here, up to a configured maximum in parallel. It'll speed up
> the
> >> uploads considerably.
> >
> > Will it?
>
> Yes.
>
> > If network utilisation is near 100% then doing more
>
> You're not necessarily guaranteed 100% net utilisation from a single TCP
> connection. Especially over international links. I can get much better
> speeds here if I have more than one session at once, because handshaking
> has such a high latency that the TCP window still fails to do anything
> useful.
>
> > concurrently won't speed it up. I guess it would mean that more TCP
> > slow-starts could happen together, instead of each time there's a
> > connection. Beyond that I'm not sure. I want to get my teeth into some
> > Go-style concurrency, but I'm not sure this warrants it.
>
> Trust me, it does warrant it. I do a lot of downloading from Australia
> and I see real speed gains with multiple sessions.
>
> Like I said, we don't need it right now in this branch but I think it
> should be done. Besides, you want to play with goroutines, right? :)
Yes. It's about all the pleasure there's to be had from Go. Okay, I'll
try to add it when I'm stalling on something else.
Gavin Panella (allenap) wrote : | # |
> On reflection I think it's early optimisation (if there is such a thing
> for file organisation). Do you strongly object to just leaving it all
> in the same file?
I do, if only because it's more work to split it out than it is to
merge it later if we decide we want it that way. It all appears in the
gwacl package, but I'd like to maintain a mental fence between the API
atoms and the things that are built with them.
- 107. By Gavin Panella
-
Add logging package to wrap the standard library's log package into something just about useful.
- 108. By Gavin Panella
-
Use the new logging stuff in storage2.go.
- 109. By Gavin Panella
-
Remove superflous semicolon.
- 110. By Gavin Panella
-
Encode the blockIDs in the test, instead of hard-coding the base64 versions.
- 111. By Gavin Panella
-
Add GPL header to logging.go.
- 112. By Gavin Panella
-
Make WARN the default log level.
- 113. By Gavin Panella
-
Set log.level from the environment.
- 114. By Gavin Panella
-
Renames.
- 115. By Gavin Panella
-
No need to define level's type.
- 116. By Gavin Panella
-
Add a rudimentary test for the logging stuff.
- 117. By Gavin Panella
-
Move logging stuff into subpackage.
- 118. By Gavin Panella
-
Use the logging package in x509dispatcher too.
Preview Diff
1 | === modified file 'example/storage/run.go' |
2 | --- example/storage/run.go 2013-04-17 10:11:07 +0000 |
3 | +++ example/storage/run.go 2013-04-22 16:24:27 +0000 |
4 | @@ -10,15 +10,11 @@ |
5 | package main |
6 | |
7 | import ( |
8 | - "bytes" |
9 | "flag" |
10 | "fmt" |
11 | - "io" |
12 | "io/ioutil" |
13 | "launchpad.net/gwacl" |
14 | - "log" |
15 | "os" |
16 | - "strconv" |
17 | ) |
18 | |
19 | var account string |
20 | @@ -125,35 +121,11 @@ |
21 | } |
22 | defer file.Close() |
23 | |
24 | - buffer := make([]byte, 1024*1024) // 1MB buffer |
25 | - blockList := &gwacl.BlockList{} |
26 | - |
27 | - // Upload the file in chunks. |
28 | - for blockNum := int64(0); ; blockNum++ { |
29 | - blockSize, err := file.Read(buffer) |
30 | - if err == io.EOF { |
31 | - break |
32 | - } |
33 | - if err != nil { |
34 | - dumpError(err) |
35 | - return |
36 | - } |
37 | - block := bytes.NewReader(buffer[:blockSize]) |
38 | - blockID := strconv.FormatInt(blockNum, 36) |
39 | - log.Printf("Uploading block %d (size=%d, id=%s).\n", |
40 | - blockNum, blockSize, blockID) |
41 | - err = storage.PutBlock(container, filename, blockID, block) |
42 | - if err != nil { |
43 | - dumpError(err) |
44 | - return |
45 | - } |
46 | - blockList.Add(gwacl.BlockListLatest, blockID) |
47 | + err = storage.UploadBlockBlob(container, filename, file) |
48 | + if err != nil { |
49 | + dumpError(err) |
50 | + return |
51 | } |
52 | - |
53 | - // Commit those blocks by writing the block list. |
54 | - log.Printf("Committing %d blocks.\n", len(blockList.Items)) |
55 | - err = storage.PutBlockList(container, filename, blockList) |
56 | - dumpError(err) |
57 | } |
58 | |
59 | func deleteblob(storage gwacl.StorageContext) { |
60 | |
61 | === added directory 'logging' |
62 | === added file 'logging/logging.go' |
63 | --- logging/logging.go 1970-01-01 00:00:00 +0000 |
64 | +++ logging/logging.go 2013-04-22 16:24:27 +0000 |
65 | @@ -0,0 +1,83 @@ |
66 | +// Copyright 2013 Canonical Ltd. This software is licensed under the |
67 | +// GNU Lesser General Public License version 3 (see the file COPYING). |
68 | + |
69 | +package logging |
70 | + |
71 | +import ( |
72 | + "log" |
73 | + "os" |
74 | +) |
75 | + |
76 | +const ( |
77 | + DEBUG = 10 * (iota + 1) |
78 | + INFO |
79 | + WARN |
80 | + ERROR |
81 | +) |
82 | + |
83 | +var level = WARN |
84 | + |
85 | +func init() { |
86 | + setLevel(os.Getenv("LOGLEVEL")) |
87 | +} |
88 | + |
89 | +func setLevel(levelName string) { |
90 | + switch levelName { |
91 | + case "DEBUG": |
92 | + level = DEBUG |
93 | + case "INFO": |
94 | + level = INFO |
95 | + case "WARN": |
96 | + level = WARN |
97 | + case "ERROR": |
98 | + level = ERROR |
99 | + } |
100 | +} |
101 | + |
102 | +func Debug(args ...interface{}) { |
103 | + if level <= DEBUG { |
104 | + log.Println(args...) |
105 | + } |
106 | +} |
107 | + |
108 | +func Debugf(format string, args ...interface{}) { |
109 | + if level <= DEBUG { |
110 | + log.Printf(format, args...) |
111 | + } |
112 | +} |
113 | + |
114 | +func Info(args ...interface{}) { |
115 | + if level <= INFO { |
116 | + log.Println(args...) |
117 | + } |
118 | +} |
119 | + |
120 | +func Infof(format string, args ...interface{}) { |
121 | + if level <= INFO { |
122 | + log.Printf(format, args...) |
123 | + } |
124 | +} |
125 | + |
126 | +func Warn(args ...interface{}) { |
127 | + if level <= WARN { |
128 | + log.Println(args...) |
129 | + } |
130 | +} |
131 | + |
132 | +func Warnf(format string, args ...interface{}) { |
133 | + if level <= WARN { |
134 | + log.Printf(format, args...) |
135 | + } |
136 | +} |
137 | + |
138 | +func Error(args ...interface{}) { |
139 | + if level <= ERROR { |
140 | + log.Println(args...) |
141 | + } |
142 | +} |
143 | + |
144 | +func Errorf(format string, args ...interface{}) { |
145 | + if level <= ERROR { |
146 | + log.Printf(format, args...) |
147 | + } |
148 | +} |
149 | |
150 | === added file 'logging/logging_test.go' |
151 | --- logging/logging_test.go 1970-01-01 00:00:00 +0000 |
152 | +++ logging/logging_test.go 2013-04-22 16:24:27 +0000 |
153 | @@ -0,0 +1,39 @@ |
154 | +// Copyright 2013 Canonical Ltd. This software is licensed under the |
155 | +// GNU Lesser General Public License version 3 (see the file COPYING). |
156 | + |
157 | +package logging |
158 | + |
159 | +import ( |
160 | + . "launchpad.net/gocheck" |
161 | +) |
162 | + |
163 | +var originalLevel = level |
164 | + |
165 | +func restoreLevel() { |
166 | + level = originalLevel |
167 | +} |
168 | + |
169 | +type testLogging struct{} |
170 | + |
171 | +var _ = Suite(&testLogging{}) |
172 | + |
173 | +func (suite *testLogging) TestSetLevel(c *C) { |
174 | + defer restoreLevel() |
175 | + // The names of the logging constants are recognised arguments to |
176 | + // setLevel(). |
177 | + level = -1 |
178 | + setLevel("DEBUG") |
179 | + c.Check(level, Equals, DEBUG) |
180 | + setLevel("INFO") |
181 | + c.Check(level, Equals, INFO) |
182 | + setLevel("WARN") |
183 | + c.Check(level, Equals, WARN) |
184 | + setLevel("ERROR") |
185 | + c.Check(level, Equals, ERROR) |
186 | + // Unrecognised arguments are ignored. |
187 | + level = -1 |
188 | + setLevel("FOOBAR") |
189 | + c.Check(level, Equals, -1) |
190 | + setLevel("123") |
191 | + c.Check(level, Equals, -1) |
192 | +} |
193 | |
194 | === added file 'storage.go' |
195 | --- storage.go 1970-01-01 00:00:00 +0000 |
196 | +++ storage.go 2013-04-22 16:24:27 +0000 |
197 | @@ -0,0 +1,47 @@ |
198 | +// Copyright 2013 Canonical Ltd. This software is licensed under the |
199 | +// GNU Lesser General Public License version 3 (see the file COPYING). |
200 | + |
201 | +package gwacl |
202 | + |
203 | +// This file contains higher level operations necessary to work with the Azure |
204 | +// file storage API. |
205 | + |
206 | +import ( |
207 | + "bytes" |
208 | + "io" |
209 | + . "launchpad.net/gwacl/logging" |
210 | + "strconv" |
211 | +) |
212 | + |
213 | +// UploadBlockBlob uses PutBlock and PutBlockList API operations to upload |
214 | +// arbitrarily large files, 1MB at a time. |
215 | +func (context *StorageContext) UploadBlockBlob( |
216 | + container, filename string, data io.Reader) error { |
217 | + |
218 | + buffer := make([]byte, 1024*1024) // 1MB buffer |
219 | + blockList := &BlockList{} |
220 | + |
221 | + // Upload the file in chunks. |
222 | + for blockNum := int64(0); ; blockNum++ { |
223 | + blockSize, err := data.Read(buffer) |
224 | + if err == io.EOF { |
225 | + break |
226 | + } |
227 | + if err != nil { |
228 | + return err |
229 | + } |
230 | + block := bytes.NewReader(buffer[:blockSize]) |
231 | + blockID := strconv.FormatInt(blockNum, 36) |
232 | + Debugf("Uploading block %d (size=%d, id=%s).\n", |
233 | + blockNum, blockSize, blockID) |
234 | + err = context.PutBlock(container, filename, blockID, block) |
235 | + if err != nil { |
236 | + return err |
237 | + } |
238 | + blockList.Add(BlockListLatest, blockID) |
239 | + } |
240 | + |
241 | + // Commit those blocks by writing the block list. |
242 | + Debugf("Committing %d blocks.\n", len(blockList.Items)) |
243 | + return context.PutBlockList(container, filename, blockList) |
244 | +} |
245 | |
246 | === renamed file 'storage.go' => 'storage_base.go' |
247 | === renamed file 'storage_test.go' => 'storage_base_test.go' |
248 | === added file 'storage_test.go' |
249 | --- storage_test.go 1970-01-01 00:00:00 +0000 |
250 | +++ storage_test.go 2013-04-22 16:24:27 +0000 |
251 | @@ -0,0 +1,130 @@ |
252 | +// Copyright 2013 Canonical Ltd. This software is licensed under the |
253 | +// GNU Lesser General Public License version 3 (see the file COPYING). |
254 | + |
255 | +package gwacl |
256 | + |
257 | +import ( |
258 | + "bytes" |
259 | + "encoding/base64" |
260 | + "fmt" |
261 | + "io/ioutil" |
262 | + . "launchpad.net/gocheck" |
263 | + "net/http" |
264 | + "net/url" |
265 | +) |
266 | + |
267 | +func b64(s string) string { |
268 | + return base64.StdEncoding.EncodeToString([]byte(s)) |
269 | +} |
270 | + |
271 | +type TestTransport2Exchange struct { |
272 | + Request *http.Request |
273 | + Response *http.Response |
274 | + Error error |
275 | +} |
276 | + |
277 | +// TestTransport2 is used as an http.Client.Transport for testing. The only |
278 | +// requirement is that it adhere to the http.RoundTripper interface. |
279 | +type TestTransport2 struct { |
280 | + Exchanges []*TestTransport2Exchange |
281 | + ExchangeCount int |
282 | +} |
283 | + |
284 | +func (t *TestTransport2) AddExchange(response *http.Response, error error) { |
285 | + exchange := TestTransport2Exchange{Response: response, Error: error} |
286 | + t.Exchanges = append(t.Exchanges, &exchange) |
287 | +} |
288 | + |
289 | +func (t *TestTransport2) RoundTrip(req *http.Request) (resp *http.Response, err error) { |
290 | + exchange := t.Exchanges[t.ExchangeCount] |
291 | + t.ExchangeCount += 1 |
292 | + exchange.Request = req |
293 | + return exchange.Response, exchange.Error |
294 | +} |
295 | + |
296 | +type testUploadBlockBlob struct{} |
297 | + |
298 | +var _ = Suite(&testUploadBlockBlob{}) |
299 | + |
300 | +func (suite *testUploadBlockBlob) TestSmallFile(c *C) { |
301 | + context := makeStorageContext() |
302 | + transport := &TestTransport2{} |
303 | + // UploadBlockBlob uses PutBlock to upload the data. |
304 | + transport.AddExchange(makeFakeCreatedResponse(), nil) |
305 | + // UploadBlockBlob then sends the list of blocks with PutBlockList. |
306 | + transport.AddExchange(makeFakeCreatedResponse(), nil) |
307 | + // Plug in the test transport. |
308 | + context.client = &http.Client{Transport: transport} |
309 | + // Upload a random blob of data. |
310 | + data := uploadRandomBlob(c, context, 10) |
311 | + // There were two exchanges. |
312 | + c.Assert(transport.ExchangeCount, Equals, 2) |
313 | + // The first request is a Put Block with the block data. |
314 | + assertBlockSent(c, context, data, b64("0"), transport.Exchanges[0]) |
315 | + // The second request is Put Block List to commit the block above. |
316 | + assertBlockListSent(c, context, []string{b64("0")}, transport.Exchanges[1]) |
317 | +} |
318 | + |
319 | +func (suite *testUploadBlockBlob) TestLargeFile(c *C) { |
320 | + context := makeStorageContext() |
321 | + transport := &TestTransport2{} |
322 | + // UploadBlockBlob uses PutBlock twice to upload the data. |
323 | + transport.AddExchange(makeFakeCreatedResponse(), nil) |
324 | + transport.AddExchange(makeFakeCreatedResponse(), nil) |
325 | + // UploadBlockBlob then sends the list of blocks with PutBlockList. |
326 | + transport.AddExchange(makeFakeCreatedResponse(), nil) |
327 | + // Plug in the test transport. |
328 | + context.client = &http.Client{Transport: transport} |
329 | + // Upload a large random blob of data. |
330 | + data := uploadRandomBlob(c, context, 1348*1024) |
331 | + // There were three exchanges. |
332 | + c.Assert(transport.ExchangeCount, Equals, 3) |
333 | + // The first two requests are Put Block with chunks of the block data. The |
334 | + // weird looking block IDs are base64 encodings of the strings "0" and "1". |
335 | + assertBlockSent(c, context, data[:1024*1024], b64("0"), transport.Exchanges[0]) |
336 | + assertBlockSent(c, context, data[1024*1024:], b64("1"), transport.Exchanges[1]) |
337 | + // The second request is Put Block List to commit the block above. |
338 | + assertBlockListSent(c, context, []string{b64("0"), b64("1")}, transport.Exchanges[2]) |
339 | +} |
340 | + |
341 | +func makeFakeCreatedResponse() *http.Response { |
342 | + return &http.Response{ |
343 | + Status: fmt.Sprintf("%d", http.StatusCreated), |
344 | + StatusCode: http.StatusCreated, |
345 | + Body: Empty, |
346 | + } |
347 | +} |
348 | + |
349 | +func uploadRandomBlob(c *C, context *StorageContext, size int) []byte { |
350 | + data := MakeRandomByteSlice(size) |
351 | + err := context.UploadBlockBlob( |
352 | + "MyContainer", "MyFile", bytes.NewReader(data)) |
353 | + c.Assert(err, IsNil) |
354 | + return data |
355 | +} |
356 | + |
357 | +func assertBlockSent( |
358 | + c *C, context *StorageContext, data []byte, blockID string, exchange *TestTransport2Exchange) { |
359 | + c.Check(exchange.Request.URL.String(), Equals, fmt.Sprintf( |
360 | + "http://%s.blob.core.windows.net/MyContainer/MyFile"+ |
361 | + "?comp=block&blockid=%s", |
362 | + context.Account, url.QueryEscape(blockID))) |
363 | + body, err := ioutil.ReadAll(exchange.Request.Body) |
364 | + c.Check(err, IsNil) |
365 | + c.Check(body, DeepEquals, data) |
366 | +} |
367 | + |
368 | +func assertBlockListSent( |
369 | + c *C, context *StorageContext, blockIDs []string, exchange *TestTransport2Exchange) { |
370 | + c.Check(exchange.Request.URL.String(), Equals, fmt.Sprintf( |
371 | + "http://%s.blob.core.windows.net/MyContainer/MyFile"+ |
372 | + "?comp=blocklist", context.Account)) |
373 | + body, err := ioutil.ReadAll(exchange.Request.Body) |
374 | + c.Check(err, IsNil) |
375 | + expected := "\n<BlockList>\n" |
376 | + for _, blockID := range blockIDs { |
377 | + expected += " <Latest>" + blockID + "</Latest>\n" |
378 | + } |
379 | + expected += "</BlockList>" |
380 | + c.Check(string(body), Equals, expected) |
381 | +} |
382 | |
383 | === modified file 'test_helpers.go' |
384 | --- test_helpers.go 2013-04-02 08:51:18 +0000 |
385 | +++ test_helpers.go 2013-04-22 16:24:27 +0000 |
386 | @@ -4,7 +4,10 @@ |
387 | package gwacl |
388 | |
389 | import ( |
390 | + "bytes" |
391 | "fmt" |
392 | + "io" |
393 | + "io/ioutil" |
394 | "math/rand" |
395 | "net/http" |
396 | "strings" |
397 | @@ -15,6 +18,9 @@ |
398 | // Perhaps we can add it to gocheck, or start a testtools-like project. |
399 | const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz" |
400 | |
401 | +// A Reader and ReadCloser that EOFs immediately. |
402 | +var Empty io.ReadCloser = ioutil.NopCloser(bytes.NewReader(nil)) |
403 | + |
404 | func MakeRandomString(length int) string { |
405 | return string(MakeRandomByteSlice(length)) |
406 | } |
407 | |
408 | === modified file 'x509dispatcher.go' |
409 | --- x509dispatcher.go 2013-03-26 06:50:50 +0000 |
410 | +++ x509dispatcher.go 2013-04-22 16:24:27 +0000 |
411 | @@ -5,7 +5,7 @@ |
412 | "bytes" |
413 | "fmt" |
414 | curl "github.com/andelf/go-curl" |
415 | - "log" |
416 | + . "launchpad.net/gwacl/logging" |
417 | "net/http" |
418 | "net/textproto" |
419 | ) |
420 | @@ -143,10 +143,10 @@ |
421 | |
422 | func performX509CurlRequest(session *x509Session, request *x509Request) (*x509Response, error) { |
423 | if verbose { |
424 | - log.Println("Performing request") |
425 | - log.Println("Request url: " + request.URL) |
426 | - log.Println("Request method: " + request.Method) |
427 | - log.Printf("Request body: %s\n", request.Payload) |
428 | + Debug("Performing request") |
429 | + Debug("Request url: " + request.URL) |
430 | + Debug("Request method: " + request.Method) |
431 | + Debugf("Request body: %s\n", request.Payload) |
432 | } |
433 | response := newX509Response() |
434 | ch := request.makeCurlRequest(session, response) |
435 | @@ -170,10 +170,10 @@ |
436 | } |
437 | response.StatusCode = status.(int) |
438 | if verbose { |
439 | - log.Println("Got response") |
440 | - log.Printf("Response status: %d\n", response.StatusCode) |
441 | - log.Printf("Response headers: %s\n", response.RawHeader) |
442 | - log.Printf("Response body: %s\n", response.Body) |
443 | + Debug("Got response") |
444 | + Debugf("Response status: %d\n", response.StatusCode) |
445 | + Debugf("Response headers: %s\n", response.RawHeader) |
446 | + Debugf("Response body: %s\n", response.Body) |
447 | } |
448 | return response, nil |
449 | } |
61 === added file 'storage2.go'
Can you not think of a better name for this? In fact I'm not even sure we need another file. If you insist, we could have a new file called storage_blob.go and move all the blob stuff into it.