test_boto_vpc.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. # -*- coding: utf-8 -*-
  2. # Import Python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import os.path
  5. import random
  6. import string
  7. import sys
  8. import pytest
  9. # Import Salt libs
  10. import salt.config
  11. import salt.states.boto_vpc as boto_vpc
  12. import salt.utils.botomod as botomod
  13. from salt.ext import six
  14. # Import 3rd-party libs
  15. from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
  16. from salt.utils.versions import LooseVersion
  17. # Import Salt Testing libs
  18. from tests.support.mixins import LoaderModuleMockMixin
  19. from tests.support.mock import patch
  20. from tests.support.runtests import RUNTIME_VARS
  21. from tests.support.unit import TestCase, skipIf
  22. # pylint: disable=import-error,unused-import
  23. from tests.unit.modules.test_boto_vpc import BotoVpcTestCaseMixin
  24. try:
  25. import boto
  26. boto.ENDPOINTS_PATH = os.path.join(
  27. RUNTIME_VARS.TESTS_DIR, "unit/files/endpoints.json"
  28. )
  29. import boto3
  30. from boto.exception import BotoServerError
  31. HAS_BOTO = True
  32. except ImportError:
  33. HAS_BOTO = False
  34. try:
  35. from moto import mock_ec2_deprecated
  36. HAS_MOTO = True
  37. except ImportError:
  38. HAS_MOTO = False
  39. def mock_ec2_deprecated(self):
  40. """
  41. if the mock_ec2_deprecated function is not available due to import failure
  42. this replaces the decorated function with stub_function.
  43. Allows boto_vpc unit tests to use the @mock_ec2_deprecated decorator
  44. without a "NameError: name 'mock_ec2_deprecated' is not defined" error.
  45. """
  46. def stub_function(self):
  47. pass
  48. return stub_function
  49. # pylint: enable=import-error,unused-import
  50. # the boto_vpc module relies on the connect_to_region() method
  51. # which was added in boto 2.8.0
  52. # https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
  53. required_boto_version = "2.8.0"
  54. region = "us-east-1"
  55. access_key = "GKTADJGHEIQSXMKKRBJ08H"
  56. secret_key = "askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs"
  57. conn_parameters = {
  58. "region": region,
  59. "key": access_key,
  60. "keyid": secret_key,
  61. "profile": {},
  62. }
  63. cidr_block = "10.0.0.0/24"
  64. subnet_id = "subnet-123456"
  65. dhcp_options_parameters = {
  66. "domain_name": "example.com",
  67. "domain_name_servers": ["1.2.3.4"],
  68. "ntp_servers": ["5.6.7.8"],
  69. "netbios_name_servers": ["10.0.0.1"],
  70. "netbios_node_type": 2,
  71. }
  72. network_acl_entry_parameters = ("fake", 100, -1, "allow", cidr_block)
  73. dhcp_options_parameters.update(conn_parameters)
  74. def _has_required_boto():
  75. """
  76. Returns True/False boolean depending on if Boto is installed and correct
  77. version.
  78. """
  79. if not HAS_BOTO:
  80. return False
  81. elif LooseVersion(boto.__version__) < LooseVersion(required_boto_version):
  82. return False
  83. else:
  84. return True
  85. class BotoVpcStateTestCaseBase(TestCase, LoaderModuleMockMixin):
  86. def setup_loader_modules(self):
  87. ctx = {}
  88. utils = salt.loader.utils(
  89. self.opts,
  90. whitelist=["boto", "boto3", "args", "systemd", "path", "platform", "reg"],
  91. context=ctx,
  92. )
  93. serializers = salt.loader.serializers(self.opts)
  94. self.funcs = salt.loader.minion_mods(
  95. self.opts, context=ctx, utils=utils, whitelist=["boto_vpc", "config"]
  96. )
  97. self.salt_states = salt.loader.states(
  98. opts=self.opts,
  99. functions=self.funcs,
  100. utils=utils,
  101. whitelist=["boto_vpc"],
  102. serializers=serializers,
  103. )
  104. return {
  105. boto_vpc: {
  106. "__opts__": self.opts,
  107. "__salt__": self.funcs,
  108. "__utils__": utils,
  109. "__states__": self.salt_states,
  110. "__serializers__": serializers,
  111. },
  112. botomod: {},
  113. }
  114. @classmethod
  115. def setUpClass(cls):
  116. cls.opts = salt.config.DEFAULT_MINION_OPTS.copy()
  117. cls.opts["grains"] = salt.loader.grains(cls.opts)
  118. @classmethod
  119. def tearDownClass(cls):
  120. del cls.opts
  121. def setUp(self):
  122. self.addCleanup(delattr, self, "funcs")
  123. self.addCleanup(delattr, self, "salt_states")
  124. # connections keep getting cached from prior tests, can't find the
  125. # correct context object to clear it. So randomize the cache key, to prevent any
  126. # cache hits
  127. conn_parameters["key"] = "".join(
  128. random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
  129. )
  130. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  131. @skipIf(HAS_MOTO is False, "The moto module must be installed.")
  132. @skipIf(
  133. _has_required_boto() is False,
  134. "The boto module must be greater than"
  135. " or equal to version {0}".format(required_boto_version),
  136. )
  137. class BotoVpcTestCase(BotoVpcStateTestCaseBase, BotoVpcTestCaseMixin):
  138. """
  139. TestCase for salt.states.boto_vpc state.module
  140. """
  141. @skipIf(
  142. sys.version_info > (3, 6),
  143. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  144. )
  145. @mock_ec2_deprecated
  146. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  147. def test_present_when_vpc_does_not_exist(self):
  148. """
  149. Tests present on a VPC that does not exist.
  150. """
  151. with patch.dict(botomod.__salt__, self.funcs):
  152. vpc_present_result = self.salt_states["boto_vpc.present"](
  153. "test", cidr_block
  154. )
  155. self.assertTrue(vpc_present_result["result"])
  156. self.assertEqual(
  157. vpc_present_result["changes"]["new"]["vpc"]["state"], "available"
  158. )
  159. @skipIf(
  160. sys.version_info > (3, 6),
  161. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  162. )
  163. @mock_ec2_deprecated
  164. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  165. def test_present_when_vpc_exists(self):
  166. vpc = self._create_vpc(name="test")
  167. vpc_present_result = self.salt_states["boto_vpc.present"]("test", cidr_block)
  168. self.assertTrue(vpc_present_result["result"])
  169. self.assertEqual(vpc_present_result["changes"], {})
  170. @mock_ec2_deprecated
  171. @skipIf(True, "Disabled pending https://github.com/spulec/moto/issues/493")
  172. def test_present_with_failure(self):
  173. with patch(
  174. "moto.ec2.models.VPCBackend.create_vpc",
  175. side_effect=BotoServerError(400, "Mocked error"),
  176. ):
  177. vpc_present_result = self.salt_states["boto_vpc.present"](
  178. "test", cidr_block
  179. )
  180. self.assertFalse(vpc_present_result["result"])
  181. self.assertTrue("Mocked error" in vpc_present_result["comment"])
  182. @skipIf(
  183. sys.version_info > (3, 6),
  184. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  185. )
  186. @mock_ec2_deprecated
  187. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  188. def test_absent_when_vpc_does_not_exist(self):
  189. """
  190. Tests absent on a VPC that does not exist.
  191. """
  192. with patch.dict(botomod.__salt__, self.funcs):
  193. vpc_absent_result = self.salt_states["boto_vpc.absent"]("test")
  194. self.assertTrue(vpc_absent_result["result"])
  195. self.assertEqual(vpc_absent_result["changes"], {})
  196. @skipIf(
  197. sys.version_info > (3, 6),
  198. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  199. )
  200. @mock_ec2_deprecated
  201. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  202. def test_absent_when_vpc_exists(self):
  203. vpc = self._create_vpc(name="test")
  204. with patch.dict(botomod.__salt__, self.funcs):
  205. vpc_absent_result = self.salt_states["boto_vpc.absent"]("test")
  206. self.assertTrue(vpc_absent_result["result"])
  207. self.assertEqual(vpc_absent_result["changes"]["new"]["vpc"], None)
  208. @mock_ec2_deprecated
  209. @skipIf(True, "Disabled pending https://github.com/spulec/moto/issues/493")
  210. def test_absent_with_failure(self):
  211. vpc = self._create_vpc(name="test")
  212. with patch(
  213. "moto.ec2.models.VPCBackend.delete_vpc",
  214. side_effect=BotoServerError(400, "Mocked error"),
  215. ):
  216. vpc_absent_result = self.salt_states["boto_vpc.absent"]("test")
  217. self.assertFalse(vpc_absent_result["result"])
  218. self.assertTrue("Mocked error" in vpc_absent_result["comment"])
  219. class BotoVpcResourceTestCaseMixin(BotoVpcTestCaseMixin):
  220. resource_type = None
  221. backend_create = None
  222. backend_delete = None
  223. extra_kwargs = {}
  224. def _create_resource(self, vpc_id=None, name=None):
  225. _create = getattr(self, "_create_" + self.resource_type)
  226. _create(vpc_id=vpc_id, name=name, **self.extra_kwargs)
  227. @skipIf(
  228. sys.version_info > (3, 6),
  229. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  230. )
  231. @mock_ec2_deprecated
  232. def test_present_when_resource_does_not_exist(self):
  233. """
  234. Tests present on a resource that does not exist.
  235. """
  236. vpc = self._create_vpc(name="test")
  237. with patch.dict(botomod.__salt__, self.funcs):
  238. resource_present_result = self.salt_states[
  239. "boto_vpc.{0}_present".format(self.resource_type)
  240. ](name="test", vpc_name="test", **self.extra_kwargs)
  241. self.assertTrue(resource_present_result["result"])
  242. exists = self.funcs["boto_vpc.resource_exists"](self.resource_type, "test").get(
  243. "exists"
  244. )
  245. self.assertTrue(exists)
  246. @skipIf(
  247. sys.version_info > (3, 6),
  248. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  249. )
  250. @mock_ec2_deprecated
  251. def test_present_when_resource_exists(self):
  252. vpc = self._create_vpc(name="test")
  253. self._create_resource(vpc_id=vpc.id, name="test")
  254. with patch.dict(botomod.__salt__, self.funcs):
  255. resource_present_result = self.salt_states[
  256. "boto_vpc.{0}_present".format(self.resource_type)
  257. ](name="test", vpc_name="test", **self.extra_kwargs)
  258. self.assertTrue(resource_present_result["result"])
  259. self.assertEqual(resource_present_result["changes"], {})
  260. @mock_ec2_deprecated
  261. @skipIf(True, "Disabled pending https://github.com/spulec/moto/issues/493")
  262. def test_present_with_failure(self):
  263. vpc = self._create_vpc(name="test")
  264. with patch(
  265. "moto.ec2.models.{0}".format(self.backend_create),
  266. side_effect=BotoServerError(400, "Mocked error"),
  267. ):
  268. resource_present_result = self.salt_states[
  269. "boto_vpc.{0}_present".format(self.resource_type)
  270. ](name="test", vpc_name="test", **self.extra_kwargs)
  271. self.assertFalse(resource_present_result["result"])
  272. self.assertTrue("Mocked error" in resource_present_result["comment"])
  273. @skipIf(
  274. sys.version_info > (3, 6),
  275. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  276. )
  277. @mock_ec2_deprecated
  278. def test_absent_when_resource_does_not_exist(self):
  279. """
  280. Tests absent on a resource that does not exist.
  281. """
  282. with patch.dict(botomod.__salt__, self.funcs):
  283. resource_absent_result = self.salt_states[
  284. "boto_vpc.{0}_absent".format(self.resource_type)
  285. ]("test")
  286. self.assertTrue(resource_absent_result["result"])
  287. self.assertEqual(resource_absent_result["changes"], {})
  288. @skipIf(
  289. sys.version_info > (3, 6),
  290. "Disabled for 3.7+ pending https://github.com/spulec/moto/issues/1706.",
  291. )
  292. @mock_ec2_deprecated
  293. def test_absent_when_resource_exists(self):
  294. vpc = self._create_vpc(name="test")
  295. self._create_resource(vpc_id=vpc.id, name="test")
  296. with patch.dict(botomod.__salt__, self.funcs):
  297. resource_absent_result = self.salt_states[
  298. "boto_vpc.{0}_absent".format(self.resource_type)
  299. ]("test")
  300. self.assertTrue(resource_absent_result["result"])
  301. self.assertEqual(
  302. resource_absent_result["changes"]["new"][self.resource_type], None
  303. )
  304. exists = self.funcs["boto_vpc.resource_exists"](self.resource_type, "test").get(
  305. "exists"
  306. )
  307. self.assertFalse(exists)
  308. @mock_ec2_deprecated
  309. @skipIf(True, "Disabled pending https://github.com/spulec/moto/issues/493")
  310. def test_absent_with_failure(self):
  311. vpc = self._create_vpc(name="test")
  312. self._create_resource(vpc_id=vpc.id, name="test")
  313. with patch(
  314. "moto.ec2.models.{0}".format(self.backend_delete),
  315. side_effect=BotoServerError(400, "Mocked error"),
  316. ):
  317. resource_absent_result = self.salt_states[
  318. "boto_vpc.{0}_absent".format(self.resource_type)
  319. ]("test")
  320. self.assertFalse(resource_absent_result["result"])
  321. self.assertTrue("Mocked error" in resource_absent_result["comment"])
  322. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  323. @skipIf(HAS_MOTO is False, "The moto module must be installed.")
  324. @skipIf(
  325. _has_required_boto() is False,
  326. "The boto module must be greater than"
  327. " or equal to version {0}".format(required_boto_version),
  328. )
  329. class BotoVpcSubnetsTestCase(BotoVpcStateTestCaseBase, BotoVpcResourceTestCaseMixin):
  330. resource_type = "subnet"
  331. backend_create = "SubnetBackend.create_subnet"
  332. backend_delete = "SubnetBackend.delete_subnet"
  333. extra_kwargs = {"cidr_block": cidr_block}
  334. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  335. @skipIf(HAS_MOTO is False, "The moto module must be installed.")
  336. @skipIf(
  337. _has_required_boto() is False,
  338. "The boto module must be greater than"
  339. " or equal to version {0}".format(required_boto_version),
  340. )
  341. class BotoVpcInternetGatewayTestCase(
  342. BotoVpcStateTestCaseBase, BotoVpcResourceTestCaseMixin
  343. ):
  344. resource_type = "internet_gateway"
  345. backend_create = "InternetGatewayBackend.create_internet_gateway"
  346. backend_delete = "InternetGatewayBackend.delete_internet_gateway"
  347. @skipIf(
  348. six.PY3,
  349. "Disabled for Python 3 due to upstream bugs: "
  350. "https://github.com/spulec/moto/issues/548 and "
  351. "https://github.com/gabrielfalcao/HTTPretty/issues/325",
  352. )
  353. @skipIf(HAS_BOTO is False, "The boto module must be installed.")
  354. @skipIf(HAS_MOTO is False, "The moto module must be installed.")
  355. @skipIf(
  356. _has_required_boto() is False,
  357. "The boto module must be greater than"
  358. " or equal to version {0}".format(required_boto_version),
  359. )
  360. class BotoVpcRouteTableTestCase(BotoVpcStateTestCaseBase, BotoVpcResourceTestCaseMixin):
  361. resource_type = "route_table"
  362. backend_create = "RouteTableBackend.create_route_table"
  363. backend_delete = "RouteTableBackend.delete_route_table"
  364. @mock_ec2_deprecated
  365. def test_present_with_subnets(self):
  366. vpc = self._create_vpc(name="test")
  367. subnet1 = self._create_subnet(
  368. vpc_id=vpc.id, cidr_block="10.0.0.0/25", name="test1"
  369. )
  370. subnet2 = self._create_subnet(
  371. vpc_id=vpc.id, cidr_block="10.0.0.128/25", name="test2"
  372. )
  373. route_table_present_result = self.salt_states["boto_vpc.route_table_present"](
  374. name="test",
  375. vpc_name="test",
  376. subnet_names=["test1"],
  377. subnet_ids=[subnet2.id],
  378. )
  379. associations = route_table_present_result["changes"]["new"][
  380. "subnets_associations"
  381. ]
  382. assoc_subnets = [x["subnet_id"] for x in associations]
  383. self.assertEqual(set(assoc_subnets), set([subnet1.id, subnet2.id]))
  384. route_table_present_result = self.salt_states["boto_vpc.route_table_present"](
  385. name="test", vpc_name="test", subnet_ids=[subnet2.id]
  386. )
  387. changes = route_table_present_result["changes"]
  388. old_subnets = [x["subnet_id"] for x in changes["old"]["subnets_associations"]]
  389. self.assertEqual(set(assoc_subnets), set(old_subnets))
  390. new_subnets = changes["new"]["subnets_associations"]
  391. self.assertEqual(new_subnets[0]["subnet_id"], subnet2.id)
  392. @mock_ec2_deprecated
  393. def test_present_with_routes(self):
  394. vpc = self._create_vpc(name="test")
  395. igw = self._create_internet_gateway(name="test", vpc_id=vpc.id)
  396. with patch.dict(botomod.__salt__, self.funcs):
  397. route_table_present_result = self.salt_states[
  398. "boto_vpc.route_table_present"
  399. ](
  400. name="test",
  401. vpc_name="test",
  402. routes=[
  403. {"destination_cidr_block": "0.0.0.0/0", "gateway_id": igw.id},
  404. {"destination_cidr_block": "10.0.0.0/24", "gateway_id": "local"},
  405. ],
  406. )
  407. routes = [
  408. x["gateway_id"]
  409. for x in route_table_present_result["changes"]["new"]["routes"]
  410. ]
  411. self.assertEqual(set(routes), set(["local", igw.id]))
  412. route_table_present_result = self.salt_states["boto_vpc.route_table_present"](
  413. name="test",
  414. vpc_name="test",
  415. routes=[{"destination_cidr_block": "10.0.0.0/24", "gateway_id": "local"}],
  416. )
  417. changes = route_table_present_result["changes"]
  418. old_routes = [x["gateway_id"] for x in changes["old"]["routes"]]
  419. self.assertEqual(set(routes), set(old_routes))
  420. self.assertEqual(changes["new"]["routes"][0]["gateway_id"], "local")