base.py 18 KB

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