resources.py 40 KB

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