Merge lp:~allenap/gwacl/chunky-blobs-operation into lp:gwacl

Proposed by Gavin Panella
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
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.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) 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.

Revision history for this message
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.FormatInt(blockNum, 36)

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("Uploading block %d (size=%d, id=%s).\n",
109 + log.Printf("Committing %d blocks.\n", len(blockList.Items))

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.NopCloser(bytes.NewReader(nil)),

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 *testUploadBlockBlob) TestNoHeaders(c *C) {

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]
    insertFakeResponse(status=..., body=)
    makeRandomData()
    uploadBlockBlob()
    assertPutBlockSent(url, data)
    assertBlockListSent(url, data)

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.

review: Needs Fixing
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.

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (3.5 KiB)

> [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.FormatInt(blockNum, 36)
>
> 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("Uploading block %d (size=%d, id=%s).\n",
> 109     +    log.Printf("Committing %d blocks.\n", len(blockList.Items))
>
> 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.NopCloser(bytes.NewReader(nil)),
>
> 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 *testUploadBlockBlob) TestNoHeaders(c *C) {
>
> 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]
>     insertFakeResponse(status=..., body=)
>     makeRandomData()
>     uploadBlockBlob()
>     assertPutBlockSent(url, data)
>     assertBlockListSent(url, data)
>
> This way we've abstracted out the boilerplate, but the tes...

Read more...

Revision history for this message
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 makeFakeCreatedResponse().

104. By Gavin Panella

Extract methods uploadRandomBlob(), assertBlockSent(), and assertBlockListSent().

105. By Gavin Panella

Move makeFakeCreatedResponse() down.

106. By Gavin Panella

Additional test for a large file that actually needs chunking.

Revision history for this message
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.

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (3.6 KiB)

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.FormatInt(blockNum, 36)
>>
>> 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("Uploading block %d (size=%d, id=%s).\n",
>> 109 + log.Printf("Committing %d blocks.\n", len(blockList.Items))
>>
>> 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.NopCloser(bytes.NewReader(nil)),
>>
>> 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...

Read more...

Revision history for this message
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.

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

+ // block ID is a base64 encoding of the string "0".
+ assertBlockSent(c, context, data, "MA==", transport.Exchanges[0])

You could use:
base64.StdEncoding.EncodeToString([]byte("0"))
like we do in the rest of the gwacl tests.

Everything else looks great, thanks.

review: Approve
Revision history for this message
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.

Revision history for this message
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

[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-17 10:11:07 +0000
+++ example/storage/run.go 2013-04-22 16:24:27 +0000
@@ -10,15 +10,11 @@
10package main10package main
1111
12import (12import (
13 "bytes"
14 "flag"13 "flag"
15 "fmt"14 "fmt"
16 "io"
17 "io/ioutil"15 "io/ioutil"
18 "launchpad.net/gwacl"16 "launchpad.net/gwacl"
19 "log"
20 "os"17 "os"
21 "strconv"
22)18)
2319
24var account string20var account string
@@ -125,35 +121,11 @@
125 }121 }
126 defer file.Close()122 defer file.Close()
127123
128 buffer := make([]byte, 1024*1024) // 1MB buffer124 err = storage.UploadBlockBlob(container, filename, file)
129 blockList := &gwacl.BlockList{}125 if err != nil {
130126 dumpError(err)
131 // Upload the file in chunks.127 return
132 for blockNum := int64(0); ; blockNum++ {
133 blockSize, err := file.Read(buffer)
134 if err == io.EOF {
135 break
136 }
137 if err != nil {
138 dumpError(err)
139 return
140 }
141 block := bytes.NewReader(buffer[:blockSize])
142 blockID := strconv.FormatInt(blockNum, 36)
143 log.Printf("Uploading block %d (size=%d, id=%s).\n",
144 blockNum, blockSize, blockID)
145 err = storage.PutBlock(container, filename, blockID, block)
146 if err != nil {
147 dumpError(err)
148 return
149 }
150 blockList.Add(gwacl.BlockListLatest, blockID)
151 }128 }
152
153 // Commit those blocks by writing the block list.
154 log.Printf("Committing %d blocks.\n", len(blockList.Items))
155 err = storage.PutBlockList(container, filename, blockList)
156 dumpError(err)
157}129}
158130
159func deleteblob(storage gwacl.StorageContext) {131func deleteblob(storage gwacl.StorageContext) {
160132
=== added directory 'logging'
=== added file 'logging/logging.go'
--- logging/logging.go 1970-01-01 00:00:00 +0000
+++ logging/logging.go 2013-04-22 16:24:27 +0000
@@ -0,0 +1,83 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package logging
5
6import (
7 "log"
8 "os"
9)
10
11const (
12 DEBUG = 10 * (iota + 1)
13 INFO
14 WARN
15 ERROR
16)
17
18var level = WARN
19
20func init() {
21 setLevel(os.Getenv("LOGLEVEL"))
22}
23
24func setLevel(levelName string) {
25 switch levelName {
26 case "DEBUG":
27 level = DEBUG
28 case "INFO":
29 level = INFO
30 case "WARN":
31 level = WARN
32 case "ERROR":
33 level = ERROR
34 }
35}
36
37func Debug(args ...interface{}) {
38 if level <= DEBUG {
39 log.Println(args...)
40 }
41}
42
43func Debugf(format string, args ...interface{}) {
44 if level <= DEBUG {
45 log.Printf(format, args...)
46 }
47}
48
49func Info(args ...interface{}) {
50 if level <= INFO {
51 log.Println(args...)
52 }
53}
54
55func Infof(format string, args ...interface{}) {
56 if level <= INFO {
57 log.Printf(format, args...)
58 }
59}
60
61func Warn(args ...interface{}) {
62 if level <= WARN {
63 log.Println(args...)
64 }
65}
66
67func Warnf(format string, args ...interface{}) {
68 if level <= WARN {
69 log.Printf(format, args...)
70 }
71}
72
73func Error(args ...interface{}) {
74 if level <= ERROR {
75 log.Println(args...)
76 }
77}
78
79func Errorf(format string, args ...interface{}) {
80 if level <= ERROR {
81 log.Printf(format, args...)
82 }
83}
084
=== added file 'logging/logging_test.go'
--- logging/logging_test.go 1970-01-01 00:00:00 +0000
+++ logging/logging_test.go 2013-04-22 16:24:27 +0000
@@ -0,0 +1,39 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package logging
5
6import (
7 . "launchpad.net/gocheck"
8)
9
10var originalLevel = level
11
12func restoreLevel() {
13 level = originalLevel
14}
15
16type testLogging struct{}
17
18var _ = Suite(&testLogging{})
19
20func (suite *testLogging) TestSetLevel(c *C) {
21 defer restoreLevel()
22 // The names of the logging constants are recognised arguments to
23 // setLevel().
24 level = -1
25 setLevel("DEBUG")
26 c.Check(level, Equals, DEBUG)
27 setLevel("INFO")
28 c.Check(level, Equals, INFO)
29 setLevel("WARN")
30 c.Check(level, Equals, WARN)
31 setLevel("ERROR")
32 c.Check(level, Equals, ERROR)
33 // Unrecognised arguments are ignored.
34 level = -1
35 setLevel("FOOBAR")
36 c.Check(level, Equals, -1)
37 setLevel("123")
38 c.Check(level, Equals, -1)
39}
040
=== added file 'storage.go'
--- storage.go 1970-01-01 00:00:00 +0000
+++ storage.go 2013-04-22 16:24:27 +0000
@@ -0,0 +1,47 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package gwacl
5
6// This file contains higher level operations necessary to work with the Azure
7// file storage API.
8
9import (
10 "bytes"
11 "io"
12 . "launchpad.net/gwacl/logging"
13 "strconv"
14)
15
16// UploadBlockBlob uses PutBlock and PutBlockList API operations to upload
17// arbitrarily large files, 1MB at a time.
18func (context *StorageContext) UploadBlockBlob(
19 container, filename string, data io.Reader) error {
20
21 buffer := make([]byte, 1024*1024) // 1MB buffer
22 blockList := &BlockList{}
23
24 // Upload the file in chunks.
25 for blockNum := int64(0); ; blockNum++ {
26 blockSize, err := data.Read(buffer)
27 if err == io.EOF {
28 break
29 }
30 if err != nil {
31 return err
32 }
33 block := bytes.NewReader(buffer[:blockSize])
34 blockID := strconv.FormatInt(blockNum, 36)
35 Debugf("Uploading block %d (size=%d, id=%s).\n",
36 blockNum, blockSize, blockID)
37 err = context.PutBlock(container, filename, blockID, block)
38 if err != nil {
39 return err
40 }
41 blockList.Add(BlockListLatest, blockID)
42 }
43
44 // Commit those blocks by writing the block list.
45 Debugf("Committing %d blocks.\n", len(blockList.Items))
46 return context.PutBlockList(container, filename, blockList)
47}
048
=== renamed file 'storage.go' => 'storage_base.go'
=== renamed file 'storage_test.go' => 'storage_base_test.go'
=== added file 'storage_test.go'
--- storage_test.go 1970-01-01 00:00:00 +0000
+++ storage_test.go 2013-04-22 16:24:27 +0000
@@ -0,0 +1,130 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package gwacl
5
6import (
7 "bytes"
8 "encoding/base64"
9 "fmt"
10 "io/ioutil"
11 . "launchpad.net/gocheck"
12 "net/http"
13 "net/url"
14)
15
16func b64(s string) string {
17 return base64.StdEncoding.EncodeToString([]byte(s))
18}
19
20type TestTransport2Exchange struct {
21 Request *http.Request
22 Response *http.Response
23 Error error
24}
25
26// TestTransport2 is used as an http.Client.Transport for testing. The only
27// requirement is that it adhere to the http.RoundTripper interface.
28type TestTransport2 struct {
29 Exchanges []*TestTransport2Exchange
30 ExchangeCount int
31}
32
33func (t *TestTransport2) AddExchange(response *http.Response, error error) {
34 exchange := TestTransport2Exchange{Response: response, Error: error}
35 t.Exchanges = append(t.Exchanges, &exchange)
36}
37
38func (t *TestTransport2) RoundTrip(req *http.Request) (resp *http.Response, err error) {
39 exchange := t.Exchanges[t.ExchangeCount]
40 t.ExchangeCount += 1
41 exchange.Request = req
42 return exchange.Response, exchange.Error
43}
44
45type testUploadBlockBlob struct{}
46
47var _ = Suite(&testUploadBlockBlob{})
48
49func (suite *testUploadBlockBlob) TestSmallFile(c *C) {
50 context := makeStorageContext()
51 transport := &TestTransport2{}
52 // UploadBlockBlob uses PutBlock to upload the data.
53 transport.AddExchange(makeFakeCreatedResponse(), nil)
54 // UploadBlockBlob then sends the list of blocks with PutBlockList.
55 transport.AddExchange(makeFakeCreatedResponse(), nil)
56 // Plug in the test transport.
57 context.client = &http.Client{Transport: transport}
58 // Upload a random blob of data.
59 data := uploadRandomBlob(c, context, 10)
60 // There were two exchanges.
61 c.Assert(transport.ExchangeCount, Equals, 2)
62 // The first request is a Put Block with the block data.
63 assertBlockSent(c, context, data, b64("0"), transport.Exchanges[0])
64 // The second request is Put Block List to commit the block above.
65 assertBlockListSent(c, context, []string{b64("0")}, transport.Exchanges[1])
66}
67
68func (suite *testUploadBlockBlob) TestLargeFile(c *C) {
69 context := makeStorageContext()
70 transport := &TestTransport2{}
71 // UploadBlockBlob uses PutBlock twice to upload the data.
72 transport.AddExchange(makeFakeCreatedResponse(), nil)
73 transport.AddExchange(makeFakeCreatedResponse(), nil)
74 // UploadBlockBlob then sends the list of blocks with PutBlockList.
75 transport.AddExchange(makeFakeCreatedResponse(), nil)
76 // Plug in the test transport.
77 context.client = &http.Client{Transport: transport}
78 // Upload a large random blob of data.
79 data := uploadRandomBlob(c, context, 1348*1024)
80 // There were three exchanges.
81 c.Assert(transport.ExchangeCount, Equals, 3)
82 // The first two requests are Put Block with chunks of the block data. The
83 // weird looking block IDs are base64 encodings of the strings "0" and "1".
84 assertBlockSent(c, context, data[:1024*1024], b64("0"), transport.Exchanges[0])
85 assertBlockSent(c, context, data[1024*1024:], b64("1"), transport.Exchanges[1])
86 // The second request is Put Block List to commit the block above.
87 assertBlockListSent(c, context, []string{b64("0"), b64("1")}, transport.Exchanges[2])
88}
89
90func makeFakeCreatedResponse() *http.Response {
91 return &http.Response{
92 Status: fmt.Sprintf("%d", http.StatusCreated),
93 StatusCode: http.StatusCreated,
94 Body: Empty,
95 }
96}
97
98func uploadRandomBlob(c *C, context *StorageContext, size int) []byte {
99 data := MakeRandomByteSlice(size)
100 err := context.UploadBlockBlob(
101 "MyContainer", "MyFile", bytes.NewReader(data))
102 c.Assert(err, IsNil)
103 return data
104}
105
106func assertBlockSent(
107 c *C, context *StorageContext, data []byte, blockID string, exchange *TestTransport2Exchange) {
108 c.Check(exchange.Request.URL.String(), Equals, fmt.Sprintf(
109 "http://%s.blob.core.windows.net/MyContainer/MyFile"+
110 "?comp=block&blockid=%s",
111 context.Account, url.QueryEscape(blockID)))
112 body, err := ioutil.ReadAll(exchange.Request.Body)
113 c.Check(err, IsNil)
114 c.Check(body, DeepEquals, data)
115}
116
117func assertBlockListSent(
118 c *C, context *StorageContext, blockIDs []string, exchange *TestTransport2Exchange) {
119 c.Check(exchange.Request.URL.String(), Equals, fmt.Sprintf(
120 "http://%s.blob.core.windows.net/MyContainer/MyFile"+
121 "?comp=blocklist", context.Account))
122 body, err := ioutil.ReadAll(exchange.Request.Body)
123 c.Check(err, IsNil)
124 expected := "\n<BlockList>\n"
125 for _, blockID := range blockIDs {
126 expected += " <Latest>" + blockID + "</Latest>\n"
127 }
128 expected += "</BlockList>"
129 c.Check(string(body), Equals, expected)
130}
0131
=== modified file 'test_helpers.go'
--- test_helpers.go 2013-04-02 08:51:18 +0000
+++ test_helpers.go 2013-04-22 16:24:27 +0000
@@ -4,7 +4,10 @@
4package gwacl4package gwacl
55
6import (6import (
7 "bytes"
7 "fmt"8 "fmt"
9 "io"
10 "io/ioutil"
8 "math/rand"11 "math/rand"
9 "net/http"12 "net/http"
10 "strings"13 "strings"
@@ -15,6 +18,9 @@
15// Perhaps we can add it to gocheck, or start a testtools-like project.18// Perhaps we can add it to gocheck, or start a testtools-like project.
16const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz"19const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz"
1720
21// A Reader and ReadCloser that EOFs immediately.
22var Empty io.ReadCloser = ioutil.NopCloser(bytes.NewReader(nil))
23
18func MakeRandomString(length int) string {24func MakeRandomString(length int) string {
19 return string(MakeRandomByteSlice(length))25 return string(MakeRandomByteSlice(length))
20}26}
2127
=== modified file 'x509dispatcher.go'
--- x509dispatcher.go 2013-03-26 06:50:50 +0000
+++ x509dispatcher.go 2013-04-22 16:24:27 +0000
@@ -5,7 +5,7 @@
5 "bytes"5 "bytes"
6 "fmt"6 "fmt"
7 curl "github.com/andelf/go-curl"7 curl "github.com/andelf/go-curl"
8 "log"8 . "launchpad.net/gwacl/logging"
9 "net/http"9 "net/http"
10 "net/textproto"10 "net/textproto"
11)11)
@@ -143,10 +143,10 @@
143143
144func performX509CurlRequest(session *x509Session, request *x509Request) (*x509Response, error) {144func performX509CurlRequest(session *x509Session, request *x509Request) (*x509Response, error) {
145 if verbose {145 if verbose {
146 log.Println("Performing request")146 Debug("Performing request")
147 log.Println("Request url: " + request.URL)147 Debug("Request url: " + request.URL)
148 log.Println("Request method: " + request.Method)148 Debug("Request method: " + request.Method)
149 log.Printf("Request body: %s\n", request.Payload)149 Debugf("Request body: %s\n", request.Payload)
150 }150 }
151 response := newX509Response()151 response := newX509Response()
152 ch := request.makeCurlRequest(session, response)152 ch := request.makeCurlRequest(session, response)
@@ -170,10 +170,10 @@
170 }170 }
171 response.StatusCode = status.(int)171 response.StatusCode = status.(int)
172 if verbose {172 if verbose {
173 log.Println("Got response")173 Debug("Got response")
174 log.Printf("Response status: %d\n", response.StatusCode)174 Debugf("Response status: %d\n", response.StatusCode)
175 log.Printf("Response headers: %s\n", response.RawHeader)175 Debugf("Response headers: %s\n", response.RawHeader)
176 log.Printf("Response body: %s\n", response.Body)176 Debugf("Response body: %s\n", response.Body)
177 }177 }
178 return response, nil178 return response, nil
179}179}

Subscribers

People subscribed via source and target branches

to all changes: