1
0

test_system.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. # -*- coding: utf-8 -*-
  2. # Import Python libs
  3. from __future__ import absolute_import, unicode_literals, print_function
  4. import datetime
  5. import logging
  6. import os
  7. import signal
  8. import subprocess
  9. import textwrap
  10. # Import Salt Testing libs
  11. from tests.support.case import ModuleCase
  12. from tests.support.unit import skipIf
  13. from tests.support.helpers import destructiveTest, skip_if_not_root, flaky
  14. # Import Salt libs
  15. import salt.utils.files
  16. import salt.utils.path
  17. import salt.utils.platform
  18. import salt.states.file
  19. from salt.ext.six.moves import range
  20. from salt.ext import six
  21. log = logging.getLogger(__name__)
  22. @skipIf(not salt.utils.platform.is_linux(), 'These tests can only be run on linux')
  23. class SystemModuleTest(ModuleCase):
  24. '''
  25. Validate the date/time functions in the system module
  26. '''
  27. fmt_str = "%Y-%m-%d %H:%M:%S"
  28. def __init__(self, arg):
  29. super(self.__class__, self).__init__(arg)
  30. self._orig_time = None
  31. self._machine_info = True
  32. def setUp(self):
  33. super(SystemModuleTest, self).setUp()
  34. os_grain = self.run_function('grains.item', ['kernel'])
  35. if os_grain['kernel'] not in 'Linux':
  36. self.skipTest(
  37. 'Test not applicable to \'{kernel}\' kernel'.format(
  38. **os_grain
  39. )
  40. )
  41. if self.run_function('service.available', ['systemd-timesyncd']):
  42. self.run_function('service.stop', ['systemd-timesyncd'])
  43. def tearDown(self):
  44. if self._orig_time is not None:
  45. self._restore_time()
  46. self._orig_time = None
  47. if self._machine_info is not True:
  48. self._restore_machine_info()
  49. self._machine_info = True
  50. if self.run_function('service.available', ['systemd-timesyncd']):
  51. self.run_function('service.start', ['systemd-timesyncd'])
  52. def _save_time(self):
  53. self._orig_time = datetime.datetime.utcnow()
  54. def _set_time(self, new_time, offset=None):
  55. t = new_time.timetuple()[:6]
  56. t += (offset,)
  57. return self.run_function('system.set_system_date_time', t)
  58. def _restore_time(self):
  59. result = self._set_time(self._orig_time, "+0000")
  60. self.assertTrue(result, msg="Unable to restore time properly")
  61. def _same_times(self, t1, t2, seconds_diff=30):
  62. '''
  63. Helper function to check if two datetime objects
  64. are close enough to the same time.
  65. '''
  66. return abs(t1 - t2) < datetime.timedelta(seconds=seconds_diff)
  67. def _hwclock_has_compare(self):
  68. '''
  69. Some builds of hwclock don't include the `--compare` function
  70. needed to test hw/sw clock synchronization. Returns false on
  71. systems where it's not present so that we can skip the
  72. comparison portion of the test.
  73. '''
  74. res = self.run_function('cmd.run_all', cmd='hwclock -h')
  75. return res['retcode'] == 0 and res['stdout'].find('--compare') > 0
  76. def _test_hwclock_sync(self):
  77. '''
  78. Check that hw and sw clocks are sync'd.
  79. '''
  80. if not self.run_function('system.has_settable_hwclock'):
  81. return None
  82. if not self._hwclock_has_compare():
  83. return None
  84. class CompareTimeout(BaseException):
  85. pass
  86. def _alrm_handler(sig, frame):
  87. log.warning('hwclock --compare failed to produce output after 3 seconds')
  88. raise CompareTimeout
  89. for _ in range(2):
  90. try:
  91. orig_handler = signal.signal(signal.SIGALRM, _alrm_handler)
  92. signal.alarm(3)
  93. rpipeFd, wpipeFd = os.pipe()
  94. log.debug('Comparing hwclock to sys clock')
  95. with os.fdopen(rpipeFd, "r") as rpipe:
  96. with os.fdopen(wpipeFd, "w") as wpipe:
  97. with salt.utils.files.fopen(os.devnull, "r") as nulFd:
  98. p = subprocess.Popen(args=['hwclock', '--compare'],
  99. stdin=nulFd, stdout=wpipeFd, stderr=subprocess.PIPE)
  100. p.communicate()
  101. # read header
  102. rpipe.readline()
  103. # read first time comparison
  104. timeCompStr = rpipe.readline()
  105. # stop
  106. p.terminate()
  107. timeComp = timeCompStr.split()
  108. hwTime = float(timeComp[0])
  109. swTime = float(timeComp[1])
  110. diff = abs(hwTime - swTime)
  111. self.assertTrue(diff <= 2.0,
  112. msg=("hwclock difference too big: " + six.text_type(timeCompStr)))
  113. break
  114. except CompareTimeout:
  115. p.terminate()
  116. finally:
  117. signal.alarm(0)
  118. signal.signal(signal.SIGALRM, orig_handler)
  119. else:
  120. log.error('Failed to check hwclock sync')
  121. def _save_machine_info(self):
  122. if os.path.isfile('/etc/machine-info'):
  123. with salt.utils.files.fopen('/etc/machine-info', 'r') as mach_info:
  124. self._machine_info = mach_info.read()
  125. else:
  126. self._machine_info = False
  127. def _restore_machine_info(self):
  128. if self._machine_info is not False:
  129. with salt.utils.files.fopen('/etc/machine-info', 'w') as mach_info:
  130. mach_info.write(self._machine_info)
  131. else:
  132. self.run_function('file.remove', ['/etc/machine-info'])
  133. def test_get_system_date_time(self):
  134. '''
  135. Test we are able to get the correct time
  136. '''
  137. t1 = datetime.datetime.now()
  138. res = self.run_function('system.get_system_date_time')
  139. t2 = datetime.datetime.strptime(res, self.fmt_str)
  140. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  141. .format(t1, t2))
  142. self.assertTrue(self._same_times(t1, t2, seconds_diff=2), msg=msg)
  143. def test_get_system_date_time_utc(self):
  144. '''
  145. Test we are able to get the correct time with utc
  146. '''
  147. t1 = datetime.datetime.utcnow()
  148. res = self.run_function('system.get_system_date_time',
  149. utc_offset="+0000")
  150. t2 = datetime.datetime.strptime(res, self.fmt_str)
  151. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  152. .format(t1, t2))
  153. self.assertTrue(self._same_times(t1, t2, seconds_diff=2), msg=msg)
  154. @destructiveTest
  155. @skip_if_not_root
  156. def test_set_system_date_time(self):
  157. '''
  158. Test changing the system clock. We are only able to set it up to a
  159. resolution of a second so this test may appear to run in negative time.
  160. '''
  161. self._save_time()
  162. cmp_time = datetime.datetime.now() - datetime.timedelta(days=7)
  163. result = self._set_time(cmp_time)
  164. time_now = datetime.datetime.now()
  165. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  166. .format(time_now, cmp_time))
  167. self.assertTrue(result and self._same_times(time_now, cmp_time),
  168. msg=msg)
  169. self._test_hwclock_sync()
  170. @destructiveTest
  171. @skip_if_not_root
  172. def test_set_system_date_time_utc(self):
  173. '''
  174. Test changing the system clock. We are only able to set it up to a
  175. resolution of a second so this test may appear to run in negative time.
  176. '''
  177. self._save_time()
  178. cmp_time = datetime.datetime.utcnow() - datetime.timedelta(days=7)
  179. result = self._set_time(cmp_time, offset="+0000")
  180. time_now = datetime.datetime.utcnow()
  181. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  182. .format(time_now, cmp_time))
  183. self.assertTrue(result)
  184. self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
  185. self._test_hwclock_sync()
  186. @destructiveTest
  187. @skip_if_not_root
  188. def test_set_system_date_time_utcoffset_east(self):
  189. '''
  190. Test changing the system clock. We are only able to set it up to a
  191. resolution of a second so this test may appear to run in negative time.
  192. '''
  193. self._save_time()
  194. cmp_time = datetime.datetime.utcnow() - datetime.timedelta(days=7)
  195. # 25200 seconds = 7 hours
  196. time_to_set = cmp_time - datetime.timedelta(seconds=25200)
  197. result = self._set_time(time_to_set, offset='-0700')
  198. time_now = datetime.datetime.utcnow()
  199. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  200. .format(time_now, cmp_time))
  201. self.assertTrue(result)
  202. self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
  203. self._test_hwclock_sync()
  204. @destructiveTest
  205. @skip_if_not_root
  206. def test_set_system_date_time_utcoffset_west(self):
  207. '''
  208. Test changing the system clock. We are only able to set it up to a
  209. resolution of a second so this test may appear to run in negative time.
  210. '''
  211. self._save_time()
  212. cmp_time = datetime.datetime.utcnow() - datetime.timedelta(days=7)
  213. # 7200 seconds = 2 hours
  214. time_to_set = cmp_time + datetime.timedelta(seconds=7200)
  215. result = self._set_time(time_to_set, offset='+0200')
  216. time_now = datetime.datetime.utcnow()
  217. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  218. .format(time_now, cmp_time))
  219. self.assertTrue(result)
  220. self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
  221. self._test_hwclock_sync()
  222. @flaky
  223. @destructiveTest
  224. @skip_if_not_root
  225. def test_set_system_time(self):
  226. '''
  227. Test setting the system time without adjusting the date.
  228. '''
  229. cmp_time = datetime.datetime.now().replace(hour=10, minute=5, second=0)
  230. self._save_time()
  231. result = self.run_function('system.set_system_time', ["10:05:00"])
  232. time_now = datetime.datetime.now()
  233. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  234. .format(time_now, cmp_time))
  235. self.assertTrue(result)
  236. self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
  237. self._test_hwclock_sync()
  238. @destructiveTest
  239. @skip_if_not_root
  240. def test_set_system_date(self):
  241. '''
  242. Test setting the system date without adjusting the time.
  243. '''
  244. cmp_time = datetime.datetime.now() - datetime.timedelta(days=7)
  245. self._save_time()
  246. result = self.run_function(
  247. 'system.set_system_date',
  248. [cmp_time.strftime('%Y-%m-%d')]
  249. )
  250. time_now = datetime.datetime.now()
  251. msg = ("Difference in times is too large. Now: {0} Fake: {1}"
  252. .format(time_now, cmp_time))
  253. self.assertTrue(result)
  254. self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
  255. self._test_hwclock_sync()
  256. @skip_if_not_root
  257. def test_get_computer_desc(self):
  258. '''
  259. Test getting the system hostname
  260. '''
  261. res = self.run_function('system.get_computer_desc')
  262. hostname_cmd = salt.utils.path.which('hostnamectl')
  263. if hostname_cmd:
  264. desc = self.run_function('cmd.run', ["hostnamectl status --pretty"])
  265. self.assertEqual(res, desc)
  266. else:
  267. if not os.path.isfile('/etc/machine-info'):
  268. self.assertFalse(res)
  269. else:
  270. with salt.utils.files.fopen('/etc/machine-info', 'r') as mach_info:
  271. data = mach_info.read()
  272. self.assertIn(res, data.decode('string_escape'))
  273. @destructiveTest
  274. @skip_if_not_root
  275. def test_set_computer_desc(self):
  276. '''
  277. Test setting the computer description
  278. '''
  279. self._save_machine_info()
  280. desc = "test"
  281. ret = self.run_function('system.set_computer_desc', [desc])
  282. computer_desc = self.run_function('system.get_computer_desc')
  283. self.assertTrue(ret)
  284. self.assertIn(desc, computer_desc)
  285. @destructiveTest
  286. @skip_if_not_root
  287. def test_set_computer_desc_multiline(self):
  288. '''
  289. Test setting the computer description with a multiline string with tabs
  290. and double-quotes.
  291. '''
  292. self._save_machine_info()
  293. desc = textwrap.dedent('''\
  294. 'First Line
  295. \tSecond Line: 'single-quoted string'
  296. \t\tThird Line: "double-quoted string with unicode: питон"''')
  297. ret = self.run_function('system.set_computer_desc', [desc])
  298. # self.run_function returns the serialized return, we need to convert
  299. # back to unicode to compare to desc. in the assertIn below.
  300. computer_desc = salt.utils.stringutils.to_unicode(
  301. self.run_function('system.get_computer_desc')
  302. )
  303. self.assertTrue(ret)
  304. self.assertIn(desc, computer_desc)
  305. @skip_if_not_root
  306. def test_has_hwclock(self):
  307. '''
  308. Verify platform has a settable hardware clock, if possible.
  309. '''
  310. if self.run_function('grains.get', ['os_family']) == 'NILinuxRT':
  311. self.assertTrue(self.run_function('system._has_settable_hwclock'))
  312. self.assertTrue(self._hwclock_has_compare())
  313. @skipIf(not salt.utils.platform.is_windows(), 'These tests can only be run on windows')
  314. class WinSystemModuleTest(ModuleCase):
  315. '''
  316. Validate the date/time functions in the win_system module
  317. '''
  318. @classmethod
  319. def tearDownClass(cls):
  320. if subprocess.call('w32tm /resync', shell=True) != 0:
  321. log.error("Re-syncing time failed")
  322. def test_get_computer_name(self):
  323. '''
  324. Test getting the computer name
  325. '''
  326. ret = self.run_function('system.get_computer_name')
  327. self.assertTrue(isinstance(ret, six.text_type))
  328. import socket
  329. name = socket.gethostname()
  330. self.assertEqual(name, ret)
  331. @destructiveTest
  332. def test_set_computer_desc(self):
  333. '''
  334. Test setting the computer description
  335. '''
  336. desc = 'test description'
  337. set_desc = self.run_function('system.set_computer_desc', [desc])
  338. self.assertTrue(set_desc)
  339. get_desc = self.run_function('system.get_computer_desc')
  340. self.assertEqual(set_desc['Computer Description'], get_desc)
  341. def test_get_system_time(self):
  342. '''
  343. Test getting the system time
  344. '''
  345. ret = self.run_function('system.get_system_time')
  346. now = datetime.datetime.now()
  347. self.assertEqual(now.strftime("%I:%M"), ret.rsplit(':', 1)[0])
  348. @flaky
  349. @destructiveTest
  350. @flaky
  351. def test_set_system_time(self):
  352. '''
  353. Test setting the system time
  354. '''
  355. test_time = '10:55'
  356. set_time = self.run_function('system.set_system_time', [test_time + ' AM'])
  357. get_time = self.run_function('system.get_system_time').rsplit(':', 1)[0]
  358. self.assertEqual(get_time, test_time)
  359. def test_get_system_date(self):
  360. '''
  361. Test getting system date
  362. '''
  363. ret = self.run_function('system.get_system_date')
  364. date = datetime.datetime.now().date().strftime("%m/%d/%Y")
  365. self.assertEqual(date, ret)
  366. @destructiveTest
  367. def test_set_system_date(self):
  368. '''
  369. Test setting system date
  370. '''
  371. self.assertTrue(self.run_function('system.set_system_date', ['3/25/2018']))