resources.py 41 KB

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