test_compute_service.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import ipaddress
  2. import six
  3. from unittest import skip
  4. import uuid
  5. from cloudbridge.cloud.interfaces import InvalidConfigurationException
  6. from cloudbridge.cloud.interfaces import InstanceState
  7. from cloudbridge.cloud.interfaces.resources import InstanceType
  8. from cloudbridge.cloud.interfaces.exceptions import WaitStateException
  9. from test.helpers import ProviderTestBase
  10. import test.helpers as helpers
  11. class CloudComputeServiceTestCase(ProviderTestBase):
  12. def __init__(self, methodName, provider):
  13. super(CloudComputeServiceTestCase, self).__init__(
  14. methodName=methodName, provider=provider)
  15. @helpers.skipIfNoService(['compute.instances', 'network'])
  16. @skip("Until Moto supports 'state' for DescribeSubnets filter")
  17. def test_crud_instance(self):
  18. name = "CBInstCrud-{0}-{1}".format(
  19. self.provider.name,
  20. uuid.uuid4())
  21. net, _ = helpers.create_test_network(self.provider, name)
  22. inst = helpers.get_test_instance(self.provider, name, network=net)
  23. with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
  24. inst, net)):
  25. all_instances = self.provider.compute.instances.list()
  26. list_instances = [i for i in all_instances if i.name == name]
  27. self.assertTrue(
  28. len(list_instances) == 1,
  29. "List instances does not return the expected instance %s" %
  30. name)
  31. # check iteration
  32. iter_instances = [i for i in self.provider.compute.instances
  33. if i.name == name]
  34. self.assertTrue(
  35. len(iter_instances) == 1,
  36. "Iter instances does not return the expected instance %s" %
  37. name)
  38. # check find
  39. find_instances = self.provider.compute.instances.find(name=name)
  40. self.assertTrue(
  41. len(find_instances) == 1,
  42. "Find instances does not return the expected instance %s" %
  43. name)
  44. # check non-existent find
  45. find_instances = self.provider.compute.instances.find(
  46. name="non_existent")
  47. self.assertTrue(
  48. len(find_instances) == 0,
  49. "Find() for a non-existent image returned %s" % find_instances)
  50. get_inst = self.provider.compute.instances.get(
  51. inst.id)
  52. self.assertTrue(
  53. list_instances[0] ==
  54. get_inst == inst,
  55. "Objects returned by list: {0} and get: {1} are not as "
  56. " expected: {2}" .format(list_instances[0].id,
  57. get_inst.id,
  58. inst.id))
  59. self.assertTrue(
  60. list_instances[0].name ==
  61. get_inst.name == inst.name,
  62. "Names returned by list: {0} and get: {1} are not as "
  63. " expected: {2}" .format(list_instances[0].name,
  64. get_inst.name,
  65. inst.name))
  66. deleted_inst = self.provider.compute.instances.get(
  67. inst.id)
  68. self.assertTrue(
  69. deleted_inst is None or deleted_inst.state in (
  70. InstanceState.TERMINATED,
  71. InstanceState.UNKNOWN),
  72. "Instance %s should have been deleted but still exists." %
  73. name)
  74. def _is_valid_ip(self, address):
  75. try:
  76. ipaddress.ip_address(address)
  77. except ValueError:
  78. return False
  79. return True
  80. @helpers.skipIfNoService(['compute.instances', 'network',
  81. 'security.security_groups',
  82. 'security.key_pairs'])
  83. @skip("Until Moto supports 'state' for DescribeSubnets filter")
  84. def test_instance_properties(self):
  85. name = "CBInstProps-{0}-{1}".format(
  86. self.provider.name,
  87. uuid.uuid4())
  88. net, _ = helpers.create_test_network(self.provider, name)
  89. kp = self.provider.security.key_pairs.create(name=name)
  90. sg = self.provider.security.security_groups.create(
  91. name=name, description=name, network_id=net.id)
  92. test_instance = helpers.get_test_instance(self.provider,
  93. name, key_pair=kp,
  94. security_groups=[sg],
  95. network=net)
  96. with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
  97. test_instance, net, sg, kp)):
  98. self.assertTrue(
  99. test_instance.id in repr(test_instance),
  100. "repr(obj) should contain the object id so that the object"
  101. " can be reconstructed, but does not. eval(repr(obj)) == obj")
  102. self.assertEqual(
  103. test_instance.name, name,
  104. "Instance name {0} is not equal to the expected name"
  105. " {1}".format(test_instance.name, name))
  106. image_id = helpers.get_provider_test_data(self.provider, "image")
  107. self.assertEqual(test_instance.image_id, image_id,
  108. "Image id {0} is not equal to the expected id"
  109. " {1}".format(test_instance.image_id, image_id))
  110. self.assertIsInstance(test_instance.zone_id,
  111. six.string_types)
  112. # FIXME: Moto is not returning the instance's placement zone
  113. # find_zone = [zone for zone in
  114. # self.provider.compute.regions.current.zones
  115. # if zone.id == test_instance.zone_id]
  116. # self.assertEqual(len(find_zone), 1,
  117. # "Instance's placement zone could not be "
  118. # " found in zones list")
  119. self.assertEqual(
  120. test_instance.image_id,
  121. helpers.get_provider_test_data(self.provider, "image"))
  122. self.assertIsInstance(test_instance.public_ips, list)
  123. self.assertIsInstance(test_instance.private_ips, list)
  124. self.assertEqual(
  125. test_instance.key_pair_name,
  126. kp.name)
  127. self.assertIsInstance(test_instance.security_groups, list)
  128. self.assertEqual(
  129. test_instance.security_groups[0],
  130. sg)
  131. self.assertIsInstance(test_instance.security_group_ids, list)
  132. self.assertEqual(
  133. test_instance.security_group_ids[0],
  134. sg.id)
  135. # Must have either a public or a private ip
  136. ip_private = test_instance.private_ips[0] \
  137. if test_instance.private_ips else None
  138. ip_address = test_instance.public_ips[0] \
  139. if test_instance.public_ips and test_instance.public_ips[0] \
  140. else ip_private
  141. self.assertIsNotNone(
  142. ip_address,
  143. "Instance must have either a public IP or a private IP")
  144. self.assertTrue(
  145. self._is_valid_ip(ip_address),
  146. "Instance must have a valid IP address")
  147. self.assertIsInstance(test_instance.instance_type_id,
  148. six.string_types)
  149. itype = self.provider.compute.instance_types.get(
  150. test_instance.instance_type_id)
  151. self.assertEqual(
  152. itype, test_instance.instance_type,
  153. "Instance type {0} does not match expected type {1}".format(
  154. itype.name, test_instance.instance_type))
  155. self.assertIsInstance(itype, InstanceType)
  156. expected_type = helpers.get_provider_test_data(self.provider,
  157. 'instance_type')
  158. self.assertEqual(
  159. itype.name, expected_type,
  160. "Instance type {0} does not match expected type {1}".format(
  161. itype.name, expected_type))
  162. @helpers.skipIfNoService(['compute.instances', 'compute.images',
  163. 'compute.instance_types'])
  164. def test_block_device_mapping_launch_config(self):
  165. lc = self.provider.compute.instances.create_launch_config()
  166. # specifying an invalid size should raise
  167. # an exception
  168. with self.assertRaises(InvalidConfigurationException):
  169. lc.add_volume_device(size=-1)
  170. # Attempting to add a blank volume without specifying a size
  171. # should raise an exception
  172. with self.assertRaises(InvalidConfigurationException):
  173. lc.add_volume_device(source=None)
  174. # block_devices should be empty so far
  175. self.assertListEqual(
  176. lc.block_devices, [], "No block devices should have been"
  177. " added to mappings list since the configuration was"
  178. " invalid")
  179. # Add a new volume
  180. lc.add_volume_device(size=1, delete_on_terminate=True)
  181. # Override root volume size
  182. image_id = helpers.get_provider_test_data(self.provider, "image")
  183. img = self.provider.compute.images.get(image_id)
  184. lc.add_volume_device(
  185. is_root=True,
  186. source=img,
  187. # TODO: This should be greater than the ami size or tests will fail
  188. # on actual infrastructure. Needs an image.size method
  189. size=2,
  190. delete_on_terminate=True)
  191. # Attempting to add more than one root volume should raise an
  192. # exception.
  193. with self.assertRaises(InvalidConfigurationException):
  194. lc.add_volume_device(size=1, is_root=True)
  195. # Attempting to add an incorrect source should raise an exception
  196. with self.assertRaises(InvalidConfigurationException):
  197. lc.add_volume_device(
  198. source="invalid_source",
  199. delete_on_terminate=True)
  200. # Add all available ephemeral devices
  201. instance_type_name = helpers.get_provider_test_data(
  202. self.provider,
  203. "instance_type")
  204. inst_type = self.provider.compute.instance_types.find(
  205. name=instance_type_name)[0]
  206. for _ in range(inst_type.num_ephemeral_disks):
  207. lc.add_ephemeral_device()
  208. # block_devices should be populated
  209. self.assertTrue(
  210. len(lc.block_devices) == 2 + inst_type.num_ephemeral_disks,
  211. "Expected %d total block devices bit found %d" %
  212. (2 + inst_type.num_ephemeral_disks, len(lc.block_devices)))
  213. @helpers.skipIfNoService(['compute.instances', 'compute.images',
  214. 'compute.instance_types', 'block_store.volumes'])
  215. def test_block_device_mapping_attachments(self):
  216. name = "CBInstBlkAttch-{0}-{1}".format(
  217. self.provider.name,
  218. uuid.uuid4())
  219. # test_vol = self.provider.block_store.volumes.create(
  220. # name,
  221. # 1,
  222. # helpers.get_provider_test_data(self.provider, "placement"))
  223. # with helpers.cleanup_action(lambda: test_vol.delete()):
  224. # test_vol.wait_till_ready()
  225. # test_snap = test_vol.create_snapshot(name=name,
  226. # description=name)
  227. #
  228. # def cleanup_snap(snap):
  229. # snap.delete()
  230. # snap.wait_for(
  231. # [SnapshotState.UNKNOWN],
  232. # terminal_states=[SnapshotState.ERROR])
  233. #
  234. # with helpers.cleanup_action(lambda: cleanup_snap(test_snap)):
  235. # test_snap.wait_till_ready()
  236. lc = self.provider.compute.instances.create_launch_config()
  237. # Add a new blank volume
  238. # lc.add_volume_device(size=1, delete_on_terminate=True)
  239. # Attach an existing volume
  240. # lc.add_volume_device(size=1, source=test_vol,
  241. # delete_on_terminate=True)
  242. # Add a new volume based on a snapshot
  243. # lc.add_volume_device(size=1, source=test_snap,
  244. # delete_on_terminate=True)
  245. # Override root volume size
  246. image_id = helpers.get_provider_test_data(
  247. self.provider,
  248. "image")
  249. img = self.provider.compute.images.get(image_id)
  250. lc.add_volume_device(
  251. is_root=True,
  252. source=img,
  253. # TODO: This should be greater than the ami size or tests
  254. # will fail on actual infrastructure. Needs an image.size
  255. # method
  256. size=2,
  257. delete_on_terminate=True)
  258. # Add all available ephemeral devices
  259. instance_type_name = helpers.get_provider_test_data(
  260. self.provider,
  261. "instance_type")
  262. inst_type = self.provider.compute.instance_types.find(
  263. name=instance_type_name)[0]
  264. for _ in range(inst_type.num_ephemeral_disks):
  265. lc.add_ephemeral_device()
  266. net, _ = helpers.create_test_network(self.provider, name)
  267. inst = helpers.create_test_instance(
  268. self.provider,
  269. name,
  270. network=net,
  271. # We don't have a way to match the test net placement and this zone
  272. # zone=helpers.get_provider_test_data(self.provider, 'placement'),
  273. launch_config=lc)
  274. def cleanup(instance, net):
  275. instance.terminate()
  276. instance.wait_for(
  277. [InstanceState.TERMINATED, InstanceState.UNKNOWN],
  278. terminal_states=[InstanceState.ERROR])
  279. helpers.delete_test_network(net)
  280. with helpers.cleanup_action(lambda: cleanup(inst, net)):
  281. try:
  282. inst.wait_till_ready()
  283. except WaitStateException as e:
  284. self.fail("The block device mapped launch did not "
  285. " complete successfully: %s" % e)
  286. # TODO: Check instance attachments and make sure they
  287. # correspond to requested mappings