resources.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  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. for result in self.iter():
  230. yield result
  231. def iter(self, **kwargs):
  232. result_list = self.list(**kwargs)
  233. if result_list.supports_server_paging:
  234. for result in result_list:
  235. yield result
  236. while result_list.is_truncated:
  237. result_list = self.list(marker=result_list.marker, **kwargs)
  238. for result in result_list:
  239. yield result
  240. else:
  241. for result in result_list.data:
  242. yield result
  243. class BaseVMType(BaseCloudResource, VMType):
  244. def __init__(self, provider):
  245. super(BaseVMType, self).__init__(provider)
  246. def __eq__(self, other):
  247. return (isinstance(other, VMType) and
  248. # pylint:disable=protected-access
  249. self._provider == other._provider and
  250. self.id == other.id)
  251. @property
  252. def size_total_disk(self):
  253. return self.size_root_disk + self.size_ephemeral_disks
  254. class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
  255. def __init__(self, provider):
  256. super(BaseInstance, self).__init__(provider)
  257. def __eq__(self, other):
  258. return (isinstance(other, Instance) and
  259. # pylint:disable=protected-access
  260. self._provider == other._provider and
  261. self.id == other.id and
  262. # check from most to least likely mutables
  263. self.state == other.state and
  264. self.label == other.label and
  265. self.vm_firewalls == other.vm_firewalls and
  266. self.public_ips == other.public_ips and
  267. self.private_ips == other.private_ips and
  268. self.image_id == other.image_id)
  269. def wait_till_ready(self, timeout=None, interval=None):
  270. self.wait_for(
  271. [InstanceState.RUNNING],
  272. terminal_states=[InstanceState.DELETED, InstanceState.ERROR],
  273. timeout=timeout,
  274. interval=interval)
  275. class BaseLaunchConfig(LaunchConfig):
  276. def __init__(self, provider):
  277. self.provider = provider
  278. self.block_devices = []
  279. class BlockDeviceMapping(object):
  280. """
  281. Represents a block device mapping
  282. """
  283. def __init__(self, is_volume=False, source=None, is_root=None,
  284. size=None, delete_on_terminate=None):
  285. self.is_volume = is_volume
  286. self.source = source
  287. self.is_root = is_root
  288. self.size = size
  289. self.delete_on_terminate = delete_on_terminate
  290. def add_ephemeral_device(self):
  291. block_device = BaseLaunchConfig.BlockDeviceMapping()
  292. self.block_devices.append(block_device)
  293. def add_volume_device(self, source=None, is_root=None, size=None,
  294. delete_on_terminate=None):
  295. block_device = self._validate_volume_device(
  296. source=source, is_root=is_root, size=size,
  297. delete_on_terminate=delete_on_terminate)
  298. log.debug("Appending %s to the block_devices list",
  299. block_device)
  300. self.block_devices.append(block_device)
  301. def _validate_volume_device(self, source=None, is_root=None,
  302. size=None, delete_on_terminate=None):
  303. """
  304. Validates a volume based device and throws an
  305. InvalidConfigurationException if the configuration is incorrect.
  306. """
  307. if source is None and not size:
  308. log.exception("InvalidConfigurationException raised: "
  309. "no size argument specified.")
  310. raise InvalidConfigurationException(
  311. "A size must be specified for a blank new volume.")
  312. if source and \
  313. not isinstance(source, (Snapshot, Volume, MachineImage)):
  314. log.exception("InvalidConfigurationException raised: "
  315. "source argument not specified correctly.")
  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. log.exception("InvalidConfigurationException raised: "
  321. "size argument must be an integer greater than "
  322. "0. Got type %s and value %s.", type(size), size)
  323. raise InvalidConfigurationException(
  324. "The size must be None or an integer greater than 0.")
  325. if is_root:
  326. for bd in self.block_devices:
  327. if bd.is_root:
  328. log.exception("InvalidConfigurationException raised: "
  329. "%s has already been marked as the root "
  330. "block device.", bd)
  331. raise InvalidConfigurationException(
  332. "An existing block device: {0} has already been"
  333. " marked as root. There can only be one root device.")
  334. return BaseLaunchConfig.BlockDeviceMapping(
  335. is_volume=True, source=source, is_root=is_root, size=size,
  336. delete_on_terminate=delete_on_terminate)
  337. class BaseMachineImage(
  338. BaseCloudResource, BaseObjectLifeCycleMixin, MachineImage):
  339. def __init__(self, provider):
  340. super(BaseMachineImage, self).__init__(provider)
  341. def __eq__(self, other):
  342. return (isinstance(other, MachineImage) and
  343. # pylint:disable=protected-access
  344. self._provider == other._provider and
  345. self.id == other.id and
  346. # check from most to least likely mutables
  347. self.state == other.state and
  348. self.label == other.label and
  349. self.description == other.description)
  350. def wait_till_ready(self, timeout=None, interval=None):
  351. self.wait_for(
  352. [MachineImageState.AVAILABLE],
  353. terminal_states=[MachineImageState.ERROR],
  354. timeout=timeout,
  355. interval=interval)
  356. class BaseAttachmentInfo(AttachmentInfo):
  357. def __init__(self, volume, instance_id, device):
  358. self._volume = volume
  359. self._instance_id = instance_id
  360. self._device = device
  361. @property
  362. def volume(self):
  363. return self._volume
  364. @property
  365. def instance_id(self):
  366. return self._instance_id
  367. @property
  368. def device(self):
  369. return self._device
  370. class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
  371. def __init__(self, provider):
  372. super(BaseVolume, self).__init__(provider)
  373. def __eq__(self, other):
  374. return (isinstance(other, Volume) and
  375. # pylint:disable=protected-access
  376. self._provider == other._provider and
  377. self.id == other.id and
  378. # check from most to least likely mutables
  379. self.state == other.state and
  380. self.label == other.label)
  381. def wait_till_ready(self, timeout=None, interval=None):
  382. self.wait_for(
  383. [VolumeState.AVAILABLE],
  384. terminal_states=[VolumeState.ERROR, VolumeState.DELETED],
  385. timeout=timeout,
  386. interval=interval)
  387. class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
  388. def __init__(self, provider):
  389. super(BaseSnapshot, self).__init__(provider)
  390. def __eq__(self, other):
  391. return (isinstance(other, Snapshot) and
  392. # pylint:disable=protected-access
  393. self._provider == other._provider and
  394. self.id == other.id and
  395. # check from most to least likely mutables
  396. self.state == other.state and
  397. self.label == other.label)
  398. def wait_till_ready(self, timeout=None, interval=None):
  399. self.wait_for(
  400. [SnapshotState.AVAILABLE],
  401. terminal_states=[SnapshotState.ERROR],
  402. timeout=timeout,
  403. interval=interval)
  404. class BaseKeyPair(BaseCloudResource, KeyPair):
  405. def __init__(self, provider, key_pair):
  406. super(BaseKeyPair, self).__init__(provider)
  407. self._key_pair = key_pair
  408. self._private_material = None
  409. def __eq__(self, other):
  410. return (isinstance(other, KeyPair) and
  411. # pylint:disable=protected-access
  412. self._provider == other._provider and
  413. self.name == other.name)
  414. @property
  415. def id(self):
  416. """
  417. Return the id of this key pair.
  418. """
  419. return self._key_pair.name
  420. @property
  421. def name(self):
  422. """
  423. Return the name of this key pair.
  424. """
  425. return self.id
  426. @property
  427. def material(self):
  428. return self._private_material
  429. @material.setter
  430. # pylint:disable=arguments-differ
  431. def material(self, value):
  432. self._private_material = value
  433. def delete(self):
  434. """
  435. Delete this KeyPair.
  436. :rtype: bool
  437. :return: True if successful, otherwise False.
  438. """
  439. # This implementation assumes the `delete` method exists across
  440. # multiple providers.
  441. self._key_pair.delete()
  442. class BaseVMFirewall(BaseCloudResource, VMFirewall):
  443. def __init__(self, provider, vm_firewall):
  444. super(BaseVMFirewall, self).__init__(provider)
  445. self._vm_firewall = vm_firewall
  446. def __eq__(self, other):
  447. """
  448. Check if all the defined rules match across both VM firewalls.
  449. """
  450. return (isinstance(other, VMFirewall) and
  451. # pylint:disable=protected-access
  452. self._provider == other._provider and
  453. set(self.rules) == set(other.rules))
  454. def __ne__(self, other):
  455. return not self.__eq__(other)
  456. @property
  457. def id(self):
  458. """
  459. Get the ID of this VM firewall.
  460. :rtype: str
  461. :return: VM firewall ID
  462. """
  463. return self._vm_firewall.id
  464. @property
  465. def name(self):
  466. """
  467. Return the name of this VM firewall.
  468. """
  469. return self.id
  470. @property
  471. def description(self):
  472. """
  473. Return the description of this VM firewall.
  474. """
  475. return self._vm_firewall.description
  476. def delete(self):
  477. """
  478. Delete this VM firewall.
  479. """
  480. return self._vm_firewall.delete()
  481. class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
  482. VMFirewallRuleContainer):
  483. def __init__(self, provider, firewall):
  484. self.__provider = provider
  485. self.firewall = firewall
  486. @property
  487. def _provider(self):
  488. return self.__provider
  489. def get(self, rule_id):
  490. matches = [rule for rule in self if rule.id == rule_id]
  491. if matches:
  492. return matches[0]
  493. else:
  494. return None
  495. def find(self, **kwargs):
  496. obj_list = self
  497. filters = ['name', 'direction', 'protocol', 'from_port', 'to_port',
  498. 'cidr', 'src_dest_fw', 'src_dest_fw_id']
  499. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  500. return ClientPagedResultList(self._provider, list(matches))
  501. def delete(self, rule_id):
  502. rule = self.get(rule_id)
  503. if rule:
  504. rule.delete()
  505. class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
  506. def __init__(self, parent_fw, rule):
  507. # pylint:disable=protected-access
  508. super(BaseVMFirewallRule, self).__init__(
  509. parent_fw._provider)
  510. self.firewall = parent_fw
  511. self._rule = rule
  512. # Cache name
  513. self._name = "{0}-{1}-{2}-{3}-{4}-{5}".format(
  514. self.direction, self.protocol, self.from_port, self.to_port,
  515. self.cidr, self.src_dest_fw_id).lower()
  516. @property
  517. def name(self):
  518. return self._name
  519. def __repr__(self):
  520. return ("<{0}: id: {1}; direction: {2}; protocol: {3}; from: {4};"
  521. " to: {5}; cidr: {6}, src_dest_fw: {7}>"
  522. .format(self.__class__.__name__, self.id, self.direction,
  523. self.protocol, self.from_port, self.to_port, self.cidr,
  524. self.src_dest_fw_id))
  525. def __eq__(self, other):
  526. return (isinstance(other, VMFirewallRule) and
  527. self.direction == other.direction and
  528. self.protocol == other.protocol and
  529. self.from_port == other.from_port and
  530. self.to_port == other.to_port and
  531. self.cidr == other.cidr and
  532. self.src_dest_fw_id == other.src_dest_fw_id)
  533. def __ne__(self, other):
  534. return not self.__eq__(other)
  535. def __hash__(self):
  536. """
  537. Return a hash-based interpretation of all of the object's field values.
  538. This is requeried for operations on hashed collections including
  539. ``set``, ``frozenset``, and ``dict``.
  540. """
  541. return hash("{0}{1}{2}{3}{4}{5}".format(
  542. self.direction, self.protocol, self.from_port, self.to_port,
  543. self.cidr, self.src_dest_fw_id))
  544. def to_json(self):
  545. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  546. js = {k: v for (k, v) in attr if not k.startswith('_')}
  547. js['src_dest_fw'] = self.src_dest_fw_id
  548. js['firewall'] = self.firewall.id
  549. return js
  550. class BasePlacementZone(BaseCloudResource, PlacementZone):
  551. def __init__(self, provider):
  552. super(BasePlacementZone, self).__init__(provider)
  553. def __eq__(self, other):
  554. return (isinstance(other, PlacementZone) and
  555. # pylint:disable=protected-access
  556. self._provider == other._provider and
  557. self.id == other.id)
  558. class BaseRegion(BaseCloudResource, Region):
  559. def __init__(self, provider):
  560. super(BaseRegion, self).__init__(provider)
  561. def __eq__(self, other):
  562. return (isinstance(other, Region) and
  563. # pylint:disable=protected-access
  564. self._provider == other._provider and
  565. self.id == other.id)
  566. def to_json(self):
  567. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  568. js = {k: v for(k, v) in attr if not k.startswith('_')}
  569. js['zones'] = [z.id for z in self.zones]
  570. return js
  571. class BaseBucketObject(BaseCloudResource, BucketObject):
  572. # Regular expression for valid bucket keys.
  573. # They, must match the following criteria: http://docs.aws.amazon.com/"
  574. # AmazonS3/latest/dev/UsingMetadata.html#object-key-guidelines
  575. #
  576. # Note: The following regex is based on: https://stackoverflow.com/question
  577. # s/537772/what-is-the-most-correct-regular-expression-for-a-unix-file-path
  578. CB_NAME_PATTERN = re.compile(r"[^\0]+")
  579. def __init__(self, provider):
  580. super(BaseBucketObject, self).__init__(provider)
  581. @staticmethod
  582. def is_valid_resource_name(name):
  583. return (True if BaseBucketObject.CB_NAME_PATTERN.match(name)
  584. else False)
  585. @staticmethod
  586. def assert_valid_resource_name(name):
  587. if not BaseBucketObject.is_valid_resource_name(name):
  588. log.debug("InvalidLabelException raised on %s", name,
  589. exc_info=True)
  590. raise InvalidLabelException(
  591. u"Invalid object name: %s. Name must match criteria defined "
  592. "in: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMeta"
  593. "data.html#object-key-guidelines" % name)
  594. def save_content(self, target_stream):
  595. shutil.copyfileobj(self.iter_content(), target_stream)
  596. def __eq__(self, other):
  597. return (isinstance(other, BucketObject) and
  598. # pylint:disable=protected-access
  599. self._provider == other._provider and
  600. self.id == other.id and
  601. # check from most to least likely mutables
  602. self.name == other.name)
  603. class BaseBucket(BaseCloudResource, Bucket):
  604. def __init__(self, provider):
  605. super(BaseBucket, self).__init__(provider)
  606. def __eq__(self, other):
  607. return (isinstance(other, Bucket) and
  608. # pylint:disable=protected-access
  609. self._provider == other._provider and
  610. self.id == other.id and
  611. # check from most to least likely mutables
  612. self.name == other.name)
  613. def delete(self):
  614. """
  615. Delete this bucket.
  616. """
  617. self._provider.storage.buckets.delete(self.id)
  618. # TODO: Discuss creating `create_object` method, or change docs
  619. class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
  620. def __init__(self, provider, bucket):
  621. self.__provider = provider
  622. self.bucket = bucket
  623. @property
  624. def _provider(self):
  625. return self.__provider
  626. def get(self, name):
  627. return self._provider.storage.bucket_objects.get(self.bucket, name)
  628. def list(self, limit=None, marker=None, prefix=None):
  629. return self._provider.storage.bucket_objects.list(self.bucket, limit,
  630. marker, prefix)
  631. def find(self, **kwargs):
  632. return self._provider.storage.bucket_objects.find(self.bucket,
  633. **kwargs)
  634. def create(self, name):
  635. return self._provider.storage.bucket_objects.create(self.bucket, name)
  636. class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
  637. def __init__(self, provider, network):
  638. self._network = network
  639. self._provider = provider
  640. class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
  641. CB_DEFAULT_NETWORK_LABEL = os.environ.get('CB_DEFAULT_NETWORK_LABEL',
  642. 'cloudbridge-net')
  643. CB_DEFAULT_IPV4RANGE = os.environ.get('CB_DEFAULT_IPV4RANGE',
  644. u'10.0.0.0/16')
  645. def __init__(self, provider):
  646. super(BaseNetwork, self).__init__(provider)
  647. @staticmethod
  648. def cidr_blocks_overlap(block1, block2):
  649. common_length = min(int(block1.split('/')[1]),
  650. int(block2.split('/')[1]))
  651. p1 = [format(int(b), '08b') for b in block1.split('/')[0].split('.')]
  652. prefix1 = ''.join(p1)[:common_length]
  653. p2 = [format(int(b), '08b') for b in block2.split('/')[0].split('.')]
  654. prefix2 = ''.join(p2)[:common_length]
  655. return prefix1 == prefix2
  656. def wait_till_ready(self, timeout=None, interval=None):
  657. self.wait_for(
  658. [NetworkState.AVAILABLE],
  659. terminal_states=[NetworkState.ERROR],
  660. timeout=timeout,
  661. interval=interval)
  662. def create_subnet(self, label, cidr_block, zone):
  663. return self._provider.networking.subnets.create(
  664. label=label, network=self, cidr_block=cidr_block, zone=zone)
  665. def __eq__(self, other):
  666. return (isinstance(other, Network) and
  667. # pylint:disable=protected-access
  668. self._provider == other._provider and
  669. self.id == other.id)
  670. class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
  671. CB_DEFAULT_SUBNET_LABEL = os.environ.get('CB_DEFAULT_SUBNET_LABEL',
  672. 'cloudbridge-subnet')
  673. CB_DEFAULT_SUBNET_IPV4RANGE = os.environ.get('CB_DEFAULT_SUBNET_IPV4RANGE',
  674. '10.0.0.0/24')
  675. def __init__(self, provider):
  676. super(BaseSubnet, self).__init__(provider)
  677. def __eq__(self, other):
  678. return (isinstance(other, Subnet) and
  679. # pylint:disable=protected-access
  680. self._provider == other._provider and
  681. self.id == other.id)
  682. @property
  683. def network(self):
  684. return self._provider.networking.networks.get(self.network_id)
  685. def wait_till_ready(self, timeout=None, interval=None):
  686. self.wait_for(
  687. [SubnetState.AVAILABLE],
  688. terminal_states=[SubnetState.ERROR],
  689. timeout=timeout,
  690. interval=interval)
  691. class BaseFloatingIPContainer(FloatingIPContainer, BasePageableObjectMixin):
  692. def __init__(self, provider, gateway):
  693. self.__provider = provider
  694. self.gateway = gateway
  695. @property
  696. def _provider(self):
  697. return self.__provider
  698. def find(self, **kwargs):
  699. obj_list = self
  700. filters = ['name', 'public_ip']
  701. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  702. return ClientPagedResultList(self._provider, list(matches))
  703. def delete(self, fip_id):
  704. floating_ip = self.get(fip_id)
  705. if floating_ip:
  706. floating_ip.delete()
  707. class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
  708. def __init__(self, provider):
  709. super(BaseFloatingIP, self).__init__(provider)
  710. @property
  711. def name(self):
  712. return self.public_ip
  713. @property
  714. def state(self):
  715. return (FloatingIpState.IN_USE if self.in_use
  716. else FloatingIpState.AVAILABLE)
  717. def wait_till_ready(self, timeout=None, interval=None):
  718. self.wait_for(
  719. [FloatingIpState.AVAILABLE, FloatingIpState.IN_USE],
  720. terminal_states=[FloatingIpState.ERROR],
  721. timeout=timeout,
  722. interval=interval)
  723. def __eq__(self, other):
  724. return (isinstance(other, FloatingIP) and
  725. # pylint:disable=protected-access
  726. self._provider == other._provider and
  727. self.id == other.id)
  728. class BaseRouter(BaseCloudResource, Router):
  729. CB_DEFAULT_ROUTER_LABEL = os.environ.get('CB_DEFAULT_ROUTER_LABEL',
  730. 'cloudbridge-router')
  731. def __init__(self, provider):
  732. super(BaseRouter, self).__init__(provider)
  733. def __eq__(self, other):
  734. return (isinstance(other, Router) and
  735. # pylint:disable=protected-access
  736. self._provider == other._provider and
  737. self.id == other.id)
  738. class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
  739. InternetGateway):
  740. CB_DEFAULT_INET_GATEWAY_NAME = cb_helpers.get_env(
  741. 'CB_DEFAULT_INET_GATEWAY_NAME', 'cloudbridge-inetgateway')
  742. def __init__(self, provider):
  743. super(BaseInternetGateway, self).__init__(provider)
  744. self.__provider = provider
  745. def __eq__(self, other):
  746. return (isinstance(other, InternetGateway) and
  747. # pylint:disable=protected-access
  748. self._provider == other._provider and
  749. self.id == other.id)
  750. def wait_till_ready(self, timeout=None, interval=None):
  751. self.wait_for(
  752. [GatewayState.AVAILABLE],
  753. terminal_states=[GatewayState.ERROR, GatewayState.UNKNOWN],
  754. timeout=timeout,
  755. interval=interval)