Merge lp:~axwalk/juju-core/sshstorage-tmpdir 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: 1849
Proposed branch: lp:~axwalk/juju-core/sshstorage-tmpdir
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 484 lines (+154/-118)
2 files modified
environs/sshstorage/storage.go (+47/-32)
environs/sshstorage/storage_test.go (+107/-86)
To merge this branch: bzr merge lp:~axwalk/juju-core/sshstorage-tmpdir
Reviewer Review Type Date Requested Status
Tim Penhey (community) Needs Information
Review via email: mp+185717@code.launchpad.net

Commit message

environs/sshstorage: revert storage layout

In a lapse of sanity, I changed sshstorage's
layout so that the temporary directory was
contained within the designated storage
directory. This works fine in the context of
sshstorage, but not so well with how sshstorage
is intended to be used.

For bootstrapping the null provider, sshstorage
is used to operate on a specified remote
directory; later, when the machine agent is up,
it takes ownership of that directory and serves
it via environs/localstorage. Thus, both
sshstorage and localstorage must agree on the
layout.

Now, instead of having storage/{tmp,content},
the user can specify a separate temporary
directory. If left blank, storagedir+".tmp" will
be used.

https://codereview.appspot.com/13660047/

Description of the change

environs/sshstorage: revert storage layout

In a lapse of sanity, I changed sshstorage's
layout so that the temporary directory was
contained within the designated storage
directory. This works fine in the context of
sshstorage, but not so well with how sshstorage
is intended to be used.

For bootstrapping the null provider, sshstorage
is used to operate on a specified remote
directory; later, when the machine agent is up,
it takes ownership of that directory and serves
it via environs/localstorage. Thus, both
sshstorage and localstorage must agree on the
layout.

Now, instead of having storage/{tmp,content},
the user can specify a separate temporary
directory. If left blank, storagedir+".tmp" will
be used.

https://codereview.appspot.com/13660047/

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

Reviewers: mp+185717_code.launchpad.net,

Message:
Please take a look.

Description:
environs/sshstorage: revert storage layout

In a lapse of sanity, I changed sshstorage's
layout so that the temporary directory was
contained within the designated storage
directory. This works fine in the context of
sshstorage, but not so well with how sshstorage
is intended to be used.

For bootstrapping the null provider, sshstorage
is used to operate on a specified remote
directory; later, when the machine agent is up,
it takes ownership of that directory and serves
it via environs/localstorage. Thus, both
sshstorage and localstorage must agree on the
layout.

Now, instead of having storage/{tmp,content},
the user can specify a separate temporary
directory. If left blank, storagedir+".tmp" will
be used.

https://code.launchpad.net/~axwalk/juju-core/sshstorage-tmpdir/+merge/185717

(do not edit description out of merge proposal)

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

Affected files (+48, -58 lines):
   A [revision details]
   M environs/sshstorage/storage.go
   M environs/sshstorage/storage_test.go

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

Mostly comments about test improvements.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go
File environs/sshstorage/storage_test.go (right):

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode80
environs/sshstorage/storage_test.go:80: for i := 0; i < 2; i++ {
It isn't immediately obvious why you are doing this twice.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode81
environs/sshstorage/storage_test.go:81: storage, err :=
NewSSHStorage("example.com", storageDir, "")
Same comment as for the filestorage, the "" here isn't obvious.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode101
environs/sshstorage/storage_test.go:101:
c.Assert(os.Mkdir(filepath.Join(storageDir, "a"), 0755), gc.IsNil)
I have to admit to finding putting the work inside the assert harder to
follow than having them separated out. To me it appears to confuse the
work and the test.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode170
environs/sshstorage/storage_test.go:170: defer storage.Close()
Now that I've seen this same code three times... a function is in order.

func (s *storageSuite) makeStorage(c *gc.C) <storageType> {
   storageDir := c.MkDir()
   storage, err := NewSSHStorage("example.com", storageDir, "")
   c.Assert(err, gc.IsNil)
   s.AddCleanup(func (*gc.C) {
     storage.Close()
   })
   return storage
}

The tests then each get shorter, and logic is in one place.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode177
environs/sshstorage/storage_test.go:177:
c.Assert(ioutil.WriteFile(filepath.Join(storageDir, "a", "b1"), nil, 0),
gc.IsNil)
This is also screaming out for a helper method.

You want the method to describe what is happening.

https://codereview.appspot.com/13660047/

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

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go
File environs/sshstorage/storage_test.go (right):

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode80
environs/sshstorage/storage_test.go:80: for i := 0; i < 2; i++ {
On 2013/09/18 04:39:41, thumper wrote:
> It isn't immediately obvious why you are doing this twice.

Added a comment.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode81
environs/sshstorage/storage_test.go:81: storage, err :=
NewSSHStorage("example.com", storageDir, "")
On 2013/09/18 04:39:41, thumper wrote:
> Same comment as for the filestorage, the "" here isn't obvious.

Done. Also updated Put to create/remove remove the default temporary
directory, though it's less of an issue here.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode101
environs/sshstorage/storage_test.go:101:
c.Assert(os.Mkdir(filepath.Join(storageDir, "a"), 0755), gc.IsNil)
On 2013/09/18 04:39:41, thumper wrote:
> I have to admit to finding putting the work inside the assert harder
to follow
> than having them separated out. To me it appears to confuse the work
and the
> test.

I've separated the lengthier ones out to make it obvious what's the
code-under-test and what's the assertion; I've left many of the shorter
assertions alone, e.g. c.Assert(storage.Remove("a"), gc.NotNil).

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode170
environs/sshstorage/storage_test.go:170: defer storage.Close()
On 2013/09/18 04:39:41, thumper wrote:
> Now that I've seen this same code three times... a function is in
order.

> func (s *storageSuite) makeStorage(c *gc.C) <storageType> {
> storageDir := c.MkDir()
> storage, err := NewSSHStorage("example.com", storageDir, "")
> c.Assert(err, gc.IsNil)
> s.AddCleanup(func (*gc.C) {
> storage.Close()
> })
> return storage
> }

> The tests then each get shorter, and logic is in one place.

Done.

https://codereview.appspot.com/13660047/diff/1/environs/sshstorage/storage_test.go#newcode177
environs/sshstorage/storage_test.go:177:
c.Assert(ioutil.WriteFile(filepath.Join(storageDir, "a", "b1"), nil, 0),
gc.IsNil)
On 2013/09/18 04:39:41, thumper wrote:
> This is also screaming out for a helper method.

> You want the method to describe what is happening.

Yep, done. Added a "createFiles" test helper.

https://codereview.appspot.com/13660047/

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

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go
File environs/sshstorage/storage_test.go (right):

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go#newcode4
environs/sshstorage/storage_test.go:4: package sshstorage
Normally the tests would be in sshstorage_test package.

Is there any reason why this is a whitebox test?

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go#newcode74
environs/sshstorage/storage_test.go:74: func (s *storageSuite)
makeStorage(c *gc.C) (storage *SSHStorage, storageDir string) {
Why name the return parameters here? You aren't really gaining much. Is
it just to provide more assistance to the reader?

https://codereview.appspot.com/13660047/

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

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go
File environs/sshstorage/storage_test.go (right):

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go#newcode4
environs/sshstorage/storage_test.go:4: package sshstorage
On 2013/09/18 21:30:56, thumper wrote:
> Normally the tests would be in sshstorage_test package.

> Is there any reason why this is a whitebox test?

Yes, so the tests can mock out "sshCommand". See the call to jc.Set in
SetUpSuite.

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go#newcode74
environs/sshstorage/storage_test.go:74: func (s *storageSuite)
makeStorage(c *gc.C) (storage *SSHStorage, storageDir string) {
On 2013/09/18 21:30:56, thumper wrote:
> Why name the return parameters here? You aren't really gaining much.
Is it just
> to provide more assistance to the reader?

Yes, that was my rationale; I don't want to have to look at the
implementation to know what the result is.

It could be a doc comment, but in this case a self-documenting named
result is an easy win.

https://codereview.appspot.com/13660047/

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

for tests that are whitebox tests, we have had an informal convention of
naming them _whitebox_test.go instead of _test.go

LGTM

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go
File environs/sshstorage/storage_test.go (right):

https://codereview.appspot.com/13660047/diff/7001/environs/sshstorage/storage_test.go#newcode4
environs/sshstorage/storage_test.go:4: package sshstorage
On 2013/09/19 02:36:19, axw1 wrote:
> On 2013/09/18 21:30:56, thumper wrote:
> > Normally the tests would be in sshstorage_test package.
> >
> > Is there any reason why this is a whitebox test?

> Yes, so the tests can mock out "sshCommand". See the call to jc.Set in
> SetUpSuite.

jc.Set has moved, and is about to move again with my branch :-)

   testing.PatchValue
until it becomes
   testbase.PatchValue

https://codereview.appspot.com/13660047/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (6.6 KiB)

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

ok launchpad.net/juju-core/agent 0.743s
ok launchpad.net/juju-core/agent/tools 0.275s
ok launchpad.net/juju-core/bzr 6.637s
ok launchpad.net/juju-core/cert 2.373s
ok launchpad.net/juju-core/charm 0.541s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.021s
ok launchpad.net/juju-core/cmd 0.244s
? 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 154.458s
ok launchpad.net/juju-core/cmd/jujud 39.250s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 1.840s
ok launchpad.net/juju-core/constraints 0.028s
ok launchpad.net/juju-core/container/lxc 0.351s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.288s
ok launchpad.net/juju-core/environs 1.844s
ok launchpad.net/juju-core/environs/bootstrap 4.872s
ok launchpad.net/juju-core/environs/cloudinit 0.426s
ok launchpad.net/juju-core/environs/config 0.799s
ok launchpad.net/juju-core/environs/configstore 0.063s
ok launchpad.net/juju-core/environs/filestorage 0.030s
ok launchpad.net/juju-core/environs/httpstorage 0.048s
ok launchpad.net/juju-core/environs/imagemetadata 0.489s
ok launchpad.net/juju-core/environs/instances 0.052s
ok launchpad.net/juju-core/environs/jujutest 0.214s
ok launchpad.net/juju-core/environs/manual 1.115s
ok launchpad.net/juju-core/environs/simplestreams 0.265s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
FAIL launchpad.net/juju-core/environs/sshstorage [build failed]
ok launchpad.net/juju-core/environs/storage 0.011s
ok launchpad.net/juju-core/environs/sync 26.073s
ok launchpad.net/juju-core/environs/testing 0.011s
ok launchpad.net/juju-core/environs/tools 10.424s
? 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.022s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 20.195s
ok launchpad.net/juju-core/juju/osenv 0.018s
? 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.022s
ok launchpad.net/juju-core/names 0.024s
ok launchpad.net/juju-core/provider 0.328s
? launchpad.net/juju-core/provider/all [no test files]
ok launchpad.net/juju-core/provider/azure 6.439s
ok launchpad.net/juju-core/provider/dummy 20.225s
ok launchpad.net/juju-core/provider/ec2 5.228s
ok launchpad.net/juju-core/provider/ec2/httpstorage 0.050s
ok launchpad.net/juju-core/provider/local 1.779s
? launchpad.net/juju-core/provider/local/storage [no test files]
ok launchpad.net/juju-core/provider/maas 3.733s
ok launchpad.net/juju-core/provider/openstack 8.343s
ok launchpad.net/juju-core/rpc 0.080s
ok launchpad.net/juju-core/rpc/jsoncodec 0.038s
ok launchpad.net/juju-core/schema 0....

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/sshstorage/storage.go'
2--- environs/sshstorage/storage.go 2013-09-18 22:54:32 +0000
3+++ environs/sshstorage/storage.go 2013-09-20 04:07:37 +0000
4@@ -21,18 +21,6 @@
5 "launchpad.net/juju-core/utils"
6 )
7
8-const (
9- // tmpdir is the name of the subdirectory
10- // inside the remote storage directory where
11- // temporary files are created.
12- tmpdir = "tmp"
13-
14- // contentdir is the name of the subdirectory
15- // inside the remote storage directory where
16- // files are stored.
17- contentdir = "content"
18-)
19-
20 // SSHStorage implements storage.Storage.
21 //
22 // The storage is created under sudo, and ownership given over to the
23@@ -42,6 +30,7 @@
24 type SSHStorage struct {
25 host string
26 remotepath string
27+ tmpdir string
28
29 cmd *exec.Cmd
30 stdin io.WriteCloser
31@@ -65,12 +54,24 @@
32 flockExclusive flockmode = "-x"
33 )
34
35+// UseDefaultTmpDir may be passed into NewSSHStorage
36+// for the tmpdir argument, to signify that the default
37+// value should be used. See NewSSHStorage for more.
38+const UseDefaultTmpDir = ""
39+
40 // NewSSHStorage creates a new SSHStorage, connected to the
41 // specified host, managing state under the specified remote path.
42-func NewSSHStorage(host string, remotepath string) (*SSHStorage, error) {
43- contentdir := path.Join(remotepath, contentdir)
44- tmpdir := path.Join(remotepath, tmpdir)
45- script := fmt.Sprintf("install -d -g $SUDO_GID -o $SUDO_UID %s %s", contentdir, tmpdir)
46+//
47+// A temporary directory may be specified, in which case it should
48+// be a directory on the same filesystem as the storage directory
49+// to ensure atomic writes. If left unspecified, tmpdir will be
50+// assigned a value of storagedir+".tmp".
51+//
52+// If tmpdir == UseDefaultTmpDir, it will be created when Put is invoked,
53+// and will be removed afterwards. If tmpdir != UseDefaultTmpDir, it must
54+// already exist, and will never be removed.
55+func NewSSHStorage(host, storagedir, tmpdir string) (*SSHStorage, error) {
56+ script := fmt.Sprintf("install -d -g $SUDO_GID -o $SUDO_UID %s", utils.ShQuote(storagedir))
57 cmd := sshCommand(host, true, fmt.Sprintf("sudo bash -c '%s'", script))
58 cmd.Stdin = os.Stdin
59 if out, err := cmd.CombinedOutput(); err != nil {
60@@ -93,7 +94,8 @@
61 }
62 stor := &SSHStorage{
63 host: host,
64- remotepath: remotepath,
65+ remotepath: storagedir,
66+ tmpdir: tmpdir,
67 cmd: cmd,
68 stdin: stdin,
69 stdout: stdout,
70@@ -101,13 +103,8 @@
71 }
72 cmd.Start()
73
74- // Verify we have write permissions, and set the temporary directory.
75- _, err = stor.runf(
76- flockExclusive,
77- "touch %s && export TMPDIR=%s",
78- utils.ShQuote(remotepath),
79- utils.ShQuote(tmpdir),
80- )
81+ // Verify we have write permissions.
82+ _, err = stor.runf(flockExclusive, "touch %s", utils.ShQuote(storagedir))
83 if err != nil {
84 stdin.Close()
85 stdout.Close()
86@@ -164,9 +161,8 @@
87
88 // path returns a remote absolute path for a storage object name.
89 func (s *SSHStorage) path(name string) (string, error) {
90- contentdir := path.Join(s.remotepath, contentdir)
91- remotepath := path.Clean(path.Join(contentdir, name))
92- if !strings.HasPrefix(remotepath, contentdir) {
93+ remotepath := path.Clean(path.Join(s.remotepath, name))
94+ if !strings.HasPrefix(remotepath, s.remotepath) {
95 return "", fmt.Errorf("%q escapes storage directory", name)
96 }
97 return remotepath, nil
98@@ -209,10 +205,9 @@
99 return nil, nil
100 }
101 var names []string
102- contentdir := path.Join(s.remotepath, contentdir)
103 for _, name := range strings.Split(out, "\n") {
104 if strings.HasPrefix(name[len(dir):], prefix) {
105- names = append(names, name[len(contentdir)+1:])
106+ names = append(names, name[len(s.remotepath)+1:])
107 }
108 }
109 sort.Strings(names)
110@@ -250,9 +245,30 @@
111 }
112 encoded := base64.StdEncoding.EncodeToString(buf)
113 path = utils.ShQuote(path)
114+
115+ tmpdir := s.tmpdir
116+ if tmpdir == UseDefaultTmpDir {
117+ tmpdir = s.remotepath + ".tmp"
118+ }
119+ tmpdir = utils.ShQuote(tmpdir)
120+
121 // Write to a temporary file ($TMPFILE), then mv atomically.
122 command := fmt.Sprintf("mkdir -p `dirname %s` && base64 -d > $TMPFILE", path)
123- command = fmt.Sprintf("TMPFILE=`mktemp` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)", command, path)
124+ command = fmt.Sprintf(
125+ "export TMPDIR=%s && TMPFILE=`mktemp` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
126+ tmpdir, command, path,
127+ )
128+
129+ // If UseDefaultTmpDir is passed, then create the
130+ // temporary directory, and remove it afterwards.
131+ // Otherwise, the temporary directory is expected
132+ // to already exist, and is never removed.
133+ if s.tmpdir == UseDefaultTmpDir {
134+ installTmpdir := fmt.Sprintf("install -d -g $SUDO_GID -o $SUDO_UID %s", tmpdir)
135+ removeTmpdir := fmt.Sprintf("rm -fr %s", tmpdir)
136+ command = fmt.Sprintf("%s && (%s); rc=$?; %s; exit $rc", installTmpdir, command, removeTmpdir)
137+ }
138+
139 command = fmt.Sprintf("(%s) << EOF\n%s\nEOF", command, encoded)
140 _, err = s.runf(flockExclusive, command+"\n")
141 return err
142@@ -271,7 +287,6 @@
143
144 // RemoveAll implements storage.StorageWriter.RemoveAll
145 func (s *SSHStorage) RemoveAll() error {
146- contentdir := path.Join(s.remotepath, contentdir)
147- _, err := s.runf(flockExclusive, "rm -fr %s/*", utils.ShQuote(contentdir))
148+ _, err := s.runf(flockExclusive, "rm -fr %s/*", utils.ShQuote(s.remotepath))
149 return err
150 }
151
152=== modified file 'environs/sshstorage/storage_test.go'
153--- environs/sshstorage/storage_test.go 2013-09-20 02:53:59 +0000
154+++ environs/sshstorage/storage_test.go 2013-09-20 04:07:37 +0000
155@@ -27,7 +27,6 @@
156
157 type storageSuite struct {
158 testbase.LoggingSuite
159- restoreEnv func()
160 }
161
162 var _ = gc.Suite(&storageSuite{})
163@@ -36,8 +35,6 @@
164 gc.TestingT(t)
165 }
166
167-var sshCommandOrig = sshCommand
168-
169 func sshCommandTesting(host string, tty bool, command string) *exec.Cmd {
170 cmd := exec.Command("bash", "-c", command)
171 uid := fmt.Sprint(os.Getuid())
172@@ -59,50 +56,70 @@
173 c.Assert(err, gc.IsNil)
174
175 bin := c.MkDir()
176- s.restoreEnv = testbase.PatchEnvironment("PATH", bin+":"+os.Getenv("PATH"))
177+ restoreEnv := testbase.PatchEnvironment("PATH", bin+":"+os.Getenv("PATH"))
178+ s.AddSuiteCleanup(func(*gc.C) { restoreEnv() })
179
180 // Create a "sudo" command which just executes its args.
181- c.Assert(os.Symlink("/usr/bin/env", filepath.Join(bin, "sudo")), gc.IsNil)
182- sshCommand = sshCommandTesting
183+ err = os.Symlink("/usr/bin/env", filepath.Join(bin, "sudo"))
184+ c.Assert(err, gc.IsNil)
185+ restoreSshCommand := testbase.PatchValue(&sshCommand, sshCommandTesting)
186+ s.AddSuiteCleanup(func(*gc.C) { restoreSshCommand() })
187
188 // Create a new "flock" which calls the original, but in non-blocking mode.
189 data := []byte(fmt.Sprintf("#!/bin/sh\nexec %s --nonblock \"$@\"", flockBin))
190- c.Assert(ioutil.WriteFile(filepath.Join(bin, "flock"), data, 0755), gc.IsNil)
191-}
192-
193-func (s *storageSuite) TearDownSuite(c *gc.C) {
194- sshCommand = sshCommandOrig
195- s.restoreEnv()
196- s.LoggingSuite.TearDownSuite(c)
197+ err = ioutil.WriteFile(filepath.Join(bin, "flock"), data, 0755)
198+ c.Assert(err, gc.IsNil)
199+}
200+
201+func (s *storageSuite) makeStorage(c *gc.C) (storage *SSHStorage, storageDir string) {
202+ storageDir = c.MkDir()
203+ storage, err := NewSSHStorage("example.com", storageDir, "")
204+ c.Assert(err, gc.IsNil)
205+ c.Assert(storage, gc.NotNil)
206+ s.AddCleanup(func(*gc.C) { storage.Close() })
207+ return storage, storageDir
208+}
209+
210+// createFiles creates empty files in the storage directory
211+// with the given storage names.
212+func createFiles(c *gc.C, storageDir string, names ...string) {
213+ for _, name := range names {
214+ path := filepath.Join(storageDir, filepath.FromSlash(name))
215+ dir := filepath.Dir(path)
216+ if err := os.MkdirAll(dir, 0755); err != nil {
217+ c.Assert(err, jc.Satisfies, os.IsExist)
218+ }
219+ err := ioutil.WriteFile(path, nil, 0644)
220+ c.Assert(err, gc.IsNil)
221+ }
222 }
223
224 func (s *storageSuite) TestNewSSHStorage(c *gc.C) {
225 storageDir := c.MkDir()
226+ // Run this block twice to ensure NewSSHStorage can reuse
227+ // an existing storage location.
228 for i := 0; i < 2; i++ {
229- stor, err := NewSSHStorage("example.com", storageDir)
230+ stor, err := NewSSHStorage("example.com", storageDir, UseDefaultTmpDir)
231 c.Assert(err, gc.IsNil)
232 c.Assert(stor, gc.NotNil)
233 c.Assert(stor.Close(), gc.IsNil)
234 }
235- c.Assert(os.RemoveAll(storageDir), gc.IsNil)
236+ err := os.RemoveAll(storageDir)
237+ c.Assert(err, gc.IsNil)
238
239 // You must have permissions to create the directory.
240 storageDir = c.MkDir()
241- c.Assert(os.Chmod(storageDir, 0555), gc.IsNil)
242- _, err := NewSSHStorage("example.com", filepath.Join(storageDir))
243+ err = os.Chmod(storageDir, 0555)
244+ c.Assert(err, gc.IsNil)
245+ _, err = NewSSHStorage("example.com", filepath.Join(storageDir, "subdir"), UseDefaultTmpDir)
246 c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot change owner and permissions of(.|\n)*")
247 }
248
249 func (s *storageSuite) TestPathValidity(c *gc.C) {
250- storageDir := c.MkDir()
251- stor, err := NewSSHStorage("example.com", storageDir)
252- c.Assert(err, gc.IsNil)
253- defer stor.Close()
254-
255- c.Assert(os.Mkdir(filepath.Join(storageDir, contentdir, "a"), 0755), gc.IsNil)
256- f, err := os.Create(filepath.Join(storageDir, contentdir, "a", "b"))
257- c.Assert(err, gc.IsNil)
258- c.Assert(f.Close(), gc.IsNil)
259+ stor, storageDir := s.makeStorage(c)
260+ err := os.Mkdir(filepath.Join(storageDir, "a"), 0755)
261+ c.Assert(err, gc.IsNil)
262+ createFiles(c, storageDir, "a/b")
263
264 for _, prefix := range []string{"..", "a/../.."} {
265 c.Logf("prefix: %q", prefix)
266@@ -122,14 +139,12 @@
267 }
268
269 func (s *storageSuite) TestGet(c *gc.C) {
270- storageDir := c.MkDir()
271- stor, err := NewSSHStorage("example.com", storageDir)
272+ stor, storageDir := s.makeStorage(c)
273+ data := []byte("abc\000def")
274+ err := os.Mkdir(filepath.Join(storageDir, "a"), 0755)
275 c.Assert(err, gc.IsNil)
276- defer stor.Close()
277- data := []byte("abc\000def")
278- c.Assert(os.Mkdir(filepath.Join(storageDir, contentdir, "a"), 0755), gc.IsNil)
279 for _, name := range []string{"b", filepath.Join("a", "b")} {
280- err = ioutil.WriteFile(filepath.Join(storageDir, contentdir, name), data, 0644)
281+ err = ioutil.WriteFile(filepath.Join(storageDir, name), data, 0644)
282 c.Assert(err, gc.IsNil)
283 r, err := storage.Get(stor, name)
284 c.Assert(err, gc.IsNil)
285@@ -137,21 +152,17 @@
286 c.Assert(err, gc.IsNil)
287 c.Assert(out, gc.DeepEquals, data)
288 }
289-
290 _, err = storage.Get(stor, "notthere")
291 c.Assert(err, jc.Satisfies, coreerrors.IsNotFoundError)
292 }
293
294 func (s *storageSuite) TestPut(c *gc.C) {
295- storageDir := c.MkDir()
296- stor, err := NewSSHStorage("example.com", storageDir)
297- c.Assert(err, gc.IsNil)
298- defer stor.Close()
299+ stor, storageDir := s.makeStorage(c)
300 data := []byte("abc\000def")
301 for _, name := range []string{"b", filepath.Join("a", "b")} {
302- err = stor.Put(name, bytes.NewBuffer(data), int64(len(data)))
303+ err := stor.Put(name, bytes.NewBuffer(data), int64(len(data)))
304 c.Assert(err, gc.IsNil)
305- out, err := ioutil.ReadFile(filepath.Join(storageDir, contentdir, name))
306+ out, err := ioutil.ReadFile(filepath.Join(storageDir, name))
307 c.Assert(err, gc.IsNil)
308 c.Assert(out, gc.DeepEquals, data)
309 }
310@@ -165,20 +176,15 @@
311 }
312
313 func (s *storageSuite) TestList(c *gc.C) {
314- storageDir := c.MkDir()
315- stor, err := NewSSHStorage("example.com", storageDir)
316- c.Assert(err, gc.IsNil)
317- defer stor.Close()
318+ stor, storageDir := s.makeStorage(c)
319 s.assertList(c, stor, "", nil)
320
321 // Directories don't show up in List.
322- contentDir := filepath.Join(storageDir, contentdir)
323- c.Assert(os.Mkdir(filepath.Join(contentDir, "a"), 0755), gc.IsNil)
324+ err := os.Mkdir(filepath.Join(storageDir, "a"), 0755)
325+ c.Assert(err, gc.IsNil)
326 s.assertList(c, stor, "", nil)
327 s.assertList(c, stor, "a", nil)
328- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b1"), nil, 0), gc.IsNil)
329- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b2"), nil, 0), gc.IsNil)
330- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "b"), nil, 0), gc.IsNil)
331+ createFiles(c, storageDir, "a/b1", "a/b2", "b")
332 s.assertList(c, stor, "", []string{"a/b1", "a/b2", "b"})
333 s.assertList(c, stor, "a", []string{"a/b1", "a/b2"})
334 s.assertList(c, stor, "a/b", []string{"a/b1", "a/b2"})
335@@ -189,15 +195,10 @@
336 }
337
338 func (s *storageSuite) TestRemove(c *gc.C) {
339- storageDir := c.MkDir()
340- stor, err := NewSSHStorage("example.com", storageDir)
341+ stor, storageDir := s.makeStorage(c)
342+ err := os.Mkdir(filepath.Join(storageDir, "a"), 0755)
343 c.Assert(err, gc.IsNil)
344- defer stor.Close()
345-
346- contentDir := filepath.Join(storageDir, contentdir)
347- c.Assert(os.Mkdir(filepath.Join(contentDir, "a"), 0755), gc.IsNil)
348- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b1"), nil, 0), gc.IsNil)
349- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b2"), nil, 0), gc.IsNil)
350+ createFiles(c, storageDir, "a/b1", "a/b2")
351 c.Assert(stor.Remove("a"), gc.ErrorMatches, "rm: cannot remove.*Is a directory")
352 s.assertList(c, stor, "", []string{"a/b1", "a/b2"})
353 c.Assert(stor.Remove("a/b"), gc.IsNil) // doesn't exist; not an error
354@@ -209,39 +210,28 @@
355 }
356
357 func (s *storageSuite) TestRemoveAll(c *gc.C) {
358- storageDir := c.MkDir()
359- stor, err := NewSSHStorage("example.com", storageDir)
360+ stor, storageDir := s.makeStorage(c)
361+ err := os.Mkdir(filepath.Join(storageDir, "a"), 0755)
362 c.Assert(err, gc.IsNil)
363- defer stor.Close()
364-
365- contentDir := filepath.Join(storageDir, contentdir)
366- c.Assert(os.Mkdir(filepath.Join(contentDir, "a"), 0755), gc.IsNil)
367- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b1"), nil, 0), gc.IsNil)
368- c.Assert(ioutil.WriteFile(filepath.Join(contentDir, "a", "b2"), nil, 0), gc.IsNil)
369+ createFiles(c, storageDir, "a/b1", "a/b2")
370 s.assertList(c, stor, "", []string{"a/b1", "a/b2"})
371 c.Assert(stor.RemoveAll(), gc.IsNil)
372 s.assertList(c, stor, "", nil)
373
374 // RemoveAll does not remove the base storage directory.
375- _, err = os.Stat(contentDir)
376+ _, err = os.Stat(storageDir)
377 c.Assert(err, gc.IsNil)
378 }
379
380 func (s *storageSuite) TestURL(c *gc.C) {
381- storageDir := c.MkDir()
382- stor, err := NewSSHStorage("example.com", storageDir)
383- c.Assert(err, gc.IsNil)
384- defer stor.Close()
385+ stor, storageDir := s.makeStorage(c)
386 url, err := stor.URL("a/b")
387 c.Assert(err, gc.IsNil)
388- c.Assert(url, gc.Equals, "sftp://example.com/"+path.Join(storageDir, contentdir, "a/b"))
389+ c.Assert(url, gc.Equals, "sftp://example.com/"+path.Join(storageDir, "a/b"))
390 }
391
392-func (s *storageSuite) TestConsistencyStrategy(c *gc.C) {
393- storageDir := c.MkDir()
394- stor, err := NewSSHStorage("example.com", storageDir)
395- c.Assert(err, gc.IsNil)
396- defer stor.Close()
397+func (s *storageSuite) TestDefaultConsistencyStrategy(c *gc.C) {
398+ stor, _ := s.makeStorage(c)
399 c.Assert(stor.DefaultConsistencyStrategy(), gc.Equals, utils.AttemptStrategy{})
400 }
401
402@@ -271,22 +261,19 @@
403 //
404 // flock exits with exit code 1 if it can't acquire the
405 // lock immediately in non-blocking mode (which the tests force).
406- _, err := NewSSHStorage("example.com", storageDir)
407+ _, err := NewSSHStorage("example.com", storageDir, UseDefaultTmpDir)
408 c.Assert(err, gc.ErrorMatches, "exit code 1")
409 }
410
411 func (s *storageSuite) TestWithSharedLocks(c *gc.C) {
412- storageDir := c.MkDir()
413- stor, err := NewSSHStorage("example.com", storageDir)
414- c.Assert(err, gc.IsNil)
415+ stor, storageDir := s.makeStorage(c)
416
417 // Get and List should be able to proceed with a shared lock.
418 // All other methods should fail.
419- data := []byte("abc\000def")
420- c.Assert(ioutil.WriteFile(filepath.Join(storageDir, contentdir, "a"), data, 0644), gc.IsNil)
421+ createFiles(c, storageDir, "a")
422
423 s.flock(c, flockShared, storageDir)
424- _, err = storage.Get(stor, "a")
425+ _, err := storage.Get(stor, "a")
426 c.Assert(err, gc.IsNil)
427 _, err = storage.List(stor, "")
428 c.Assert(err, gc.IsNil)
429@@ -296,13 +283,11 @@
430 }
431
432 func (s *storageSuite) TestWithExclusiveLocks(c *gc.C) {
433- storageDir := c.MkDir()
434- stor, err := NewSSHStorage("example.com", storageDir)
435- c.Assert(err, gc.IsNil)
436+ stor, storageDir := s.makeStorage(c)
437 // None of the methods (apart from URL) should be able to do anything
438 // while an exclusive lock is held.
439 s.flock(c, flockExclusive, storageDir)
440- _, err = stor.URL("a")
441+ _, err := stor.URL("a")
442 c.Assert(err, gc.IsNil)
443 c.Assert(stor.Put("a", bytes.NewBuffer(nil), 0), gc.NotNil)
444 c.Assert(stor.Remove("a"), gc.NotNil)
445@@ -312,3 +297,39 @@
446 _, err = storage.List(stor, "")
447 c.Assert(err, gc.NotNil)
448 }
449+
450+func (s *storageSuite) TestPutTmpDir(c *gc.C) {
451+ stor, storageDir := s.makeStorage(c)
452+
453+ // Put should create and clean up the temporary directory if
454+ // tmpdir==UseDefaultTmpDir.
455+ err := stor.Put("test-write", bytes.NewReader(nil), 0)
456+ c.Assert(err, gc.IsNil)
457+ _, err = os.Stat(storageDir + ".tmp")
458+ c.Assert(err, jc.Satisfies, os.IsNotExist)
459+
460+ // To deal with recovering from hard failure, UseDefaultTmpDir
461+ // doesn't care if the temporary directory already exists. It
462+ // still removes it, though.
463+ err = os.Mkdir(storageDir+".tmp", 0755)
464+ c.Assert(err, gc.IsNil)
465+ err = stor.Put("test-write", bytes.NewReader(nil), 0)
466+ c.Assert(err, gc.IsNil)
467+ _, err = os.Stat(storageDir + ".tmp")
468+ c.Assert(err, jc.Satisfies, os.IsNotExist)
469+
470+ // If we explicitly set the temporary directory, it must already exist.
471+ stor.Close()
472+ stor, err = NewSSHStorage("example.com", storageDir, storageDir+".tmp")
473+ defer stor.Close()
474+ c.Assert(err, gc.IsNil)
475+ err = stor.Put("test-write", bytes.NewReader(nil), 0)
476+ c.Assert(err, gc.ErrorMatches, "mktemp: failed to create file.*No such file or directory")
477+ err = os.Mkdir(storageDir+".tmp", 0755)
478+ c.Assert(err, gc.IsNil)
479+ err = stor.Put("test-write", bytes.NewReader(nil), 0)
480+ c.Assert(err, gc.IsNil)
481+ // Temporary directory should not have been moved.
482+ _, err = os.Stat(storageDir + ".tmp")
483+ c.Assert(err, gc.IsNil)
484+}

Subscribers

People subscribed via source and target branches

to status/vote changes: