blob: 5481e1d840d7618d2f30c4408a2fd6ac7a876874 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright OpenEmbedded Contributors
3#
Brad Bishopc342db32019-05-15 21:57:59 -04004# SPDX-License-Identifier: MIT
5#
6
Brad Bishop6e60e8b2018-02-01 10:27:11 -05007import re
Patrick Williams169d7bc2024-01-05 11:33:25 -06008import threading
Brad Bishop6e60e8b2018-02-01 10:27:11 -05009import time
10
11from oeqa.runtime.case import OERuntimeTestCase
12from oeqa.core.decorator.depends import OETestDepends
Brad Bishop6e60e8b2018-02-01 10:27:11 -050013from oeqa.core.decorator.data import skipIfDataVar, skipIfNotDataVar
14from oeqa.runtime.decorator.package import OEHasPackage
Brad Bishop64c979e2019-11-04 13:55:29 -050015from oeqa.core.decorator.data import skipIfNotFeature, skipIfFeature
Brad Bishop6e60e8b2018-02-01 10:27:11 -050016
17class SystemdTest(OERuntimeTestCase):
18
19 def systemctl(self, action='', target='', expected=0, verbose=False):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080020 command = 'SYSTEMD_BUS_TIMEOUT=240s systemctl %s %s' % (action, target)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050021 status, output = self.target.run(command)
22 message = '\n'.join([command, output])
23 if status != expected and verbose:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080024 cmd = 'SYSTEMD_BUS_TIMEOUT=240s systemctl status --full %s' % target
Brad Bishop6e60e8b2018-02-01 10:27:11 -050025 message += self.target.run(cmd)[1]
26 self.assertEqual(status, expected, message)
27 return output
28
29 #TODO: use pyjournalctl instead
30 def journalctl(self, args='',l_match_units=None):
31 """
32 Request for the journalctl output to the current target system
33
34 Arguments:
35 -args, an optional argument pass through argument
36 -l_match_units, an optional list of units to filter the output
37 Returns:
38 -string output of the journalctl command
39 Raises:
40 -AssertionError, on remote commands that fail
41 -ValueError, on a journalctl call with filtering by l_match_units that
42 returned no entries
43 """
44
45 query_units=''
46 if l_match_units:
47 query_units = ['_SYSTEMD_UNIT='+unit for unit in l_match_units]
48 query_units = ' '.join(query_units)
49 command = 'journalctl %s %s' %(args, query_units)
50 status, output = self.target.run(command)
51 if status:
52 raise AssertionError("Command '%s' returned non-zero exit "
53 'code %d:\n%s' % (command, status, output))
54 if len(output) == 1 and "-- No entries --" in output:
55 raise ValueError('List of units to match: %s, returned no entries'
56 % l_match_units)
57 return output
58
59class SystemdBasicTests(SystemdTest):
60
61 def settle(self):
62 """
63 Block until systemd has finished activating any units being activated,
64 or until two minutes has elapsed.
65
66 Returns a tuple, either (True, '') if all units have finished
67 activating, or (False, message string) if there are still units
68 activating (generally, failing units that restart).
69 """
70 endtime = time.time() + (60 * 2)
71 while True:
Patrick Williams705982a2024-01-12 09:51:57 -060072 status, output = self.target.run('SYSTEMD_BUS_TIMEOUT=240s systemctl is-system-running')
73 if "running" in output or "degraded" in output:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050074 return (True, '')
75 if time.time() >= endtime:
76 return (False, output)
77 time.sleep(10)
78
79 @skipIfNotFeature('systemd',
80 'Test requires systemd to be in DISTRO_FEATURES')
81 @skipIfNotDataVar('VIRTUAL-RUNTIME_init_manager', 'systemd',
82 'systemd is not the init manager for this image')
83 @OETestDepends(['ssh.SSHTest.test_ssh'])
84 def test_systemd_basic(self):
85 self.systemctl('--version')
86
Brad Bishop6e60e8b2018-02-01 10:27:11 -050087 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
88 def test_systemd_list(self):
89 self.systemctl('list-unit-files')
90
Brad Bishop6e60e8b2018-02-01 10:27:11 -050091 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
92 def test_systemd_failed(self):
93 settled, output = self.settle()
94 msg = "Timed out waiting for systemd to settle:\n%s" % output
95 self.assertTrue(settled, msg=msg)
96
97 output = self.systemctl('list-units', '--failed')
98 match = re.search('0 loaded units listed', output)
99 if not match:
100 output += self.systemctl('status --full --failed')
101 self.assertTrue(match, msg='Some systemd units failed:\n%s' % output)
102
103
104class SystemdServiceTests(SystemdTest):
105
106 @OEHasPackage(['avahi-daemon'])
107 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
108 def test_systemd_status(self):
109 self.systemctl('status --full', 'avahi-daemon.service')
110
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500111 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
112 def test_systemd_stop_start(self):
113 self.systemctl('stop', 'avahi-daemon.service')
114 self.systemctl('is-active', 'avahi-daemon.service',
115 expected=3, verbose=True)
116 self.systemctl('start','avahi-daemon.service')
117 self.systemctl('is-active', 'avahi-daemon.service', verbose=True)
118
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500119 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
Brad Bishop64c979e2019-11-04 13:55:29 -0500120 @skipIfFeature('read-only-rootfs',
121 'Test is only meant to run without read-only-rootfs in IMAGE_FEATURES')
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500122 def test_systemd_disable_enable(self):
123 self.systemctl('disable', 'avahi-daemon.service')
124 self.systemctl('is-enabled', 'avahi-daemon.service', expected=1)
125 self.systemctl('enable', 'avahi-daemon.service')
126 self.systemctl('is-enabled', 'avahi-daemon.service')
127
Brad Bishop64c979e2019-11-04 13:55:29 -0500128 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
129 @skipIfNotFeature('read-only-rootfs',
130 'Test is only meant to run with read-only-rootfs in IMAGE_FEATURES')
131 def test_systemd_disable_enable_ro(self):
132 status = self.target.run('mount -orw,remount /')[0]
133 self.assertTrue(status == 0, msg='Remounting / as r/w failed')
134 try:
135 self.test_systemd_disable_enable()
136 finally:
137 status = self.target.run('mount -oro,remount /')[0]
138 self.assertTrue(status == 0, msg='Remounting / as r/o failed')
139
Patrick Williams169d7bc2024-01-05 11:33:25 -0600140 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
141 @skipIfNotFeature('minidebuginfo', 'Test requires minidebuginfo to be in DISTRO_FEATURES')
142 @OEHasPackage(['busybox'])
143 def test_systemd_coredump_minidebuginfo(self):
144 """
145 Verify that call-stacks generated by systemd-coredump contain symbolicated call-stacks,
146 extracted from the minidebuginfo metadata (.gnu_debugdata elf section).
147 """
148 t_thread = threading.Thread(target=self.target.run, args=("ulimit -c unlimited && sleep 1000",))
149 t_thread.start()
150 time.sleep(1)
151
152 status, output = self.target.run('pidof sleep')
153 # cause segfault on purpose
154 self.target.run('kill -SEGV %s' % output)
155 self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % output)
156
157 (status, output) = self.target.run('coredumpctl info')
158 self.assertEqual(status, 0, msg='MiniDebugInfo Test failed: %s' % output)
159 self.assertEqual('sleep_for_duration (busybox.nosuid' in output, True, msg='Call stack is missing minidebuginfo symbols (functions shown as "n/a"): %s' % output)
160
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500161class SystemdJournalTests(SystemdTest):
162
163 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
164 def test_systemd_journal(self):
165 status, output = self.target.run('journalctl')
166 self.assertEqual(status, 0, output)
167
168 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
169 def test_systemd_boot_time(self, systemd_TimeoutStartSec=90):
170 """
171 Get the target boot time from journalctl and log it
172
173 Arguments:
174 -systemd_TimeoutStartSec, an optional argument containing systemd's
175 unit start timeout to compare against
176 """
177
178 # The expression chain that uniquely identifies the time boot message.
Patrick Williams8e7b46e2023-05-01 14:19:06 -0500179 expr_items=['Startup finished', 'kernel', 'userspace', r'\.$']
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500180 try:
181 output = self.journalctl(args='-o cat --reverse')
182 except AssertionError:
183 self.fail('Error occurred while calling journalctl')
184 if not len(output):
185 self.fail('Error, unable to get startup time from systemd journal')
186
187 # Check for the regular expression items that match the startup time.
188 for line in output.split('\n'):
189 check_match = ''.join(re.findall('.*'.join(expr_items), line))
190 if check_match:
191 break
192 # Put the startup time in the test log
193 if check_match:
194 self.tc.logger.info('%s' % check_match)
195 else:
196 self.skipTest('Error at obtaining the boot time from journalctl')
197 boot_time_sec = 0
198
199 # Get the numeric values from the string and convert them to seconds
200 # same data will be placed in list and string for manipulation.
201 l_boot_time = check_match.split(' ')[-2:]
202 s_boot_time = ' '.join(l_boot_time)
203 try:
204 # Obtain the minutes it took to boot.
205 if l_boot_time[0].endswith('min') and l_boot_time[0][0].isdigit():
206 boot_time_min = s_boot_time.split('min')[0]
207 # Convert to seconds and accumulate it.
208 boot_time_sec += int(boot_time_min) * 60
209 # Obtain the seconds it took to boot and accumulate.
210 boot_time_sec += float(l_boot_time[1].split('s')[0])
211 except ValueError:
212 self.skipTest('Error when parsing time from boot string')
213
214 # Assert the target boot time against systemd's unit start timeout.
215 if boot_time_sec > systemd_TimeoutStartSec:
216 msg = ("Target boot time %s exceeds systemd's TimeoutStartSec %s"
217 % (boot_time_sec, systemd_TimeoutStartSec))
218 self.tc.logger.info(msg)