test_system.py 18 KB

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