resources.py 40 KB

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