test_compute_service.py 17 KB

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