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