systemd.py 7.3 KB

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