resources.py 35 KB

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