1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import bitbake, runqemu
9from oeqa.core.decorator import OETestTag
10from oeqa.core.decorator.data import skipIfNotMachine
11
12def getline_qemu(out, line):
13    for l in out.split('\n'):
14        if line in l:
15            return l
16
17def getline(res, line):
18    return getline_qemu(res.output, line)
19
20class OverlayFSTests(OESelftestTestCase):
21    """Overlayfs class usage tests"""
22
23    def add_overlay_conf_to_machine(self):
24        machine_inc = """
25OVERLAYFS_MOUNT_POINT[mnt-overlay] = "/mnt/overlay"
26"""
27        self.set_machine_config(machine_inc)
28
29    def test_distro_features_missing(self):
30        """
31        Summary:   Check that required DISTRO_FEATURES are set
32        Expected:  Fail when either systemd or overlayfs are not in DISTRO_FEATURES
33        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
34        """
35
36        config = """
37IMAGE_INSTALL:append = " overlayfs-user"
38"""
39        overlayfs_recipe_append = """
40inherit overlayfs
41"""
42        self.write_config(config)
43        self.add_overlay_conf_to_machine()
44        self.write_recipeinc('overlayfs-user', overlayfs_recipe_append)
45
46        res = bitbake('core-image-minimal', ignore_status=True)
47        line = getline(res, "overlayfs-user was skipped: missing required distro features")
48        self.assertTrue("overlayfs" in res.output, msg=res.output)
49        self.assertTrue("systemd" in res.output, msg=res.output)
50        self.assertTrue("ERROR: Required build target 'core-image-minimal' has no buildable providers." in res.output, msg=res.output)
51
52    def test_not_all_units_installed(self):
53        """
54        Summary:   Test QA check that we have required mount units in the image
55        Expected:  Fail because mount unit for overlay partition is not installed
56        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
57        """
58
59        config = """
60IMAGE_INSTALL:append = " overlayfs-user"
61DISTRO_FEATURES:append = " systemd overlayfs usrmerge"
62"""
63
64        self.write_config(config)
65        self.add_overlay_conf_to_machine()
66
67        res = bitbake('core-image-minimal', ignore_status=True)
68        line = getline(res, " Mount path /mnt/overlay not found in fstab and unit mnt-overlay.mount not found in systemd unit directories")
69        self.assertTrue(line and line.startswith("WARNING:"), msg=res.output)
70        line = getline(res, "Not all mount paths and units are installed in the image")
71        self.assertTrue(line and line.startswith("ERROR:"), msg=res.output)
72
73    def test_not_all_units_installed_but_qa_skipped(self):
74        """
75        Summary:   Test skipping the QA check
76        Expected:  Image is created successfully
77        Author:    Claudius Heine <ch@denx.de>
78        """
79
80        config = """
81IMAGE_INSTALL:append = " overlayfs-user"
82DISTRO_FEATURES:append = " systemd overlayfs usrmerge"
83OVERLAYFS_QA_SKIP[mnt-overlay] = "mount-configured"
84"""
85
86        self.write_config(config)
87        self.add_overlay_conf_to_machine()
88
89        bitbake('core-image-minimal')
90
91    def test_mount_unit_not_set(self):
92        """
93        Summary:   Test whether mount unit was set properly
94        Expected:  Fail because mount unit was not set
95        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
96        """
97
98        config = """
99IMAGE_INSTALL:append = " overlayfs-user"
100DISTRO_FEATURES:append = " systemd overlayfs usrmerge"
101"""
102
103        self.write_config(config)
104
105        res = bitbake('core-image-minimal', ignore_status=True)
106        line = getline(res, "A recipe uses overlayfs class but there is no OVERLAYFS_MOUNT_POINT set in your MACHINE configuration")
107        self.assertTrue(line and line.startswith("Parsing recipes...ERROR:"), msg=res.output)
108
109    def test_wrong_mount_unit_set(self):
110        """
111        Summary:   Test whether mount unit was set properly
112        Expected:  Fail because not the correct flag used for mount unit
113        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
114        """
115
116        config = """
117IMAGE_INSTALL:append = " overlayfs-user"
118DISTRO_FEATURES:append = " systemd overlayfs usrmerge"
119"""
120
121        wrong_machine_config = """
122OVERLAYFS_MOUNT_POINT[usr-share-overlay] = "/usr/share/overlay"
123"""
124
125        self.write_config(config)
126        self.set_machine_config(wrong_machine_config)
127
128        res = bitbake('core-image-minimal', ignore_status=True)
129        line = getline(res, "Missing required mount point for OVERLAYFS_MOUNT_POINT[mnt-overlay] in your MACHINE configuration")
130        self.assertTrue(line and line.startswith("Parsing recipes...ERROR:"), msg=res.output)
131
132    def _test_correct_image(self, recipe, data):
133        """
134        Summary:   Check that we can create an image when all parameters are
135                   set correctly
136        Expected:  Image is created successfully
137        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
138        """
139
140        config = """
141IMAGE_INSTALL:append = " overlayfs-user systemd-machine-units"
142DISTRO_FEATURES:append = " overlayfs"
143
144# Use systemd as init manager
145INIT_MANAGER = "systemd"
146
147# enable overlayfs in the kernel
148KERNEL_EXTRA_FEATURES:append = " features/overlayfs/overlayfs.scc"
149"""
150
151        overlayfs_recipe_append = """
152OVERLAYFS_WRITABLE_PATHS[mnt-overlay] += "/usr/share/another-overlay-mount"
153
154SYSTEMD_SERVICE:${PN} += " \
155    my-application.service \
156"
157
158do_install:append() {
159    install -d ${D}${systemd_system_unitdir}
160    cat <<EOT > ${D}${systemd_system_unitdir}/my-application.service
161[Unit]
162Description=Sample application start-up unit
163After=overlayfs-user-overlays.service
164Requires=overlayfs-user-overlays.service
165
166[Service]
167Type=oneshot
168ExecStart=/bin/true
169RemainAfterExit=true
170
171[Install]
172WantedBy=multi-user.target
173EOT
174}
175"""
176
177        self.write_config(config)
178        self.add_overlay_conf_to_machine()
179        self.write_recipeinc(recipe, data)
180        self.write_recipeinc('overlayfs-user', overlayfs_recipe_append)
181
182        bitbake('core-image-minimal')
183
184        with runqemu('core-image-minimal') as qemu:
185            # Check that application service started
186            status, output = qemu.run_serial("systemctl status my-application")
187            self.assertTrue("active (exited)" in output, msg=output)
188
189            # Check that overlay mounts are dependencies of our application unit
190            status, output = qemu.run_serial("systemctl list-dependencies my-application")
191            self.assertTrue("overlayfs-user-overlays.service" in output, msg=output)
192
193            status, output = qemu.run_serial("systemctl list-dependencies overlayfs-user-overlays")
194            self.assertTrue("usr-share-another\\x2doverlay\\x2dmount.mount" in output, msg=output)
195            self.assertTrue("usr-share-my\\x2dapplication.mount" in output, msg=output)
196
197            # Check that we have /mnt/overlay fs mounted as tmpfs and
198            # /usr/share/my-application as an overlay (see overlayfs-user recipe)
199            status, output = qemu.run_serial("/bin/mount -t tmpfs,overlay")
200
201            line = getline_qemu(output, "on /mnt/overlay")
202            self.assertTrue(line and line.startswith("tmpfs"), msg=output)
203
204            line = getline_qemu(output, "upperdir=/mnt/overlay/upper/usr/share/my-application")
205            self.assertTrue(line and line.startswith("overlay"), msg=output)
206
207            line = getline_qemu(output, "upperdir=/mnt/overlay/upper/usr/share/another-overlay-mount")
208            self.assertTrue(line and line.startswith("overlay"), msg=output)
209
210    @OETestTag("runqemu")
211    def test_correct_image_fstab(self):
212        """
213        Summary:   Check that we can create an image when all parameters are
214                   set correctly via fstab
215        Expected:  Image is created successfully
216        Author:    Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
217        """
218
219        base_files_append = """
220do_install:append() {
221    cat <<EOT >> ${D}${sysconfdir}/fstab
222tmpfs                /mnt/overlay         tmpfs      mode=1777,strictatime,nosuid,nodev  0  0
223EOT
224}
225"""
226
227        self._test_correct_image('base-files', base_files_append)
228
229    @OETestTag("runqemu")
230    def test_correct_image_unit(self):
231        """
232        Summary:   Check that we can create an image when all parameters are
233                   set correctly via mount unit
234        Expected:  Image is created successfully
235        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
236        """
237
238        systemd_machine_unit_append = """
239SYSTEMD_SERVICE:${PN} += " \
240    mnt-overlay.mount \
241"
242
243do_install:append() {
244    install -d ${D}${systemd_system_unitdir}
245    cat <<EOT > ${D}${systemd_system_unitdir}/mnt-overlay.mount
246[Unit]
247Description=Tmpfs directory
248DefaultDependencies=no
249
250[Mount]
251What=tmpfs
252Where=/mnt/overlay
253Type=tmpfs
254Options=mode=1777,strictatime,nosuid,nodev
255
256[Install]
257WantedBy=multi-user.target
258EOT
259}
260
261"""
262
263        self._test_correct_image('systemd-machine-units', systemd_machine_unit_append)
264
265@OETestTag("runqemu")
266class OverlayFSEtcRunTimeTests(OESelftestTestCase):
267    """overlayfs-etc class tests"""
268
269    def test_all_required_variables_set(self):
270        """
271        Summary:   Check that required variables are set
272        Expected:  Fail when any of required variables is missing
273        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
274        """
275
276        configBase = """
277# Use systemd as init manager
278INIT_MANAGER = "systemd"
279
280# enable overlayfs in the kernel
281KERNEL_EXTRA_FEATURES:append = " features/overlayfs/overlayfs.scc"
282
283# Image configuration for overlayfs-etc
284EXTRA_IMAGE_FEATURES += "overlayfs-etc"
285IMAGE_FEATURES:remove = "package-management"
286"""
287        configMountPoint = """
288OVERLAYFS_ETC_MOUNT_POINT = "/data"
289"""
290        configDevice = """
291OVERLAYFS_ETC_DEVICE = "/dev/mmcblk0p1"
292"""
293
294        self.write_config(configBase)
295        res = bitbake('core-image-minimal', ignore_status=True)
296        line = getline(res, "OVERLAYFS_ETC_MOUNT_POINT must be set in your MACHINE configuration")
297        self.assertTrue(line, msg=res.output)
298
299        self.append_config(configMountPoint)
300        res = bitbake('core-image-minimal', ignore_status=True)
301        line = getline(res, "OVERLAYFS_ETC_DEVICE must be set in your MACHINE configuration")
302        self.assertTrue(line, msg=res.output)
303
304        self.append_config(configDevice)
305        res = bitbake('core-image-minimal', ignore_status=True)
306        line = getline(res, "OVERLAYFS_ETC_FSTYPE should contain a valid file system type on /dev/mmcblk0p1")
307        self.assertTrue(line, msg=res.output)
308
309    def test_image_feature_conflict(self):
310        """
311        Summary:   Overlayfs-etc is not allowed to be used with package-management
312        Expected:  Feature conflict
313        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
314        """
315
316        config = """
317# Use systemd as init manager
318INIT_MANAGER = "systemd"
319
320# enable overlayfs in the kernel
321KERNEL_EXTRA_FEATURES:append = " features/overlayfs/overlayfs.scc"
322EXTRA_IMAGE_FEATURES += "overlayfs-etc"
323EXTRA_IMAGE_FEATURES += "package-management"
324"""
325
326        self.write_config(config)
327
328        res = bitbake('core-image-minimal', ignore_status=True)
329        line = getline(res, "contains conflicting IMAGE_FEATURES")
330        self.assertTrue("overlayfs-etc" in res.output, msg=res.output)
331        self.assertTrue("package-management" in res.output, msg=res.output)
332
333    # https://bugzilla.yoctoproject.org/show_bug.cgi?id=14963
334    @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
335    def test_image_feature_is_missing(self):
336        """
337        Summary:   Overlayfs-etc class is not applied when image feature is not set
338        Expected:  Image is created successfully but /etc is not an overlay
339        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
340        """
341
342        config = """
343# Use systemd as init manager
344INIT_MANAGER = "systemd"
345
346# enable overlayfs in the kernel
347KERNEL_EXTRA_FEATURES:append = " features/overlayfs/overlayfs.scc"
348
349IMAGE_FSTYPES += "wic"
350WKS_FILE = "overlayfs_etc.wks.in"
351
352EXTRA_IMAGE_FEATURES += "read-only-rootfs"
353# Image configuration for overlayfs-etc
354OVERLAYFS_ETC_MOUNT_POINT = "/data"
355OVERLAYFS_ETC_DEVICE = "/dev/sda3"
356OVERLAYFS_ROOTFS_TYPE = "ext4"
357"""
358
359        self.write_config(config)
360
361        bitbake('core-image-minimal')
362
363        with runqemu('core-image-minimal', image_fstype='wic') as qemu:
364            status, output = qemu.run_serial("/bin/mount")
365
366            line = getline_qemu(output, "upperdir=/data/overlay-etc/upper")
367            self.assertFalse(line, msg=output)
368
369    @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
370    def test_sbin_init_preinit(self):
371        self.run_sbin_init(False, "ext4")
372
373    @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
374    def test_sbin_init_original(self):
375        self.run_sbin_init(True, "ext4")
376
377    @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
378    def test_sbin_init_read_only(self):
379        self.run_sbin_init(True, "squashfs")
380
381    def run_sbin_init(self, origInit, rootfsType):
382        """
383        Summary:   Confirm we can replace original init and mount overlay on top of /etc
384        Expected:  Image is created successfully and /etc is mounted as an overlay
385        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
386        """
387
388        config = self.get_working_config()
389
390        args = {
391            'OVERLAYFS_INIT_OPTION': "" if origInit else "init=/sbin/preinit",
392            'OVERLAYFS_ETC_USE_ORIG_INIT_NAME': int(origInit == True),
393            'OVERLAYFS_ROOTFS_TYPE': rootfsType,
394            'OVERLAYFS_ETC_CREATE_MOUNT_DIRS': int(rootfsType == "ext4")
395        }
396
397        self.write_config(config.format(**args))
398
399        bitbake('core-image-minimal')
400        testFile = "/etc/my-test-data"
401
402        with runqemu('core-image-minimal', image_fstype='wic', discard_writes=False) as qemu:
403            status, output = qemu.run_serial("/bin/mount")
404
405            line = getline_qemu(output, "/dev/sda3")
406            self.assertTrue("/data" in output, msg=output)
407
408            line = getline_qemu(output, "upperdir=/data/overlay-etc/upper")
409            self.assertTrue(line and line.startswith("/data/overlay-etc/upper on /etc type overlay"), msg=output)
410
411            # check that lower layer is not available
412            status, output = qemu.run_serial("ls -1 /data/overlay-etc/lower")
413            line = getline_qemu(output, "No such file or directory")
414            self.assertTrue(line, msg=output)
415
416            status, output = qemu.run_serial("touch " + testFile)
417            status, output = qemu.run_serial("sync")
418            status, output = qemu.run_serial("ls -1 " + testFile)
419            line = getline_qemu(output, testFile)
420            self.assertTrue(line and line.startswith(testFile), msg=output)
421
422        # Check that file exists in /etc after reboot
423        with runqemu('core-image-minimal', image_fstype='wic') as qemu:
424            status, output = qemu.run_serial("ls -1 " + testFile)
425            line = getline_qemu(output, testFile)
426            self.assertTrue(line and line.startswith(testFile), msg=output)
427
428    @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
429    def test_lower_layer_access(self):
430        """
431        Summary:   Test that lower layer of /etc is available read-only when configured
432        Expected:  Can't write to lower layer. The files on lower and upper different after
433                   modification
434        Author:    Vyacheslav Yurkov <uvv.mail@gmail.com>
435        """
436
437        config = self.get_working_config()
438
439        configLower = """
440OVERLAYFS_ETC_EXPOSE_LOWER = "1"
441IMAGE_INSTALL:append = " overlayfs-user"
442"""
443        testFile = "lower-layer-test.txt"
444
445        args = {
446            'OVERLAYFS_INIT_OPTION': "",
447            'OVERLAYFS_ETC_USE_ORIG_INIT_NAME': 1,
448            'OVERLAYFS_ROOTFS_TYPE': "ext4",
449            'OVERLAYFS_ETC_CREATE_MOUNT_DIRS': 1
450        }
451
452        self.write_config(config.format(**args))
453
454        self.append_config(configLower)
455        bitbake('core-image-minimal')
456
457        with runqemu('core-image-minimal', image_fstype='wic') as qemu:
458            status, output = qemu.run_serial("echo \"Modified in upper\" > /etc/" + testFile)
459            status, output = qemu.run_serial("diff /etc/" + testFile + " /data/overlay-etc/lower/" + testFile)
460            line = getline_qemu(output, "Modified in upper")
461            self.assertTrue(line, msg=output)
462            line = getline_qemu(output, "Original file")
463            self.assertTrue(line, msg=output)
464
465            status, output = qemu.run_serial("touch /data/overlay-etc/lower/ro-test.txt")
466            line = getline_qemu(output, "Read-only file system")
467            self.assertTrue(line, msg=output)
468
469    def get_working_config(self):
470        return """
471# Use systemd as init manager
472INIT_MANAGER = "systemd"
473
474# enable overlayfs in the kernel
475KERNEL_EXTRA_FEATURES:append = " \
476    features/overlayfs/overlayfs.scc \
477    cfg/fs/squashfs.scc"
478
479IMAGE_FSTYPES += "wic"
480OVERLAYFS_INIT_OPTION = "{OVERLAYFS_INIT_OPTION}"
481OVERLAYFS_ROOTFS_TYPE = "{OVERLAYFS_ROOTFS_TYPE}"
482OVERLAYFS_ETC_CREATE_MOUNT_DIRS = "{OVERLAYFS_ETC_CREATE_MOUNT_DIRS}"
483WKS_FILE = "overlayfs_etc.wks.in"
484
485EXTRA_IMAGE_FEATURES += "read-only-rootfs"
486# Image configuration for overlayfs-etc
487EXTRA_IMAGE_FEATURES += "overlayfs-etc"
488IMAGE_FEATURES:remove = "package-management"
489OVERLAYFS_ETC_MOUNT_POINT = "/data"
490OVERLAYFS_ETC_FSTYPE = "ext4"
491OVERLAYFS_ETC_DEVICE = "/dev/sda3"
492OVERLAYFS_ETC_USE_ORIG_INIT_NAME = "{OVERLAYFS_ETC_USE_ORIG_INIT_NAME}"
493
494ROOTFS_POSTPROCESS_COMMAND += "{OVERLAYFS_ROOTFS_TYPE}_rootfs"
495
496ext4_rootfs() {{
497}}
498
499squashfs_rootfs() {{
500    mkdir -p ${{IMAGE_ROOTFS}}/data
501}}
502"""
503