test_saltify.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Alexander Schwartz <alexander.schwartz@gmx.net>
  4. """
  5. # Import Python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. # Import Salt Libs
  8. import salt.client
  9. from salt.cloud.clouds import saltify
  10. # Import Salt Testing Libs
  11. from tests.support.mixins import LoaderModuleMockMixin
  12. from tests.support.mock import ANY, MagicMock, patch
  13. from tests.support.unit import TestCase
  14. TEST_PROFILES = {
  15. "testprofile1": NotImplemented,
  16. "testprofile2": { # this profile is used in test_saltify_destroy()
  17. "ssh_username": "fred",
  18. "remove_config_on_destroy": False, # expected for test
  19. "shutdown_on_destroy": True, # expected value for test
  20. },
  21. "testprofile3": { # this profile is used in test_create_wake_on_lan()
  22. "wake_on_lan_mac": "aa-bb-cc-dd-ee-ff",
  23. "wol_sender_node": "friend1",
  24. "wol_boot_wait": 0.01, # we want the wait to be very short
  25. },
  26. }
  27. TEST_PROFILE_NAMES = ["testprofile1", "testprofile2", "testprofile3"]
  28. class SaltifyTestCase(TestCase, LoaderModuleMockMixin):
  29. """
  30. Test cases for salt.cloud.clouds.saltify
  31. """
  32. LOCAL_OPTS = {
  33. "providers": {
  34. "sfy1": {"saltify": {"driver": "saltify", "profiles": TEST_PROFILES}},
  35. },
  36. "profiles": TEST_PROFILES,
  37. "sock_dir": "/var/sockxxx",
  38. "transport": "tcp",
  39. }
  40. def setup_loader_modules(self):
  41. saltify_globals = {
  42. "__active_provider_name__": "",
  43. "__utils__": {
  44. "cloud.bootstrap": MagicMock(),
  45. "cloud.fire_event": MagicMock(),
  46. },
  47. "__opts__": self.LOCAL_OPTS,
  48. }
  49. return {saltify: saltify_globals}
  50. def test_create_no_deploy(self):
  51. """
  52. Test if deployment fails. This is the most basic test as saltify doesn't contain much logic
  53. """
  54. with patch("salt.cloud.clouds.saltify._verify", MagicMock(return_value=True)):
  55. vm = {"deploy": False, "driver": "saltify", "name": "dummy"}
  56. self.assertTrue(saltify.create(vm))
  57. def test_create_and_deploy(self):
  58. """
  59. Test if deployment can be done.
  60. """
  61. mock_cmd = MagicMock(return_value=True)
  62. with patch.dict(
  63. "salt.cloud.clouds.saltify.__utils__", {"cloud.bootstrap": mock_cmd}
  64. ):
  65. vm_ = {
  66. "deploy": True,
  67. "driver": "saltify",
  68. "name": "new2",
  69. "profile": "testprofile2",
  70. }
  71. result = saltify.create(vm_)
  72. mock_cmd.assert_called_once_with(vm_, ANY)
  73. self.assertTrue(result)
  74. def test_create_no_ssh_host(self):
  75. """
  76. Test that ssh_host is set to the vm name if not defined
  77. """
  78. mock_cmd = MagicMock(return_value=True)
  79. with patch.dict(
  80. "salt.cloud.clouds.saltify.__utils__", {"cloud.bootstrap": mock_cmd}
  81. ):
  82. vm_ = {
  83. "deploy": True,
  84. "driver": "saltify",
  85. "name": "new2",
  86. "profile": "testprofile2",
  87. }
  88. result = saltify.create(vm_)
  89. mock_cmd.assert_called_once_with(vm_, ANY)
  90. assert result
  91. # Make sure that ssh_host was added to the vm. Note that this is
  92. # done in two asserts so that the failure is more explicit about
  93. # what is wrong. If ssh_host wasn't inserted in the vm_ dict, the
  94. # failure would be a KeyError, which would be harder to
  95. # troubleshoot.
  96. assert "ssh_host" in vm_
  97. assert vm_["ssh_host"] == "new2"
  98. def test_create_wake_on_lan(self):
  99. """
  100. Test if wake on lan works
  101. """
  102. mock_sleep = MagicMock()
  103. mock_cmd = MagicMock(return_value=True)
  104. mm_cmd = MagicMock(return_value={"friend1": True})
  105. lcl = salt.client.LocalClient()
  106. lcl.cmd = mm_cmd
  107. with patch("time.sleep", mock_sleep):
  108. with patch("salt.client.LocalClient", return_value=lcl):
  109. with patch.dict(
  110. "salt.cloud.clouds.saltify.__utils__", {"cloud.bootstrap": mock_cmd}
  111. ):
  112. vm_ = {
  113. "deploy": True,
  114. "driver": "saltify",
  115. "name": "new1",
  116. "profile": "testprofile3",
  117. }
  118. result = saltify.create(vm_)
  119. mock_cmd.assert_called_once_with(vm_, ANY)
  120. mm_cmd.assert_called_with(
  121. "friend1", "network.wol", ["aa-bb-cc-dd-ee-ff"]
  122. )
  123. # The test suite might call time.sleep, look for any call
  124. # that has the expected wait time.
  125. mock_sleep.assert_any_call(0.01)
  126. self.assertTrue(result)
  127. def test_avail_locations(self):
  128. """
  129. Test the avail_locations will always return {}
  130. """
  131. self.assertEqual(saltify.avail_locations(), {})
  132. def test_avail_sizes(self):
  133. """
  134. Test the avail_sizes will always return {}
  135. """
  136. self.assertEqual(saltify.avail_sizes(), {})
  137. def test_avail_images(self):
  138. """
  139. Test the avail_images will return profiles
  140. """
  141. testlist = list(TEST_PROFILE_NAMES) # copy
  142. self.assertEqual(saltify.avail_images()["Profiles"].sort(), testlist.sort())
  143. def test_list_nodes(self):
  144. """
  145. Test list_nodes will return required fields only
  146. """
  147. testgrains = {
  148. "nodeX1": {
  149. "id": "nodeX1",
  150. "ipv4": ["127.0.0.1", "192.1.2.22", "172.16.17.18"],
  151. "ipv6": ["::1", "fdef:bad:add::f00", "3001:DB8::F00D"],
  152. "salt-cloud": {
  153. "driver": "saltify",
  154. "provider": "saltyfy",
  155. "profile": "testprofile2",
  156. },
  157. "extra_stuff": "does not belong",
  158. }
  159. }
  160. expected_result = {
  161. "nodeX1": {
  162. "id": "nodeX1",
  163. "image": "testprofile2",
  164. "private_ips": ["172.16.17.18", "fdef:bad:add::f00"],
  165. "public_ips": ["192.1.2.22", "3001:DB8::F00D"],
  166. "size": "",
  167. "state": "running",
  168. }
  169. }
  170. mm_cmd = MagicMock(return_value=testgrains)
  171. lcl = salt.client.LocalClient()
  172. lcl.cmd = mm_cmd
  173. with patch("salt.client.LocalClient", return_value=lcl):
  174. self.assertEqual(saltify.list_nodes(), expected_result)
  175. def test_saltify_reboot(self):
  176. mm_cmd = MagicMock(return_value=True)
  177. lcl = salt.client.LocalClient()
  178. lcl.cmd = mm_cmd
  179. with patch("salt.client.LocalClient", return_value=lcl):
  180. result = saltify.reboot("nodeS1", "action")
  181. mm_cmd.assert_called_with("nodeS1", "system.reboot")
  182. self.assertTrue(result)
  183. def test_saltify_destroy(self):
  184. # destroy calls local.cmd several times and expects
  185. # different results, so we will provide a list of
  186. # results. Each call will get the next value.
  187. # NOTE: this assumes that the call order never changes,
  188. # so to keep things simple, we will not use remove_config...
  189. result_list = [
  190. {
  191. "nodeS1": { # first call is grains.get
  192. "driver": "saltify",
  193. "provider": "saltify",
  194. "profile": "testprofile2",
  195. }
  196. },
  197. # Note:
  198. # testprofile2 has remove_config_on_destroy: False
  199. # and shutdown_on_destroy: True
  200. {
  201. "nodeS1": "a system.shutdown worked message" # last call shuts down the minion
  202. },
  203. ]
  204. mm_cmd = MagicMock(side_effect=result_list)
  205. lcl = salt.client.LocalClient()
  206. lcl.cmd = mm_cmd
  207. with patch("salt.client.LocalClient", return_value=lcl):
  208. result = saltify.destroy("nodeS1", "action")
  209. mm_cmd.assert_called_with("nodeS1", "system.shutdown")
  210. self.assertTrue(result)