resources.py 40 KB

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