test_compute_service.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import ipaddress
  2. from test import helpers
  3. from test.helpers import ProviderTestBase
  4. from test.helpers import standard_interface_tests as sit
  5. from cloudbridge.cloud.factory import ProviderList
  6. from cloudbridge.cloud.interfaces import InstanceState
  7. from cloudbridge.cloud.interfaces import InvalidConfigurationException
  8. from cloudbridge.cloud.interfaces import TestMockHelperMixin
  9. from cloudbridge.cloud.interfaces.exceptions import WaitStateException
  10. from cloudbridge.cloud.interfaces.resources import InstanceType
  11. from cloudbridge.cloud.interfaces.resources import SnapshotState
  12. import six
  13. class CloudComputeServiceTestCase(ProviderTestBase):
  14. @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
  15. def test_crud_instance(self):
  16. name = "cb_instcrud-{0}".format(helpers.get_uuid())
  17. # Declare these variables and late binding will allow
  18. # the cleanup method access to the most current values
  19. inst = None
  20. net = None
  21. with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
  22. inst, net)):
  23. net, subnet = helpers.create_test_network(self.provider, name)
  24. inst = helpers.get_test_instance(self.provider, name,
  25. subnet=subnet)
  26. sit.check_standard_behaviour(
  27. self, self.provider.compute.instances, inst)
  28. deleted_inst = self.provider.compute.instances.get(
  29. inst.id)
  30. self.assertTrue(
  31. deleted_inst is None or deleted_inst.state in (
  32. InstanceState.TERMINATED,
  33. InstanceState.UNKNOWN),
  34. "Instance %s should have been deleted but still exists." %
  35. name)
  36. def _is_valid_ip(self, address):
  37. try:
  38. ipaddress.ip_address(address)
  39. except ValueError:
  40. return False
  41. return True
  42. @helpers.skipIfNoService(['compute.instances', 'networking.networks',
  43. 'security.security_groups',
  44. 'security.key_pairs'])
  45. def test_instance_properties(self):
  46. name = "cb_inst_props-{0}".format(helpers.get_uuid())
  47. # Declare these variables and late binding will allow
  48. # the cleanup method access to the most current values
  49. test_instance = None
  50. net = None
  51. sg = None
  52. kp = None
  53. with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
  54. test_instance, net, sg, kp)):
  55. net, subnet = helpers.create_test_network(self.provider, name)
  56. kp = self.provider.security.key_pairs.create(name=name)
  57. sg = self.provider.security.security_groups.create(
  58. name=name, description=name, network_id=net.id)
  59. test_instance = helpers.get_test_instance(self.provider,
  60. name, key_pair=kp,
  61. security_groups=[sg],
  62. subnet=subnet)
  63. self.assertEqual(
  64. test_instance.name, name,
  65. "Instance name {0} is not equal to the expected name"
  66. " {1}".format(test_instance.name, name))
  67. image_id = helpers.get_provider_test_data(self.provider, "image")
  68. self.assertEqual(test_instance.image_id, image_id,
  69. "Image id {0} is not equal to the expected id"
  70. " {1}".format(test_instance.image_id, image_id))
  71. self.assertIsInstance(test_instance.zone_id,
  72. six.string_types)
  73. self.assertEqual(
  74. test_instance.image_id,
  75. helpers.get_provider_test_data(self.provider, "image"))
  76. self.assertIsInstance(test_instance.public_ips, list)
  77. self.assertIsInstance(test_instance.private_ips, list)
  78. self.assertEqual(
  79. test_instance.key_pair_name,
  80. kp.name)
  81. self.assertIsInstance(test_instance.security_groups, list)
  82. self.assertEqual(
  83. test_instance.security_groups[0],
  84. sg)
  85. self.assertIsInstance(test_instance.security_group_ids, list)
  86. self.assertEqual(
  87. test_instance.security_group_ids[0],
  88. sg.id)
  89. # Must have either a public or a private ip
  90. ip_private = test_instance.private_ips[0] \
  91. if test_instance.private_ips else None
  92. ip_address = test_instance.public_ips[0] \
  93. if test_instance.public_ips and test_instance.public_ips[0] \
  94. else ip_private
  95. self.assertIsNotNone(
  96. ip_address,
  97. "Instance must have either a public IP or a private IP")
  98. self.assertTrue(
  99. self._is_valid_ip(ip_address),
  100. "Instance must have a valid IP address")
  101. self.assertIsInstance(test_instance.instance_type_id,
  102. six.string_types)
  103. itype = self.provider.compute.instance_types.get(
  104. test_instance.instance_type_id)
  105. self.assertEqual(
  106. itype, test_instance.instance_type,
  107. "Instance type {0} does not match expected type {1}".format(
  108. itype.name, test_instance.instance_type))
  109. self.assertIsInstance(itype, InstanceType)
  110. expected_type = helpers.get_provider_test_data(self.provider,
  111. 'instance_type')
  112. self.assertEqual(
  113. itype.name, expected_type,
  114. "Instance type {0} does not match expected type {1}".format(
  115. itype.name, expected_type))
  116. if isinstance(self.provider, TestMockHelperMixin):
  117. raise self.skipTest(
  118. "Skipping rest of test because Moto is not returning the"
  119. " instance's placement zone correctly")
  120. find_zone = [zone for zone in
  121. self.provider.compute.regions.current.zones
  122. if zone.id == test_instance.zone_id]
  123. self.assertEqual(len(find_zone), 1,
  124. "Instance's placement zone could not be "
  125. " found in zones list")
  126. @helpers.skipIfNoService(['compute.instances', 'compute.images',
  127. 'compute.instance_types'])
  128. def test_block_device_mapping_launch_config(self):
  129. lc = self.provider.compute.instances.create_launch_config()
  130. # specifying an invalid size should raise
  131. # an exception
  132. with self.assertRaises(InvalidConfigurationException):
  133. lc.add_volume_device(size=-1)
  134. # Attempting to add a blank volume without specifying a size
  135. # should raise an exception
  136. with self.assertRaises(InvalidConfigurationException):
  137. lc.add_volume_device(source=None)
  138. # block_devices should be empty so far
  139. self.assertListEqual(
  140. lc.block_devices, [], "No block devices should have been"
  141. " added to mappings list since the configuration was"
  142. " invalid")
  143. # Add a new volume
  144. lc.add_volume_device(size=1, delete_on_terminate=True)
  145. # Override root volume size
  146. image_id = helpers.get_provider_test_data(self.provider, "image")
  147. img = self.provider.compute.images.get(image_id)
  148. # The size should be greater then the ami size
  149. # and therefore, img.min_disk is used.
  150. lc.add_volume_device(
  151. is_root=True,
  152. source=img,
  153. size=img.min_disk if img and img.min_disk else 2,
  154. delete_on_terminate=True)
  155. # Attempting to add more than one root volume should raise an
  156. # exception.
  157. with self.assertRaises(InvalidConfigurationException):
  158. lc.add_volume_device(size=1, is_root=True)
  159. # Attempting to add an incorrect source should raise an exception
  160. with self.assertRaises(InvalidConfigurationException):
  161. lc.add_volume_device(
  162. source="invalid_source",
  163. delete_on_terminate=True)
  164. # Add all available ephemeral devices
  165. instance_type_name = helpers.get_provider_test_data(
  166. self.provider,
  167. "instance_type")
  168. inst_type = self.provider.compute.instance_types.find(
  169. name=instance_type_name)[0]
  170. for _ in range(inst_type.num_ephemeral_disks):
  171. lc.add_ephemeral_device()
  172. # block_devices should be populated
  173. self.assertTrue(
  174. len(lc.block_devices) == 2 + inst_type.num_ephemeral_disks,
  175. "Expected %d total block devices bit found %d" %
  176. (2 + inst_type.num_ephemeral_disks, len(lc.block_devices)))
  177. @helpers.skipIfNoService(['compute.instances', 'compute.images',
  178. 'compute.instance_types', 'block_store.volumes'])
  179. def test_block_device_mapping_attachments(self):
  180. name = "cb_blkattch-{0}".format(helpers.get_uuid())
  181. if self.provider.PROVIDER_ID == ProviderList.OPENSTACK:
  182. raise self.skipTest("Not running BDM tests because OpenStack is"
  183. " not stable enough yet")
  184. test_vol = self.provider.block_store.volumes.create(
  185. name,
  186. 1,
  187. helpers.get_provider_test_data(self.provider,
  188. "placement"))
  189. with helpers.cleanup_action(lambda: test_vol.delete()):
  190. test_vol.wait_till_ready()
  191. test_snap = test_vol.create_snapshot(name=name,
  192. description=name)
  193. def cleanup_snap(snap):
  194. snap.delete()
  195. snap.wait_for([SnapshotState.UNKNOWN],
  196. terminal_states=[SnapshotState.ERROR])
  197. with helpers.cleanup_action(lambda:
  198. cleanup_snap(test_snap)):
  199. test_snap.wait_till_ready()
  200. lc = self.provider.compute.instances.create_launch_config()
  201. # Add a new blank volume
  202. lc.add_volume_device(size=1, delete_on_terminate=True)
  203. # Attach an existing volume
  204. lc.add_volume_device(size=1, source=test_vol,
  205. delete_on_terminate=True)
  206. # Add a new volume based on a snapshot
  207. lc.add_volume_device(size=1, source=test_snap,
  208. delete_on_terminate=True)
  209. # Override root volume size
  210. image_id = helpers.get_provider_test_data(
  211. self.provider,
  212. "image")
  213. img = self.provider.compute.images.get(image_id)
  214. # The size should be greater then the ami size
  215. # and therefore, img.min_disk is used.
  216. lc.add_volume_device(
  217. is_root=True,
  218. source=img,
  219. size=img.min_disk if img and img.min_disk else 2,
  220. delete_on_terminate=True)
  221. # Add all available ephemeral devices
  222. instance_type_name = helpers.get_provider_test_data(
  223. self.provider,
  224. "instance_type")
  225. inst_type = self.provider.compute.instance_types.find(
  226. name=instance_type_name)[0]
  227. for _ in range(inst_type.num_ephemeral_disks):
  228. lc.add_ephemeral_device()
  229. net, subnet = helpers.create_test_network(self.provider, name)
  230. with helpers.cleanup_action(lambda:
  231. helpers.delete_test_network(net)):
  232. inst = helpers.create_test_instance(
  233. self.provider,
  234. name,
  235. subnet=subnet,
  236. launch_config=lc)
  237. with helpers.cleanup_action(lambda:
  238. helpers.delete_test_instance(
  239. inst)):
  240. try:
  241. inst.wait_till_ready()
  242. except WaitStateException as e:
  243. self.fail("The block device mapped launch did not "
  244. " complete successfully: %s" % e)
  245. # TODO: Check instance attachments and make sure they
  246. # correspond to requested mappings
  247. @helpers.skipIfNoService(['compute.instances', 'networking.networks',
  248. 'security.security_groups'])
  249. def test_instance_methods(self):
  250. name = "cb_instmethods-{0}".format(helpers.get_uuid())
  251. # Declare these variables and late binding will allow
  252. # the cleanup method access to the most current values
  253. test_inst = None
  254. net = None
  255. sg = None
  256. with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
  257. test_inst, net, sg)):
  258. net, subnet = helpers.create_test_network(self.provider, name)
  259. test_inst = helpers.get_test_instance(self.provider, name,
  260. subnet=subnet)
  261. sg = self.provider.security.security_groups.create(
  262. name=name, description=name, network_id=net.id)
  263. # Check adding a security group to a running instance
  264. test_inst.add_security_group(sg)
  265. test_inst.refresh()
  266. self.assertTrue(
  267. sg in test_inst.security_groups, "Expected security group '%s'"
  268. " to be among instance security_groups: [%s]" %
  269. (sg, test_inst.security_groups))
  270. # Check removing a security group from a running instance
  271. test_inst.remove_security_group(sg)
  272. test_inst.refresh()
  273. self.assertTrue(
  274. sg not in test_inst.security_groups, "Expected security group"
  275. " '%s' to be removed from instance security_groups: [%s]" %
  276. (sg, test_inst.security_groups))
  277. # check floating ips
  278. router = self.provider.networking.routers.create(name, net)
  279. gateway = None
  280. def cleanup_router():
  281. with helpers.cleanup_action(lambda: router.delete()):
  282. with helpers.cleanup_action(lambda: gateway.delete()):
  283. router.detach_subnet(subnet)
  284. router.detach_gateway(gateway)
  285. with helpers.cleanup_action(lambda: cleanup_router()):
  286. router.attach_subnet(subnet)
  287. gateway = (self.provider.networking.gateways
  288. .get_or_create_inet_gateway(name))
  289. router.attach_gateway(gateway)
  290. # check whether adding an elastic ip works
  291. fip = (self.provider.networking.networks
  292. .create_floating_ip())
  293. with helpers.cleanup_action(lambda: fip.delete()):
  294. test_inst.add_floating_ip(fip.public_ip)
  295. test_inst.refresh()
  296. self.assertIn(fip.public_ip, test_inst.public_ips)
  297. if isinstance(self.provider, TestMockHelperMixin):
  298. # TODO: Moto bug does not refresh removed public ip
  299. return
  300. # check whether removing an elastic ip works
  301. test_inst.remove_floating_ip(fip.public_ip)
  302. test_inst.refresh()
  303. self.assertNotIn(fip.public_ip, test_inst.public_ips)