1c342db35SBrad Bishop#
292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors
392b42cb3SPatrick Williams#
4c342db35SBrad Bishop# SPDX-License-Identifier: MIT
5c342db35SBrad Bishop#
6c342db35SBrad Bishop
7eb8dc403SDave Cobbleyimport re
8169d7bccSPatrick Williamsimport threading
9eb8dc403SDave Cobbleyimport time
10eb8dc403SDave Cobbley
11eb8dc403SDave Cobbleyfrom oeqa.runtime.case import OERuntimeTestCase
12eb8dc403SDave Cobbleyfrom oeqa.core.decorator.depends import OETestDepends
13eb8dc403SDave Cobbleyfrom oeqa.core.decorator.data import skipIfDataVar, skipIfNotDataVar
14eb8dc403SDave Cobbleyfrom oeqa.runtime.decorator.package import OEHasPackage
1564c979e8SBrad Bishopfrom oeqa.core.decorator.data import skipIfNotFeature, skipIfFeature
16eb8dc403SDave Cobbley
17eb8dc403SDave Cobbleyclass SystemdTest(OERuntimeTestCase):
18eb8dc403SDave Cobbley
19eb8dc403SDave Cobbley    def systemctl(self, action='', target='', expected=0, verbose=False):
201a4b7ee2SBrad Bishop        command = 'SYSTEMD_BUS_TIMEOUT=240s systemctl %s %s' % (action, target)
21eb8dc403SDave Cobbley        status, output = self.target.run(command)
22eb8dc403SDave Cobbley        message = '\n'.join([command, output])
23eb8dc403SDave Cobbley        if status != expected and verbose:
241a4b7ee2SBrad Bishop            cmd = 'SYSTEMD_BUS_TIMEOUT=240s systemctl status --full %s' % target
25eb8dc403SDave Cobbley            message += self.target.run(cmd)[1]
26eb8dc403SDave Cobbley        self.assertEqual(status, expected, message)
27eb8dc403SDave Cobbley        return output
28eb8dc403SDave Cobbley
29eb8dc403SDave Cobbley    #TODO: use pyjournalctl instead
30eb8dc403SDave Cobbley    def journalctl(self, args='',l_match_units=None):
31eb8dc403SDave Cobbley        """
32eb8dc403SDave Cobbley        Request for the journalctl output to the current target system
33eb8dc403SDave Cobbley
34eb8dc403SDave Cobbley        Arguments:
35eb8dc403SDave Cobbley        -args, an optional argument pass through argument
36eb8dc403SDave Cobbley        -l_match_units, an optional list of units to filter the output
37eb8dc403SDave Cobbley        Returns:
38eb8dc403SDave Cobbley        -string output of the journalctl command
39eb8dc403SDave Cobbley        Raises:
40eb8dc403SDave Cobbley        -AssertionError, on remote commands that fail
41eb8dc403SDave Cobbley        -ValueError, on a journalctl call with filtering by l_match_units that
42eb8dc403SDave Cobbley        returned no entries
43eb8dc403SDave Cobbley        """
44eb8dc403SDave Cobbley
45eb8dc403SDave Cobbley        query_units=''
46eb8dc403SDave Cobbley        if l_match_units:
47eb8dc403SDave Cobbley            query_units = ['_SYSTEMD_UNIT='+unit for unit in l_match_units]
48eb8dc403SDave Cobbley            query_units = ' '.join(query_units)
49eb8dc403SDave Cobbley        command = 'journalctl %s %s' %(args, query_units)
50eb8dc403SDave Cobbley        status, output = self.target.run(command)
51eb8dc403SDave Cobbley        if status:
52eb8dc403SDave Cobbley            raise AssertionError("Command '%s' returned non-zero exit "
53eb8dc403SDave Cobbley                    'code %d:\n%s' % (command, status, output))
54eb8dc403SDave Cobbley        if len(output) == 1 and "-- No entries --" in output:
55eb8dc403SDave Cobbley            raise ValueError('List of units to match: %s, returned no entries'
56eb8dc403SDave Cobbley                    % l_match_units)
57eb8dc403SDave Cobbley        return output
58eb8dc403SDave Cobbley
59eb8dc403SDave Cobbleyclass SystemdBasicTests(SystemdTest):
60eb8dc403SDave Cobbley
61eb8dc403SDave Cobbley    def settle(self):
62eb8dc403SDave Cobbley        """
63eb8dc403SDave Cobbley        Block until systemd has finished activating any units being activated,
64eb8dc403SDave Cobbley        or until two minutes has elapsed.
65eb8dc403SDave Cobbley
66eb8dc403SDave Cobbley        Returns a tuple, either (True, '') if all units have finished
67eb8dc403SDave Cobbley        activating, or (False, message string) if there are still units
68eb8dc403SDave Cobbley        activating (generally, failing units that restart).
69eb8dc403SDave Cobbley        """
70eb8dc403SDave Cobbley        endtime = time.time() + (60 * 2)
71eb8dc403SDave Cobbley        while True:
72*705982a5SPatrick Williams            status, output = self.target.run('SYSTEMD_BUS_TIMEOUT=240s systemctl is-system-running')
73*705982a5SPatrick Williams            if "running" in output or "degraded" in output:
74eb8dc403SDave Cobbley                return (True, '')
75eb8dc403SDave Cobbley            if time.time() >= endtime:
76eb8dc403SDave Cobbley                return (False, output)
77eb8dc403SDave Cobbley            time.sleep(10)
78eb8dc403SDave Cobbley
79eb8dc403SDave Cobbley    @skipIfNotFeature('systemd',
80eb8dc403SDave Cobbley                      'Test requires systemd to be in DISTRO_FEATURES')
81eb8dc403SDave Cobbley    @skipIfNotDataVar('VIRTUAL-RUNTIME_init_manager', 'systemd',
82eb8dc403SDave Cobbley                      'systemd is not the init manager for this image')
83eb8dc403SDave Cobbley    @OETestDepends(['ssh.SSHTest.test_ssh'])
84eb8dc403SDave Cobbley    def test_systemd_basic(self):
85eb8dc403SDave Cobbley        self.systemctl('--version')
86eb8dc403SDave Cobbley
87eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
88eb8dc403SDave Cobbley    def test_systemd_list(self):
89eb8dc403SDave Cobbley        self.systemctl('list-unit-files')
90eb8dc403SDave Cobbley
91eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
92eb8dc403SDave Cobbley    def test_systemd_failed(self):
93eb8dc403SDave Cobbley        settled, output = self.settle()
94eb8dc403SDave Cobbley        msg = "Timed out waiting for systemd to settle:\n%s" % output
95eb8dc403SDave Cobbley        self.assertTrue(settled, msg=msg)
96eb8dc403SDave Cobbley
97eb8dc403SDave Cobbley        output = self.systemctl('list-units', '--failed')
98eb8dc403SDave Cobbley        match = re.search('0 loaded units listed', output)
99eb8dc403SDave Cobbley        if not match:
100eb8dc403SDave Cobbley            output += self.systemctl('status --full --failed')
101eb8dc403SDave Cobbley        self.assertTrue(match, msg='Some systemd units failed:\n%s' % output)
102eb8dc403SDave Cobbley
103eb8dc403SDave Cobbley
104eb8dc403SDave Cobbleyclass SystemdServiceTests(SystemdTest):
105eb8dc403SDave Cobbley
106eb8dc403SDave Cobbley    @OEHasPackage(['avahi-daemon'])
107eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
108eb8dc403SDave Cobbley    def test_systemd_status(self):
109eb8dc403SDave Cobbley        self.systemctl('status --full', 'avahi-daemon.service')
110eb8dc403SDave Cobbley
111eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
112eb8dc403SDave Cobbley    def test_systemd_stop_start(self):
113eb8dc403SDave Cobbley        self.systemctl('stop', 'avahi-daemon.service')
114eb8dc403SDave Cobbley        self.systemctl('is-active', 'avahi-daemon.service',
115eb8dc403SDave Cobbley                       expected=3, verbose=True)
116eb8dc403SDave Cobbley        self.systemctl('start','avahi-daemon.service')
117eb8dc403SDave Cobbley        self.systemctl('is-active', 'avahi-daemon.service', verbose=True)
118eb8dc403SDave Cobbley
119eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
12064c979e8SBrad Bishop    @skipIfFeature('read-only-rootfs',
12164c979e8SBrad Bishop                   'Test is only meant to run without read-only-rootfs in IMAGE_FEATURES')
122eb8dc403SDave Cobbley    def test_systemd_disable_enable(self):
123eb8dc403SDave Cobbley        self.systemctl('disable', 'avahi-daemon.service')
124eb8dc403SDave Cobbley        self.systemctl('is-enabled', 'avahi-daemon.service', expected=1)
125eb8dc403SDave Cobbley        self.systemctl('enable', 'avahi-daemon.service')
126eb8dc403SDave Cobbley        self.systemctl('is-enabled', 'avahi-daemon.service')
127eb8dc403SDave Cobbley
12864c979e8SBrad Bishop    @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
12964c979e8SBrad Bishop    @skipIfNotFeature('read-only-rootfs',
13064c979e8SBrad Bishop                      'Test is only meant to run with read-only-rootfs in IMAGE_FEATURES')
13164c979e8SBrad Bishop    def test_systemd_disable_enable_ro(self):
13264c979e8SBrad Bishop        status = self.target.run('mount -orw,remount /')[0]
13364c979e8SBrad Bishop        self.assertTrue(status == 0, msg='Remounting / as r/w failed')
13464c979e8SBrad Bishop        try:
13564c979e8SBrad Bishop            self.test_systemd_disable_enable()
13664c979e8SBrad Bishop        finally:
13764c979e8SBrad Bishop            status = self.target.run('mount -oro,remount /')[0]
13864c979e8SBrad Bishop            self.assertTrue(status == 0, msg='Remounting / as r/o failed')
13964c979e8SBrad Bishop
140169d7bccSPatrick Williams    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
141169d7bccSPatrick Williams    @skipIfNotFeature('minidebuginfo', 'Test requires minidebuginfo to be in DISTRO_FEATURES')
142169d7bccSPatrick Williams    @OEHasPackage(['busybox'])
143169d7bccSPatrick Williams    def test_systemd_coredump_minidebuginfo(self):
144169d7bccSPatrick Williams        """
145169d7bccSPatrick Williams        Verify that call-stacks generated by systemd-coredump contain symbolicated call-stacks,
146169d7bccSPatrick Williams        extracted from the minidebuginfo metadata (.gnu_debugdata elf section).
147169d7bccSPatrick Williams        """
148169d7bccSPatrick Williams        t_thread = threading.Thread(target=self.target.run, args=("ulimit -c unlimited && sleep 1000",))
149169d7bccSPatrick Williams        t_thread.start()
150169d7bccSPatrick Williams        time.sleep(1)
151169d7bccSPatrick Williams
152169d7bccSPatrick Williams        status, output = self.target.run('pidof sleep')
153169d7bccSPatrick Williams        # cause segfault on purpose
154169d7bccSPatrick Williams        self.target.run('kill -SEGV %s' % output)
155169d7bccSPatrick Williams        self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % output)
156169d7bccSPatrick Williams
157169d7bccSPatrick Williams        (status, output) = self.target.run('coredumpctl info')
158169d7bccSPatrick Williams        self.assertEqual(status, 0, msg='MiniDebugInfo Test failed: %s' % output)
159169d7bccSPatrick Williams        self.assertEqual('sleep_for_duration (busybox.nosuid' in output, True, msg='Call stack is missing minidebuginfo symbols (functions shown as "n/a"): %s' % output)
160169d7bccSPatrick Williams
161eb8dc403SDave Cobbleyclass SystemdJournalTests(SystemdTest):
162eb8dc403SDave Cobbley
163eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
164eb8dc403SDave Cobbley    def test_systemd_journal(self):
165eb8dc403SDave Cobbley        status, output = self.target.run('journalctl')
166eb8dc403SDave Cobbley        self.assertEqual(status, 0, output)
167eb8dc403SDave Cobbley
168eb8dc403SDave Cobbley    @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
169eb8dc403SDave Cobbley    def test_systemd_boot_time(self, systemd_TimeoutStartSec=90):
170eb8dc403SDave Cobbley        """
171eb8dc403SDave Cobbley        Get the target boot time from journalctl and log it
172eb8dc403SDave Cobbley
173eb8dc403SDave Cobbley        Arguments:
174eb8dc403SDave Cobbley        -systemd_TimeoutStartSec, an optional argument containing systemd's
175eb8dc403SDave Cobbley        unit start timeout to compare against
176eb8dc403SDave Cobbley        """
177eb8dc403SDave Cobbley
178eb8dc403SDave Cobbley        # The expression chain that uniquely identifies the time boot message.
1798e7b46e2SPatrick Williams        expr_items=['Startup finished', 'kernel', 'userspace', r'\.$']
180eb8dc403SDave Cobbley        try:
181eb8dc403SDave Cobbley            output = self.journalctl(args='-o cat --reverse')
182eb8dc403SDave Cobbley        except AssertionError:
183eb8dc403SDave Cobbley            self.fail('Error occurred while calling journalctl')
184eb8dc403SDave Cobbley        if not len(output):
185eb8dc403SDave Cobbley            self.fail('Error, unable to get startup time from systemd journal')
186eb8dc403SDave Cobbley
187eb8dc403SDave Cobbley        # Check for the regular expression items that match the startup time.
188eb8dc403SDave Cobbley        for line in output.split('\n'):
189eb8dc403SDave Cobbley            check_match = ''.join(re.findall('.*'.join(expr_items), line))
190eb8dc403SDave Cobbley            if check_match:
191eb8dc403SDave Cobbley                break
192eb8dc403SDave Cobbley        # Put the startup time in the test log
193eb8dc403SDave Cobbley        if check_match:
194eb8dc403SDave Cobbley            self.tc.logger.info('%s' % check_match)
195eb8dc403SDave Cobbley        else:
196eb8dc403SDave Cobbley            self.skipTest('Error at obtaining the boot time from journalctl')
197eb8dc403SDave Cobbley        boot_time_sec = 0
198eb8dc403SDave Cobbley
199eb8dc403SDave Cobbley        # Get the numeric values from the string and convert them to seconds
200eb8dc403SDave Cobbley        # same data will be placed in list and string for manipulation.
201eb8dc403SDave Cobbley        l_boot_time = check_match.split(' ')[-2:]
202eb8dc403SDave Cobbley        s_boot_time = ' '.join(l_boot_time)
203eb8dc403SDave Cobbley        try:
204eb8dc403SDave Cobbley            # Obtain the minutes it took to boot.
205eb8dc403SDave Cobbley            if l_boot_time[0].endswith('min') and l_boot_time[0][0].isdigit():
206eb8dc403SDave Cobbley                boot_time_min = s_boot_time.split('min')[0]
207eb8dc403SDave Cobbley                # Convert to seconds and accumulate it.
208eb8dc403SDave Cobbley                boot_time_sec += int(boot_time_min) * 60
209eb8dc403SDave Cobbley            # Obtain the seconds it took to boot and accumulate.
210eb8dc403SDave Cobbley            boot_time_sec += float(l_boot_time[1].split('s')[0])
211eb8dc403SDave Cobbley        except ValueError:
212eb8dc403SDave Cobbley            self.skipTest('Error when parsing time from boot string')
213eb8dc403SDave Cobbley
214eb8dc403SDave Cobbley        # Assert the target boot time against systemd's unit start timeout.
215eb8dc403SDave Cobbley        if boot_time_sec > systemd_TimeoutStartSec:
216eb8dc403SDave Cobbley            msg = ("Target boot time %s exceeds systemd's TimeoutStartSec %s"
217eb8dc403SDave Cobbley                    % (boot_time_sec, systemd_TimeoutStartSec))
218eb8dc403SDave Cobbley            self.tc.logger.info(msg)
219