test_botomod.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import os
  5. import salt.utils.boto3mod as boto3mod
  6. # Import Salt libs
  7. import salt.utils.botomod as botomod
  8. from salt.exceptions import SaltInvocationError
  9. from salt.ext import six
  10. from salt.utils.versions import LooseVersion
  11. # Import Salt Testing libs
  12. from tests.support.mixins import LoaderModuleMockMixin
  13. from tests.support.mock import MagicMock, patch
  14. from tests.support.runtests import RUNTIME_VARS
  15. from tests.support.unit import TestCase, skipIf
  16. # Import 3rd-party libs
  17. # pylint: disable=import-error
  18. try:
  19. import boto
  20. boto.ENDPOINTS_PATH = os.path.join(
  21. RUNTIME_VARS.TESTS_DIR, "unit/files/endpoints.json"
  22. )
  23. import boto.exception
  24. from boto.exception import BotoServerError
  25. HAS_BOTO = True
  26. except ImportError:
  27. HAS_BOTO = False
  28. try:
  29. import boto3
  30. HAS_BOTO3 = True
  31. except ImportError:
  32. HAS_BOTO3 = False
  33. try:
  34. from moto import mock_ec2
  35. HAS_MOTO = True
  36. except ImportError:
  37. HAS_MOTO = False
  38. def mock_ec2(self):
  39. """
  40. if the mock_ec2 function is not available due to import failure
  41. this replaces the decorated function with stub_function.
  42. Allows unit tests to use the @mock_ec2 decorator
  43. without a "NameError: name 'mock_ec2' is not defined" error.
  44. """
  45. def stub_function(self):
  46. pass
  47. return stub_function
  48. required_boto_version = "2.0.0"
  49. required_boto3_version = "1.2.1"
  50. region = "us-east-1"
  51. access_key = "GKTADJGHEIQSXMKKRBJ08H"
  52. secret_key = "askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs"
  53. conn_parameters = {
  54. "region": region,
  55. "key": access_key,
  56. "keyid": secret_key,
  57. "profile": {},
  58. }
  59. service = "ec2"
  60. resource_name = "test-instance"
  61. resource_id = "i-a1b2c3"
  62. error_body = """
  63. <Response>
  64. <Errors>
  65. <Error>
  66. <Code>Error code text</Code>
  67. <Message>Error message</Message>
  68. </Error>
  69. </Errors>
  70. <RequestID>request ID</RequestID>
  71. </Response>
  72. """
  73. no_error_body = """
  74. <Response>
  75. <Errors />
  76. <RequestID>request ID</RequestID>
  77. </Response>
  78. """
  79. def _has_required_boto():
  80. """
  81. Returns True/False boolean depending on if Boto is installed and correct
  82. version.
  83. """
  84. if not HAS_BOTO:
  85. return False
  86. elif LooseVersion(boto.__version__) < LooseVersion(required_boto_version):
  87. return False
  88. else:
  89. return True
  90. def _has_required_boto3():
  91. """
  92. Returns True/False boolean depending on if Boto is installed and correct
  93. version.
  94. """
  95. try:
  96. if not HAS_BOTO3:
  97. return False
  98. elif LooseVersion(boto3.__version__) < LooseVersion(required_boto3_version):
  99. return False
  100. else:
  101. return True
  102. except AttributeError as exc:
  103. if "has no attribute '__version__'" not in six.text_type(exc):
  104. raise
  105. return False
  106. def _has_required_moto():
  107. """
  108. Returns True/False boolean depending on if Moto is installed and correct
  109. version.
  110. """
  111. if not HAS_MOTO:
  112. return False
  113. else:
  114. import pkg_resources
  115. if LooseVersion(pkg_resources.get_distribution("moto").version) < LooseVersion(
  116. "0.3.7"
  117. ):
  118. return False
  119. return True
  120. class BotoUtilsTestCaseBase(TestCase, LoaderModuleMockMixin):
  121. def setup_loader_modules(self):
  122. module_globals = {
  123. "__salt__": {"config.option": MagicMock(return_value="dummy_opt")}
  124. }
  125. return {botomod: module_globals, boto3mod: module_globals}
  126. class BotoUtilsCacheIdTestCase(BotoUtilsTestCaseBase):
  127. def test_set_and_get_with_no_auth_params(self):
  128. botomod.cache_id(service, resource_name, resource_id=resource_id)
  129. self.assertEqual(botomod.cache_id(service, resource_name), resource_id)
  130. def test_set_and_get_with_explicit_auth_params(self):
  131. botomod.cache_id(
  132. service, resource_name, resource_id=resource_id, **conn_parameters
  133. )
  134. self.assertEqual(
  135. botomod.cache_id(service, resource_name, **conn_parameters), resource_id
  136. )
  137. def test_set_and_get_with_different_region_returns_none(self):
  138. botomod.cache_id(
  139. service, resource_name, resource_id=resource_id, region="us-east-1"
  140. )
  141. self.assertEqual(
  142. botomod.cache_id(service, resource_name, region="us-west-2"), None
  143. )
  144. def test_set_and_get_after_invalidation_returns_none(self):
  145. botomod.cache_id(service, resource_name, resource_id=resource_id)
  146. botomod.cache_id(
  147. service, resource_name, resource_id=resource_id, invalidate=True
  148. )
  149. self.assertEqual(botomod.cache_id(service, resource_name), None)
  150. def test_partial(self):
  151. cache_id = botomod.cache_id_func(service)
  152. cache_id(resource_name, resource_id=resource_id)
  153. self.assertEqual(cache_id(resource_name), resource_id)
  154. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  155. @skipIf(HAS_MOTO is False, "The moto module must be installed.")
  156. @skipIf(
  157. _has_required_boto() is False,
  158. "The boto module must be greater than"
  159. " or equal to version {0}".format(required_boto_version),
  160. )
  161. class BotoUtilsGetConnTestCase(BotoUtilsTestCaseBase):
  162. @mock_ec2
  163. def test_conn_is_cached(self):
  164. conn = botomod.get_connection(service, **conn_parameters)
  165. self.assertTrue(conn in botomod.__context__.values())
  166. @mock_ec2
  167. def test_conn_is_cache_with_profile(self):
  168. conn = botomod.get_connection(service, profile=conn_parameters)
  169. self.assertTrue(conn in botomod.__context__.values())
  170. @mock_ec2
  171. def test_get_conn_with_no_auth_params_raises_invocation_error(self):
  172. with patch(
  173. "boto.{0}.connect_to_region".format(service),
  174. side_effect=boto.exception.NoAuthHandlerFound(),
  175. ):
  176. with self.assertRaises(SaltInvocationError):
  177. botomod.get_connection(service)
  178. @mock_ec2
  179. def test_get_conn_error_raises_command_execution_error(self):
  180. with patch(
  181. "boto.{0}.connect_to_region".format(service),
  182. side_effect=BotoServerError(400, "Mocked error", body=error_body),
  183. ):
  184. with self.assertRaises(BotoServerError):
  185. botomod.get_connection(service)
  186. @mock_ec2
  187. def test_partial(self):
  188. get_conn = botomod.get_connection_func(service)
  189. conn = get_conn(**conn_parameters)
  190. self.assertTrue(conn in botomod.__context__.values())
  191. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  192. @skipIf(
  193. _has_required_boto() is False,
  194. "The boto module must be greater than"
  195. " or equal to version {0}".format(required_boto_version),
  196. )
  197. class BotoUtilsGetErrorTestCase(BotoUtilsTestCaseBase):
  198. def test_error_message(self):
  199. e = BotoServerError("400", "Mocked error", body=error_body)
  200. r = botomod.get_error(e)
  201. expected = {
  202. "aws": {
  203. "code": "Error code text",
  204. "message": "Error message",
  205. "reason": "Mocked error",
  206. "status": "400",
  207. },
  208. "message": "Mocked error: Error message",
  209. }
  210. self.assertEqual(r, expected)
  211. def test_exception_message_with_no_body(self):
  212. e = BotoServerError("400", "Mocked error")
  213. r = botomod.get_error(e)
  214. expected = {
  215. "aws": {"reason": "Mocked error", "status": "400"},
  216. "message": "Mocked error",
  217. }
  218. self.assertEqual(r, expected)
  219. def test_exception_message_with_no_error_in_body(self):
  220. e = BotoServerError("400", "Mocked error", body=no_error_body)
  221. r = botomod.get_error(e)
  222. expected = {
  223. "aws": {"reason": "Mocked error", "status": "400"},
  224. "message": "Mocked error",
  225. }
  226. self.assertEqual(r, expected)
  227. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  228. @skipIf(
  229. _has_required_boto() is False,
  230. "The boto module must be greater than"
  231. " or equal to version {0}".format(required_boto_version),
  232. )
  233. @skipIf(HAS_BOTO3 is False, "The boto3 module must be installed.")
  234. @skipIf(
  235. _has_required_boto3() is False,
  236. "The boto3 module must be greater than"
  237. " or equal to version {0}".format(required_boto3_version),
  238. )
  239. class BotoBoto3CacheContextCollisionTest(BotoUtilsTestCaseBase):
  240. def test_context_conflict_between_boto_and_boto3_utils(self):
  241. botomod.assign_funcs(__name__, "ec2")
  242. boto3mod.assign_funcs(__name__, "ec2", get_conn_funcname="_get_conn3")
  243. boto_ec2_conn = botomod.get_connection(
  244. "ec2", region=region, key=secret_key, keyid=access_key
  245. )
  246. boto3_ec2_conn = boto3mod.get_connection(
  247. "ec2", region=region, key=secret_key, keyid=access_key
  248. )
  249. # These should *not* be the same object!
  250. self.assertNotEqual(id(boto_ec2_conn), id(boto3_ec2_conn))