resources.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. """
  2. DataTypes used by this provider
  3. """
  4. import shutil
  5. import ipaddress
  6. from swiftclient.exceptions import ClientException
  7. from cloudbridge.cloud.base import BaseInstance
  8. from cloudbridge.cloud.base import BaseInstanceType
  9. from cloudbridge.cloud.base import BaseKeyPair
  10. from cloudbridge.cloud.base import BaseMachineImage
  11. from cloudbridge.cloud.base import BaseRegion
  12. from cloudbridge.cloud.base import BaseSecurityGroup
  13. from cloudbridge.cloud.base import BaseSecurityGroupRule
  14. from cloudbridge.cloud.base import BaseSnapshot
  15. from cloudbridge.cloud.base import BaseVolume
  16. from cloudbridge.cloud.interfaces.resources import Container
  17. from cloudbridge.cloud.interfaces.resources import ContainerObject
  18. from cloudbridge.cloud.interfaces.resources import InstanceState
  19. from cloudbridge.cloud.interfaces.resources import MachineImageState
  20. from cloudbridge.cloud.interfaces.resources import PlacementZone
  21. from cloudbridge.cloud.interfaces.resources import SnapshotState
  22. from cloudbridge.cloud.interfaces.resources import VolumeState
  23. class OpenStackMachineImage(BaseMachineImage):
  24. # ref: http://docs.openstack.org/developer/glance/statuses.html
  25. IMAGE_STATE_MAP = {
  26. 'QUEUED': MachineImageState.PENDING,
  27. 'SAVING': MachineImageState.PENDING,
  28. 'ACTIVE': MachineImageState.AVAILABLE,
  29. 'KILLED': MachineImageState.ERROR,
  30. 'DELETED': MachineImageState.ERROR,
  31. 'PENDING_DELETE': MachineImageState.ERROR
  32. }
  33. def __init__(self, provider, os_image):
  34. self._provider = provider
  35. if isinstance(os_image, OpenStackMachineImage):
  36. self._os_image = os_image._os_image
  37. else:
  38. self._os_image = os_image
  39. @property
  40. def id(self):
  41. """
  42. Get the image identifier.
  43. """
  44. return self._os_image.id
  45. @property
  46. def name(self):
  47. """
  48. Get the image name.
  49. """
  50. return self._os_image.name
  51. @property
  52. def description(self):
  53. """
  54. Get the image description.
  55. """
  56. return None
  57. def delete(self):
  58. """
  59. Delete this image
  60. """
  61. self._os_image.delete()
  62. @property
  63. def state(self):
  64. return OpenStackMachineImage.IMAGE_STATE_MAP.get(
  65. self._os_image.status, MachineImageState.UNKNOWN)
  66. def refresh(self):
  67. """
  68. Refreshes the state of this instance by re-querying the cloud provider
  69. for its latest state.
  70. """
  71. image = self._provider.compute.images.get(self.id)
  72. if image:
  73. self._os_image = image._os_image
  74. else:
  75. # The image no longer exists and cannot be refreshed.
  76. # set the status to unknown
  77. self._os_image.status = 'unknown'
  78. class OpenStackPlacementZone(PlacementZone):
  79. def __init__(self, provider, zone):
  80. self._provider = provider
  81. if isinstance(zone, OpenStackPlacementZone):
  82. self._os_zone = zone._os_zone
  83. else:
  84. self._os_zone = zone
  85. @property
  86. def name(self):
  87. """
  88. Get the zone name.
  89. :rtype: ``str``
  90. :return: Name for this zone as returned by the cloud middleware.
  91. """
  92. # return self._os_zone.zoneName
  93. return self._os_zone
  94. @property
  95. def region(self):
  96. """
  97. Get the region that this zone belongs to.
  98. :rtype: ``str``
  99. :return: Name of this zone's region as returned by the cloud middleware
  100. """
  101. return self._os_zone.region_name
  102. class OpenStackInstanceType(BaseInstanceType):
  103. def __init__(self, os_flavor):
  104. self._os_flavor = os_flavor
  105. @property
  106. def id(self):
  107. return self._os_flavor.id
  108. @property
  109. def name(self):
  110. return self._os_flavor.name
  111. @property
  112. def family(self):
  113. # TODO: This may not be standardised accross openstack
  114. # but NeCTAR is using it this way
  115. return self.extra_data.get('flavor_class:name')
  116. @property
  117. def vcpus(self):
  118. return self._os_flavor.vcpus
  119. @property
  120. def ram(self):
  121. return self._os_flavor.ram
  122. @property
  123. def size_root_disk(self):
  124. return self._os_flavor.disk
  125. @property
  126. def size_ephemeral_disks(self):
  127. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  128. self._os_flavor.ephemeral
  129. @property
  130. def num_ephemeral_disks(self):
  131. return 0 if self._os_flavor.ephemeral == 'N/A' else \
  132. self._os_flavor.ephemeral
  133. @property
  134. def extra_data(self):
  135. extras = self._os_flavor.get_keys()
  136. extras['rxtx_factor'] = self._os_flavor.rxtx_factor
  137. extras['swap'] = self._os_flavor.swap
  138. extras['is_public'] = self._os_flavor.is_public
  139. return extras
  140. class OpenStackInstance(BaseInstance):
  141. # ref: http://docs.openstack.org/developer/nova/v2/2.0_server_concepts.html
  142. # and http://developer.openstack.org/api-ref-compute-v2.html
  143. INSTANCE_STATE_MAP = {
  144. 'ACTIVE': InstanceState.RUNNING,
  145. 'BUILD': InstanceState.PENDING,
  146. 'DELETED': InstanceState.TERMINATED,
  147. 'ERROR': InstanceState.ERROR,
  148. 'HARD_REBOOT': InstanceState.REBOOTING,
  149. 'PASSWORD': InstanceState.PENDING,
  150. 'PAUSED': InstanceState.STOPPED,
  151. 'REBOOT': InstanceState.REBOOTING,
  152. 'REBUILD': InstanceState.CONFIGURING,
  153. 'RESCUE': InstanceState.CONFIGURING,
  154. 'RESIZE': InstanceState.CONFIGURING,
  155. 'REVERT_RESIZE': InstanceState.CONFIGURING,
  156. 'SOFT_DELETED': InstanceState.STOPPED,
  157. 'STOPPED': InstanceState.STOPPED,
  158. 'SUSPENDED': InstanceState.STOPPED,
  159. 'SHUTOFF': InstanceState.STOPPED,
  160. 'UNKNOWN': InstanceState.UNKNOWN,
  161. 'VERIFY_RESIZE': InstanceState.CONFIGURING
  162. }
  163. def __init__(self, provider, os_instance):
  164. self._provider = provider
  165. self._os_instance = os_instance
  166. @property
  167. def instance_id(self):
  168. """
  169. Get the instance identifier.
  170. """
  171. return self._os_instance.id
  172. @property
  173. def name(self):
  174. """
  175. Get the instance name.
  176. """
  177. return self._os_instance.name
  178. @name.setter
  179. def name(self, value):
  180. """
  181. Set the instance name.
  182. """
  183. self._os_instance.name = value
  184. self._os_instance.update()
  185. @property
  186. def public_ips(self):
  187. """
  188. Get all the public IP addresses for this instance.
  189. """
  190. # Openstack doesn't provide an easy way to figure our whether an ip is
  191. # public or private, since the returned ips are grouped by an arbitrary
  192. # network label. Therefore, it's necessary to parse the address and
  193. # determine whether it's public or private
  194. return [address
  195. for _, addresses in self._os_instance.networks.items()
  196. for address in addresses
  197. if not ipaddress.ip_address(address).is_private]
  198. @property
  199. def private_ips(self):
  200. """
  201. Get all the private IP addresses for this instance.
  202. """
  203. return [address
  204. for _, addresses in self._os_instance.networks.items()
  205. for address in addresses
  206. if ipaddress.ip_address(address).is_private]
  207. @property
  208. def instance_type(self):
  209. """
  210. Get the instance type.
  211. """
  212. return OpenStackInstanceType(self._os_instance.flavor)
  213. def reboot(self):
  214. """
  215. Reboot this instance (using the cloud middleware API).
  216. """
  217. self._os_instance.reboot()
  218. def terminate(self):
  219. """
  220. Permanently terminate this instance.
  221. """
  222. self._os_instance.delete()
  223. @property
  224. def image_id(self):
  225. """
  226. Get the image ID for this instance.
  227. """
  228. return self._os_instance.image.get("id")
  229. @property
  230. def placement_zone(self):
  231. """
  232. Get the placement zone where this instance is running.
  233. """
  234. return OpenStackPlacementZone(
  235. self._provider,
  236. getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None))
  237. @property
  238. def mac_address(self):
  239. """
  240. Get the MAC address for this instance.
  241. """
  242. raise NotImplementedError(
  243. 'mac_address not implemented by this provider')
  244. @property
  245. def security_groups(self):
  246. """
  247. Get the security groups associated with this instance.
  248. """
  249. security_groups = []
  250. for group in self._os_instance.security_groups:
  251. security_groups.append(self._provider.nova.security_groups.find(
  252. name=group.get('name')))
  253. return [OpenStackSecurityGroup(self._provider, group)
  254. for group in security_groups]
  255. @property
  256. def key_pair_name(self):
  257. """
  258. Get the name of the key pair associated with this instance.
  259. """
  260. return self._os_instance.key_name
  261. def create_image(self, name):
  262. """
  263. Create a new image based on this instance.
  264. """
  265. image_id = self._os_instance.create_image(name)
  266. return OpenStackMachineImage(
  267. self._provider, self._provider.compute.images.get(image_id))
  268. @property
  269. def state(self):
  270. return OpenStackInstance.INSTANCE_STATE_MAP.get(
  271. self._os_instance.status, InstanceState.UNKNOWN)
  272. def refresh(self):
  273. """
  274. Refreshes the state of this instance by re-querying the cloud provider
  275. for its latest state.
  276. """
  277. instance = self._provider.compute.instances.get(
  278. self.instance_id)
  279. if instance:
  280. self._os_instance = instance._os_instance
  281. else:
  282. # The instance no longer exists and cannot be refreshed.
  283. # set the status to unknown
  284. self._os_instance.status = 'unknown'
  285. def __repr__(self):
  286. return "<CB-OSInstance: {0} ({1})>".format(self.name, self.instance_id)
  287. class OpenStackRegion(BaseRegion):
  288. def __init__(self, provider, os_region):
  289. self._provider = provider
  290. self._os_region = os_region
  291. @property
  292. def id(self):
  293. return self._os_region
  294. @property
  295. def name(self):
  296. return self._os_region
  297. @property
  298. def zones(self):
  299. # detailed must be set to ``False`` because the (default) ``True``
  300. # value requires Admin privileges
  301. return self._provider.nova.availability_zones.list(detailed=False)
  302. class OpenStackVolume(BaseVolume):
  303. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  304. VOLUME_STATE_MAP = {
  305. 'creating': VolumeState.CREATING,
  306. 'available': VolumeState.AVAILABLE,
  307. 'attaching': VolumeState.CONFIGURING,
  308. 'in-use': VolumeState.IN_USE,
  309. 'deleting': VolumeState.CONFIGURING,
  310. 'error': VolumeState.ERROR,
  311. 'error_deleting': VolumeState.ERROR,
  312. 'backing-up': VolumeState.CONFIGURING,
  313. 'restoring-backup': VolumeState.CONFIGURING,
  314. 'error_restoring': VolumeState.ERROR,
  315. 'error_extending': VolumeState.ERROR
  316. }
  317. def __init__(self, provider, volume):
  318. self._provider = provider
  319. self._volume = volume
  320. @property
  321. def id(self):
  322. return self._volume.id
  323. @property
  324. def name(self):
  325. """
  326. Get the volume name.
  327. """
  328. return self._volume.name
  329. @name.setter
  330. def name(self, value):
  331. """
  332. Set the volume name.
  333. """
  334. self._volume.name = value
  335. self._volume.update()
  336. def attach(self, instance, device):
  337. """
  338. Attach this volume to an instance.
  339. """
  340. instance_id = instance.instance_id if isinstance(
  341. instance,
  342. OpenStackInstance) else instance
  343. self._volume.attach(instance_id, device)
  344. def detach(self, force=False):
  345. """
  346. Detach this volume from an instance.
  347. """
  348. self._volume.detach()
  349. def create_snapshot(self, name, description=None):
  350. """
  351. Create a snapshot of this Volume.
  352. """
  353. return self._provider.block_store.snapshots.create(
  354. name, self, description=description)
  355. def delete(self):
  356. """
  357. Delete this volume.
  358. """
  359. self._volume.delete()
  360. @property
  361. def state(self):
  362. return OpenStackVolume.VOLUME_STATE_MAP.get(
  363. self._volume.status, VolumeState.UNKNOWN)
  364. def refresh(self):
  365. """
  366. Refreshes the state of this volume by re-querying the cloud provider
  367. for its latest state.
  368. """
  369. vol = self._provider.block_store.volumes.get(
  370. self.id)
  371. if vol:
  372. self._volume = vol._volume
  373. else:
  374. # The volume no longer exists and cannot be refreshed.
  375. # set the status to unknown
  376. self._volume.status = 'unknown'
  377. def __repr__(self):
  378. return "<CB-OSVolume: {0} ({1})>".format(self.id, self.name)
  379. class OpenStackSnapshot(BaseSnapshot):
  380. # Ref: http://developer.openstack.org/api-ref-blockstorage-v2.html
  381. SNAPSHOT_STATE_MAP = {
  382. 'creating': SnapshotState.PENDING,
  383. 'available': SnapshotState.AVAILABLE,
  384. 'deleting': SnapshotState.CONFIGURING,
  385. 'error': SnapshotState.ERROR,
  386. 'error_deleting': SnapshotState.ERROR
  387. }
  388. def __init__(self, provider, snapshot):
  389. self._provider = provider
  390. self._snapshot = snapshot
  391. @property
  392. def id(self):
  393. return self._snapshot.id
  394. @property
  395. def name(self):
  396. """
  397. Get the snapshot name.
  398. """
  399. return self._snapshot.name
  400. @name.setter
  401. def name(self, value):
  402. """
  403. Set the snapshot name.
  404. """
  405. self._snapshot.add_tag('Name', value)
  406. self._snapshot.update()
  407. @property
  408. def state(self):
  409. return OpenStackSnapshot.SNAPSHOT_STATE_MAP.get(
  410. self._snapshot.status, SnapshotState.UNKNOWN)
  411. def refresh(self):
  412. """
  413. Refreshes the state of this snapshot by re-querying the cloud provider
  414. for its latest state.
  415. """
  416. snap = self._provider.block_store.snapshots.get(
  417. self.id)
  418. if snap:
  419. self._snapshot = snap._snapshot
  420. else:
  421. # The snapshot no longer exists and cannot be refreshed.
  422. # set the status to unknown
  423. self._snapshot.status = 'unknown'
  424. def delete(self):
  425. """
  426. Delete this snapshot.
  427. """
  428. self._snapshot.delete()
  429. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  430. raise NotImplementedError(
  431. 'create_volume not implemented by this provider')
  432. def share(self, user_ids=None):
  433. raise NotImplementedError('share not implemented by this provider')
  434. def unshare(self, user_ids=None):
  435. raise NotImplementedError('share not implemented by this provider')
  436. def __repr__(self):
  437. return "<CB-OSSnapshot: {0} ({1}>".format(self.id, self.name)
  438. class OpenStackKeyPair(BaseKeyPair):
  439. def __init__(self, provider, key_pair):
  440. super(OpenStackKeyPair, self).__init__(provider, key_pair)
  441. @property
  442. def material(self):
  443. """
  444. Unencrypted private key.
  445. :rtype: str
  446. :return: Unencrypted private key or ``None`` if not available.
  447. """
  448. return getattr(self._key_pair, 'private_key', None)
  449. class OpenStackSecurityGroup(BaseSecurityGroup):
  450. def __init__(self, provider, security_group):
  451. super(OpenStackSecurityGroup, self).__init__(provider, security_group)
  452. @property
  453. def rules(self):
  454. # Update SG object; otherwise, recenlty added rules do now show
  455. self._security_group = self._provider.nova.security_groups.get(
  456. self._security_group)
  457. return [OpenStackSecurityGroupRule(self._provider, r, self)
  458. for r in self._security_group.rules]
  459. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  460. cidr_ip=None, src_group=None):
  461. """
  462. Create a security group rule.
  463. You need to pass in either ``src_group`` OR ``ip_protocol``,
  464. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  465. you are authorizing another group or you are authorizing some
  466. ip-based rule.
  467. :type ip_protocol: str
  468. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  469. :type from_port: int
  470. :param from_port: The beginning port number you are enabling
  471. :type to_port: int
  472. :param to_port: The ending port number you are enabling
  473. :type cidr_ip: str or list of strings
  474. :param cidr_ip: The CIDR block you are providing access to.
  475. :type src_group: ``object`` of :class:`.SecurityGroup`
  476. :param src_group: The Security Group you are granting access to.
  477. :rtype: bool
  478. :return: True if successful.
  479. """
  480. if src_group:
  481. for protocol in ['tcp', 'udp']:
  482. self._provider.nova.security_group_rules.create(
  483. parent_group_id=self._security_group.id,
  484. ip_protocol=protocol,
  485. from_port=1,
  486. to_port=65535,
  487. group_id=src_group.id)
  488. else:
  489. if self._provider.nova.security_group_rules.create(
  490. parent_group_id=self._security_group.id,
  491. ip_protocol=ip_protocol,
  492. from_port=from_port,
  493. to_port=to_port,
  494. cidr=cidr_ip):
  495. return True
  496. else:
  497. return False
  498. class OpenStackSecurityGroupRule(BaseSecurityGroupRule):
  499. def __init__(self, provider, rule, parent):
  500. super(OpenStackSecurityGroupRule, self).__init__(
  501. provider, rule, parent)
  502. @property
  503. def ip_protocol(self):
  504. return self._rule.get('ip_protocol')
  505. @property
  506. def from_port(self):
  507. return self._rule.get('from_port')
  508. @property
  509. def to_port(self):
  510. return self._rule.get('to_port')
  511. @property
  512. def cidr_ip(self):
  513. return self._rule.get('ip_range', {}).get('cidr')
  514. @property
  515. def group(self):
  516. cg = self._rule.get('group', {}).get('name')
  517. if cg:
  518. security_groups = self.parent._provider.nova.security_groups.list()
  519. for sg in security_groups:
  520. if sg.name == cg:
  521. return OpenStackSecurityGroup(self.parent._provider, sg)
  522. return None
  523. class OpenStackContainerObject(ContainerObject):
  524. def __init__(self, provider, cbcontainer, obj):
  525. self._provider = provider
  526. self.cbcontainer = cbcontainer
  527. self._obj = obj
  528. @property
  529. def name(self):
  530. """
  531. Get this object's name.
  532. """
  533. return self._obj.get("name")
  534. def download(self, target_stream):
  535. """
  536. Download this object and write its
  537. contents to the target_stream.
  538. """
  539. _, content = self._provider.swift.get_object(
  540. self.cbcontainer.name, self.name, resp_chunk_size=65536)
  541. shutil.copyfileobj(content, target_stream)
  542. def upload(self, data):
  543. """
  544. Set the contents of this object to the data read from the source
  545. string.
  546. """
  547. self._provider.swift.put_object(self.cbcontainer.name, self.name,
  548. data)
  549. def delete(self):
  550. """
  551. Delete this object.
  552. :rtype: bool
  553. :return: True if successful
  554. """
  555. try:
  556. self._provider.swift.delete_object(self.cbcontainer.name,
  557. self.name)
  558. except ClientException as err:
  559. if err.http_status == 404:
  560. return True
  561. return False
  562. def __repr__(self):
  563. return "<CB-OpenStackContainerObject: {0}>".format(self.name)
  564. class OpenStackContainer(Container):
  565. def __init__(self, provider, container):
  566. self._provider = provider
  567. self._container = container
  568. @property
  569. def name(self):
  570. """
  571. Get this container's name.
  572. """
  573. return self._container.get("name")
  574. def get(self, key):
  575. """
  576. Retrieve a given object from this container.
  577. """
  578. _, object_list = self._provider.swift.get_container(
  579. self.name, prefix=key)
  580. if object_list:
  581. return OpenStackContainerObject(self._provider, self,
  582. object_list[0])
  583. else:
  584. return None
  585. def list(self):
  586. """
  587. List all objects within this container.
  588. :rtype: ContainerObject
  589. :return: List of all available ContainerObjects within this container
  590. """
  591. _, object_list = self._provider.swift.get_container(self.name)
  592. return [
  593. OpenStackContainer(self._provider, o) for o in object_list]
  594. def delete(self, delete_contents=False):
  595. """
  596. Delete this container.
  597. """
  598. self._provider.swift.delete_container(self.name)
  599. def create_object(self, object_name):
  600. self._provider.swift.put_object(self.name, object_name, None)
  601. return self.get(object_name)
  602. def __repr__(self):
  603. return "<CB-OpenStackContainer: {0}>".format(self.name)