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