Merge ~smoser/curtin:bug/1767979-support-tmpfs-mounts into curtin:master
- Git
- lp:~smoser/curtin
- bug/1767979-support-tmpfs-mounts
- Merge into master
Status: | Merged |
---|---|
Approved by: | Ryan Harper |
Approved revision: | 3689a17896504548ce350b3914e433783a178c37 |
Merge reported by: | Ryan Harper |
Merged at revision: | 9e6e66af224132d8af524f3f408f73d769adf3e4 |
Proposed branch: | ~smoser/curtin:bug/1767979-support-tmpfs-mounts |
Merge into: | curtin:master |
Diff against target: |
841 lines (+629/-76) 6 files modified
curtin/commands/block_meta.py (+145/-69) doc/topics/storage.rst (+53/-3) examples/tests/filesystem_battery.yaml (+23/-0) tests/unittests/helpers.py (+3/-1) tests/unittests/test_commands_block_meta.py (+356/-3) tests/vmtests/test_fs_battery.py (+49/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper (community) | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+345107@code.launchpad.net |
Commit message
Support mount entries not tied to a device, including bind and tmpfs.
Previously an entry in the storage configuration of type 'mount'
was required to have a 'device' entry that was a reference to
an id of type 'format'. That meant that there was no way to add
a mount for anything that wasn't a traditional filesystem.
The change here allows a type 'mount' entry to not have a 'device'
as long as it has a 'spec' and a 'fstype' entry. The word 'spec'
comes from fstab(5).
It also adds support for the mount type to have a 'passno' and 'freq'
which are entries in a fstab file.
LP: #1767979
Description of the change
see commit message
Scott Moser (smoser) wrote : | # |
Scott Moser (smoser) wrote : | # |
vmtest run started at https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e4814d18c90
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 87d1f5b... by Scott Moser
-
mount with -t. ramfs and tmpfs will need -t to be understood.
others (bind) specifically need *no* -t. fun.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:b3f770bd459
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- df20728... by Scott Moser
-
Create source dir in bind mounts.
- b14e065... by Scott Moser
-
add some mounts (ramfs, tmpfs, bind) to fsbattery.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:d58185d615c
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- f7d3bce... by Scott Moser
-
add some vmtests to fsbattery
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:aee72448951
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- fc2ad9b... by Scott Moser
-
add documentation
Scott Moser (smoser) wrote : | # |
vmtest-debug running at
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:540069c5776
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 9e46635... by Scott Moser
-
fix fat32 and add unit test
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:a227ea4a4e5
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 541a532... by Scott Moser
-
swap entries do not have to have a path.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:d9fad91f724
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Alberto Donato (ack) wrote : | # |
Tested this branch together with MAAS from https:/
Adding special mounts (tmpfs and ramfs) works as expected when deploying nodes.
Ryan Harper (raharper) : | # |
- 5c2fea4... by Scott Moser
-
address review feedback from Ryan
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:413ea05f37a
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
Looks great. Ill approve with
a) nits on test docstrings
b) passing vmtest run (previous debug run failed in TestNvme)
ERROR: test suite for <class 'vmtests.
with an untranslated fat32 -> vfat
[ 62.788688] cloud-init[1244]: Unexpected error while running command.
[ 62.789453] cloud-init[1244]: Command: ['mount', '-t', 'fat32', '-o', 'defaults', '/dev/vda1', '/tmp/tmpnbodfn
[ 62.792585] cloud-init[1244]: Stderr: mount: unknown filesystem type 'fat32'
- 199f25c... by Scott Moser
-
Add docstrings to all the test cases.
- 8fb88ed... by Scott Moser
-
when calling mount_data from tests, do so possibly more obviously.
instead of
block_meta.mount_ data(mnt, scfg))
use
block_meta.mount_ data(scfg[ 'm1'], scfg)) as 'mnt' was part of scfg which is the OrderedDict of mounts.
- 3689a17... by Scott Moser
-
get test coverage to 100% of new lines.
Scott Moser (smoser) wrote : | # |
ryan's inline comments are addressed.
I also added some assertRaises tests and got coverage of the new lines to 100%.
I'm running on diglett from 3689a1789650454
I'll post a log here.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:3689a178965
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
I successfully ran on diglett.
$ git rev-parse HEAD
3689a1789650454
$ git branch | grep '^*'
* (HEAD detached at smoser/
$ ( ./tools/
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
TGT_IPC_
TGT_LOG_
TGT_PID=14464
TGT_PORTAL=
http_proxy=
https_proxy=
no_proxy=
Quering synced ephemeral images/kernels in /srv/images
=======
Release Codename ImageDate Arch/SubArch Path
-------
12.04 precise 20170424 amd64/hwe-t precise/
12.04 precise 20170424.1 amd64/hwe-p precise/
14.04 trusty 20180502 amd64/hwe-t trusty/
14.04 trusty 20180502 amd64/hwe-x trusty/
14.04 trusty 20180502 i386/hwe-t trusty/
14.04 trusty 20180502 i386/hwe-x trusty/
16.04 xenial 20180511 amd64/ga-16.04 xenial/
16.04 xenial 20180511 amd64/hwe-16.04 xenial/
16.04 xenial 20180511 amd64/hwe-
16.04 xenial 20180511 i386/ga-16.04 xenial/
16.04 xenial 20180511 i386/hwe-16.04 xenial/
16.04 xenial 20180511 i386/hwe-16.04-edge xenial/
17.04 zesty 20171219 amd64/ga-17.04 zesty/amd64/
17.10 artful 20180516 amd64/ga-17.10 artful/
18.04 bionic 20180509 amd64/ga-18.04 bionic/
18.04 bionic 20180509 i386/ga-18.04 bionic/
=======
Thu, 17 May 2018 12:33:54 +0000: vmtest start: nosetests3 --process-
...
Ran 3089 tests in 13381.016s
OK (SKIP=229)
Thu, 17 May 2018 17:44:56 +0000: vmtest end [0] in 13382s
0
Ryan Harper (raharper) wrote : | # |
An upstream commit landed for this bug.
To view that commit see the following URL:
https:/
Preview Diff
1 | diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py |
2 | index c77f787..ff2ab80 100644 |
3 | --- a/curtin/commands/block_meta.py |
4 | +++ b/curtin/commands/block_meta.py |
5 | @@ -1,6 +1,6 @@ |
6 | # This file is part of curtin. See LICENSE file for copyright and license info. |
7 | |
8 | -from collections import OrderedDict |
9 | +from collections import OrderedDict, namedtuple |
10 | from curtin import (block, config, util) |
11 | from curtin.block import (bcache, mdadm, mkfs, clear_holders, lvm, iscsi, zfs) |
12 | from curtin.log import LOG |
13 | @@ -17,6 +17,12 @@ import sys |
14 | import tempfile |
15 | import time |
16 | |
17 | +FstabData = namedtuple( |
18 | + "FstabData", ('spec', 'path', 'fstype', 'options', 'freq', 'passno', |
19 | + 'device')) |
20 | +FstabData.__new__.__defaults__ = (None, None, None, "", "0", "0", None) |
21 | + |
22 | + |
23 | SIMPLE = 'simple' |
24 | SIMPLE_BOOT = 'simple-boot' |
25 | CUSTOM = 'custom' |
26 | @@ -628,6 +634,142 @@ def format_handler(info, storage_config): |
27 | udevadm_trigger([volume_path]) |
28 | |
29 | |
30 | +def mount_data(info, storage_config): |
31 | + """Return information necessary for a mount or fstab entry. |
32 | + |
33 | + :param info: a 'mount' type from storage config. |
34 | + :param storage_config: related storage_config ordered dict by id. |
35 | + |
36 | + :return FstabData type.""" |
37 | + if info.get('type') != "mount": |
38 | + raise ValueError("entry is not type 'mount' (%s)" % info) |
39 | + |
40 | + spec = info.get('spec') |
41 | + fstype = info.get('fstype') |
42 | + path = info.get('path') |
43 | + freq = str(info.get('freq', 0)) |
44 | + passno = str(info.get('passno', 0)) |
45 | + |
46 | + # turn empty options into "defaults", which works in fstab and mount -o. |
47 | + if not info.get('options'): |
48 | + options = ["defaults"] |
49 | + else: |
50 | + options = info.get('options').split(",") |
51 | + |
52 | + volume_path = None |
53 | + |
54 | + if 'device' not in info: |
55 | + missing = [m for m in ('spec', 'fstype') if not info.get(m)] |
56 | + if not (fstype and spec): |
57 | + raise ValueError( |
58 | + "mount entry without 'device' missing: %s. (%s)" % |
59 | + (missing, info)) |
60 | + |
61 | + else: |
62 | + if info['device'] not in storage_config: |
63 | + raise ValueError( |
64 | + "mount entry refers to non-existant device %s: (%s)" % |
65 | + (info['device'], info)) |
66 | + if not (fstype and spec): |
67 | + format_info = storage_config.get(info['device']) |
68 | + if not fstype: |
69 | + fstype = format_info['fstype'] |
70 | + if not spec: |
71 | + if format_info.get('volume') not in storage_config: |
72 | + raise ValueError( |
73 | + "format type refers to non-existant id %s: (%s)" % |
74 | + (format_info.get('volume'), format_info)) |
75 | + volume_path = get_path_to_storage_volume( |
76 | + format_info['volume'], storage_config) |
77 | + if "_netdev" not in options: |
78 | + if iscsi.volpath_is_iscsi(volume_path): |
79 | + options.append("_netdev") |
80 | + |
81 | + if fstype in ("fat", "fat12", "fat16", "fat32", "fat64"): |
82 | + fstype = "vfat" |
83 | + |
84 | + return FstabData( |
85 | + spec, path, fstype, ",".join(options), freq, passno, volume_path) |
86 | + |
87 | + |
88 | +def fstab_line_for_data(fdata): |
89 | + """Return a string representing fdata in /etc/fstab format. |
90 | + |
91 | + :param fdata: a FstabData type |
92 | + :return a newline terminated string for /etc/fstab.""" |
93 | + path = fdata.path |
94 | + if not path: |
95 | + if fdata.fstype == "swap": |
96 | + path = "none" |
97 | + else: |
98 | + raise ValueError("empty path in %s." % str(fdata)) |
99 | + |
100 | + if fdata.spec is None: |
101 | + if not fdata.device: |
102 | + raise ValueError("FstabData missing both spec and device.") |
103 | + uuid = block.get_volume_uuid(fdata.device) |
104 | + spec = ("UUID=%s" % uuid) if uuid else fdata.device |
105 | + else: |
106 | + spec = fdata.spec |
107 | + |
108 | + if fdata.options in (None, "", "defaults"): |
109 | + if fdata.fstype == "swap": |
110 | + options = "sw" |
111 | + else: |
112 | + options = "defaults" |
113 | + else: |
114 | + options = fdata.options |
115 | + |
116 | + return ' '.join((spec, path, fdata.fstype, options, |
117 | + fdata.freq, fdata.passno)) + "\n" |
118 | + |
119 | + |
120 | +def mount_fstab_data(fdata, target=None): |
121 | + """mount the FstabData fdata with root at target. |
122 | + |
123 | + :param fdata: a FstabData type |
124 | + :return None.""" |
125 | + mp = util.target_path(target, fdata.path) |
126 | + if fdata.device: |
127 | + device = fdata.device |
128 | + else: |
129 | + if fdata.spec.startswith("/") and not fdata.spec.startswith("/dev/"): |
130 | + device = util.target_path(target, fdata.spec) |
131 | + else: |
132 | + device = fdata.spec |
133 | + |
134 | + options = fdata.options if fdata.options else "defaults" |
135 | + |
136 | + mcmd = ['mount'] |
137 | + if fdata.fstype not in ("bind", None, "none"): |
138 | + mcmd.extend(['-t', fdata.fstype]) |
139 | + mcmd.extend(['-o', options, device, mp]) |
140 | + |
141 | + if fdata.fstype == "bind" or "bind" in options.split(","): |
142 | + # for bind mounts, create the 'src' dir (mount -o bind src target) |
143 | + util.ensure_dir(device) |
144 | + util.ensure_dir(mp) |
145 | + |
146 | + try: |
147 | + util.subp(mcmd, capture=True) |
148 | + except util.ProcessExecutionError as e: |
149 | + LOG.exception(e) |
150 | + msg = 'Mount failed: %s @ %s with options %s' % (device, mp, options) |
151 | + LOG.error(msg) |
152 | + raise RuntimeError(msg) |
153 | + |
154 | + |
155 | +def mount_apply(fdata, target=None, fstab=None): |
156 | + if fdata.fstype != "swap": |
157 | + mount_fstab_data(fdata, target=target) |
158 | + |
159 | + # Add volume to fstab |
160 | + if fstab: |
161 | + util.write_file(fstab, fstab_line_for_data(fdata), omode="a") |
162 | + else: |
163 | + LOG.info("fstab not in environment, so not writing") |
164 | + |
165 | + |
166 | def mount_handler(info, storage_config): |
167 | """ Handle storage config type: mount |
168 | |
169 | @@ -643,74 +785,8 @@ def mount_handler(info, storage_config): |
170 | fstab entry. |
171 | """ |
172 | state = util.load_command_environment() |
173 | - path = info.get('path') |
174 | - filesystem = storage_config.get(info.get('device')) |
175 | - mount_options = info.get('options') |
176 | - # handle unset, or empty('') strings |
177 | - if not mount_options: |
178 | - mount_options = 'defaults' |
179 | - |
180 | - if not path and filesystem.get('fstype') != "swap": |
181 | - raise ValueError("path to mountpoint must be specified") |
182 | - volume = storage_config.get(filesystem.get('volume')) |
183 | - |
184 | - # Get path to volume |
185 | - volume_path = get_path_to_storage_volume(filesystem.get('volume'), |
186 | - storage_config) |
187 | - |
188 | - if filesystem.get('fstype') != "swap": |
189 | - # Figure out what point should be |
190 | - while len(path) > 0 and path[0] == "/": |
191 | - path = path[1:] |
192 | - mount_point = os.path.sep.join([state['target'], path]) |
193 | - mount_point = os.path.normpath(mount_point) |
194 | - |
195 | - options = mount_options.split(",") |
196 | - # If the volume_path's kname is backed by iSCSI or (in the case of |
197 | - # LVM/DM) if any of its slaves are backed by iSCSI, then we need to |
198 | - # append _netdev to the fstab line |
199 | - if iscsi.volpath_is_iscsi(volume_path): |
200 | - LOG.debug("Marking volume_path:%s as '_netdev'", volume_path) |
201 | - options.append("_netdev") |
202 | - |
203 | - # Create mount point if does not exist |
204 | - util.ensure_dir(mount_point) |
205 | - |
206 | - # Mount volume, with options |
207 | - try: |
208 | - opts = ['-o', ','.join(options)] |
209 | - util.subp(['mount', volume_path, mount_point] + opts, capture=True) |
210 | - except util.ProcessExecutionError as e: |
211 | - LOG.exception(e) |
212 | - msg = ('Mount failed: %s @ %s with options %s' % (volume_path, |
213 | - mount_point, |
214 | - ",".join(opts))) |
215 | - LOG.error(msg) |
216 | - raise RuntimeError(msg) |
217 | - |
218 | - # set path |
219 | - path = "/%s" % path |
220 | - |
221 | - else: |
222 | - path = "none" |
223 | - options = ["sw"] |
224 | - |
225 | - # Add volume to fstab |
226 | - if state['fstab']: |
227 | - uuid = block.get_volume_uuid(volume_path) |
228 | - location = ("UUID=%s" % uuid) if uuid else ( |
229 | - get_path_to_storage_volume(volume.get('id'), |
230 | - storage_config)) |
231 | - |
232 | - fstype = filesystem.get('fstype') |
233 | - if fstype in ["fat", "fat12", "fat16", "fat32", "fat64"]: |
234 | - fstype = "vfat" |
235 | - |
236 | - fstab_entry = "%s %s %s %s 0 0\n" % (location, path, fstype, |
237 | - ",".join(options)) |
238 | - util.write_file(state['fstab'], fstab_entry, omode='a') |
239 | - else: |
240 | - LOG.info("fstab not in environment, so not writing") |
241 | + mount_apply(mount_data(info, storage_config), |
242 | + target=state.get('target'), fstab=state.get('fstab')) |
243 | |
244 | |
245 | def lvm_volgroup_handler(info, storage_config): |
246 | diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst |
247 | index 04823b9..ca6253c 100644 |
248 | --- a/doc/topics/storage.rst |
249 | +++ b/doc/topics/storage.rst |
250 | @@ -277,6 +277,8 @@ exists and will not modify the partition. |
251 | device: disk0 |
252 | flag: boot |
253 | |
254 | +.. _format: |
255 | + |
256 | Format Command |
257 | ~~~~~~~~~~~~~~ |
258 | The format command makes filesystems on a volume. The filesystem type and |
259 | @@ -369,9 +371,8 @@ in ``/dev``. |
260 | |
261 | **device**: *<device id>* |
262 | |
263 | -The ``device`` key refers to the ``id`` of the target device in the storage |
264 | -config. The target device must already contain a valid filesystem and be |
265 | -accessible. |
266 | +The ``device`` key refers to the ``id`` of a :ref:`Format <format>` entry. |
267 | +One of ``device`` or ``spec`` must be present. |
268 | |
269 | .. note:: |
270 | |
271 | @@ -379,6 +380,12 @@ accessible. |
272 | fstab entry will contain ``_netdev`` to indicate networking is |
273 | required to mount this filesystem. |
274 | |
275 | +**fstype**: *<fileystem type>* |
276 | + |
277 | +``fstype`` is only required if ``device`` is not present. It indicates |
278 | +the filesystem type and will be used for mount operations and written |
279 | +to ``/etc/fstab`` |
280 | + |
281 | **options**: *<mount(8) comma-separated options string>* |
282 | |
283 | The ``options`` key will replace the default options value of ``defaults``. |
284 | @@ -396,6 +403,14 @@ The ``options`` key will replace the default options value of ``defaults``. |
285 | If either of the environments (install or target) do not have support for |
286 | the provided options, the behavior is undefined. |
287 | |
288 | +**spec**: *<fs_spec>* |
289 | + |
290 | +The ``spec`` attribute defines the fsspec as defined in fstab(5). |
291 | +If ``spec`` is present with ``device``, then mounts will be done |
292 | +according to ``spec`` rather than determined via inspection of ``device``. |
293 | +If ``spec`` is present without ``device`` then ``fstype`` must be present. |
294 | + |
295 | + |
296 | **Config Example**:: |
297 | |
298 | - id: disk0-part1-fs1-mount0 |
299 | @@ -404,6 +419,41 @@ The ``options`` key will replace the default options value of ``defaults``. |
300 | device: disk0-part1-fs1 |
301 | options: 'noatime,errors=remount-ro' |
302 | |
303 | +**Bind Mount** |
304 | + |
305 | +Below is an example of configuring a bind mount. |
306 | + |
307 | +.. code-block:: yaml |
308 | + |
309 | + - id: bind1 |
310 | + fstype: "none" |
311 | + options: "bind" |
312 | + path: "/var/lib" |
313 | + spec: "/my/bind-over-var-lib" |
314 | + type: mount |
315 | + |
316 | +That would result in a fstab entry like:: |
317 | + |
318 | + /my/bind-over-var-lib /var/lib none bind 0 0 |
319 | + |
320 | +**Tmpfs Mount** |
321 | + |
322 | +Below is an example of configuring a tmpfsbind mount. |
323 | + |
324 | +.. code-block:: yaml |
325 | + |
326 | + - id: tmpfs1 |
327 | + type: mount |
328 | + spec: "none" |
329 | + path: "/my/tmpfs" |
330 | + options: size=4194304 |
331 | + fstype: "tmpfs" |
332 | + |
333 | +That would result in a fstab entry like:: |
334 | + |
335 | + none /my/tmpfs tmpfs size=4194304 0 0 |
336 | + |
337 | + |
338 | Lvm Volgroup Command |
339 | ~~~~~~~~~~~~~~~~~~~~ |
340 | The lvm_volgroup command creates LVM Physical Volumes (PV) and connects them in |
341 | diff --git a/examples/tests/filesystem_battery.yaml b/examples/tests/filesystem_battery.yaml |
342 | index ba4fcac..3b1edbf 100644 |
343 | --- a/examples/tests/filesystem_battery.yaml |
344 | +++ b/examples/tests/filesystem_battery.yaml |
345 | @@ -99,3 +99,26 @@ storage: |
346 | label: myxfs |
347 | volume: d2p10 |
348 | uuid: 9c537621-f2f4-4e24-a071-e05012a1a997 |
349 | + - id: tmpfs1 |
350 | + type: mount |
351 | + spec: "none" |
352 | + path: "/my/tmpfs" |
353 | + options: size=4194304 |
354 | + fstype: "tmpfs" |
355 | + - id: ramfs1 |
356 | + type: mount |
357 | + spec: "none" |
358 | + path: "/my/ramfs" |
359 | + fstype: "ramfs" |
360 | + - id: bind1 |
361 | + fstype: "none" |
362 | + options: "bind" |
363 | + path: "/var/lib" |
364 | + spec: "/my/bind-over-var-lib" |
365 | + type: mount |
366 | + - id: bind2 |
367 | + fstype: "none" |
368 | + options: "bind,ro" |
369 | + path: "/my/bind-ro-etc" |
370 | + spec: "/etc" |
371 | + type: mount |
372 | diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py |
373 | index bd07708..58e068b 100644 |
374 | --- a/tests/unittests/helpers.py |
375 | +++ b/tests/unittests/helpers.py |
376 | @@ -63,7 +63,9 @@ class CiTestCase(TestCase): |
377 | # the file is not created or modified. |
378 | if _dir is None: |
379 | _dir = self.tmp_dir() |
380 | - return os.path.normpath(os.path.abspath(os.path.join(_dir, path))) |
381 | + |
382 | + return os.path.normpath( |
383 | + os.path.abspath(os.path.sep.join((_dir, path)))) |
384 | |
385 | |
386 | def dir2dict(startdir, prefix=None): |
387 | diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py |
388 | index 5eefbd3..17186d4 100644 |
389 | --- a/tests/unittests/test_commands_block_meta.py |
390 | +++ b/tests/unittests/test_commands_block_meta.py |
391 | @@ -2,14 +2,14 @@ |
392 | |
393 | from argparse import Namespace |
394 | from collections import OrderedDict |
395 | +import copy |
396 | from mock import patch, call |
397 | +import os |
398 | |
399 | from curtin.commands import block_meta |
400 | from curtin import util |
401 | from .helpers import CiTestCase |
402 | |
403 | -import copy |
404 | - |
405 | |
406 | class TestBlockMetaSimple(CiTestCase): |
407 | def setUp(self): |
408 | @@ -323,7 +323,7 @@ class TestBlockMeta(CiTestCase): |
409 | rendered_fstab = fh.read() |
410 | |
411 | print(rendered_fstab) |
412 | - self.assertEqual(rendered_fstab, expected) |
413 | + self.assertEqual(expected, rendered_fstab) |
414 | |
415 | |
416 | class TestZpoolHandler(CiTestCase): |
417 | @@ -413,4 +413,357 @@ class TestZFSRootUpdates(CiTestCase): |
418 | with self.assertRaises(ValueError): |
419 | block_meta.zfsroot_update_storage_config(scfg) |
420 | |
421 | + |
422 | +class TestFstabData(CiTestCase): |
423 | + mnt = {'id': 'm1', 'type': 'mount', 'device': 'fs1', 'path': '/', |
424 | + 'options': 'noatime'} |
425 | + base_cfg = [ |
426 | + {'id': 'xda', 'type': 'disk', 'ptable': 'msdos'}, |
427 | + {'id': 'xda1', 'type': 'partition', 'size': '3GB', |
428 | + 'device': 'xda'}, |
429 | + {'id': 'fs1', 'type': 'format', 'fstype': 'ext4', |
430 | + 'volume': 'xda1', 'label': 'rfs'}, |
431 | + ] |
432 | + |
433 | + def _my_gptsv(self, d_id, _scfg): |
434 | + """local test replacement for get_path_to_storage_volume.""" |
435 | + if d_id in ("xda", "xda1"): |
436 | + return "/dev/" + d_id |
437 | + raise RuntimeError("Unexpected call to gptsv with %s" % d_id) |
438 | + |
439 | + def test_mount_data_raises_valueerror_if_not_mount(self): |
440 | + """mount_data on non-mount type raises ValueError.""" |
441 | + mnt = self.mnt.copy() |
442 | + mnt['type'] = "not-mount" |
443 | + with self.assertRaisesRegexp(ValueError, r".*not type 'mount'"): |
444 | + block_meta.mount_data(mnt, {mnt['id']: mnt}) |
445 | + |
446 | + def test_mount_data_no_device_or_spec_raises_valueerror(self): |
447 | + """test_mount_data raises ValueError if no device or spec.""" |
448 | + mnt = self.mnt.copy() |
449 | + del mnt['device'] |
450 | + with self.assertRaisesRegexp(ValueError, r".*mount.*missing.*"): |
451 | + block_meta.mount_data(mnt, {mnt['id']: mnt}) |
452 | + |
453 | + def test_mount_data_invalid_device_ref_raises_valueerror(self): |
454 | + """test_mount_data raises ValueError if device is invalid ref.""" |
455 | + mnt = self.mnt.copy() |
456 | + mnt['device'] = 'myinvalid' |
457 | + scfg = OrderedDict([(i['id'], i) for i in self.base_cfg + [mnt]]) |
458 | + with self.assertRaisesRegexp(ValueError, r".*refers.*myinvalid"): |
459 | + block_meta.mount_data(mnt, scfg) |
460 | + |
461 | + def test_mount_data_invalid_format_ref_raises_valueerror(self): |
462 | + """test_mount_data raises ValueError if format.volume is invalid.""" |
463 | + mycfg = copy.deepcopy(self.base_cfg) + [self.mnt.copy()] |
464 | + scfg = OrderedDict([(i['id'], i) for i in mycfg]) |
465 | + # change the 'volume' entry for the 'format' type. |
466 | + scfg['fs1']['volume'] = 'myinvalidvol' |
467 | + with self.assertRaisesRegexp(ValueError, r".*refers.*myinvalidvol"): |
468 | + block_meta.mount_data(scfg['m1'], scfg) |
469 | + |
470 | + def test_non_device_mount_with_spec(self): |
471 | + """mount_info with a spec does not need device.""" |
472 | + info = {'id': 'xm1', 'spec': 'none', 'type': 'mount', |
473 | + 'fstype': 'tmpfs', 'path': '/tmpfs'} |
474 | + self.assertEqual( |
475 | + block_meta.FstabData( |
476 | + spec="none", fstype="tmpfs", path="/tmpfs", |
477 | + options="defaults", freq="0", passno="0", device=None), |
478 | + block_meta.mount_data(info, {'xm1': info})) |
479 | + |
480 | + @patch('curtin.block.iscsi.volpath_is_iscsi') |
481 | + @patch('curtin.commands.block_meta.get_path_to_storage_volume') |
482 | + def test_device_mount_basic(self, m_gptsv, m_is_iscsi): |
483 | + """Test mount_data for FstabData with a device.""" |
484 | + m_gptsv.side_effect = self._my_gptsv |
485 | + m_is_iscsi.return_value = False |
486 | + |
487 | + scfg = OrderedDict( |
488 | + [(i['id'], i) for i in self.base_cfg + [self.mnt]]) |
489 | + self.assertEqual( |
490 | + block_meta.FstabData( |
491 | + spec=None, fstype="ext4", path="/", |
492 | + options="noatime", freq="0", passno="0", device="/dev/xda1"), |
493 | + block_meta.mount_data(scfg['m1'], scfg)) |
494 | + |
495 | + @patch('curtin.block.iscsi.volpath_is_iscsi', return_value=False) |
496 | + @patch('curtin.commands.block_meta.get_path_to_storage_volume') |
497 | + def test_device_mount_boot_efi(self, m_gptsv, m_is_iscsi): |
498 | + """Test mount_data fat fs gets converted to vfat.""" |
499 | + bcfg = copy.deepcopy(self.base_cfg) |
500 | + bcfg[2]['fstype'] = 'fat32' |
501 | + mnt = {'id': 'm1', 'type': 'mount', 'device': 'fs1', |
502 | + 'path': '/boot/efi'} |
503 | + m_gptsv.side_effect = self._my_gptsv |
504 | + |
505 | + scfg = OrderedDict( |
506 | + [(i['id'], i) for i in bcfg + [mnt]]) |
507 | + self.assertEqual( |
508 | + block_meta.FstabData( |
509 | + spec=None, fstype="vfat", path="/boot/efi", |
510 | + options="defaults", freq="0", passno="0", device="/dev/xda1"), |
511 | + block_meta.mount_data(scfg['m1'], scfg)) |
512 | + |
513 | + @patch('curtin.block.iscsi.volpath_is_iscsi') |
514 | + @patch('curtin.commands.block_meta.get_path_to_storage_volume') |
515 | + def test_device_mount_iscsi(self, m_gptsv, m_is_iscsi): |
516 | + """mount_data for a iscsi device should have _netdev in opts.""" |
517 | + m_gptsv.side_effect = self._my_gptsv |
518 | + m_is_iscsi.return_value = True |
519 | + |
520 | + scfg = OrderedDict([(i['id'], i) for i in self.base_cfg + [self.mnt]]) |
521 | + self.assertEqual( |
522 | + block_meta.FstabData( |
523 | + spec=None, fstype="ext4", path="/", |
524 | + options="noatime,_netdev", freq="0", passno="0", |
525 | + device="/dev/xda1"), |
526 | + block_meta.mount_data(scfg['m1'], scfg)) |
527 | + |
528 | + @patch('curtin.block.iscsi.volpath_is_iscsi') |
529 | + @patch('curtin.commands.block_meta.get_path_to_storage_volume') |
530 | + def test_spec_fstype_override_inline(self, m_gptsv, m_is_iscsi): |
531 | + """spec and fstype are preferred over lookups from 'device' ref. |
532 | + |
533 | + If a mount entry has 'fstype' and 'spec', those are prefered over |
534 | + values looked up via the 'device' reference present in the entry. |
535 | + The test here enforces that the device reference present in |
536 | + the mount entry is not looked up, that isn't strictly necessary. |
537 | + """ |
538 | + m_gptsv.side_effect = Exception( |
539 | + "Unexpected Call to get_path_to_storage_volume") |
540 | + m_is_iscsi.return_value = Exception( |
541 | + "Unexpected Call to volpath_is_iscsi") |
542 | + |
543 | + myspec = '/dev/disk/by-label/LABEL=rfs' |
544 | + mnt = {'id': 'm1', 'type': 'mount', 'device': 'fs1', 'path': '/', |
545 | + 'options': 'noatime', 'spec': myspec, 'fstype': 'ext3'} |
546 | + scfg = OrderedDict([(i['id'], i) for i in self.base_cfg + [mnt]]) |
547 | + self.assertEqual( |
548 | + block_meta.FstabData( |
549 | + spec=myspec, fstype="ext3", path="/", |
550 | + options="noatime", freq="0", passno="0", |
551 | + device=None), |
552 | + block_meta.mount_data(mnt, scfg)) |
553 | + |
554 | + @patch('curtin.commands.block_meta.mount_fstab_data') |
555 | + def test_mount_apply_skips_mounting_swap(self, m_mount_fstab_data): |
556 | + """mount_apply does not mount swap fs, but should write fstab.""" |
557 | + fdata = block_meta.FstabData( |
558 | + spec="/dev/xxxx1", path="none", fstype='swap') |
559 | + fstab = self.tmp_path("fstab") |
560 | + block_meta.mount_apply(fdata, fstab=fstab) |
561 | + contents = util.load_file(fstab) |
562 | + self.assertEqual(0, m_mount_fstab_data.call_count) |
563 | + self.assertIn("/dev/xxxx1", contents) |
564 | + self.assertIn("swap", contents) |
565 | + |
566 | + @patch('curtin.commands.block_meta.mount_fstab_data') |
567 | + def test_mount_apply_calls_mount_fstab_data(self, m_mount_fstab_data): |
568 | + """mount_apply should call mount_fstab_data to mount.""" |
569 | + fdata = block_meta.FstabData( |
570 | + spec="/dev/xxxx1", path="none", fstype='ext3') |
571 | + target = self.tmp_dir() |
572 | + block_meta.mount_apply(fdata, target=target, fstab=None) |
573 | + self.assertEqual([call(fdata, target=target)], |
574 | + m_mount_fstab_data.call_args_list) |
575 | + |
576 | + @patch('curtin.commands.block_meta.mount_fstab_data') |
577 | + def test_mount_apply_appends_to_fstab(self, m_mount_fstab_data): |
578 | + """mount_apply should append to fstab.""" |
579 | + fdslash = block_meta.FstabData( |
580 | + spec="/dev/disk2", path="/", fstype='ext4') |
581 | + fdboot = block_meta.FstabData( |
582 | + spec="/dev/disk1", path="/boot", fstype='ext3') |
583 | + fstab = self.tmp_path("fstab") |
584 | + existing_line = "# this is my line" |
585 | + util.write_file(fstab, existing_line + "\n") |
586 | + block_meta.mount_apply(fdslash, fstab=fstab) |
587 | + block_meta.mount_apply(fdboot, fstab=fstab) |
588 | + |
589 | + self.assertEqual(2, m_mount_fstab_data.call_count) |
590 | + lines = util.load_file(fstab).splitlines() |
591 | + self.assertEqual(existing_line, lines[0]) |
592 | + self.assertIn("/dev/disk2", lines[1]) |
593 | + self.assertIn("/dev/disk1", lines[2]) |
594 | + |
595 | + def test_fstab_line_for_data_swap(self): |
596 | + """fstab_line_for_data return value for swap fstab line.""" |
597 | + fdata = block_meta.FstabData( |
598 | + spec="/dev/disk2", path="none", fstype='swap') |
599 | + self.assertEqual( |
600 | + ["/dev/disk2", "none", "swap", "sw", "0", "0"], |
601 | + block_meta.fstab_line_for_data(fdata).split()) |
602 | + |
603 | + def test_fstab_line_for_data_swap_no_path(self): |
604 | + """fstab_line_for_data return value for swap with path=None.""" |
605 | + fdata = block_meta.FstabData( |
606 | + spec="/dev/disk2", path=None, fstype='swap') |
607 | + self.assertEqual( |
608 | + ["/dev/disk2", "none", "swap", "sw", "0", "0"], |
609 | + block_meta.fstab_line_for_data(fdata).split()) |
610 | + |
611 | + def test_fstab_line_for_data_not_swap_and_no_path(self): |
612 | + """fstab_line_for_data raises ValueError if no path and not swap.""" |
613 | + fdata = block_meta.FstabData( |
614 | + spec="/dev/disk2", device=None, path="", fstype='ext3') |
615 | + with self.assertRaisesRegexp(ValueError, r".*empty.*path"): |
616 | + block_meta.fstab_line_for_data(fdata) |
617 | + |
618 | + def test_fstab_line_for_data_with_options(self): |
619 | + """fstab_line_for_data return value with options.""" |
620 | + fdata = block_meta.FstabData( |
621 | + spec="/dev/disk2", path="/mnt", fstype='btrfs', options='noatime') |
622 | + self.assertEqual( |
623 | + ["/dev/disk2", "/mnt", "btrfs", "noatime", "0", "0"], |
624 | + block_meta.fstab_line_for_data(fdata).split()) |
625 | + |
626 | + def test_fstab_line_for_data_with_passno_and_freq(self): |
627 | + """fstab_line_for_data should respect passno and freq.""" |
628 | + fdata = block_meta.FstabData( |
629 | + spec="/dev/d1", path="/mnt", fstype='ext4', freq="1", passno="2") |
630 | + self.assertEqual( |
631 | + ["1", "2"], block_meta.fstab_line_for_data(fdata).split()[4:6]) |
632 | + |
633 | + def test_fstab_line_for_data_raises_error_without_spec_or_device(self): |
634 | + """fstab_line_for_data should raise ValueError if no spec or device.""" |
635 | + fdata = block_meta.FstabData( |
636 | + spec=None, device=None, path="/", fstype='ext3') |
637 | + match = r".*missing.*spec.*device" |
638 | + with self.assertRaisesRegexp(ValueError, match): |
639 | + block_meta.fstab_line_for_data(fdata) |
640 | + |
641 | + @patch('curtin.block.get_volume_uuid') |
642 | + def test_fstab_line_for_data_uses_uuid(self, m_get_uuid): |
643 | + """fstab_line_for_data with a device mounts by uuid.""" |
644 | + fdata = block_meta.FstabData( |
645 | + device="/dev/disk2", path="/mnt", fstype='ext4') |
646 | + uuid = 'b30d2389-5152-4fbc-8f18-0385ef3046c5' |
647 | + m_get_uuid.side_effect = lambda d: uuid if d == "/dev/disk2" else None |
648 | + self.assertEqual( |
649 | + ["UUID=%s" % uuid, "/mnt", "ext4", "defaults", "0", "0"], |
650 | + block_meta.fstab_line_for_data(fdata).split()) |
651 | + self.assertEqual(1, m_get_uuid.call_count) |
652 | + |
653 | + @patch('curtin.block.get_volume_uuid') |
654 | + def test_fstab_line_for_data_uses_device_if_no_uuid(self, m_get_uuid): |
655 | + """fstab_line_for_data with a device and no uuid uses device.""" |
656 | + fdata = block_meta.FstabData( |
657 | + device="/dev/disk2", path="/mnt", fstype='ext4') |
658 | + m_get_uuid.return_value = None |
659 | + self.assertEqual( |
660 | + ["/dev/disk2", "/mnt", "ext4", "defaults", "0", "0"], |
661 | + block_meta.fstab_line_for_data(fdata).split()) |
662 | + self.assertEqual(1, m_get_uuid.call_count) |
663 | + |
664 | + @patch('curtin.block.get_volume_uuid') |
665 | + def test_fstab_line_for_data__spec_and_dev_prefers_spec(self, m_get_uuid): |
666 | + """fstab_line_for_data should prefer spec over device.""" |
667 | + spec = "/dev/xvda1" |
668 | + fdata = block_meta.FstabData( |
669 | + spec=spec, device="/dev/disk/by-uuid/7AC9-DEFF", |
670 | + path="/mnt", fstype='ext4') |
671 | + m_get_uuid.return_value = None |
672 | + self.assertEqual( |
673 | + ["/dev/xvda1", "/mnt", "ext4", "defaults", "0", "0"], |
674 | + block_meta.fstab_line_for_data(fdata).split()) |
675 | + self.assertEqual(0, m_get_uuid.call_count) |
676 | + |
677 | + @patch('curtin.util.ensure_dir') |
678 | + @patch('curtin.util.subp') |
679 | + def test_mount_fstab_data_without_target(self, m_subp, m_ensure_dir): |
680 | + """mount_fstab_data with no target param does the right thing.""" |
681 | + fdata = block_meta.FstabData( |
682 | + device="/dev/disk1", path="/mnt", fstype='ext4') |
683 | + block_meta.mount_fstab_data(fdata) |
684 | + self.assertEqual( |
685 | + call(['mount', "-t", "ext4", "-o", "defaults", |
686 | + "/dev/disk1", "/mnt"], capture=True), |
687 | + m_subp.call_args) |
688 | + m_ensure_dir.assert_called() |
689 | + |
690 | + def _check_mount_fstab_subp(self, fdata, expected, target=None): |
691 | + # expected currently is like: mount <device> <mp> |
692 | + # and thus mp will always be target + fdata.path |
693 | + if target is None: |
694 | + target = self.tmp_dir() |
695 | + |
696 | + expected = [a if a != "_T_MP" else util.target_path(target, fdata.path) |
697 | + for a in expected] |
698 | + with patch("curtin.util.subp") as m_subp: |
699 | + block_meta.mount_fstab_data(fdata, target=target) |
700 | + |
701 | + self.assertEqual(call(expected, capture=True), m_subp.call_args) |
702 | + self.assertTrue(os.path.isdir(self.tmp_path(fdata.path, target))) |
703 | + |
704 | + def test_mount_fstab_data_with_spec_and_device(self): |
705 | + """mount_fstab_data with spec and device should use device.""" |
706 | + self._check_mount_fstab_subp( |
707 | + block_meta.FstabData( |
708 | + spec="LABEL=foo", device="/dev/disk1", path="/mnt", |
709 | + fstype='ext4'), |
710 | + ['mount', "-t", "ext4", "-o", "defaults", "/dev/disk1", "_T_MP"]) |
711 | + |
712 | + def test_mount_fstab_data_with_spec_that_is_path(self): |
713 | + """If spec is a path outside of /dev, then prefix target.""" |
714 | + target = self.tmp_dir() |
715 | + spec = "/mydata" |
716 | + self._check_mount_fstab_subp( |
717 | + block_meta.FstabData( |
718 | + spec=spec, path="/var/lib", fstype="none", options="bind"), |
719 | + ['mount', "-o", "bind", self.tmp_path(spec, target), "_T_MP"], |
720 | + target) |
721 | + |
722 | + def test_mount_fstab_data_bind_type_creates_src(self): |
723 | + """Bind mounts should have both src and target dir created.""" |
724 | + target = self.tmp_dir() |
725 | + spec = "/mydata" |
726 | + self._check_mount_fstab_subp( |
727 | + block_meta.FstabData( |
728 | + spec=spec, path="/var/lib", fstype="none", options="bind"), |
729 | + ['mount', "-o", "bind", self.tmp_path(spec, target), "_T_MP"], |
730 | + target) |
731 | + self.assertTrue(os.path.isdir(self.tmp_path(spec, target))) |
732 | + |
733 | + def test_mount_fstab_data_with_spec_that_is_device(self): |
734 | + """If spec looks like a path to a device, then use it.""" |
735 | + spec = "/dev/xxda1" |
736 | + self._check_mount_fstab_subp( |
737 | + block_meta.FstabData(spec=spec, path="/var/", fstype="ext3"), |
738 | + ['mount', "-t", "ext3", "-o", "defaults", spec, "_T_MP"]) |
739 | + |
740 | + def test_mount_fstab_data_with_device_no_spec(self): |
741 | + """mount_fstab_data mounts by spec if present, not require device.""" |
742 | + spec = "/dev/xxda1" |
743 | + self._check_mount_fstab_subp( |
744 | + block_meta.FstabData(spec=spec, path="/home", fstype="ext3"), |
745 | + ['mount', "-t", "ext3", "-o", "defaults", spec, "_T_MP"]) |
746 | + |
747 | + def test_mount_fstab_data_with_uses_options(self): |
748 | + """mount_fstab_data mounts with -o options.""" |
749 | + device = "/dev/xxda1" |
750 | + opts = "option1,option2,x=4" |
751 | + self._check_mount_fstab_subp( |
752 | + block_meta.FstabData( |
753 | + device=device, path="/var", fstype="ext3", options=opts), |
754 | + ['mount', "-t", "ext3", "-o", opts, device, "_T_MP"]) |
755 | + |
756 | + @patch('curtin.util.subp') |
757 | + def test_mount_fstab_data_does_not_swallow_subp_exception(self, m_subp): |
758 | + """verify that subp exception gets raised. |
759 | + |
760 | + The implementation there could/should change to raise the |
761 | + ProcessExecutionError directly. Currently raises a RuntimeError.""" |
762 | + my_error = util.ProcessExecutionError( |
763 | + stdout="", stderr="BOOM", exit_code=4) |
764 | + m_subp.side_effect = my_error |
765 | + |
766 | + mp = self.tmp_path("my-mountpoint") |
767 | + with self.assertRaisesRegexp(RuntimeError, r"Mount failed.*"): |
768 | + block_meta.mount_fstab_data( |
769 | + block_meta.FstabData(device="/dev/disk1", path="/var"), |
770 | + target=mp) |
771 | + # dir should be created before call to subp failed. |
772 | + self.assertTrue(os.path.isdir(mp)) |
773 | + |
774 | # vi: ts=4 expandtab syntax=python |
775 | diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py |
776 | index 5798d48..423cc1e 100644 |
777 | --- a/tests/vmtests/test_fs_battery.py |
778 | +++ b/tests/vmtests/test_fs_battery.py |
779 | @@ -52,6 +52,12 @@ class TestFsBattery(VMBaseClass): |
780 | cat /proc/partitions > proc_partitions |
781 | find /etc/network/interfaces.d > find_interfacesd |
782 | cat /proc/cmdline > cmdline |
783 | + cat /etc/fstab > fstab |
784 | + cat /proc/1/mountinfo > mountinfo |
785 | + |
786 | + for p in /my/bind-over-var-lib/apt /my/bind-ro-etc/passwd; do |
787 | + [ -e "$p" ] && echo "$p: present" || echo "$p: missing" |
788 | + done > my-path-checks |
789 | |
790 | set +x |
791 | serial="fsbattery" |
792 | @@ -151,6 +157,49 @@ class TestFsBattery(VMBaseClass): |
793 | ["%s umount: PASS" % k for k in entries]) |
794 | self.assertEqual(sorted(expected), sorted(results)) |
795 | |
796 | + def test_fstab_has_mounts(self): |
797 | + """Verify each of the expected "my" mounts got into fstab.""" |
798 | + expected = [ |
799 | + "none /my/tmpfs tmpfs size=4194304 0 0".split(), |
800 | + "none /my/ramfs ramfs defaults 0 0".split(), |
801 | + "/my/bind-over-var-lib /var/lib none bind 0 0".split(), |
802 | + "/etc /my/bind-ro-etc none bind,ro 0 0".split(), |
803 | + ] |
804 | + fstab_found = [ |
805 | + l.split() for l in self.load_collect_file("fstab").splitlines()] |
806 | + self.assertEqual(expected, [e for e in expected if e in fstab_found]) |
807 | + |
808 | + def test_mountinfo_has_mounts(self): |
809 | + """Verify the my mounts got into mountinfo. |
810 | + |
811 | + This is a light check that things got mounted. We do not check |
812 | + options as to not break on different kernel behavior. |
813 | + Maybe it could/should.""" |
814 | + # mountinfo has src and path as 4th and 5th field. |
815 | + data = self.load_collect_file("mountinfo").splitlines() |
816 | + dest_src = {} |
817 | + for line in data: |
818 | + toks = line.split() |
819 | + if not (toks[3].startswith("/my/") or toks[4].startswith("/my/")): |
820 | + continue |
821 | + dest_src[toks[4]] = toks[3] |
822 | + self.assertTrue("/my/ramfs" in dest_src) |
823 | + self.assertTrue("/my/tmpfs" in dest_src) |
824 | + self.assertEqual(dest_src.get("/var/lib"), "/my/bind-over-var-lib") |
825 | + self.assertEqual(dest_src.get("/my/bind-ro-etc"), "/etc") |
826 | + |
827 | + def test_expected_files_from_bind_mounts(self): |
828 | + data = self.load_collect_file("my-path-checks") |
829 | + # this file is <path>: (present|missing) |
830 | + paths = {} |
831 | + for line in data.splitlines(): |
832 | + path, _, val = line.partition(":") |
833 | + paths[path] = val.strip() |
834 | + |
835 | + self.assertEqual( |
836 | + {'/my/bind-over-var-lib/apt': 'present', |
837 | + '/my/bind-ro-etc/passwd': 'present'}, paths) |
838 | + |
839 | |
840 | class TrustyTestFsBattery(relbase.trusty, TestFsBattery): |
841 | __test__ = True |
I still have doc to add for sure. And i'll probably add vmtests for a few different types (bind, tmpfs)