resources.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  1. """
  2. DataTypes used by this provider
  3. """
  4. import inspect
  5. import ipaddress
  6. import logging
  7. import os
  8. import cloudbridge.cloud.base.helpers as cb_helpers
  9. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  10. from cloudbridge.cloud.base.resources import BaseBucket
  11. from cloudbridge.cloud.base.resources import BaseBucketContainer
  12. from cloudbridge.cloud.base.resources import BaseBucketObject
  13. from cloudbridge.cloud.base.resources import BaseFloatingIP
  14. from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
  15. from cloudbridge.cloud.base.resources import BaseGatewayContainer
  16. from cloudbridge.cloud.base.resources import BaseInstance
  17. from cloudbridge.cloud.base.resources import BaseInternetGateway
  18. from cloudbridge.cloud.base.resources import BaseKeyPair
  19. from cloudbridge.cloud.base.resources import BaseMachineImage
  20. from cloudbridge.cloud.base.resources import BaseNetwork
  21. from cloudbridge.cloud.base.resources import BasePlacementZone
  22. from cloudbridge.cloud.base.resources import BaseRegion
  23. from cloudbridge.cloud.base.resources import BaseRouter
  24. from cloudbridge.cloud.base.resources import BaseSnapshot
  25. from cloudbridge.cloud.base.resources import BaseSubnet
  26. from cloudbridge.cloud.base.resources import BaseVMFirewall
  27. from cloudbridge.cloud.base.resources import BaseVMFirewallRule
  28. from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
  29. from cloudbridge.cloud.base.resources import BaseVMType
  30. from cloudbridge.cloud.base.resources import BaseVolume
  31. from cloudbridge.cloud.base.resources import ClientPagedResultList
  32. from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
  33. from cloudbridge.cloud.interfaces.resources import GatewayState
  34. from cloudbridge.cloud.interfaces.resources import InstanceState
  35. from cloudbridge.cloud.interfaces.resources import MachineImageState
  36. from cloudbridge.cloud.interfaces.resources import NetworkState
  37. from cloudbridge.cloud.interfaces.resources import RouterState
  38. from cloudbridge.cloud.interfaces.resources import SnapshotState
  39. from cloudbridge.cloud.interfaces.resources import SubnetState
  40. from cloudbridge.cloud.interfaces.resources import TrafficDirection
  41. from cloudbridge.cloud.interfaces.resources import VolumeState
  42. from cloudbridge.cloud.providers.openstack import helpers as oshelpers
  43. from keystoneclient.v3.regions import Region
  44. from neutronclient.common.exceptions import PortNotFoundClient
  45. import novaclient.exceptions as novaex
  46. from openstack.exceptions import HttpException
  47. from openstack.exceptions import ResourceNotFound
  48. import swiftclient
  49. from swiftclient.service import SwiftService, SwiftUploadObject
  50. ONE_GIG = 1048576000 # in bytes
  51. FIVE_GIG = ONE_GIG * 5 # in bytes
  52. log = logging.getLogger(__name__)
  53. class OpenStackMachineImage(BaseMachineImage):
  54. # ref: http://docs.openstack.org/developer/glance/statuses.html
  55. IMAGE_STATE_MAP = {
  56. 'queued': MachineImageState.PENDING,
  57. 'saving': MachineImageState.PENDING,
  58. 'active': MachineImageState.AVAILABLE,
  59. 'killed': MachineImageState.ERROR,
  60. 'deleted': MachineImageState.ERROR,
  61. 'pending_delete': MachineImageState.ERROR,
  62. 'deactivated': MachineImageState.ERROR
  63. }
  64. def __init__(self, provider, os_image):
  65. super(OpenStackMachineImage, self).__init__(provider)
  66. if isinstance(os_image, OpenStackMachineImage):
  67. # pylint:disable=protected-access
  68. self._os_image = os_image._os_image
  69. else:
  70. self._os_image = os_image
  71. @property
  72. def id(self):
  73. """
  74. Get the image identifier.
  75. """
  76. return self._os_image.id
  77. @property
  78. def name(self):
  79. """
  80. Get the image name.
  81. """
  82. return self._os_image.name
  83. @property
  84. def description(self):
  85. """
  86. Get the image description.
  87. """
  88. return None
  89. @property
  90. def min_disk(self):
  91. """
  92. Returns the minimum size of the disk that's required to
  93. boot this image (in GB)
  94. :rtype: ``int``
  95. :return: The minimum disk size needed by this image
  96. """
  97. return self._os_image.min_disk
  98. def delete(self):
  99. """
  100. Delete this image
  101. """
  102. self._os_image.delete(self._provider.os_conn.session)
  103. @property
  104. def state(self):
  105. return OpenStackMachineImage.IMAGE_STATE_MAP.get(
  106. self._os_image.status, MachineImageState.UNKNOWN)
  107. def refresh(self):
  108. """
  109. Refreshes the state of this instance by re-querying the cloud provider
  110. for its latest state.
  111. """
  112. log.debug("Refreshing OpenStack Machine Image")
  113. image = self._provider.compute.images.get(self.id)
  114. if image:
  115. self._os_image = image._os_image # pylint:disable=protected-access
  116. else:
  117. # The image no longer exists and cannot be refreshed.
  118. # set the status to unknown
  119. self._os_image.status = 'unknown'
  120. class OpenStackPlacementZone(BasePlacementZone):
  121. def __init__(self, provider, zone, region):
  122. super(OpenStackPlacementZone, self).__init__(provider)
  123. if isinstance(zone, OpenStackPlacementZone):
  124. # pylint:disable=protected-access
  125. self._os_zone = zone._os_zone
  126. # pylint:disable=protected-access
  127. self._os_region = zone._os_region
  128. else:
  129. self._os_zone = zone
  130. self._os_region = region
  131. @property
  132. def id(self):
  133. """
  134. Get the zone id
  135. :rtype: ``str``
  136. :return: ID for this zone as returned by the cloud middleware.
  137. """
  138. return self._os_zone
  139. @property
  140. def name(self):
  141. """
  142. Get the zone name.
  143. :rtype: ``str``
  144. :return: Name for this zone as returned by the cloud middleware.
  145. """
  146. # return self._os_zone.zoneName
  147. return self._os_zone
  148. @property
  149. def region_name(self):
  150. """
  151. Get the region that this zone belongs to.
  152. :rtype: ``str``
  153. :return: Name of this zone's region as returned by the cloud middleware
  154. """
  155. return self._os_region
  156. class OpenStackVMType(BaseVMType):
  157. def __init__(self, provider, os_flavor):
  158. super(OpenStackVMType, self).__init__(provider)
  159. self._os_flavor = os_flavor
  160. @property
  161. def id(self):
  162. return self._os_flavor.id
  163. @property
  164. def name(self):
  165. return self._os_flavor.name
  166. @property
  167. def family(self):
  168. # TODO: This may not be standardised across OpenStack
  169. # but NeCTAR is using it this way
  170. return self.extra_data.get('flavor_class:name')
  171. @property
  172. def vcpus(self):
  173. return self._os_flavor.vcpus
  174. @property
  175. def ram(self):
  176. return int(self._os_flavor.ram) / 1024
  177. @property
  178. def size_root_disk(self):
  179. return self._os_flavor.disk
  180. @property
  181. def size_ephemeral_disks(self):
  182. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  183. self._os_flavor.ephemeral
  184. @property
  185. def num_ephemeral_disks(self):
  186. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  187. self._os_flavor.ephemeral
  188. @property
  189. def extra_data(self):
  190. extras = self._os_flavor.get_keys()
  191. extras['rxtx_factor'] = self._os_flavor.rxtx_factor
  192. extras['swap'] = self._os_flavor.swap
  193. extras['is_public'] = self._os_flavor.is_public
  194. return extras
  195. class OpenStackInstance(BaseInstance):
  196. # ref: http://docs.openstack.org/developer/nova/v2/2.0_server_concepts.html
  197. # and http://developer.openstack.org/api-ref-compute-v2.html
  198. INSTANCE_STATE_MAP = {
  199. 'ACTIVE': InstanceState.RUNNING,
  200. 'BUILD': InstanceState.PENDING,
  201. 'DELETED': InstanceState.DELETED,
  202. 'ERROR': InstanceState.ERROR,
  203. 'HARD_REBOOT': InstanceState.REBOOTING,
  204. 'PASSWORD': InstanceState.PENDING,
  205. 'PAUSED': InstanceState.STOPPED,
  206. 'REBOOT': InstanceState.REBOOTING,
  207. 'REBUILD': InstanceState.CONFIGURING,
  208. 'RESCUE': InstanceState.CONFIGURING,
  209. 'RESIZE': InstanceState.CONFIGURING,
  210. 'REVERT_RESIZE': InstanceState.CONFIGURING,
  211. 'SOFT_DELETED': InstanceState.STOPPED,
  212. 'STOPPED': InstanceState.STOPPED,
  213. 'SUSPENDED': InstanceState.STOPPED,
  214. 'SHUTOFF': InstanceState.STOPPED,
  215. 'UNKNOWN': InstanceState.UNKNOWN,
  216. 'VERIFY_RESIZE': InstanceState.CONFIGURING
  217. }
  218. def __init__(self, provider, os_instance):
  219. super(OpenStackInstance, self).__init__(provider)
  220. self._os_instance = os_instance
  221. @property
  222. def id(self):
  223. """
  224. Get the instance identifier.
  225. """
  226. return self._os_instance.id
  227. @property
  228. # pylint:disable=arguments-differ
  229. def name(self):
  230. """
  231. Get the instance name.
  232. """
  233. return self._os_instance.name
  234. @name.setter
  235. # pylint:disable=arguments-differ
  236. def name(self, value):
  237. """
  238. Set the instance name.
  239. """
  240. self.assert_valid_resource_name(value)
  241. self._os_instance.name = value
  242. self._os_instance.update(name=value)
  243. @property
  244. def public_ips(self):
  245. """
  246. Get all the public IP addresses for this instance.
  247. """
  248. # OpenStack doesn't provide an easy way to figure our whether an IP is
  249. # public or private, since the returned IPs are grouped by an arbitrary
  250. # network label. Therefore, it's necessary to parse the address and
  251. # determine whether it's public or private
  252. return [address
  253. for _, addresses in self._os_instance.networks.items()
  254. for address in addresses
  255. if not ipaddress.ip_address(address).is_private]
  256. @property
  257. def private_ips(self):
  258. """
  259. Get all the private IP addresses for this instance.
  260. """
  261. return [address
  262. for _, addresses in self._os_instance.networks.items()
  263. for address in addresses
  264. if ipaddress.ip_address(address).is_private]
  265. @property
  266. def vm_type_id(self):
  267. """
  268. Get the VM type name.
  269. """
  270. return self._os_instance.flavor.get('id')
  271. @property
  272. def vm_type(self):
  273. """
  274. Get the VM type object.
  275. """
  276. flavor = self._provider.nova.flavors.get(
  277. self._os_instance.flavor.get('id'))
  278. return OpenStackVMType(self._provider, flavor)
  279. def reboot(self):
  280. """
  281. Reboot this instance (using the cloud middleware API).
  282. """
  283. self._os_instance.reboot()
  284. def delete(self):
  285. """
  286. Permanently delete this instance.
  287. """
  288. # delete the port we created when launching
  289. # Assumption: it's the first interface in the list
  290. iface_list = self._os_instance.interface_list()
  291. if iface_list:
  292. self._provider.neutron.delete_port(iface_list[0].port_id)
  293. self._os_instance.delete()
  294. @property
  295. def image_id(self):
  296. """
  297. Get the image ID for this instance.
  298. """
  299. # In OpenStack, the Machine Image of a running instance may
  300. # be deleted, so make sure the image exists before attempting to
  301. # retrieve its id
  302. return (self._os_instance.image.get("id")
  303. if self._os_instance.image else "")
  304. @property
  305. def zone_id(self):
  306. """
  307. Get the placement zone where this instance is running.
  308. """
  309. return getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None)
  310. @property
  311. def vm_firewalls(self):
  312. return [
  313. self._provider.security.vm_firewalls.get(group.id)
  314. for group in self._os_instance.list_security_group()
  315. ]
  316. @property
  317. def vm_firewall_ids(self):
  318. """
  319. Get the VM firewall IDs associated with this instance.
  320. """
  321. return [fw.id for fw in self.vm_firewalls]
  322. @property
  323. def key_pair_name(self):
  324. """
  325. Get the name of the key pair associated with this instance.
  326. """
  327. return self._os_instance.key_name
  328. def create_image(self, name):
  329. """
  330. Create a new image based on this instance.
  331. """
  332. log.debug("Creating OpenStack Image with the name %s", name)
  333. self.assert_valid_resource_name(name)
  334. image_id = self._os_instance.create_image(name)
  335. return OpenStackMachineImage(
  336. self._provider, self._provider.compute.images.get(image_id))
  337. def _get_fip(self, floating_ip):
  338. """Get a floating IP object based on the supplied ID."""
  339. return OpenStackFloatingIP(
  340. self._provider,
  341. self._provider.os_conn.network.get_ip(floating_ip))
  342. def add_floating_ip(self, floating_ip):
  343. """
  344. Add a floating IP address to this instance.
  345. """
  346. log.debug("Adding floating IP adress: %s", floating_ip)
  347. fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
  348. else self._get_fip(floating_ip))
  349. self._os_instance.add_floating_ip(fip.public_ip)
  350. def remove_floating_ip(self, floating_ip):
  351. """
  352. Remove a floating IP address from this instance.
  353. """
  354. log.debug("Removing floating IP adress: %s", floating_ip)
  355. fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
  356. else self._get_fip(floating_ip))
  357. self._os_instance.remove_floating_ip(fip.public_ip)
  358. def add_vm_firewall(self, firewall):
  359. """
  360. Add a VM firewall to this instance
  361. """
  362. log.debug("Adding firewall: %s", firewall)
  363. self._os_instance.add_security_group(firewall.id)
  364. def remove_vm_firewall(self, firewall):
  365. """
  366. Remove a VM firewall from this instance
  367. """
  368. log.debug("Removing firewall: %s", firewall)
  369. self._os_instance.remove_security_group(firewall.id)
  370. @property
  371. def state(self):
  372. return OpenStackInstance.INSTANCE_STATE_MAP.get(
  373. self._os_instance.status, InstanceState.UNKNOWN)
  374. def refresh(self):
  375. """
  376. Refreshes the state of this instance by re-querying the cloud provider
  377. for its latest state.
  378. """
  379. instance = self._provider.compute.instances.get(
  380. self.id)
  381. if instance:
  382. # pylint:disable=protected-access
  383. self._os_instance = instance._os_instance
  384. else:
  385. # The instance no longer exists and cannot be refreshed.
  386. # set the status to unknown
  387. self._os_instance.status = 'unknown'
  388. class OpenStackRegion(BaseRegion):
  389. def __init__(self, provider, os_region):
  390. super(OpenStackRegion, self).__init__(provider)
  391. self._os_region = os_region
  392. @property
  393. def id(self):
  394. return (self._os_region.id if type(self._os_region) == Region else
  395. self._os_region)
  396. @property
  397. def name(self):
  398. return (self._os_region.id if type(self._os_region) == Region else
  399. self._os_region)
  400. @property
  401. def zones(self):
  402. # ``detailed`` param must be set to ``False`` because the (default)
  403. # ``True`` value requires Admin privileges
  404. if self.name == self._provider.region_name: # optimisation
  405. zones = self._provider.nova.availability_zones.list(detailed=False)
  406. else:
  407. try:
  408. # pylint:disable=protected-access
  409. region_nova = self._provider._connect_nova_region(self.name)
  410. zones = region_nova.availability_zones.list(detailed=False)
  411. except novaex.EndpointNotFound:
  412. # This region may not have a compute endpoint. If so just
  413. # return an empty list
  414. zones = []
  415. return [OpenStackPlacementZone(self._provider, z.zoneName, self.name)
  416. for z in zones]
  417. class OpenStackVolume(BaseVolume):
  418. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  419. VOLUME_STATE_MAP = {
  420. 'creating': VolumeState.CREATING,
  421. 'available': VolumeState.AVAILABLE,
  422. 'attaching': VolumeState.CONFIGURING,
  423. 'in-use': VolumeState.IN_USE,
  424. 'deleting': VolumeState.CONFIGURING,
  425. 'error': VolumeState.ERROR,
  426. 'error_deleting': VolumeState.ERROR,
  427. 'backing-up': VolumeState.CONFIGURING,
  428. 'restoring-backup': VolumeState.CONFIGURING,
  429. 'error_restoring': VolumeState.ERROR,
  430. 'error_extending': VolumeState.ERROR
  431. }
  432. def __init__(self, provider, volume):
  433. super(OpenStackVolume, self).__init__(provider)
  434. self._volume = volume
  435. @property
  436. def id(self):
  437. return self._volume.id
  438. @property
  439. # pylint:disable=arguments-differ
  440. def name(self):
  441. """
  442. Get the volume name.
  443. """
  444. return self._volume.name
  445. @name.setter
  446. # pylint:disable=arguments-differ
  447. def name(self, value):
  448. """
  449. Set the volume name.
  450. """
  451. self.assert_valid_resource_name(value)
  452. self._volume.name = value
  453. self._volume.update(name=value)
  454. @property
  455. def description(self):
  456. return self._volume.description
  457. @description.setter
  458. def description(self, value):
  459. self._volume.description = value
  460. self._volume.update(description=value)
  461. @property
  462. def size(self):
  463. return self._volume.size
  464. @property
  465. def create_time(self):
  466. return self._volume.created_at
  467. @property
  468. def zone_id(self):
  469. return self._volume.availability_zone
  470. @property
  471. def source(self):
  472. if self._volume.snapshot_id:
  473. return self._provider.storage.snapshots.get(
  474. self._volume.snapshot_id)
  475. return None
  476. @property
  477. def attachments(self):
  478. if self._volume.attachments:
  479. return BaseAttachmentInfo(
  480. self,
  481. self._volume.attachments[0].get('server_id'),
  482. self._volume.attachments[0].get('device'))
  483. else:
  484. return None
  485. def attach(self, instance, device):
  486. """
  487. Attach this volume to an instance.
  488. """
  489. log.debug("Attaching %s to %s instance", device, instance)
  490. instance_id = instance.id if isinstance(
  491. instance,
  492. OpenStackInstance) else instance
  493. self._volume.attach(instance_id, device)
  494. def detach(self, force=False):
  495. """
  496. Detach this volume from an instance.
  497. """
  498. self._volume.detach()
  499. def create_snapshot(self, name, description=None):
  500. """
  501. Create a snapshot of this Volume.
  502. """
  503. log.debug("Creating snapchat of volume: %s with the "
  504. "description: %s", name, description)
  505. return self._provider.storage.snapshots.create(
  506. name, self, description=description)
  507. def delete(self):
  508. """
  509. Delete this volume.
  510. """
  511. self._volume.delete()
  512. @property
  513. def state(self):
  514. return OpenStackVolume.VOLUME_STATE_MAP.get(
  515. self._volume.status, VolumeState.UNKNOWN)
  516. def refresh(self):
  517. """
  518. Refreshes the state of this volume by re-querying the cloud provider
  519. for its latest state.
  520. """
  521. vol = self._provider.storage.volumes.get(
  522. self.id)
  523. if vol:
  524. self._volume = vol._volume # pylint:disable=protected-access
  525. else:
  526. # The volume no longer exists and cannot be refreshed.
  527. # set the status to unknown
  528. self._volume.status = 'unknown'
  529. class OpenStackSnapshot(BaseSnapshot):
  530. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  531. SNAPSHOT_STATE_MAP = {
  532. 'creating': SnapshotState.PENDING,
  533. 'available': SnapshotState.AVAILABLE,
  534. 'deleting': SnapshotState.CONFIGURING,
  535. 'error': SnapshotState.ERROR,
  536. 'error_deleting': SnapshotState.ERROR
  537. }
  538. def __init__(self, provider, snapshot):
  539. super(OpenStackSnapshot, self).__init__(provider)
  540. self._snapshot = snapshot
  541. @property
  542. def id(self):
  543. return self._snapshot.id
  544. @property
  545. # pylint:disable=arguments-differ
  546. def name(self):
  547. """
  548. Get the snapshot name.
  549. """
  550. return self._snapshot.name
  551. @name.setter
  552. # pylint:disable=arguments-differ
  553. def name(self, value):
  554. """
  555. Set the snapshot name.
  556. """
  557. self.assert_valid_resource_name(value)
  558. self._snapshot.name = value
  559. self._snapshot.update(name=value)
  560. @property
  561. def description(self):
  562. return self._snapshot.description
  563. @description.setter
  564. def description(self, value):
  565. self._snapshot.description = value
  566. self._snapshot.update(description=value)
  567. @property
  568. def size(self):
  569. return self._snapshot.size
  570. @property
  571. def volume_id(self):
  572. return self._snapshot.volume_id
  573. @property
  574. def create_time(self):
  575. return self._snapshot.created_at
  576. @property
  577. def state(self):
  578. return OpenStackSnapshot.SNAPSHOT_STATE_MAP.get(
  579. self._snapshot.status, SnapshotState.UNKNOWN)
  580. def refresh(self):
  581. """
  582. Refreshes the state of this snapshot by re-querying the cloud provider
  583. for its latest state.
  584. """
  585. snap = self._provider.storage.snapshots.get(
  586. self.id)
  587. if snap:
  588. self._snapshot = snap._snapshot # pylint:disable=protected-access
  589. else:
  590. # The snapshot no longer exists and cannot be refreshed.
  591. # set the status to unknown
  592. self._snapshot.status = 'unknown'
  593. def delete(self):
  594. """
  595. Delete this snapshot.
  596. """
  597. self._snapshot.delete()
  598. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  599. """
  600. Create a new Volume from this Snapshot.
  601. """
  602. vol_name = "from_snap_{0}".format(self.id or self.name)
  603. size = size if size else self._snapshot.size
  604. os_vol = self._provider.cinder.volumes.create(
  605. size, name=vol_name, availability_zone=placement,
  606. snapshot_id=self._snapshot.id)
  607. cb_vol = OpenStackVolume(self._provider, os_vol)
  608. cb_vol.name = vol_name
  609. return cb_vol
  610. class OpenStackGatewayContainer(BaseGatewayContainer):
  611. """For OpenStack, an internet gateway is a just an 'external' network."""
  612. def __init__(self, provider, network):
  613. super(OpenStackGatewayContainer, self).__init__(provider, network)
  614. def _check_fip_connectivity(self, external_net):
  615. # Due to current limitations in OpenStack:
  616. # https://bugs.launchpad.net/neutron/+bug/1743480, it's not
  617. # possible to differentiate between floating ip networks and provider
  618. # external networks. Therefore, we systematically step through
  619. # all available networks and perform an assignment test to infer valid
  620. # floating ip nets.
  621. dummy_router = self._provider.networking.routers.create(
  622. network=self._network, name='cb_conn_test_router')
  623. with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
  624. try:
  625. dummy_router.attach_gateway(external_net)
  626. return True
  627. except Exception:
  628. return False
  629. def get_or_create_inet_gateway(self, name=None):
  630. """For OS, inet gtw is any net that has `external` property set."""
  631. if name:
  632. OpenStackInternetGateway.assert_valid_resource_name(name)
  633. external_nets = (n for n in self._provider.networking.networks
  634. if n.external)
  635. for net in external_nets:
  636. if self._check_fip_connectivity(net):
  637. return OpenStackInternetGateway(self._provider, net)
  638. return None
  639. def delete(self, gateway):
  640. log.debug("Deleting OpenStack Gateway: %s", gateway)
  641. gateway.delete()
  642. def list(self, limit=None, marker=None):
  643. log.debug("OpenStack listing of all current internet gateways")
  644. igl = [OpenStackInternetGateway(self._provider, n)
  645. for n in self._provider.networking.networks
  646. if n.external and self._check_fip_connectivity(n)]
  647. return ClientPagedResultList(self._provider, igl, limit=limit,
  648. marker=marker)
  649. class OpenStackNetwork(BaseNetwork):
  650. # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
  651. # common/constants.py
  652. _NETWORK_STATE_MAP = {
  653. 'PENDING_CREATE': NetworkState.PENDING,
  654. 'PENDING_UPDATE': NetworkState.PENDING,
  655. 'PENDING_DELETE': NetworkState.PENDING,
  656. 'CREATED': NetworkState.PENDING,
  657. 'INACTIVE': NetworkState.PENDING,
  658. 'DOWN': NetworkState.DOWN,
  659. 'ERROR': NetworkState.ERROR,
  660. 'ACTIVE': NetworkState.AVAILABLE
  661. }
  662. def __init__(self, provider, network):
  663. super(OpenStackNetwork, self).__init__(provider)
  664. self._network = network
  665. self._gateway_service = OpenStackGatewayContainer(provider, self)
  666. @property
  667. def id(self):
  668. return self._network.get('id', None)
  669. @property
  670. def name(self):
  671. return self._network.get('name', None)
  672. @name.setter
  673. def name(self, value): # pylint:disable=arguments-differ
  674. """
  675. Set the network name.
  676. """
  677. self.assert_valid_resource_name(value)
  678. self._provider.neutron.update_network(self.id,
  679. {'network': {'name': value}})
  680. self.refresh()
  681. @property
  682. def external(self):
  683. return self._network.get('router:external', False)
  684. @property
  685. def state(self):
  686. self.refresh()
  687. return OpenStackNetwork._NETWORK_STATE_MAP.get(
  688. self._network.get('status', None),
  689. NetworkState.UNKNOWN)
  690. @property
  691. def cidr_block(self):
  692. # OpenStack does not define a CIDR block for networks
  693. return ''
  694. def delete(self):
  695. if not self.external and self.id in str(
  696. self._provider.neutron.list_networks()):
  697. # If there are ports associated with the network, it won't delete
  698. ports = self._provider.neutron.list_ports(
  699. network_id=self.id).get('ports', [])
  700. for port in ports:
  701. try:
  702. self._provider.neutron.delete_port(port.get('id'))
  703. except PortNotFoundClient:
  704. # Ports could have already been deleted if instances
  705. # are terminated etc. so exceptions can be safely ignored
  706. pass
  707. self._provider.neutron.delete_network(self.id)
  708. @property
  709. def subnets(self):
  710. subnets = (self._provider.neutron.list_subnets(network_id=self.id)
  711. .get('subnets', []))
  712. return [OpenStackSubnet(self._provider, subnet) for subnet in subnets]
  713. def refresh(self):
  714. """Refresh the state of this network by re-querying the provider."""
  715. network = self._provider.networking.networks.get(self.id)
  716. if network:
  717. # pylint:disable=protected-access
  718. self._network = network._network
  719. else:
  720. # subnet no longer exists
  721. self._network.state = NetworkState.UNKNOWN
  722. @property
  723. def gateways(self):
  724. return self._gateway_service
  725. class OpenStackSubnet(BaseSubnet):
  726. def __init__(self, provider, subnet):
  727. super(OpenStackSubnet, self).__init__(provider)
  728. self._subnet = subnet
  729. self._state = None
  730. @property
  731. def id(self):
  732. return self._subnet.get('id', None)
  733. @property
  734. def name(self):
  735. return self._subnet.get('name', None)
  736. @name.setter
  737. def name(self, value): # pylint:disable=arguments-differ
  738. """
  739. Set the subnet name.
  740. """
  741. self.assert_valid_resource_name(value)
  742. self._provider.neutron.update_subnet(
  743. self.id, {'subnet': {'name': value}})
  744. self._subnet['name'] = value
  745. @property
  746. def cidr_block(self):
  747. return self._subnet.get('cidr', None)
  748. @property
  749. def network_id(self):
  750. return self._subnet.get('network_id', None)
  751. @property
  752. def zone(self):
  753. """
  754. OpenStack does not have a notion of placement zone for subnets.
  755. Default to None.
  756. """
  757. return None
  758. def delete(self):
  759. if self.id in str(self._provider.neutron.list_subnets()):
  760. self._provider.neutron.delete_subnet(self.id)
  761. @property
  762. def state(self):
  763. return SubnetState.UNKNOWN if self._state == SubnetState.UNKNOWN \
  764. else SubnetState.AVAILABLE
  765. def refresh(self):
  766. subnet = self._provider.networking.subnets.get(self.id)
  767. if subnet:
  768. # pylint:disable=protected-access
  769. self._subnet = subnet._subnet
  770. self._state = SubnetState.AVAILABLE
  771. else:
  772. # subnet no longer exists
  773. self._state = SubnetState.UNKNOWN
  774. class OpenStackFloatingIPContainer(BaseFloatingIPContainer):
  775. def __init__(self, provider, gateway):
  776. super(OpenStackFloatingIPContainer, self).__init__(provider, gateway)
  777. def get(self, fip_id):
  778. try:
  779. return OpenStackFloatingIP(
  780. self._provider, self._provider.os_conn.network.get_ip(fip_id))
  781. except ResourceNotFound:
  782. return None
  783. def list(self, limit=None, marker=None):
  784. fips = [OpenStackFloatingIP(self._provider, fip)
  785. for fip in self._provider.os_conn.network.ips(
  786. floating_network_id=self.gateway.id
  787. )]
  788. return ClientPagedResultList(self._provider, fips,
  789. limit=limit, marker=marker)
  790. def create(self):
  791. return OpenStackFloatingIP(
  792. self._provider, self._provider.os_conn.network.create_ip(
  793. floating_network_id=self.gateway.id))
  794. class OpenStackFloatingIP(BaseFloatingIP):
  795. def __init__(self, provider, floating_ip):
  796. super(OpenStackFloatingIP, self).__init__(provider)
  797. self._ip = floating_ip
  798. @property
  799. def id(self):
  800. return self._ip.id
  801. @property
  802. def public_ip(self):
  803. return self._ip.floating_ip_address
  804. @property
  805. def private_ip(self):
  806. return self._ip.fixed_ip_address
  807. @property
  808. def in_use(self):
  809. return bool(self._ip.port_id)
  810. def delete(self):
  811. self._ip.delete(self._provider.os_conn.session)
  812. def refresh(self):
  813. net = self._provider.networking.networks.get(
  814. self._ip.floating_network_id)
  815. gw = net.gateways.get_or_create_inet_gateway()
  816. fip = gw.floating_ips.get(self.id)
  817. # pylint:disable=protected-access
  818. self._ip = fip._ip
  819. class OpenStackRouter(BaseRouter):
  820. def __init__(self, provider, router):
  821. super(OpenStackRouter, self).__init__(provider)
  822. self._router = router
  823. @property
  824. def id(self):
  825. return self._router.get('id', None)
  826. @property
  827. def name(self):
  828. return self._router.get('name', None)
  829. @name.setter
  830. def name(self, value): # pylint:disable=arguments-differ
  831. """
  832. Set the router name.
  833. """
  834. self.assert_valid_resource_name(value)
  835. self._provider.neutron.update_router(
  836. self.id, {'router': {'name': value}})
  837. self.refresh()
  838. def refresh(self):
  839. self._router = self._provider.neutron.show_router(self.id)['router']
  840. @property
  841. def state(self):
  842. if self._router.get('external_gateway_info'):
  843. return RouterState.ATTACHED
  844. return RouterState.DETACHED
  845. @property
  846. def network_id(self):
  847. if self.state == RouterState.ATTACHED:
  848. return self._router.get('external_gateway_info', {}).get(
  849. 'network_id', None)
  850. return None
  851. def delete(self):
  852. self._provider.neutron.delete_router(self.id)
  853. def attach_subnet(self, subnet):
  854. router_interface = {'subnet_id': subnet.id}
  855. ret = self._provider.neutron.add_interface_router(
  856. self.id, router_interface)
  857. if subnet.id in ret.get('subnet_ids', ""):
  858. return True
  859. return False
  860. def detach_subnet(self, subnet):
  861. router_interface = {'subnet_id': subnet.id}
  862. ret = self._provider.neutron.remove_interface_router(
  863. self.id, router_interface)
  864. if subnet.id in ret.get('subnet_ids', ""):
  865. return True
  866. return False
  867. def attach_gateway(self, gateway):
  868. self._provider.neutron.add_gateway_router(
  869. self.id, {'network_id': gateway.id})
  870. def detach_gateway(self, gateway):
  871. self._provider.neutron.remove_gateway_router(
  872. self.id).get('router', self._router)
  873. class OpenStackInternetGateway(BaseInternetGateway):
  874. GATEWAY_STATE_MAP = {
  875. NetworkState.AVAILABLE: GatewayState.AVAILABLE,
  876. NetworkState.DOWN: GatewayState.ERROR,
  877. NetworkState.ERROR: GatewayState.ERROR,
  878. NetworkState.PENDING: GatewayState.CONFIGURING,
  879. NetworkState.UNKNOWN: GatewayState.UNKNOWN
  880. }
  881. def __init__(self, provider, gateway_net):
  882. super(OpenStackInternetGateway, self).__init__(provider)
  883. if isinstance(gateway_net, OpenStackNetwork):
  884. # pylint:disable=protected-access
  885. gateway_net = gateway_net._network
  886. self._gateway_net = gateway_net
  887. self._fips_container = OpenStackFloatingIPContainer(provider, self)
  888. @property
  889. def id(self):
  890. return self._gateway_net.get('id', None)
  891. @property
  892. def name(self):
  893. return self._gateway_net.get('name', None)
  894. @name.setter
  895. # pylint:disable=arguments-differ
  896. def name(self, value):
  897. self.assert_valid_resource_name(value)
  898. self._provider.neutron.update_network(self.id,
  899. {'network': {'name': value}})
  900. self.refresh()
  901. @property
  902. def network_id(self):
  903. return self._gateway_net.get('id')
  904. def refresh(self):
  905. """Refresh the state of this network by re-querying the provider."""
  906. network = self._provider.networking.networks.get(self.id)
  907. if network:
  908. # pylint:disable=protected-access
  909. self._gateway_net = network._network
  910. else:
  911. # subnet no longer exists
  912. self._gateway_net.state = NetworkState.UNKNOWN
  913. @property
  914. def state(self):
  915. return self.GATEWAY_STATE_MAP.get(
  916. self._gateway_net.state, GatewayState.UNKNOWN)
  917. def delete(self):
  918. """Do nothing on openstack"""
  919. pass
  920. @property
  921. def floating_ips(self):
  922. return self._fips_container
  923. class OpenStackKeyPair(BaseKeyPair):
  924. def __init__(self, provider, key_pair):
  925. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  926. class OpenStackVMFirewall(BaseVMFirewall):
  927. def __init__(self, provider, vm_firewall):
  928. super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
  929. self._rule_svc = OpenStackVMFirewallRuleContainer(provider, self)
  930. @property
  931. def network_id(self):
  932. """
  933. OpenStack does not associate a SG with a network so default to None.
  934. :return: Always return ``None``.
  935. """
  936. return None
  937. @property
  938. def rules(self):
  939. return self._rule_svc
  940. def delete(self):
  941. return self._vm_firewall.delete(self._provider.os_conn.session)
  942. def refresh(self):
  943. self._vm_firewall = self._provider.os_conn.network.get_security_group(
  944. self.id)
  945. def to_json(self):
  946. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  947. js = {k: v for(k, v) in attr if not k.startswith('_')}
  948. json_rules = [r.to_json() for r in self.rules]
  949. js['rules'] = json_rules
  950. return js
  951. class OpenStackVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
  952. def __init__(self, provider, firewall):
  953. super(OpenStackVMFirewallRuleContainer, self).__init__(
  954. provider, firewall)
  955. def list(self, limit=None, marker=None):
  956. # pylint:disable=protected-access
  957. rules = [OpenStackVMFirewallRule(self.firewall, r)
  958. for r in self.firewall._vm_firewall.security_group_rules]
  959. return ClientPagedResultList(self._provider, rules,
  960. limit=limit, marker=marker)
  961. def create(self, direction, protocol=None, from_port=None,
  962. to_port=None, cidr=None, src_dest_fw=None):
  963. src_dest_fw_id = (src_dest_fw.id if isinstance(src_dest_fw,
  964. OpenStackVMFirewall)
  965. else src_dest_fw)
  966. try:
  967. if direction == TrafficDirection.INBOUND:
  968. os_direction = 'ingress'
  969. elif direction == TrafficDirection.OUTBOUND:
  970. os_direction = 'egress'
  971. else:
  972. raise InvalidValueException("direction", direction)
  973. # pylint:disable=protected-access
  974. rule = self._provider.os_conn.network.create_security_group_rule(
  975. security_group_id=self.firewall.id,
  976. direction=os_direction,
  977. port_range_max=to_port,
  978. port_range_min=from_port,
  979. protocol=protocol,
  980. remote_ip_prefix=cidr,
  981. remote_group_id=src_dest_fw_id)
  982. self.firewall.refresh()
  983. return OpenStackVMFirewallRule(self.firewall, rule.to_dict())
  984. except HttpException as e:
  985. self.firewall.refresh()
  986. # 409=Conflict, raised for duplicate rule
  987. if e.http_status == 409:
  988. existing = self.find(direction=direction, protocol=protocol,
  989. from_port=from_port, to_port=to_port,
  990. cidr=cidr, src_dest_fw_id=src_dest_fw_id)
  991. return existing[0]
  992. else:
  993. raise e
  994. class OpenStackVMFirewallRule(BaseVMFirewallRule):
  995. def __init__(self, parent_fw, rule):
  996. super(OpenStackVMFirewallRule, self).__init__(parent_fw, rule)
  997. @property
  998. def id(self):
  999. return self._rule.get('id')
  1000. @property
  1001. def direction(self):
  1002. direction = self._rule.get('direction')
  1003. if direction == 'ingress':
  1004. return TrafficDirection.INBOUND
  1005. elif direction == 'egress':
  1006. return TrafficDirection.OUTBOUND
  1007. else:
  1008. return None
  1009. @property
  1010. def protocol(self):
  1011. return self._rule.get('protocol')
  1012. @property
  1013. def from_port(self):
  1014. return self._rule.get('port_range_min')
  1015. @property
  1016. def to_port(self):
  1017. return self._rule.get('port_range_max')
  1018. @property
  1019. def cidr(self):
  1020. return self._rule.get('remote_ip_prefix')
  1021. @property
  1022. def src_dest_fw_id(self):
  1023. fw = self.src_dest_fw
  1024. if fw:
  1025. return fw.id
  1026. return None
  1027. @property
  1028. def src_dest_fw(self):
  1029. fw_id = self._rule.get('remote_group_id')
  1030. if fw_id:
  1031. return self._provider.security.vm_firewalls.get(fw_id)
  1032. return None
  1033. def delete(self):
  1034. self._provider.os_conn.network.delete_security_group_rule(self.id)
  1035. self.firewall.refresh()
  1036. class OpenStackBucketObject(BaseBucketObject):
  1037. def __init__(self, provider, cbcontainer, obj):
  1038. super(OpenStackBucketObject, self).__init__(provider)
  1039. self.cbcontainer = cbcontainer
  1040. self._obj = obj
  1041. @property
  1042. def id(self):
  1043. return self._obj.get("name")
  1044. @property
  1045. def name(self):
  1046. """Get this object's name."""
  1047. return self._obj.get("name")
  1048. @property
  1049. def size(self):
  1050. return self._obj.get("bytes")
  1051. @property
  1052. def last_modified(self):
  1053. return self._obj.get("last_modified")
  1054. def iter_content(self):
  1055. """Returns this object's content as an iterable."""
  1056. _, content = self._provider.swift.get_object(
  1057. self.cbcontainer.name, self.name, resp_chunk_size=65536)
  1058. return content
  1059. def upload(self, data):
  1060. """
  1061. Set the contents of this object to the data read from the source
  1062. string.
  1063. .. warning:: Will fail if the data is larger than 5 Gig.
  1064. """
  1065. self._provider.swift.put_object(self.cbcontainer.name, self.name,
  1066. data)
  1067. def upload_from_file(self, path):
  1068. """
  1069. Stores the contents of the file pointed by the ``path`` variable.
  1070. If the file is bigger than 5 Gig, it will be broken into segments.
  1071. :type path: ``str``
  1072. :param path: Absolute path to the file to be uploaded to Swift.
  1073. :rtype: ``bool``
  1074. :return: ``True`` if successful, ``False`` if not.
  1075. .. note::
  1076. * The size of the segments chosen (or any of the other upload
  1077. options) is not under user control.
  1078. * If called this method will remap the
  1079. ``swiftclient.service.get_conn`` factory method to
  1080. ``self._provider._connect_swift``
  1081. .. seealso:: https://github.com/gvlproject/cloudbridge/issues/35#issuecomment-297629661 # noqa
  1082. """
  1083. upload_options = {}
  1084. if 'segment_size' not in upload_options:
  1085. if os.path.getsize(path) >= FIVE_GIG:
  1086. upload_options['segment_size'] = FIVE_GIG
  1087. # remap the swift service's connection factory method
  1088. # pylint:disable=protected-access
  1089. swiftclient.service.get_conn = self._provider._connect_swift
  1090. result = True
  1091. with SwiftService() as swift:
  1092. upload_object = SwiftUploadObject(path, object_name=self.name)
  1093. for up_res in swift.upload(self.cbcontainer.name,
  1094. [upload_object, ],
  1095. options=upload_options):
  1096. result = result and up_res['success']
  1097. return result
  1098. def delete(self):
  1099. """
  1100. Delete this object.
  1101. :rtype: ``bool``
  1102. :return: True if successful
  1103. .. note:: If called this method will remap the
  1104. ``swiftclient.service.get_conn`` factory method to
  1105. ``self._provider._connect_swift``
  1106. """
  1107. # remap the swift service's connection factory method
  1108. # pylint:disable=protected-access
  1109. swiftclient.service.get_conn = self._provider._connect_swift
  1110. result = True
  1111. with SwiftService() as swift:
  1112. for del_res in swift.delete(self.cbcontainer.name, [self.name, ]):
  1113. result = result and del_res['success']
  1114. return result
  1115. def generate_url(self, expires_in=0):
  1116. """
  1117. Generates a URL to this object.
  1118. If the object is public, `expires_in` argument is not necessary, but if
  1119. the object is private, the life time of URL is set using `expires_in`
  1120. argument.
  1121. See here for implementation details:
  1122. http://stackoverflow.com/a/37057172
  1123. """
  1124. raise NotImplementedError("This functionality is not implemented yet.")
  1125. class OpenStackBucket(BaseBucket):
  1126. def __init__(self, provider, bucket):
  1127. super(OpenStackBucket, self).__init__(provider)
  1128. self._bucket = bucket
  1129. self._object_container = OpenStackBucketContainer(provider, self)
  1130. @property
  1131. def id(self):
  1132. return self._bucket.get("name")
  1133. @property
  1134. def name(self):
  1135. return self._bucket.get("name")
  1136. @property
  1137. def objects(self):
  1138. return self._object_container
  1139. def delete(self, delete_contents=False):
  1140. self._provider.swift.delete_container(self.name)
  1141. class OpenStackBucketContainer(BaseBucketContainer):
  1142. def __init__(self, provider, bucket):
  1143. super(OpenStackBucketContainer, self).__init__(provider, bucket)
  1144. def get(self, name):
  1145. """
  1146. Retrieve a given object from this bucket.
  1147. FIXME: If multiple objects match the name as their name prefix,
  1148. all will be returned by the provider but this method will only
  1149. return the first element.
  1150. """
  1151. _, object_list = self._provider.swift.get_container(
  1152. self.bucket.name, prefix=name)
  1153. if object_list:
  1154. return OpenStackBucketObject(self._provider, self.bucket,
  1155. object_list[0])
  1156. else:
  1157. return None
  1158. def list(self, limit=None, marker=None, prefix=None):
  1159. """
  1160. List all objects within this bucket.
  1161. :rtype: BucketObject
  1162. :return: List of all available BucketObjects within this bucket.
  1163. """
  1164. _, object_list = self._provider.swift.get_container(
  1165. self.bucket.name,
  1166. limit=oshelpers.os_result_limit(self._provider, limit),
  1167. marker=marker, prefix=prefix)
  1168. cb_objects = [OpenStackBucketObject(
  1169. self._provider, self.bucket, obj) for obj in object_list]
  1170. return oshelpers.to_server_paged_list(
  1171. self._provider,
  1172. cb_objects,
  1173. limit)
  1174. def find(self, **kwargs):
  1175. obj_list = self
  1176. filters = ['name']
  1177. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  1178. return ClientPagedResultList(self._provider, list(matches))
  1179. def create(self, object_name):
  1180. self._provider.swift.put_object(self.bucket.name, object_name, None)
  1181. return self.get(object_name)