PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/unit/modules/zypper_test.py

https://gitlab.com/ricardo.hernandez/salt
Python | 418 lines | 378 code | 18 blank | 22 comment | 3 complexity | 9e81780070a809c532e0d973e830ffa3 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
  4. '''
  5. # Import Python Libs
  6. from __future__ import absolute_import
  7. # Import Salt Testing Libs
  8. from salttesting import TestCase, skipIf
  9. from salttesting.mock import (
  10. MagicMock,
  11. patch,
  12. NO_MOCK,
  13. NO_MOCK_REASON
  14. )
  15. from salt.exceptions import CommandExecutionError
  16. import os
  17. from salt.ext.six.moves import configparser
  18. import StringIO
  19. from salttesting.helpers import ensure_in_syspath
  20. ensure_in_syspath('../../')
  21. class ZyppCallMock(object):
  22. def __init__(self, return_value=None):
  23. self.__return_value = return_value
  24. def __getattr__(self, item):
  25. return self
  26. def __call__(self, *args, **kwargs):
  27. return MagicMock(return_value=self.__return_value)()
  28. def get_test_data(filename):
  29. '''
  30. Return static test data
  31. '''
  32. return open(os.path.join(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'zypp'), filename)).read()
  33. # Import Salt Libs
  34. from salt.modules import zypper
  35. # Globals
  36. zypper.__salt__ = dict()
  37. zypper.__context__ = dict()
  38. zypper.rpm = None
  39. @skipIf(NO_MOCK, NO_MOCK_REASON)
  40. class ZypperTestCase(TestCase):
  41. '''
  42. Test cases for salt.modules.zypper
  43. '''
  44. def test_list_upgrades(self):
  45. '''
  46. List package upgrades
  47. :return:
  48. '''
  49. ref_out = {
  50. 'stdout': get_test_data('zypper-updates.xml'),
  51. 'stderr': None,
  52. 'retcode': 0
  53. }
  54. with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
  55. upgrades = zypper.list_upgrades(refresh=False)
  56. self.assertEqual(len(upgrades), 3)
  57. for pkg, version in {'SUSEConnect': '0.2.33-7.1',
  58. 'bind-utils': '9.9.6P1-35.1',
  59. 'bind-libs': '9.9.6P1-35.1'}.items():
  60. self.assertIn(pkg, upgrades)
  61. self.assertEqual(upgrades[pkg], version)
  62. def test_zypper_caller(self):
  63. '''
  64. Test Zypper caller.
  65. :return:
  66. '''
  67. class RunSniffer(object):
  68. def __init__(self, stdout=None, stderr=None, retcode=None):
  69. self.calls = list()
  70. self._stdout = stdout or ''
  71. self._stderr = stderr or ''
  72. self._retcode = retcode or 0
  73. def __call__(self, *args, **kwargs):
  74. self.calls.append({'args': args, 'kwargs': kwargs})
  75. return {'stdout': self._stdout,
  76. 'stderr': self._stderr,
  77. 'retcode': self._retcode}
  78. stdout_xml_snippet = '<?xml version="1.0"?><test foo="bar"/>'
  79. sniffer = RunSniffer(stdout=stdout_xml_snippet)
  80. with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}):
  81. self.assertEqual(zypper.__zypper__.call('foo'), stdout_xml_snippet)
  82. self.assertEqual(len(sniffer.calls), 1)
  83. zypper.__zypper__.call('bar')
  84. self.assertEqual(len(sniffer.calls), 2)
  85. self.assertEqual(sniffer.calls[0]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'foo'])
  86. self.assertEqual(sniffer.calls[1]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'bar'])
  87. dom = zypper.__zypper__.xml.call('xml-test')
  88. self.assertEqual(sniffer.calls[2]['args'][0], ['zypper', '--non-interactive', '--xmlout',
  89. '--no-refresh', 'xml-test'])
  90. self.assertEqual(dom.getElementsByTagName('test')[0].getAttribute('foo'), 'bar')
  91. zypper.__zypper__.refreshable.call('refresh-test')
  92. self.assertEqual(sniffer.calls[3]['args'][0], ['zypper', '--non-interactive', 'refresh-test'])
  93. zypper.__zypper__.nolock.call('no-locking-test')
  94. self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), "1")
  95. self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1")
  96. zypper.__zypper__.call('locking-test')
  97. self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), None)
  98. self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1")
  99. # Test exceptions
  100. stdout_xml_snippet = '<?xml version="1.0"?><stream><message type="error">Booya!</message></stream>'
  101. sniffer = RunSniffer(stdout=stdout_xml_snippet, retcode=1)
  102. with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}):
  103. with self.assertRaisesRegexp(CommandExecutionError, '^Zypper command failure: Booya!$'):
  104. zypper.__zypper__.xml.call('crashme')
  105. with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"):
  106. zypper.__zypper__.call('crashme again')
  107. zypper.__zypper__.noraise.call('stay quiet')
  108. self.assertEqual(zypper.__zypper__.error_msg, "Check Zypper's logs.")
  109. def test_list_upgrades_error_handling(self):
  110. '''
  111. Test error handling in the list package upgrades.
  112. :return:
  113. '''
  114. # Test handled errors
  115. ref_out = {
  116. 'stdout': '''<?xml version='1.0'?>
  117. <stream>
  118. <message type="info">Refreshing service &apos;container-suseconnect&apos;.</message>
  119. <message type="error">Some handled zypper internal error</message>
  120. <message type="error">Another zypper internal error</message>
  121. </stream>
  122. ''',
  123. 'stderr': '',
  124. 'retcode': 1,
  125. }
  126. with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}):
  127. with self.assertRaisesRegexp(CommandExecutionError,
  128. "^Zypper command failure: Some handled zypper internal error\nAnother zypper internal error$"):
  129. zypper.list_upgrades(refresh=False)
  130. # Test unhandled error
  131. ref_out = {
  132. 'retcode': 1,
  133. 'stdout': '',
  134. 'stderr': ''
  135. }
  136. with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}):
  137. with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"):
  138. zypper.list_upgrades(refresh=False)
  139. def test_list_products(self):
  140. '''
  141. List products test.
  142. '''
  143. for filename, test_data in {
  144. 'zypper-products-sle12sp1.xml': {
  145. 'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy',
  146. 'SUSE-Manager-Server', 'sle-manager-tools-beta',
  147. 'sle-manager-tools-beta-broken-eol', 'sle-manager-tools-beta-no-eol'],
  148. 'vendor': 'SUSE LLC <https://www.suse.com/>',
  149. 'release': ['0', '0', '0', '0', '0', '0', '0'],
  150. 'productline': [False, False, False, False, False, False, 'sles'],
  151. 'eol_t': [None, 0, 1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
  152. 'isbase': [False, False, False, False, False, False, True],
  153. 'installed': [False, False, False, False, False, False, True],
  154. },
  155. 'zypper-products-sle11sp3.xml': {
  156. 'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', 'SUSE-Manager-Server-Broken-EOL',
  157. 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'],
  158. 'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany',
  159. 'release': ['1.138', '1.2', '1.2', '1.2', '1.201', '1.201', '1.4'],
  160. 'productline': [False, False, False, False, False, 'manager', 'manager'],
  161. 'eol_t': [None, 0, 0, 0, 0, 0, 0],
  162. 'isbase': [False, False, False, False, False, True, True],
  163. 'installed': [False, False, False, False, False, True, True],
  164. }}.items():
  165. ref_out = {
  166. 'retcode': 0,
  167. 'stdout': get_test_data(filename)
  168. }
  169. with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
  170. products = zypper.list_products()
  171. self.assertEqual(len(products), 7)
  172. self.assertIn(test_data['vendor'], [product['vendor'] for product in products])
  173. for kwd in ['name', 'isbase', 'installed', 'release', 'productline', 'eol_t']:
  174. self.assertEqual(test_data[kwd], sorted([prod.get(kwd) for prod in products]))
  175. def test_refresh_db(self):
  176. '''
  177. Test if refresh DB handled correctly
  178. '''
  179. ref_out = [
  180. "Repository 'openSUSE-Leap-42.1-LATEST' is up to date.",
  181. "Repository 'openSUSE-Leap-42.1-Update' is up to date.",
  182. "Retrieving repository 'openSUSE-Leap-42.1-Update-Non-Oss' metadata",
  183. "Forcing building of repository cache",
  184. "Building repository 'openSUSE-Leap-42.1-Update-Non-Oss' cache ..........[done]",
  185. "Building repository 'salt-dev' cache",
  186. "All repositories have been refreshed."
  187. ]
  188. run_out = {
  189. 'stderr': '', 'stdout': '\n'.join(ref_out), 'retcode': 0
  190. }
  191. with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=run_out)}):
  192. result = zypper.refresh_db()
  193. self.assertEqual(result.get("openSUSE-Leap-42.1-LATEST"), False)
  194. self.assertEqual(result.get("openSUSE-Leap-42.1-Update"), False)
  195. self.assertEqual(result.get("openSUSE-Leap-42.1-Update-Non-Oss"), True)
  196. def test_info_installed(self):
  197. '''
  198. Test the return information of the named package(s), installed on the system.
  199. :return:
  200. '''
  201. run_out = {
  202. 'virgo-dummy':
  203. {'build_date': '2015-07-09T10:55:19Z',
  204. 'vendor': 'openSUSE Build Service',
  205. 'description': 'This is the Virgo dummy package used for testing SUSE Manager',
  206. 'license': 'GPL-2.0', 'build_host': 'sheep05', 'url': 'http://www.suse.com',
  207. 'build_date_time_t': 1436432119, 'relocations': '(not relocatable)',
  208. 'source_rpm': 'virgo-dummy-1.0-1.1.src.rpm', 'install_date': '2016-02-23T16:31:57Z',
  209. 'install_date_time_t': 1456241517, 'summary': 'Virgo dummy package', 'version': '1.0',
  210. 'signature': 'DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9',
  211. 'release': '1.1', 'group': 'Applications/System', 'arch': 'noarch', 'size': '17992'},
  212. 'libopenssl1_0_0':
  213. {'build_date': '2015-11-04T23:20:34Z', 'vendor': 'SUSE LLC <https://www.suse.com/>',
  214. 'description': 'The OpenSSL Project is a collaborative effort.',
  215. 'license': 'OpenSSL', 'build_host': 'sheep11', 'url': 'https://www.openssl.org/',
  216. 'build_date_time_t': 1446675634, 'relocations': '(not relocatable)',
  217. 'source_rpm': 'openssl-1.0.1i-34.1.src.rpm', 'install_date': '2016-02-23T16:31:35Z',
  218. 'install_date_time_t': 1456241495, 'summary': 'Secure Sockets and Transport Layer Security',
  219. 'version': '1.0.1i', 'signature': 'RSA/SHA256, Wed Nov 4 22:21:34 2015, Key ID 70af9e8139db7c82',
  220. 'release': '34.1', 'group': 'Productivity/Networking/Security', 'packager': 'https://www.suse.com/',
  221. 'arch': 'x86_64', 'size': '2576912'},
  222. }
  223. with patch.dict(zypper.__salt__, {'lowpkg.info': MagicMock(return_value=run_out)}):
  224. installed = zypper.info_installed()
  225. # Test overall products length
  226. self.assertEqual(len(installed), 2)
  227. # Test translated fields
  228. for pkg_name, pkg_info in installed.items():
  229. self.assertEqual(installed[pkg_name].get('source'), run_out[pkg_name]['source_rpm'])
  230. # Test keys transition from the lowpkg.info
  231. for pn_key, pn_val in run_out['virgo-dummy'].items():
  232. if pn_key == 'source_rpm':
  233. continue
  234. self.assertEqual(installed['virgo-dummy'][pn_key], pn_val)
  235. def test_info_available(self):
  236. '''
  237. Test return the information of the named package available for the system.
  238. :return:
  239. '''
  240. test_pkgs = ['vim', 'emacs', 'python']
  241. with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))):
  242. available = zypper.info_available(*test_pkgs, refresh=False)
  243. self.assertEqual(len(available), 3)
  244. for pkg_name, pkg_info in available.items():
  245. self.assertIn(pkg_name, test_pkgs)
  246. self.assertEqual(available['emacs']['status'], 'up-to-date')
  247. self.assertTrue(available['emacs']['installed'])
  248. self.assertEqual(available['emacs']['support level'], 'Level 3')
  249. self.assertEqual(available['emacs']['vendor'], 'SUSE LLC <https://www.suse.com/>')
  250. self.assertEqual(available['emacs']['summary'], 'GNU Emacs Base Package')
  251. self.assertEqual(available['vim']['status'], 'not installed')
  252. self.assertFalse(available['vim']['installed'])
  253. self.assertEqual(available['vim']['support level'], 'Level 3')
  254. self.assertEqual(available['vim']['vendor'], 'SUSE LLC <https://www.suse.com/>')
  255. self.assertEqual(available['vim']['summary'], 'Vi IMproved')
  256. @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True))
  257. def test_latest_version(self):
  258. '''
  259. Test the latest version of the named package available for upgrade or installation.
  260. :return:
  261. '''
  262. with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))):
  263. self.assertEqual(zypper.latest_version('vim'), '7.4.326-2.62')
  264. @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True))
  265. def test_upgrade_available(self):
  266. '''
  267. Test whether or not an upgrade is available for a given package.
  268. :return:
  269. '''
  270. ref_out = get_test_data('zypper-available.txt')
  271. with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))):
  272. for pkg_name in ['emacs', 'python']:
  273. self.assertFalse(zypper.upgrade_available(pkg_name))
  274. self.assertTrue(zypper.upgrade_available('vim'))
  275. def test_list_pkgs(self):
  276. '''
  277. Test packages listing.
  278. :return:
  279. '''
  280. def _add_data(data, key, value):
  281. data[key] = value
  282. rpm_out = [
  283. 'protobuf-java_|-2.6.1_|-3.1.develHead_|-',
  284. 'yast2-ftp-server_|-3.1.8_|-8.1_|-',
  285. 'jose4j_|-0.4.4_|-2.1.develHead_|-',
  286. 'apache-commons-cli_|-1.2_|-1.233_|-',
  287. 'jakarta-commons-discovery_|-0.4_|-129.686_|-',
  288. 'susemanager-build-keys-web_|-12.0_|-5.1.develHead_|-',
  289. ]
  290. with patch.dict(zypper.__salt__, {'cmd.run': MagicMock(return_value=os.linesep.join(rpm_out))}):
  291. with patch.dict(zypper.__salt__, {'pkg_resource.add_pkg': _add_data}):
  292. with patch.dict(zypper.__salt__, {'pkg_resource.sort_pkglist': MagicMock()}):
  293. with patch.dict(zypper.__salt__, {'pkg_resource.stringify': MagicMock()}):
  294. pkgs = zypper.list_pkgs()
  295. for pkg_name, pkg_version in {
  296. 'jakarta-commons-discovery': '0.4-129.686',
  297. 'yast2-ftp-server': '3.1.8-8.1',
  298. 'protobuf-java': '2.6.1-3.1.develHead',
  299. 'susemanager-build-keys-web': '12.0-5.1.develHead',
  300. 'apache-commons-cli': '1.2-1.233',
  301. 'jose4j': '0.4.4-2.1.develHead'}.items():
  302. self.assertTrue(pkgs.get(pkg_name))
  303. self.assertEqual(pkgs[pkg_name], pkg_version)
  304. def test_remove_purge(self):
  305. '''
  306. Test package removal
  307. :return:
  308. '''
  309. class ListPackages(object):
  310. def __init__(self):
  311. self._packages = ['vim', 'pico']
  312. self._pkgs = {
  313. 'vim': '0.18.0',
  314. 'emacs': '24.0.1',
  315. 'pico': '0.1.1',
  316. }
  317. def __call__(self):
  318. pkgs = self._pkgs.copy()
  319. for target in self._packages:
  320. if self._pkgs.get(target):
  321. del self._pkgs[target]
  322. return pkgs
  323. parsed_targets = [{'vim': None, 'pico': None}, None]
  324. cmd_out = {
  325. 'retcode': 0,
  326. 'stdout': '',
  327. 'stderr': ''
  328. }
  329. with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=cmd_out)}):
  330. with patch.dict(zypper.__salt__, {'pkg_resource.parse_targets': MagicMock(return_value=parsed_targets)}):
  331. with patch.dict(zypper.__salt__, {'pkg_resource.stringify': MagicMock()}):
  332. with patch('salt.modules.zypper.list_pkgs', ListPackages()):
  333. diff = zypper.remove(name='vim,pico')
  334. for pkg_name in ['vim', 'pico']:
  335. self.assertTrue(diff.get(pkg_name))
  336. self.assertTrue(diff[pkg_name]['old'])
  337. self.assertFalse(diff[pkg_name]['new'])
  338. def test_repo_value_info(self):
  339. '''
  340. Tests if repo info is properly parsed.
  341. :return:
  342. '''
  343. repos_cfg = configparser.ConfigParser()
  344. for cfg in ['zypper-repo-1.cfg', 'zypper-repo-2.cfg']:
  345. repos_cfg.readfp(StringIO.StringIO(get_test_data(cfg)))
  346. for alias in repos_cfg.sections():
  347. r_info = zypper._get_repo_info(alias, repos_cfg=repos_cfg)
  348. self.assertEqual(type(r_info['type']), type(None))
  349. self.assertEqual(type(r_info['enabled']), bool)
  350. self.assertEqual(type(r_info['autorefresh']), bool)
  351. self.assertEqual(type(r_info['baseurl']), str)
  352. self.assertEqual(r_info['type'], None)
  353. self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update')
  354. self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update')
  355. if __name__ == '__main__':
  356. from integration import run_tests
  357. run_tests(ZypperTestCase, needs_daemon=False)