test_virtualbox.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. # -*- coding: utf-8 -*-
  2. # This code assumes vboxapi.py from VirtualBox distribution
  3. # being in PYTHONPATH, or installed system-wide
  4. # Import Python Libs
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import os
  7. import logging
  8. import socket
  9. # Import Salt Testing Libs
  10. from tests.support.unit import TestCase, skipIf
  11. from tests.support.runtime import RUNTIME_VARS
  12. from tests.integration.cloud.helpers.virtualbox import (VirtualboxTestCase,
  13. VirtualboxCloudTestCase,
  14. CONFIG_NAME,
  15. PROVIDER_NAME,
  16. PROFILE_NAME,
  17. BASE_BOX_NAME,
  18. INSTANCE_NAME,
  19. BOOTABLE_BASE_BOX_NAME,
  20. DEPLOY_PROFILE_NAME)
  21. # Import Salt Libs
  22. from salt.ext import six
  23. from salt.ext.six.moves import range
  24. from salt.config import cloud_providers_config, vm_profiles_config
  25. from salt.utils.virtualbox import (vb_xpcom_to_attribute_dict,
  26. vb_clone_vm,
  27. vb_destroy_machine,
  28. vb_create_machine,
  29. vb_get_box,
  30. vb_machine_exists,
  31. XPCOM_ATTRIBUTES,
  32. vb_start_vm,
  33. vb_stop_vm,
  34. vb_get_network_addresses,
  35. vb_wait_for_network_address,
  36. machine_get_machinestate_str,
  37. HAS_LIBS)
  38. log = logging.getLogger(__name__)
  39. # As described in the documentation of list_nodes (this may change with time)
  40. MINIMAL_MACHINE_ATTRIBUTES = [
  41. "id",
  42. "image",
  43. "size",
  44. "state",
  45. "private_ips",
  46. "public_ips",
  47. ]
  48. class VirtualboxProviderTest(VirtualboxCloudTestCase):
  49. """
  50. Integration tests for the Virtualbox cloud provider using the Virtualbox driver
  51. """
  52. def run_cloud_destroy(self, machine_name):
  53. """
  54. Calls salt-cloud to destroy a machine and returns the destroyed machine object (should be None)
  55. @param machine_name:
  56. @type str:
  57. @return:
  58. @rtype: dict
  59. """
  60. output = self.run_cloud('-d {0} --assume-yes --log-level=debug'.format(machine_name))
  61. return output.get(CONFIG_NAME, {}).get(PROVIDER_NAME, {})
  62. def setUp(self):
  63. """
  64. Sets up the test requirements
  65. """
  66. super(VirtualboxProviderTest, self).setUp()
  67. # check if appropriate cloud provider and profile files are present
  68. profile_str = 'virtualbox-config'
  69. providers = self.run_cloud('--list-providers')
  70. log.debug("providers: %s", providers)
  71. if profile_str not in providers:
  72. self.skipTest(
  73. 'Configuration file for {0} was not found. Check {0}.conf files '
  74. 'in tests/integration/files/conf/cloud.*.d/ to run these tests.'.format(PROVIDER_NAME)
  75. )
  76. # check if personal access token, ssh_key_file, and ssh_key_names are present
  77. config_path = os.path.join(
  78. RUNTIME_VARS.FILES,
  79. 'conf',
  80. 'cloud.providers.d',
  81. PROVIDER_NAME + '.conf'
  82. )
  83. log.debug("config_path: %s", config_path)
  84. providers = cloud_providers_config(config_path)
  85. log.debug("config: %s", providers)
  86. config_path = os.path.join(
  87. RUNTIME_VARS.FILES,
  88. 'conf',
  89. 'cloud.profiles.d',
  90. PROVIDER_NAME + '.conf'
  91. )
  92. profiles = vm_profiles_config(config_path, providers)
  93. profile = profiles.get(PROFILE_NAME)
  94. if not profile:
  95. self.skipTest(
  96. 'Profile {0} was not found. Check {1}.conf files '
  97. 'in tests/integration/files/conf/cloud.profiles.d/ to run these tests.'.format(PROFILE_NAME,
  98. PROVIDER_NAME)
  99. )
  100. base_box_name = profile.get("clonefrom")
  101. if base_box_name != BASE_BOX_NAME:
  102. self.skipTest(
  103. 'Profile {0} does not have a base box to clone from. Check {1}.conf files '
  104. 'in tests/integration/files/conf/cloud.profiles.d/ to run these tests.'
  105. 'And add a "clone_from: {2}" to the profile'.format(PROFILE_NAME, PROVIDER_NAME, BASE_BOX_NAME)
  106. )
  107. @classmethod
  108. def setUpClass(cls):
  109. vb_create_machine(BASE_BOX_NAME)
  110. @classmethod
  111. def tearDownClass(cls):
  112. vb_destroy_machine(BASE_BOX_NAME)
  113. def test_cloud_create(self):
  114. """
  115. Simply create a machine and make sure it was created
  116. """
  117. machines = self.run_cloud('-p {0} {1} --log-level=debug'.format(PROFILE_NAME, INSTANCE_NAME))
  118. self.assertIn(INSTANCE_NAME, machines.keys())
  119. def test_cloud_list(self):
  120. """
  121. List all machines in virtualbox and make sure the requested attributes are included
  122. """
  123. machines = self.run_cloud_function('list_nodes')
  124. expected_attributes = MINIMAL_MACHINE_ATTRIBUTES
  125. names = machines.keys()
  126. self.assertGreaterEqual(len(names), 1, "No machines found")
  127. for name, machine in six.iteritems(machines):
  128. if six.PY3:
  129. self.assertCountEqual(expected_attributes, machine.keys())
  130. else:
  131. self.assertItemsEqual(expected_attributes, machine.keys())
  132. self.assertIn(BASE_BOX_NAME, names)
  133. def test_cloud_list_full(self):
  134. """
  135. List all machines and make sure full information in included
  136. """
  137. machines = self.run_cloud_function('list_nodes_full')
  138. expected_minimal_attribute_count = len(MINIMAL_MACHINE_ATTRIBUTES)
  139. names = machines.keys()
  140. self.assertGreaterEqual(len(names), 1, "No machines found")
  141. for name, machine in six.iteritems(machines):
  142. self.assertGreaterEqual(len(machine.keys()), expected_minimal_attribute_count)
  143. self.assertIn(BASE_BOX_NAME, names)
  144. def test_cloud_list_select(self):
  145. """
  146. List selected attributes of all machines
  147. """
  148. machines = self.run_cloud_function('list_nodes_select')
  149. # TODO find out how to get query.selection from the "cloud" config
  150. expected_attributes = ["id"]
  151. names = machines.keys()
  152. self.assertGreaterEqual(len(names), 1, "No machines found")
  153. for name, machine in six.iteritems(machines):
  154. if six.PY3:
  155. self.assertCountEqual(expected_attributes, machine.keys())
  156. else:
  157. self.assertItemsEqual(expected_attributes, machine.keys())
  158. self.assertIn(BASE_BOX_NAME, names)
  159. def test_cloud_destroy(self):
  160. """
  161. Test creating an instance on virtualbox with the virtualbox driver
  162. """
  163. # check if instance with salt installed returned
  164. self.test_cloud_create()
  165. ret = self.run_cloud_destroy(INSTANCE_NAME)
  166. # destroy the instance
  167. self.assertIn(INSTANCE_NAME, ret.keys())
  168. def test_function_show_instance(self):
  169. kw_function_args = {
  170. "image": BASE_BOX_NAME
  171. }
  172. machines = self.run_cloud_function('show_image', kw_function_args, timeout=30)
  173. expected_minimal_attribute_count = len(MINIMAL_MACHINE_ATTRIBUTES)
  174. self.assertIn(BASE_BOX_NAME, machines)
  175. machine = machines[BASE_BOX_NAME]
  176. self.assertGreaterEqual(len(machine.keys()), expected_minimal_attribute_count)
  177. def tearDown(self):
  178. """
  179. Clean up after tests
  180. """
  181. if vb_machine_exists(INSTANCE_NAME):
  182. vb_destroy_machine(INSTANCE_NAME)
  183. @skipIf(HAS_LIBS and vb_machine_exists(BOOTABLE_BASE_BOX_NAME) is False,
  184. "Bootable VM '{0}' not found. Cannot run tests.".format(BOOTABLE_BASE_BOX_NAME)
  185. )
  186. class VirtualboxProviderHeavyTests(VirtualboxCloudTestCase):
  187. """
  188. Tests that include actually booting a machine and doing operations on it that might be lengthy.
  189. """
  190. def assertIsIpAddress(self, ip_str):
  191. """
  192. Is it either a IPv4 or IPv6 address
  193. @param ip_str:
  194. @type ip_str: str
  195. @raise AssertionError
  196. """
  197. try:
  198. socket.inet_aton(ip_str)
  199. except Exception:
  200. try:
  201. socket.inet_pton(socket.AF_INET6, ip_str)
  202. except Exception:
  203. self.fail("{0} is not a valid IP address".format(ip_str))
  204. def setUp(self):
  205. """
  206. Sets up the test requirements
  207. """
  208. # check if appropriate cloud provider and profile files are present
  209. provider_str = CONFIG_NAME
  210. providers = self.run_cloud('--list-providers')
  211. log.debug("providers: %s", providers)
  212. if provider_str not in providers:
  213. self.skipTest(
  214. 'Configuration file for {0} was not found. Check {0}.conf files '
  215. 'in tests/integration/files/conf/cloud.*.d/ to run these tests.'.format(PROVIDER_NAME)
  216. )
  217. # check if personal access token, ssh_key_file, and ssh_key_names are present
  218. config_path = os.path.join(
  219. RUNTIME_VARS.FILES,
  220. 'conf',
  221. 'cloud.providers.d',
  222. PROVIDER_NAME + '.conf'
  223. )
  224. log.debug("config_path: %s", config_path)
  225. providers = cloud_providers_config(config_path)
  226. log.debug("config: %s", providers)
  227. config_path = os.path.join(
  228. RUNTIME_VARS.FILES,
  229. 'conf',
  230. 'cloud.profiles.d',
  231. PROVIDER_NAME + '.conf'
  232. )
  233. profiles = vm_profiles_config(config_path, providers)
  234. profile = profiles.get(DEPLOY_PROFILE_NAME)
  235. if not profile:
  236. self.skipTest(
  237. 'Profile {0} was not found. Check {1}.conf files '
  238. 'in tests/integration/files/conf/cloud.profiles.d/ to run these tests.'.format(DEPLOY_PROFILE_NAME,
  239. PROVIDER_NAME)
  240. )
  241. base_box_name = profile.get("clonefrom")
  242. if base_box_name != BOOTABLE_BASE_BOX_NAME:
  243. self.skipTest(
  244. 'Profile {0} does not have a base box to clone from. Check {1}.conf files '
  245. 'in tests/integration/files/conf/cloud.profiles.d/ to run these tests.'
  246. 'And add a "clone_from: {2}" to the profile'.format(PROFILE_NAME, PROVIDER_NAME, BOOTABLE_BASE_BOX_NAME)
  247. )
  248. def tearDown(self):
  249. try:
  250. vb_stop_vm(BOOTABLE_BASE_BOX_NAME)
  251. except Exception:
  252. pass
  253. if vb_machine_exists(INSTANCE_NAME):
  254. try:
  255. vb_stop_vm(INSTANCE_NAME)
  256. vb_destroy_machine(INSTANCE_NAME)
  257. except Exception as e:
  258. log.warning("Possibly dirty state after exception", exc_info=True)
  259. def test_deploy(self):
  260. machines = self.run_cloud('-p {0} {1} --log-level=debug'.format(DEPLOY_PROFILE_NAME, INSTANCE_NAME))
  261. self.assertIn(INSTANCE_NAME, machines.keys())
  262. machine = machines[INSTANCE_NAME]
  263. self.assertIn("deployed", machine)
  264. self.assertTrue(machine["deployed"], "Machine wasn't deployed :(")
  265. def test_start_stop_action(self):
  266. res = self.run_cloud_action("start", BOOTABLE_BASE_BOX_NAME, timeout=10)
  267. log.info(res)
  268. machine = res.get(BOOTABLE_BASE_BOX_NAME)
  269. self.assertIsNotNone(machine)
  270. expected_state = "Running"
  271. state = machine.get("state")
  272. self.assertEqual(state, expected_state)
  273. res = self.run_cloud_action("stop", BOOTABLE_BASE_BOX_NAME, timeout=10)
  274. log.info(res)
  275. machine = res.get(BOOTABLE_BASE_BOX_NAME)
  276. self.assertIsNotNone(machine)
  277. expected_state = "PoweredOff"
  278. state = machine.get("state")
  279. self.assertEqual(state, expected_state)
  280. def test_restart_action(self):
  281. pass
  282. def test_network_addresses(self):
  283. # Machine is off
  284. ip_addresses = vb_get_network_addresses(machine_name=BOOTABLE_BASE_BOX_NAME)
  285. network_count = len(ip_addresses)
  286. self.assertEqual(network_count, 0)
  287. # Machine is up again
  288. vb_start_vm(BOOTABLE_BASE_BOX_NAME)
  289. ip_addresses = vb_wait_for_network_address(20, machine_name=BOOTABLE_BASE_BOX_NAME)
  290. network_count = len(ip_addresses)
  291. self.assertGreater(network_count, 0)
  292. for ip_address in ip_addresses:
  293. self.assertIsIpAddress(ip_address)
  294. @skipIf(HAS_LIBS is False, 'The \'vboxapi\' library is not available')
  295. class BaseVirtualboxTests(TestCase):
  296. def test_get_manager(self):
  297. self.assertIsNotNone(vb_get_box())
  298. class CreationDestructionVirtualboxTests(VirtualboxTestCase):
  299. def setUp(self):
  300. super(CreationDestructionVirtualboxTests, self).setUp()
  301. def test_vm_creation_and_destruction(self):
  302. vm_name = BASE_BOX_NAME
  303. vb_create_machine(vm_name)
  304. self.assertMachineExists(vm_name)
  305. vb_destroy_machine(vm_name)
  306. self.assertMachineDoesNotExist(vm_name)
  307. class CloneVirtualboxTests(VirtualboxTestCase):
  308. def setUp(self):
  309. self.vbox = vb_get_box()
  310. self.name = "SaltCloudVirtualboxTestVM"
  311. vb_create_machine(self.name)
  312. self.assertMachineExists(self.name)
  313. def tearDown(self):
  314. vb_destroy_machine(self.name)
  315. self.assertMachineDoesNotExist(self.name)
  316. def test_create_machine(self):
  317. vb_name = "NewTestMachine"
  318. machine = vb_clone_vm(
  319. name=vb_name,
  320. clone_from=self.name
  321. )
  322. self.assertEqual(machine.get("name"), vb_name)
  323. self.assertMachineExists(vb_name)
  324. vb_destroy_machine(vb_name)
  325. self.assertMachineDoesNotExist(vb_name)
  326. @skipIf(HAS_LIBS and vb_machine_exists(BOOTABLE_BASE_BOX_NAME) is False,
  327. "Bootable VM '{0}' not found. Cannot run tests.".format(BOOTABLE_BASE_BOX_NAME)
  328. )
  329. class BootVirtualboxTests(VirtualboxTestCase):
  330. def test_start_stop(self):
  331. for i in range(2):
  332. machine = vb_start_vm(BOOTABLE_BASE_BOX_NAME, 20000)
  333. self.assertEqual(machine_get_machinestate_str(machine), "Running")
  334. machine = vb_stop_vm(BOOTABLE_BASE_BOX_NAME)
  335. self.assertEqual(machine_get_machinestate_str(machine), "PoweredOff")
  336. class XpcomConversionTests(TestCase):
  337. @classmethod
  338. def _mock_xpcom_object(cls, interface_name=None, attributes=None):
  339. class XPCOM(object):
  340. def __str__(self):
  341. return "<XPCOM component '<unknown>' (implementing {0})>".format(interface_name)
  342. o = XPCOM()
  343. if attributes and isinstance(attributes, dict):
  344. for key, value in six.iteritems(attributes):
  345. setattr(o, key, value)
  346. return o
  347. def test_unknown_object(self):
  348. xpcom = XpcomConversionTests._mock_xpcom_object()
  349. ret = vb_xpcom_to_attribute_dict(xpcom)
  350. self.assertDictEqual(ret, dict())
  351. def test_imachine_object_default(self):
  352. interface = "IMachine"
  353. imachine = XpcomConversionTests._mock_xpcom_object(interface)
  354. ret = vb_xpcom_to_attribute_dict(imachine, interface_name=interface)
  355. expected_attributes = XPCOM_ATTRIBUTES[interface]
  356. self.assertIsNotNone(expected_attributes, "%s is unknown")
  357. for key in ret:
  358. self.assertIn(key, expected_attributes)
  359. def test_override_attributes(self):
  360. expected_dict = {
  361. "herp": "derp",
  362. "lol": "rofl",
  363. "something": 12345
  364. }
  365. xpc = XpcomConversionTests._mock_xpcom_object(attributes=expected_dict)
  366. ret = vb_xpcom_to_attribute_dict(xpc, attributes=expected_dict.keys())
  367. self.assertDictEqual(ret, expected_dict)
  368. def test_extra_attributes(self):
  369. interface = "IMachine"
  370. expected_extras = {
  371. "extra": "extra",
  372. }
  373. expected_machine = dict([(attribute, attribute) for attribute in XPCOM_ATTRIBUTES[interface]])
  374. expected_machine.update(expected_extras)
  375. imachine = XpcomConversionTests._mock_xpcom_object(interface, attributes=expected_machine)
  376. ret = vb_xpcom_to_attribute_dict(
  377. imachine,
  378. interface_name=interface,
  379. extra_attributes=expected_extras.keys()
  380. )
  381. self.assertDictEqual(ret, expected_machine)
  382. ret_keys = ret.keys()
  383. for key in expected_extras:
  384. self.assertIn(key, ret_keys)
  385. def test_extra_nonexistent_attributes(self):
  386. expected_extra_dict = {
  387. "nonexistent": ""
  388. }
  389. xpcom = XpcomConversionTests._mock_xpcom_object()
  390. ret = vb_xpcom_to_attribute_dict(xpcom, extra_attributes=expected_extra_dict.keys())
  391. self.assertDictEqual(ret, expected_extra_dict)
  392. def test_extra_nonexistent_attribute_with_default(self):
  393. expected_extras = [("nonexistent", list)]
  394. expected_extra_dict = {
  395. "nonexistent": []
  396. }
  397. xpcom = XpcomConversionTests._mock_xpcom_object()
  398. ret = vb_xpcom_to_attribute_dict(xpcom, extra_attributes=expected_extras)
  399. self.assertDictEqual(ret, expected_extra_dict)