test_pkg.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import os
  4. import pprint
  5. import pytest
  6. import salt.utils.path
  7. import salt.utils.pkg
  8. import salt.utils.platform
  9. from tests.support.case import ModuleCase
  10. from tests.support.helpers import requires_system_grains
  11. from tests.support.mixins import SaltReturnAssertsMixin
  12. from tests.support.unit import skipIf
  13. @pytest.mark.windows_whitelisted
  14. class PkgModuleTest(ModuleCase, SaltReturnAssertsMixin):
  15. """
  16. Validate the pkg module
  17. """
  18. @classmethod
  19. @requires_system_grains
  20. def setUpClass(cls, grains): # pylint: disable=arguments-differ
  21. cls.ctx = {}
  22. cls.pkg = "figlet"
  23. if salt.utils.platform.is_windows():
  24. cls.pkg = "putty"
  25. elif grains["os_family"] == "RedHat":
  26. cls.pkg = "units"
  27. @pytest.mark.skip_if_not_root
  28. @pytest.mark.requires_salt_modules("pkg.refresh_db")
  29. def setUp(self):
  30. if "refresh" not in self.ctx:
  31. self.run_function("pkg.refresh_db")
  32. self.ctx["refresh"] = True
  33. @pytest.mark.requires_salt_modules("pkg.list_pkgs")
  34. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  35. def test_list(self):
  36. """
  37. verify that packages are installed
  38. """
  39. ret = self.run_function("pkg.list_pkgs")
  40. self.assertNotEqual(len(ret.keys()), 0)
  41. @pytest.mark.requires_salt_modules("pkg.version_cmp")
  42. @requires_system_grains
  43. @pytest.mark.slow_test(seconds=5) # Test takes >1 and <=5 seconds
  44. def test_version_cmp(self, grains):
  45. """
  46. test package version comparison on supported platforms
  47. """
  48. func = "pkg.version_cmp"
  49. if grains["os_family"] == "Debian":
  50. lt = ["0.2.4-0ubuntu1", "0.2.4.1-0ubuntu1"]
  51. eq = ["0.2.4-0ubuntu1", "0.2.4-0ubuntu1"]
  52. gt = ["0.2.4.1-0ubuntu1", "0.2.4-0ubuntu1"]
  53. elif grains["os_family"] == "Suse":
  54. lt = ["2.3.0-1", "2.3.1-15.1"]
  55. eq = ["2.3.1-15.1", "2.3.1-15.1"]
  56. gt = ["2.3.2-15.1", "2.3.1-15.1"]
  57. else:
  58. lt = ["2.3.0", "2.3.1"]
  59. eq = ["2.3.1", "2.3.1"]
  60. gt = ["2.3.2", "2.3.1"]
  61. self.assertEqual(self.run_function(func, lt), -1)
  62. self.assertEqual(self.run_function(func, eq), 0)
  63. self.assertEqual(self.run_function(func, gt), 1)
  64. @pytest.mark.destructive_test
  65. @pytest.mark.requires_salt_modules("pkg.mod_repo", "pkg.del_repo", "pkg.get_repo")
  66. @pytest.mark.requires_network
  67. @requires_system_grains
  68. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  69. def test_mod_del_repo(self, grains):
  70. """
  71. test modifying and deleting a software repository
  72. """
  73. repo = None
  74. try:
  75. if grains["os"] == "Ubuntu":
  76. repo = "ppa:otto-kesselgulasch/gimp-edge"
  77. uri = "http://ppa.launchpad.net/otto-kesselgulasch/gimp-edge/ubuntu"
  78. ret = self.run_function("pkg.mod_repo", [repo, "comps=main"])
  79. self.assertNotEqual(ret, {})
  80. ret = self.run_function("pkg.get_repo", [repo])
  81. self.assertIsInstance(
  82. ret,
  83. dict,
  84. "The 'pkg.get_repo' command did not return the excepted dictionary. "
  85. "Output:\n{}".format(ret),
  86. )
  87. self.assertEqual(
  88. ret["uri"],
  89. uri,
  90. msg="The URI did not match. Full return:\n{}".format(
  91. pprint.pformat(ret)
  92. ),
  93. )
  94. elif grains["os_family"] == "RedHat":
  95. repo = "saltstack"
  96. name = "SaltStack repo for RHEL/CentOS {0}".format(
  97. grains["osmajorrelease"]
  98. )
  99. baseurl = "http://repo.saltstack.com/yum/redhat/{0}/x86_64/latest/".format(
  100. grains["osmajorrelease"]
  101. )
  102. gpgkey = "https://repo.saltstack.com/yum/rhel{0}/SALTSTACK-GPG-KEY.pub".format(
  103. grains["osmajorrelease"]
  104. )
  105. gpgcheck = 1
  106. enabled = 1
  107. ret = self.run_function(
  108. "pkg.mod_repo",
  109. [repo],
  110. name=name,
  111. baseurl=baseurl,
  112. gpgkey=gpgkey,
  113. gpgcheck=gpgcheck,
  114. enabled=enabled,
  115. )
  116. # return data from pkg.mod_repo contains the file modified at
  117. # the top level, so use next(iter(ret)) to get that key
  118. self.assertNotEqual(ret, {})
  119. repo_info = ret[next(iter(ret))]
  120. self.assertIn(repo, repo_info)
  121. self.assertEqual(repo_info[repo]["baseurl"], baseurl)
  122. ret = self.run_function("pkg.get_repo", [repo])
  123. self.assertEqual(ret["baseurl"], baseurl)
  124. finally:
  125. if repo is not None:
  126. self.run_function("pkg.del_repo", [repo])
  127. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  128. def test_mod_del_repo_multiline_values(self):
  129. """
  130. test modifying and deleting a software repository defined with multiline values
  131. """
  132. os_grain = self.run_function("grains.item", ["os"])["os"]
  133. repo = None
  134. try:
  135. if os_grain in ["CentOS", "RedHat"]:
  136. my_baseurl = (
  137. "http://my.fake.repo/foo/bar/\n http://my.fake.repo.alt/foo/bar/"
  138. )
  139. expected_get_repo_baseurl = (
  140. "http://my.fake.repo/foo/bar/\nhttp://my.fake.repo.alt/foo/bar/"
  141. )
  142. major_release = int(
  143. self.run_function("grains.item", ["osmajorrelease"])[
  144. "osmajorrelease"
  145. ]
  146. )
  147. repo = "fakerepo"
  148. name = "Fake repo for RHEL/CentOS/SUSE"
  149. baseurl = my_baseurl
  150. gpgkey = "https://my.fake.repo/foo/bar/MY-GPG-KEY.pub"
  151. failovermethod = "priority"
  152. gpgcheck = 1
  153. enabled = 1
  154. ret = self.run_function(
  155. "pkg.mod_repo",
  156. [repo],
  157. name=name,
  158. baseurl=baseurl,
  159. gpgkey=gpgkey,
  160. gpgcheck=gpgcheck,
  161. enabled=enabled,
  162. failovermethod=failovermethod,
  163. )
  164. # return data from pkg.mod_repo contains the file modified at
  165. # the top level, so use next(iter(ret)) to get that key
  166. self.assertNotEqual(ret, {})
  167. repo_info = ret[next(iter(ret))]
  168. self.assertIn(repo, repo_info)
  169. self.assertEqual(repo_info[repo]["baseurl"], my_baseurl)
  170. ret = self.run_function("pkg.get_repo", [repo])
  171. self.assertEqual(ret["baseurl"], expected_get_repo_baseurl)
  172. self.run_function("pkg.mod_repo", [repo])
  173. ret = self.run_function("pkg.get_repo", [repo])
  174. self.assertEqual(ret["baseurl"], expected_get_repo_baseurl)
  175. finally:
  176. if repo is not None:
  177. self.run_function("pkg.del_repo", [repo])
  178. @pytest.mark.requires_salt_modules("pkg.owner")
  179. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  180. def test_owner(self):
  181. """
  182. test finding the package owning a file
  183. """
  184. func = "pkg.owner"
  185. ret = self.run_function(func, ["/bin/ls"])
  186. self.assertNotEqual(len(ret), 0)
  187. # Similar to pkg.owner, but for FreeBSD's pkgng
  188. @pytest.mark.requires_salt_modules("pkg.which")
  189. def test_which(self):
  190. """
  191. test finding the package owning a file
  192. """
  193. func = "pkg.which"
  194. ret = self.run_function(func, ["/usr/local/bin/salt-call"])
  195. self.assertNotEqual(len(ret), 0)
  196. @pytest.mark.destructive_test
  197. @pytest.mark.requires_salt_modules("pkg.version", "pkg.install", "pkg.remove")
  198. @pytest.mark.requires_network
  199. @pytest.mark.slow_test(seconds=120) # Test takes >60 and <=120 seconds
  200. def test_install_remove(self):
  201. """
  202. successfully install and uninstall a package
  203. """
  204. version = self.run_function("pkg.version", [self.pkg])
  205. def test_install():
  206. install_ret = self.run_function("pkg.install", [self.pkg])
  207. self.assertIn(self.pkg, install_ret)
  208. def test_remove():
  209. remove_ret = self.run_function("pkg.remove", [self.pkg])
  210. self.assertIn(self.pkg, remove_ret)
  211. if version and isinstance(version, dict):
  212. version = version[self.pkg]
  213. if version:
  214. test_remove()
  215. test_install()
  216. else:
  217. test_install()
  218. test_remove()
  219. @pytest.mark.destructive_test
  220. @pytest.mark.requires_salt_states("pkg.installed")
  221. @pytest.mark.requires_salt_modules(
  222. "pkg.hold",
  223. "pkg.unhold",
  224. "pkg.install",
  225. "pkg.version",
  226. "pkg.remove",
  227. "pkg.list_pkgs",
  228. )
  229. @pytest.mark.requires_network
  230. @requires_system_grains
  231. @pytest.mark.slow_test(seconds=120) # Test takes >60 and <=120 seconds
  232. def test_hold_unhold(self, grains):
  233. """
  234. test holding and unholding a package
  235. """
  236. versionlock_pkg = None
  237. if grains["os_family"] == "RedHat":
  238. pkgs = {
  239. p for p in self.run_function("pkg.list_pkgs") if "-versionlock" in p
  240. }
  241. if not pkgs:
  242. self.skipTest("No versionlock package found in repositories")
  243. for versionlock_pkg in pkgs:
  244. ret = self.run_state(
  245. "pkg.installed", name=versionlock_pkg, refresh=False
  246. )
  247. # Exit loop if a versionlock package installed correctly
  248. try:
  249. self.assertSaltTrueReturn(ret)
  250. break
  251. except AssertionError:
  252. pass
  253. else:
  254. self.fail("Could not install versionlock package from {}".format(pkgs))
  255. self.run_function("pkg.install", [self.pkg])
  256. try:
  257. hold_ret = self.run_function("pkg.hold", [self.pkg])
  258. if versionlock_pkg and "-versionlock is not installed" in str(hold_ret):
  259. self.skipTest("{} `{}` is installed".format(hold_ret, versionlock_pkg))
  260. self.assertIn(self.pkg, hold_ret)
  261. self.assertTrue(hold_ret[self.pkg]["result"])
  262. unhold_ret = self.run_function("pkg.unhold", [self.pkg])
  263. self.assertIn(self.pkg, unhold_ret)
  264. self.assertTrue(unhold_ret[self.pkg]["result"])
  265. self.run_function("pkg.remove", [self.pkg])
  266. finally:
  267. if versionlock_pkg:
  268. ret = self.run_state("pkg.removed", name=versionlock_pkg)
  269. self.assertSaltTrueReturn(ret)
  270. @pytest.mark.destructive_test
  271. @pytest.mark.requires_salt_modules("pkg.refresh_db")
  272. @pytest.mark.requires_network
  273. @requires_system_grains
  274. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  275. def test_refresh_db(self, grains):
  276. """
  277. test refreshing the package database
  278. """
  279. func = "pkg.refresh_db"
  280. rtag = salt.utils.pkg.rtag(self.minion_opts)
  281. salt.utils.pkg.write_rtag(self.minion_opts)
  282. self.assertTrue(os.path.isfile(rtag))
  283. ret = self.run_function(func)
  284. if not isinstance(ret, dict):
  285. self.skipTest(
  286. "Upstream repo did not return coherent results: {}".format(ret)
  287. )
  288. if grains["os_family"] == "RedHat":
  289. self.assertIn(ret, (True, None))
  290. elif grains["os_family"] == "Suse":
  291. if not isinstance(ret, dict):
  292. self.skipTest(
  293. "Upstream repo did not return coherent results. Skipping test."
  294. )
  295. self.assertNotEqual(ret, {})
  296. for source, state in ret.items():
  297. self.assertIn(state, (True, False, None))
  298. self.assertFalse(os.path.isfile(rtag))
  299. @pytest.mark.requires_salt_modules("pkg.info_installed")
  300. @requires_system_grains
  301. @pytest.mark.slow_test(seconds=5) # Test takes >1 and <=5 seconds
  302. def test_pkg_info(self, grains):
  303. """
  304. Test returning useful information on Ubuntu systems.
  305. """
  306. func = "pkg.info_installed"
  307. if grains["os_family"] == "Debian":
  308. ret = self.run_function(func, ["bash", "dpkg"])
  309. keys = ret.keys()
  310. self.assertIn("bash", keys)
  311. self.assertIn("dpkg", keys)
  312. elif grains["os_family"] == "RedHat":
  313. ret = self.run_function(func, ["rpm", "bash"])
  314. keys = ret.keys()
  315. self.assertIn("rpm", keys)
  316. self.assertIn("bash", keys)
  317. elif grains["os_family"] == "Suse":
  318. ret = self.run_function(func, ["less", "zypper"])
  319. keys = ret.keys()
  320. self.assertIn("less", keys)
  321. self.assertIn("zypper", keys)
  322. else:
  323. ret = self.run_function(func, [self.pkg])
  324. keys = ret.keys()
  325. self.assertIn(self.pkg, keys)
  326. @pytest.mark.destructive_test
  327. @pytest.mark.requires_network
  328. @pytest.mark.requires_salt_modules(
  329. "pkg.refresh_db",
  330. "pkg.upgrade",
  331. "pkg.install",
  332. "pkg.list_repo_pkgs",
  333. "pkg.list_upgrades",
  334. )
  335. @requires_system_grains
  336. def test_pkg_upgrade_has_pending_upgrades(self, grains):
  337. """
  338. Test running a system upgrade when there are packages that need upgrading
  339. """
  340. if grains["os"] == "Arch":
  341. self.skipTest("Arch moved to Python 3.8 and we're not ready for it yet")
  342. func = "pkg.upgrade"
  343. # First make sure that an up-to-date copy of the package db is available
  344. self.run_function("pkg.refresh_db")
  345. if grains["os_family"] == "Suse":
  346. # This test assumes that there are multiple possible versions of a
  347. # package available. That makes it brittle if you pick just one
  348. # target, as changes in the available packages will break the test.
  349. # Therefore, we'll choose from several packages to make sure we get
  350. # one that is suitable for this test.
  351. packages = ("hwinfo", "avrdude", "diffoscope", "vim")
  352. available = self.run_function("pkg.list_repo_pkgs", packages)
  353. for package in packages:
  354. try:
  355. new, old = available[package][:2]
  356. except (KeyError, ValueError):
  357. # Package not available, or less than 2 versions
  358. # available. This is not a suitable target.
  359. continue
  360. else:
  361. target = package
  362. break
  363. else:
  364. # None of the packages have more than one version available, so
  365. # we need to find new package(s). pkg.list_repo_pkgs can be
  366. # used to get an overview of the available packages. We should
  367. # try to find packages with few dependencies and small download
  368. # sizes, to keep this test from taking longer than necessary.
  369. self.fail("No suitable package found for this test")
  370. # Make sure we have the 2nd-oldest available version installed
  371. ret = self.run_function("pkg.install", [target], version=old)
  372. if not isinstance(ret, dict):
  373. if ret.startswith("ERROR"):
  374. self.skipTest(
  375. "Could not install older {0} to complete "
  376. "test.".format(target)
  377. )
  378. # Run a system upgrade, which should catch the fact that the
  379. # targeted package needs upgrading, and upgrade it.
  380. ret = self.run_function(func)
  381. # The changes dictionary should not be empty.
  382. if "changes" in ret:
  383. self.assertIn(target, ret["changes"])
  384. else:
  385. self.assertIn(target, ret)
  386. else:
  387. ret = self.run_function("pkg.list_upgrades")
  388. if ret == "" or ret == {}:
  389. self.skipTest(
  390. "No updates available for this machine. Skipping pkg.upgrade test."
  391. )
  392. else:
  393. args = []
  394. if grains["os_family"] == "Debian":
  395. args = ["dist_upgrade=True"]
  396. ret = self.run_function(func, args)
  397. self.assertNotEqual(ret, {})
  398. @skipIf(
  399. salt.utils.platform.is_darwin(),
  400. "The jenkins user is equivalent to root on mac, causing the test to be unrunnable",
  401. )
  402. @pytest.mark.destructive_test
  403. @pytest.mark.requires_salt_states("pkg.removed")
  404. @pytest.mark.requires_salt_modules("pkg.remove", "pkg.latest_version")
  405. @requires_system_grains
  406. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  407. def test_pkg_latest_version(self, grains):
  408. """
  409. Check that pkg.latest_version returns the latest version of the uninstalled package.
  410. The package is not installed. Only the package version is checked.
  411. """
  412. self.run_state("pkg.removed", name=self.pkg)
  413. cmd_pkg = []
  414. if grains["os_family"] == "RedHat":
  415. cmd_pkg = self.run_function("cmd.run", ["yum list {0}".format(self.pkg)])
  416. elif salt.utils.platform.is_windows():
  417. cmd_pkg = self.run_function("pkg.list_available", [self.pkg])
  418. elif grains["os_family"] == "Debian":
  419. cmd_pkg = self.run_function("cmd.run", ["apt list {0}".format(self.pkg)])
  420. elif grains["os_family"] == "Arch":
  421. cmd_pkg = self.run_function("cmd.run", ["pacman -Si {0}".format(self.pkg)])
  422. elif grains["os_family"] == "FreeBSD":
  423. cmd_pkg = self.run_function(
  424. "cmd.run", ["pkg search -S name -qQ version -e {0}".format(self.pkg)]
  425. )
  426. elif grains["os_family"] == "Suse":
  427. cmd_pkg = self.run_function("cmd.run", ["zypper info {0}".format(self.pkg)])
  428. elif grains["os_family"] == "MacOS":
  429. brew_bin = salt.utils.path.which("brew")
  430. mac_user = self.run_function("file.get_user", [brew_bin])
  431. if mac_user == "root":
  432. self.skipTest(
  433. "brew cannot run as root, try a user in {}".format(
  434. os.listdir("/Users/")
  435. )
  436. )
  437. cmd_pkg = self.run_function(
  438. "cmd.run", ["brew info {0}".format(self.pkg)], run_as=mac_user
  439. )
  440. else:
  441. self.skipTest(
  442. "TODO: test not configured for {}".format(grains["os_family"])
  443. )
  444. pkg_latest = self.run_function("pkg.latest_version", [self.pkg])
  445. self.assertIn(pkg_latest, cmd_pkg)