resources.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. """
  2. Base implementation for data objects exposed through a provider or service
  3. """
  4. import inspect
  5. import itertools
  6. import logging
  7. import os
  8. import re
  9. import shutil
  10. import time
  11. import uuid
  12. import six
  13. import cloudbridge.cloud.base.helpers as cb_helpers
  14. from cloudbridge.cloud.interfaces.exceptions \
  15. import InvalidConfigurationException
  16. from cloudbridge.cloud.interfaces.exceptions import InvalidLabelException
  17. from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
  18. from cloudbridge.cloud.interfaces.exceptions import WaitStateException
  19. from cloudbridge.cloud.interfaces.resources import AttachmentInfo
  20. from cloudbridge.cloud.interfaces.resources import Bucket
  21. from cloudbridge.cloud.interfaces.resources import BucketContainer
  22. from cloudbridge.cloud.interfaces.resources import BucketObject
  23. from cloudbridge.cloud.interfaces.resources import CloudResource
  24. from cloudbridge.cloud.interfaces.resources import FloatingIP
  25. from cloudbridge.cloud.interfaces.resources import FloatingIPContainer
  26. from cloudbridge.cloud.interfaces.resources import FloatingIpState
  27. from cloudbridge.cloud.interfaces.resources import GatewayContainer
  28. from cloudbridge.cloud.interfaces.resources import GatewayState
  29. from cloudbridge.cloud.interfaces.resources import Instance
  30. from cloudbridge.cloud.interfaces.resources import InstanceState
  31. from cloudbridge.cloud.interfaces.resources import InternetGateway
  32. from cloudbridge.cloud.interfaces.resources import KeyPair
  33. from cloudbridge.cloud.interfaces.resources import LaunchConfig
  34. from cloudbridge.cloud.interfaces.resources import MachineImage
  35. from cloudbridge.cloud.interfaces.resources import MachineImageState
  36. from cloudbridge.cloud.interfaces.resources import Network
  37. from cloudbridge.cloud.interfaces.resources import NetworkState
  38. from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
  39. from cloudbridge.cloud.interfaces.resources import PageableObjectMixin
  40. from cloudbridge.cloud.interfaces.resources import PlacementZone
  41. from cloudbridge.cloud.interfaces.resources import Region
  42. from cloudbridge.cloud.interfaces.resources import ResultList
  43. from cloudbridge.cloud.interfaces.resources import Router
  44. from cloudbridge.cloud.interfaces.resources import Snapshot
  45. from cloudbridge.cloud.interfaces.resources import SnapshotState
  46. from cloudbridge.cloud.interfaces.resources import Subnet
  47. from cloudbridge.cloud.interfaces.resources import SubnetState
  48. from cloudbridge.cloud.interfaces.resources import VMFirewall
  49. from cloudbridge.cloud.interfaces.resources import VMFirewallRule
  50. from cloudbridge.cloud.interfaces.resources import VMFirewallRuleContainer
  51. from cloudbridge.cloud.interfaces.resources import VMType
  52. from cloudbridge.cloud.interfaces.resources import Volume
  53. from cloudbridge.cloud.interfaces.resources import VolumeState
  54. log = logging.getLogger(__name__)
  55. class BaseCloudResource(CloudResource):
  56. """
  57. Base implementation of a CloudBridge Resource.
  58. """
  59. # Regular expression for valid cloudbridge resource names/labels.
  60. # Can be alphanumeric string that does not start or end with a dash
  61. # Must be at least 3 characters in length.
  62. # Ref: https://stackoverflow.com/questions/2525327/regex-for-a-za-z0-9
  63. # -with-dashes-allowed-in-between-but-not-at-the-start-or-e
  64. CB_NAME_PATTERN = re.compile(r"^[a-z][-a-z0-9]{1,61}[a-z0-9]$")
  65. def __init__(self, provider):
  66. self.__provider = provider
  67. @staticmethod
  68. def is_valid_resource_name(name):
  69. if not name:
  70. return False
  71. else:
  72. return (True if BaseCloudResource.CB_NAME_PATTERN.match(name)
  73. else False)
  74. @staticmethod
  75. def assert_valid_resource_label(name):
  76. if not BaseCloudResource.is_valid_resource_name(name):
  77. log.debug("InvalidLabelException raised on %s", name)
  78. raise InvalidLabelException(
  79. u"Invalid label: %s. Label must be at least 3 characters long"
  80. " and at most 63 characters. It must consist of lowercase"
  81. " letters, numbers, or dashes. The label must start with a "
  82. "letter and not end with a dash." % name)
  83. @staticmethod
  84. def assert_valid_resource_name(name):
  85. if not BaseCloudResource.is_valid_resource_name(name):
  86. log.debug("InvalidLabelException raised on %s", name)
  87. raise InvalidNameException(
  88. u"Invalid name: %s. Name must be at least 3 characters long"
  89. " and at most 63 characters. It must consist of lowercase"
  90. " letters, numbers, or dashes. The name must not start or"
  91. " end with a dash." % name)
  92. @staticmethod
  93. def _generate_name_from_label(label, default):
  94. if not label:
  95. label = default
  96. name = label[:55] + '-' + uuid.uuid4().hex[:6]
  97. BaseCloudResource.assert_valid_resource_name(name)
  98. return name
  99. @property
  100. def _provider(self):
  101. return self.__provider
  102. def to_json(self):
  103. # Get all attributes but filter methods and private/magic ones
  104. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  105. js = {k: v for(k, v) in attr if not k.startswith('_')}
  106. return js
  107. def __repr__(self):
  108. name_or_label = getattr(self, 'label', self.name)
  109. if name_or_label == self.id:
  110. return "<CB-{0}: {1}>".format(
  111. self.__class__.__name__, self.id)
  112. else:
  113. return "<CB-{0}: {1} ({2})>".format(
  114. self.__class__.__name__, name_or_label, self.id)
  115. class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
  116. """
  117. A base implementation of an ObjectLifeCycleMixin.
  118. This base implementation has an implementation of wait_for
  119. which refreshes the object's state till the desired ready states
  120. are reached. Subclasses must still implement the wait_till_ready
  121. method, since the desired ready states are object specific.
  122. """
  123. def wait_for(self, target_states, terminal_states=None, timeout=None,
  124. interval=None):
  125. if timeout is None:
  126. timeout = self._provider.config.default_wait_timeout
  127. if interval is None:
  128. interval = self._provider.config.default_wait_interval
  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. result_list = self.list()
  230. if result_list.supports_server_paging:
  231. for result in result_list:
  232. yield result
  233. while result_list.is_truncated:
  234. result_list = self.list(marker=result_list.marker)
  235. for result in result_list:
  236. yield result
  237. else:
  238. for result in result_list.data:
  239. yield result
  240. class BaseVMType(BaseCloudResource, VMType):
  241. def __init__(self, provider):
  242. super(BaseVMType, self).__init__(provider)
  243. def __eq__(self, other):
  244. return (isinstance(other, VMType) and
  245. # pylint:disable=protected-access
  246. self._provider == other._provider and
  247. self.id == other.id)
  248. @property
  249. def size_total_disk(self):
  250. return self.size_root_disk + self.size_ephemeral_disks
  251. class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
  252. def __init__(self, provider):
  253. super(BaseInstance, self).__init__(provider)
  254. def __eq__(self, other):
  255. return (isinstance(other, Instance) and
  256. # pylint:disable=protected-access
  257. self._provider == other._provider and
  258. self.id == other.id and
  259. # check from most to least likely mutables
  260. self.state == other.state and
  261. self.label == other.label and
  262. self.vm_firewalls == other.vm_firewalls and
  263. self.public_ips == other.public_ips and
  264. self.private_ips == other.private_ips and
  265. self.image_id == other.image_id)
  266. def wait_till_ready(self, timeout=None, interval=None):
  267. self.wait_for(
  268. [InstanceState.RUNNING],
  269. terminal_states=[InstanceState.DELETED, InstanceState.ERROR],
  270. timeout=timeout,
  271. interval=interval)
  272. class BaseLaunchConfig(LaunchConfig):
  273. def __init__(self, provider):
  274. self.provider = provider
  275. self.block_devices = []
  276. class BlockDeviceMapping(object):
  277. """
  278. Represents a block device mapping
  279. """
  280. def __init__(self, is_volume=False, source=None, is_root=None,
  281. size=None, delete_on_terminate=None):
  282. self.is_volume = is_volume
  283. self.source = source
  284. self.is_root = is_root
  285. self.size = size
  286. self.delete_on_terminate = delete_on_terminate
  287. def add_ephemeral_device(self):
  288. block_device = BaseLaunchConfig.BlockDeviceMapping()
  289. self.block_devices.append(block_device)
  290. def add_volume_device(self, source=None, is_root=None, size=None,
  291. delete_on_terminate=None):
  292. block_device = self._validate_volume_device(
  293. source=source, is_root=is_root, size=size,
  294. delete_on_terminate=delete_on_terminate)
  295. log.debug("Appending %s to the block_devices list",
  296. block_device)
  297. self.block_devices.append(block_device)
  298. def _validate_volume_device(self, source=None, is_root=None,
  299. size=None, delete_on_terminate=None):
  300. """
  301. Validates a volume based device and throws an
  302. InvalidConfigurationException if the configuration is incorrect.
  303. """
  304. if source is None and not size:
  305. log.exception("InvalidConfigurationException raised: "
  306. "no size argument specified.")
  307. raise InvalidConfigurationException(
  308. "A size must be specified for a blank new volume.")
  309. if source and \
  310. not isinstance(source, (Snapshot, Volume, MachineImage)):
  311. log.exception("InvalidConfigurationException raised: "
  312. "source argument not specified correctly.")
  313. raise InvalidConfigurationException(
  314. "Source must be a Snapshot, Volume, MachineImage, or None.")
  315. if size:
  316. if not isinstance(size, six.integer_types) or not size > 0:
  317. log.exception("InvalidConfigurationException raised: "
  318. "size argument must be an integer greater than "
  319. "0. Got type %s and value %s.", type(size), size)
  320. raise InvalidConfigurationException(
  321. "The size must be None or an integer greater than 0.")
  322. if is_root:
  323. for bd in self.block_devices:
  324. if bd.is_root:
  325. log.exception("InvalidConfigurationException raised: "
  326. "%s has already been marked as the root "
  327. "block device.", bd)
  328. raise InvalidConfigurationException(
  329. "An existing block device: {0} has already been"
  330. " marked as root. There can only be one root device.")
  331. return BaseLaunchConfig.BlockDeviceMapping(
  332. is_volume=True, source=source, is_root=is_root, size=size,
  333. delete_on_terminate=delete_on_terminate)
  334. class BaseMachineImage(
  335. BaseCloudResource, BaseObjectLifeCycleMixin, MachineImage):
  336. def __init__(self, provider):
  337. super(BaseMachineImage, self).__init__(provider)
  338. def __eq__(self, other):
  339. return (isinstance(other, MachineImage) and
  340. # pylint:disable=protected-access
  341. self._provider == other._provider and
  342. self.id == other.id and
  343. # check from most to least likely mutables
  344. self.state == other.state and
  345. self.label == other.label and
  346. self.description == other.description)
  347. def wait_till_ready(self, timeout=None, interval=None):
  348. self.wait_for(
  349. [MachineImageState.AVAILABLE],
  350. terminal_states=[MachineImageState.ERROR],
  351. timeout=timeout,
  352. interval=interval)
  353. class BaseAttachmentInfo(AttachmentInfo):
  354. def __init__(self, volume, instance_id, device):
  355. self._volume = volume
  356. self._instance_id = instance_id
  357. self._device = device
  358. @property
  359. def volume(self):
  360. return self._volume
  361. @property
  362. def instance_id(self):
  363. return self._instance_id
  364. @property
  365. def device(self):
  366. return self._device
  367. class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
  368. def __init__(self, provider):
  369. super(BaseVolume, self).__init__(provider)
  370. def __eq__(self, other):
  371. return (isinstance(other, Volume) and
  372. # pylint:disable=protected-access
  373. self._provider == other._provider and
  374. self.id == other.id and
  375. # check from most to least likely mutables
  376. self.state == other.state and
  377. self.label == other.label)
  378. def wait_till_ready(self, timeout=None, interval=None):
  379. self.wait_for(
  380. [VolumeState.AVAILABLE],
  381. terminal_states=[VolumeState.ERROR, VolumeState.DELETED],
  382. timeout=timeout,
  383. interval=interval)
  384. class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
  385. def __init__(self, provider):
  386. super(BaseSnapshot, self).__init__(provider)
  387. def __eq__(self, other):
  388. return (isinstance(other, Snapshot) and
  389. # pylint:disable=protected-access
  390. self._provider == other._provider and
  391. self.id == other.id and
  392. # check from most to least likely mutables
  393. self.state == other.state and
  394. self.label == other.label)
  395. def wait_till_ready(self, timeout=None, interval=None):
  396. self.wait_for(
  397. [SnapshotState.AVAILABLE],
  398. terminal_states=[SnapshotState.ERROR],
  399. timeout=timeout,
  400. interval=interval)
  401. class BaseKeyPair(BaseCloudResource, KeyPair):
  402. def __init__(self, provider, key_pair):
  403. super(BaseKeyPair, self).__init__(provider)
  404. self._key_pair = key_pair
  405. self._private_material = None
  406. def __eq__(self, other):
  407. return (isinstance(other, KeyPair) and
  408. # pylint:disable=protected-access
  409. self._provider == other._provider and
  410. self.name == other.name)
  411. @property
  412. def id(self):
  413. """
  414. Return the id of this key pair.
  415. """
  416. return self._key_pair.name
  417. @property
  418. def name(self):
  419. """
  420. Return the name of this key pair.
  421. """
  422. return self.id
  423. @property
  424. def material(self):
  425. return self._private_material
  426. @material.setter
  427. # pylint:disable=arguments-differ
  428. def material(self, value):
  429. self._private_material = value
  430. def delete(self):
  431. """
  432. Delete this KeyPair.
  433. :rtype: bool
  434. :return: True if successful, otherwise False.
  435. """
  436. # This implementation assumes the `delete` method exists across
  437. # multiple providers.
  438. self._key_pair.delete()
  439. class BaseVMFirewall(BaseCloudResource, VMFirewall):
  440. def __init__(self, provider, vm_firewall):
  441. super(BaseVMFirewall, self).__init__(provider)
  442. self._vm_firewall = vm_firewall
  443. def __eq__(self, other):
  444. """
  445. Check if all the defined rules match across both VM firewalls.
  446. """
  447. return (isinstance(other, VMFirewall) and
  448. # pylint:disable=protected-access
  449. self._provider == other._provider and
  450. set(self.rules) == set(other.rules))
  451. def __ne__(self, other):
  452. return not self.__eq__(other)
  453. @property
  454. def id(self):
  455. """
  456. Get the ID of this VM firewall.
  457. :rtype: str
  458. :return: VM firewall ID
  459. """
  460. return self._vm_firewall.id
  461. @property
  462. def name(self):
  463. """
  464. Return the name of this VM firewall.
  465. """
  466. return self.id
  467. @property
  468. def description(self):
  469. """
  470. Return the description of this VM firewall.
  471. """
  472. return self._vm_firewall.description
  473. def delete(self):
  474. """
  475. Delete this VM firewall.
  476. """
  477. return self._vm_firewall.delete()
  478. class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
  479. VMFirewallRuleContainer):
  480. def __init__(self, provider, firewall):
  481. self.__provider = provider
  482. self.firewall = firewall
  483. @property
  484. def _provider(self):
  485. return self.__provider
  486. def get(self, rule_id):
  487. matches = [rule for rule in self if rule.id == rule_id]
  488. if matches:
  489. return matches[0]
  490. else:
  491. return None
  492. def find(self, **kwargs):
  493. obj_list = self
  494. filters = ['name', 'direction', 'protocol', 'from_port', 'to_port',
  495. 'cidr', 'src_dest_fw', 'src_dest_fw_id']
  496. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  497. return ClientPagedResultList(self._provider, list(matches))
  498. def delete(self, rule_id):
  499. rule = self.get(rule_id)
  500. if rule:
  501. rule.delete()
  502. class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
  503. def __init__(self, parent_fw, rule):
  504. # pylint:disable=protected-access
  505. super(BaseVMFirewallRule, self).__init__(
  506. parent_fw._provider)
  507. self.firewall = parent_fw
  508. self._rule = rule
  509. # Cache name
  510. self._name = "{0}-{1}-{2}-{3}-{4}-{5}".format(
  511. self.direction, self.protocol, self.from_port, self.to_port,
  512. self.cidr, self.src_dest_fw_id).lower()
  513. @property
  514. def name(self):
  515. return self._name
  516. def __repr__(self):
  517. return ("<{0}: id: {1}; direction: {2}; protocol: {3}; from: {4};"
  518. " to: {5}; cidr: {6}, src_dest_fw: {7}>"
  519. .format(self.__class__.__name__, self.id, self.direction,
  520. self.protocol, self.from_port, self.to_port, self.cidr,
  521. self.src_dest_fw_id))
  522. def __eq__(self, other):
  523. return (isinstance(other, VMFirewallRule) and
  524. self.direction == other.direction and
  525. self.protocol == other.protocol and
  526. self.from_port == other.from_port and
  527. self.to_port == other.to_port and
  528. self.cidr == other.cidr and
  529. self.src_dest_fw_id == other.src_dest_fw_id)
  530. def __ne__(self, other):
  531. return not self.__eq__(other)
  532. def __hash__(self):
  533. """
  534. Return a hash-based interpretation of all of the object's field values.
  535. This is requeried for operations on hashed collections including
  536. ``set``, ``frozenset``, and ``dict``.
  537. """
  538. return hash("{0}{1}{2}{3}{4}{5}".format(
  539. self.direction, self.protocol, self.from_port, self.to_port,
  540. self.cidr, self.src_dest_fw_id))
  541. def to_json(self):
  542. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  543. js = {k: v for (k, v) in attr if not k.startswith('_')}
  544. js['src_dest_fw'] = self.src_dest_fw_id
  545. js['firewall'] = self.firewall.id
  546. return js
  547. class BasePlacementZone(BaseCloudResource, PlacementZone):
  548. def __init__(self, provider):
  549. super(BasePlacementZone, self).__init__(provider)
  550. def __eq__(self, other):
  551. return (isinstance(other, PlacementZone) and
  552. # pylint:disable=protected-access
  553. self._provider == other._provider and
  554. self.id == other.id)
  555. class BaseRegion(BaseCloudResource, Region):
  556. def __init__(self, provider):
  557. super(BaseRegion, self).__init__(provider)
  558. def __eq__(self, other):
  559. return (isinstance(other, Region) and
  560. # pylint:disable=protected-access
  561. self._provider == other._provider and
  562. self.id == other.id)
  563. def to_json(self):
  564. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  565. js = {k: v for(k, v) in attr if not k.startswith('_')}
  566. js['zones'] = [z.id for z in self.zones]
  567. return js
  568. class BaseBucketObject(BaseCloudResource, BucketObject):
  569. # Regular expression for valid bucket keys.
  570. # They, must match the following criteria: http://docs.aws.amazon.com/"
  571. # AmazonS3/latest/dev/UsingMetadata.html#object-key-guidelines
  572. #
  573. # Note: The following regex is based on: https://stackoverflow.com/question
  574. # s/537772/what-is-the-most-correct-regular-expression-for-a-unix-file-path
  575. CB_NAME_PATTERN = re.compile(r"[^\0]+")
  576. def __init__(self, provider):
  577. super(BaseBucketObject, self).__init__(provider)
  578. @staticmethod
  579. def is_valid_resource_name(name):
  580. return (True if BaseBucketObject.CB_NAME_PATTERN.match(name)
  581. else False)
  582. @staticmethod
  583. def assert_valid_resource_name(name):
  584. if not BaseBucketObject.is_valid_resource_name(name):
  585. log.debug("InvalidLabelException raised on %s", name,
  586. exc_info=True)
  587. raise InvalidLabelException(
  588. u"Invalid object name: %s. Name must match criteria defined "
  589. "in: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMeta"
  590. "data.html#object-key-guidelines" % name)
  591. def save_content(self, target_stream):
  592. shutil.copyfileobj(self.iter_content(), target_stream)
  593. def __eq__(self, other):
  594. return (isinstance(other, BucketObject) and
  595. # pylint:disable=protected-access
  596. self._provider == other._provider and
  597. self.id == other.id and
  598. # check from most to least likely mutables
  599. self.name == other.name)
  600. class BaseBucket(BaseCloudResource, Bucket):
  601. def __init__(self, provider):
  602. super(BaseBucket, self).__init__(provider)
  603. def __eq__(self, other):
  604. return (isinstance(other, Bucket) and
  605. # pylint:disable=protected-access
  606. self._provider == other._provider and
  607. self.id == other.id and
  608. # check from most to least likely mutables
  609. self.name == other.name)
  610. class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
  611. def __init__(self, provider, bucket):
  612. self.__provider = provider
  613. self.bucket = bucket
  614. @property
  615. def _provider(self):
  616. return self.__provider
  617. class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
  618. def __init__(self, provider, network):
  619. self._network = network
  620. self._provider = provider
  621. class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
  622. CB_DEFAULT_NETWORK_LABEL = os.environ.get('CB_DEFAULT_NETWORK_LABEL',
  623. 'cloudbridge-net')
  624. def __init__(self, provider):
  625. super(BaseNetwork, self).__init__(provider)
  626. def wait_till_ready(self, timeout=None, interval=None):
  627. self.wait_for(
  628. [NetworkState.AVAILABLE],
  629. terminal_states=[NetworkState.ERROR],
  630. timeout=timeout,
  631. interval=interval)
  632. def create_subnet(self, label, cidr_block, zone=None):
  633. return self._provider.networking.subnets.create(
  634. label=label, network=self, cidr_block=cidr_block, zone=zone)
  635. def __eq__(self, other):
  636. return (isinstance(other, Network) and
  637. # pylint:disable=protected-access
  638. self._provider == other._provider and
  639. self.id == other.id)
  640. class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
  641. CB_DEFAULT_SUBNET_LABEL = os.environ.get('CB_DEFAULT_SUBNET_LABEL',
  642. 'cloudbridge-subnet')
  643. def __init__(self, provider):
  644. super(BaseSubnet, self).__init__(provider)
  645. def __eq__(self, other):
  646. return (isinstance(other, Subnet) and
  647. # pylint:disable=protected-access
  648. self._provider == other._provider and
  649. self.id == other.id)
  650. @property
  651. def network(self):
  652. return self._provider.networking.networks.get(self.network_id)
  653. def wait_till_ready(self, timeout=None, interval=None):
  654. self.wait_for(
  655. [SubnetState.AVAILABLE],
  656. terminal_states=[SubnetState.ERROR],
  657. timeout=timeout,
  658. interval=interval)
  659. class BaseFloatingIPContainer(FloatingIPContainer, BasePageableObjectMixin):
  660. def __init__(self, provider, gateway):
  661. self.__provider = provider
  662. self.gateway = gateway
  663. @property
  664. def _provider(self):
  665. return self.__provider
  666. def find(self, **kwargs):
  667. obj_list = self
  668. filters = ['name', 'public_ip']
  669. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  670. return ClientPagedResultList(self._provider, list(matches))
  671. def delete(self, fip_id):
  672. floating_ip = self.get(fip_id)
  673. if floating_ip:
  674. floating_ip.delete()
  675. class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
  676. def __init__(self, provider):
  677. super(BaseFloatingIP, self).__init__(provider)
  678. @property
  679. def name(self):
  680. return self.public_ip
  681. @property
  682. def state(self):
  683. return (FloatingIpState.IN_USE if self.in_use
  684. else FloatingIpState.AVAILABLE)
  685. def wait_till_ready(self, timeout=None, interval=None):
  686. self.wait_for(
  687. [FloatingIpState.AVAILABLE, FloatingIpState.IN_USE],
  688. terminal_states=[FloatingIpState.ERROR],
  689. timeout=timeout,
  690. interval=interval)
  691. def __eq__(self, other):
  692. return (isinstance(other, FloatingIP) and
  693. # pylint:disable=protected-access
  694. self._provider == other._provider and
  695. self.id == other.id)
  696. class BaseRouter(BaseCloudResource, Router):
  697. CB_DEFAULT_ROUTER_LABEL = os.environ.get('CB_DEFAULT_ROUTER_LABEL',
  698. 'cloudbridge-router')
  699. def __init__(self, provider):
  700. super(BaseRouter, self).__init__(provider)
  701. def __eq__(self, other):
  702. return (isinstance(other, Router) and
  703. # pylint:disable=protected-access
  704. self._provider == other._provider and
  705. self.id == other.id)
  706. class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
  707. InternetGateway):
  708. CB_DEFAULT_INET_GATEWAY_NAME = cb_helpers.get_env(
  709. 'CB_DEFAULT_INET_GATEWAY_NAME', 'cloudbridge-inetgateway')
  710. def __init__(self, provider):
  711. super(BaseInternetGateway, self).__init__(provider)
  712. self.__provider = provider
  713. def __eq__(self, other):
  714. return (isinstance(other, InternetGateway) and
  715. # pylint:disable=protected-access
  716. self._provider == other._provider and
  717. self.id == other.id)
  718. def wait_till_ready(self, timeout=None, interval=None):
  719. self.wait_for(
  720. [GatewayState.AVAILABLE],
  721. terminal_states=[GatewayState.ERROR, GatewayState.UNKNOWN],
  722. timeout=timeout,
  723. interval=interval)