adds pluggable brokers

* added redis broker
 * added django_redis broker
 * added task acknowledgement
This commit is contained in:
Ilan Steemers
2015-08-29 18:54:07 +02:00
parent 0de742b417
commit 0740c90148
12 changed files with 247 additions and 163 deletions
+47
View File
@@ -0,0 +1,47 @@
from django_q.conf import Conf
class Broker(object):
def __init__(self, list_key=Conf.Q_LIST):
self.connection = self.get_connection()
self.list_key = list_key
def enqueue(self, task):
pass
def dequeue(self):
pass
def queue_size(self):
pass
def delete_queue(self, list_key=None):
pass
def acknowledge(self, ack_id):
pass
def ping(self):
pass
def set(self, key, value, timeout):
pass
def get(self, key):
pass
def get_pattern(self, pattern):
pass
@staticmethod
def get_connection():
return 0
def get_broker(list_key=Conf.Q_LIST):
if Conf.REDIS:
from brokers import redis
return redis.Redis(list_key=list_key)
elif Conf.DJANGO_REDIS:
from brokers import django_redis
return django_redis.DjangoRedis(list_key=list_key)
+10
View File
@@ -0,0 +1,10 @@
import django_redis
from django_q.brokers import redis
from django_q.conf import Conf
class DjangoRedis(redis.Redis):
@staticmethod
def get_connection():
return django_redis.get_redis_connection(Conf.DJANGO_REDIS)
+43
View File
@@ -0,0 +1,43 @@
import redis
from django_q.brokers import Broker
from django_q.conf import Conf, logger
class Redis(Broker):
def enqueue(self, task):
return self.connection.rpush(self.list_key, task)
def dequeue(self):
task = self.connection.blpop(self.list_key, 1)
if task:
return None, task[1]
def queue_size(self):
return self.connection.llen(self.list_key)
def delete_queue(self, list_key=None):
list_key = list_key if list_key else self.list_key
return self.connection.delete(list_key)
def ping(self):
try:
return self.connection.ping()
except Exception as e:
logger.error('Can not connect to Redis server.')
raise e
def set(self, key, value, timeout):
self.connection.set(key, value, timeout)
def get(self, key):
if self.connection.exists(key):
return self.connection.get(key)
def get_pattern(self, pattern):
keys = self.connection.keys(pattern=pattern)
if keys:
return self.connection.mget(keys)
@staticmethod
def get_connection():
return redis.StrictRedis(**Conf.REDIS)
+24 -21
View File
@@ -30,19 +30,20 @@ from django import db
import signing import signing
import tasks import tasks
from django_q.conf import Conf, redis_client, logger, psutil, get_ppid from django_q.conf import Conf, logger, psutil, get_ppid
from django_q.models import Task, Success, Schedule from django_q.models import Task, Success, Schedule
from django_q.status import Stat, Status, ping_redis from django_q.status import Stat, Status
from django_q.brokers import get_broker
class Cluster(object): class Cluster(object):
def __init__(self, list_key=Conf.Q_LIST): def __init__(self, broker=get_broker()):
self.broker = broker
self.sentinel = None self.sentinel = None
self.stop_event = None self.stop_event = None
self.start_event = None self.start_event = None
self.pid = current_process().pid self.pid = current_process().pid
self.host = socket.gethostname() self.host = socket.gethostname()
self.list_key = list_key
self.timeout = Conf.TIMEOUT self.timeout = Conf.TIMEOUT
signal.signal(signal.SIGTERM, self.sig_handler) signal.signal(signal.SIGTERM, self.sig_handler)
signal.signal(signal.SIGINT, self.sig_handler) signal.signal(signal.SIGINT, self.sig_handler)
@@ -58,7 +59,7 @@ class Cluster(object):
self.stop_event = Event() self.stop_event = Event()
self.start_event = Event() self.start_event = Event()
self.sentinel = Process(target=Sentinel, self.sentinel = Process(target=Sentinel,
args=(self.stop_event, self.start_event, self.list_key, self.timeout)) args=(self.stop_event, self.start_event, self.broker, self.timeout))
self.sentinel.start() self.sentinel.start()
logger.info(_('Q Cluster-{} starting.').format(self.pid)) logger.info(_('Q Cluster-{} starting.').format(self.pid))
while not self.start_event.is_set(): while not self.start_event.is_set():
@@ -105,15 +106,14 @@ class Cluster(object):
class Sentinel(object): class Sentinel(object):
def __init__(self, stop_event, start_event, list_key=Conf.Q_LIST, timeout=Conf.TIMEOUT, start=True): def __init__(self, stop_event, start_event, broker=get_broker(), timeout=Conf.TIMEOUT, start=True):
# Make sure we catch signals for the pool # Make sure we catch signals for the pool
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL)
self.pid = current_process().pid self.pid = current_process().pid
self.parent_pid = get_ppid() self.parent_pid = get_ppid()
self.name = current_process().name self.name = current_process().name
self.list_key = list_key self.broker = broker
self.r = redis_client
self.reincarnations = 0 self.reincarnations = 0
self.tob = timezone.now() self.tob = timezone.now()
self.stop_event = stop_event self.stop_event = stop_event
@@ -130,7 +130,7 @@ class Sentinel(object):
self.start() self.start()
def start(self): def start(self):
ping_redis(self.r) self.broker.ping()
self.spawn_cluster() self.spawn_cluster()
self.guard() self.guard()
@@ -165,7 +165,7 @@ class Sentinel(object):
return p return p
def spawn_pusher(self): def spawn_pusher(self):
return self.spawn_process(pusher, self.task_queue, self.event_out, self.list_key) return self.spawn_process(pusher, self.task_queue, self.event_out, self.broker)
def spawn_worker(self): def spawn_worker(self):
self.spawn_process(worker, self.task_queue, self.result_queue, Value('f', -1), self.timeout) self.spawn_process(worker, self.task_queue, self.result_queue, Value('f', -1), self.timeout)
@@ -216,7 +216,7 @@ class Sentinel(object):
self.start_event.set() self.start_event.set()
Stat(self).save() Stat(self).save()
logger.info(_('Q Cluster-{} running.').format(self.parent_pid)) logger.info(_('Q Cluster-{} running.').format(self.parent_pid))
scheduler(list_key=self.list_key) scheduler(broker=self.broker)
counter = 0 counter = 0
cycle = 0.5 # guard loop sleep in seconds cycle = 0.5 # guard loop sleep in seconds
# Guard loop. Runs at least once # Guard loop. Runs at least once
@@ -240,7 +240,7 @@ class Sentinel(object):
counter += cycle counter += cycle
if counter == 30: if counter == 30:
counter = 0 counter = 0
scheduler(list_key=self.list_key) scheduler(broker=self.broker)
# Save current status # Save current status
Stat(self).save() Stat(self).save()
sleep(cycle) sleep(cycle)
@@ -287,38 +287,38 @@ class Sentinel(object):
Stat(self).save() Stat(self).save()
def pusher(task_queue, event, list_key=Conf.Q_LIST): def pusher(task_queue, event, broker=get_broker()):
""" """
Pulls tasks of the Redis List and puts them in the task queue Pulls tasks of the Redis List and puts them in the task queue
:type task_queue: multiprocessing.Queue :type task_queue: multiprocessing.Queue
:type event: multiprocessing.Event :type event: multiprocessing.Event
:type list_key: str
""" """
logger.info(_('{} pushing tasks at {}').format(current_process().name, current_process().pid)) logger.info(_('{} pushing tasks at {}').format(current_process().name, current_process().pid))
r = redis_client
while True: while True:
try: try:
task = r.blpop(list_key, 1) task = broker.dequeue()
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
# redis probably crashed. Let the sentinel handle it. # broker probably crashed. Let the sentinel handle it.
sleep(10) sleep(10)
break break
if task: if task:
ack_id = task[0]
# unpack the task # unpack the task
try: try:
task = signing.SignedPackage.loads(task[1]) task = signing.SignedPackage.loads(task[1])
except (TypeError, signing.BadSignature) as e: except (TypeError, signing.BadSignature) as e:
logger.error(e) logger.error(e)
continue continue
task['ack_id'] = ack_id
task_queue.put(task) task_queue.put(task)
logger.debug(_('queueing from {}').format(list_key)) logger.debug(_('queueing from {}').format(broker.list_key))
if event.is_set(): if event.is_set():
break break
logger.info(_("{} stopped pushing tasks").format(current_process().name)) logger.info(_("{} stopped pushing tasks").format(current_process().name))
def monitor(result_queue): def monitor(result_queue, broker=get_broker()):
""" """
Gets finished tasks from the result queue and saves them to Django Gets finished tasks from the result queue and saves them to Django
:type result_queue: multiprocessing.Queue :type result_queue: multiprocessing.Queue
@@ -327,6 +327,9 @@ def monitor(result_queue):
logger.info(_("{} monitoring at {}").format(name, current_process().pid)) logger.info(_("{} monitoring at {}").format(name, current_process().pid))
db.close_old_connections() db.close_old_connections()
for task in iter(result_queue.get, 'STOP'): for task in iter(result_queue.get, 'STOP'):
ack_id = task.pop('ack_id', False)
if ack_id:
broker.acknowledge(ack_id)
save_task(task) save_task(task)
if task['success']: if task['success']:
logger.info(_("Processed [{}]").format(task['name'])) logger.info(_("Processed [{}]").format(task['name']))
@@ -410,7 +413,7 @@ def save_task(task):
logger.error(e) logger.error(e)
def scheduler(list_key=Conf.Q_LIST): def scheduler(broker=get_broker()):
""" """
Creates a task from a schedule at the scheduled time and schedules next run Creates a task from a schedule at the scheduled time and schedules next run
""" """
@@ -456,7 +459,7 @@ def scheduler(list_key=Conf.Q_LIST):
s.next_run = next_run.datetime s.next_run = next_run.datetime
s.repeats += -1 s.repeats += -1
# send it to the cluster # send it to the cluster
q_options['list_key'] = list_key q_options['broker'] = broker
q_options['group'] = s.name or s.id q_options['group'] = s.name or s.id
kwargs['q_options'] = q_options kwargs['q_options'] = q_options
s.task = tasks.async(s.func, *args, **kwargs) s.task = tasks.async(s.func, *args, **kwargs)
+1
View File
@@ -46,6 +46,7 @@ class Conf(object):
# Maximum number of tasks that each cluster can work on # Maximum number of tasks that each cluster can work on
QUEUE_LIMIT = conf.get('queue_limit', None) QUEUE_LIMIT = conf.get('queue_limit', None)
# Number of workers in the pool. Default is cpu count if implemented, otherwise 4. # Number of workers in the pool. Default is cpu count if implemented, otherwise 4.
WORKERS = conf.get('workers', False) WORKERS = conf.get('workers', False)
if not WORKERS: if not WORKERS:
+10 -9
View File
@@ -11,13 +11,14 @@ from django.utils.translation import ugettext as _
# local # local
from django_q.conf import Conf, redis_client from django_q.conf import Conf, redis_client
from django_q.status import Stat, ping_redis from django_q.status import Stat
from django_q.brokers import get_broker
from django_q import models from django_q import models
def monitor(run_once=False, r=redis_client): def monitor(run_once=False, broker=get_broker()):
term = Terminal() term = Terminal()
ping_redis(r) broker.ping()
with term.fullscreen(), term.hidden_cursor(), term.cbreak(): with term.fullscreen(), term.hidden_cursor(), term.cbreak():
val = None val = None
start_width = int(term.width / 8) start_width = int(term.width / 8)
@@ -36,7 +37,7 @@ def monitor(run_once=False, r=redis_client):
print(term.move(0, 6 * col_width) + term.black_on_green(term.center(_('RC'), width=col_width - 1))) print(term.move(0, 6 * col_width) + term.black_on_green(term.center(_('RC'), width=col_width - 1)))
print(term.move(0, 7 * col_width) + term.black_on_green(term.center(_('Up'), width=col_width - 1))) print(term.move(0, 7 * col_width) + term.black_on_green(term.center(_('Up'), width=col_width - 1)))
i = 2 i = 2
stats = Stat.get_all(r=r) stats = Stat.get_all(broker=broker)
print(term.clear_eos()) print(term.clear_eos())
for stat in stats: for stat in stats:
status = stat.status status = stat.status
@@ -79,15 +80,15 @@ def monitor(run_once=False, r=redis_client):
i += 1 i += 1
# for testing # for testing
if run_once: if run_once:
return Stat.get_all(r=r) return Stat.get_all(broker=broker)
print(term.move(i + 2, 0) + term.center(_('[Press q to quit]'))) print(term.move(i + 2, 0) + term.center(_('[Press q to quit]')))
val = term.inkey(timeout=1) val = term.inkey(timeout=1)
def info(r=redis_client): def info(broker=get_broker()):
term = Terminal() term = Terminal()
ping_redis(r) broker.ping()
stat = Stat.get_all(r) stat = Stat.get_all(broker=broker)
# general stats # general stats
clusters = len(stat) clusters = len(stat)
workers = 0 workers = 0
@@ -141,7 +142,7 @@ def info(r=redis_client):
) )
print(term.cyan(_('Queued')) + print(term.cyan(_('Queued')) +
term.move_x(1 * col_width) + term.move_x(1 * col_width) +
term.white(str(r.llen(Conf.Q_LIST))) + term.white(str(broker.queue_size())) +
term.move_x(2 * col_width) + term.move_x(2 * col_width) +
term.cyan(_('Successes')) + term.cyan(_('Successes')) +
term.move_x(3 * col_width) + term.move_x(3 * col_width) +
+15 -25
View File
@@ -1,6 +1,7 @@
import socket import socket
from django.utils import timezone from django.utils import timezone
from django_q.conf import Conf, logger, redis_client from django_q.brokers import get_broker
from django_q.conf import Conf, logger
import signing import signing
@@ -27,7 +28,7 @@ class Stat(Status):
def __init__(self, sentinel): def __init__(self, sentinel):
super(Stat, self).__init__(sentinel.parent_pid or sentinel.pid) super(Stat, self).__init__(sentinel.parent_pid or sentinel.pid)
self.r = sentinel.r self.broker = sentinel.broker or get_broker()
self.tob = sentinel.tob self.tob = sentinel.tob
self.reincarnations = sentinel.reincarnations self.reincarnations = sentinel.reincarnations
self.sentinel = sentinel.pid self.sentinel = sentinel.pid
@@ -63,7 +64,7 @@ class Stat(Status):
def save(self): def save(self):
try: try:
self.r.set(self.key, signing.SignedPackage.dumps(self, True), 3) self.broker.set(self.key, signing.SignedPackage.dumps(self, True), 3)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@@ -71,15 +72,14 @@ class Stat(Status):
return self.done_q_size + self.task_q_size == 0 return self.done_q_size + self.task_q_size == 0
@staticmethod @staticmethod
def get(cluster_id, r=redis_client): def get(cluster_id, broker=get_broker()):
""" """
gets the current status for the cluster gets the current status for the cluster
:param cluster_id: id of the cluster :param cluster_id: id of the cluster
:return: Stat or Status :return: Stat or Status
""" """
key = Stat.get_key(cluster_id) pack = broker.get(Stat.get_key(cluster_id))
if r.exists(key): if pack:
pack = r.get(key)
try: try:
return signing.SignedPackage.loads(pack) return signing.SignedPackage.loads(pack)
except signing.BadSignature: except signing.BadSignature:
@@ -87,33 +87,23 @@ class Stat(Status):
return Status(cluster_id) return Status(cluster_id)
@staticmethod @staticmethod
def get_all(r=redis_client): def get_all(broker=get_broker()):
""" """
Get the status for all currently running clusters with the same prefix Get the status for all currently running clusters with the same prefix
and secret key. and secret key.
:return: list of type Stat :return: list of type Stat
""" """
stats = [] stats = []
keys = r.keys(pattern='{}:*'.format(Conf.Q_STAT)) packs = broker.get_pattern('{}:*'.format(Conf.Q_STAT)) or []
if keys: for pack in packs:
packs = r.mget(keys) try:
for pack in packs: stats.append(signing.SignedPackage.loads(pack))
try: except signing.BadSignature:
stats.append(signing.SignedPackage.loads(pack)) continue
except signing.BadSignature:
continue
return stats return stats
def __getstate__(self): def __getstate__(self):
# Don't pickle the redis connection # Don't pickle the redis connection
state = dict(self.__dict__) state = dict(self.__dict__)
del state['r'] del state['broker']
return state return state
def ping_redis(r):
try:
r.ping()
except Exception as e:
logger.error('Can not connect to Redis server.')
raise e
+8 -8
View File
@@ -7,9 +7,10 @@ from django.utils import timezone
# local # local
import signing import signing
import cluster import cluster
from django_q.conf import Conf, redis_client, logger from django_q.conf import Conf, logger
from django_q.models import Schedule, Task from django_q.models import Schedule, Task
from django_q.humanhash import uuid from django_q.humanhash import uuid
from django_q.brokers import get_broker
def async(func, *args, **kwargs): def async(func, *args, **kwargs):
@@ -17,8 +18,7 @@ def async(func, *args, **kwargs):
# get options from q_options dict or direct from kwargs # get options from q_options dict or direct from kwargs
options = kwargs.pop('q_options', kwargs) options = kwargs.pop('q_options', kwargs)
hook = options.pop('hook', None) hook = options.pop('hook', None)
list_key = options.pop('list_key', Conf.Q_LIST) broker = options.pop('broker', get_broker())
redis = options.pop('redis', redis_client)
sync = options.pop('sync', False) sync = options.pop('sync', False)
group = options.pop('group', None) group = options.pop('group', None)
save = options.pop('save', None) save = options.pop('save', None)
@@ -42,7 +42,7 @@ def async(func, *args, **kwargs):
if sync or Conf.SYNC: if sync or Conf.SYNC:
return _sync(pack) return _sync(pack)
# push it # push it
redis.rpush(list_key, pack) broker.enqueue(pack)
logger.debug('Pushed {}'.format(tag)) logger.debug('Pushed {}'.format(tag))
return task['id'] return task['id']
@@ -152,17 +152,17 @@ def delete_group(group_id, tasks=False):
return Task.delete_group(group_id, tasks) return Task.delete_group(group_id, tasks)
def queue_size(list_key=Conf.Q_LIST, r=redis_client): def queue_size(broker=get_broker()):
""" """
Returns the current queue size. Returns the current queue size.
Note that this doesn't count any tasks currently being processed by workers. Note that this doesn't count any tasks currently being processed by workers.
:param list_key: optional redis key :param list_key: optional list key
:param r: optional redis connection :param broker: optional broker
:return: current queue size :return: current queue size
:rtype: int :rtype: int
""" """
return r.llen(list_key) return broker.queue_size()
def _sync(pack): def _sync(pack):
+73 -72
View File
@@ -13,8 +13,9 @@ from django_q.cluster import Cluster, Sentinel, pusher, worker, monitor
from django_q.humanhash import DEFAULT_WORDLIST from django_q.humanhash import DEFAULT_WORDLIST
from django_q.tasks import fetch, fetch_group, async, result, result_group, count_group, delete_group, queue_size from django_q.tasks import fetch, fetch_group, async, result, result_group, count_group, delete_group, queue_size
from django_q.models import Task, Success from django_q.models import Task, Success
from django_q.conf import Conf, redis_client from django_q.conf import Conf
from django_q.status import Stat from django_q.status import Stat
from django_q.brokers import get_broker
from .tasks import multiply from .tasks import multiply
@@ -27,25 +28,25 @@ class WordClass(object):
@pytest.fixture @pytest.fixture
def r(): def broker():
return redis_client return get_broker()
def test_redis_connection(r): def test_redis_connection(broker):
assert r.ping() is True assert broker.ping() is True
@pytest.mark.django_db @pytest.mark.django_db
def test_sync(r): def test_sync(broker):
task = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, redis=r, sync=True) task = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, broker=broker, sync=True)
assert result(task) == 1506 assert result(task) == 1506
@pytest.mark.django_db @pytest.mark.django_db
def test_cluster_initial(r): def test_cluster_initial(broker):
list_key = 'initial_test:q' broker.list_key = 'initial_test:q'
r.delete(list_key) broker.delete_queue()
c = Cluster(list_key=list_key) c = Cluster(broker=broker)
assert c.sentinel is None assert c.sentinel is None
assert c.stat.status == Conf.STOPPED assert c.stat.status == Conf.STOPPED
assert c.start() > 0 assert c.start() > 0
@@ -59,7 +60,7 @@ def test_cluster_initial(r):
assert c.stop() is True assert c.stop() is True
assert c.sentinel.is_alive() is False assert c.sentinel.is_alive() is False
assert c.has_stopped assert c.has_stopped
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
@@ -67,17 +68,17 @@ def test_sentinel():
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
s = Sentinel(stop_event, start_event, list_key='sentinel_test:q') s = Sentinel(stop_event, start_event, broker=get_broker('sentinel_test:q'))
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
@pytest.mark.django_db @pytest.mark.django_db
def test_cluster(r): def test_cluster(broker):
list_key = 'cluster_test:q' broker.list_key = 'cluster_test:q'
r.delete(list_key) broker.delete_queue()
task = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, list_key=list_key) task = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, broker=broker)
assert queue_size(list_key=list_key, r=r) == 1 assert broker.queue_size() == 1
task_queue = Queue() task_queue = Queue()
assert task_queue.qsize() == 0 assert task_queue.qsize() == 0
result_queue = Queue() result_queue = Queue()
@@ -85,9 +86,9 @@ def test_cluster(r):
event = Event() event = Event()
event.set() event.set()
# Test push # Test push
pusher(task_queue, event, list_key=list_key) pusher(task_queue, event, broker=broker)
assert task_queue.qsize() == 1 assert task_queue.qsize() == 1
assert queue_size(list_key=list_key, r=r) == 0 assert queue_size(broker=broker) == 0
# Test work # Test work
task_queue.put('STOP') task_queue.put('STOP')
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value('f', -1))
@@ -99,36 +100,36 @@ def test_cluster(r):
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
# check result # check result
assert result(task) == 1506 assert result(task) == 1506
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_async(r, admin_user): def test_async(broker, admin_user):
list_key = 'cluster_test:q' broker.list_key = 'cluster_test:q'
r.delete(list_key) broker.delete_queue()
a = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, hook='django_q.tests.test_cluster.assert_result', a = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, hook='django_q.tests.test_cluster.assert_result',
list_key=list_key, redis=r) broker=broker)
b = async('django_q.tests.tasks.count_letters2', WordClass(), hook='django_q.tests.test_cluster.assert_result', b = async('django_q.tests.tasks.count_letters2', WordClass(), hook='django_q.tests.test_cluster.assert_result',
list_key=list_key, redis=r) broker=broker)
# unknown argument # unknown argument
c = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, 'oneargumentoomany', c = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, 'oneargumentoomany',
hook='django_q.tests.test_cluster.assert_bad_result', list_key=list_key, redis=r) hook='django_q.tests.test_cluster.assert_bad_result', broker=broker)
# unknown function # unknown function
d = async('django_q.tests.tasks.does_not_exist', WordClass(), hook='django_q.tests.test_cluster.assert_bad_result', d = async('django_q.tests.tasks.does_not_exist', WordClass(), hook='django_q.tests.test_cluster.assert_bad_result',
list_key=list_key, redis=r) broker=broker)
# function without result # function without result
e = async('django_q.tests.tasks.countdown', 100000, list_key=list_key, redis=r) e = async('django_q.tests.tasks.countdown', 100000, broker=broker)
# function as instance # function as instance
f = async(multiply, 753, 2, hook=assert_result, list_key=list_key, redis=r) f = async(multiply, 753, 2, hook=assert_result, broker=broker)
# model as argument # model as argument
g = async('django_q.tests.tasks.get_task_name', Task(name='John'), list_key=list_key, redis=r) g = async('django_q.tests.tasks.get_task_name', Task(name='John'), broker=broker)
# args,kwargs, group and broken hook # args,kwargs, group and broken hook
h = async('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', list_key=list_key, redis=r) h = async('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', broker=broker)
# args unpickle test # args unpickle test
j = async('django_q.tests.tasks.get_user_id', admin_user, list_key=list_key, group='test_j', redis=r) j = async('django_q.tests.tasks.get_user_id', admin_user, broker=broker, group='test_j')
# q_options and save opt_out test # q_options and save opt_out test
k = async('django_q.tests.tasks.get_user_id', admin_user, k = async('django_q.tests.tasks.get_user_id', admin_user,
q_options={'list_key': list_key, 'group': 'test_k', 'redis': r, 'save': False, 'timeout': 90}) q_options={'broker': broker, 'group': 'test_k', 'save': False, 'timeout': 90})
# check if everything has a task id # check if everything has a task id
assert isinstance(a, str) assert isinstance(a, str)
assert isinstance(b, str) assert isinstance(b, str)
@@ -142,14 +143,14 @@ def test_async(r, admin_user):
assert isinstance(k, str) assert isinstance(k, str)
# run the cluster to execute the tasks # run the cluster to execute the tasks
task_count = 10 task_count = 10
assert queue_size(list_key=list_key, r=r) == task_count assert broker.queue_size() == task_count
task_queue = Queue() task_queue = Queue()
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
# push the tasks # push the tasks
for i in range(task_count): for i in range(task_count):
pusher(task_queue, stop_event, list_key=list_key) pusher(task_queue, stop_event, broker=broker)
assert queue_size(list_key=list_key, r=r) == 0 assert broker.queue_size() == 0
assert task_queue.qsize() == task_count assert task_queue.qsize() == task_count
task_queue.put('STOP') task_queue.put('STOP')
# let a worker handle them # let a worker handle them
@@ -218,64 +219,64 @@ def test_async(r, admin_user):
assert delete_group('test_j', tasks=True) is None assert delete_group('test_j', tasks=True) is None
# task k should not have been saved # task k should not have been saved
assert fetch(k) is None assert fetch(k) is None
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_timeout(r): def test_timeout(broker):
# set up the Sentinel # set up the Sentinel
list_key = 'timeout_test:q' broker.list_key = 'timeout_test:q'
async('django_q.tests.tasks.count_forever', list_key=list_key) async('django_q.tests.tasks.count_forever',broker=broker)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
# Set a timer to stop the Sentinel # Set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, list_key=list_key, timeout=1) s = Sentinel(stop_event, start_event, broker=broker, timeout=1)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_timeout(r): def test_timeout(broker):
# set up the Sentinel # set up the Sentinel
list_key = 'timeout_test:q' broker.list_key = 'timeout_test:q'
async('django_q.tests.tasks.count_forever', list_key=list_key) async('django_q.tests.tasks.count_forever', broker=broker)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
# Set a timer to stop the Sentinel # Set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, list_key=list_key, timeout=1) s = Sentinel(stop_event, start_event,broker=broker, timeout=1)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_timeout_override(r): def test_timeout_override(broker):
# set up the Sentinel # set up the Sentinel
list_key = 'timeout_override_test:q' broker.list_key = 'timeout_override_test:q'
async('django_q.tests.tasks.count_forever', list_key=list_key, timeout=1) async('django_q.tests.tasks.count_forever', broker=broker, timeout=1)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
# Set a timer to stop the Sentinel # Set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, list_key=list_key, timeout=10) s = Sentinel(stop_event, start_event, broker=broker, timeout=10)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_recycle(r): def test_recycle(broker):
# set up the Sentinel # set up the Sentinel
list_key = 'test_recycle_test:q' broker.list_key = 'test_recycle_test:q'
async('django_q.tests.tasks.multiply', 2, 2, list_key=list_key, redis=r) async('django_q.tests.tasks.multiply', 2, 2, broker=broker)
async('django_q.tests.tasks.multiply', 2, 2, list_key=list_key, redis=r) async('django_q.tests.tasks.multiply', 2, 2, broker=broker)
async('django_q.tests.tasks.multiply', 2, 2, list_key=list_key, redis=r) async('django_q.tests.tasks.multiply', 2, 2, broker=broker)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
# override settings # override settings
@@ -283,17 +284,17 @@ def test_recycle(r):
Conf.WORKERS = 1 Conf.WORKERS = 1
# set a timer to stop the Sentinel # set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, list_key=list_key) s = Sentinel(stop_event, start_event,broker=broker)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
async('django_q.tests.tasks.multiply', 2, 2, list_key=list_key, redis=r) async('django_q.tests.tasks.multiply', 2, 2, broker=broker)
async('django_q.tests.tasks.multiply', 2, 2, list_key=list_key, redis=r) async('django_q.tests.tasks.multiply', 2, 2, broker=broker)
task_queue = Queue() task_queue = Queue()
result_queue = Queue() result_queue = Queue()
# push two tasks # push two tasks
pusher(task_queue, stop_event, list_key=list_key) pusher(task_queue, stop_event, broker=broker)
pusher(task_queue, stop_event, list_key=list_key) pusher(task_queue, stop_event, broker=broker)
# worker should exit on recycle # worker should exit on recycle
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value('f', -1))
# check if the work has been done # check if the work has been done
@@ -304,30 +305,30 @@ def test_recycle(r):
# run monitor # run monitor
monitor(result_queue) monitor(result_queue)
assert Success.objects.count() == Conf.SAVE_LIMIT assert Success.objects.count() == Conf.SAVE_LIMIT
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_bad_secret(r, monkeypatch): def test_bad_secret(broker, monkeypatch):
list_key = 'test_bad_secret' broker.list_key='test_bad_secret:q'
async('math.copysign', 1, -1, list_key=list_key) async('math.copysign', 1, -1, broker=broker)
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
start_event = Event() start_event = Event()
s = Sentinel(stop_event, start_event, list_key=list_key, start=False) s = Sentinel(stop_event, start_event, broker=broker, start=False)
Stat(s).save() Stat(s).save()
# change the SECRET # change the SECRET
monkeypatch.setattr(Conf, "SECRET_KEY", "OOPS") monkeypatch.setattr(Conf, "SECRET_KEY", "OOPS")
stat = Stat.get_all(r) stat = Stat.get_all()
assert len(stat) == 0 assert len(stat) == 0
assert Stat.get(s.parent_pid, r) is None assert Stat.get(s.parent_pid) is None
task_queue = Queue() task_queue = Queue()
pusher(task_queue, stop_event, list_key=list_key) pusher(task_queue, stop_event, broker=broker)
result_queue = Queue() result_queue = Queue()
task_queue.put('STOP') task_queue.put('STOP')
worker(task_queue, result_queue, Value('f', -1), ) worker(task_queue, result_queue, Value('f', -1), )
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
r.delete(list_key) broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
-5
View File
@@ -3,11 +3,6 @@ import pytest
from django_q import conf from django_q import conf
@pytest.fixture
def r():
return conf.redis_client
def test_django_redis(): def test_django_redis():
conf.Conf.DJANGO_REDIS = None conf.Conf.DJANGO_REDIS = None
assert conf.redis_client.ping() is True assert conf.redis_client.ping() is True
+1 -9
View File
@@ -1,10 +1,9 @@
import pytest import pytest
import redis
from django_q import async from django_q import async
from django_q.cluster import Cluster from django_q.cluster import Cluster
from django_q.monitor import monitor, info from django_q.monitor import monitor, info
from django_q.status import Stat, ping_redis from django_q.status import Stat
@pytest.mark.django_db @pytest.mark.django_db
@@ -37,10 +36,3 @@ def test_info():
def do_sync(): def do_sync():
async('django_q.tests.tasks.countdown', 1, sync=True, save=True) async('django_q.tests.tasks.countdown', 1, sync=True, save=True)
@pytest.mark.django_db
def test_ping_redis():
r = redis.StrictRedis(port=6388)
with pytest.raises(Exception):
ping_redis(r)
+15 -14
View File
@@ -6,20 +6,21 @@ import arrow
from django.utils import timezone from django.utils import timezone
from django_q.conf import redis_client, Conf from django_q.brokers import get_broker
from django_q.conf import Conf
from django_q.cluster import pusher, worker, monitor, scheduler from django_q.cluster import pusher, worker, monitor, scheduler
from django_q.tasks import Schedule, fetch, schedule as create_schedule, queue_size from django_q.tasks import Schedule, fetch, schedule as create_schedule, queue_size
@pytest.fixture @pytest.fixture
def r(): def broker():
return redis_client return get_broker()
@pytest.mark.django_db @pytest.mark.django_db
def test_scheduler(r): def test_scheduler(broker):
list_key = 'scheduler_test:q' broker.list_key = 'scheduler_test:q'
r.delete(list_key) broker.delete_queue()
schedule = create_schedule('math.copysign', schedule = create_schedule('math.copysign',
1, -1, 1, -1,
name='test math', name='test math',
@@ -28,15 +29,15 @@ def test_scheduler(r):
repeats=1) repeats=1)
assert schedule.last_run() is None assert schedule.last_run() is None
# run scheduler # run scheduler
scheduler(list_key=list_key) scheduler(broker=broker)
# set up the workflow # set up the workflow
task_queue = Queue() task_queue = Queue()
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
# push it # push it
pusher(task_queue, stop_event, list_key=list_key) pusher(task_queue, stop_event, broker=broker)
assert task_queue.qsize() == 1 assert task_queue.qsize() == 1
assert queue_size(list_key=list_key, r=r) == 0 assert broker.queue_size() == 0
task_queue.put('STOP') task_queue.put('STOP')
# let a worker handle them # let a worker handle them
result_queue = Queue() result_queue = Queue()
@@ -91,7 +92,7 @@ def test_scheduler(r):
) )
assert schedule is not None assert schedule is not None
assert schedule.last_run() is None assert schedule.last_run() is None
scheduler(list_key=list_key) scheduler(broker=broker)
# via model # via model
Schedule.objects.create(func='django_q.tests.tasks.word_multiply', Schedule.objects.create(func='django_q.tests.tasks.word_multiply',
args='2', args='2',
@@ -99,7 +100,7 @@ def test_scheduler(r):
schedule_type=Schedule.DAILY schedule_type=Schedule.DAILY
) )
# scheduler # scheduler
scheduler(list_key=list_key) scheduler(broker=broker)
# ONCE schedule should be deleted # ONCE schedule should be deleted
assert Schedule.objects.filter(pk=once_schedule.pk).exists() is False assert Schedule.objects.filter(pk=once_schedule.pk).exists() is False
# Catch up On # Catch up On
@@ -112,13 +113,13 @@ def test_scheduler(r):
next_run=timezone.now() - timedelta(hours=12), next_run=timezone.now() - timedelta(hours=12),
repeats=-1 repeats=-1
) )
scheduler(list_key=list_key) scheduler(broker=broker)
schedule = Schedule.objects.get(pk=schedule.pk) schedule = Schedule.objects.get(pk=schedule.pk)
assert schedule.next_run < now assert schedule.next_run < now
# Catch up off # Catch up off
Conf.CATCH_UP = False Conf.CATCH_UP = False
scheduler(list_key=list_key) scheduler(broker=broker)
schedule = Schedule.objects.get(pk=schedule.pk) schedule = Schedule.objects.get(pk=schedule.pk)
assert schedule.next_run > now assert schedule.next_run > now
# Done # Done
r.delete(list_key) broker.delete_queue()