Merge ~larsks/cloud-init:bz/1542578 into cloud-init:master
- Git
- lp:~larsks/cloud-init
- bz/1542578
- Merge into master
Status: | Merged |
---|---|
Approved by: | Scott Moser |
Approved revision: | e65b8058aa663c096de90854b56e98f390717636 |
Merge reported by: | Scott Moser |
Merged at revision: | 529d48f69d3784b2314397f5eab9d750ab03cf6a |
Proposed branch: | ~larsks/cloud-init:bz/1542578 |
Merge into: | cloud-init:master |
Diff against target: |
263 lines (+136/-25) 3 files modified
cloudinit/config/cc_mounts.py (+34/-19) cloudinit/tests/helpers.py (+2/-2) tests/unittests/test_handler/test_handler_mounts.py (+100/-4) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Approve | ||
Chad Smith | Approve | ||
Review via email: mp+345113@code.launchpad.net |
Commit message
cc_mounts: Do not add devices to fstab that are already present.
Do not add new entries to /etc/fstab for devices that already have an
existing fstab entry.
Resolves: rhbz#1542578
Description of the change
We had a customer who was booting images in OpenStack that had a pre-configured swap partition. Cloud-init was adding a second entry for the same partition. This commit resolves the problem by parsing device names from /etc/fstab and declining to generate new entries for devices that already exist.
As with the existing code, entries with the "comment=
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
Thats my first quick pass.
This code is really in need of re-factoring.
And could really use unit tests.
Lars Kellogg-Stedman (larsks) wrote : | # |
I agree re: refactoring, but I'm not sure I'm up to that right now. I'll make the changes you suggested and I'll add a test or two.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:acd63782c1c
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Lars Kellogg-Stedman (larsks) wrote : | # |
I've added a few unit tests for the fstab handling code.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:62a5986a8d8
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:74671dc8366
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Lars Kellogg-Stedman (larsks) wrote : | # |
Ping @smoser; let me know if this looks okay. Thanks!
Scott Moser (smoser) wrote : | # |
Lars, thank you, and prefectly fine to poke me.
I'll try to take a look at this on monday.
one comment in line about some infrastructure that can make this easier.
Thank you for adding some tests!
Scott
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:dd61338ba19
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:68c2ca587a0
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) : | # |
Lars Kellogg-Stedman (larsks) wrote : | # |
Updated to address Chad's comments.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:759107db5ad
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:5bd9e873de6
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
This looks good to me. Thanks for the changes Lars!
Scott Moser (smoser) wrote : | # |
Lars, can you rebase this ... it has conflicts on head and wont land.
Lars Kellogg-Stedman (larsks) wrote : | # |
I've rebased this on b4ae0e1fb8a48a8
Scott Moser (smoser) wrote : | # |
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e65b8058aa6
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
An upstream commit landed for this bug.
To view that commit see the following URL:
https:/
Preview Diff
1 | diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py |
2 | index b3b25c3..eca6ea3 100644 |
3 | --- a/cloudinit/config/cc_mounts.py |
4 | +++ b/cloudinit/config/cc_mounts.py |
5 | @@ -76,6 +76,7 @@ DEVICE_NAME_FILTER = r"^([x]{0,1}[shv]d[a-z][0-9]*|sr[0-9]+)$" |
6 | DEVICE_NAME_RE = re.compile(DEVICE_NAME_FILTER) |
7 | WS = re.compile("[%s]+" % (whitespace)) |
8 | FSTAB_PATH = "/etc/fstab" |
9 | +MNT_COMMENT = "comment=cloudconfig" |
10 | |
11 | LOG = logging.getLogger(__name__) |
12 | |
13 | @@ -327,6 +328,22 @@ def handle(_name, cfg, cloud, log, _args): |
14 | |
15 | LOG.debug("mounts configuration is %s", cfgmnt) |
16 | |
17 | + fstab_lines = [] |
18 | + fstab_devs = {} |
19 | + fstab_removed = [] |
20 | + |
21 | + for line in util.load_file(FSTAB_PATH).splitlines(): |
22 | + if MNT_COMMENT in line: |
23 | + fstab_removed.append(line) |
24 | + continue |
25 | + |
26 | + try: |
27 | + toks = WS.split(line) |
28 | + except Exception: |
29 | + pass |
30 | + fstab_devs[toks[0]] = line |
31 | + fstab_lines.append(line) |
32 | + |
33 | for i in range(len(cfgmnt)): |
34 | # skip something that wasn't a list |
35 | if not isinstance(cfgmnt[i], list): |
36 | @@ -336,12 +353,17 @@ def handle(_name, cfg, cloud, log, _args): |
37 | |
38 | start = str(cfgmnt[i][0]) |
39 | sanitized = sanitize_devname(start, cloud.device_name_to_device, log) |
40 | + if sanitized != start: |
41 | + log.debug("changed %s => %s" % (start, sanitized)) |
42 | + |
43 | if sanitized is None: |
44 | log.debug("Ignoring nonexistent named mount %s", start) |
45 | continue |
46 | + elif sanitized in fstab_devs: |
47 | + log.info("Device %s already defined in fstab: %s", |
48 | + sanitized, fstab_devs[sanitized]) |
49 | + continue |
50 | |
51 | - if sanitized != start: |
52 | - log.debug("changed %s => %s" % (start, sanitized)) |
53 | cfgmnt[i][0] = sanitized |
54 | |
55 | # in case the user did not quote a field (likely fs-freq, fs_passno) |
56 | @@ -373,11 +395,17 @@ def handle(_name, cfg, cloud, log, _args): |
57 | for defmnt in defmnts: |
58 | start = defmnt[0] |
59 | sanitized = sanitize_devname(start, cloud.device_name_to_device, log) |
60 | + if sanitized != start: |
61 | + log.debug("changed default device %s => %s" % (start, sanitized)) |
62 | + |
63 | if sanitized is None: |
64 | log.debug("Ignoring nonexistent default named mount %s", start) |
65 | continue |
66 | - if sanitized != start: |
67 | - log.debug("changed default device %s => %s" % (start, sanitized)) |
68 | + elif sanitized in fstab_devs: |
69 | + log.debug("Device %s already defined in fstab: %s", |
70 | + sanitized, fstab_devs[sanitized]) |
71 | + continue |
72 | + |
73 | defmnt[0] = sanitized |
74 | |
75 | cfgmnt_has = False |
76 | @@ -409,31 +437,18 @@ def handle(_name, cfg, cloud, log, _args): |
77 | log.debug("No modifications to fstab needed") |
78 | return |
79 | |
80 | - comment = "comment=cloudconfig" |
81 | cc_lines = [] |
82 | needswap = False |
83 | dirs = [] |
84 | for line in actlist: |
85 | # write 'comment' in the fs_mntops, entry, claiming this |
86 | - line[3] = "%s,%s" % (line[3], comment) |
87 | + line[3] = "%s,%s" % (line[3], MNT_COMMENT) |
88 | if line[2] == "swap": |
89 | needswap = True |
90 | if line[1].startswith("/"): |
91 | dirs.append(line[1]) |
92 | cc_lines.append('\t'.join(line)) |
93 | |
94 | - fstab_lines = [] |
95 | - removed = [] |
96 | - for line in util.load_file(FSTAB_PATH).splitlines(): |
97 | - try: |
98 | - toks = WS.split(line) |
99 | - if toks[3].find(comment) != -1: |
100 | - removed.append(line) |
101 | - continue |
102 | - except Exception: |
103 | - pass |
104 | - fstab_lines.append(line) |
105 | - |
106 | for d in dirs: |
107 | try: |
108 | util.ensure_dir(d) |
109 | @@ -441,7 +456,7 @@ def handle(_name, cfg, cloud, log, _args): |
110 | util.logexc(log, "Failed to make '%s' config-mount", d) |
111 | |
112 | sadds = [WS.sub(" ", n) for n in cc_lines] |
113 | - sdrops = [WS.sub(" ", n) for n in removed] |
114 | + sdrops = [WS.sub(" ", n) for n in fstab_removed] |
115 | |
116 | sops = (["- " + drop for drop in sdrops if drop not in sadds] + |
117 | ["+ " + add for add in sadds if add not in sdrops]) |
118 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py |
119 | index 117a9cf..07059fd 100644 |
120 | --- a/cloudinit/tests/helpers.py |
121 | +++ b/cloudinit/tests/helpers.py |
122 | @@ -111,12 +111,12 @@ class TestCase(unittest2.TestCase): |
123 | super(TestCase, self).setUp() |
124 | self.reset_global_state() |
125 | |
126 | - def add_patch(self, target, attr, **kwargs): |
127 | + def add_patch(self, target, attr, *args, **kwargs): |
128 | """Patches specified target object and sets it as attr on test |
129 | instance also schedules cleanup""" |
130 | if 'autospec' not in kwargs: |
131 | kwargs['autospec'] = True |
132 | - m = mock.patch(target, **kwargs) |
133 | + m = mock.patch(target, *args, **kwargs) |
134 | p = m.start() |
135 | self.addCleanup(m.stop) |
136 | setattr(self, attr, p) |
137 | diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py |
138 | index fe492d4..8fea6c2 100644 |
139 | --- a/tests/unittests/test_handler/test_handler_mounts.py |
140 | +++ b/tests/unittests/test_handler/test_handler_mounts.py |
141 | @@ -1,8 +1,6 @@ |
142 | # This file is part of cloud-init. See LICENSE file for license information. |
143 | |
144 | import os.path |
145 | -import shutil |
146 | -import tempfile |
147 | |
148 | from cloudinit.config import cc_mounts |
149 | |
150 | @@ -18,8 +16,7 @@ class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): |
151 | |
152 | def setUp(self): |
153 | super(TestSanitizeDevname, self).setUp() |
154 | - self.new_root = tempfile.mkdtemp() |
155 | - self.addCleanup(shutil.rmtree, self.new_root) |
156 | + self.new_root = self.tmp_dir() |
157 | self.patchOS(self.new_root) |
158 | |
159 | def _touch(self, path): |
160 | @@ -134,4 +131,103 @@ class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): |
161 | cc_mounts.sanitize_devname( |
162 | 'ephemeral0.1', lambda x: disk_path, mock.Mock())) |
163 | |
164 | + |
165 | +class TestFstabHandling(test_helpers.FilesystemMockingTestCase): |
166 | + |
167 | + swap_path = '/dev/sdb1' |
168 | + |
169 | + def setUp(self): |
170 | + super(TestFstabHandling, self).setUp() |
171 | + self.new_root = self.tmp_dir() |
172 | + self.patchOS(self.new_root) |
173 | + |
174 | + self.fstab_path = os.path.join(self.new_root, 'etc/fstab') |
175 | + self._makedirs('/etc') |
176 | + |
177 | + self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH', |
178 | + 'mock_fstab_path', |
179 | + self.fstab_path, |
180 | + autospec=False) |
181 | + |
182 | + self.add_patch('cloudinit.config.cc_mounts._is_block_device', |
183 | + 'mock_is_block_device', |
184 | + return_value=True) |
185 | + |
186 | + self.add_patch('cloudinit.config.cc_mounts.util.subp', |
187 | + 'mock_util_subp') |
188 | + |
189 | + self.mock_cloud = mock.Mock() |
190 | + self.mock_log = mock.Mock() |
191 | + self.mock_cloud.device_name_to_device = self.device_name_to_device |
192 | + |
193 | + def _makedirs(self, directory): |
194 | + directory = os.path.join(self.new_root, directory.lstrip('/')) |
195 | + if not os.path.exists(directory): |
196 | + os.makedirs(directory) |
197 | + |
198 | + def device_name_to_device(self, path): |
199 | + if path == 'swap': |
200 | + return self.swap_path |
201 | + else: |
202 | + dev = None |
203 | + |
204 | + return dev |
205 | + |
206 | + def test_fstab_no_swap_device(self): |
207 | + '''Ensure that cloud-init adds a discovered swap partition |
208 | + to /etc/fstab.''' |
209 | + |
210 | + fstab_original_content = '' |
211 | + fstab_expected_content = ( |
212 | + '%s\tnone\tswap\tsw,comment=cloudconfig\t' |
213 | + '0\t0\n' % (self.swap_path,) |
214 | + ) |
215 | + |
216 | + with open(cc_mounts.FSTAB_PATH, 'w') as fd: |
217 | + fd.write(fstab_original_content) |
218 | + |
219 | + cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) |
220 | + |
221 | + with open(cc_mounts.FSTAB_PATH, 'r') as fd: |
222 | + fstab_new_content = fd.read() |
223 | + self.assertEqual(fstab_expected_content, fstab_new_content) |
224 | + |
225 | + def test_fstab_same_swap_device_already_configured(self): |
226 | + '''Ensure that cloud-init will not add a swap device if the same |
227 | + device already exists in /etc/fstab.''' |
228 | + |
229 | + fstab_original_content = '%s swap swap defaults 0 0\n' % ( |
230 | + self.swap_path,) |
231 | + fstab_expected_content = fstab_original_content |
232 | + |
233 | + with open(cc_mounts.FSTAB_PATH, 'w') as fd: |
234 | + fd.write(fstab_original_content) |
235 | + |
236 | + cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) |
237 | + |
238 | + with open(cc_mounts.FSTAB_PATH, 'r') as fd: |
239 | + fstab_new_content = fd.read() |
240 | + self.assertEqual(fstab_expected_content, fstab_new_content) |
241 | + |
242 | + def test_fstab_alternate_swap_device_already_configured(self): |
243 | + '''Ensure that cloud-init will add a discovered swap device to |
244 | + /etc/fstab even when there exists a swap definition on another |
245 | + device.''' |
246 | + |
247 | + fstab_original_content = '/dev/sdc1 swap swap defaults 0 0\n' |
248 | + fstab_expected_content = ( |
249 | + fstab_original_content + |
250 | + '%s\tnone\tswap\tsw,comment=cloudconfig\t' |
251 | + '0\t0\n' % (self.swap_path,) |
252 | + ) |
253 | + |
254 | + with open(cc_mounts.FSTAB_PATH, 'w') as fd: |
255 | + fd.write(fstab_original_content) |
256 | + |
257 | + cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) |
258 | + |
259 | + with open(cc_mounts.FSTAB_PATH, 'r') as fd: |
260 | + fstab_new_content = fd.read() |
261 | + self.assertEqual(fstab_expected_content, fstab_new_content) |
262 | + |
263 | # vi: ts=4 expandtab |
PASSED: Continuous integration, rev:288fbfe09ef 5846547e9621225 2c7e8cb8d53fd7 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1102/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1102/rebuild
https:/