resources.py 33 KB

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