test_botomod.py 9.0 KB

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