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