test_system.py 19 KB

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