resources.py 42 KB

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