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