resources.py 36 KB

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