Merge lp:~ubuntudotcom1/maas/bug-1443346-atomic-write-copy-ownership into lp:~maas-committers/maas/trunk

Proposed by ubuntudotcom1
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 3832
Proposed branch: lp:~ubuntudotcom1/maas/bug-1443346-atomic-write-copy-ownership
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 106 lines (+44/-6)
2 files modified
src/provisioningserver/utils/fs.py (+18/-3)
src/provisioningserver/utils/tests/test_fs.py (+26/-3)
To merge this branch: bzr merge lp:~ubuntudotcom1/maas/bug-1443346-atomic-write-copy-ownership
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Mike Pontillo (community) Approve
Review via email: mp+255972@code.launchpad.net

Commit message

Make utils.fs.atomic_write() preserve file ownership details if file previously exists.

Description of the change

Make utils.fs.atomic_write() preserve file ownership details if file previously exists.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good, but one question below.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good. Lots of comments, most of which we've discussed in person.

review: Needs Fixing
Revision history for this message
ubuntudotcom1 (ubuntudotcom1) wrote :

Address comments.

Revision history for this message
Gavin Panella (allenap) wrote :

This looks great. Some more comments, but it's basically good to land.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/utils/fs.py'
2--- src/provisioningserver/utils/fs.py 2015-04-03 16:35:20 +0000
3+++ src/provisioningserver/utils/fs.py 2015-04-15 12:05:23 +0000
4@@ -29,7 +29,12 @@
5 import hashlib
6 from itertools import count
7 import os
8-from os import environ
9+from os import (
10+ environ,
11+ rename,
12+ stat,
13+ chown,
14+)
15 from os.path import isdir
16 from random import randint
17 from shutil import rmtree
18@@ -101,15 +106,25 @@
19 """
20 temp_file = _write_temp_file(content, filename)
21 os.chmod(temp_file, mode)
22+
23+ # Copy over ownership attributes if file exists
24+ try:
25+ prev_stats = stat(filename)
26+ except OSError as error:
27+ if error.errno != errno.ENOENT:
28+ raise # Something's seriously wrong.
29+ else:
30+ chown(temp_file, prev_stats.st_uid, prev_stats.st_gid)
31+
32 try:
33 if overwrite:
34- os.rename(temp_file, filename)
35+ rename(temp_file, filename)
36 else:
37 lock = FileLock(filename)
38 lock.acquire()
39 try:
40 if not os.path.isfile(filename):
41- os.rename(temp_file, filename)
42+ rename(temp_file, filename)
43 finally:
44 lock.release()
45 finally:
46
47=== modified file 'src/provisioningserver/utils/tests/test_fs.py'
48--- src/provisioningserver/utils/tests/test_fs.py 2015-04-03 15:48:28 +0000
49+++ src/provisioningserver/utils/tests/test_fs.py 2015-04-15 12:05:23 +0000
50@@ -32,7 +32,11 @@
51 from maastesting.fakemethod import FakeMethod
52 from maastesting.matchers import MockCalledOnceWith
53 from maastesting.testcase import MAASTestCase
54-from mock import Mock
55+from mock import (
56+ ANY,
57+ Mock,
58+ sentinel,
59+)
60 from provisioningserver.utils.fs import (
61 atomic_symlink,
62 atomic_write,
63@@ -94,7 +98,7 @@
64 def test_atomic_write_does_not_leak_temp_file_on_failure(self):
65 # If the overwrite fails, atomic_write does not leak its
66 # temporary file.
67- self.patch(os, 'rename', Mock(side_effect=OSError()))
68+ self.patch(fs_module, 'rename', Mock(side_effect=OSError()))
69 filename = self.make_file()
70 with ExpectedException(OSError):
71 atomic_write(factory.make_string(), filename)
72@@ -119,7 +123,7 @@
73 """Stub for os.rename: get source file's access mode."""
74 recorded_modes.append(os.stat(source).st_mode)
75
76- self.patch(os, 'rename', Mock(side_effect=record_mode))
77+ self.patch(fs_module, 'rename', Mock(side_effect=record_mode))
78 playground = self.make_dir()
79 atomic_file = os.path.join(playground, factory.make_name('atomic'))
80 mode = 0o323
81@@ -127,6 +131,25 @@
82 [recorded_mode] = recorded_modes
83 self.assertEqual(mode, stat.S_IMODE(recorded_mode))
84
85+ def test_atomic_write_preserves_ownership_before_moving_into_place(self):
86+ atomic_file = self.make_file('atomic')
87+
88+ self.patch(fs_module, 'isfile').return_value = True
89+ self.patch(fs_module, 'chown')
90+ self.patch(fs_module, 'rename')
91+ self.patch(fs_module, 'stat')
92+
93+ ret_stat = fs_module.stat.return_value
94+ ret_stat.st_uid = sentinel.uid
95+ ret_stat.st_gid = sentinel.gid
96+ ret_stat.st_mode = stat.S_IFREG
97+
98+ atomic_write(factory.make_string(), atomic_file)
99+
100+ self.assertThat(fs_module.stat, MockCalledOnceWith(atomic_file))
101+ self.assertThat(fs_module.chown, MockCalledOnceWith(
102+ ANY, sentinel.uid, sentinel.gid))
103+
104 def test_atomic_write_sets_OSError_filename_if_undefined(self):
105 # When the filename attribute of an OSError is undefined when
106 # attempting to create a temporary file, atomic_write fills it in with