Merge lp:~benji/lpsetup/bug-1023519-store-version-information into lp:lpsetup

Proposed by Benji York on 2012-07-12
Status: Merged
Approved by: Benji York on 2012-07-13
Approved revision: 54
Merged at revision: 53
Proposed branch: lp:~benji/lpsetup/bug-1023519-store-version-information
Merge into: lp:lpsetup
Diff against target: 146 lines (+89/-5)
2 files modified
lpsetup/subcommands/inithost.py (+39/-5)
lpsetup/tests/subcommands/test_inithost.py (+50/-0)
To merge this branch: bzr merge lp:~benji/lpsetup/bug-1023519-store-version-information
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code 2012-07-12 Approve on 2012-07-13
Review via email: mp+114736@code.launchpad.net

Commit Message

adds creation of a version directory (/var/lib/lpsetup/VERSION_NUMBER) and backing up of system files to a new "originals" directory underneath it

Description of the Change

This branch adds creation of a version directory (/var/lib/lpsetup/VERSION_NUMBER) and backing up of system files to a new "originals" directory underneath it.

This branch fixes bug 1023519 but we may want to create a new follow-up bug that contains the parts of 1023519 that were optional and this branch does not address.

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

There doesn't actually appear to be a diff here, Benji. Expected, or a commit/MP error?

52. By Benji York on 2012-07-13

add a version directory and backing up of system files we change/delete

Benji York (benji) wrote :

> There doesn't actually appear to be a diff here, Benji. Expected, or a
> commit/MP error?

Heh, yeah PEBKAC error. It should be better now.

53. By Benji York on 2012-07-13

merge from tunk, fixing conflicts

Brad Crittenden (bac) wrote :

Benji,

* It is a wee optimization in make_version_dir but since mkdirs creates parents you could eliminate the call mkdirs(version_path()) and just do mkdirs(originals). Doing so will have impact on your tests, though, as your mock mkdirs has different behavior.

* make_version_dir should utilize backup_path.

* Why explicitly use os.path.sep instead of os.path.join?

* I would expand your comment in the docstring for initialize_lxc. I had to read it a few times before realizing what you meant. Perhaps adding "so that it is only called when in a container" would make it clearer.

* In make_backup I was initially confused until i saw that backup_path is not a path but a method. Perhaps a name that would make that more obvious could be used.

Does this strategy of using the version number for storing the original files put any restrictions on how we update the version? Or is the idea that when the version changes we'll first restore the files saved in the previous version, remove the saved copies, and then proceed, applying the new workarounds under the new version number?

review: Approve (code)
Benji York (benji) wrote :

> Benji,
>
> * It is a wee optimization in make_version_dir but since mkdirs creates
> parents you could eliminate the call mkdirs(version_path()) and just do
> mkdirs(originals). Doing so will have impact on your tests, though, as your
> mock mkdirs has different behavior.

Right, I think it's a slight win to keep the version and originals
directories explicitly created.

> * make_version_dir should utilize backup_path.

Good catch! Fixed.

> * Why explicitly use os.path.sep instead of os.path.join?

I guess that bit deserves a comment. Here is the one I added:

# We don't use os.path.join here because both of the input paths are
# absolute (i.e., they start with a slash) so we instead construct the
# path ourselves in order to "re-root" the otherwise absolute source path
# in the backup directory.

> * I would expand your comment in the docstring for initialize_lxc. I had to
> read it a few times before realizing what you meant. Perhaps adding "so that
> it is only called when in a container" would make it clearer.

Good idea. Done.

> * In make_backup I was initially confused until i saw that backup_path is not
> a path but a method. Perhaps a name that would make that more obvious could
> be used.

I vacillated a bit on that. I normally prefer verb function names too.
You mentioning it has pushed me back to the other side. I changed
backup_path and version_path to be get_backup_path and get_version_path.

> Does this strategy of using the version number for storing the original files
> put any restrictions on how we update the version? Or is the idea that when
> the version changes we'll first restore the files saved in the previous
> version, remove the saved copies, and then proceed, applying the new
> workarounds under the new version number?

We aren't exactly sure how the saved files will be used, if at all. At
a minimum they provide some protection against a bug in lpsetup
catastrophically modifying a person's system. They also provide some
hint as to what lpsetup has changed (after the fact).

54. By Benji York on 2012-07-13

respond to code review by renaming some methods and adding/enchancing some
comments

Gary Poster (gary) wrote :

> Or is the idea that when
> the version changes we'll first restore the files saved in the previous
> version, remove the saved copies, and then proceed, applying the new
> workarounds under the new version number?

Yeah, that's my idea, at least.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/subcommands/inithost.py'
2--- lpsetup/subcommands/inithost.py 2012-07-12 22:12:09 +0000
3+++ lpsetup/subcommands/inithost.py 2012-07-13 15:18:33 +0000
4@@ -28,7 +28,10 @@
5 user_exists,
6 )
7
8-from lpsetup import argparser
9+from lpsetup import (
10+ argparser,
11+ get_version,
12+)
13 from lpsetup.handlers import (
14 handle_lpuser_as_username,
15 handle_ssh_keys,
16@@ -53,6 +56,31 @@
17 )
18
19
20+def get_version_path():
21+ return '/var/lib/lpsetup/' + get_version()
22+
23+
24+def get_backup_path():
25+ return os.path.join(get_version_path(), 'originals')
26+
27+
28+def make_version_dir(mkdirs=mkdirs):
29+ mkdirs(get_version_path())
30+ mkdirs(get_backup_path())
31+
32+
33+def make_backup(source, get_backup_path=get_backup_path, mkdirs=mkdirs):
34+ """Make a backup of a file in the current version's backup location."""
35+ # We don't use os.path.join here because both of the input paths are
36+ # absolute (i.e., they start with a slash) so we instead construct the
37+ # path ourselves in order to "re-root" the otherwise absolute source path
38+ # in the backup directory.
39+ destination = os.path.normpath(get_backup_path() + os.path.sep + source)
40+ destination_path = os.path.dirname(destination)
41+ mkdirs(destination_path)
42+ shutil.copyfile(source, destination)
43+
44+
45 def write_file_contents(filename, contents, mode, header=None):
46 """Write `contents` to filename and chmod it to 0644."""
47 assert mode in ('w', 'a'), "Mode should be 'w' or 'a' only."
48@@ -121,6 +149,7 @@
49 user, full_name, email, lpuser, private_key, public_key, valid_ssh_keys,
50 ssh_key_path):
51 """Initialize host machine."""
52+ make_version_dir()
53 initialize_base(user)
54 with su(user) as env:
55 ssh_dir = os.path.join(env.home, '.ssh')
56@@ -143,12 +172,17 @@
57 lines = [get_file_header()]
58 lines.extend(['{0}\t{1}\n'.format(ip, names)
59 for ip, names in HOSTS_CONTENT])
60+ make_backup(HOSTS_FILE)
61 for line in lines:
62 file_append(HOSTS_FILE, line)
63
64
65 def initialize_lxc():
66- """Initialize LXC container."""
67+ """Initialize LXC container.
68+
69+ Note that this step is guarded by the call_initialize_lxc method so that
70+ it is only called when lpsetup is running in a container.
71+ """
72 lxc_os = get_distro()
73 # XXX benji 2012-03-19 bug=959352: this is so graphviz will work in an
74 # ephemeral container
75@@ -163,10 +197,10 @@
76 # wait for udev (that is, use start on (local-filesystems)).
77 udevtrigger = '/etc/init/udevtrigger.conf'
78 if os.path.exists(udevtrigger):
79- shutil.move(udevtrigger, udevtrigger + '.orig')
80+ make_backup(udevtrigger)
81+ os.remove(udevtrigger)
82 networking = '/etc/init/networking.conf'
83- if not os.path.exists(networking + '.orig'):
84- shutil.move(networking, networking + '.orig')
85+ make_backup(networking)
86 render_to_file('networking.conf', {}, networking)
87
88
89
90=== modified file 'lpsetup/tests/subcommands/test_inithost.py'
91--- lpsetup/tests/subcommands/test_inithost.py 2012-07-12 18:23:37 +0000
92+++ lpsetup/tests/subcommands/test_inithost.py 2012-07-13 15:18:33 +0000
93@@ -162,3 +162,53 @@
94 self.ssh_dir, None, None, False, self.temp_filename)
95 self.assertTrue(os.path.exists(self.ssh_dir))
96 self.assertTrue(os.path.isdir(self.ssh_dir))
97+
98+
99+class VersionTests(unittest.TestCase):
100+ """Tests for version and upgrade data storage."""
101+
102+ def setUp(self):
103+ self.directories_made = []
104+
105+ def mkdirs(self, directory):
106+ self.directories_made.append(directory)
107+
108+ def test_version_dir_creation(self):
109+ # If the version path does not exist, it is created.
110+ inithost.make_version_dir(mkdirs=self.mkdirs)
111+ self.assertIn(inithost.get_version_path(), self.directories_made)
112+
113+ def test_originals_dir_creation(self):
114+ # If the version path contains an "originals" directory for backups of
115+ # files that we modify.
116+ inithost.make_version_dir(mkdirs=self.mkdirs)
117+ self.assertIn(inithost.get_version_path(), self.directories_made)
118+
119+
120+def create(filename, contents=None):
121+ """Create an empty file."""
122+ f = open(filename, 'a')
123+ if contents is not None:
124+ f.write(contents)
125+ f.close()
126+
127+
128+class BackupTests(unittest.TestCase):
129+ """Make sure the backup functions work correctly."""
130+
131+ def setUp(self):
132+ self.tempdir = tempfile.mkdtemp()
133+
134+ def tearDown(self):
135+ shutil.rmtree(self.tempdir, ignore_errors=True)
136+
137+ def test_backups(self):
138+ # When asked to make a backup, make_backup does.
139+ source = os.path.join(self.tempdir, 'source')
140+ destination_dir = lambda: self.tempdir
141+ contents = get_random_string()
142+ create(source, contents)
143+ inithost.make_backup(source, destination_dir)
144+ get_backup_path = destination_dir() + os.path.sep + source
145+ self.assertTrue(os.path.exists(get_backup_path))
146+ self.assertEqual(file(get_backup_path).read(), contents)

Subscribers

People subscribed via source and target branches

to all changes: