base.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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 Instance
  11. from cloudbridge.cloud.interfaces.resources import InstanceState
  12. from cloudbridge.cloud.interfaces.resources import InstanceType
  13. from cloudbridge.cloud.interfaces.resources import KeyPair
  14. from cloudbridge.cloud.interfaces.resources import LaunchConfig
  15. from cloudbridge.cloud.interfaces.resources import MachineImage
  16. from cloudbridge.cloud.interfaces.resources import MachineImageState
  17. from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
  18. from cloudbridge.cloud.interfaces.resources import Region
  19. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  20. from cloudbridge.cloud.interfaces.resources import SecurityGroupRule
  21. from cloudbridge.cloud.interfaces.resources import Snapshot
  22. from cloudbridge.cloud.interfaces.resources import SnapshotState
  23. from cloudbridge.cloud.interfaces.resources import Volume
  24. from cloudbridge.cloud.interfaces.resources import VolumeState
  25. from cloudbridge.cloud.interfaces.resources import WaitStateException
  26. from cloudbridge.cloud.interfaces.services import BlockStoreService
  27. from cloudbridge.cloud.interfaces.services import ComputeService
  28. from cloudbridge.cloud.interfaces.services import ImageService
  29. from cloudbridge.cloud.interfaces.services import InstanceService
  30. from cloudbridge.cloud.interfaces.services import InstanceTypesService
  31. from cloudbridge.cloud.interfaces.services import KeyPairService
  32. from cloudbridge.cloud.interfaces.services import ObjectStoreService
  33. from cloudbridge.cloud.interfaces.services import ProviderService
  34. from cloudbridge.cloud.interfaces.services import RegionService
  35. from cloudbridge.cloud.interfaces.services import SecurityGroupService
  36. from cloudbridge.cloud.interfaces.services import SecurityService
  37. from cloudbridge.cloud.interfaces.services import SnapshotService
  38. from cloudbridge.cloud.interfaces.services import VolumeService
  39. log = logging.getLogger(__name__)
  40. class BaseCloudProvider(CloudProvider):
  41. def __init__(self, config):
  42. self._config = config
  43. @property
  44. def config(self):
  45. return self._config
  46. @config.setter
  47. def config(self, config):
  48. self._config = config
  49. @property
  50. def name(self):
  51. return str(self.__class__.__name__)
  52. def has_service(self, service_type):
  53. """
  54. Checks whether this provider supports a given service.
  55. :type service_type: str or :class:``.CloudProviderServiceType``
  56. :param service_type: Type of service to check support for.
  57. :rtype: bool
  58. :return: ``True`` if the service type is supported.
  59. """
  60. try:
  61. if getattr(self, service_type):
  62. return True
  63. except AttributeError:
  64. pass # Undefined service type
  65. return False
  66. def _get_config_value(self, key, default_value):
  67. """
  68. A convenience method to extract a configuration value.
  69. :type key: str
  70. :param key: a field to look for in the ``self.config`` field
  71. :type default_value: anything
  72. : param default_value: the default value to return if a value for the
  73. ``key`` is not available
  74. :return: a configuration value for the supplied ``key``
  75. """
  76. if isinstance(self.config, dict):
  77. return self.config.get(key, default_value)
  78. else:
  79. return getattr(self.config, key) if hasattr(
  80. self.config, key) and getattr(self.config, key) else \
  81. default_value
  82. class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
  83. """
  84. A base implementation of an ObjectLifeCycleMixin.
  85. This base implementation has an implementation of wait_till_ready
  86. which refreshes the object's state till the desired ready states
  87. are reached. Subclasses must implement two new properties - ready_states
  88. and terminal_states, which return a list of states to wait for.
  89. """
  90. @property
  91. def ready_states(self):
  92. raise NotImplementedError(
  93. "ready_states not implemented by this object. Subclasses must"
  94. " implement this method and return a valid set of ready states")
  95. @property
  96. def terminal_states(self):
  97. raise NotImplementedError(
  98. "terminal_states not implemented by this object. Subclasses must"
  99. " implement this method and return a valid set of terminal states")
  100. def wait_for(self, target_states, terminal_states=None, timeout=600,
  101. interval=5):
  102. assert timeout > 0
  103. assert timeout > interval
  104. end_time = time.time() + timeout
  105. while True:
  106. if self.state in target_states:
  107. log.debug(
  108. "Object: {0} successfully reached target state:"
  109. " {1}".format(self, self.state))
  110. return True
  111. elif self.state in terminal_states:
  112. raise WaitStateException(
  113. "Object: {0} is in state: {1} which is a terminal state"
  114. " and cannot be waited on.".format(self, self.state))
  115. else:
  116. log.debug(
  117. "Object {0} is in state: {1}. Waiting another {2}"
  118. " seconds to reach target state(s): {3}...".format(
  119. self,
  120. self.state,
  121. int(end_time - time.time()),
  122. target_states))
  123. time.sleep(interval)
  124. if time.time() > end_time:
  125. raise WaitStateException(
  126. "Waited too long for object: {0} to become ready. It's"
  127. " still in state: {1}".format(self, self.state))
  128. self.refresh()
  129. def wait_till_ready(self, timeout=600, interval=5):
  130. self.wait_for(
  131. self.ready_states,
  132. self.terminal_states,
  133. timeout,
  134. interval)
  135. class BaseInstanceType(InstanceType):
  136. @property
  137. def size_total_disk(self):
  138. return self.size_root_disk + self.size_ephemeral_disks
  139. def __repr__(self):
  140. return "<CB-{0}: {1}>".format(self.__class__.__name__, self.name)
  141. class BaseInstance(BaseObjectLifeCycleMixin, Instance):
  142. @property
  143. def ready_states(self):
  144. return [InstanceState.RUNNING]
  145. @property
  146. def terminal_states(self):
  147. return [InstanceState.TERMINATED, InstanceState.ERROR]
  148. class BaseLaunchConfig(LaunchConfig):
  149. def __init__(self, provider):
  150. self.provider = provider
  151. self.block_devices = []
  152. self.net_ids = []
  153. class BlockDeviceMapping(object):
  154. """
  155. Represents a block device mapping
  156. """
  157. def __init__(self, is_volume=False, source=None, is_root=None,
  158. size=None, delete_on_terminate=None):
  159. self.is_volume = is_volume
  160. self.source = source
  161. self.is_root = is_root
  162. self.size = size
  163. self.delete_on_terminate = delete_on_terminate
  164. def __repr__(self):
  165. return "<CB-{0}: Dest: {1}, Src: {2}, IsRoot: {3}, Size: {4}>" \
  166. .format(self.__class__.__name__,
  167. "volume" if self.is_volume else "ephemeral",
  168. self.source, self.is_root, self.size)
  169. def add_ephemeral_device(self):
  170. block_device = BaseLaunchConfig.BlockDeviceMapping()
  171. self.block_devices.append(block_device)
  172. def add_volume_device(self, source=None, is_root=None, size=None,
  173. delete_on_terminate=None):
  174. block_device = self._validate_volume_device(
  175. source=source, is_root=is_root, size=size,
  176. delete_on_terminate=delete_on_terminate)
  177. self.block_devices.append(block_device)
  178. def _validate_volume_device(self, source=None, is_root=None,
  179. size=None, delete_on_terminate=None):
  180. """
  181. Validates a volume based device and throws an
  182. InvalidConfigurationException if the configuration is incorrect.
  183. """
  184. if source is None and not size:
  185. raise InvalidConfigurationException(
  186. "A size must be specified for a blank new volume")
  187. if source and \
  188. not isinstance(source, (Snapshot, Volume, MachineImage)):
  189. raise InvalidConfigurationException(
  190. "Source must be a Snapshot, Volume, MachineImage or None")
  191. if size:
  192. if not isinstance(size, six.integer_types) or not size > 0:
  193. raise InvalidConfigurationException(
  194. "The size must be None or a number greater than 0")
  195. if is_root:
  196. for bd in self.block_devices:
  197. if bd.is_root:
  198. raise InvalidConfigurationException(
  199. "An existing block device: {0} has already been"
  200. " marked as root. There can only be one root device.")
  201. return BaseLaunchConfig.BlockDeviceMapping(
  202. is_volume=True, source=source, is_root=is_root, size=size,
  203. delete_on_terminate=delete_on_terminate)
  204. def add_network_interface(self, net_id):
  205. self.net_ids.append(net_id)
  206. class BaseMachineImage(BaseObjectLifeCycleMixin, MachineImage):
  207. @property
  208. def ready_states(self):
  209. return [MachineImageState.AVAILABLE]
  210. @property
  211. def terminal_states(self):
  212. return [MachineImageState.ERROR]
  213. def __repr__(self):
  214. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  215. self.id, self.name)
  216. class BaseVolume(BaseObjectLifeCycleMixin, Volume):
  217. @property
  218. def ready_states(self):
  219. return [VolumeState.AVAILABLE]
  220. @property
  221. def terminal_states(self):
  222. return [VolumeState.ERROR, VolumeState.DELETED]
  223. class BaseSnapshot(BaseObjectLifeCycleMixin, Snapshot):
  224. @property
  225. def ready_states(self):
  226. return [SnapshotState.AVAILABLE]
  227. @property
  228. def terminal_states(self):
  229. return [SnapshotState.ERROR]
  230. class BaseKeyPair(KeyPair):
  231. def __init__(self, provider, key_pair):
  232. self._provider = provider
  233. self._key_pair = key_pair
  234. def __eq__(self, other):
  235. return isinstance(other, KeyPair) and \
  236. self._provider == other._provider and \
  237. self.name == other.name
  238. @property
  239. def name(self):
  240. """
  241. Return the name of this key pair.
  242. """
  243. return self._key_pair.name
  244. def delete(self):
  245. """
  246. Delete this KeyPair.
  247. :rtype: bool
  248. :return: True if successful, otherwise False.
  249. """
  250. # This implementation assumes the `delete` method exists across
  251. # multiple providers.
  252. self._key_pair.delete()
  253. def __repr__(self):
  254. return "<CBKeyPair: {0}>".format(self.name)
  255. class BaseSecurityGroup(SecurityGroup):
  256. def __init__(self, provider, security_group):
  257. self._provider = provider
  258. self._security_group = security_group
  259. def __eq__(self, other):
  260. """
  261. Check if all the defined rules match across both security groups.
  262. """
  263. return (isinstance(other, SecurityGroup) and
  264. self._provider == other._provider and
  265. len(self.rules) == len(other.rules) and # Shortcut
  266. set(self.rules) == set(other.rules))
  267. def __ne__(self, other):
  268. return not self.__eq__(other)
  269. def rule_exists(self, rules, rule):
  270. """
  271. Check if an authorization rule exists in a list of rules.
  272. :type rules: list of :class:``.SecurityGroupRule``
  273. :param rules: A list of rules to check against
  274. :type rule: :class:``.SecurityGroupRule``
  275. :param rule: A rule whose existence to check for
  276. :rtype: bool
  277. :return: ``True`` if an existing rule matches the supplied rule;
  278. ``False`` otherwise.
  279. """
  280. for r in rules:
  281. if r == rule:
  282. return True
  283. return False
  284. @property
  285. def id(self):
  286. """
  287. Get the ID of this security group.
  288. :rtype: str
  289. :return: Security group ID
  290. """
  291. return self._security_group.id
  292. @property
  293. def name(self):
  294. """
  295. Return the name of this security group.
  296. """
  297. return self._security_group.name
  298. @property
  299. def description(self):
  300. """
  301. Return the description of this security group.
  302. """
  303. return self._security_group.description
  304. def delete(self):
  305. """
  306. Delete this security group.
  307. """
  308. return self._security_group.delete()
  309. def __repr__(self):
  310. return "<CBSecurityGroup: {0}>".format(self.name)
  311. class BaseSecurityGroupRule(SecurityGroupRule):
  312. def __init__(self, provider, rule, parent):
  313. self._provider = provider
  314. self._rule = rule
  315. self.parent = parent
  316. def __repr__(self):
  317. return "<CBSecurityGroupRule: IP: {0}; from: {1}; to: {2}>".format(
  318. self.ip_protocol, self.from_port, self.to_port)
  319. def __eq__(self, other):
  320. return self.ip_protocol == other.ip_protocol and \
  321. self.from_port == other.from_port and \
  322. self.to_port == other.to_port and \
  323. self.cidr_ip == other.cidr_ip
  324. def __ne__(self, other):
  325. return not self.__eq__(other)
  326. def __hash__(self):
  327. """
  328. Return a hash-based interpretation of all of the object's field values.
  329. This is requried for operations on hashed collections including
  330. ``set``, ``frozenset``, and ``dict``.
  331. """
  332. return hash("{0}{1}{2}{3}{4}".format(self.ip_protocol, self.from_port,
  333. self.to_port, self.cidr_ip,
  334. self.group))
  335. class BaseRegion(Region):
  336. def __init__(self, provider, region):
  337. self._provider = provider
  338. self._region = region
  339. def __repr__(self):
  340. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  341. self.name)
  342. def __eq__(self, other):
  343. if isinstance(other, Region):
  344. return self._provider == other._provider and \
  345. self.id == other.id
  346. class BaseProviderService(ProviderService):
  347. def __init__(self, provider):
  348. self._provider = provider
  349. @property
  350. def provider(self):
  351. return self._provider
  352. class BaseComputeService(ComputeService, BaseProviderService):
  353. def __init__(self, provider):
  354. super(BaseComputeService, self).__init__(provider)
  355. class BaseVolumeService(VolumeService, BaseProviderService):
  356. def __init__(self, provider):
  357. super(BaseVolumeService, self).__init__(provider)
  358. class BaseSnapshotService(SnapshotService, BaseProviderService):
  359. def __init__(self, provider):
  360. super(BaseSnapshotService, self).__init__(provider)
  361. class BaseBlockStoreService(BlockStoreService, BaseProviderService):
  362. def __init__(self, provider):
  363. super(BaseBlockStoreService, self).__init__(provider)
  364. class BaseImageService(ImageService, BaseProviderService):
  365. def __init__(self, provider):
  366. super(BaseImageService, self).__init__(provider)
  367. class BaseObjectStoreService(ObjectStoreService, BaseProviderService):
  368. def __init__(self, provider):
  369. super(BaseObjectStoreService, self).__init__(provider)
  370. class BaseSecurityService(SecurityService, BaseProviderService):
  371. def __init__(self, provider):
  372. super(BaseSecurityService, self).__init__(provider)
  373. class BaseKeyPairService(KeyPairService, BaseProviderService):
  374. def __init__(self, provider):
  375. super(BaseKeyPairService, self).__init__(provider)
  376. class BaseSecurityGroupService(SecurityGroupService, BaseProviderService):
  377. def __init__(self, provider):
  378. super(BaseSecurityGroupService, self).__init__(provider)
  379. class BaseInstanceTypesService(InstanceTypesService, BaseProviderService):
  380. def __init__(self, provider):
  381. super(BaseInstanceTypesService, self).__init__(provider)
  382. def find(self, **kwargs):
  383. name = kwargs.get('name')
  384. if name:
  385. return (itype for itype in self.list() if itype.name == name)
  386. else:
  387. return None
  388. class BaseInstanceService(InstanceService, BaseProviderService):
  389. def __init__(self, provider):
  390. super(BaseInstanceService, self).__init__(provider)
  391. class BaseRegionService(RegionService, BaseProviderService):
  392. def __init__(self, provider):
  393. super(BaseRegionService, self).__init__(provider)