base.py 26 KB

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