Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cassandra.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # -* coding: utf-8 -*-
  2. """
  3. celery.backends.cassandra
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~
  5. Apache Cassandra result store backend.
  6. """
  7. from __future__ import absolute_import
  8. try: # pragma: no cover
  9. import pycassa
  10. from thrift import Thrift
  11. C = pycassa.cassandra.ttypes
  12. except ImportError: # pragma: no cover
  13. pycassa = None # noqa
  14. import socket
  15. import time
  16. from celery import states
  17. from celery.exceptions import ImproperlyConfigured
  18. from celery.five import monotonic
  19. from celery.utils.log import get_logger
  20. from celery.utils.timeutils import maybe_timedelta, timedelta_seconds
  21. from .base import BaseBackend
  22. __all__ = ['CassandraBackend']
  23. logger = get_logger(__name__)
  24. class CassandraBackend(BaseBackend):
  25. """Highly fault tolerant Cassandra backend.
  26. .. attribute:: servers
  27. List of Cassandra servers with format: ``hostname:port``.
  28. :raises celery.exceptions.ImproperlyConfigured: if
  29. module :mod:`pycassa` is not available.
  30. """
  31. servers = []
  32. keyspace = None
  33. column_family = None
  34. detailed_mode = False
  35. _retry_timeout = 300
  36. _retry_wait = 3
  37. supports_autoexpire = True
  38. def __init__(self, servers=None, keyspace=None, column_family=None,
  39. cassandra_options=None, detailed_mode=False, **kwargs):
  40. """Initialize Cassandra backend.
  41. Raises :class:`celery.exceptions.ImproperlyConfigured` if
  42. the :setting:`CASSANDRA_SERVERS` setting is not set.
  43. """
  44. super(CassandraBackend, self).__init__(**kwargs)
  45. self.expires = kwargs.get('expires') or maybe_timedelta(
  46. self.app.conf.CELERY_TASK_RESULT_EXPIRES)
  47. if not pycassa:
  48. raise ImproperlyConfigured(
  49. 'You need to install the pycassa library to use the '
  50. 'Cassandra backend. See https://github.com/pycassa/pycassa')
  51. conf = self.app.conf
  52. self.servers = (servers or
  53. conf.get('CASSANDRA_SERVERS') or
  54. self.servers)
  55. self.keyspace = (keyspace or
  56. conf.get('CASSANDRA_KEYSPACE') or
  57. self.keyspace)
  58. self.column_family = (column_family or
  59. conf.get('CASSANDRA_COLUMN_FAMILY') or
  60. self.column_family)
  61. self.cassandra_options = dict(conf.get('CASSANDRA_OPTIONS') or {},
  62. **cassandra_options or {})
  63. self.detailed_mode = (detailed_mode or
  64. conf.get('CASSANDRA_DETAILED_MODE') or
  65. self.detailed_mode)
  66. read_cons = conf.get('CASSANDRA_READ_CONSISTENCY') or 'LOCAL_QUORUM'
  67. write_cons = conf.get('CASSANDRA_WRITE_CONSISTENCY') or 'LOCAL_QUORUM'
  68. try:
  69. self.read_consistency = getattr(pycassa.ConsistencyLevel,
  70. read_cons)
  71. except AttributeError:
  72. self.read_consistency = pycassa.ConsistencyLevel.LOCAL_QUORUM
  73. try:
  74. self.write_consistency = getattr(pycassa.ConsistencyLevel,
  75. write_cons)
  76. except AttributeError:
  77. self.write_consistency = pycassa.ConsistencyLevel.LOCAL_QUORUM
  78. if not self.servers or not self.keyspace or not self.column_family:
  79. raise ImproperlyConfigured(
  80. 'Cassandra backend not configured.')
  81. self._column_family = None
  82. def _retry_on_error(self, fun, *args, **kwargs):
  83. ts = monotonic() + self._retry_timeout
  84. while 1:
  85. try:
  86. return fun(*args, **kwargs)
  87. except (pycassa.InvalidRequestException,
  88. pycassa.TimedOutException,
  89. pycassa.UnavailableException,
  90. pycassa.AllServersUnavailable,
  91. socket.error,
  92. socket.timeout,
  93. Thrift.TException) as exc:
  94. if monotonic() > ts:
  95. raise
  96. logger.warning('Cassandra error: %r. Retrying...', exc)
  97. time.sleep(self._retry_wait)
  98. def _get_column_family(self):
  99. if self._column_family is None:
  100. conn = pycassa.ConnectionPool(self.keyspace,
  101. server_list=self.servers,
  102. **self.cassandra_options)
  103. self._column_family = pycassa.ColumnFamily(
  104. conn, self.column_family,
  105. read_consistency_level=self.read_consistency,
  106. write_consistency_level=self.write_consistency,
  107. )
  108. return self._column_family
  109. def process_cleanup(self):
  110. if self._column_family is not None:
  111. self._column_family = None
  112. def _store_result(self, task_id, result, status,
  113. traceback=None, request=None, **kwargs):
  114. """Store return value and status of an executed task."""
  115. def _do_store():
  116. cf = self._get_column_family()
  117. date_done = self.app.now()
  118. meta = {'status': status,
  119. 'date_done': date_done.strftime('%Y-%m-%dT%H:%M:%SZ'),
  120. 'traceback': self.encode(traceback),
  121. 'result': self.encode(result),
  122. 'children': self.encode(
  123. self.current_task_children(request),
  124. )}
  125. if self.detailed_mode:
  126. cf.insert(task_id, {date_done: self.encode(meta)},
  127. ttl=self.expires and timedelta_seconds(self.expires))
  128. else:
  129. cf.insert(task_id, meta,
  130. ttl=self.expires and timedelta_seconds(self.expires))
  131. return self._retry_on_error(_do_store)
  132. def as_uri(self, include_password=True):
  133. return 'cassandra://'
  134. def _get_task_meta_for(self, task_id):
  135. """Get task metadata for a task by id."""
  136. def _do_get():
  137. cf = self._get_column_family()
  138. try:
  139. if self.detailed_mode:
  140. row = cf.get(task_id, column_reversed=True, column_count=1)
  141. obj = self.decode(list(row.values())[0])
  142. else:
  143. obj = cf.get(task_id)
  144. meta = {
  145. 'task_id': task_id,
  146. 'status': obj['status'],
  147. 'result': self.decode(obj['result']),
  148. 'date_done': obj['date_done'],
  149. 'traceback': self.decode(obj['traceback']),
  150. 'children': self.decode(obj['children']),
  151. }
  152. except (KeyError, pycassa.NotFoundException):
  153. meta = {'status': states.PENDING, 'result': None}
  154. return meta
  155. return self._retry_on_error(_do_get)
  156. def __reduce__(self, args=(), kwargs={}):
  157. kwargs.update(
  158. dict(servers=self.servers,
  159. keyspace=self.keyspace,
  160. column_family=self.column_family,
  161. cassandra_options=self.cassandra_options))
  162. return super(CassandraBackend, self).__reduce__(args, kwargs)