base.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. """
  2. Implementation of common methods across cloud providers.
  3. """
  4. import logging
  5. import time
  6. import six
  7. from cloudbridge.cloud.interfaces import CloudProvider
  8. from cloudbridge.cloud.interfaces.resources \
  9. import InvalidConfigurationException
  10. from cloudbridge.cloud.interfaces.resources import Container
  11. from cloudbridge.cloud.interfaces.resources import ContainerObject
  12. from cloudbridge.cloud.interfaces.resources import Instance
  13. from cloudbridge.cloud.interfaces.resources import InstanceState
  14. from cloudbridge.cloud.interfaces.resources import InstanceType
  15. from cloudbridge.cloud.interfaces.resources import KeyPair
  16. from cloudbridge.cloud.interfaces.resources import LaunchConfig
  17. from cloudbridge.cloud.interfaces.resources import MachineImage
  18. from cloudbridge.cloud.interfaces.resources import MachineImageState
  19. from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
  20. from cloudbridge.cloud.interfaces.resources import PageableObjectMixin
  21. from cloudbridge.cloud.interfaces.resources import PlacementZone
  22. from cloudbridge.cloud.interfaces.resources import Region
  23. from cloudbridge.cloud.interfaces.resources import ResultList
  24. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  25. from cloudbridge.cloud.interfaces.resources import SecurityGroupRule
  26. from cloudbridge.cloud.interfaces.resources import Snapshot
  27. from cloudbridge.cloud.interfaces.resources import SnapshotState
  28. from cloudbridge.cloud.interfaces.resources import Volume
  29. from cloudbridge.cloud.interfaces.resources import VolumeState
  30. from cloudbridge.cloud.interfaces.resources import WaitStateException
  31. from cloudbridge.cloud.interfaces.services import BlockStoreService
  32. from cloudbridge.cloud.interfaces.services import ComputeService
  33. from cloudbridge.cloud.interfaces.services import ImageService
  34. from cloudbridge.cloud.interfaces.services import InstanceService
  35. from cloudbridge.cloud.interfaces.services import InstanceTypesService
  36. from cloudbridge.cloud.interfaces.services import KeyPairService
  37. from cloudbridge.cloud.interfaces.services import ObjectStoreService
  38. from cloudbridge.cloud.interfaces.services import ProviderService
  39. from cloudbridge.cloud.interfaces.services import RegionService
  40. from cloudbridge.cloud.interfaces.services import SecurityGroupService
  41. from cloudbridge.cloud.interfaces.services import SecurityService
  42. from cloudbridge.cloud.interfaces.services import SnapshotService
  43. from cloudbridge.cloud.interfaces.services import VolumeService
  44. log = logging.getLogger(__name__)
  45. DEFAULT_RESULT_LIMIT = 50
  46. class BaseConfiguration(dict):
  47. def __init__(self, user_config):
  48. self.update(user_config)
  49. @property
  50. def result_limit(self):
  51. """
  52. Get the maximum number of results to return for a
  53. list method
  54. :rtype: ``int``
  55. :return: The maximum number of results to return
  56. """
  57. return self.get('result_limit', DEFAULT_RESULT_LIMIT)
  58. class BaseCloudProvider(CloudProvider):
  59. def __init__(self, config):
  60. self._config = BaseConfiguration(config)
  61. @property
  62. def config(self):
  63. return self._config
  64. @property
  65. def name(self):
  66. return str(self.__class__.__name__)
  67. def has_service(self, service_type):
  68. """
  69. Checks whether this provider supports a given service.
  70. :type service_type: str or :class:``.CloudProviderServiceType``
  71. :param service_type: Type of service to check support for.
  72. :rtype: bool
  73. :return: ``True`` if the service type is supported.
  74. """
  75. try:
  76. if getattr(self, service_type):
  77. return True
  78. except AttributeError:
  79. pass # Undefined service type
  80. return False
  81. def _get_config_value(self, key, default_value):
  82. """
  83. A convenience method to extract a configuration value.
  84. :type key: str
  85. :param key: a field to look for in the ``self.config`` field
  86. :type default_value: anything
  87. : param default_value: the default value to return if a value for the
  88. ``key`` is not available
  89. :return: a configuration value for the supplied ``key``
  90. """
  91. if isinstance(self.config, dict):
  92. return self.config.get(key, default_value)
  93. else:
  94. return getattr(self.config, key) if hasattr(
  95. self.config, key) and getattr(self.config, key) else \
  96. default_value
  97. class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
  98. """
  99. A base implementation of an ObjectLifeCycleMixin.
  100. This base implementation has an implementation of wait_for
  101. which refreshes the object's state till the desired ready states
  102. are reached. Subclasses must still implement the wait_till_ready
  103. method, since the desired ready states are object specific.
  104. """
  105. def wait_for(self, target_states, terminal_states=None, timeout=600,
  106. interval=5):
  107. assert timeout >= 0
  108. assert interval >= 0
  109. assert timeout >= interval
  110. end_time = time.time() + timeout
  111. while self.state not in target_states:
  112. if self.state in (terminal_states or []):
  113. raise WaitStateException(
  114. "Object: {0} is in state: {1} which is a terminal state"
  115. " and cannot be waited on.".format(self, self.state))
  116. else:
  117. log.debug(
  118. "Object %s is in state: %s. Waiting another %s"
  119. " seconds to reach target state(s): %s...",
  120. self,
  121. self.state,
  122. int(end_time - time.time()),
  123. target_states)
  124. time.sleep(interval)
  125. if time.time() > end_time:
  126. raise WaitStateException(
  127. "Waited too long for object: {0} to become ready. It's"
  128. " still in state: {1}".format(self, self.state))
  129. self.refresh()
  130. log.debug("Object: %s successfully reached target state: %s",
  131. self, self.state)
  132. return True
  133. class BaseResultList(ResultList):
  134. def __init__(
  135. self, is_truncated, marker, supports_total, total=None, data=None):
  136. super(BaseResultList, self).__init__(data or [])
  137. self._marker = marker
  138. self._is_truncated = is_truncated
  139. self._supports_total = True if supports_total else False
  140. self._total = total
  141. @property
  142. def marker(self):
  143. return self._marker
  144. @property
  145. def is_truncated(self):
  146. return self._is_truncated
  147. @property
  148. def supports_total(self):
  149. return self._supports_total
  150. @property
  151. def total_results(self):
  152. return self._total
  153. class BasePageableObjectMixin(PageableObjectMixin):
  154. """
  155. A mixin to provide iteration capability for a class
  156. that support a list(limit, marker) method.
  157. """
  158. def __iter__(self):
  159. more_results = True
  160. marker = None
  161. while more_results:
  162. result_list = self.list(marker=marker)
  163. for result in result_list:
  164. yield result
  165. marker = result_list.marker
  166. more_results = result_list.is_truncated
  167. class BaseInstanceType(InstanceType):
  168. def __init__(self, provider):
  169. self._provider = provider
  170. def __eq__(self, other):
  171. return (isinstance(other, InstanceType) and
  172. self._provider == other._provider and
  173. self.id == other.id)
  174. @property
  175. def size_total_disk(self):
  176. return self.size_root_disk + self.size_ephemeral_disks
  177. def __repr__(self):
  178. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  179. self.name, self.id)
  180. class BaseInstance(BaseObjectLifeCycleMixin, Instance):
  181. def __init__(self, provider):
  182. self._provider = provider
  183. def __eq__(self, other):
  184. return (isinstance(other, Instance) and
  185. self._provider == other._provider and
  186. self.id == other.id and
  187. # check from most to least likely mutables
  188. self.state == other.state and
  189. self.name == other.name and
  190. self.security_groups == other.security_groups and
  191. self.public_ips == other.public_ips and
  192. self.private_ips == other.private_ips and
  193. self.image_id == other.image_id)
  194. def wait_till_ready(self, timeout=600, interval=5):
  195. self.wait_for(
  196. [InstanceState.RUNNING],
  197. terminal_states=[InstanceState.TERMINATED, InstanceState.ERROR],
  198. timeout=timeout,
  199. interval=interval)
  200. def __repr__(self):
  201. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  202. self.name, self.id)
  203. class BaseLaunchConfig(LaunchConfig):
  204. def __init__(self, provider):
  205. self.provider = provider
  206. self.block_devices = []
  207. self.net_ids = []
  208. class BlockDeviceMapping(object):
  209. """
  210. Represents a block device mapping
  211. """
  212. def __init__(self, is_volume=False, source=None, is_root=None,
  213. size=None, delete_on_terminate=None):
  214. self.is_volume = is_volume
  215. self.source = source
  216. self.is_root = is_root
  217. self.size = size
  218. self.delete_on_terminate = delete_on_terminate
  219. def __repr__(self):
  220. return "<CB-{0}: Dest: {1}, Src: {2}, IsRoot: {3}, Size: {4}>" \
  221. .format(self.__class__.__name__,
  222. "volume" if self.is_volume else "ephemeral",
  223. self.source, self.is_root, self.size)
  224. def add_ephemeral_device(self):
  225. block_device = BaseLaunchConfig.BlockDeviceMapping()
  226. self.block_devices.append(block_device)
  227. def add_volume_device(self, source=None, is_root=None, size=None,
  228. delete_on_terminate=None):
  229. block_device = self._validate_volume_device(
  230. source=source, is_root=is_root, size=size,
  231. delete_on_terminate=delete_on_terminate)
  232. self.block_devices.append(block_device)
  233. def _validate_volume_device(self, source=None, is_root=None,
  234. size=None, delete_on_terminate=None):
  235. """
  236. Validates a volume based device and throws an
  237. InvalidConfigurationException if the configuration is incorrect.
  238. """
  239. if source is None and not size:
  240. raise InvalidConfigurationException(
  241. "A size must be specified for a blank new volume")
  242. if source and \
  243. not isinstance(source, (Snapshot, Volume, MachineImage)):
  244. raise InvalidConfigurationException(
  245. "Source must be a Snapshot, Volume, MachineImage or None")
  246. if size:
  247. if not isinstance(size, six.integer_types) or not size > 0:
  248. raise InvalidConfigurationException(
  249. "The size must be None or a number greater than 0")
  250. if is_root:
  251. for bd in self.block_devices:
  252. if bd.is_root:
  253. raise InvalidConfigurationException(
  254. "An existing block device: {0} has already been"
  255. " marked as root. There can only be one root device.")
  256. return BaseLaunchConfig.BlockDeviceMapping(
  257. is_volume=True, source=source, is_root=is_root, size=size,
  258. delete_on_terminate=delete_on_terminate)
  259. def add_network_interface(self, net_id):
  260. self.net_ids.append(net_id)
  261. class BaseMachineImage(BaseObjectLifeCycleMixin, MachineImage):
  262. def __init__(self, provider):
  263. self._provider = provider
  264. def __eq__(self, other):
  265. return (isinstance(other, MachineImage) and
  266. self._provider == other._provider and
  267. self.id == other.id and
  268. # check from most to least likely mutables
  269. self.state == other.state and
  270. self.name == other.name and
  271. self.description == other.description)
  272. def wait_till_ready(self, timeout=600, interval=5):
  273. self.wait_for(
  274. [MachineImageState.AVAILABLE],
  275. terminal_states=[MachineImageState.ERROR],
  276. timeout=timeout,
  277. interval=interval)
  278. def __repr__(self):
  279. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  280. self.name, self.id)
  281. class BaseVolume(BaseObjectLifeCycleMixin, Volume):
  282. def __init__(self, provider):
  283. self._provider = provider
  284. def __eq__(self, other):
  285. return (isinstance(other, Volume) and
  286. self._provider == other._provider and
  287. self.id == other.id and
  288. # check from most to least likely mutables
  289. self.state == other.state and
  290. self.name == other.name)
  291. def wait_till_ready(self, timeout=600, interval=5):
  292. self.wait_for(
  293. [VolumeState.AVAILABLE],
  294. terminal_states=[VolumeState.ERROR, VolumeState.DELETED],
  295. timeout=timeout,
  296. interval=interval)
  297. def __repr__(self):
  298. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  299. self.name, self.id)
  300. class BaseSnapshot(BaseObjectLifeCycleMixin, Snapshot):
  301. def __init__(self, provider):
  302. self._provider = provider
  303. def __eq__(self, other):
  304. return (isinstance(other, Snapshot) and
  305. self._provider == other._provider and
  306. self.id == other.id and
  307. # check from most to least likely mutables
  308. self.state == other.state and
  309. self.name == other.name)
  310. def wait_till_ready(self, timeout=600, interval=5):
  311. self.wait_for(
  312. [SnapshotState.AVAILABLE],
  313. terminal_states=[SnapshotState.ERROR],
  314. timeout=timeout,
  315. interval=interval)
  316. def __repr__(self):
  317. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  318. self.name, self.id)
  319. class BaseKeyPair(KeyPair):
  320. def __init__(self, provider, key_pair):
  321. self._provider = provider
  322. self._key_pair = key_pair
  323. def __eq__(self, other):
  324. return isinstance(other, KeyPair) and \
  325. self._provider == other._provider and \
  326. self.name == other.name
  327. @property
  328. def id(self):
  329. """
  330. Return the id of this key pair.
  331. """
  332. return self._key_pair.name
  333. @property
  334. def name(self):
  335. """
  336. Return the name of this key pair.
  337. """
  338. return self._key_pair.name
  339. def delete(self):
  340. """
  341. Delete this KeyPair.
  342. :rtype: bool
  343. :return: True if successful, otherwise False.
  344. """
  345. # This implementation assumes the `delete` method exists across
  346. # multiple providers.
  347. self._key_pair.delete()
  348. def __repr__(self):
  349. return "<CBKeyPair: {0}>".format(self.name)
  350. class BaseSecurityGroup(SecurityGroup):
  351. def __init__(self, provider, security_group):
  352. self._provider = provider
  353. self._security_group = security_group
  354. def __eq__(self, other):
  355. """
  356. Check if all the defined rules match across both security groups.
  357. """
  358. return (isinstance(other, SecurityGroup) and
  359. self._provider == other._provider and
  360. len(self.rules) == len(other.rules) and # Shortcut
  361. set(self.rules) == set(other.rules))
  362. def __ne__(self, other):
  363. return not self.__eq__(other)
  364. @property
  365. def id(self):
  366. """
  367. Get the ID of this security group.
  368. :rtype: str
  369. :return: Security group ID
  370. """
  371. return self._security_group.id
  372. @property
  373. def name(self):
  374. """
  375. Return the name of this security group.
  376. """
  377. return self._security_group.name
  378. @property
  379. def description(self):
  380. """
  381. Return the description of this security group.
  382. """
  383. return self._security_group.description
  384. def delete(self):
  385. """
  386. Delete this security group.
  387. """
  388. return self._security_group.delete()
  389. def __repr__(self):
  390. return "<CBSecurityGroup: {0}>".format(self.name)
  391. class BaseSecurityGroupRule(SecurityGroupRule):
  392. def __init__(self, provider, rule, parent):
  393. self._provider = provider
  394. self._rule = rule
  395. self.parent = parent
  396. def __repr__(self):
  397. return "<CBSecurityGroupRule: IP: {0}; from: {1}; to: {2}>".format(
  398. self.ip_protocol, self.from_port, self.to_port)
  399. def __eq__(self, other):
  400. return self.ip_protocol == other.ip_protocol and \
  401. self.from_port == other.from_port and \
  402. self.to_port == other.to_port and \
  403. self.cidr_ip == other.cidr_ip
  404. def __ne__(self, other):
  405. return not self.__eq__(other)
  406. def __hash__(self):
  407. """
  408. Return a hash-based interpretation of all of the object's field values.
  409. This is requried for operations on hashed collections including
  410. ``set``, ``frozenset``, and ``dict``.
  411. """
  412. return hash("{0}{1}{2}{3}{4}".format(self.ip_protocol, self.from_port,
  413. self.to_port, self.cidr_ip,
  414. self.group))
  415. class BasePlacementZone(PlacementZone):
  416. def __init__(self, provider):
  417. self._provider = provider
  418. def __repr__(self):
  419. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  420. self.id)
  421. def __eq__(self, other):
  422. return (isinstance(other, PlacementZone) and
  423. self._provider == other._provider and
  424. self.id == other.id)
  425. class BaseRegion(Region):
  426. def __init__(self, provider):
  427. self._provider = provider
  428. def __repr__(self):
  429. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  430. self.id)
  431. def __eq__(self, other):
  432. return (isinstance(other, Region) and
  433. self._provider == other._provider and
  434. self.id == other.id)
  435. class BaseContainerObject(ContainerObject):
  436. def __init__(self, provider):
  437. self._provider = provider
  438. def __eq__(self, other):
  439. return (isinstance(other, ContainerObject) and
  440. self._provider == other._provider and
  441. self.id == other.id and
  442. # check from most to least likely mutables
  443. self.name == other.name)
  444. def __repr__(self):
  445. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  446. self.name)
  447. class BaseContainer(BasePageableObjectMixin, Container):
  448. def __init__(self, provider):
  449. self._provider = provider
  450. def __eq__(self, other):
  451. return (isinstance(other, Container) and
  452. self._provider == other._provider and
  453. self.id == other.id and
  454. # check from most to least likely mutables
  455. self.name == other.name)
  456. def __repr__(self):
  457. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  458. self.name)
  459. class BaseProviderService(ProviderService):
  460. def __init__(self, provider):
  461. self._provider = provider
  462. @property
  463. def provider(self):
  464. return self._provider
  465. class BaseComputeService(ComputeService, BaseProviderService):
  466. def __init__(self, provider):
  467. super(BaseComputeService, self).__init__(provider)
  468. class BaseVolumeService(
  469. BasePageableObjectMixin, VolumeService, BaseProviderService):
  470. def __init__(self, provider):
  471. super(BaseVolumeService, self).__init__(provider)
  472. class BaseSnapshotService(
  473. BasePageableObjectMixin, SnapshotService, BaseProviderService):
  474. def __init__(self, provider):
  475. super(BaseSnapshotService, self).__init__(provider)
  476. class BaseBlockStoreService(BlockStoreService, BaseProviderService):
  477. def __init__(self, provider):
  478. super(BaseBlockStoreService, self).__init__(provider)
  479. class BaseImageService(
  480. BasePageableObjectMixin, ImageService, BaseProviderService):
  481. def __init__(self, provider):
  482. super(BaseImageService, self).__init__(provider)
  483. class BaseObjectStoreService(
  484. BasePageableObjectMixin, ObjectStoreService, BaseProviderService):
  485. def __init__(self, provider):
  486. super(BaseObjectStoreService, self).__init__(provider)
  487. class BaseSecurityService(SecurityService, BaseProviderService):
  488. def __init__(self, provider):
  489. super(BaseSecurityService, self).__init__(provider)
  490. class BaseKeyPairService(
  491. BasePageableObjectMixin, KeyPairService, BaseProviderService):
  492. def __init__(self, provider):
  493. super(BaseKeyPairService, self).__init__(provider)
  494. def delete(self, name):
  495. """
  496. Delete an existing key pair.
  497. :type name: str
  498. :param name: The name of the key pair to be deleted.
  499. :rtype: ``bool``
  500. :return: ``True`` if the key does not exist. Note that this implies
  501. that the key may not have been deleted by this method but
  502. instead has not existed in the first place.
  503. """
  504. kp = self.find(name=name)
  505. if kp:
  506. kp.delete()
  507. return True
  508. class BaseSecurityGroupService(
  509. BasePageableObjectMixin, SecurityGroupService, BaseProviderService):
  510. def __init__(self, provider):
  511. super(BaseSecurityGroupService, self).__init__(provider)
  512. class BaseInstanceTypesService(
  513. BasePageableObjectMixin, InstanceTypesService, BaseProviderService):
  514. def __init__(self, provider):
  515. super(BaseInstanceTypesService, self).__init__(provider)
  516. def find(self, **kwargs):
  517. name = kwargs.get('name')
  518. if name:
  519. return (itype for itype in self.list() if itype.name == name)
  520. else:
  521. raise TypeError(
  522. "Invalid parameters for search. Supported attributes: {name}")
  523. class BaseInstanceService(
  524. BasePageableObjectMixin, InstanceService, BaseProviderService):
  525. def __init__(self, provider):
  526. super(BaseInstanceService, self).__init__(provider)
  527. class BaseRegionService(
  528. BasePageableObjectMixin, RegionService, BaseProviderService):
  529. def __init__(self, provider):
  530. super(BaseRegionService, self).__init__(provider)