1
0

test_virtualbox.py 17 KB

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