test_compute_service.py 18 KB

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