resources.py 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  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=''):
  584. subnet_info = {'name': name, 'network_id': self.id,
  585. 'cidr': cidr_block, 'ip_version': 4}
  586. subnet = (self._provider.neutron.create_subnet({'subnet': subnet_info})
  587. .get('subnet'))
  588. return OpenStackSubnet(self._provider, subnet)
  589. def refresh(self):
  590. """
  591. Refreshes the state of this network by re-querying the cloud provider
  592. for its latest state.
  593. """
  594. return self.state
  595. class OpenStackSubnet(BaseSubnet):
  596. def __init__(self, provider, subnet):
  597. super(OpenStackSubnet, self).__init__(provider)
  598. self._subnet = subnet
  599. @property
  600. def id(self):
  601. return self._subnet.get('id', None)
  602. @property
  603. def name(self):
  604. return self._subnet.get('name', None)
  605. @property
  606. def cidr_block(self):
  607. return self._subnet.get('cidr', None)
  608. @property
  609. def network_id(self):
  610. return self._subnet.get('network_id', None)
  611. def delete(self):
  612. if self.id in str(self._provider.neutron.list_subnets()):
  613. self._provider.neutron.delete_subnet(self.id)
  614. # Adhere to the interface docs
  615. if self.id not in str(self._provider.neutron.list_subnets()):
  616. return True
  617. class OpenStackFloatingIP(BaseFloatingIP):
  618. def __init__(self, provider, floating_ip):
  619. super(OpenStackFloatingIP, self).__init__(provider)
  620. self._ip = floating_ip
  621. @property
  622. def id(self):
  623. return self._ip.get('id', None)
  624. @property
  625. def public_ip(self):
  626. return self._ip.get('floating_ip_address', None)
  627. @property
  628. def private_ip(self):
  629. return self._ip.get('fixed_ip_address', None)
  630. def in_use(self):
  631. return True if self._ip.get('status', None) == 'ACTIVE' else False
  632. def delete(self):
  633. self._provider.neutron.delete_floatingip(self.id)
  634. # Adhere to the interface docs
  635. if self.id not in str(self._provider.neutron.list_floatingips()):
  636. return True
  637. class OpenStackRouter(BaseRouter):
  638. def __init__(self, provider, router):
  639. super(OpenStackRouter, self).__init__(provider)
  640. self._router = router
  641. @property
  642. def id(self):
  643. return self._router.get('id', None)
  644. @property
  645. def name(self):
  646. return self._router.get('name', None)
  647. def refresh(self):
  648. self._router = self._provider.neutron.show_router(self.id)['router']
  649. @property
  650. def state(self):
  651. if self._router.get('external_gateway_info'):
  652. return RouterState.ATTACHED
  653. return RouterState.DETACHED
  654. @property
  655. def network_id(self):
  656. if self.state == RouterState.ATTACHED:
  657. return self._router.get('external_gateway_info', {}).get(
  658. 'network_id', None)
  659. return None
  660. def delete(self):
  661. self._provider.neutron.delete_router(self.id)
  662. # Adhere to the interface docs
  663. if self.id not in str(self._provider.neutron.list_routers()):
  664. return True
  665. def attach_network(self, network_id):
  666. self._router = self._provider.neutron.add_gateway_router(
  667. self.id, {'network_id': network_id}).get('router', self._router)
  668. if self.network_id and self.network_id == network_id:
  669. return True
  670. return False
  671. def detach_network(self):
  672. self._router = self._provider.neutron.remove_gateway_router(
  673. self.id).get('router', self._router)
  674. if not self.network_id:
  675. return True
  676. return False
  677. def add_route(self, subnet_id):
  678. router_interface = {'subnet_id': subnet_id}
  679. ret = self._provider.neutron.add_interface_router(
  680. self.id, router_interface)
  681. if subnet_id in ret.get('subnet_ids', ""):
  682. return True
  683. return False
  684. def remove_route(self, subnet_id):
  685. router_interface = {'subnet_id': subnet_id}
  686. ret = self._provider.neutron.remove_interface_router(
  687. self.id, router_interface)
  688. if subnet_id in ret.get('subnet_ids', ""):
  689. return True
  690. return False
  691. class OpenStackKeyPair(BaseKeyPair):
  692. def __init__(self, provider, key_pair):
  693. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  694. @property
  695. def material(self):
  696. """
  697. Unencrypted private key.
  698. :rtype: str
  699. :return: Unencrypted private key or ``None`` if not available.
  700. """
  701. return getattr(self._key_pair, 'private_key', None)
  702. class OpenStackSecurityGroup(BaseSecurityGroup):
  703. def __init__(self, provider, security_group):
  704. super(OpenStackSecurityGroup, self).__init__(provider, security_group)
  705. @property
  706. def network_id(self):
  707. """
  708. OpenStack does not associate a SG with a network so default to None.
  709. :return: Always return ``None``.
  710. """
  711. return None
  712. @property
  713. def rules(self):
  714. # Update SG object; otherwise, recently added rules do now show
  715. self._security_group = self._provider.nova.security_groups.get(
  716. self._security_group)
  717. return [OpenStackSecurityGroupRule(self._provider, r, self)
  718. for r in self._security_group.rules]
  719. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  720. cidr_ip=None, src_group=None):
  721. """
  722. Create a security group rule.
  723. You need to pass in either ``src_group`` OR ``ip_protocol``,
  724. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  725. you are authorizing another group or you are authorizing some
  726. ip-based rule.
  727. :type ip_protocol: str
  728. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  729. :type from_port: int
  730. :param from_port: The beginning port number you are enabling
  731. :type to_port: int
  732. :param to_port: The ending port number you are enabling
  733. :type cidr_ip: str or list of strings
  734. :param cidr_ip: The CIDR block you are providing access to.
  735. :type src_group: ``object`` of :class:`.SecurityGroup`
  736. :param src_group: The Security Group you are granting access to.
  737. :rtype: :class:``.SecurityGroupRule``
  738. :return: Rule object if successful or ``None``.
  739. """
  740. if src_group:
  741. if not isinstance(src_group, SecurityGroup):
  742. src_group = self._provider.security.security_groups.get(
  743. src_group)
  744. for protocol in ['udp', 'tcp']:
  745. existing_rule = self.get_rule(ip_protocol=ip_protocol,
  746. from_port=1,
  747. to_port=65535,
  748. src_group=src_group)
  749. if existing_rule:
  750. return existing_rule
  751. rule = self._provider.nova.security_group_rules.create(
  752. parent_group_id=self._security_group.id,
  753. ip_protocol=protocol,
  754. from_port=1,
  755. to_port=65535,
  756. group_id=src_group.id)
  757. if rule:
  758. # We can only return one Rule so default to TCP (ie, last in
  759. # the for loop above).
  760. return OpenStackSecurityGroupRule(self._provider,
  761. rule.to_dict(), self)
  762. else:
  763. existing_rule = self.get_rule(ip_protocol=ip_protocol,
  764. from_port=from_port,
  765. to_port=to_port,
  766. cidr_ip=cidr_ip)
  767. if existing_rule:
  768. return existing_rule
  769. rule = self._provider.nova.security_group_rules.create(
  770. parent_group_id=self._security_group.id,
  771. ip_protocol=ip_protocol,
  772. from_port=from_port,
  773. to_port=to_port,
  774. cidr=cidr_ip)
  775. if rule:
  776. return OpenStackSecurityGroupRule(self._provider,
  777. rule.to_dict(), self)
  778. return None
  779. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  780. cidr_ip=None, src_group=None):
  781. # Update SG object; otherwise, recently added rules do now show
  782. self._security_group = self._provider.nova.security_groups.get(
  783. self._security_group)
  784. for rule in self._security_group.rules:
  785. if (rule['ip_protocol'] == ip_protocol and
  786. rule['from_port'] == from_port and
  787. rule['to_port'] == to_port and
  788. rule['ip_range'].get('cidr') == cidr_ip) or \
  789. (rule['group'].get('name') == src_group.name if src_group
  790. else False):
  791. return OpenStackSecurityGroupRule(self._provider, rule, self)
  792. return None
  793. def to_json(self):
  794. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  795. js = {k: v for(k, v) in attr if not k.startswith('_')}
  796. json_rules = [r.to_json() for r in self.rules]
  797. js['rules'] = [json.loads(r) for r in json_rules]
  798. return json.dumps(js, sort_keys=True)
  799. class OpenStackSecurityGroupRule(BaseSecurityGroupRule):
  800. def __init__(self, provider, rule, parent):
  801. super(OpenStackSecurityGroupRule, self).__init__(
  802. provider, rule, parent)
  803. @property
  804. def id(self):
  805. return self._rule.get('id')
  806. @property
  807. def ip_protocol(self):
  808. return self._rule.get('ip_protocol')
  809. @property
  810. def from_port(self):
  811. return int(self._rule.get('from_port') or 0)
  812. @property
  813. def to_port(self):
  814. return int(self._rule.get('to_port') or 0)
  815. @property
  816. def cidr_ip(self):
  817. return self._rule.get('ip_range', {}).get('cidr')
  818. @property
  819. def group(self):
  820. cg = self._rule.get('group', {}).get('name')
  821. if cg:
  822. security_groups = self._provider.nova.security_groups.list()
  823. for sg in security_groups:
  824. if sg.name == cg:
  825. return OpenStackSecurityGroup(self._provider, sg)
  826. return None
  827. def to_json(self):
  828. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  829. js = {k: v for(k, v) in attr if not k.startswith('_')}
  830. js['group'] = self.group.id if self.group else ''
  831. js['parent'] = self.parent.id if self.parent else ''
  832. return json.dumps(js, sort_keys=True)
  833. def delete(self):
  834. return self._provider.nova.security_group_rules.delete(self.id)
  835. class OpenStackBucketObject(BaseBucketObject):
  836. def __init__(self, provider, cbcontainer, obj):
  837. super(OpenStackBucketObject, self).__init__(provider)
  838. self.cbcontainer = cbcontainer
  839. self._obj = obj
  840. @property
  841. def id(self):
  842. return self._obj.get("name")
  843. @property
  844. def name(self):
  845. """
  846. Get this object's name.
  847. """
  848. return self._obj.get("name")
  849. @property
  850. def size(self):
  851. return self._obj.get("bytes")
  852. @property
  853. def last_modified(self):
  854. return self._obj.get("last_modified")
  855. def iter_content(self):
  856. """
  857. Returns this object's content as an
  858. iterable.
  859. """
  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 delete(self):
  871. """
  872. Delete this object.
  873. :rtype: bool
  874. :return: True if successful
  875. """
  876. try:
  877. self._provider.swift.delete_object(self.cbcontainer.name,
  878. self.name)
  879. except swiftex.ClientException as err:
  880. if err.http_status == 404:
  881. return True
  882. return False
  883. class OpenStackBucket(BaseBucket):
  884. def __init__(self, provider, bucket):
  885. super(OpenStackBucket, self).__init__(provider)
  886. self._bucket = bucket
  887. @property
  888. def id(self):
  889. return self._bucket.get("name")
  890. @property
  891. def name(self):
  892. """
  893. Get this bucket's name.
  894. """
  895. return self._bucket.get("name")
  896. def get(self, key):
  897. """
  898. Retrieve a given object from this bucket.
  899. """
  900. _, object_list = self._provider.swift.get_container(
  901. self.name, prefix=key)
  902. if object_list:
  903. return OpenStackBucketObject(self._provider, self,
  904. object_list[0])
  905. else:
  906. return None
  907. def list(self, limit=None, marker=None):
  908. """
  909. List all objects within this bucket.
  910. :rtype: BucketObject
  911. :return: List of all available BucketObjects within this bucket.
  912. """
  913. _, object_list = self._provider.swift.get_container(
  914. self.name, limit=oshelpers.os_result_limit(self._provider, limit),
  915. marker=marker)
  916. cb_objects = [OpenStackBucketObject(
  917. self._provider, self, obj) for obj in object_list]
  918. return oshelpers.to_server_paged_list(
  919. self._provider,
  920. cb_objects,
  921. limit)
  922. def delete(self, delete_contents=False):
  923. """
  924. Delete this bucket.
  925. """
  926. self._provider.swift.delete_container(self.name)
  927. def create_object(self, object_name):
  928. self._provider.swift.put_object(self.name, object_name, None)
  929. return self.get(object_name)