resources.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. """
  2. DataTypes used by this provider
  3. """
  4. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  5. from cloudbridge.cloud.base.resources import BaseBucket
  6. from cloudbridge.cloud.base.resources import BaseBucketObject
  7. from cloudbridge.cloud.base.resources import BaseInstance
  8. from cloudbridge.cloud.base.resources import BaseInstanceType
  9. from cloudbridge.cloud.base.resources import BaseKeyPair
  10. from cloudbridge.cloud.base.resources import BaseMachineImage
  11. from cloudbridge.cloud.base.resources import BaseNetwork
  12. from cloudbridge.cloud.base.resources import BasePlacementZone
  13. from cloudbridge.cloud.base.resources import BaseRegion
  14. from cloudbridge.cloud.base.resources import BaseSecurityGroup
  15. from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
  16. from cloudbridge.cloud.base.resources import BaseSnapshot
  17. from cloudbridge.cloud.base.resources import BaseSubnet
  18. from cloudbridge.cloud.base.resources import BaseVolume
  19. from cloudbridge.cloud.interfaces.resources import InstanceState
  20. from cloudbridge.cloud.interfaces.resources import MachineImageState
  21. from cloudbridge.cloud.interfaces.resources import NetworkState
  22. from cloudbridge.cloud.interfaces.resources import SnapshotState
  23. from cloudbridge.cloud.interfaces.resources import VolumeState
  24. from cloudbridge.cloud.providers.openstack import helpers as oshelpers
  25. import inspect
  26. import json
  27. import ipaddress
  28. import novaclient.exceptions as novaex
  29. import swiftclient.exceptions as swiftex
  30. class OpenStackMachineImage(BaseMachineImage):
  31. # ref: http://docs.openstack.org/developer/glance/statuses.html
  32. IMAGE_STATE_MAP = {
  33. 'QUEUED': MachineImageState.PENDING,
  34. 'SAVING': MachineImageState.PENDING,
  35. 'ACTIVE': MachineImageState.AVAILABLE,
  36. 'KILLED': MachineImageState.ERROR,
  37. 'DELETED': MachineImageState.ERROR,
  38. 'PENDING_DELETE': MachineImageState.ERROR
  39. }
  40. def __init__(self, provider, os_image):
  41. super(OpenStackMachineImage, self).__init__(provider)
  42. if isinstance(os_image, OpenStackMachineImage):
  43. # pylint:disable=protected-access
  44. self._os_image = os_image._os_image
  45. else:
  46. self._os_image = os_image
  47. @property
  48. def id(self):
  49. """
  50. Get the image identifier.
  51. """
  52. return self._os_image.id
  53. @property
  54. def name(self):
  55. """
  56. Get the image name.
  57. """
  58. return self._os_image.name
  59. @property
  60. def description(self):
  61. """
  62. Get the image description.
  63. """
  64. return None
  65. def delete(self):
  66. """
  67. Delete this image
  68. """
  69. self._os_image.delete()
  70. @property
  71. def state(self):
  72. return OpenStackMachineImage.IMAGE_STATE_MAP.get(
  73. self._os_image.status, MachineImageState.UNKNOWN)
  74. def refresh(self):
  75. """
  76. Refreshes the state of this instance by re-querying the cloud provider
  77. for its latest state.
  78. """
  79. image = self._provider.compute.images.get(self.id)
  80. if image:
  81. self._os_image = image._os_image # pylint:disable=protected-access
  82. else:
  83. # The image no longer exists and cannot be refreshed.
  84. # set the status to unknown
  85. self._os_image.status = 'unknown'
  86. class OpenStackPlacementZone(BasePlacementZone):
  87. def __init__(self, provider, zone, region):
  88. super(OpenStackPlacementZone, self).__init__(provider)
  89. if isinstance(zone, OpenStackPlacementZone):
  90. self._os_zone = zone._os_zone # pylint:disable=protected-access
  91. self._os_region = zone._os_region
  92. else:
  93. self._os_zone = zone
  94. self._os_region = region
  95. @property
  96. def id(self):
  97. """
  98. Get the zone id
  99. :rtype: ``str``
  100. :return: ID for this zone as returned by the cloud middleware.
  101. """
  102. return self._os_zone
  103. @property
  104. def name(self):
  105. """
  106. Get the zone name.
  107. :rtype: ``str``
  108. :return: Name for this zone as returned by the cloud middleware.
  109. """
  110. # return self._os_zone.zoneName
  111. return self._os_zone
  112. @property
  113. def region_name(self):
  114. """
  115. Get the region that this zone belongs to.
  116. :rtype: ``str``
  117. :return: Name of this zone's region as returned by the cloud middleware
  118. """
  119. return self._os_region
  120. class OpenStackInstanceType(BaseInstanceType):
  121. def __init__(self, provider, os_flavor):
  122. super(OpenStackInstanceType, self).__init__(provider)
  123. self._os_flavor = os_flavor
  124. @property
  125. def id(self):
  126. return self._os_flavor.id
  127. @property
  128. def name(self):
  129. return self._os_flavor.name
  130. @property
  131. def family(self):
  132. # TODO: This may not be standardised accross openstack
  133. # but NeCTAR is using it this way
  134. return self.extra_data.get('flavor_class:name')
  135. @property
  136. def vcpus(self):
  137. return self._os_flavor.vcpus
  138. @property
  139. def ram(self):
  140. return self._os_flavor.ram
  141. @property
  142. def size_root_disk(self):
  143. return self._os_flavor.disk
  144. @property
  145. def size_ephemeral_disks(self):
  146. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  147. self._os_flavor.ephemeral
  148. @property
  149. def num_ephemeral_disks(self):
  150. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  151. self._os_flavor.ephemeral
  152. @property
  153. def extra_data(self):
  154. extras = self._os_flavor.get_keys()
  155. extras['rxtx_factor'] = self._os_flavor.rxtx_factor
  156. extras['swap'] = self._os_flavor.swap
  157. extras['is_public'] = self._os_flavor.is_public
  158. return extras
  159. class OpenStackInstance(BaseInstance):
  160. # ref: http://docs.openstack.org/developer/nova/v2/2.0_server_concepts.html
  161. # and http://developer.openstack.org/api-ref-compute-v2.html
  162. INSTANCE_STATE_MAP = {
  163. 'ACTIVE': InstanceState.RUNNING,
  164. 'BUILD': InstanceState.PENDING,
  165. 'DELETED': InstanceState.TERMINATED,
  166. 'ERROR': InstanceState.ERROR,
  167. 'HARD_REBOOT': InstanceState.REBOOTING,
  168. 'PASSWORD': InstanceState.PENDING,
  169. 'PAUSED': InstanceState.STOPPED,
  170. 'REBOOT': InstanceState.REBOOTING,
  171. 'REBUILD': InstanceState.CONFIGURING,
  172. 'RESCUE': InstanceState.CONFIGURING,
  173. 'RESIZE': InstanceState.CONFIGURING,
  174. 'REVERT_RESIZE': InstanceState.CONFIGURING,
  175. 'SOFT_DELETED': InstanceState.STOPPED,
  176. 'STOPPED': InstanceState.STOPPED,
  177. 'SUSPENDED': InstanceState.STOPPED,
  178. 'SHUTOFF': InstanceState.STOPPED,
  179. 'UNKNOWN': InstanceState.UNKNOWN,
  180. 'VERIFY_RESIZE': InstanceState.CONFIGURING
  181. }
  182. def __init__(self, provider, os_instance):
  183. super(OpenStackInstance, self).__init__(provider)
  184. self._os_instance = os_instance
  185. @property
  186. def id(self):
  187. """
  188. Get the instance identifier.
  189. """
  190. return self._os_instance.id
  191. @property
  192. def name(self):
  193. """
  194. Get the instance name.
  195. """
  196. return self._os_instance.name
  197. @name.setter
  198. # pylint:disable=arguments-differ
  199. def name(self, value):
  200. """
  201. Set the instance name.
  202. """
  203. self._os_instance.name = value
  204. self._os_instance.update()
  205. @property
  206. def public_ips(self):
  207. """
  208. Get all the public IP addresses for this instance.
  209. """
  210. # Openstack doesn't provide an easy way to figure our whether an ip is
  211. # public or private, since the returned ips are grouped by an arbitrary
  212. # network label. Therefore, it's necessary to parse the address and
  213. # determine whether it's public or private
  214. return [address
  215. for _, addresses in self._os_instance.networks.items()
  216. for address in addresses
  217. if not ipaddress.ip_address(address).is_private]
  218. @property
  219. def private_ips(self):
  220. """
  221. Get all the private IP addresses for this instance.
  222. """
  223. return [address
  224. for _, addresses in self._os_instance.networks.items()
  225. for address in addresses
  226. if ipaddress.ip_address(address).is_private]
  227. @property
  228. def instance_type_id(self):
  229. """
  230. Get the instance type name.
  231. """
  232. return self._os_instance.flavor.get('id')
  233. @property
  234. def instance_type(self):
  235. """
  236. Get the instance type object.
  237. """
  238. flavor = self._provider.nova.flavors.get(
  239. self._os_instance.flavor.get('id'))
  240. return OpenStackInstanceType(self._provider, flavor)
  241. def reboot(self):
  242. """
  243. Reboot this instance (using the cloud middleware API).
  244. """
  245. self._os_instance.reboot()
  246. def terminate(self):
  247. """
  248. Permanently terminate this instance.
  249. """
  250. self._os_instance.delete()
  251. @property
  252. def image_id(self):
  253. """
  254. Get the image ID for this instance.
  255. """
  256. # In OpenStack, the Machine Image of a running instance may
  257. # be deleted, so make sure the image exists before attempting to
  258. # retrieve its id
  259. return (self._os_instance.image.get("id")
  260. if self._os_instance.image else "")
  261. @property
  262. def zone_id(self):
  263. """
  264. Get the placement zone where this instance is running.
  265. """
  266. return getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None)
  267. @property
  268. def security_groups(self):
  269. """
  270. Get the security groups associated with this instance.
  271. """
  272. return [self._provider.security.security_groups.find(group['name'])[0]
  273. for group in self._os_instance.security_groups]
  274. @property
  275. def security_group_ids(self):
  276. """
  277. Get the security groups IDs associated with this instance.
  278. """
  279. return [group.id for group in self.security_groups]
  280. @property
  281. def key_pair_name(self):
  282. """
  283. Get the name of the key pair associated with this instance.
  284. """
  285. return self._os_instance.key_name
  286. def create_image(self, name):
  287. """
  288. Create a new image based on this instance.
  289. """
  290. image_id = self._os_instance.create_image(name)
  291. return OpenStackMachineImage(
  292. self._provider, self._provider.compute.images.get(image_id))
  293. def add_floating_ip(self, ip_address):
  294. """
  295. Add a floating IP address to this instance.
  296. """
  297. self._os_instance.add_floating_ip(ip_address)
  298. def remove_floating_ip(self, ip_address):
  299. """
  300. Remove a floating IP address from this instance.
  301. """
  302. self._os_instance.remove_floating_ip(ip_address)
  303. @property
  304. def state(self):
  305. return OpenStackInstance.INSTANCE_STATE_MAP.get(
  306. self._os_instance.status, InstanceState.UNKNOWN)
  307. def refresh(self):
  308. """
  309. Refreshes the state of this instance by re-querying the cloud provider
  310. for its latest state.
  311. """
  312. instance = self._provider.compute.instances.get(
  313. self.id)
  314. if instance:
  315. # pylint:disable=protected-access
  316. self._os_instance = instance._os_instance
  317. else:
  318. # The instance no longer exists and cannot be refreshed.
  319. # set the status to unknown
  320. self._os_instance.status = 'unknown'
  321. class OpenStackRegion(BaseRegion):
  322. def __init__(self, provider, os_region):
  323. super(OpenStackRegion, self).__init__(provider)
  324. self._os_region = os_region
  325. @property
  326. def id(self):
  327. return self._os_region
  328. @property
  329. def name(self):
  330. return self._os_region
  331. @property
  332. def zones(self):
  333. # detailed must be set to ``False`` because the (default) ``True``
  334. # value requires Admin privileges
  335. if self.name == self._provider.region_name: # optimisation
  336. zones = self._provider.nova.availability_zones.list(detailed=False)
  337. else:
  338. try:
  339. region_nova = self._provider._connect_nova_region(self.name)
  340. zones = region_nova.availability_zones.list(detailed=False)
  341. except novaex.EndpointNotFound:
  342. # This region may not have a compute endpoint. If so just
  343. # return an empty list
  344. zones = []
  345. return [OpenStackPlacementZone(self._provider, z.zoneName,
  346. self._os_region)
  347. for z in zones]
  348. class OpenStackVolume(BaseVolume):
  349. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  350. VOLUME_STATE_MAP = {
  351. 'creating': VolumeState.CREATING,
  352. 'available': VolumeState.AVAILABLE,
  353. 'attaching': VolumeState.CONFIGURING,
  354. 'in-use': VolumeState.IN_USE,
  355. 'deleting': VolumeState.CONFIGURING,
  356. 'error': VolumeState.ERROR,
  357. 'error_deleting': VolumeState.ERROR,
  358. 'backing-up': VolumeState.CONFIGURING,
  359. 'restoring-backup': VolumeState.CONFIGURING,
  360. 'error_restoring': VolumeState.ERROR,
  361. 'error_extending': VolumeState.ERROR
  362. }
  363. def __init__(self, provider, volume):
  364. super(OpenStackVolume, self).__init__(provider)
  365. self._volume = volume
  366. @property
  367. def id(self):
  368. return self._volume.id
  369. @property
  370. def name(self):
  371. """
  372. Get the volume name.
  373. """
  374. return self._volume.name
  375. @name.setter
  376. def name(self, value): # pylint:disable=arguments-differ
  377. """
  378. Set the volume name.
  379. """
  380. self._volume.name = value
  381. self._volume.update(name=value)
  382. @property
  383. def description(self):
  384. return self._volume.description
  385. @description.setter
  386. def description(self, value):
  387. self._volume.description = value
  388. self._volume.update(description=value)
  389. @property
  390. def size(self):
  391. return self._volume.size
  392. @property
  393. def create_time(self):
  394. return self._volume.created_at
  395. @property
  396. def zone_id(self):
  397. return self._volume.availability_zone
  398. @property
  399. def source(self):
  400. if self._volume.snapshot_id:
  401. return self._provider.block_store.snapshots.get(
  402. self._volume.snapshot_id)
  403. return None
  404. @property
  405. def attachments(self):
  406. if self._volume.attachments:
  407. return BaseAttachmentInfo(
  408. self,
  409. self._volume.attachments[0].get('server_id'),
  410. self._volume.attachments[0].get('device'))
  411. else:
  412. return None
  413. def attach(self, instance, device):
  414. """
  415. Attach this volume to an instance.
  416. """
  417. instance_id = instance.id if isinstance(
  418. instance,
  419. OpenStackInstance) else instance
  420. self._volume.attach(instance_id, device)
  421. def detach(self, force=False):
  422. """
  423. Detach this volume from an instance.
  424. """
  425. self._volume.detach()
  426. def create_snapshot(self, name, description=None):
  427. """
  428. Create a snapshot of this Volume.
  429. """
  430. return self._provider.block_store.snapshots.create(
  431. name, self, description=description)
  432. def delete(self):
  433. """
  434. Delete this volume.
  435. """
  436. self._volume.delete()
  437. @property
  438. def state(self):
  439. return OpenStackVolume.VOLUME_STATE_MAP.get(
  440. self._volume.status, VolumeState.UNKNOWN)
  441. def refresh(self):
  442. """
  443. Refreshes the state of this volume by re-querying the cloud provider
  444. for its latest state.
  445. """
  446. vol = self._provider.block_store.volumes.get(
  447. self.id)
  448. if vol:
  449. self._volume = vol._volume # pylint:disable=protected-access
  450. else:
  451. # The volume no longer exists and cannot be refreshed.
  452. # set the status to unknown
  453. self._volume.status = 'unknown'
  454. class OpenStackSnapshot(BaseSnapshot):
  455. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  456. SNAPSHOT_STATE_MAP = {
  457. 'creating': SnapshotState.PENDING,
  458. 'available': SnapshotState.AVAILABLE,
  459. 'deleting': SnapshotState.CONFIGURING,
  460. 'error': SnapshotState.ERROR,
  461. 'error_deleting': SnapshotState.ERROR
  462. }
  463. def __init__(self, provider, snapshot):
  464. super(OpenStackSnapshot, self).__init__(provider)
  465. self._snapshot = snapshot
  466. @property
  467. def id(self):
  468. return self._snapshot.id
  469. @property
  470. def name(self):
  471. """
  472. Get the snapshot name.
  473. """
  474. return self._snapshot.name
  475. @name.setter
  476. def name(self, value): # pylint:disable=arguments-differ
  477. """
  478. Set the snapshot name.
  479. """
  480. self._snapshot.name = value
  481. self._snapshot.update(name=value)
  482. @property
  483. def description(self):
  484. return self._snapshot.description
  485. @description.setter
  486. def description(self, value):
  487. self._snapshot.description = value
  488. self._snapshot.update(description=value)
  489. @property
  490. def size(self):
  491. return self._snapshot.size
  492. @property
  493. def volume_id(self):
  494. return self._snapshot.volume_id
  495. @property
  496. def create_time(self):
  497. return self._snapshot.created_at
  498. @property
  499. def state(self):
  500. return OpenStackSnapshot.SNAPSHOT_STATE_MAP.get(
  501. self._snapshot.status, SnapshotState.UNKNOWN)
  502. def refresh(self):
  503. """
  504. Refreshes the state of this snapshot by re-querying the cloud provider
  505. for its latest state.
  506. """
  507. snap = self._provider.block_store.snapshots.get(
  508. self.id)
  509. if snap:
  510. self._snapshot = snap._snapshot # pylint:disable=protected-access
  511. else:
  512. # The snapshot no longer exists and cannot be refreshed.
  513. # set the status to unknown
  514. self._snapshot.status = 'unknown'
  515. def delete(self):
  516. """
  517. Delete this snapshot.
  518. """
  519. self._snapshot.delete()
  520. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  521. """
  522. Create a new Volume from this Snapshot.
  523. """
  524. vol_name = "Created from {0} ({1})".format(self.id, self.name)
  525. size = size if size else self._snapshot.size
  526. os_vol = self._provider.cinder.volumes.create(
  527. size, name=vol_name, availability_zone=placement,
  528. snapshot_id=self._snapshot.id)
  529. cb_vol = OpenStackVolume(self._provider, os_vol)
  530. cb_vol.name = vol_name
  531. return cb_vol
  532. class OpenStackNetwork(BaseNetwork):
  533. # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
  534. # common/constants.py
  535. _NETWORK_STATE_MAP = {
  536. 'PENDING_CREATE': NetworkState.PENDING,
  537. 'PENDING_UPDATE': NetworkState.PENDING,
  538. 'PENDING_DELETE': NetworkState.PENDING,
  539. 'CREATED': NetworkState.PENDING,
  540. 'INACTIVE': NetworkState.PENDING,
  541. 'DOWN': NetworkState.DOWN,
  542. 'ERROR': NetworkState.ERROR,
  543. 'ACTIVE': NetworkState.AVAILABLE
  544. }
  545. def __init__(self, provider, network):
  546. super(OpenStackNetwork, self).__init__(provider)
  547. self._network = network
  548. @property
  549. def id(self):
  550. return self._network.get('id', None)
  551. @property
  552. def name(self):
  553. return self._network.get('name', None)
  554. @property
  555. def state(self):
  556. return OpenStackNetwork._NETWORK_STATE_MAP.get(
  557. self._network.get('status', None),
  558. NetworkState.UNKNOWN)
  559. @property
  560. def cidr_block(self):
  561. # OpenStack does not define a CIDR block for networks
  562. return ''
  563. def delete(self):
  564. if self.id in str(self._provider.neutron.list_networks()):
  565. self._provider.neutron.delete_network(self.id)
  566. # Adhear to the interface docs
  567. if self.id not in str(self._provider.neutron.list_networks()):
  568. return True
  569. def subnets(self):
  570. subnets = (self._provider.neutron.list_subnets(network_id=self.id)
  571. .get('subnets', []))
  572. return [OpenStackSubnet(self._provider, subnet) for subnet in subnets]
  573. def create_subnet(self, cidr_block, name=''):
  574. subnet_info = {'name': name, 'network_id': self.id,
  575. 'cidr': cidr_block, 'ip_version': 4}
  576. subnet = (self._provider.neutron.create_subnet({'subnet': subnet_info})
  577. .get('subnet'))
  578. return OpenStackSubnet(self._provider, subnet)
  579. def refresh(self):
  580. """
  581. Refreshes the state of this network by re-querying the cloud provider
  582. for its latest state.
  583. """
  584. return self.state
  585. class OpenStackSubnet(BaseSubnet):
  586. def __init__(self, provider, subnet):
  587. super(OpenStackSubnet, self).__init__(provider)
  588. self._subnet = subnet
  589. @property
  590. def id(self):
  591. return self._subnet.get('id', None)
  592. @property
  593. def name(self):
  594. return self._subnet.get('name', None)
  595. @property
  596. def cidr_block(self):
  597. return self._subnet.get('cidr', None)
  598. @property
  599. def network_id(self):
  600. return self._subnet.get('network_id', None)
  601. def delete(self):
  602. if self.id in str(self._provider.neutron.list_subnets()):
  603. self._provider.neutron.delete_subnet(self.id)
  604. # Adhear to the interface docs
  605. if self.id not in str(self._provider.neutron.list_subnets()):
  606. return True
  607. class OpenStackKeyPair(BaseKeyPair):
  608. def __init__(self, provider, key_pair):
  609. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  610. @property
  611. def material(self):
  612. """
  613. Unencrypted private key.
  614. :rtype: str
  615. :return: Unencrypted private key or ``None`` if not available.
  616. """
  617. return getattr(self._key_pair, 'private_key', None)
  618. class OpenStackSecurityGroup(BaseSecurityGroup):
  619. def __init__(self, provider, security_group):
  620. super(OpenStackSecurityGroup, self).__init__(provider, security_group)
  621. @property
  622. def rules(self):
  623. # Update SG object; otherwise, recenlty added rules do now show
  624. self._security_group = self._provider.nova.security_groups.get(
  625. self._security_group)
  626. return [OpenStackSecurityGroupRule(self._provider, r, self)
  627. for r in self._security_group.rules]
  628. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  629. cidr_ip=None, src_group=None):
  630. """
  631. Create a security group rule.
  632. You need to pass in either ``src_group`` OR ``ip_protocol``,
  633. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  634. you are authorizing another group or you are authorizing some
  635. ip-based rule.
  636. :type ip_protocol: str
  637. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  638. :type from_port: int
  639. :param from_port: The beginning port number you are enabling
  640. :type to_port: int
  641. :param to_port: The ending port number you are enabling
  642. :type cidr_ip: str or list of strings
  643. :param cidr_ip: The CIDR block you are providing access to.
  644. :type src_group: ``object`` of :class:`.SecurityGroup`
  645. :param src_group: The Security Group you are granting access to.
  646. :rtype: :class:``.SecurityGroupRule``
  647. :return: Rule object if successful or ``None``.
  648. """
  649. if src_group:
  650. for protocol in ['udp', 'tcp']:
  651. rule = self._provider.nova.security_group_rules.create(
  652. parent_group_id=self._security_group.id,
  653. ip_protocol=protocol,
  654. from_port=1,
  655. to_port=65535,
  656. group_id=src_group.id)
  657. if rule:
  658. # We can only return one Rule so default to TCP (ie, last in
  659. # the for loop above).
  660. return OpenStackSecurityGroupRule(self._provider,
  661. rule.to_dict(), self)
  662. else:
  663. rule = self._provider.nova.security_group_rules.create(
  664. parent_group_id=self._security_group.id,
  665. ip_protocol=ip_protocol,
  666. from_port=from_port,
  667. to_port=to_port,
  668. cidr=cidr_ip)
  669. if rule:
  670. return OpenStackSecurityGroupRule(self._provider,
  671. rule.to_dict(), self)
  672. return None
  673. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  674. cidr_ip=None, src_group=None):
  675. # Update SG object; otherwise, recenlty added rules do now show
  676. self._security_group = self._provider.nova.security_groups.get(
  677. self._security_group)
  678. for rule in self._security_group.rules:
  679. if (rule['ip_protocol'] == ip_protocol and
  680. rule['from_port'] == from_port and
  681. rule['to_port'] == to_port and
  682. rule['ip_range'].get('cidr') == cidr_ip) or \
  683. (rule['group'].get('name') == src_group.name if src_group
  684. else False):
  685. return OpenStackSecurityGroupRule(self._provider, rule, self)
  686. return None
  687. def to_json(self):
  688. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  689. js = {k: v for(k, v) in attr if not k.startswith('_')}
  690. json_rules = [r.to_json() for r in self.rules]
  691. js['rules'] = [json.loads(r) for r in json_rules]
  692. return json.dumps(js, sort_keys=True)
  693. class OpenStackSecurityGroupRule(BaseSecurityGroupRule):
  694. def __init__(self, provider, rule, parent):
  695. super(OpenStackSecurityGroupRule, self).__init__(
  696. provider, rule, parent)
  697. @property
  698. def id(self):
  699. return self._rule.get('id')
  700. @property
  701. def ip_protocol(self):
  702. return self._rule.get('ip_protocol')
  703. @property
  704. def from_port(self):
  705. return int(self._rule.get('from_port') or 0)
  706. @property
  707. def to_port(self):
  708. return int(self._rule.get('to_port') or 0)
  709. @property
  710. def cidr_ip(self):
  711. return self._rule.get('ip_range', {}).get('cidr')
  712. @property
  713. def group(self):
  714. cg = self._rule.get('group', {}).get('name')
  715. if cg:
  716. security_groups = self._provider.nova.security_groups.list()
  717. for sg in security_groups:
  718. if sg.name == cg:
  719. return OpenStackSecurityGroup(self._provider, sg)
  720. return None
  721. def to_json(self):
  722. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  723. js = {k: v for(k, v) in attr if not k.startswith('_')}
  724. js['group'] = self.group.id if self.group else ''
  725. js['parent'] = self.parent.id if self.parent else ''
  726. return json.dumps(js, sort_keys=True)
  727. def delete(self):
  728. return self._provider.nova.security_group_rules.delete(self.id)
  729. class OpenStackBucketObject(BaseBucketObject):
  730. def __init__(self, provider, cbcontainer, obj):
  731. super(OpenStackBucketObject, self).__init__(provider)
  732. self.cbcontainer = cbcontainer
  733. self._obj = obj
  734. @property
  735. def id(self):
  736. return self._obj.get("name")
  737. @property
  738. def name(self):
  739. """
  740. Get this object's name.
  741. """
  742. return self._obj.get("name")
  743. def iter_content(self):
  744. """
  745. Returns this object's content as an
  746. iterable.
  747. """
  748. _, content = self._provider.swift.get_object(
  749. self.cbcontainer.name, self.name, resp_chunk_size=65536)
  750. return content
  751. def upload(self, data):
  752. """
  753. Set the contents of this object to the data read from the source
  754. string.
  755. """
  756. self._provider.swift.put_object(self.cbcontainer.name, self.name,
  757. data)
  758. def delete(self):
  759. """
  760. Delete this object.
  761. :rtype: bool
  762. :return: True if successful
  763. """
  764. try:
  765. self._provider.swift.delete_object(self.cbcontainer.name,
  766. self.name)
  767. except swiftex.ClientException as err:
  768. if err.http_status == 404:
  769. return True
  770. return False
  771. class OpenStackBucket(BaseBucket):
  772. def __init__(self, provider, bucket):
  773. super(OpenStackBucket, self).__init__(provider)
  774. self._bucket = bucket
  775. @property
  776. def id(self):
  777. return self._bucket.get("name")
  778. @property
  779. def name(self):
  780. """
  781. Get this bucket's name.
  782. """
  783. return self._bucket.get("name")
  784. def get(self, key):
  785. """
  786. Retrieve a given object from this bucket.
  787. """
  788. _, object_list = self._provider.swift.get_container(
  789. self.name, prefix=key)
  790. if object_list:
  791. return OpenStackBucketObject(self._provider, self,
  792. object_list[0])
  793. else:
  794. return None
  795. def list(self, limit=None, marker=None):
  796. """
  797. List all objects within this bucket.
  798. :rtype: BucketObject
  799. :return: List of all available BucketObjects within this bucket.
  800. """
  801. _, object_list = self._provider.swift.get_container(
  802. self.name, limit=oshelpers.os_result_limit(self._provider, limit),
  803. marker=marker)
  804. cb_objects = [OpenStackBucketObject(
  805. self._provider, self, obj) for obj in object_list]
  806. return oshelpers.to_server_paged_list(
  807. self._provider,
  808. cb_objects,
  809. limit)
  810. def delete(self, delete_contents=False):
  811. """
  812. Delete this bucket.
  813. """
  814. self._provider.swift.delete_container(self.name)
  815. def create_object(self, object_name):
  816. self._provider.swift.put_object(self.name, object_name, None)
  817. return self.get(object_name)