Merge lp:~larsks/cloud-init/fix-systemd-detection into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Lars Kellogg-Stedman
Status: Merged
Merged at revision: 1112
Proposed branch: lp:~larsks/cloud-init/fix-systemd-detection
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 102 lines (+37/-12)
4 files modified
cloudinit/distros/__init__.py (+8/-0)
cloudinit/distros/rhel.py (+0/-8)
tests/unittests/helpers.py (+6/-4)
tests/unittests/test_distros/test_generic.py (+23/-0)
To merge this branch: bzr merge lp:~larsks/cloud-init/fix-systemd-detection
Reviewer Review Type Date Requested Status
Dan Watkins Approve
Joshua Harlow Pending
Review via email: mp+260885@code.launchpad.net

Description of the change

check for systemd using sd_booted() semantics

The existing cloud-init code determines if systemd is in use by looking at the
distribution name and version. This is prone to error because:

- RHEL derivatives other than CentOS (e.g., Scientific Linux) will fail this test, and
- Distributions that are not derived from RHEL also use systemd

This patch makes cloud-init use the same logic that is used in systemd's
sd_booted() method
(http://www.freedesktop.org/software/systemd/man/sd_booted.html)

To post a comment you must log in.
Revision history for this message
Dan Watkins (oddbloke) wrote :

Hi Lars,

Thanks for taking the time to do this, this looks like a good improvement.

Some tests for this would be good, to ensure that we don't regress here[0]. TestGenericDistro in tests/unittests/test_distros/test_generic.py already has the means to do filesystem mocking in place, so it shouldn't be too painful.

Dan

[0] If I weren't looking at the code too carefully, I'd replace what you've written with os.path.isdir; but that would be incorrect because it follows symbolic links. Having something that makes us notice if someone does this would be valuable.

1112. By Lars Kellogg-Stedman

transform paths in functions taking more than a single argument

Patch FilesystemMockingTestcase.patchOS to support methods taking more
than a single path argument. This is required in order to properly mock
`os.symlink`, which takes two path arguments.

1113. By Lars Kellogg-Stedman

add tests for systemd detection

This adds the following tests in test_distros.test_generic:

- test_systemd_in_use

  Test the situation in which /run/systemd/system exists.

- test_systemd_not_in_use

  Test the situation in which /run/systemd/system does not exists.

- test_systemd_symlink

  This tests the situation in which /run/systemd/system exists but is a
  *symlink* to a directory, which according to sd_booted() should return
  false.

Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

Dan,

Thanks for the review. I hope this addresses your suggestions.

Revision history for this message
Dan Watkins (oddbloke) wrote :

Looks good, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/distros/__init__.py'
2--- cloudinit/distros/__init__.py 2015-05-19 15:21:34 +0000
3+++ cloudinit/distros/__init__.py 2015-06-03 17:20:32 +0000
4@@ -27,6 +27,7 @@
5 import abc
6 import os
7 import re
8+import stat
9
10 from cloudinit import importer
11 from cloudinit import log as logging
12@@ -89,6 +90,13 @@
13 self._write_hostname(writeable_hostname, self.hostname_conf_fn)
14 self._apply_hostname(writeable_hostname)
15
16+ def uses_systemd(self):
17+ try:
18+ res = os.lstat('/run/systemd/system')
19+ return stat.S_ISDIR(res.st_mode)
20+ except:
21+ return False
22+
23 @abc.abstractmethod
24 def package_command(self, cmd, args=None, pkgs=None):
25 raise NotImplementedError()
26
27=== modified file 'cloudinit/distros/rhel.py'
28--- cloudinit/distros/rhel.py 2015-05-15 20:28:24 +0000
29+++ cloudinit/distros/rhel.py 2015-06-03 17:20:32 +0000
30@@ -111,14 +111,6 @@
31 rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg)
32 return dev_names
33
34- def uses_systemd(self):
35- # Fedora 18 and RHEL 7 were the first adopters in their series
36- (dist, vers) = util.system_info()['dist'][:2]
37- major = (int)(vers.split('.')[0])
38- return ((dist.startswith('Red Hat Enterprise Linux') and major >= 7)
39- or (dist.startswith('CentOS Linux') and major >= 7)
40- or (dist.startswith('Fedora') and major >= 18))
41-
42 def apply_locale(self, locale, out_fn=None):
43 if self.uses_systemd():
44 if not out_fn:
45
46=== modified file 'tests/unittests/helpers.py'
47--- tests/unittests/helpers.py 2015-03-04 12:29:29 +0000
48+++ tests/unittests/helpers.py 2015-06-03 17:20:32 +0000
49@@ -248,13 +248,15 @@
50
51 def patchOS(self, new_root):
52 patch_funcs = {
53- os.path: ['isfile', 'exists', 'islink', 'isdir'],
54- os: ['listdir'],
55+ os.path: [('isfile', 1), ('exists', 1),
56+ ('islink', 1), ('isdir', 1)],
57+ os: [('listdir', 1), ('mkdir', 1),
58+ ('lstat', 1), ('symlink', 2)],
59 }
60 for (mod, funcs) in patch_funcs.items():
61- for f in funcs:
62+ for f, nargs in funcs:
63 func = getattr(mod, f)
64- trap_func = retarget_many_wrapper(new_root, 1, func)
65+ trap_func = retarget_many_wrapper(new_root, nargs, func)
66 self.patched_funcs.enter_context(
67 mock.patch.object(mod, f, trap_func))
68
69
70=== modified file 'tests/unittests/test_distros/test_generic.py'
71--- tests/unittests/test_distros/test_generic.py 2015-01-23 00:53:04 +0000
72+++ tests/unittests/test_distros/test_generic.py 2015-06-03 17:20:32 +0000
73@@ -194,6 +194,29 @@
74 {'primary': 'http://fs-primary-intel',
75 'security': 'http://security-mirror2-intel'})
76
77+ def test_systemd_in_use(self):
78+ cls = distros.fetch("ubuntu")
79+ d = cls("ubuntu", {}, None)
80+ self.patchOS(self.tmp)
81+ self.patchUtils(self.tmp)
82+ os.makedirs('/run/systemd/system')
83+ self.assertTrue(d.uses_systemd())
84+
85+ def test_systemd_not_in_use(self):
86+ cls = distros.fetch("ubuntu")
87+ d = cls("ubuntu", {}, None)
88+ self.patchOS(self.tmp)
89+ self.patchUtils(self.tmp)
90+ self.assertFalse(d.uses_systemd())
91+
92+ def test_systemd_symlink(self):
93+ cls = distros.fetch("ubuntu")
94+ d = cls("ubuntu", {}, None)
95+ self.patchOS(self.tmp)
96+ self.patchUtils(self.tmp)
97+ os.makedirs('/run/systemd')
98+ os.symlink('/', '/run/systemd/system')
99+ self.assertFalse(d.uses_systemd())
100
101 # def _get_package_mirror_info(mirror_info, availability_zone=None,
102 # mirror_filter=util.search_for_mirror):