Merge lp:~axwalk/juju-core/localstorage-to-httpstorage into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 1832
Proposed branch: lp:~axwalk/juju-core/localstorage-to-httpstorage
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 953 lines (+211/-212)
12 files modified
environs/bootstrap/bootstrap_test.go (+8/-15)
environs/filestorage/filestorage.go (+31/-19)
environs/filestorage/filestorage_test.go (+45/-11)
environs/httpstorage/backend.go (+36/-76)
environs/httpstorage/backend_test.go (+6/-3)
environs/httpstorage/storage.go (+2/-1)
environs/httpstorage/storage_test.go (+5/-5)
environs/testing/storage.go (+7/-4)
provider/azure/environ_test.go (+33/-49)
provider/local/environ.go (+9/-4)
provider/local/storage/worker.go (+14/-3)
provider/state_test.go (+15/-22)
To merge this branch: bzr merge lp:~axwalk/juju-core/localstorage-to-httpstorage
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+185958@code.launchpad.net

Commit message

environs/localstorage -> environs/httpstorage

localstorage becomes httpstorage; it no longer
operates on the disk, but instead delegates to
another environs.Storage object.

This work goes together with changes to
environs/filestorage, which will be modified to
write to temporary storage and atomically move,
in the same way that sshstorage has been coded.

https://codereview.appspot.com/13736043/

Description of the change

environs/localstorage -> environs/httpstorage

localstorage becomes httpstorage; it no longer
operates on the disk, but instead delegates to
another environs.Storage object.

This work goes together with changes to
environs/filestorage, which will be modified to
write to temporary storage and atomically move,
in the same way that sshstorage has been coded.

https://codereview.appspot.com/13736043/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+185958_code.launchpad.net,

Message:
Please take a look.

Description:
environs/localstorage -> environs/httpstorage

localstorage becomes httpstorage; it no longer
operates on the disk, but instead delegates to
another environs.Storage object.

This work goes together with changes to
environs/filestorage, which will be modified to
write to temporary storage and atomically move,
in the same way that sshstorage has been coded.

https://code.launchpad.net/~axwalk/juju-core/localstorage-to-httpstorage/+merge/185958

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/13736043/

Affected files (+129, -133 lines):
   A [revision details]
   M cmd/jujud/machine.go
   M environs/bootstrap/bootstrap_test.go
   M environs/filestorage/filestorage.go
   M environs/httpstorage/backend.go
   M environs/httpstorage/backend_test.go
   M environs/httpstorage/storage.go
   M environs/httpstorage/storage_test.go
   M environs/testing/storage.go
   M provider/azure/environ_test.go
   M provider/local/environ.go
   M provider/local/storage/worker.go
   M provider/state_test.go

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

A direct test for the glob change, and a helper function to create the
storage are needed.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go
File environs/httpstorage/backend.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode54
environs/httpstorage/backend.go:54: defer readcloser.Close()
Why defer? There is no harm in calling Close right now.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode75
environs/httpstorage/backend.go:75: if req.ContentLength <= -1 {
isn't " < 0 " more readable and understandable?

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend_test.go
File environs/httpstorage/backend_test.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend_test.go#newcode109
environs/httpstorage/backend_test.go:109: // Get on a directory returns
a 500 as it is
I argue with this status code.

5xx means it is our fault. 4xx means it is your fault. This is
certainly a bad request.

https://codereview.appspot.com/13736043/diff/4001/environs/testing/storage.go
File environs/testing/storage.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/testing/storage.go#newcode33
environs/testing/storage.go:33: listener, err :=
httpstorage.Serve("localhost:0", underlying)
Given this is the second or third time I have seen these lines of code,
perhaps a testing helper function would be good.

https://codereview.appspot.com/13736043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go
File environs/httpstorage/backend.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode54
environs/httpstorage/backend.go:54: defer readcloser.Close()
On 2013/09/18 04:26:28, thumper wrote:
> Why defer? There is no harm in calling Close right now.

Indeed. I'll keep the defer, but move it closer to Get/before ReadAll.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode75
environs/httpstorage/backend.go:75: if req.ContentLength <= -1 {
On 2013/09/18 04:26:28, thumper wrote:
> isn't " < 0 " more readable and understandable?

Done.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend_test.go
File environs/httpstorage/backend_test.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend_test.go#newcode109
environs/httpstorage/backend_test.go:109: // Get on a directory returns
a 500 as it is
On 2013/09/18 04:26:28, thumper wrote:
> I argue with this status code.

> 5xx means it is our fault. 4xx means it is your fault. This is
certainly a bad
> request.

Yeah, I'll change the implementation (of filestorage's Get) and put this
back. Thanks.

https://codereview.appspot.com/13736043/diff/4001/environs/testing/storage.go
File environs/testing/storage.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/testing/storage.go#newcode33
environs/testing/storage.go:33: listener, err :=
httpstorage.Serve("localhost:0", underlying)
On 2013/09/18 04:26:28, thumper wrote:
> Given this is the second or third time I have seen these lines of
code, perhaps
> a testing helper function would be good.

This is a testing helper function ;)
I will modify the other code to use it where it makes sense.

https://codereview.appspot.com/13736043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

LGTM - although I'd prefer with the added testing helper method, this
isn't essential.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go
File environs/httpstorage/backend.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode54
environs/httpstorage/backend.go:54: defer readcloser.Close()
On 2013/09/18 05:33:28, axw1 wrote:
> On 2013/09/18 04:26:28, thumper wrote:
> > Why defer? There is no harm in calling Close right now.

> Indeed. I'll keep the defer, but move it closer to Get/before ReadAll.

Good call. I missed the err just above.

https://codereview.appspot.com/13736043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2013/09/18 05:43:00, thumper wrote:
> LGTM - although I'd prefer with the added testing helper method, this
isn't
> essential.

Done. I've also updated the code to use AddCleanup.

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go
> File environs/httpstorage/backend.go (right):

https://codereview.appspot.com/13736043/diff/4001/environs/httpstorage/backend.go#newcode54
> environs/httpstorage/backend.go:54: defer readcloser.Close()
> On 2013/09/18 05:33:28, axw1 wrote:
> > On 2013/09/18 04:26:28, thumper wrote:
> > > Why defer? There is no harm in calling Close right now.
> >
> > Indeed. I'll keep the defer, but move it closer to Get/before
ReadAll.

> Good call. I missed the err just above.

https://codereview.appspot.com/13736043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Go Bot (go-bot) wrote :
Download full text (7.8 KiB)

The attempt to merge lp:~axwalk/juju-core/localstorage-to-httpstorage into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 0.940s
ok launchpad.net/juju-core/agent/tools 0.304s
ok launchpad.net/juju-core/bzr 7.841s
ok launchpad.net/juju-core/cert 2.141s
ok launchpad.net/juju-core/charm 0.601s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.022s
ok launchpad.net/juju-core/cmd 0.207s
? launchpad.net/juju-core/cmd/builddb [no test files]
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 138.797s
ok launchpad.net/juju-core/cmd/jujud 40.019s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 1.869s
ok launchpad.net/juju-core/constraints 0.024s
ok launchpad.net/juju-core/container/lxc 0.394s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.282s
ok launchpad.net/juju-core/environs 1.976s
ok launchpad.net/juju-core/environs/bootstrap 5.172s
ok launchpad.net/juju-core/environs/cloudinit 0.451s
ok launchpad.net/juju-core/environs/config 0.796s
ok launchpad.net/juju-core/environs/configstore 0.066s
ok launchpad.net/juju-core/environs/filestorage 0.061s
ok launchpad.net/juju-core/environs/httpstorage 0.245s
ok launchpad.net/juju-core/environs/imagemetadata 0.535s
ok launchpad.net/juju-core/environs/instances 0.256s
ok launchpad.net/juju-core/environs/jujutest 0.226s
ok launchpad.net/juju-core/environs/manual 1.173s
ok launchpad.net/juju-core/environs/simplestreams 0.256s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 0.744s
ok launchpad.net/juju-core/environs/sync 26.682s
ok launchpad.net/juju-core/environs/testing 0.011s
ok launchpad.net/juju-core/environs/tools 10.007s
? launchpad.net/juju-core/environs/tools/testing [no test files]
? launchpad.net/juju-core/errors [no test files]
ok launchpad.net/juju-core/instance 0.020s
ok launchpad.net/juju-core/juju 17.497s
ok launchpad.net/juju-core/juju/osenv 0.219s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.015s
ok launchpad.net/juju-core/log/syslog 0.023s
ok launchpad.net/juju-core/names 0.037s
ok launchpad.net/juju-core/provider 0.259s
? launchpad.net/juju-core/provider/all [no test files]
ok launchpad.net/juju-core/provider/azure 6.380s
ok launchpad.net/juju-core/provider/dummy 20.464s
ok launchpad.net/juju-core/provider/ec2 5.396s
ok launchpad.net/juju-core/provider/ec2/httpstorage 0.049s
ok launchpad.net/juju-core/provider/local 1.800s
? launchpad.net/juju-core/provider/local/storage [no test files]
ok launchpad.net/juju-core/provider/maas 3.655s
ok launchpad.net/juju-core/provider/openstack 8.236s
ok launchpad.net/juju-core/rpc 0.264s
ok launchpad.net/juju-core/rpc/jsoncodec 0.270s
ok launchpad.net/juju-core/schema 0.018s

----------------------------------------------------------------------
PANIC: service_test.go:500: ServiceS...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/bootstrap/bootstrap_test.go'
2--- environs/bootstrap/bootstrap_test.go 2013-09-12 12:38:04 +0000
3+++ environs/bootstrap/bootstrap_test.go 2013-09-18 06:28:38 +0000
4@@ -13,7 +13,6 @@
5 "launchpad.net/juju-core/environs"
6 "launchpad.net/juju-core/environs/bootstrap"
7 "launchpad.net/juju-core/environs/config"
8- "launchpad.net/juju-core/environs/localstorage"
9 envtesting "launchpad.net/juju-core/environs/testing"
10 "launchpad.net/juju-core/provider/dummy"
11 coretesting "launchpad.net/juju-core/testing"
12@@ -52,8 +51,7 @@
13
14 func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) {
15 env := newEnviron("bar", noKeysDefined)
16- cleanup := setDummyStorage(c, env)
17- defer cleanup()
18+ s.setDummyStorage(c, env)
19 fixEnv := func(key string, value interface{}) {
20 cfg, err := env.Config().Apply(map[string]interface{}{
21 key: value,
22@@ -87,8 +85,7 @@
23
24 func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) {
25 env := newEnviron("foo", useDefaultKeys)
26- cleanup := setDummyStorage(c, env)
27- defer cleanup()
28+ s.setDummyStorage(c, env)
29 uploadTools(c, env)
30 err := bootstrap.Bootstrap(env, constraints.Value{})
31 c.Assert(err, gc.IsNil)
32@@ -98,8 +95,7 @@
33
34 func (s *bootstrapSuite) TestBootstrapSpecifiedConstraints(c *gc.C) {
35 env := newEnviron("foo", useDefaultKeys)
36- cleanup := setDummyStorage(c, env)
37- defer cleanup()
38+ s.setDummyStorage(c, env)
39 uploadTools(c, env)
40 cons := constraints.MustParse("cpu-cores=2 mem=4G")
41 err := bootstrap.Bootstrap(env, cons)
42@@ -188,8 +184,7 @@
43
44 func (s *bootstrapSuite) TestBootstrapNeedsTools(c *gc.C) {
45 env := newEnviron("foo", useDefaultKeys)
46- cleanup := setDummyStorage(c, env)
47- defer cleanup()
48+ s.setDummyStorage(c, env)
49 envtesting.RemoveFakeTools(c, env.Storage())
50 err := bootstrap.Bootstrap(env, constraints.Value{})
51 c.Check(err, gc.ErrorMatches, "cannot find bootstrap tools: no tools available")
52@@ -228,13 +223,11 @@
53 // setDummyStorage injects the local provider's fake storage implementation
54 // into the given environment, so that tests can manipulate storage as if it
55 // were real.
56-// Returns a cleanup function that must be called when done with the storage.
57-func setDummyStorage(c *gc.C, env *bootstrapEnviron) func() {
58- listener, err := localstorage.Serve("127.0.0.1:0", c.MkDir())
59- c.Assert(err, gc.IsNil)
60- env.storage = localstorage.Client(listener.Addr().String())
61+func (s *bootstrapSuite) setDummyStorage(c *gc.C, env *bootstrapEnviron) {
62+ closer, storage, _ := envtesting.CreateLocalTestStorage(c)
63+ env.storage = storage
64 envtesting.UploadFakeTools(c, env.storage)
65- return func() { listener.Close() }
66+ s.AddCleanup(func(c *gc.C) { closer.Close() })
67 }
68
69 func (e *bootstrapEnviron) Name() string {
70
71=== modified file 'environs/filestorage/filestorage.go'
72--- environs/filestorage/filestorage.go 2013-09-12 05:04:40 +0000
73+++ environs/filestorage/filestorage.go 2013-09-18 06:28:38 +0000
74@@ -10,8 +10,10 @@
75 "os"
76 "path/filepath"
77 "sort"
78+ "strings"
79
80 "launchpad.net/juju-core/environs"
81+ coreerrors "launchpad.net/juju-core/errors"
82 "launchpad.net/juju-core/utils"
83 )
84
85@@ -42,6 +44,15 @@
86 // Get implements environs.StorageReader.Get.
87 func (f *fileStorageReader) Get(name string) (io.ReadCloser, error) {
88 filename := f.fullPath(name)
89+ fi, err := os.Stat(filename)
90+ if err != nil {
91+ if os.IsNotExist(err) {
92+ err = coreerrors.NewNotFoundError(err, "")
93+ }
94+ return nil, err
95+ } else if fi.IsDir() {
96+ return nil, coreerrors.NotFoundf("no such file with name %q", name)
97+ }
98 file, err := os.Open(filename)
99 if err != nil {
100 return nil, err
101@@ -51,26 +62,23 @@
102
103 // List implements environs.StorageReader.List.
104 func (f *fileStorageReader) List(prefix string) ([]string, error) {
105- // Add one for the missing path separator.
106- pathlen := len(f.path) + 1
107- pattern := filepath.Join(f.path, prefix+"*")
108- matches, err := filepath.Glob(pattern)
109- if err != nil {
110- return nil, err
111- }
112- list := []string{}
113- for _, match := range matches {
114- fi, err := os.Stat(match)
115+ prefix = filepath.Join(f.path, prefix)
116+ dir := filepath.Dir(prefix)
117+ var names []string
118+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
119 if err != nil {
120- return nil, err
121- }
122- if !fi.Mode().IsDir() {
123- filename := match[pathlen:]
124- list = append(list, filename)
125- }
126+ return err
127+ }
128+ if !info.IsDir() && strings.HasPrefix(path, prefix) {
129+ names = append(names, path[len(f.path)+1:])
130+ }
131+ return nil
132+ })
133+ if err != nil && !os.IsNotExist(err) {
134+ return nil, err
135 }
136- sort.Strings(list)
137- return list, nil
138+ sort.Strings(names)
139+ return names, nil
140 }
141
142 // URL implements environs.StorageReader.URL.
143@@ -110,7 +118,11 @@
144
145 func (f *fileStorageWriter) Remove(name string) error {
146 fullpath := f.fullPath(name)
147- return os.Remove(fullpath)
148+ err := os.Remove(fullpath)
149+ if os.IsNotExist(err) {
150+ err = nil
151+ }
152+ return err
153 }
154
155 func (f *fileStorageWriter) RemoveAll() error {
156
157=== modified file 'environs/filestorage/filestorage_test.go'
158--- environs/filestorage/filestorage_test.go 2013-09-12 05:04:40 +0000
159+++ environs/filestorage/filestorage_test.go 2013-09-18 06:28:38 +0000
160@@ -10,6 +10,7 @@
161 import (
162 "bytes"
163 "io/ioutil"
164+ "os"
165 "path/filepath"
166 "testing"
167
168@@ -17,6 +18,8 @@
169
170 "launchpad.net/juju-core/environs"
171 "launchpad.net/juju-core/environs/filestorage"
172+ coreerrors "launchpad.net/juju-core/errors"
173+ jc "launchpad.net/juju-core/testing/checkers"
174 )
175
176 func TestPackage(t *testing.T) {
177@@ -40,8 +43,10 @@
178 c.Assert(err, gc.IsNil)
179 }
180
181-func (s *filestorageSuite) createFile(c *gc.C) (fullpath string, data []byte) {
182- fullpath = filepath.Join(s.dir, "test-file")
183+func (s *filestorageSuite) createFile(c *gc.C, name string) (fullpath string, data []byte) {
184+ fullpath = filepath.Join(s.dir, name)
185+ dir := filepath.Dir(fullpath)
186+ c.Assert(os.MkdirAll(dir, 0755), gc.IsNil)
187 data = []byte{1, 2, 3, 4, 5}
188 err := ioutil.WriteFile(fullpath, data, 0644)
189 c.Assert(err, gc.IsNil)
190@@ -49,15 +54,35 @@
191 }
192
193 func (s *filestorageSuite) TestList(c *gc.C) {
194- expectedpath, _ := s.createFile(c)
195- files, err := s.reader.List("test-")
196- c.Assert(err, gc.IsNil)
197- _, file := filepath.Split(expectedpath)
198- c.Assert(files, gc.DeepEquals, []string{file})
199+ names := []string{
200+ "a/b/c",
201+ "a/bb",
202+ "a/c",
203+ "aa",
204+ "b/c/d",
205+ }
206+ for _, name := range names {
207+ s.createFile(c, name)
208+ }
209+ type test struct {
210+ prefix string
211+ expected []string
212+ }
213+ for i, test := range []test{
214+ {"a", []string{"a/b/c", "a/bb", "a/c", "aa"}},
215+ {"a/b", []string{"a/b/c", "a/bb"}},
216+ {"a/b/c", []string{"a/b/c"}},
217+ {"", names},
218+ } {
219+ c.Logf("test %d: prefix=%q", i, test.prefix)
220+ files, err := s.reader.List(test.prefix)
221+ c.Assert(err, gc.IsNil)
222+ c.Assert(files, gc.DeepEquals, test.expected)
223+ }
224 }
225
226 func (s *filestorageSuite) TestURL(c *gc.C) {
227- expectedpath, _ := s.createFile(c)
228+ expectedpath, _ := s.createFile(c, "test-file")
229 _, file := filepath.Split(expectedpath)
230 url, err := s.reader.URL(file)
231 c.Assert(err, gc.IsNil)
232@@ -65,7 +90,7 @@
233 }
234
235 func (s *filestorageSuite) TestGet(c *gc.C) {
236- expectedpath, data := s.createFile(c)
237+ expectedpath, data := s.createFile(c, "test-file")
238 _, file := filepath.Split(expectedpath)
239 rc, err := s.reader.Get(file)
240 c.Assert(err, gc.IsNil)
241@@ -74,6 +99,15 @@
242 b, err := ioutil.ReadAll(rc)
243 c.Assert(err, gc.IsNil)
244 c.Assert(b, gc.DeepEquals, data)
245+
246+ // Get on a non-existant path returns NotFoundError
247+ _, err = s.reader.Get("nowhere")
248+ c.Assert(err, jc.Satisfies, coreerrors.IsNotFoundError)
249+
250+ // Get on a directory returns NotFoundError
251+ s.createFile(c, "dir/file")
252+ _, err = s.reader.Get("dir")
253+ c.Assert(err, jc.Satisfies, coreerrors.IsNotFoundError)
254 }
255
256 func (s *filestorageSuite) TestPut(c *gc.C) {
257@@ -86,7 +120,7 @@
258 }
259
260 func (s *filestorageSuite) TestRemove(c *gc.C) {
261- expectedpath, _ := s.createFile(c)
262+ expectedpath, _ := s.createFile(c, "test-file")
263 _, file := filepath.Split(expectedpath)
264 err := s.writer.Remove(file)
265 c.Assert(err, gc.IsNil)
266@@ -95,7 +129,7 @@
267 }
268
269 func (s *filestorageSuite) TestRemoveAll(c *gc.C) {
270- expectedpath, _ := s.createFile(c)
271+ expectedpath, _ := s.createFile(c, "test-file")
272 err := s.writer.RemoveAll()
273 c.Assert(err, gc.IsNil)
274 _, err = ioutil.ReadFile(expectedpath)
275
276=== renamed directory 'environs/localstorage' => 'environs/httpstorage'
277=== modified file 'environs/httpstorage/backend.go'
278--- environs/localstorage/backend.go 2013-07-03 00:15:19 +0000
279+++ environs/httpstorage/backend.go 2013-09-18 06:28:38 +0000
280@@ -1,26 +1,24 @@
281 // Copyright 2013 Canonical Ltd.
282 // Licensed under the AGPLv3, see LICENCE file for details.
283
284-package localstorage
285+package httpstorage
286
287 import (
288 "fmt"
289- "io"
290 "io/ioutil"
291 "net"
292 "net/http"
293- "os"
294- "path/filepath"
295- "sort"
296 "strings"
297+
298+ "launchpad.net/juju-core/environs"
299 )
300
301-// storageBackend provides HTTP access to a defined path. The local
302-// provider otimally would use a much simpler Storage, but this
303-// code may be useful in storage-free environs. Here it requires
304-// additional authentication work before it's viable.
305+// TODO(axw) 2013-09-16 bug #1225916
306+// Implement authentication for modifying storage.
307+
308+// storageBackend provides HTTP access to a storage object.
309 type storageBackend struct {
310- dir string
311+ backend environs.Storage
312 }
313
314 // ServeHTTP handles the HTTP requests to the container.
315@@ -43,9 +41,15 @@
316
317 // handleGet returns a storage file to the client.
318 func (s *storageBackend) handleGet(w http.ResponseWriter, req *http.Request) {
319- data, err := ioutil.ReadFile(filepath.Join(s.dir, req.URL.Path))
320- if err != nil {
321- http.Error(w, fmt.Sprintf("404 %v", err), http.StatusNotFound)
322+ readcloser, err := s.backend.Get(req.URL.Path[1:])
323+ if err != nil {
324+ http.Error(w, fmt.Sprint(err), http.StatusNotFound)
325+ return
326+ }
327+ defer readcloser.Close()
328+ data, err := ioutil.ReadAll(readcloser)
329+ if err != nil {
330+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
331 return
332 }
333 w.Header().Set("Content-Type", "application/octet-stream")
334@@ -54,62 +58,27 @@
335
336 // handleList returns the file names in the storage to the client.
337 func (s *storageBackend) handleList(w http.ResponseWriter, req *http.Request) {
338- fp := filepath.Join(s.dir, req.URL.Path)
339- dir, prefix := filepath.Split(fp)
340- names, err := readDirs(dir, prefix[:len(prefix)-1], len(s.dir)+1)
341+ prefix := req.URL.Path
342+ prefix = prefix[1 : len(prefix)-1] // drop the leading '/' and trailing '*'
343+ names, err := s.backend.List(prefix)
344 if err != nil {
345- http.Error(w, fmt.Sprintf("404 %v", err), http.StatusNotFound)
346+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
347 return
348 }
349- sort.Strings(names)
350 data := []byte(strings.Join(names, "\n"))
351 w.Header().Set("Content-Type", "application/octet-stream")
352 w.Write(data)
353 }
354
355-// readDirs reads the directory hierarchy and compares the found
356-// names with the given prefix.
357-func readDirs(dir, prefix string, start int) ([]string, error) {
358- names := []string{}
359- fis, err := ioutil.ReadDir(dir)
360- if err != nil {
361- return nil, err
362- }
363- for _, fi := range fis {
364- name := fi.Name()
365- if strings.HasPrefix(name, prefix) {
366- if fi.IsDir() {
367- dnames, err := readDirs(filepath.Join(dir, name), prefix, start)
368- if err != nil {
369- return nil, err
370- }
371- names = append(names, dnames...)
372- continue
373- }
374- fullname := filepath.Join(dir, name)[start:]
375- names = append(names, fullname)
376- }
377- }
378- return names, nil
379-}
380-
381 // handlePut stores data from the client in the storage.
382 func (s *storageBackend) handlePut(w http.ResponseWriter, req *http.Request) {
383- fp := filepath.Join(s.dir, req.URL.Path)
384- dir, _ := filepath.Split(fp)
385- err := os.MkdirAll(dir, 0777)
386- if err != nil {
387- http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
388- return
389- }
390- out, err := os.Create(fp)
391- if err != nil {
392- http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
393- return
394- }
395- defer out.Close()
396- if _, err := io.Copy(out, req.Body); err != nil {
397- http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
398+ if req.ContentLength < 0 {
399+ http.Error(w, "missing or invalid Content-Length header", http.StatusInternalServerError)
400+ return
401+ }
402+ err := s.backend.Put(req.URL.Path[1:], req.Body, req.ContentLength)
403+ if err != nil {
404+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
405 return
406 }
407 w.WriteHeader(http.StatusCreated)
408@@ -117,28 +86,19 @@
409
410 // handleDelete removes a file from the storage.
411 func (s *storageBackend) handleDelete(w http.ResponseWriter, req *http.Request) {
412- fp := filepath.Join(s.dir, req.URL.Path)
413- if err := os.Remove(fp); err != nil && !os.IsNotExist(err) {
414- http.Error(w, fmt.Sprintf("500 %v", err), http.StatusInternalServerError)
415+ err := s.backend.Remove(req.URL.Path[1:])
416+ if err != nil {
417+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
418 return
419 }
420 w.WriteHeader(http.StatusOK)
421 }
422
423-// Serve runs a storage server on the given network address, storing
424-// data under the given directory. It returns the network listener.
425-// This can then be attached to with Client.
426-func Serve(addr, dir string) (net.Listener, error) {
427- backend := &storageBackend{
428- dir: dir,
429- }
430- info, err := os.Stat(dir)
431- if err != nil {
432- return nil, fmt.Errorf("cannot stat directory: %v", err)
433- }
434- if !info.IsDir() {
435- return nil, fmt.Errorf("%q is not a directory", dir)
436- }
437+// Serve runs a storage server on the given network address, relaying
438+// requests to the given storage implementation. It returns the network
439+// listener. This can then be attached to with Client.
440+func Serve(addr string, storage environs.Storage) (net.Listener, error) {
441+ backend := &storageBackend{backend: storage}
442 listener, err := net.Listen("tcp", addr)
443 if err != nil {
444 return nil, fmt.Errorf("cannot start listener: %v", err)
445
446=== modified file 'environs/httpstorage/backend_test.go'
447--- environs/localstorage/backend_test.go 2013-09-13 14:48:13 +0000
448+++ environs/httpstorage/backend_test.go 2013-09-18 06:28:38 +0000
449@@ -1,7 +1,7 @@
450 // Copyright 2013 Canonical Ltd.
451 // Licensed under the AGPLv3, see LICENCE file for details.
452
453-package localstorage_test
454+package httpstorage_test
455
456 import (
457 "bytes"
458@@ -16,7 +16,8 @@
459
460 gc "launchpad.net/gocheck"
461
462- "launchpad.net/juju-core/environs/localstorage"
463+ "launchpad.net/juju-core/environs/filestorage"
464+ "launchpad.net/juju-core/environs/httpstorage"
465 "launchpad.net/juju-core/testing"
466 )
467
468@@ -35,7 +36,9 @@
469 // a base URL for the server and the directory path.
470 func startServer(c *gc.C) (listener net.Listener, url, dataDir string) {
471 dataDir = c.MkDir()
472- listener, err := localstorage.Serve("localhost:0", dataDir)
473+ embedded, err := filestorage.NewFileStorageWriter(dataDir)
474+ c.Assert(err, gc.IsNil)
475+ listener, err = httpstorage.Serve("localhost:0", embedded)
476 c.Assert(err, gc.IsNil)
477 return listener, fmt.Sprintf("http://%s/", listener.Addr()), dataDir
478 }
479
480=== modified file 'environs/httpstorage/storage.go'
481--- environs/localstorage/storage.go 2013-09-02 04:11:11 +0000
482+++ environs/httpstorage/storage.go 2013-09-18 06:28:38 +0000
483@@ -1,7 +1,7 @@
484 // Copyright 2013 Canonical Ltd.
485 // Licensed under the AGPLv3, see LICENCE file for details.
486
487-package localstorage
488+package httpstorage
489
490 import (
491 "fmt"
492@@ -112,6 +112,7 @@
493 return err
494 }
495 req.Header.Set("Content-Type", "application/octet-stream")
496+ req.ContentLength = length
497 resp, err := http.DefaultClient.Do(req)
498 if err != nil {
499 return err
500
501=== modified file 'environs/httpstorage/storage_test.go'
502--- environs/localstorage/storage_test.go 2013-08-29 01:46:55 +0000
503+++ environs/httpstorage/storage_test.go 2013-09-18 06:28:38 +0000
504@@ -1,7 +1,7 @@
505 // Copyright 2013 Canonical Ltd.
506 // Licensed under the AGPLv3, see LICENCE file for details.
507
508-package localstorage_test
509+package httpstorage_test
510
511 import (
512 "bytes"
513@@ -13,7 +13,7 @@
514 gc "launchpad.net/gocheck"
515
516 "launchpad.net/juju-core/environs"
517- "launchpad.net/juju-core/environs/localstorage"
518+ "launchpad.net/juju-core/environs/httpstorage"
519 "launchpad.net/juju-core/errors"
520 jc "launchpad.net/juju-core/testing/checkers"
521 )
522@@ -25,7 +25,7 @@
523 func (s *storageSuite) TestList(c *gc.C) {
524 listener, _, _ := startServer(c)
525 defer listener.Close()
526- storage := localstorage.Client(listener.Addr().String())
527+ storage := httpstorage.Client(listener.Addr().String())
528 names, err := storage.List("a/b/c")
529 c.Assert(err, gc.IsNil)
530 c.Assert(names, gc.HasLen, 0)
531@@ -37,7 +37,7 @@
532 listener, _, _ := startServer(c)
533 defer listener.Close()
534
535- storage := localstorage.Client(listener.Addr().String())
536+ storage := httpstorage.Client(listener.Addr().String())
537 names := []string{
538 "aa",
539 "zzz/aa",
540@@ -51,7 +51,7 @@
541 checkList(c, storage, "a", []string{"aa"})
542 checkList(c, storage, "zzz/", []string{"zzz/aa", "zzz/bb"})
543
544- storage2 := localstorage.Client(listener.Addr().String())
545+ storage2 := httpstorage.Client(listener.Addr().String())
546 for _, name := range names {
547 checkFileHasContents(c, storage2, name, []byte(name))
548 }
549
550=== modified file 'environs/testing/storage.go'
551--- environs/testing/storage.go 2013-09-17 05:29:35 +0000
552+++ environs/testing/storage.go 2013-09-18 06:28:38 +0000
553@@ -17,7 +17,8 @@
554 gc "launchpad.net/gocheck"
555
556 "launchpad.net/juju-core/environs"
557- "launchpad.net/juju-core/environs/localstorage"
558+ "launchpad.net/juju-core/environs/filestorage"
559+ "launchpad.net/juju-core/environs/httpstorage"
560 "launchpad.net/juju-core/environs/tools"
561 "launchpad.net/juju-core/version"
562 )
563@@ -27,9 +28,11 @@
564 // directory.
565 func CreateLocalTestStorage(c *gc.C) (closer io.Closer, storage environs.Storage, dataDir string) {
566 dataDir = c.MkDir()
567- listener, err := localstorage.Serve("localhost:0", dataDir)
568- c.Assert(err, gc.IsNil)
569- storage = localstorage.Client(listener.Addr().String())
570+ underlying, err := filestorage.NewFileStorageWriter(dataDir)
571+ c.Assert(err, gc.IsNil)
572+ listener, err := httpstorage.Serve("localhost:0", underlying)
573+ c.Assert(err, gc.IsNil)
574+ storage = httpstorage.Client(listener.Addr().String())
575 closer = listener
576 return
577 }
578
579=== modified file 'provider/azure/environ_test.go'
580--- provider/azure/environ_test.go 2013-09-12 14:16:48 +0000
581+++ provider/azure/environ_test.go 2013-09-18 06:28:38 +0000
582@@ -23,8 +23,8 @@
583 "launchpad.net/juju-core/environs"
584 "launchpad.net/juju-core/environs/config"
585 "launchpad.net/juju-core/environs/imagemetadata"
586- "launchpad.net/juju-core/environs/localstorage"
587 "launchpad.net/juju-core/environs/simplestreams"
588+ envtesting "launchpad.net/juju-core/environs/testing"
589 "launchpad.net/juju-core/environs/tools"
590 "launchpad.net/juju-core/errors"
591 "launchpad.net/juju-core/instance"
592@@ -54,12 +54,10 @@
593 // setDummyStorage injects the local provider's fake storage implementation
594 // into the given environment, so that tests can manipulate storage as if it
595 // were real.
596-// Returns a cleanup function that must be called when done with the storage.
597-func setDummyStorage(c *gc.C, env *azureEnviron) func() {
598- listener, err := localstorage.Serve("127.0.0.1:0", c.MkDir())
599- c.Assert(err, gc.IsNil)
600- env.storage = localstorage.Client(listener.Addr().String())
601- return func() { listener.Close() }
602+func (s *environSuite) setDummyStorage(c *gc.C, env *azureEnviron) {
603+ closer, storage, _ := envtesting.CreateLocalTestStorage(c)
604+ env.storage = storage
605+ s.AddCleanup(func(c *gc.C) { closer.Close() })
606 }
607
608 func (*environSuite) TestGetSnapshot(c *gc.C) {
609@@ -469,22 +467,20 @@
610 c.Check(env.storageAccountKey, gc.Equals, "")
611 }
612
613-func (*environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
614+func (s *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
615 env := makeEnviron(c)
616- cleanup := setDummyStorage(c, env)
617- defer cleanup()
618+ s.setDummyStorage(c, env)
619 _, _, err := env.StateInfo()
620 c.Check(err, jc.Satisfies, errors.IsNotBootstrapped)
621 }
622
623-func (*environSuite) TestStateInfo(c *gc.C) {
624+func (s *environSuite) TestStateInfo(c *gc.C) {
625 instanceID := "my-instance"
626 patchWithServiceListResponse(c, []gwacl.HostedServiceDescriptor{{
627 ServiceName: instanceID,
628 }})
629 env := makeEnviron(c)
630- cleanup := setDummyStorage(c, env)
631- defer cleanup()
632+ s.setDummyStorage(c, env)
633 err := provider.SaveState(
634 env.Storage(),
635 &provider.BootstrapState{StateInstances: []instance.Id{instance.Id(instanceID)}})
636@@ -705,15 +701,13 @@
637 return service1, service1Desc
638 }
639
640-func setServiceDeletionConcurrency(nbGoroutines int) func() {
641- oldMaxConcurrentDeletes := maxConcurrentDeletes
642- maxConcurrentDeletes = nbGoroutines
643- return func() { maxConcurrentDeletes = oldMaxConcurrentDeletes }
644+func (s *environSuite) setServiceDeletionConcurrency(nbGoroutines int) {
645+ restore := jc.Set(&maxConcurrentDeletes, nbGoroutines)
646+ s.AddCleanup(func(*gc.C) { restore() })
647 }
648
649-func (*environSuite) TestStopInstancesDestroysMachines(c *gc.C) {
650- cleanup := setServiceDeletionConcurrency(3)
651- defer cleanup()
652+func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) {
653+ s.setServiceDeletionConcurrency(3)
654 service1Name := "service1"
655 service1, service1Desc := makeAzureService(service1Name)
656 service2Name := "service2"
657@@ -739,9 +733,8 @@
658 assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*")
659 }
660
661-func (*environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) {
662- cleanup := setServiceDeletionConcurrency(3)
663- defer cleanup()
664+func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) {
665+ s.setServiceDeletionConcurrency(3)
666 responses := []gwacl.DispatcherResponse{
667 gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil),
668 }
669@@ -767,9 +760,8 @@
670 assertOneRequestMatches(c, *requests, "DELETE", ".*")
671 }
672
673-func (*environSuite) TestStopInstancesWithLimitedConcurrency(c *gc.C) {
674- cleanup := setServiceDeletionConcurrency(3)
675- defer cleanup()
676+func (s *environSuite) TestStopInstancesWithLimitedConcurrency(c *gc.C) {
677+ s.setServiceDeletionConcurrency(3)
678 services := []*gwacl.HostedService{}
679 serviceDescs := []gwacl.HostedServiceDescriptor{}
680 for i := 0; i < 10; i++ {
681@@ -788,9 +780,8 @@
682 c.Check(len(*requests), gc.Equals, len(services)*2)
683 }
684
685-func (*environSuite) TestStopInstancesWithZeroInstance(c *gc.C) {
686- cleanup := setServiceDeletionConcurrency(3)
687- defer cleanup()
688+func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) {
689+ s.setServiceDeletionConcurrency(3)
690 env := makeEnviron(c)
691 instances := []instance.Instance{}
692
693@@ -818,10 +809,9 @@
694 return cleanupResponses
695 }
696
697-func (*environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) {
698+func (s *environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) {
699 env := makeEnviron(c)
700- cleanup := setDummyStorage(c, env)
701- defer cleanup()
702+ s.setDummyStorage(c, env)
703 // Populate storage.
704 err := provider.SaveState(
705 env.Storage(),
706@@ -840,10 +830,9 @@
707 c.Check(files, gc.HasLen, 1)
708 }
709
710-func (*environSuite) TestDestroyCleansUpStorage(c *gc.C) {
711+func (s *environSuite) TestDestroyCleansUpStorage(c *gc.C) {
712 env := makeEnviron(c)
713- cleanup := setDummyStorage(c, env)
714- defer cleanup()
715+ s.setDummyStorage(c, env)
716 // Populate storage.
717 err := provider.SaveState(
718 env.Storage(),
719@@ -864,10 +853,9 @@
720 c.Check(files, gc.HasLen, 0)
721 }
722
723-func (*environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) {
724+func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) {
725 env := makeEnviron(c)
726- cleanup := setDummyStorage(c, env)
727- defer cleanup()
728+ s.setDummyStorage(c, env)
729 services := []gwacl.HostedServiceDescriptor{}
730 responses := getAzureServiceListResponse(c, services)
731 // Prepare a configuration with a single virtual network.
732@@ -933,12 +921,10 @@
733 c.Error(fmt.Sprintf("none of the requests matches: Method=%v, URL pattern=%v", method, urlPattern))
734 }
735
736-func (*environSuite) TestDestroyStopsAllInstances(c *gc.C) {
737- cleanup1 := setServiceDeletionConcurrency(3)
738- defer cleanup1()
739+func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) {
740+ s.setServiceDeletionConcurrency(3)
741 env := makeEnviron(c)
742- cleanup2 := setDummyStorage(c, env)
743- defer cleanup2()
744+ s.setDummyStorage(c, env)
745
746 // Simulate 2 instances corresponding to two Azure services.
747 prefix := env.getEnvPrefix()
748@@ -1300,10 +1286,9 @@
749 c.Assert(retrieved, gc.DeepEquals, content)
750 }
751
752-func (*environSuite) TestGetImageMetadataSources(c *gc.C) {
753+func (s *environSuite) TestGetImageMetadataSources(c *gc.C) {
754 env := makeEnviron(c)
755- cleanup := setDummyStorage(c, env)
756- defer cleanup()
757+ s.setDummyStorage(c, env)
758
759 data := []byte{1, 2, 3, 4}
760 env.Storage().Put("filename", bytes.NewReader(data), int64(len(data)))
761@@ -1320,10 +1305,9 @@
762 c.Assert(url, gc.Equals, imagemetadata.DefaultBaseURL+"/")
763 }
764
765-func (*environSuite) TestGetToolsMetadataSources(c *gc.C) {
766+func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
767 env := makeEnviron(c)
768- cleanup := setDummyStorage(c, env)
769- defer cleanup()
770+ s.setDummyStorage(c, env)
771
772 data := []byte{1, 2, 3, 4}
773 env.Storage().Put("tools/filename", bytes.NewReader(data), int64(len(data)))
774
775=== modified file 'provider/local/environ.go'
776--- provider/local/environ.go 2013-09-16 22:02:24 +0000
777+++ provider/local/environ.go 2013-09-18 06:28:38 +0000
778@@ -21,7 +21,8 @@
779 "launchpad.net/juju-core/environs/bootstrap"
780 "launchpad.net/juju-core/environs/cloudinit"
781 "launchpad.net/juju-core/environs/config"
782- "launchpad.net/juju-core/environs/localstorage"
783+ "launchpad.net/juju-core/environs/filestorage"
784+ "launchpad.net/juju-core/environs/httpstorage"
785 "launchpad.net/juju-core/instance"
786 "launchpad.net/juju-core/juju/osenv"
787 "launchpad.net/juju-core/names"
788@@ -164,7 +165,11 @@
789 } else if !info.Mode().IsDir() {
790 return nil, fmt.Errorf("%q exists but is not a directory (and it needs to be)", dir)
791 }
792- return localstorage.Serve(address, dir)
793+ storage, err := filestorage.NewFileStorageWriter(dir)
794+ if err != nil {
795+ return nil, err
796+ }
797+ return httpstorage.Serve(address, storage)
798 }
799
800 // SetConfig is specified in the Environ interface.
801@@ -319,12 +324,12 @@
802
803 // Storage is specified in the Environ interface.
804 func (env *localEnviron) Storage() environs.Storage {
805- return localstorage.Client(env.config.storageAddr())
806+ return httpstorage.Client(env.config.storageAddr())
807 }
808
809 // PublicStorage is specified in the Environ interface.
810 func (env *localEnviron) PublicStorage() environs.StorageReader {
811- return localstorage.Client(env.config.sharedStorageAddr())
812+ return httpstorage.Client(env.config.sharedStorageAddr())
813 }
814
815 // Destroy is specified in the Environ interface.
816
817=== modified file 'provider/local/storage/worker.go'
818--- provider/local/storage/worker.go 2013-08-30 04:07:04 +0000
819+++ provider/local/storage/worker.go 2013-09-18 06:28:38 +0000
820@@ -5,7 +5,8 @@
821 "launchpad.net/tomb"
822
823 "launchpad.net/juju-core/agent"
824- "launchpad.net/juju-core/environs/localstorage"
825+ "launchpad.net/juju-core/environs/filestorage"
826+ "launchpad.net/juju-core/environs/httpstorage"
827 "launchpad.net/juju-core/worker"
828 )
829
830@@ -40,7 +41,12 @@
831 storageAddr := s.config.Value(agent.StorageAddr)
832 logger.Infof("serving %s on %s", storageDir, storageAddr)
833
834- storageListener, err := localstorage.Serve(storageAddr, storageDir)
835+ storage, err := filestorage.NewFileStorageWriter(storageDir)
836+ if err != nil {
837+ logger.Errorf("error with local storage: %v", err)
838+ return err
839+ }
840+ storageListener, err := httpstorage.Serve(storageAddr, storage)
841 if err != nil {
842 logger.Errorf("error with local storage: %v", err)
843 return err
844@@ -51,7 +57,12 @@
845 sharedStorageAddr := s.config.Value(agent.SharedStorageAddr)
846 logger.Infof("serving %s on %s", sharedStorageDir, sharedStorageAddr)
847
848- sharedStorageListener, err := localstorage.Serve(sharedStorageAddr, sharedStorageDir)
849+ sharedStorage, err := filestorage.NewFileStorageWriter(sharedStorageDir)
850+ if err != nil {
851+ logger.Errorf("error with local storage: %v", err)
852+ return err
853+ }
854+ sharedStorageListener, err := httpstorage.Serve(sharedStorageAddr, sharedStorage)
855 if err != nil {
856 logger.Errorf("error with local storage: %v", err)
857 return err
858
859=== modified file 'provider/state_test.go'
860--- provider/state_test.go 2013-09-12 12:38:04 +0000
861+++ provider/state_test.go 2013-09-18 06:28:38 +0000
862@@ -12,7 +12,7 @@
863
864 "launchpad.net/juju-core/environs"
865 "launchpad.net/juju-core/environs/config"
866- "launchpad.net/juju-core/environs/localstorage"
867+ envtesting "launchpad.net/juju-core/environs/testing"
868 "launchpad.net/juju-core/errors"
869 "launchpad.net/juju-core/instance"
870 "launchpad.net/juju-core/provider"
871@@ -20,23 +20,21 @@
872 jc "launchpad.net/juju-core/testing/checkers"
873 )
874
875-type StateSuite struct{}
876+type StateSuite struct {
877+ testing.LoggingSuite
878+}
879
880 var _ = gc.Suite(&StateSuite{})
881
882 // makeDummyStorage creates a local storage.
883-// Returns a cleanup function that must be called when done with the storage.
884-func makeDummyStorage(c *gc.C) (environs.Storage, func()) {
885- listener, err := localstorage.Serve("127.0.0.1:0", c.MkDir())
886- c.Assert(err, gc.IsNil)
887- storage := localstorage.Client(listener.Addr().String())
888- cleanup := func() { listener.Close() }
889- return storage, cleanup
890+func (suite *StateSuite) makeDummyStorage(c *gc.C) environs.Storage {
891+ closer, storage, _ := envtesting.CreateLocalTestStorage(c)
892+ suite.AddCleanup(func(*gc.C) { closer.Close() })
893+ return storage
894 }
895
896-func (*StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
897- storage, cleanup := makeDummyStorage(c)
898- defer cleanup()
899+func (suite *StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
900+ storage := suite.makeDummyStorage(c)
901
902 url, err := provider.CreateStateFile(storage)
903 c.Assert(err, gc.IsNil)
904@@ -53,8 +51,7 @@
905 }
906
907 func (suite *StateSuite) TestSaveStateWritesStateFile(c *gc.C) {
908- storage, cleanup := makeDummyStorage(c)
909- defer cleanup()
910+ storage := suite.makeDummyStorage(c)
911 arch := "amd64"
912 state := provider.BootstrapState{
913 StateInstances: []instance.Id{instance.Id("an-instance-id")},
914@@ -85,8 +82,7 @@
915 }
916
917 func (suite *StateSuite) TestLoadStateReadsStateFile(c *gc.C) {
918- storage, cleanup := makeDummyStorage(c)
919- defer cleanup()
920+ storage := suite.makeDummyStorage(c)
921 state := suite.setUpSavedState(c, storage)
922 storedState, err := provider.LoadState(storage)
923 c.Assert(err, gc.IsNil)
924@@ -94,8 +90,7 @@
925 }
926
927 func (suite *StateSuite) TestLoadStateFromURLReadsStateFile(c *gc.C) {
928- storage, cleanup := makeDummyStorage(c)
929- defer cleanup()
930+ storage := suite.makeDummyStorage(c)
931 state := suite.setUpSavedState(c, storage)
932 url, err := storage.URL(provider.StateFile)
933 c.Assert(err, gc.IsNil)
934@@ -105,8 +100,7 @@
935 }
936
937 func (suite *StateSuite) TestLoadStateMissingFile(c *gc.C) {
938- storage, cleanup := makeDummyStorage(c)
939- defer cleanup()
940+ storage := suite.makeDummyStorage(c)
941
942 _, err := provider.LoadState(storage)
943
944@@ -114,8 +108,7 @@
945 }
946
947 func (suite *StateSuite) TestLoadStateIntegratesWithSaveState(c *gc.C) {
948- storage, cleanup := makeDummyStorage(c)
949- defer cleanup()
950+ storage := suite.makeDummyStorage(c)
951 arch := "amd64"
952 state := provider.BootstrapState{
953 StateInstances: []instance.Id{instance.Id("an-instance-id")},

Subscribers

People subscribed via source and target branches

to status/vote changes: