resources.py 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571
  1. """
  2. DataTypes used by this provider
  3. """
  4. import inspect
  5. import ipaddress
  6. import logging
  7. import os
  8. try:
  9. from urllib.parse import urlparse
  10. from urllib.parse import urljoin
  11. except ImportError: # python 2
  12. from urlparse import urlparse
  13. from urlparse import urljoin
  14. from keystoneclient.v3.regions import Region
  15. from neutronclient.common.exceptions import PortNotFoundClient
  16. import novaclient.exceptions as novaex
  17. from openstack.exceptions import HttpException
  18. from openstack.exceptions import NotFoundException
  19. from openstack.exceptions import ResourceNotFound
  20. import swiftclient
  21. from swiftclient.service import SwiftService, SwiftUploadObject
  22. from swiftclient.utils import generate_temp_url
  23. import cloudbridge.cloud.base.helpers as cb_helpers
  24. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  25. from cloudbridge.cloud.base.resources import BaseBucket
  26. from cloudbridge.cloud.base.resources import BaseBucketContainer
  27. from cloudbridge.cloud.base.resources import BaseBucketObject
  28. from cloudbridge.cloud.base.resources import BaseFloatingIP
  29. from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
  30. from cloudbridge.cloud.base.resources import BaseGatewayContainer
  31. from cloudbridge.cloud.base.resources import BaseInstance
  32. from cloudbridge.cloud.base.resources import BaseInternetGateway
  33. from cloudbridge.cloud.base.resources import BaseKeyPair
  34. from cloudbridge.cloud.base.resources import BaseMachineImage
  35. from cloudbridge.cloud.base.resources import BaseNetwork
  36. from cloudbridge.cloud.base.resources import BasePlacementZone
  37. from cloudbridge.cloud.base.resources import BaseRegion
  38. from cloudbridge.cloud.base.resources import BaseRouter
  39. from cloudbridge.cloud.base.resources import BaseSnapshot
  40. from cloudbridge.cloud.base.resources import BaseSubnet
  41. from cloudbridge.cloud.base.resources import BaseVMFirewall
  42. from cloudbridge.cloud.base.resources import BaseVMFirewallRule
  43. from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
  44. from cloudbridge.cloud.base.resources import BaseVMType
  45. from cloudbridge.cloud.base.resources import BaseVolume
  46. from cloudbridge.cloud.base.resources import ClientPagedResultList
  47. from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
  48. from cloudbridge.cloud.interfaces.resources import GatewayState
  49. from cloudbridge.cloud.interfaces.resources import InstanceState
  50. from cloudbridge.cloud.interfaces.resources import MachineImageState
  51. from cloudbridge.cloud.interfaces.resources import NetworkState
  52. from cloudbridge.cloud.interfaces.resources import RouterState
  53. from cloudbridge.cloud.interfaces.resources import SnapshotState
  54. from cloudbridge.cloud.interfaces.resources import SubnetState
  55. from cloudbridge.cloud.interfaces.resources import TrafficDirection
  56. from cloudbridge.cloud.interfaces.resources import VolumeState
  57. from cloudbridge.cloud.providers.openstack import helpers as oshelpers
  58. ONE_GIG = 1048576000 # in bytes
  59. FIVE_GIG = ONE_GIG * 5 # in bytes
  60. log = logging.getLogger(__name__)
  61. class OpenStackMachineImage(BaseMachineImage):
  62. # ref: http://docs.openstack.org/developer/glance/statuses.html
  63. IMAGE_STATE_MAP = {
  64. 'queued': MachineImageState.PENDING,
  65. 'saving': MachineImageState.PENDING,
  66. 'active': MachineImageState.AVAILABLE,
  67. 'killed': MachineImageState.ERROR,
  68. 'deleted': MachineImageState.UNKNOWN,
  69. 'pending_delete': MachineImageState.PENDING,
  70. 'deactivated': MachineImageState.ERROR
  71. }
  72. def __init__(self, provider, os_image):
  73. super(OpenStackMachineImage, self).__init__(provider)
  74. if isinstance(os_image, OpenStackMachineImage):
  75. # pylint:disable=protected-access
  76. self._os_image = os_image._os_image
  77. else:
  78. self._os_image = os_image
  79. @property
  80. def id(self):
  81. """
  82. Get the image identifier.
  83. """
  84. return self._os_image.id
  85. @property
  86. def name(self):
  87. """
  88. Get the image identifier.
  89. """
  90. return self._os_image.id
  91. @property
  92. def label(self):
  93. """
  94. Get the image label.
  95. """
  96. return self._os_image.name
  97. @label.setter
  98. # pylint:disable=arguments-differ
  99. def label(self, value):
  100. """
  101. Set the image label.
  102. """
  103. self.assert_valid_resource_label(value)
  104. self._provider.os_conn.image.update_image(
  105. self._os_image, name=value or "")
  106. @property
  107. def description(self):
  108. """
  109. Get the image description.
  110. """
  111. return None
  112. @property
  113. def min_disk(self):
  114. """
  115. Returns the minimum size of the disk that's required to
  116. boot this image (in GB)
  117. :rtype: ``int``
  118. :return: The minimum disk size needed by this image
  119. """
  120. return self._os_image.min_disk
  121. def delete(self):
  122. """
  123. Delete this image
  124. """
  125. self._os_image.delete(self._provider.os_conn.session)
  126. @property
  127. def state(self):
  128. return OpenStackMachineImage.IMAGE_STATE_MAP.get(
  129. self._os_image.status, MachineImageState.UNKNOWN)
  130. def refresh(self):
  131. """
  132. Refreshes the state of this instance by re-querying the cloud provider
  133. for its latest state.
  134. """
  135. log.debug("Refreshing OpenStack Machine Image")
  136. image = self._provider.compute.images.get(self.id)
  137. if image:
  138. self._os_image = image._os_image # pylint:disable=protected-access
  139. else:
  140. # The image no longer exists and cannot be refreshed.
  141. # set the status to unknown
  142. self._os_image.status = 'unknown'
  143. class OpenStackPlacementZone(BasePlacementZone):
  144. def __init__(self, provider, zone, region):
  145. super(OpenStackPlacementZone, self).__init__(provider)
  146. if isinstance(zone, OpenStackPlacementZone):
  147. # pylint:disable=protected-access
  148. self._os_zone = zone._os_zone
  149. # pylint:disable=protected-access
  150. self._os_region = zone._os_region
  151. else:
  152. self._os_zone = zone
  153. self._os_region = region
  154. @property
  155. def id(self):
  156. """
  157. Get the zone id
  158. :rtype: ``str``
  159. :return: ID for this zone as returned by the cloud middleware.
  160. """
  161. return self._os_zone
  162. @property
  163. def name(self):
  164. """
  165. Get the zone name.
  166. :rtype: ``str``
  167. :return: Name for this zone as returned by the cloud middleware.
  168. """
  169. # return self._os_zone.zoneName
  170. return self._os_zone
  171. @property
  172. def region_name(self):
  173. """
  174. Get the region that this zone belongs to.
  175. :rtype: ``str``
  176. :return: Name of this zone's region as returned by the cloud middleware
  177. """
  178. return self._os_region
  179. class OpenStackVMType(BaseVMType):
  180. def __init__(self, provider, os_flavor):
  181. super(OpenStackVMType, self).__init__(provider)
  182. self._os_flavor = os_flavor
  183. @property
  184. def id(self):
  185. return self._os_flavor.id
  186. @property
  187. def name(self):
  188. return self._os_flavor.name
  189. @property
  190. def family(self):
  191. # TODO: This may not be standardised across OpenStack
  192. # but NeCTAR is using it this way
  193. return self.extra_data.get('flavor_class:name')
  194. @property
  195. def vcpus(self):
  196. return self._os_flavor.vcpus
  197. @property
  198. def ram(self):
  199. return int(self._os_flavor.ram) / 1024
  200. @property
  201. def size_root_disk(self):
  202. return self._os_flavor.disk
  203. @property
  204. def size_ephemeral_disks(self):
  205. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  206. self._os_flavor.ephemeral
  207. @property
  208. def num_ephemeral_disks(self):
  209. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  210. self._os_flavor.ephemeral
  211. @property
  212. def extra_data(self):
  213. extras = self._os_flavor.get_keys()
  214. extras['rxtx_factor'] = self._os_flavor.rxtx_factor
  215. extras['swap'] = self._os_flavor.swap
  216. extras['is_public'] = self._os_flavor.is_public
  217. return extras
  218. class OpenStackInstance(BaseInstance):
  219. # ref: http://docs.openstack.org/developer/nova/v2/2.0_server_concepts.html
  220. # and http://developer.openstack.org/api-ref-compute-v2.html
  221. INSTANCE_STATE_MAP = {
  222. 'ACTIVE': InstanceState.RUNNING,
  223. 'BUILD': InstanceState.PENDING,
  224. 'DELETED': InstanceState.DELETED,
  225. 'ERROR': InstanceState.ERROR,
  226. 'HARD_REBOOT': InstanceState.REBOOTING,
  227. 'PASSWORD': InstanceState.PENDING,
  228. 'PAUSED': InstanceState.STOPPED,
  229. 'REBOOT': InstanceState.REBOOTING,
  230. 'REBUILD': InstanceState.CONFIGURING,
  231. 'RESCUE': InstanceState.CONFIGURING,
  232. 'RESIZE': InstanceState.CONFIGURING,
  233. 'REVERT_RESIZE': InstanceState.CONFIGURING,
  234. 'SOFT_DELETED': InstanceState.STOPPED,
  235. 'STOPPED': InstanceState.STOPPED,
  236. 'SUSPENDED': InstanceState.STOPPED,
  237. 'SHUTOFF': InstanceState.STOPPED,
  238. 'UNKNOWN': InstanceState.UNKNOWN,
  239. 'VERIFY_RESIZE': InstanceState.CONFIGURING
  240. }
  241. def __init__(self, provider, os_instance):
  242. super(OpenStackInstance, self).__init__(provider)
  243. self._os_instance = os_instance
  244. @property
  245. def id(self):
  246. """
  247. Get the instance identifier.
  248. """
  249. return self._os_instance.id
  250. @property
  251. def name(self):
  252. """
  253. Get the instance identifier.
  254. """
  255. return self.id
  256. @property
  257. # pylint:disable=arguments-differ
  258. def label(self):
  259. """
  260. Get the instance label.
  261. """
  262. return self._os_instance.name
  263. @label.setter
  264. # pylint:disable=arguments-differ
  265. def label(self, value):
  266. """
  267. Set the instance label.
  268. """
  269. self.assert_valid_resource_label(value)
  270. self._os_instance.name = value
  271. self._os_instance.update(name=value or "cb-inst")
  272. @property
  273. def public_ips(self):
  274. """
  275. Get all the public IP addresses for this instance.
  276. """
  277. # OpenStack doesn't provide an easy way to figure our whether an IP is
  278. # public or private, since the returned IPs are grouped by an arbitrary
  279. # network label. Therefore, it's necessary to parse the address and
  280. # determine whether it's public or private
  281. return [address
  282. for _, addresses in self._os_instance.networks.items()
  283. for address in addresses
  284. if not ipaddress.ip_address(address).is_private]
  285. @property
  286. def private_ips(self):
  287. """
  288. Get all the private IP addresses for this instance.
  289. """
  290. return [address
  291. for _, addresses in self._os_instance.networks.items()
  292. for address in addresses
  293. if ipaddress.ip_address(address).is_private]
  294. @property
  295. def vm_type_id(self):
  296. """
  297. Get the VM type name.
  298. """
  299. return self._os_instance.flavor.get('id')
  300. @property
  301. def vm_type(self):
  302. """
  303. Get the VM type object.
  304. """
  305. flavor = self._provider.nova.flavors.get(
  306. self._os_instance.flavor.get('id'))
  307. return OpenStackVMType(self._provider, flavor)
  308. def reboot(self):
  309. """
  310. Reboot this instance (using the cloud middleware API).
  311. """
  312. self._os_instance.reboot()
  313. def delete(self):
  314. """
  315. Permanently delete this instance.
  316. """
  317. # delete the port we created when launching
  318. # Assumption: it's the first interface in the list
  319. iface_list = self._os_instance.interface_list()
  320. if iface_list:
  321. self._provider.neutron.delete_port(iface_list[0].port_id)
  322. self._os_instance.delete()
  323. @property
  324. def image_id(self):
  325. """
  326. Get the image ID for this instance.
  327. """
  328. # In OpenStack, the Machine Image of a running instance may
  329. # be deleted, so make sure the image exists before attempting to
  330. # retrieve its id
  331. return (self._os_instance.image.get("id")
  332. if self._os_instance.image else "")
  333. @property
  334. def zone_id(self):
  335. """
  336. Get the placement zone where this instance is running.
  337. """
  338. return getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None)
  339. @property
  340. def subnet_id(self):
  341. """
  342. Extract (one) subnet id associated with this instance.
  343. In OpenStack, instances are associated with ports instead of
  344. subnets so we need to dig through several connections to retrieve
  345. the subnet_id. Further, there can potentially be several ports each
  346. connected to different subnets. This implementation retrieves one
  347. subnet, the one corresponding to port associated with the first
  348. private IP associated with the instance.
  349. """
  350. # MAC address can be used to identify a port so extract the MAC
  351. # address corresponding to the (first) private IP associated with the
  352. # instance.
  353. for net in self._os_instance.to_dict().get('addresses').keys():
  354. for iface in self._os_instance.to_dict().get('addresses')[net]:
  355. if iface.get('OS-EXT-IPS:type') == 'fixed':
  356. port = iface.get('OS-EXT-IPS-MAC:mac_addr')
  357. addr = iface.get('addr')
  358. break
  359. # Now get a handle to a port with the given MAC address and get the
  360. # subnet to which the private IP is connected as the desired id.
  361. for prt in self._provider.neutron.list_ports().get('ports'):
  362. if prt.get('mac_address') == port:
  363. for ip in prt.get('fixed_ips'):
  364. if ip.get('ip_address') == addr:
  365. return ip.get('subnet_id')
  366. @property
  367. def vm_firewalls(self):
  368. return [
  369. self._provider.security.vm_firewalls.get(group.id)
  370. for group in self._os_instance.list_security_group()
  371. ]
  372. @property
  373. def vm_firewall_ids(self):
  374. """
  375. Get the VM firewall IDs associated with this instance.
  376. """
  377. return [fw.id for fw in self.vm_firewalls]
  378. @property
  379. def key_pair_id(self):
  380. """
  381. Get the id of the key pair associated with this instance.
  382. """
  383. return self._os_instance.key_name
  384. def create_image(self, label):
  385. """
  386. Create a new image based on this instance.
  387. """
  388. log.debug("Creating OpenStack Image with the label %s", label)
  389. self.assert_valid_resource_label(label)
  390. image_id = self._os_instance.create_image(label)
  391. img = OpenStackMachineImage(
  392. self._provider, self._provider.compute.images.get(image_id))
  393. return img
  394. def _get_fip(self, floating_ip):
  395. """Get a floating IP object based on the supplied ID."""
  396. return OpenStackFloatingIP(
  397. self._provider,
  398. self._provider.os_conn.network.get_ip(floating_ip))
  399. def add_floating_ip(self, floating_ip):
  400. """
  401. Add a floating IP address to this instance.
  402. """
  403. log.debug("Adding floating IP adress: %s", floating_ip)
  404. fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
  405. else self._get_fip(floating_ip))
  406. self._provider.os_conn.compute.add_floating_ip_to_server(
  407. self.id, fip.public_ip)
  408. def remove_floating_ip(self, floating_ip):
  409. """
  410. Remove a floating IP address from this instance.
  411. """
  412. log.debug("Removing floating IP adress: %s", floating_ip)
  413. fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
  414. else self._get_fip(floating_ip))
  415. self._provider.os_conn.compute.remove_floating_ip_from_server(
  416. self.id, fip.public_ip)
  417. def add_vm_firewall(self, firewall):
  418. """
  419. Add a VM firewall to this instance
  420. """
  421. log.debug("Adding firewall: %s", firewall)
  422. self._os_instance.add_security_group(firewall.id)
  423. def remove_vm_firewall(self, firewall):
  424. """
  425. Remove a VM firewall from this instance
  426. """
  427. log.debug("Removing firewall: %s", firewall)
  428. self._os_instance.remove_security_group(firewall.id)
  429. @property
  430. def state(self):
  431. return OpenStackInstance.INSTANCE_STATE_MAP.get(
  432. self._os_instance.status, InstanceState.UNKNOWN)
  433. def refresh(self):
  434. """
  435. Refreshes the state of this instance by re-querying the cloud provider
  436. for its latest state.
  437. """
  438. instance = self._provider.compute.instances.get(
  439. self.id)
  440. if instance:
  441. # pylint:disable=protected-access
  442. self._os_instance = instance._os_instance
  443. else:
  444. # The instance no longer exists and cannot be refreshed.
  445. # set the status to unknown
  446. self._os_instance.status = 'unknown'
  447. class OpenStackRegion(BaseRegion):
  448. def __init__(self, provider, os_region):
  449. super(OpenStackRegion, self).__init__(provider)
  450. self._os_region = os_region
  451. @property
  452. def id(self):
  453. return (self._os_region.id if type(self._os_region) == Region else
  454. self._os_region)
  455. @property
  456. def name(self):
  457. return self.id
  458. @property
  459. def zones(self):
  460. # ``detailed`` param must be set to ``False`` because the (default)
  461. # ``True`` value requires Admin privileges
  462. if self.name == self._provider.region_name: # optimisation
  463. zones = self._provider.nova.availability_zones.list(detailed=False)
  464. else:
  465. try:
  466. # pylint:disable=protected-access
  467. region_nova = self._provider._connect_nova_region(self.name)
  468. zones = region_nova.availability_zones.list(detailed=False)
  469. except novaex.EndpointNotFound:
  470. # This region may not have a compute endpoint. If so just
  471. # return an empty list
  472. zones = []
  473. return [OpenStackPlacementZone(self._provider, z.zoneName, self.name)
  474. for z in zones]
  475. class OpenStackVolume(BaseVolume):
  476. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  477. VOLUME_STATE_MAP = {
  478. 'creating': VolumeState.CREATING,
  479. 'available': VolumeState.AVAILABLE,
  480. 'attaching': VolumeState.CONFIGURING,
  481. 'in-use': VolumeState.IN_USE,
  482. 'deleting': VolumeState.CONFIGURING,
  483. 'error': VolumeState.ERROR,
  484. 'error_deleting': VolumeState.ERROR,
  485. 'backing-up': VolumeState.CONFIGURING,
  486. 'restoring-backup': VolumeState.CONFIGURING,
  487. 'error_restoring': VolumeState.ERROR,
  488. 'error_extending': VolumeState.ERROR
  489. }
  490. def __init__(self, provider, volume):
  491. super(OpenStackVolume, self).__init__(provider)
  492. self._volume = volume
  493. @property
  494. def id(self):
  495. return self._volume.id
  496. @property
  497. def name(self):
  498. return self.id
  499. @property
  500. # pylint:disable=arguments-differ
  501. def label(self):
  502. """
  503. Get the volume label.
  504. """
  505. return self._volume.name
  506. @label.setter
  507. # pylint:disable=arguments-differ
  508. def label(self, value):
  509. """
  510. Set the volume label.
  511. """
  512. self.assert_valid_resource_label(value)
  513. self._volume.name = value
  514. self._volume.update(name=value or "")
  515. @property
  516. def description(self):
  517. return self._volume.description
  518. @description.setter
  519. def description(self, value):
  520. self._volume.description = value
  521. self._volume.update(description=value)
  522. @property
  523. def size(self):
  524. return self._volume.size
  525. @property
  526. def create_time(self):
  527. return self._volume.created_at
  528. @property
  529. def zone_id(self):
  530. return self._volume.availability_zone
  531. @property
  532. def source(self):
  533. if self._volume.snapshot_id:
  534. return self._provider.storage.snapshots.get(
  535. self._volume.snapshot_id)
  536. return None
  537. @property
  538. def attachments(self):
  539. if self._volume.attachments:
  540. return BaseAttachmentInfo(
  541. self,
  542. self._volume.attachments[0].get('server_id'),
  543. self._volume.attachments[0].get('device'))
  544. else:
  545. return None
  546. def attach(self, instance, device):
  547. """
  548. Attach this volume to an instance.
  549. """
  550. log.debug("Attaching %s to %s instance", device, instance)
  551. instance_id = instance.id if isinstance(
  552. instance,
  553. OpenStackInstance) else instance
  554. self._volume.attach(instance_id, device)
  555. def detach(self, force=False):
  556. """
  557. Detach this volume from an instance.
  558. """
  559. self._volume.detach()
  560. def create_snapshot(self, label, description=None):
  561. """
  562. Create a snapshot of this Volume.
  563. """
  564. log.debug("Creating snapchat of volume: %s with the "
  565. "description: %s", label, description)
  566. return self._provider.storage.snapshots.create(
  567. label, self, description=description)
  568. def delete(self):
  569. """
  570. Delete this volume.
  571. """
  572. self._volume.delete()
  573. @property
  574. def state(self):
  575. return OpenStackVolume.VOLUME_STATE_MAP.get(
  576. self._volume.status, VolumeState.UNKNOWN)
  577. def refresh(self):
  578. """
  579. Refreshes the state of this volume by re-querying the cloud provider
  580. for its latest state.
  581. """
  582. vol = self._provider.storage.volumes.get(
  583. self.id)
  584. if vol:
  585. self._volume = vol._volume # pylint:disable=protected-access
  586. else:
  587. # The volume no longer exists and cannot be refreshed.
  588. # set the status to unknown
  589. self._volume.status = 'unknown'
  590. class OpenStackSnapshot(BaseSnapshot):
  591. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  592. SNAPSHOT_STATE_MAP = {
  593. 'creating': SnapshotState.PENDING,
  594. 'available': SnapshotState.AVAILABLE,
  595. 'deleting': SnapshotState.CONFIGURING,
  596. 'error': SnapshotState.ERROR,
  597. 'error_deleting': SnapshotState.ERROR
  598. }
  599. def __init__(self, provider, snapshot):
  600. super(OpenStackSnapshot, self).__init__(provider)
  601. self._snapshot = snapshot
  602. @property
  603. def id(self):
  604. return self._snapshot.id
  605. @property
  606. def name(self):
  607. return self.id
  608. @property
  609. # pylint:disable=arguments-differ
  610. def label(self):
  611. """
  612. Get the snapshot label.
  613. """
  614. return self._snapshot.name
  615. @label.setter
  616. # pylint:disable=arguments-differ
  617. def label(self, value):
  618. """
  619. Set the snapshot label.
  620. """
  621. self.assert_valid_resource_label(value)
  622. self._snapshot.name = value
  623. self._snapshot.update(name=value or "")
  624. @property
  625. def description(self):
  626. return self._snapshot.description
  627. @description.setter
  628. def description(self, value):
  629. self._snapshot.description = value
  630. self._snapshot.update(description=value)
  631. @property
  632. def size(self):
  633. return self._snapshot.size
  634. @property
  635. def volume_id(self):
  636. return self._snapshot.volume_id
  637. @property
  638. def create_time(self):
  639. return self._snapshot.created_at
  640. @property
  641. def state(self):
  642. return OpenStackSnapshot.SNAPSHOT_STATE_MAP.get(
  643. self._snapshot.status, SnapshotState.UNKNOWN)
  644. def refresh(self):
  645. """
  646. Refreshes the state of this snapshot by re-querying the cloud provider
  647. for its latest state.
  648. """
  649. snap = self._provider.storage.snapshots.get(
  650. self.id)
  651. if snap:
  652. self._snapshot = snap._snapshot # pylint:disable=protected-access
  653. else:
  654. # The snapshot no longer exists and cannot be refreshed.
  655. # set the status to unknown
  656. self._snapshot.status = 'unknown'
  657. def delete(self):
  658. """
  659. Delete this snapshot.
  660. """
  661. self._snapshot.delete()
  662. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  663. """
  664. Create a new Volume from this Snapshot.
  665. """
  666. vol_label = "from-snap-{0}".format(self.id or self.label)
  667. self.assert_valid_resource_label(vol_label)
  668. size = size if size else self._snapshot.size
  669. os_vol = self._provider.cinder.volumes.create(
  670. size, name=vol_label, availability_zone=placement,
  671. snapshot_id=self._snapshot.id)
  672. cb_vol = OpenStackVolume(self._provider, os_vol)
  673. return cb_vol
  674. class OpenStackGatewayContainer(BaseGatewayContainer):
  675. """For OpenStack, an internet gateway is a just an 'external' network."""
  676. def __init__(self, provider, network):
  677. super(OpenStackGatewayContainer, self).__init__(provider, network)
  678. def _check_fip_connectivity(self, external_net):
  679. # Due to current limitations in OpenStack:
  680. # https://bugs.launchpad.net/neutron/+bug/1743480, it's not
  681. # possible to differentiate between floating ip networks and provider
  682. # external networks. Therefore, we systematically step through
  683. # all available networks and perform an assignment test to infer valid
  684. # floating ip nets.
  685. dummy_router = self._provider.networking.routers.create(
  686. label='cb-conn-test-router', network=self._network)
  687. with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
  688. try:
  689. dummy_router.attach_gateway(external_net)
  690. return True
  691. except Exception:
  692. return False
  693. def get_or_create_inet_gateway(self):
  694. """For OS, inet gtw is any net that has `external` property set."""
  695. external_nets = (n for n in self._provider.networking.networks
  696. if n.external)
  697. for net in external_nets:
  698. if self._check_fip_connectivity(net):
  699. return OpenStackInternetGateway(self._provider, net)
  700. return None
  701. def delete(self, gateway):
  702. log.debug("Deleting OpenStack Gateway: %s", gateway)
  703. gateway.delete()
  704. def list(self, limit=None, marker=None):
  705. log.debug("OpenStack listing of all current internet gateways")
  706. igl = [OpenStackInternetGateway(self._provider, n)
  707. for n in self._provider.networking.networks
  708. if n.external and self._check_fip_connectivity(n)]
  709. return ClientPagedResultList(self._provider, igl, limit=limit,
  710. marker=marker)
  711. class OpenStackNetwork(BaseNetwork):
  712. # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
  713. # common/constants.py
  714. _NETWORK_STATE_MAP = {
  715. 'PENDING_CREATE': NetworkState.PENDING,
  716. 'PENDING_UPDATE': NetworkState.PENDING,
  717. 'PENDING_DELETE': NetworkState.PENDING,
  718. 'CREATED': NetworkState.PENDING,
  719. 'INACTIVE': NetworkState.PENDING,
  720. 'DOWN': NetworkState.DOWN,
  721. 'ERROR': NetworkState.ERROR,
  722. 'ACTIVE': NetworkState.AVAILABLE
  723. }
  724. def __init__(self, provider, network):
  725. super(OpenStackNetwork, self).__init__(provider)
  726. self._network = network
  727. self._gateway_service = OpenStackGatewayContainer(provider, self)
  728. @property
  729. def id(self):
  730. return self._network.get('id', None)
  731. @property
  732. def name(self):
  733. return self.id
  734. @property
  735. def label(self):
  736. return self._network.get('name', None)
  737. @label.setter
  738. def label(self, value): # pylint:disable=arguments-differ
  739. """
  740. Set the network label.
  741. """
  742. self.assert_valid_resource_label(value)
  743. self._provider.neutron.update_network(
  744. self.id, {'network': {'name': value or ""}})
  745. self.refresh()
  746. @property
  747. def external(self):
  748. return self._network.get('router:external', False)
  749. @property
  750. def state(self):
  751. self.refresh()
  752. return OpenStackNetwork._NETWORK_STATE_MAP.get(
  753. self._network.get('status', None),
  754. NetworkState.UNKNOWN)
  755. @property
  756. def cidr_block(self):
  757. # OpenStack does not define a CIDR block for networks
  758. return ''
  759. def delete(self):
  760. if not self.external and self.id in str(
  761. self._provider.neutron.list_networks()):
  762. # If there are ports associated with the network, it won't delete
  763. ports = self._provider.neutron.list_ports(
  764. network_id=self.id).get('ports', [])
  765. for port in ports:
  766. try:
  767. self._provider.neutron.delete_port(port.get('id'))
  768. except PortNotFoundClient:
  769. # Ports could have already been deleted if instances
  770. # are terminated etc. so exceptions can be safely ignored
  771. pass
  772. self._provider.neutron.delete_network(self.id)
  773. @property
  774. def subnets(self):
  775. subnets = (self._provider.neutron.list_subnets(network_id=self.id)
  776. .get('subnets', []))
  777. return [OpenStackSubnet(self._provider, subnet) for subnet in subnets]
  778. def refresh(self):
  779. """Refresh the state of this network by re-querying the provider."""
  780. network = self._provider.networking.networks.get(self.id)
  781. if network:
  782. # pylint:disable=protected-access
  783. self._network = network._network
  784. else:
  785. # subnet no longer exists
  786. self._network.state = NetworkState.UNKNOWN
  787. @property
  788. def gateways(self):
  789. return self._gateway_service
  790. class OpenStackSubnet(BaseSubnet):
  791. def __init__(self, provider, subnet):
  792. super(OpenStackSubnet, self).__init__(provider)
  793. self._subnet = subnet
  794. self._state = None
  795. @property
  796. def id(self):
  797. return self._subnet.get('id', None)
  798. @property
  799. def name(self):
  800. return self.id
  801. @property
  802. def label(self):
  803. return self._subnet.get('name', None)
  804. @label.setter
  805. def label(self, value): # pylint:disable=arguments-differ
  806. """
  807. Set the subnet label.
  808. """
  809. self.assert_valid_resource_label(value)
  810. self._provider.neutron.update_subnet(
  811. self.id, {'subnet': {'name': value or ""}})
  812. self._subnet['name'] = value
  813. @property
  814. def cidr_block(self):
  815. return self._subnet.get('cidr', None)
  816. @property
  817. def network_id(self):
  818. return self._subnet.get('network_id', None)
  819. @property
  820. def zone(self):
  821. """
  822. OpenStack does not have a notion of placement zone for subnets.
  823. Default to None.
  824. """
  825. return None
  826. def delete(self):
  827. if self.id in str(self._provider.neutron.list_subnets()):
  828. self._provider.neutron.delete_subnet(self.id)
  829. @property
  830. def state(self):
  831. return SubnetState.UNKNOWN if self._state == SubnetState.UNKNOWN \
  832. else SubnetState.AVAILABLE
  833. def refresh(self):
  834. subnet = self._provider.networking.subnets.get(self.id)
  835. if subnet:
  836. # pylint:disable=protected-access
  837. self._subnet = subnet._subnet
  838. self._state = SubnetState.AVAILABLE
  839. else:
  840. # subnet no longer exists
  841. self._state = SubnetState.UNKNOWN
  842. class OpenStackFloatingIPContainer(BaseFloatingIPContainer):
  843. def __init__(self, provider, gateway):
  844. super(OpenStackFloatingIPContainer, self).__init__(provider, gateway)
  845. def get(self, fip_id):
  846. try:
  847. return OpenStackFloatingIP(
  848. self._provider, self._provider.os_conn.network.get_ip(fip_id))
  849. except (ResourceNotFound, NotFoundException):
  850. log.debug("Floating IP %s not found.", fip_id)
  851. return None
  852. def list(self, limit=None, marker=None):
  853. fips = [OpenStackFloatingIP(self._provider, fip)
  854. for fip in self._provider.os_conn.network.ips(
  855. floating_network_id=self.gateway.id
  856. )]
  857. return ClientPagedResultList(self._provider, fips,
  858. limit=limit, marker=marker)
  859. def create(self):
  860. return OpenStackFloatingIP(
  861. self._provider, self._provider.os_conn.network.create_ip(
  862. floating_network_id=self.gateway.id))
  863. class OpenStackFloatingIP(BaseFloatingIP):
  864. def __init__(self, provider, floating_ip):
  865. super(OpenStackFloatingIP, self).__init__(provider)
  866. self._ip = floating_ip
  867. @property
  868. def id(self):
  869. return self._ip.id
  870. @property
  871. def public_ip(self):
  872. return self._ip.floating_ip_address
  873. @property
  874. def private_ip(self):
  875. return self._ip.fixed_ip_address
  876. @property
  877. def in_use(self):
  878. return bool(self._ip.port_id)
  879. def delete(self):
  880. self._ip.delete(self._provider.os_conn.session)
  881. def refresh(self):
  882. net = self._provider.networking.networks.get(
  883. self._ip.floating_network_id)
  884. gw = net.gateways.get_or_create_inet_gateway()
  885. fip = gw.floating_ips.get(self.id)
  886. # pylint:disable=protected-access
  887. self._ip = fip._ip
  888. class OpenStackRouter(BaseRouter):
  889. def __init__(self, provider, router):
  890. super(OpenStackRouter, self).__init__(provider)
  891. self._router = router
  892. @property
  893. def id(self):
  894. return self._router.get('id', None)
  895. @property
  896. def name(self):
  897. return self.id
  898. @property
  899. def label(self):
  900. return self._router.get('name', None)
  901. @label.setter
  902. def label(self, value): # pylint:disable=arguments-differ
  903. """
  904. Set the router label.
  905. """
  906. self.assert_valid_resource_label(value)
  907. self._provider.neutron.update_router(
  908. self.id, {'router': {'name': value or ""}})
  909. self.refresh()
  910. def refresh(self):
  911. self._router = self._provider.neutron.show_router(self.id)['router']
  912. @property
  913. def state(self):
  914. if self._router.get('external_gateway_info'):
  915. return RouterState.ATTACHED
  916. return RouterState.DETACHED
  917. @property
  918. def network_id(self):
  919. if self.state == RouterState.ATTACHED:
  920. return self._router.get('external_gateway_info', {}).get(
  921. 'network_id', None)
  922. return None
  923. def delete(self):
  924. self._provider.neutron.delete_router(self.id)
  925. def attach_subnet(self, subnet):
  926. router_interface = {'subnet_id': subnet.id}
  927. ret = self._provider.neutron.add_interface_router(
  928. self.id, router_interface)
  929. if subnet.id in ret.get('subnet_ids', ""):
  930. return True
  931. return False
  932. def detach_subnet(self, subnet):
  933. router_interface = {'subnet_id': subnet.id}
  934. ret = self._provider.neutron.remove_interface_router(
  935. self.id, router_interface)
  936. if subnet.id in ret.get('subnet_ids', ""):
  937. return True
  938. return False
  939. @property
  940. def subnets(self):
  941. # A router and a subnet are linked via a port, so traverse all ports
  942. # to find a list of subnets associated with the current router.
  943. subnets = []
  944. for prt in self._provider.neutron.list_ports().get('ports'):
  945. if prt.get('device_id') == self.id and \
  946. prt.get('device_owner') == 'network:router_interface':
  947. for fixed_ip in prt.get('fixed_ips'):
  948. subnets.append(self._provider.networking.subnets.get(
  949. fixed_ip.get('subnet_id')))
  950. return subnets
  951. def attach_gateway(self, gateway):
  952. self._provider.neutron.add_gateway_router(
  953. self.id, {'network_id': gateway.id})
  954. def detach_gateway(self, gateway):
  955. self._provider.neutron.remove_gateway_router(
  956. self.id).get('router', self._router)
  957. class OpenStackInternetGateway(BaseInternetGateway):
  958. GATEWAY_STATE_MAP = {
  959. NetworkState.AVAILABLE: GatewayState.AVAILABLE,
  960. NetworkState.DOWN: GatewayState.ERROR,
  961. NetworkState.ERROR: GatewayState.ERROR,
  962. NetworkState.PENDING: GatewayState.CONFIGURING,
  963. NetworkState.UNKNOWN: GatewayState.UNKNOWN
  964. }
  965. def __init__(self, provider, gateway_net):
  966. super(OpenStackInternetGateway, self).__init__(provider)
  967. if isinstance(gateway_net, OpenStackNetwork):
  968. # pylint:disable=protected-access
  969. gateway_net = gateway_net._network
  970. self._gateway_net = gateway_net
  971. self._fips_container = OpenStackFloatingIPContainer(provider, self)
  972. @property
  973. def id(self):
  974. return self._gateway_net.get('id', None)
  975. @property
  976. def name(self):
  977. return self._gateway_net.get('name', None)
  978. @property
  979. def network_id(self):
  980. return self._gateway_net.get('id')
  981. def refresh(self):
  982. """Refresh the state of this network by re-querying the provider."""
  983. network = self._provider.networking.networks.get(self.id)
  984. if network:
  985. # pylint:disable=protected-access
  986. self._gateway_net = network._network
  987. else:
  988. # subnet no longer exists
  989. self._gateway_net.state = NetworkState.UNKNOWN
  990. @property
  991. def state(self):
  992. return self.GATEWAY_STATE_MAP.get(
  993. self._gateway_net.state, GatewayState.UNKNOWN)
  994. def delete(self):
  995. """Do nothing on openstack"""
  996. pass
  997. @property
  998. def floating_ips(self):
  999. return self._fips_container
  1000. class OpenStackKeyPair(BaseKeyPair):
  1001. def __init__(self, provider, key_pair):
  1002. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  1003. class OpenStackVMFirewall(BaseVMFirewall):
  1004. _network_id_tag = "CB-AUTO-associated-network-id: "
  1005. def __init__(self, provider, vm_firewall):
  1006. super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
  1007. self._rule_svc = OpenStackVMFirewallRuleContainer(provider, self)
  1008. @property
  1009. def network_id(self):
  1010. """
  1011. OpenStack does not associate a SG with a network so default to None.
  1012. :return: Always return ``None``.
  1013. """
  1014. # Best way would be to use regex, but using this hacky way to avoid
  1015. # importing the re package
  1016. net_id = self._description\
  1017. .split(" [{}".format(self._network_id_tag))[-1]\
  1018. .split(']')[0]
  1019. # We generally mandate a network to be associated with a firewall,
  1020. # however because of some networking specificity in Nectar, we must
  1021. # allow None value as well, which will parse here as an empty string
  1022. if not net_id:
  1023. return None
  1024. return net_id
  1025. @property
  1026. def _description(self):
  1027. return self._vm_firewall.description or ""
  1028. @property
  1029. def description(self):
  1030. desc_fragment = " [{}{}]".format(self._network_id_tag,
  1031. self.network_id)
  1032. desc = self._description
  1033. if desc:
  1034. return desc.replace(desc_fragment, "")
  1035. else:
  1036. return None
  1037. @property
  1038. def name(self):
  1039. """
  1040. Return the name of this VM firewall.
  1041. """
  1042. return self.id
  1043. @property
  1044. def label(self):
  1045. return self._vm_firewall.name
  1046. @label.setter
  1047. # pylint:disable=arguments-differ
  1048. def label(self, value):
  1049. self.assert_valid_resource_label(value)
  1050. self._provider.os_conn.network.update_security_group(
  1051. self.id, name=value or "")
  1052. self.refresh()
  1053. @property
  1054. def rules(self):
  1055. return self._rule_svc
  1056. def delete(self):
  1057. return self._vm_firewall.delete(self._provider.os_conn.session)
  1058. def refresh(self):
  1059. self._vm_firewall = self._provider.os_conn.network.get_security_group(
  1060. self.id)
  1061. def to_json(self):
  1062. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  1063. js = {k: v for(k, v) in attr if not k.startswith('_')}
  1064. json_rules = [r.to_json() for r in self.rules]
  1065. js['rules'] = json_rules
  1066. return js
  1067. class OpenStackVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
  1068. def __init__(self, provider, firewall):
  1069. super(OpenStackVMFirewallRuleContainer, self).__init__(
  1070. provider, firewall)
  1071. def list(self, limit=None, marker=None):
  1072. # pylint:disable=protected-access
  1073. rules = [OpenStackVMFirewallRule(self.firewall, r)
  1074. for r in self.firewall._vm_firewall.security_group_rules]
  1075. return ClientPagedResultList(self._provider, rules,
  1076. limit=limit, marker=marker)
  1077. def create(self, direction, protocol=None, from_port=None,
  1078. to_port=None, cidr=None, src_dest_fw=None):
  1079. src_dest_fw_id = (src_dest_fw.id if isinstance(src_dest_fw,
  1080. OpenStackVMFirewall)
  1081. else src_dest_fw)
  1082. try:
  1083. if direction == TrafficDirection.INBOUND:
  1084. os_direction = 'ingress'
  1085. elif direction == TrafficDirection.OUTBOUND:
  1086. os_direction = 'egress'
  1087. else:
  1088. raise InvalidValueException("direction", direction)
  1089. # pylint:disable=protected-access
  1090. rule = self._provider.os_conn.network.create_security_group_rule(
  1091. security_group_id=self.firewall.id,
  1092. direction=os_direction,
  1093. port_range_max=to_port,
  1094. port_range_min=from_port,
  1095. protocol=protocol,
  1096. remote_ip_prefix=cidr,
  1097. remote_group_id=src_dest_fw_id)
  1098. self.firewall.refresh()
  1099. return OpenStackVMFirewallRule(self.firewall, rule.to_dict())
  1100. except HttpException as e:
  1101. self.firewall.refresh()
  1102. # 409=Conflict, raised for duplicate rule
  1103. if e.status_code == 409:
  1104. existing = self.find(direction=direction, protocol=protocol,
  1105. from_port=from_port, to_port=to_port,
  1106. cidr=cidr, src_dest_fw_id=src_dest_fw_id)
  1107. return existing[0]
  1108. else:
  1109. raise e
  1110. class OpenStackVMFirewallRule(BaseVMFirewallRule):
  1111. def __init__(self, parent_fw, rule):
  1112. super(OpenStackVMFirewallRule, self).__init__(parent_fw, rule)
  1113. @property
  1114. def id(self):
  1115. return self._rule.get('id')
  1116. @property
  1117. def direction(self):
  1118. direction = self._rule.get('direction')
  1119. if direction == 'ingress':
  1120. return TrafficDirection.INBOUND
  1121. elif direction == 'egress':
  1122. return TrafficDirection.OUTBOUND
  1123. else:
  1124. return None
  1125. @property
  1126. def protocol(self):
  1127. return self._rule.get('protocol')
  1128. @property
  1129. def from_port(self):
  1130. return self._rule.get('port_range_min')
  1131. @property
  1132. def to_port(self):
  1133. return self._rule.get('port_range_max')
  1134. @property
  1135. def cidr(self):
  1136. return self._rule.get('remote_ip_prefix')
  1137. @property
  1138. def src_dest_fw_id(self):
  1139. fw = self.src_dest_fw
  1140. if fw:
  1141. return fw.id
  1142. return None
  1143. @property
  1144. def src_dest_fw(self):
  1145. fw_id = self._rule.get('remote_group_id')
  1146. if fw_id:
  1147. return self._provider.security.vm_firewalls.get(fw_id)
  1148. return None
  1149. def delete(self):
  1150. self._provider.os_conn.network.delete_security_group_rule(self.id)
  1151. self.firewall.refresh()
  1152. class OpenStackBucketObject(BaseBucketObject):
  1153. def __init__(self, provider, cbcontainer, obj):
  1154. super(OpenStackBucketObject, self).__init__(provider)
  1155. self.cbcontainer = cbcontainer
  1156. self._obj = obj
  1157. @property
  1158. def id(self):
  1159. return self._obj.get("name")
  1160. @property
  1161. def name(self):
  1162. """Get this object's name."""
  1163. return self.id
  1164. @property
  1165. def size(self):
  1166. return self._obj.get("bytes")
  1167. @property
  1168. def last_modified(self):
  1169. return self._obj.get("last_modified")
  1170. def iter_content(self):
  1171. """Returns this object's content as an iterable."""
  1172. _, content = self._provider.swift.get_object(
  1173. self.cbcontainer.name, self.name, resp_chunk_size=65536)
  1174. return content
  1175. def upload(self, data):
  1176. """
  1177. Set the contents of this object to the data read from the source
  1178. string.
  1179. .. warning:: Will fail if the data is larger than 5 Gig.
  1180. """
  1181. self._provider.swift.put_object(self.cbcontainer.name, self.name,
  1182. data)
  1183. def upload_from_file(self, path):
  1184. """
  1185. Stores the contents of the file pointed by the ``path`` variable.
  1186. If the file is bigger than 5 Gig, it will be broken into segments.
  1187. :type path: ``str``
  1188. :param path: Absolute path to the file to be uploaded to Swift.
  1189. :rtype: ``bool``
  1190. :return: ``True`` if successful, ``False`` if not.
  1191. .. note::
  1192. * The size of the segments chosen (or any of the other upload
  1193. options) is not under user control.
  1194. * If called this method will remap the
  1195. ``swiftclient.service.get_conn`` factory method to
  1196. ``self._provider._connect_swift``
  1197. .. seealso:: https://github.com/CloudVE/cloudbridge/issues/35#issuecomment-297629661 # noqa
  1198. """
  1199. upload_options = {}
  1200. if 'segment_size' not in upload_options:
  1201. if os.path.getsize(path) >= FIVE_GIG:
  1202. upload_options['segment_size'] = FIVE_GIG
  1203. # remap the swift service's connection factory method
  1204. # pylint:disable=protected-access
  1205. swiftclient.service.get_conn = self._provider._connect_swift
  1206. result = True
  1207. with SwiftService() as swift:
  1208. upload_object = SwiftUploadObject(path, object_name=self.name)
  1209. for up_res in swift.upload(self.cbcontainer.name,
  1210. [upload_object, ],
  1211. options=upload_options):
  1212. result = result and up_res['success']
  1213. return result
  1214. def delete(self):
  1215. """
  1216. Delete this object.
  1217. :rtype: ``bool``
  1218. :return: True if successful
  1219. .. note:: If called this method will remap the
  1220. ``swiftclient.service.get_conn`` factory method to
  1221. ``self._provider._connect_swift``
  1222. """
  1223. # remap the swift service's connection factory method
  1224. # pylint:disable=protected-access
  1225. swiftclient.service.get_conn = self._provider._connect_swift
  1226. result = True
  1227. with SwiftService() as swift:
  1228. for del_res in swift.delete(self.cbcontainer.name, [self.name, ]):
  1229. result = result and del_res['success']
  1230. return result
  1231. def generate_url(self, expires_in):
  1232. # Set a temp url key on the object (http://bit.ly/2NBiXGD)
  1233. temp_url_key = "cloudbridge-tmp-url-key"
  1234. self._provider.swift.post_account(
  1235. headers={"x-account-meta-temp-url-key": temp_url_key})
  1236. base_url = urlparse(self._provider.swift.get_service_auth()[0])
  1237. access_point = "{0}://{1}".format(base_url.scheme, base_url.netloc)
  1238. url_path = "/".join([base_url.path, self.cbcontainer.name, self.name])
  1239. return urljoin(access_point, generate_temp_url(url_path, expires_in,
  1240. temp_url_key, 'GET'))
  1241. def refresh(self):
  1242. self._obj = self.cbcontainer.objects.get(self.id)._obj
  1243. class OpenStackBucket(BaseBucket):
  1244. def __init__(self, provider, bucket):
  1245. super(OpenStackBucket, self).__init__(provider)
  1246. self._bucket = bucket
  1247. self._object_container = OpenStackBucketContainer(provider, self)
  1248. @property
  1249. def id(self):
  1250. return self._bucket.get("name")
  1251. @property
  1252. def name(self):
  1253. return self.id
  1254. @property
  1255. def objects(self):
  1256. return self._object_container
  1257. def delete(self, delete_contents=False):
  1258. self._provider.swift.delete_container(self.name)
  1259. class OpenStackBucketContainer(BaseBucketContainer):
  1260. def __init__(self, provider, bucket):
  1261. super(OpenStackBucketContainer, self).__init__(provider, bucket)
  1262. def get(self, name):
  1263. """
  1264. Retrieve a given object from this bucket.
  1265. """
  1266. # Swift always returns a reference for the container first,
  1267. # followed by a list containing references to objects.
  1268. _, object_list = self._provider.swift.get_container(
  1269. self.bucket.name, prefix=name)
  1270. # Loop through list of objects looking for an exact name vs. a prefix
  1271. for obj in object_list:
  1272. if obj.get('name') == name:
  1273. return OpenStackBucketObject(self._provider,
  1274. self.bucket,
  1275. obj)
  1276. return None
  1277. def list(self, limit=None, marker=None, prefix=None):
  1278. """
  1279. List all objects within this bucket.
  1280. :rtype: BucketObject
  1281. :return: List of all available BucketObjects within this bucket.
  1282. """
  1283. _, object_list = self._provider.swift.get_container(
  1284. self.bucket.name,
  1285. limit=oshelpers.os_result_limit(self._provider, limit),
  1286. marker=marker, prefix=prefix)
  1287. cb_objects = [OpenStackBucketObject(
  1288. self._provider, self.bucket, obj) for obj in object_list]
  1289. return oshelpers.to_server_paged_list(
  1290. self._provider,
  1291. cb_objects,
  1292. limit)
  1293. def find(self, **kwargs):
  1294. obj_list = self
  1295. filters = ['name']
  1296. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  1297. return ClientPagedResultList(self._provider, list(matches))
  1298. def create(self, object_name):
  1299. self._provider.swift.put_object(self.bucket.name, object_name, None)
  1300. return self.get(object_name)