resources.py 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  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 must be set to ``False`` because the (default) ``True``
  341. # 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,
  353. self._os_region)
  354. for z in zones]
  355. class OpenStackVolume(BaseVolume):
  356. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  357. VOLUME_STATE_MAP = {
  358. 'creating': VolumeState.CREATING,
  359. 'available': VolumeState.AVAILABLE,
  360. 'attaching': VolumeState.CONFIGURING,
  361. 'in-use': VolumeState.IN_USE,
  362. 'deleting': VolumeState.CONFIGURING,
  363. 'error': VolumeState.ERROR,
  364. 'error_deleting': VolumeState.ERROR,
  365. 'backing-up': VolumeState.CONFIGURING,
  366. 'restoring-backup': VolumeState.CONFIGURING,
  367. 'error_restoring': VolumeState.ERROR,
  368. 'error_extending': VolumeState.ERROR
  369. }
  370. def __init__(self, provider, volume):
  371. super(OpenStackVolume, self).__init__(provider)
  372. self._volume = volume
  373. @property
  374. def id(self):
  375. return self._volume.id
  376. @property
  377. def name(self):
  378. """
  379. Get the volume name.
  380. """
  381. return self._volume.name
  382. @name.setter
  383. def name(self, value): # pylint:disable=arguments-differ
  384. """
  385. Set the volume name.
  386. """
  387. self._volume.name = value
  388. self._volume.update(name=value)
  389. @property
  390. def description(self):
  391. return self._volume.description
  392. @description.setter
  393. def description(self, value):
  394. self._volume.description = value
  395. self._volume.update(description=value)
  396. @property
  397. def size(self):
  398. return self._volume.size
  399. @property
  400. def create_time(self):
  401. return self._volume.created_at
  402. @property
  403. def zone_id(self):
  404. return self._volume.availability_zone
  405. @property
  406. def source(self):
  407. if self._volume.snapshot_id:
  408. return self._provider.block_store.snapshots.get(
  409. self._volume.snapshot_id)
  410. return None
  411. @property
  412. def attachments(self):
  413. if self._volume.attachments:
  414. return BaseAttachmentInfo(
  415. self,
  416. self._volume.attachments[0].get('server_id'),
  417. self._volume.attachments[0].get('device'))
  418. else:
  419. return None
  420. def attach(self, instance, device):
  421. """
  422. Attach this volume to an instance.
  423. """
  424. instance_id = instance.id if isinstance(
  425. instance,
  426. OpenStackInstance) else instance
  427. self._volume.attach(instance_id, device)
  428. def detach(self, force=False):
  429. """
  430. Detach this volume from an instance.
  431. """
  432. self._volume.detach()
  433. def create_snapshot(self, name, description=None):
  434. """
  435. Create a snapshot of this Volume.
  436. """
  437. return self._provider.block_store.snapshots.create(
  438. name, self, description=description)
  439. def delete(self):
  440. """
  441. Delete this volume.
  442. """
  443. self._volume.delete()
  444. @property
  445. def state(self):
  446. return OpenStackVolume.VOLUME_STATE_MAP.get(
  447. self._volume.status, VolumeState.UNKNOWN)
  448. def refresh(self):
  449. """
  450. Refreshes the state of this volume by re-querying the cloud provider
  451. for its latest state.
  452. """
  453. vol = self._provider.block_store.volumes.get(
  454. self.id)
  455. if vol:
  456. self._volume = vol._volume # pylint:disable=protected-access
  457. else:
  458. # The volume no longer exists and cannot be refreshed.
  459. # set the status to unknown
  460. self._volume.status = 'unknown'
  461. class OpenStackSnapshot(BaseSnapshot):
  462. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  463. SNAPSHOT_STATE_MAP = {
  464. 'creating': SnapshotState.PENDING,
  465. 'available': SnapshotState.AVAILABLE,
  466. 'deleting': SnapshotState.CONFIGURING,
  467. 'error': SnapshotState.ERROR,
  468. 'error_deleting': SnapshotState.ERROR
  469. }
  470. def __init__(self, provider, snapshot):
  471. super(OpenStackSnapshot, self).__init__(provider)
  472. self._snapshot = snapshot
  473. @property
  474. def id(self):
  475. return self._snapshot.id
  476. @property
  477. def name(self):
  478. """
  479. Get the snapshot name.
  480. """
  481. return self._snapshot.name
  482. @name.setter
  483. def name(self, value): # pylint:disable=arguments-differ
  484. """
  485. Set the snapshot name.
  486. """
  487. self._snapshot.name = value
  488. self._snapshot.update(name=value)
  489. @property
  490. def description(self):
  491. return self._snapshot.description
  492. @description.setter
  493. def description(self, value):
  494. self._snapshot.description = value
  495. self._snapshot.update(description=value)
  496. @property
  497. def size(self):
  498. return self._snapshot.size
  499. @property
  500. def volume_id(self):
  501. return self._snapshot.volume_id
  502. @property
  503. def create_time(self):
  504. return self._snapshot.created_at
  505. @property
  506. def state(self):
  507. return OpenStackSnapshot.SNAPSHOT_STATE_MAP.get(
  508. self._snapshot.status, SnapshotState.UNKNOWN)
  509. def refresh(self):
  510. """
  511. Refreshes the state of this snapshot by re-querying the cloud provider
  512. for its latest state.
  513. """
  514. snap = self._provider.block_store.snapshots.get(
  515. self.id)
  516. if snap:
  517. self._snapshot = snap._snapshot # pylint:disable=protected-access
  518. else:
  519. # The snapshot no longer exists and cannot be refreshed.
  520. # set the status to unknown
  521. self._snapshot.status = 'unknown'
  522. def delete(self):
  523. """
  524. Delete this snapshot.
  525. """
  526. self._snapshot.delete()
  527. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  528. """
  529. Create a new Volume from this Snapshot.
  530. """
  531. vol_name = "Created from {0} ({1})".format(self.id, self.name)
  532. size = size if size else self._snapshot.size
  533. os_vol = self._provider.cinder.volumes.create(
  534. size, name=vol_name, availability_zone=placement,
  535. snapshot_id=self._snapshot.id)
  536. cb_vol = OpenStackVolume(self._provider, os_vol)
  537. cb_vol.name = vol_name
  538. return cb_vol
  539. class OpenStackNetwork(BaseNetwork):
  540. # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
  541. # common/constants.py
  542. _NETWORK_STATE_MAP = {
  543. 'PENDING_CREATE': NetworkState.PENDING,
  544. 'PENDING_UPDATE': NetworkState.PENDING,
  545. 'PENDING_DELETE': NetworkState.PENDING,
  546. 'CREATED': NetworkState.PENDING,
  547. 'INACTIVE': NetworkState.PENDING,
  548. 'DOWN': NetworkState.DOWN,
  549. 'ERROR': NetworkState.ERROR,
  550. 'ACTIVE': NetworkState.AVAILABLE
  551. }
  552. def __init__(self, provider, network):
  553. super(OpenStackNetwork, self).__init__(provider)
  554. self._network = network
  555. @property
  556. def id(self):
  557. return self._network.get('id', None)
  558. @property
  559. def name(self):
  560. return self._network.get('name', None)
  561. @property
  562. def external(self):
  563. return self._network.get('router:external', False)
  564. @property
  565. def state(self):
  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. """
  592. Refreshes the state of this network by re-querying the cloud provider
  593. for its latest state.
  594. """
  595. return self.state
  596. class OpenStackSubnet(BaseSubnet):
  597. def __init__(self, provider, subnet):
  598. super(OpenStackSubnet, self).__init__(provider)
  599. self._subnet = subnet
  600. @property
  601. def id(self):
  602. return self._subnet.get('id', None)
  603. @property
  604. def name(self):
  605. return self._subnet.get('name', None)
  606. @property
  607. def cidr_block(self):
  608. return self._subnet.get('cidr', None)
  609. @property
  610. def network_id(self):
  611. return self._subnet.get('network_id', None)
  612. @property
  613. def zone(self):
  614. """
  615. OpenStack does not have a notion of placement zone for subnets.
  616. Default to None.
  617. """
  618. return None
  619. def delete(self):
  620. if self.id in str(self._provider.neutron.list_subnets()):
  621. self._provider.neutron.delete_subnet(self.id)
  622. # Adhere to the interface docs
  623. if self.id not in str(self._provider.neutron.list_subnets()):
  624. return True
  625. class OpenStackFloatingIP(BaseFloatingIP):
  626. def __init__(self, provider, floating_ip):
  627. super(OpenStackFloatingIP, self).__init__(provider)
  628. self._ip = floating_ip
  629. @property
  630. def id(self):
  631. return self._ip.get('id', None)
  632. @property
  633. def public_ip(self):
  634. return self._ip.get('floating_ip_address', None)
  635. @property
  636. def private_ip(self):
  637. return self._ip.get('fixed_ip_address', None)
  638. def in_use(self):
  639. return True if self._ip.get('status', None) == 'ACTIVE' else False
  640. def delete(self):
  641. self._provider.neutron.delete_floatingip(self.id)
  642. # Adhere to the interface docs
  643. if self.id not in str(self._provider.neutron.list_floatingips()):
  644. return True
  645. class OpenStackRouter(BaseRouter):
  646. def __init__(self, provider, router):
  647. super(OpenStackRouter, self).__init__(provider)
  648. self._router = router
  649. @property
  650. def id(self):
  651. return self._router.get('id', None)
  652. @property
  653. def name(self):
  654. return self._router.get('name', None)
  655. def refresh(self):
  656. self._router = self._provider.neutron.show_router(self.id)['router']
  657. @property
  658. def state(self):
  659. if self._router.get('external_gateway_info'):
  660. return RouterState.ATTACHED
  661. return RouterState.DETACHED
  662. @property
  663. def network_id(self):
  664. if self.state == RouterState.ATTACHED:
  665. return self._router.get('external_gateway_info', {}).get(
  666. 'network_id', None)
  667. return None
  668. def delete(self):
  669. self._provider.neutron.delete_router(self.id)
  670. # Adhere to the interface docs
  671. if self.id not in str(self._provider.neutron.list_routers()):
  672. return True
  673. def attach_network(self, network_id):
  674. self._router = self._provider.neutron.add_gateway_router(
  675. self.id, {'network_id': network_id}).get('router', self._router)
  676. if self.network_id and self.network_id == network_id:
  677. return True
  678. return False
  679. def detach_network(self):
  680. self._router = self._provider.neutron.remove_gateway_router(
  681. self.id).get('router', self._router)
  682. if not self.network_id:
  683. return True
  684. return False
  685. def add_route(self, subnet_id):
  686. router_interface = {'subnet_id': subnet_id}
  687. ret = self._provider.neutron.add_interface_router(
  688. self.id, router_interface)
  689. if subnet_id in ret.get('subnet_ids', ""):
  690. return True
  691. return False
  692. def remove_route(self, subnet_id):
  693. router_interface = {'subnet_id': subnet_id}
  694. ret = self._provider.neutron.remove_interface_router(
  695. self.id, router_interface)
  696. if subnet_id in ret.get('subnet_ids', ""):
  697. return True
  698. return False
  699. class OpenStackKeyPair(BaseKeyPair):
  700. def __init__(self, provider, key_pair):
  701. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  702. @property
  703. def material(self):
  704. """
  705. Unencrypted private key.
  706. :rtype: str
  707. :return: Unencrypted private key or ``None`` if not available.
  708. """
  709. return getattr(self._key_pair, 'private_key', None)
  710. class OpenStackSecurityGroup(BaseSecurityGroup):
  711. def __init__(self, provider, security_group):
  712. super(OpenStackSecurityGroup, self).__init__(provider, security_group)
  713. @property
  714. def network_id(self):
  715. """
  716. OpenStack does not associate a SG with a network so default to None.
  717. :return: Always return ``None``.
  718. """
  719. return None
  720. @property
  721. def rules(self):
  722. # Update SG object; otherwise, recently added rules do now show
  723. self._security_group = self._provider.nova.security_groups.get(
  724. self._security_group)
  725. return [OpenStackSecurityGroupRule(self._provider, r, self)
  726. for r in self._security_group.rules]
  727. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  728. cidr_ip=None, src_group=None):
  729. """
  730. Create a security group rule.
  731. You need to pass in either ``src_group`` OR ``ip_protocol`` AND
  732. ``from_port``, ``to_port``, ``cidr_ip``. In other words, either
  733. you are authorizing another group or you are authorizing some
  734. ip-based rule.
  735. :type ip_protocol: str
  736. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  737. :type from_port: int
  738. :param from_port: The beginning port number you are enabling
  739. :type to_port: int
  740. :param to_port: The ending port number you are enabling
  741. :type cidr_ip: str or list of strings
  742. :param cidr_ip: The CIDR block you are providing access to.
  743. :type src_group: ``object`` of :class:`.SecurityGroup`
  744. :param src_group: The Security Group you are granting access to.
  745. :rtype: :class:``.SecurityGroupRule``
  746. :return: Rule object if successful or ``None``.
  747. """
  748. if src_group:
  749. if not isinstance(src_group, SecurityGroup):
  750. src_group = self._provider.security.security_groups.get(
  751. src_group)
  752. existing_rule = self.get_rule(ip_protocol=ip_protocol,
  753. from_port=from_port,
  754. to_port=to_port,
  755. src_group=src_group)
  756. if existing_rule:
  757. return existing_rule
  758. rule = self._provider.nova.security_group_rules.create(
  759. parent_group_id=self._security_group.id,
  760. ip_protocol=ip_protocol,
  761. from_port=from_port,
  762. to_port=to_port,
  763. group_id=src_group.id)
  764. if rule:
  765. # We can only return one Rule so default to TCP (ie, last in
  766. # the for loop above).
  767. return OpenStackSecurityGroupRule(self._provider,
  768. rule.to_dict(), self)
  769. else:
  770. existing_rule = self.get_rule(ip_protocol=ip_protocol,
  771. from_port=from_port,
  772. to_port=to_port,
  773. cidr_ip=cidr_ip)
  774. if existing_rule:
  775. return existing_rule
  776. rule = self._provider.nova.security_group_rules.create(
  777. parent_group_id=self._security_group.id,
  778. ip_protocol=ip_protocol,
  779. from_port=from_port,
  780. to_port=to_port,
  781. cidr=cidr_ip)
  782. if rule:
  783. return OpenStackSecurityGroupRule(self._provider,
  784. rule.to_dict(), self)
  785. return None
  786. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  787. cidr_ip=None, src_group=None):
  788. # Update SG object; otherwise, recently added rules do not show
  789. self._security_group = self._provider.nova.security_groups.get(
  790. self._security_group)
  791. for rule in self._security_group.rules:
  792. if (rule['ip_protocol'] == ip_protocol and
  793. rule['from_port'] == from_port and
  794. rule['to_port'] == to_port and
  795. (rule['ip_range'].get('cidr') == cidr_ip or
  796. (rule['group'].get('name') == src_group.name if src_group
  797. else False))):
  798. return OpenStackSecurityGroupRule(self._provider, rule, self)
  799. return None
  800. def to_json(self):
  801. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  802. js = {k: v for(k, v) in attr if not k.startswith('_')}
  803. json_rules = [r.to_json() for r in self.rules]
  804. js['rules'] = [json.loads(r) for r in json_rules]
  805. return json.dumps(js, sort_keys=True)
  806. class OpenStackSecurityGroupRule(BaseSecurityGroupRule):
  807. def __init__(self, provider, rule, parent):
  808. super(OpenStackSecurityGroupRule, self).__init__(
  809. provider, rule, parent)
  810. @property
  811. def id(self):
  812. return self._rule.get('id')
  813. @property
  814. def ip_protocol(self):
  815. return self._rule.get('ip_protocol')
  816. @property
  817. def from_port(self):
  818. return int(self._rule.get('from_port') or 0)
  819. @property
  820. def to_port(self):
  821. return int(self._rule.get('to_port') or 0)
  822. @property
  823. def cidr_ip(self):
  824. return self._rule.get('ip_range', {}).get('cidr')
  825. @property
  826. def group(self):
  827. cg = self._rule.get('group', {}).get('name')
  828. if cg:
  829. security_groups = self._provider.nova.security_groups.list()
  830. for sg in security_groups:
  831. if sg.name == cg:
  832. return OpenStackSecurityGroup(self._provider, sg)
  833. return None
  834. def to_json(self):
  835. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  836. js = {k: v for(k, v) in attr if not k.startswith('_')}
  837. js['group'] = self.group.id if self.group else ''
  838. js['parent'] = self.parent.id if self.parent else ''
  839. return json.dumps(js, sort_keys=True)
  840. def delete(self):
  841. return self._provider.nova.security_group_rules.delete(self.id)
  842. class OpenStackBucketObject(BaseBucketObject):
  843. def __init__(self, provider, cbcontainer, obj):
  844. super(OpenStackBucketObject, self).__init__(provider)
  845. self.cbcontainer = cbcontainer
  846. self._obj = obj
  847. @property
  848. def id(self):
  849. return self._obj.get("name")
  850. @property
  851. def name(self):
  852. """Get this object's name."""
  853. return self._obj.get("name")
  854. @property
  855. def size(self):
  856. return self._obj.get("bytes")
  857. @property
  858. def last_modified(self):
  859. return self._obj.get("last_modified")
  860. def iter_content(self):
  861. """Returns this object's content as an iterable."""
  862. _, content = self._provider.swift.get_object(
  863. self.cbcontainer.name, self.name, resp_chunk_size=65536)
  864. return content
  865. def upload(self, data):
  866. """
  867. Set the contents of this object to the data read from the source
  868. string.
  869. """
  870. self._provider.swift.put_object(self.cbcontainer.name, self.name,
  871. data)
  872. def upload_from_file(self, path):
  873. """
  874. Stores the contents of the file pointed by the "path" variable.
  875. """
  876. raise NotImplementedError("This functionality is not implemented yet.")
  877. def delete(self):
  878. """
  879. Delete this object.
  880. :rtype: ``bool``
  881. :return: True if successful
  882. """
  883. try:
  884. self._provider.swift.delete_object(self.cbcontainer.name,
  885. self.name)
  886. except swiftex.ClientException as err:
  887. if err.http_status == 404:
  888. return True
  889. return False
  890. def generate_url(self, expires_in=0):
  891. """
  892. Generates a URL to this object. If the object is public, `expires_in`
  893. argument is not necessary, but if the object is private, the life time
  894. of URL is set using `expires_in` argument.
  895. :param expires_in: time to live of the generated URL in seconds.
  896. :return: A URL to access the object.
  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, key):
  913. """
  914. Retrieve a given object from this bucket.
  915. """
  916. _, object_list = self._provider.swift.get_container(
  917. self.name, prefix=key)
  918. if object_list:
  919. return OpenStackBucketObject(self._provider, self,
  920. object_list[0])
  921. else:
  922. return None
  923. def list(self, limit=None, marker=None, prefix=None):
  924. """
  925. List all objects within this bucket.
  926. :rtype: BucketObject
  927. :return: List of all available BucketObjects within this bucket.
  928. """
  929. if prefix is not None:
  930. raise NotImplementedError("Prefix functionality is not "
  931. "implemented yet.")
  932. _, object_list = self._provider.swift.get_container(
  933. self.name, limit=oshelpers.os_result_limit(self._provider, limit),
  934. marker=marker)
  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)
  949. def exists(self, name):
  950. """
  951. Determines if an object with given name exists in this bucket.
  952. """
  953. raise NotImplementedError("This functionality is not implemented yet.")