test_compute_service.py 16 KB

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