blob: 720b4b517ae28ae341eb2023cb538aec2fd5f6ae [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
8import time
9
10from oeqa.runtime.case import OERuntimeTestCase
11from oeqa.core.decorator.depends import OETestDepends
Brad Bishop6e60e8b2018-02-01 10:27:11 -050012from oeqa.core.decorator.data import skipIfDataVar, skipIfNotDataVar
13from oeqa.runtime.decorator.package import OEHasPackage
Brad Bishop64c979e2019-11-04 13:55:29 -050014from oeqa.core.decorator.data import skipIfNotFeature, skipIfFeature
Brad Bishop6e60e8b2018-02-01 10:27:11 -050015
16class SystemdTest(OERuntimeTestCase):
17
18 def systemctl(self, action='', target='', expected=0, verbose=False):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080019 command = 'SYSTEMD_BUS_TIMEOUT=240s systemctl %s %s' % (action, target)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050020 status, output = self.target.run(command)
21 message = '\n'.join([command, output])
22 if status != expected and verbose:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080023 cmd = 'SYSTEMD_BUS_TIMEOUT=240s systemctl status --full %s' % target
Brad Bishop6e60e8b2018-02-01 10:27:11 -050024 message += self.target.run(cmd)[1]
25 self.assertEqual(status, expected, message)
26 return output
27
28 #TODO: use pyjournalctl instead
29 def journalctl(self, args='',l_match_units=None):
30 """
31 Request for the journalctl output to the current target system
32
33 Arguments:
34 -args, an optional argument pass through argument
35 -l_match_units, an optional list of units to filter the output
36 Returns:
37 -string output of the journalctl command
38 Raises:
39 -AssertionError, on remote commands that fail
40 -ValueError, on a journalctl call with filtering by l_match_units that
41 returned no entries
42 """
43
44 query_units=''
45 if l_match_units:
46 query_units = ['_SYSTEMD_UNIT='+unit for unit in l_match_units]
47 query_units = ' '.join(query_units)
48 command = 'journalctl %s %s' %(args, query_units)
49 status, output = self.target.run(command)
50 if status:
51 raise AssertionError("Command '%s' returned non-zero exit "
52 'code %d:\n%s' % (command, status, output))
53 if len(output) == 1 and "-- No entries --" in output:
54 raise ValueError('List of units to match: %s, returned no entries'
55 % l_match_units)
56 return output
57
58class SystemdBasicTests(SystemdTest):
59
60 def settle(self):
61 """
62 Block until systemd has finished activating any units being activated,
63 or until two minutes has elapsed.
64
65 Returns a tuple, either (True, '') if all units have finished
66 activating, or (False, message string) if there are still units
67 activating (generally, failing units that restart).
68 """
69 endtime = time.time() + (60 * 2)
70 while True:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080071 status, output = self.target.run('SYSTEMD_BUS_TIMEOUT=240s systemctl --state=activating')
Brad Bishop6e60e8b2018-02-01 10:27:11 -050072 if "0 loaded units listed" in output:
73 return (True, '')
74 if time.time() >= endtime:
75 return (False, output)
76 time.sleep(10)
77
78 @skipIfNotFeature('systemd',
79 'Test requires systemd to be in DISTRO_FEATURES')
80 @skipIfNotDataVar('VIRTUAL-RUNTIME_init_manager', 'systemd',
81 'systemd is not the init manager for this image')
82 @OETestDepends(['ssh.SSHTest.test_ssh'])
83 def test_systemd_basic(self):
84 self.systemctl('--version')
85
Brad Bishop6e60e8b2018-02-01 10:27:11 -050086 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
87 def test_systemd_list(self):
88 self.systemctl('list-unit-files')
89
Brad Bishop6e60e8b2018-02-01 10:27:11 -050090 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
91 def test_systemd_failed(self):
92 settled, output = self.settle()
93 msg = "Timed out waiting for systemd to settle:\n%s" % output
94 self.assertTrue(settled, msg=msg)
95
96 output = self.systemctl('list-units', '--failed')
97 match = re.search('0 loaded units listed', output)
98 if not match:
99 output += self.systemctl('status --full --failed')
100 self.assertTrue(match, msg='Some systemd units failed:\n%s' % output)
101
102
103class SystemdServiceTests(SystemdTest):
104
105 @OEHasPackage(['avahi-daemon'])
106 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
107 def test_systemd_status(self):
108 self.systemctl('status --full', 'avahi-daemon.service')
109
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500110 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
111 def test_systemd_stop_start(self):
112 self.systemctl('stop', 'avahi-daemon.service')
113 self.systemctl('is-active', 'avahi-daemon.service',
114 expected=3, verbose=True)
115 self.systemctl('start','avahi-daemon.service')
116 self.systemctl('is-active', 'avahi-daemon.service', verbose=True)
117
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500118 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
Brad Bishop64c979e2019-11-04 13:55:29 -0500119 @skipIfFeature('read-only-rootfs',
120 'Test is only meant to run without read-only-rootfs in IMAGE_FEATURES')
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500121 def test_systemd_disable_enable(self):
122 self.systemctl('disable', 'avahi-daemon.service')
123 self.systemctl('is-enabled', 'avahi-daemon.service', expected=1)
124 self.systemctl('enable', 'avahi-daemon.service')
125 self.systemctl('is-enabled', 'avahi-daemon.service')
126
Brad Bishop64c979e2019-11-04 13:55:29 -0500127 @OETestDepends(['systemd.SystemdServiceTests.test_systemd_status'])
128 @skipIfNotFeature('read-only-rootfs',
129 'Test is only meant to run with read-only-rootfs in IMAGE_FEATURES')
130 def test_systemd_disable_enable_ro(self):
131 status = self.target.run('mount -orw,remount /')[0]
132 self.assertTrue(status == 0, msg='Remounting / as r/w failed')
133 try:
134 self.test_systemd_disable_enable()
135 finally:
136 status = self.target.run('mount -oro,remount /')[0]
137 self.assertTrue(status == 0, msg='Remounting / as r/o failed')
138
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500139class SystemdJournalTests(SystemdTest):
140
141 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
142 def test_systemd_journal(self):
143 status, output = self.target.run('journalctl')
144 self.assertEqual(status, 0, output)
145
146 @OETestDepends(['systemd.SystemdBasicTests.test_systemd_basic'])
147 def test_systemd_boot_time(self, systemd_TimeoutStartSec=90):
148 """
149 Get the target boot time from journalctl and log it
150
151 Arguments:
152 -systemd_TimeoutStartSec, an optional argument containing systemd's
153 unit start timeout to compare against
154 """
155
156 # The expression chain that uniquely identifies the time boot message.
157 expr_items=['Startup finished', 'kernel', 'userspace','\.$']
158 try:
159 output = self.journalctl(args='-o cat --reverse')
160 except AssertionError:
161 self.fail('Error occurred while calling journalctl')
162 if not len(output):
163 self.fail('Error, unable to get startup time from systemd journal')
164
165 # Check for the regular expression items that match the startup time.
166 for line in output.split('\n'):
167 check_match = ''.join(re.findall('.*'.join(expr_items), line))
168 if check_match:
169 break
170 # Put the startup time in the test log
171 if check_match:
172 self.tc.logger.info('%s' % check_match)
173 else:
174 self.skipTest('Error at obtaining the boot time from journalctl')
175 boot_time_sec = 0
176
177 # Get the numeric values from the string and convert them to seconds
178 # same data will be placed in list and string for manipulation.
179 l_boot_time = check_match.split(' ')[-2:]
180 s_boot_time = ' '.join(l_boot_time)
181 try:
182 # Obtain the minutes it took to boot.
183 if l_boot_time[0].endswith('min') and l_boot_time[0][0].isdigit():
184 boot_time_min = s_boot_time.split('min')[0]
185 # Convert to seconds and accumulate it.
186 boot_time_sec += int(boot_time_min) * 60
187 # Obtain the seconds it took to boot and accumulate.
188 boot_time_sec += float(l_boot_time[1].split('s')[0])
189 except ValueError:
190 self.skipTest('Error when parsing time from boot string')
191
192 # Assert the target boot time against systemd's unit start timeout.
193 if boot_time_sec > systemd_TimeoutStartSec:
194 msg = ("Target boot time %s exceeds systemd's TimeoutStartSec %s"
195 % (boot_time_sec, systemd_TimeoutStartSec))
196 self.tc.logger.info(msg)