A new parameter is added to NewFileStorageWriter
to specify a temporary directory. Put will now
write to a temporary file in this location, and
then move it to the final destination; as long
as the temporary directory exists on the same
filesystem as the storage directory, this should
be atomic.
As with io/ioutil TemporaryFile, if the temporary
directory is blank, os.TempDir will be used to
get the default temporary directory.
-// newFileStorageReader returns a new storage reader for
+// NewFileStorageReader returns a new storage reader for
// a directory inside the local file system.
func NewFileStorageReader(path string) (environs.StorageReader, error) {
p := filepath.Clean(path)
@@ -85,14 +85,22 @@
type fileStorageWriter struct {
fileStorageReader
+ tmpdir string
}
-func NewFileStorageWriter(path string) (environs.Storage, error) {
+// NewFileStorageWriter returns a new read/write storag for
+// a directory inside the local file system.
+//
+// A temporary directory may be specified, in which files will be written
+// to before moving to the final destination. If specified, the temporary
+// directory should be on the same filesystem as the storage directory
+// to ensure atomicity. If left unspecified (blank), $TMPDIR will be used.
+func NewFileStorageWriter(path, tmpdir string) (environs.Storage, error) {
reader, err := NewFileStorageReader(path)
if err != nil {
return nil, err
}
- return &fileStorageWriter{*reader.(*fileStorageReader)}, nil
+ return &fileStorageWriter{*reader.(*fileStorageReader), tmpdir}, nil
}
Reviewers: mp+185715_ code.launchpad. net,
Message:
Please take a look.
Description: filestorage: optional atomic Put
environs/
As requested in review of manual-bootstrap: /code.launchpad .net/~axwalk/ juju-core/ manual- bootstrap/ +merge/ 184714
https:/
A new parameter is added to NewFileStorageW riter
to specify a temporary directory. Put will now
write to a temporary file in this location, and
then move it to the final destination; as long
as the temporary directory exists on the same
filesystem as the storage directory, this should
be atomic.
As with io/ioutil TemporaryFile, if the temporary
directory is blank, os.TempDir will be used to
get the default temporary directory.
https:/ /code.launchpad .net/~axwalk/ juju-core/ filestorage- write-tmpdir/ +merge/ 185715
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/13727043/ 20130916061421- 7r11uoyztquho14 o
Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-
+New revision: <email address hidden>
Index: cmd/juju/ synctools. go synctools. go' synctools. go 2013-09-13 04:16:42 +0000 synctools. go 2013-09-16 06:41:18 +0000
=== modified file 'cmd/juju/
--- cmd/juju/
+++ cmd/juju/
@@ -83,7 +83,7 @@
target := environ.Storage() NewFileStorageW riter(c. destination) NewFileStorageW riter(c. destination, "")
if c.destination != "" {
- target, err = filestorage.
+ target, err = filestorage.
if err != nil {
return err
}
Index: environs/ filestorage/ filestorage. go filestorage/ filestorage. go' filestorage/ filestorage. go 2013-09-12 05:04:40 +0000 filestorage/ filestorage. go 2013-09-16 06:41:18 +0000
=== modified file 'environs/
--- environs/
+++ environs/
@@ -21,7 +21,7 @@
path string
}
-// newFileStorageR eader returns a new storage reader for eader returns a new storage reader for eader(path string) (environs. StorageReader, error) { Clean(path)
+// NewFileStorageR
// a directory inside the local file system.
func NewFileStorageR
p := filepath.
@@ -85,14 +85,22 @@
type fileStorageWriter struct { eader
fileStorageR
+ tmpdir string
}
-func NewFileStorageW riter(path string) (environs.Storage, error) { riter returns a new read/write storag for riter(path, tmpdir string) (environs.Storage, error) { eader(path) ter{*reader. (*fileStorageRe ader)}, nil ter{*reader. (*fileStorageRe ader), tmpdir}, nil
+// NewFileStorageW
+// a directory inside the local file system.
+//
+// A temporary directory may be specified, in which files will be written
+// to before moving to the final destination. If specified, the temporary
+// directory should be on the same filesystem as the storage directory
+// to ensure atomicity. If left unspecified (blank), $TMPDIR will be used.
+func NewFileStorageW
reader, err := NewFileStorageR
if err != nil {
return nil, err
}
- return &fileStorageWri
+ return &fileStorageWri
}
func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) WriteFile( fullpath, data, 0644) TempFile( f.tmpdir, "juju-filestora ge-"+name) file.Name( )) file.Name( ), fullpath)
error {
@@ -101,11 +109,18 @@
if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
return err
}
- data, err := ioutil.ReadAll(r)
- if err != nil {
- return err
- }
- return ioutil.
+ // Write to a temporary file first, and then move (atomically).
+ file, err := ioutil.
+ if err != nil {
+ return err
+ }
+ _, err = io.CopyN(file, r, length)
+ file.Close()
+ if err != nil {
+ os.Remove(
+ return err
+ }
+ return os.Rename(
}
func (f *fileStorageWriter) Remove(name string) error {
Index: environs/ filestorage/ filestorage_ test.go filestorage/ filestorage_ test.go' filestorage/ filestorage_ test.go 2013-09-12 05:04:40 +0000 filestorage/ filestorage_ test.go 2013-09-16 06:41:18 +0000 NewFileStorageR eader(s. dir) NewFileStorageW riter(s. dir) NewFileStorageW riter(s. dir, "")
=== modified file 'environs/
--- environs/
+++ environs/
@@ -36,7 +36,7 @@
var err error
s.reader, err = filestorage.
c.Assert(err, gc.IsNil)
- s.writer, err = filestorage.
+ s.writer, err = filestorage.
c.Assert(err, gc.IsNil)
}
Index: environs/ tools/simplestr eams_test. go tools/simplestr eams_test. go' tools/simplestr eams_test. go 2013-09-13 02:02:59 +0000 tools/simplestr eams_test. go 2013-09-16 06:41:18 +0000 NewFileStorageW riter(dir) NewFileStorageW riter(dir, "") data(toolsList, false, writer) releases/ juju-2. 0.1-raring- amd64.tgz" ), NewFileStorageW riter(dir) NewFileStorageW riter(dir, "") data(toolsList, true, writer) NewFileStorageW riter(dir) NewFileStorageW riter(dir, "") data(existingTo olsList, true, writer)
=== modified file 'environs/
--- environs/
+++ environs/
@@ -284,7 +284,7 @@
},
}
dir := c.MkDir()
- writer, err := filestorage.
+ writer, err := filestorage.
c.Assert(err, gc.IsNil)
err = tools.WriteMeta
c.Assert(err, gc.IsNil)
@@ -311,7 +311,7 @@
URL: "file://" +
filepath.Join(dir, "tools/
},
}
- writer, err := filestorage.
+ writer, err := filestorage.
c.Assert(err, gc.IsNil)
err = tools.WriteMeta
c.Assert(err, gc.IsNil)
@@ -332,7 +332,7 @@
SHA256: "xyz",
},
}
- writer, err := filestorage.
+ writer, err := filestorage.
c.Assert(err, gc.IsNil)
err = tools.WriteMeta
c.Assert(err, gc.IsNil)
Index: cmd/plugins/ juju-metadata/ toolsmetadata. go juju-metadata/ toolsmetadata. go' juju-metadata/ toolsmetadata. go 2013-09-12 05:04:40 +0000 juju-metadata/ toolsmetadata. go 2013-09-16 06:41:18 +0000
=== modified file 'cmd/plugins/
--- cmd/plugins/
+++ cmd/plugins/
@@ -69,7 +69,7 @@
return err
}
- targetStorage, err := filestorage. NewFileStorageW riter(c. metadataDir) NewFileStorageW riter(c. metadataDir, "")
+ targetStorage, err := filestorage.
if err != nil {
return err
}