resources.py 19 KB

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