resources.py 40 KB

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