test_compute_service.py 18 KB

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