Added tasks app
This commit is contained in:
parent
1bb37b1175
commit
184436dbe4
53
TODO.md
53
TODO.md
|
@ -297,13 +297,8 @@ https://code.djangoproject.com/ticket/24576
|
||||||
# admin edit relevant djanog settings
|
# admin edit relevant djanog settings
|
||||||
# django SITE_NAME vs ORCHESTRA_SITE_NAME ?
|
# django SITE_NAME vs ORCHESTRA_SITE_NAME ?
|
||||||
|
|
||||||
# accounts.migrations link to last auth migration instead of first
|
|
||||||
|
|
||||||
|
|
||||||
# DNS allow transfer other NS servers instead of masters and slaves!
|
|
||||||
|
|
||||||
Replace celery by a custom solution?
|
Replace celery by a custom solution?
|
||||||
# TODO create periodic task like settings, but parsing cronfiles!
|
|
||||||
# TODO create decorator wrapper that abstract the task away from the backen (cron/celery)
|
# TODO create decorator wrapper that abstract the task away from the backen (cron/celery)
|
||||||
# TODO crontab model localhost/autoadded attribute
|
# TODO crontab model localhost/autoadded attribute
|
||||||
* No more jumbo dependencies and wierd bugs
|
* No more jumbo dependencies and wierd bugs
|
||||||
|
@ -316,15 +311,49 @@ Replace celery by a custom solution?
|
||||||
*priority: custom Thread backend
|
*priority: custom Thread backend
|
||||||
*bulk: wrapper arround django-mailer to avoid loading django system
|
*bulk: wrapper arround django-mailer to avoid loading django system
|
||||||
|
|
||||||
|
python3 -mvenv env-django-orchestra
|
||||||
|
source env-django-orchestra/bin/activate
|
||||||
|
pip3 install django-orchestra==dev --allow-external django-orchestra --allow-unverified django-orchestra
|
||||||
|
pip3 install -r https://raw.githubusercontent.com/glic3rinu/django-orchestra/master/requirements.txt
|
||||||
|
|
||||||
|
# TODO make them optional
|
||||||
|
sudo apt-get install python3.4-dev libxml2-dev libxslt1-dev libcrack2-dev
|
||||||
|
wget -O - https://raw.githubusercontent.com/glic3rinu/django-orchestra/master/requirements.txt | xargs pip3 install
|
||||||
|
django-admin.py startproject panel --template="$HOME/django-orchestra/orchestra/conf/project_template"
|
||||||
|
python3 panel/manage.py migrate accounts
|
||||||
|
python3 panel/manage.py migrate
|
||||||
|
python3 panel/manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Collecting lxml==3.3.5 (from -r re (line 22))
|
||||||
|
Downloading lxml-3.3.5.tar.gz (3.5MB)
|
||||||
|
100% |################################| 3.5MB 60kB/s
|
||||||
|
Building lxml version 3.3.5.
|
||||||
|
Building without Cython.
|
||||||
|
ERROR: b'/bin/sh: 1: xslt-config: not found\n'
|
||||||
|
** make sure the development packages of libxml2 and libxslt are installed **
|
||||||
|
Using build configuration of libxslt
|
||||||
|
/usr/lib/python3.4/distutils/dist.py:260: UserWarning: Unknown distribution option: 'bugtrack_url'
|
||||||
|
warnings.warn(msg)
|
||||||
|
|
||||||
|
|
||||||
|
# Setupcron
|
||||||
# uwsgi enable threads
|
# uwsgi enable threads
|
||||||
# Create superuser on migrate
|
|
||||||
# register signals in app ready()
|
# register signals in app ready()
|
||||||
def ready(self):
|
|
||||||
if self.has_attr('ready_run'): return
|
|
||||||
self.ready_run = True
|
|
||||||
|
|
||||||
# database_ready(): connect to the database or inspect django connection
|
# database_ready(): connect to the database or inspect django connection
|
||||||
# beat.sh
|
|
||||||
|
|
||||||
# do settings validation on orchestra.apps.ready(), not during startime
|
# move Setting to contrib app __init__
|
||||||
|
# cracklib vs crack
|
||||||
|
# remove system dependencies
|
||||||
|
# deprecate install_dependnecies in favour of only requirements.txt
|
||||||
|
# import module and sed
|
||||||
|
# if setting.value == default. remove
|
||||||
|
# cron backend: os.cron or uwsgi.cron
|
||||||
|
# reload generic admin view ?redirect=http...
|
||||||
|
# inspecting django db connection for asserting db readines?
|
||||||
|
# wake up django mailer on send_mail
|
||||||
|
|
||||||
|
# project settings modified copy of django's default project settings
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
default_app_config = 'orchestra.apps.OrchestraConfig'
|
||||||
|
|
||||||
VERSION = (0, 0, 1, 'alpha', 1)
|
VERSION = (0, 0, 1, 'alpha', 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
6
orchestra/apps.py
Normal file
6
orchestra/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OrchestraConfig(AppConfig):
|
||||||
|
name = 'orchestra'
|
||||||
|
verbose_name = 'Orchestra'
|
|
@ -122,6 +122,11 @@ function install_requirements () {
|
||||||
check_root || true
|
check_root || true
|
||||||
ORCHESTRA_PATH=$(get_orchestra_dir) || true
|
ORCHESTRA_PATH=$(get_orchestra_dir) || true
|
||||||
|
|
||||||
|
# TODO reduce this list to 0
|
||||||
|
# include /usr/sbin/named-checkzone
|
||||||
|
# wkhtmltopdf -> reportlab
|
||||||
|
# remove rabbit, postgres
|
||||||
|
# uwsgi –py-autoreload for devel
|
||||||
APT="python3 \
|
APT="python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3-psycopg2 \
|
python3-psycopg2 \
|
||||||
|
@ -137,6 +142,7 @@ function install_requirements () {
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
gettext"
|
gettext"
|
||||||
|
|
||||||
|
# TODO remove celery deps, django 1.8.1, glic3rinu fork, celery email
|
||||||
PIP="django==1.8 \
|
PIP="django==1.8 \
|
||||||
django-celery-email==1.0.4 \
|
django-celery-email==1.0.4 \
|
||||||
https://github.com/glic3rinu/django-fluent-dashboard/archive/master.zip \
|
https://github.com/glic3rinu/django-fluent-dashboard/archive/master.zip \
|
||||||
|
@ -202,12 +208,14 @@ function install_requirements () {
|
||||||
|
|
||||||
run pip3 install $PIP
|
run pip3 install $PIP
|
||||||
|
|
||||||
|
# TODO remove
|
||||||
# Some versions of rabbitmq-server will not start automatically by default unless ...
|
# Some versions of rabbitmq-server will not start automatically by default unless ...
|
||||||
sed -i "s/# Default-Start:.*/# Default-Start: 2 3 4 5/" /etc/init.d/rabbitmq-server
|
sed -i "s/# Default-Start:.*/# Default-Start: 2 3 4 5/" /etc/init.d/rabbitmq-server
|
||||||
sed -i "s/# Default-Stop:.*/# Default-Stop: 0 1 6/" /etc/init.d/rabbitmq-server
|
sed -i "s/# Default-Stop:.*/# Default-Stop: 0 1 6/" /etc/init.d/rabbitmq-server
|
||||||
run update-rc.d rabbitmq-server defaults
|
run update-rc.d rabbitmq-server defaults
|
||||||
|
|
||||||
# Patch passlib
|
# Patch passlib
|
||||||
|
# TODO discover locaion by importing it
|
||||||
IMPORT="from django.contrib.auth.hashers import mask_hash, _"
|
IMPORT="from django.contrib.auth.hashers import mask_hash, _"
|
||||||
COLLECTIONS="from collections import OrderedDict"
|
COLLECTIONS="from collections import OrderedDict"
|
||||||
ls /usr/local/lib/python*/dist-packages/passlib/ext/django/utils.py \
|
ls /usr/local/lib/python*/dist-packages/passlib/ext/django/utils.py \
|
||||||
|
|
|
@ -89,7 +89,7 @@ DOMAINS_DEFAULT_MX = Setting('DOMAINS_DEFAULT_MX',
|
||||||
'10 mail.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
'10 mail.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
'10 mail2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
'10 mail2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
),
|
),
|
||||||
validators=[lambda mxs: map(validate_mx_record, mxs)],
|
validators=[lambda mxs: list(map(validate_mx_record, mxs))],
|
||||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ DOMAINS_DEFAULT_NS = Setting('DOMAINS_DEFAULT_NS',
|
||||||
'ns1.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
'ns1.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
'ns2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
'ns2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
),
|
),
|
||||||
validators=[lambda nss: map(validate_domain_name, nss)],
|
validators=[lambda nss: list(map(validate_domain_name, nss))],
|
||||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -118,6 +118,6 @@ DOMAINS_FORBIDDEN = Setting('DOMAINS_FORBIDDEN',
|
||||||
|
|
||||||
DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
|
DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
|
||||||
(),
|
(),
|
||||||
validators=[lambda masters: map(validate_ip_address, masters)],
|
validators=[lambda masters: list(map(validate_ip_address, masters))],
|
||||||
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from celery.task.schedules import crontab
|
from celery.task.schedules import crontab
|
||||||
from celery.decorators import periodic_task
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from orchestra.contrib.tasks import periodic_task
|
||||||
|
|
||||||
from .models import BackendLog
|
from .models import BackendLog
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from djcelery.models import CrontabSchedule
|
|
||||||
|
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.models import queryset, fields
|
from orchestra.models import queryset, fields
|
||||||
|
@ -64,7 +63,7 @@ class Resource(models.Model):
|
||||||
"be prorcessed to match with unit. e.g. <tt>10**9</tt>"))
|
"be prorcessed to match with unit. e.g. <tt>10**9</tt>"))
|
||||||
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
|
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
|
||||||
help_text=_("Disables monitors exeeded and recovery triggers"))
|
help_text=_("Disables monitors exeeded and recovery triggers"))
|
||||||
crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"),
|
crontab = models.ForeignKey('djcelery.CrontabSchedule', verbose_name=_("crontab"),
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
help_text=_("Crontab for periodic execution. "
|
help_text=_("Crontab for periodic execution. "
|
||||||
"Leave it empty to disable periodic monitoring"))
|
"Leave it empty to disable periodic monitoring"))
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
from celery import shared_task
|
|
||||||
|
|
||||||
from orchestra.contrib.orchestration import Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
|
from orchestra.contrib.tasks import task
|
||||||
from orchestra.models.utils import get_model_field_path
|
from orchestra.models.utils import get_model_field_path
|
||||||
|
|
||||||
from .backends import ServiceMonitor
|
from .backends import ServiceMonitor
|
||||||
|
|
||||||
|
|
||||||
@shared_task(name='resources.Monitor')
|
@task(name='resources.Monitor')
|
||||||
def monitor(resource_id, ids=None, async=True):
|
def monitor(resource_id, ids=None, async=True):
|
||||||
from .models import ResourceData, Resource
|
from .models import ResourceData, Resource
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,17 @@ class SettingView(generic.edit.FormView):
|
||||||
messages.success(self.request, _("No changes have been detected."))
|
messages.success(self.request, _("No changes have been detected."))
|
||||||
return super(SettingView, self).form_valid(form)
|
return super(SettingView, self).form_valid(form)
|
||||||
|
|
||||||
|
from orchestra.contrib.tasks import task
|
||||||
|
import time, sys
|
||||||
|
@task(name='rata')
|
||||||
|
def counter(num, log):
|
||||||
|
for i in range(1, num):
|
||||||
|
with open(log, 'a') as handler:
|
||||||
|
handler.write(str(i))
|
||||||
|
# sys.stderr.write('hola\n')
|
||||||
|
time.sleep(1)
|
||||||
|
#counter.apply_async(10, '/tmp/kakas')
|
||||||
|
|
||||||
|
|
||||||
class SettingFileView(generic.TemplateView):
|
class SettingFileView(generic.TemplateView):
|
||||||
template_name = 'admin/settings/view.html'
|
template_name = 'admin/settings/view.html'
|
||||||
|
|
103
orchestra/contrib/tasks/__init__.py
Normal file
103
orchestra/contrib/tasks/__init__.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import traceback
|
||||||
|
from functools import partial, wraps, update_wrapper
|
||||||
|
from multiprocessing import Process
|
||||||
|
from uuid import uuid4
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
from celery import shared_task as celery_shared_task
|
||||||
|
from celery import states
|
||||||
|
from celery.decorators import periodic_task as celery_periodic_task
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from orchestra.utils.db import close_connection
|
||||||
|
from orchestra.utils.python import AttrDict, OrderedSet
|
||||||
|
|
||||||
|
|
||||||
|
def get_id():
|
||||||
|
return str(uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
def get_name(fn):
|
||||||
|
return '.'.join((fn.__module__, fn.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
def keep_state(fn):
|
||||||
|
""" logs task on djcelery's TaskState model """
|
||||||
|
@wraps(fn)
|
||||||
|
def wrapper(task_id, name, *args, **kwargs):
|
||||||
|
from djcelery.models import TaskState
|
||||||
|
now = timezone.now()
|
||||||
|
state = TaskState.objects.create(state=states.STARTED, task_id=task_id, name=name, args=str(args),
|
||||||
|
kwargs=str(kwargs), tstamp=now)
|
||||||
|
try:
|
||||||
|
result = fn(*args, **kwargs)
|
||||||
|
except Exception as exc:
|
||||||
|
state.state = states.FAILURE
|
||||||
|
state.traceback = traceback.format_exc()
|
||||||
|
state.runtime = (timezone.now()-now).total_seconds()
|
||||||
|
state.save()
|
||||||
|
return
|
||||||
|
# TODO send email
|
||||||
|
else:
|
||||||
|
state.state = states.SUCCESS
|
||||||
|
state.result = str(result)
|
||||||
|
state.runtime = (timezone.now()-now).total_seconds()
|
||||||
|
state.save()
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def apply_async(fn, name=None, method='thread'):
|
||||||
|
""" replaces celery apply_async """
|
||||||
|
def inner(fn, name, method, *args, **kwargs):
|
||||||
|
task_id = get_id()
|
||||||
|
args = (task_id, name) + args
|
||||||
|
thread = Process(target=fn, args=args, kwargs=kwargs)
|
||||||
|
thread.start()
|
||||||
|
# Celery API compat
|
||||||
|
thread.request = AttrDict(id=task_id)
|
||||||
|
return thread
|
||||||
|
if name is None:
|
||||||
|
name = get_name(fn)
|
||||||
|
if method == 'thread':
|
||||||
|
method = Thread
|
||||||
|
elif method == 'process':
|
||||||
|
method = Process
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Support for %s concurrency method is not supported." % method)
|
||||||
|
fn.apply_async = partial(inner, close_connection(keep_state(fn)), name, method)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
|
||||||
|
def apply_async_override(fn, name):
|
||||||
|
if fn is None:
|
||||||
|
def decorator(fn):
|
||||||
|
return update_wrapper(apply_async(fn), fn)
|
||||||
|
return decorator
|
||||||
|
return update_wrapper(apply_async(fn, name), fn)
|
||||||
|
|
||||||
|
|
||||||
|
def task(fn=None, **kwargs):
|
||||||
|
from . import settings
|
||||||
|
# register task
|
||||||
|
if fn is None:
|
||||||
|
fn = celery_shared_task(**kwargs)
|
||||||
|
else:
|
||||||
|
fn = celery_shared_task(fn)
|
||||||
|
if settings.TASKS_BACKEND in ('thread', 'process'):
|
||||||
|
name = kwargs.pop('name', None)
|
||||||
|
apply_async_override(fn, name)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
|
||||||
|
def periodic_task(fn=None, **kwargs):
|
||||||
|
from . import settings
|
||||||
|
# register task
|
||||||
|
if fn is None:
|
||||||
|
fn = celery_periodic_task(**kwargs)
|
||||||
|
else:
|
||||||
|
fn = celery_periodic_task(fn)
|
||||||
|
if settings.TASKS_BACKEND in ('thread', 'process'):
|
||||||
|
name = kwargs.pop('name', None)
|
||||||
|
apply_async_override(fn, name)
|
||||||
|
return fn
|
9
orchestra/contrib/tasks/admin.py
Normal file
9
orchestra/contrib/tasks/admin.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from djcelery.admin import PeriodicTaskAdmin
|
||||||
|
|
||||||
|
from orchestra.admin.utils import admin_date
|
||||||
|
|
||||||
|
|
||||||
|
display_last_run_at = admin_date('last_run_at', short_description=_("Last run"))
|
||||||
|
|
||||||
|
PeriodicTaskAdmin.list_display = ('__unicode__', display_last_run_at, 'total_run_count', 'enabled')
|
43
orchestra/contrib/tasks/beat.py
Normal file
43
orchestra/contrib/tasks/beat.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from celery import current_app
|
||||||
|
from celery.schedules import crontab_parser as CrontabParser
|
||||||
|
from django.utils import timezone
|
||||||
|
from djcelery.models import PeriodicTask
|
||||||
|
|
||||||
|
from . import apply_async
|
||||||
|
|
||||||
|
|
||||||
|
def is_due(task, time=None):
|
||||||
|
if time is None:
|
||||||
|
time = timezone.now()
|
||||||
|
crontab = task.crontab
|
||||||
|
parts = map(int, time.strftime("%M %H %w %d %m").split())
|
||||||
|
n_minute, n_hour, n_day_of_week, n_day_of_month, n_month_of_year = parts
|
||||||
|
return bool(
|
||||||
|
n_minute in CrontabParser(60).parse(crontab.minute) and
|
||||||
|
n_hour in CrontabParser(24).parse(crontab.hour) and
|
||||||
|
n_day_of_week in CrontabParser(7).parse(crontab.day_of_week) and
|
||||||
|
n_day_of_month in CrontabParser(31, 1).parse(crontab.day_of_month) and
|
||||||
|
n_month_of_year in CrontabParser(12, 1).parse(crontab.month_of_year)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_task(task, thread=True, process=False, async=False):
|
||||||
|
args = json.loads(task.args)
|
||||||
|
kwargs = json.loads(task.kwargs)
|
||||||
|
task_fn = current_app.tasks.get(task.task)
|
||||||
|
if async:
|
||||||
|
method = 'process' if process else 'thread'
|
||||||
|
return apply_async(task_fn, method=method).apply_async(*args, **kwargs)
|
||||||
|
return task_fn(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
now = timezone.now()
|
||||||
|
procs = []
|
||||||
|
for task in PeriodicTask.objects.enabled().select_related('crontab'):
|
||||||
|
if is_due(task, now):
|
||||||
|
proc = run_task(task, process=True, async=True)
|
||||||
|
procs.append(proc)
|
||||||
|
[proc.join() for proc in procs]
|
83
orchestra/contrib/tasks/bin/orchestra-beat
Executable file
83
orchestra/contrib/tasks/bin/orchestra-beat
Executable file
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# High performance alternative to beat management command
|
||||||
|
#
|
||||||
|
# USAGE: beat /path/to/project/manage.py
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from orchestra.utils import db
|
||||||
|
from orchestra.utils.python import import_class
|
||||||
|
from orchestra.utils.sys import run, join
|
||||||
|
|
||||||
|
from celery.schedules import crontab_parser as CrontabParser
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings_file(manage):
|
||||||
|
with open(manage, 'r') as handler:
|
||||||
|
regex = re.compile(r'"DJANGO_SETTINGS_MODULE"\s*,\s*"([^"]+)"')
|
||||||
|
for line in handler.readlines():
|
||||||
|
match = regex.search(line)
|
||||||
|
if match:
|
||||||
|
settings_module = match.groups()[0]
|
||||||
|
settings_file = os.path.join(*settings_module.split('.')) + '.py'
|
||||||
|
settings_file = os.path.join(os.path.dirname(manage), settings_file)
|
||||||
|
return settings_file
|
||||||
|
raise ValueError("settings module not found in %s" % manage)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tasks(manage):
|
||||||
|
settings_file = get_settings_file(manage)
|
||||||
|
settings = db.get_settings(settings_file)
|
||||||
|
try:
|
||||||
|
conn = db.get_connection(settings)
|
||||||
|
except:
|
||||||
|
sys.stdout.write("ERROR")
|
||||||
|
sys.stderr.write("I am unable to connect to the database\n")
|
||||||
|
sys.exit(1)
|
||||||
|
script, settings_file = sys.argv[:2]
|
||||||
|
query = (
|
||||||
|
"SELECT c.minute, c.hour, c.day_of_week, c.day_of_month, c.month_of_year, p.id "
|
||||||
|
"FROM djcelery_periodictask as p, djcelery_crontabschedule as c "
|
||||||
|
"WHERE p.crontab_id = c.id AND p.enabled = True"
|
||||||
|
)
|
||||||
|
tasks = db.run_query(conn, query)
|
||||||
|
conn.close()
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
|
||||||
|
def is_due(now, minute, hour, day_of_week, day_of_month, month_of_year):
|
||||||
|
n_minute, n_hour, n_day_of_week, n_day_of_month, n_month_of_year = now
|
||||||
|
return (
|
||||||
|
n_minute in CrontabParser(60).parse(minute) and
|
||||||
|
n_hour in CrontabParser(24).parse(hour) and
|
||||||
|
n_day_of_week in CrontabParser(7).parse(day_of_week) and
|
||||||
|
n_day_of_month in CrontabParser(31, 1).parse(day_of_month) and
|
||||||
|
n_month_of_year in CrontabParser(12, 1).parse(month_of_year)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
manage = sys.argv[1]
|
||||||
|
now = datetime.utcnow()
|
||||||
|
now = tuple(map(int, now.strftime("%M %H %w %d %m").split()))
|
||||||
|
procs = []
|
||||||
|
for minute, hour, day_of_week, day_of_month, month_of_year, task_id in get_tasks(manage):
|
||||||
|
if is_due(now, minute, hour, day_of_week, day_of_month, month_of_year):
|
||||||
|
command = 'python3 -W ignore::DeprecationWarning {manage} runtask {task_id}'.format(
|
||||||
|
manage=manage, task_id=task_id)
|
||||||
|
proc = run(command, async=True)
|
||||||
|
procs.append(proc)
|
||||||
|
code = 0
|
||||||
|
for proc in procs:
|
||||||
|
result = join(proc)
|
||||||
|
sys.stdout.write(result.stdout.decode('utf8'))
|
||||||
|
sys.stderr.write(result.stderr.decode('utf8'))
|
||||||
|
if result.return_code != 0:
|
||||||
|
code = result.return_code
|
||||||
|
sys.exit(code)
|
10
orchestra/contrib/tasks/management/commands/beat.py
Normal file
10
orchestra/contrib/tasks/management/commands/beat.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from ... import beat
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Runs periodic tasks.'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
beat.run()
|
32
orchestra/contrib/tasks/management/commands/runfunction.py
Normal file
32
orchestra/contrib/tasks/management/commands/runfunction.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
|
from ... import keep_state, get_id, get_name
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Runs Orchestra method.'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('method',
|
||||||
|
help='Python path to the method to execute.')
|
||||||
|
parser.add_argument('args', nargs='*',
|
||||||
|
help='Additional arguments passed to the method.')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
method = import_class(options['method'])
|
||||||
|
kwargs = {}
|
||||||
|
arguments = []
|
||||||
|
for arg in args:
|
||||||
|
if '=' in args:
|
||||||
|
name, value = arg.split('=')
|
||||||
|
if value.isdigit():
|
||||||
|
value = int(value)
|
||||||
|
kwargs[name] = value
|
||||||
|
else:
|
||||||
|
if arg.isdigit():
|
||||||
|
arg = int(arg)
|
||||||
|
arguments.append(arg)
|
||||||
|
args = arguments
|
||||||
|
keep_state(method)(get_id(), get_name(method), *args, **kwargs)
|
49
orchestra/contrib/tasks/management/commands/runtask.py
Normal file
49
orchestra/contrib/tasks/management/commands/runtask.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from celery import current_app
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.utils import timezone
|
||||||
|
from djcelery.models import PeriodicTask
|
||||||
|
|
||||||
|
from ... import keep_state, get_id, get_name
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Runs Orchestra method.'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('task',
|
||||||
|
help='Periodic task ID or task name.')
|
||||||
|
parser.add_argument('args', nargs='*',
|
||||||
|
help='Additional arguments passed to the task, when task name is used.')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
|
task = options.get('task')
|
||||||
|
if task.isdigit():
|
||||||
|
# periodic task
|
||||||
|
ptask = PeriodicTask.objects.get(pk=int(task))
|
||||||
|
task = current_app.tasks[ptask.task]
|
||||||
|
args = json.loads(ptask.args)
|
||||||
|
kwargs = json.loads(ptask.kwargs)
|
||||||
|
ptask.last_run_at = timezone.now()
|
||||||
|
ptask.total_run_count += 1
|
||||||
|
ptask.save()
|
||||||
|
else:
|
||||||
|
# task name
|
||||||
|
task = current_app.tasks[task]
|
||||||
|
kwargs = {}
|
||||||
|
arguments = []
|
||||||
|
for arg in args:
|
||||||
|
if '=' in args:
|
||||||
|
name, value = arg.split('=')
|
||||||
|
if value.isdigit():
|
||||||
|
value = int(value)
|
||||||
|
kwargs[name] = value
|
||||||
|
else:
|
||||||
|
if arg.isdigit():
|
||||||
|
arg = int(arg)
|
||||||
|
arguments.append(arg)
|
||||||
|
args = arguments
|
||||||
|
# Run task synchronously, but logging TaskState
|
||||||
|
keep_state(task)(get_id(), get_name(task), *args, **kwargs)
|
|
@ -0,0 +1,2 @@
|
||||||
|
# create crontab entries for defines periodic tasks
|
||||||
|
|
61
orchestra/contrib/tasks/parser.py
Normal file
61
orchestra/contrib/tasks/parser.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
# Rename module to handler.py
|
||||||
|
class CronHandler(object):
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.content = None
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
comments = []
|
||||||
|
self.content = []
|
||||||
|
with open(self.filename, 'r') as handler:
|
||||||
|
for line in handler.readlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('#'):
|
||||||
|
comments.append(line)
|
||||||
|
else:
|
||||||
|
schedule = line.split()[:5]
|
||||||
|
command = ' '.join(line.split()[5:]).strip()
|
||||||
|
self.content.append((schedule, command, comments))
|
||||||
|
comments = []
|
||||||
|
|
||||||
|
def save(self, backup=True):
|
||||||
|
if self.content is None:
|
||||||
|
raise Exception("First read() the cron file!")
|
||||||
|
if backup:
|
||||||
|
os.rename(self.filename, self.filename + '.backup')
|
||||||
|
with open(self.filename, 'w') as handler:
|
||||||
|
handler.write('\n'.join(self.content))
|
||||||
|
handler.truncate()
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
pass
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
def remove(self, command):
|
||||||
|
if self.content is None:
|
||||||
|
raise Exception("First read() the cron file!")
|
||||||
|
new_content = []
|
||||||
|
for c_schedule, c_command, c_comments in self.content:
|
||||||
|
if command != c_command:
|
||||||
|
new_content.append((c_schedule, c_command, c_comments))
|
||||||
|
self.content = new_content
|
||||||
|
|
||||||
|
def add_or_update(self, schedule, command, comments=None):
|
||||||
|
""" if content contains an equal command, its schedule is updated """
|
||||||
|
if self.content is None:
|
||||||
|
raise Exception("First read() the cron file!")
|
||||||
|
new_content = []
|
||||||
|
replaced = False
|
||||||
|
for c_schedule, c_command, c_comments in self.content:
|
||||||
|
if command == c_command:
|
||||||
|
replaced = True
|
||||||
|
new_content.append((schedule, command, comments or c_comments))
|
||||||
|
else:
|
||||||
|
new_content.append((c_schedule, c_command, c_comments))
|
||||||
|
if not replaced:
|
||||||
|
new_content.append((schedule, command, comments or []))
|
||||||
|
self.content = new_content
|
119
orchestra/contrib/tasks/schedules.py
Normal file
119
orchestra/contrib/tasks/schedules.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#import re
|
||||||
|
|
||||||
|
|
||||||
|
#class CronTab(object):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
#class ParseException(Exception):
|
||||||
|
# """Raised by crontab_parser when the input can't be parsed."""
|
||||||
|
|
||||||
|
|
||||||
|
## https://github.com/celery/celery/blob/master/celery/schedules.py
|
||||||
|
#class CrontabParser(object):
|
||||||
|
# """Parser for crontab expressions. Any expression of the form 'groups'
|
||||||
|
# (see BNF grammar below) is accepted and expanded to a set of numbers.
|
||||||
|
# These numbers represent the units of time that the crontab needs to
|
||||||
|
# run on::
|
||||||
|
# digit :: '0'..'9'
|
||||||
|
# dow :: 'a'..'z'
|
||||||
|
# number :: digit+ | dow+
|
||||||
|
# steps :: number
|
||||||
|
# range :: number ( '-' number ) ?
|
||||||
|
# numspec :: '*' | range
|
||||||
|
# expr :: numspec ( '/' steps ) ?
|
||||||
|
# groups :: expr ( ',' expr ) *
|
||||||
|
# The parser is a general purpose one, useful for parsing hours, minutes and
|
||||||
|
# day_of_week expressions. Example usage::
|
||||||
|
# >>> minutes = crontab_parser(60).parse('*/15')
|
||||||
|
# [0, 15, 30, 45]
|
||||||
|
# >>> hours = crontab_parser(24).parse('*/4')
|
||||||
|
# [0, 4, 8, 12, 16, 20]
|
||||||
|
# >>> day_of_week = crontab_parser(7).parse('*')
|
||||||
|
# [0, 1, 2, 3, 4, 5, 6]
|
||||||
|
# It can also parse day_of_month and month_of_year expressions if initialized
|
||||||
|
# with an minimum of 1. Example usage::
|
||||||
|
# >>> days_of_month = crontab_parser(31, 1).parse('*/3')
|
||||||
|
# [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31]
|
||||||
|
# >>> months_of_year = crontab_parser(12, 1).parse('*/2')
|
||||||
|
# [1, 3, 5, 7, 9, 11]
|
||||||
|
# >>> months_of_year = crontab_parser(12, 1).parse('2-12/2')
|
||||||
|
# [2, 4, 6, 8, 10, 12]
|
||||||
|
# The maximum possible expanded value returned is found by the formula::
|
||||||
|
# max_ + min_ - 1
|
||||||
|
# """
|
||||||
|
# ParseException = ParseException
|
||||||
|
|
||||||
|
# _range = r'(\w+?)-(\w+)'
|
||||||
|
# _steps = r'/(\w+)?'
|
||||||
|
# _star = r'\*'
|
||||||
|
|
||||||
|
# def __init__(self, max_=60, min_=0):
|
||||||
|
# self.max_ = max_
|
||||||
|
# self.min_ = min_
|
||||||
|
# self.pats = (
|
||||||
|
# (re.compile(self._range + self._steps), self._range_steps),
|
||||||
|
# (re.compile(self._range), self._expand_range),
|
||||||
|
# (re.compile(self._star + self._steps), self._star_steps),
|
||||||
|
# (re.compile('^' + self._star + '$'), self._expand_star),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# def parse(self, spec):
|
||||||
|
# acc = set()
|
||||||
|
# for part in spec.split(','):
|
||||||
|
# if not part:
|
||||||
|
# raise self.ParseException('empty part')
|
||||||
|
# acc |= set(self._parse_part(part))
|
||||||
|
# return acc
|
||||||
|
|
||||||
|
# def _parse_part(self, part):
|
||||||
|
# for regex, handler in self.pats:
|
||||||
|
# m = regex.match(part)
|
||||||
|
# if m:
|
||||||
|
# return handler(m.groups())
|
||||||
|
# return self._expand_range((part, ))
|
||||||
|
|
||||||
|
# def _expand_range(self, toks):
|
||||||
|
# fr = self._expand_number(toks[0])
|
||||||
|
# if len(toks) > 1:
|
||||||
|
# to = self._expand_number(toks[1])
|
||||||
|
# if to < fr: # Wrap around max_ if necessary
|
||||||
|
# return (list(range(fr, self.min_ + self.max_)) +
|
||||||
|
# list(range(self.min_, to + 1)))
|
||||||
|
# return list(range(fr, to + 1))
|
||||||
|
# return [fr]
|
||||||
|
|
||||||
|
# def _range_steps(self, toks):
|
||||||
|
# if len(toks) != 3 or not toks[2]:
|
||||||
|
# raise self.ParseException('empty filter')
|
||||||
|
# return self._expand_range(toks[:2])[::int(toks[2])]
|
||||||
|
|
||||||
|
# def _star_steps(self, toks):
|
||||||
|
# if not toks or not toks[0]:
|
||||||
|
# raise self.ParseException('empty filter')
|
||||||
|
# return self._expand_star()[::int(toks[0])]
|
||||||
|
|
||||||
|
# def _expand_star(self, *args):
|
||||||
|
# return list(range(self.min_, self.max_ + self.min_))
|
||||||
|
|
||||||
|
# def _expand_number(self, s):
|
||||||
|
# if isinstance(s, str) and s[0] == '-':
|
||||||
|
# raise self.ParseException('negative numbers not supported')
|
||||||
|
# try:
|
||||||
|
# i = int(s)
|
||||||
|
# except ValueError:
|
||||||
|
# try:
|
||||||
|
# i = weekday(s)
|
||||||
|
# except KeyError:
|
||||||
|
# raise ValueError('Invalid weekday literal {0!r}.'.format(s))
|
||||||
|
|
||||||
|
# max_val = self.min_ + self.max_ - 1
|
||||||
|
# if i > max_val:
|
||||||
|
# raise ValueError(
|
||||||
|
# 'Invalid end range: {0} > {1}.'.format(i, max_val))
|
||||||
|
# if i < self.min_:
|
||||||
|
# raise ValueError(
|
||||||
|
# 'Invalid beginning range: {0} < {1}.'.format(i, self.min_))
|
||||||
|
|
||||||
|
# return i
|
||||||
|
|
11
orchestra/contrib/tasks/settings.py
Normal file
11
orchestra/contrib/tasks/settings.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
|
TASKS_BACKEND = Setting('TASKS_BACKEND',
|
||||||
|
'thread',
|
||||||
|
choices=(
|
||||||
|
('thread', "threading.Thread (no queue)"),
|
||||||
|
('process', "multiprocess.Process (no queue)"),
|
||||||
|
('celery', "Celery (with queue)"),
|
||||||
|
)
|
||||||
|
)
|
12
orchestra/contrib/tasks/utils.py
Normal file
12
orchestra/contrib/tasks/utils.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from orchestra.utils.db import close_connection
|
||||||
|
|
||||||
|
|
||||||
|
# TODO import as_task
|
||||||
|
|
||||||
|
def run(method, *args, **kwargs):
|
||||||
|
async = kwargs.pop('async', True)
|
||||||
|
thread = threading.Thread(target=close_connection(method), args=args, kwargs=kwargs)
|
||||||
|
thread = Process(target=close_connection(counter))
|
||||||
|
thread.start()
|
|
@ -29,7 +29,7 @@ class SpanWidget(forms.Widget):
|
||||||
return mark_safe('<img src="%s" alt="%s">' % (icon, str(display)))
|
return mark_safe('<img src="%s" alt="%s">' % (icon, str(display)))
|
||||||
tag = self.tag[:-1]
|
tag = self.tag[:-1]
|
||||||
endtag = '/'.join((self.tag[0], self.tag[1:]))
|
endtag = '/'.join((self.tag[0], self.tag[1:]))
|
||||||
return mark_safe('%s%s >%s%s' % (tag, forms.util.flatatt(final_attrs), display, endtag))
|
return mark_safe('%s%s >%s%s' % (tag, forms.utils.flatatt(final_attrs), display, endtag))
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
return self.original
|
return self.original
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import loading
|
from django.apps import apps
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
def get_model(label, import_module=True):
|
def get_model(label, import_module=True):
|
||||||
app_label, model_name = label.split('.')
|
app_label, model_name = label.split('.')
|
||||||
model = loading.get_model(app_label, model_name)
|
model = apps.get_model(app_label, model_name)
|
||||||
if model is None:
|
if model is None:
|
||||||
# Sometimes the models module is not yet imported
|
# Sometimes the models module is not yet imported
|
||||||
for app in settings.INSTALLED_APPS:
|
for app in settings.INSTALLED_APPS:
|
||||||
if app.endswith(app_label):
|
if app.endswith(app_label):
|
||||||
app_label = app
|
app_label = app
|
||||||
importlib.import_module('%s.%s' % (app_label, 'admin'))
|
importlib.import_module('%s.%s' % (app_label, 'admin'))
|
||||||
return loading.get_model(*label.split('.'))
|
return apps.get_model(*label.split('.'))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.checks import register, Error
|
||||||
from django.core.exceptions import ValidationError, AppRegistryNotReady
|
from django.core.exceptions import ValidationError, AppRegistryNotReady
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
|
@ -42,16 +43,6 @@ class Setting(object):
|
||||||
for name, value in kwargs.items():
|
for name, value in kwargs.items():
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
self.value = self.get_value(self.name, self.default)
|
self.value = self.get_value(self.name, self.default)
|
||||||
try:
|
|
||||||
self.validate_value(self.value)
|
|
||||||
except ValidationError as exc:
|
|
||||||
# Init time warning
|
|
||||||
sys.stderr.write("Error validating setting %s with value %s\n" % (self.name, self.value))
|
|
||||||
sys.stderr.write(format_exception(exc))
|
|
||||||
raise exc
|
|
||||||
except AppRegistryNotReady:
|
|
||||||
# lazy bastards
|
|
||||||
pass
|
|
||||||
self.settings[name] = self
|
self.settings[name] = self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -117,6 +108,19 @@ class Setting(object):
|
||||||
return getattr(cls.conf_settings, name, default)
|
return getattr(cls.conf_settings, name, default)
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def check_settings(app_configs, **kwargs):
|
||||||
|
""" perfroms all the validation """
|
||||||
|
messages = []
|
||||||
|
for name, setting in Setting.settings.items():
|
||||||
|
try:
|
||||||
|
setting.validate_value(setting.value)
|
||||||
|
except ValidationError as exc:
|
||||||
|
msg = "Error validating setting with value %s: %s" % (setting.value, str(exc))
|
||||||
|
messages.append(Error(msg, obj=name, id='settings.E001'))
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_BASE_DOMAIN = Setting('ORCHESTRA_BASE_DOMAIN',
|
ORCHESTRA_BASE_DOMAIN = Setting('ORCHESTRA_BASE_DOMAIN',
|
||||||
'orchestra.lan',
|
'orchestra.lan',
|
||||||
help_text=("Base domain name used for other settings.<br>"
|
help_text=("Base domain name used for other settings.<br>"
|
||||||
|
|
49
orchestra/utils/db.py
Normal file
49
orchestra/utils/db.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import ast
|
||||||
|
|
||||||
|
from django import db
|
||||||
|
|
||||||
|
|
||||||
|
def close_connection(execute):
|
||||||
|
""" Threads have their own connection pool, closing it when finishing """
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
log = execute(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
wrapper.log = log
|
||||||
|
finally:
|
||||||
|
db.connection.close()
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings(settings_file):
|
||||||
|
""" get db settings from settings.py file without importing """
|
||||||
|
settings = {}
|
||||||
|
with open(settings_file, 'r') as handler:
|
||||||
|
body = ast.parse(handler.read()).body
|
||||||
|
for var in body:
|
||||||
|
targets = getattr(var, 'targets', None)
|
||||||
|
if targets and targets[0].id == 'DATABASES':
|
||||||
|
keys = var.value.values[0].keys
|
||||||
|
values = var.value.values[0].values
|
||||||
|
for key, value in zip(keys, values):
|
||||||
|
if key.s == 'ENGINE':
|
||||||
|
if not 'postgresql' in value.s:
|
||||||
|
raise ValueError("%s engine not supported." % value)
|
||||||
|
settings[key.s] = getattr(value, 's', None)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection(settings):
|
||||||
|
import psycopg2
|
||||||
|
conn = psycopg2.connect("dbname='{NAME}' user='{USER}' host='{HOST}' password='{PASSWORD}'".format(**settings))
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def run_query(conn, query):
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(query)
|
||||||
|
result = cur.fetchall()
|
||||||
|
cur.close()
|
||||||
|
return result
|
|
@ -45,7 +45,7 @@ def read_async(fd):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def runiterator(command, display=False, error_codes=[0], silent=False, stdin=b''):
|
def runiterator(command, display=False, stdin=b''):
|
||||||
""" Subprocess wrapper for running commands concurrently """
|
""" Subprocess wrapper for running commands concurrently """
|
||||||
if display:
|
if display:
|
||||||
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
||||||
|
@ -83,6 +83,7 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin=b''
|
||||||
state = _Attribute(stdout)
|
state = _Attribute(stdout)
|
||||||
state.stderr = stderr
|
state.stderr = stderr
|
||||||
state.return_code = p.poll()
|
state.return_code = p.poll()
|
||||||
|
state.command = command
|
||||||
yield state
|
yield state
|
||||||
|
|
||||||
if state.return_code != None:
|
if state.return_code != None:
|
||||||
|
@ -90,13 +91,8 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin=b''
|
||||||
p.stderr.close()
|
p.stderr.close()
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
|
def join(iterator, display=False, silent=False, error_codes=[0]):
|
||||||
def run(command, display=False, error_codes=[0], silent=False, stdin=b'', async=False):
|
""" joins the iterator process """
|
||||||
iterator = runiterator(command, display, error_codes, silent, stdin)
|
|
||||||
next(iterator)
|
|
||||||
if async:
|
|
||||||
return iterator
|
|
||||||
|
|
||||||
stdout = b''
|
stdout = b''
|
||||||
stderr = b''
|
stderr = b''
|
||||||
for state in iterator:
|
for state in iterator:
|
||||||
|
@ -114,7 +110,7 @@ def run(command, display=False, error_codes=[0], silent=False, stdin=b'', async=
|
||||||
if return_code not in error_codes:
|
if return_code not in error_codes:
|
||||||
out.failed = True
|
out.failed = True
|
||||||
msg = "\nrun() encountered an error (return code %s) while executing '%s'\n"
|
msg = "\nrun() encountered an error (return code %s) while executing '%s'\n"
|
||||||
msg = msg % (return_code, command)
|
msg = msg % (return_code, state.command)
|
||||||
if display:
|
if display:
|
||||||
sys.stderr.write("\n\033[1;31mCommandError: %s %s\033[m\n" % (msg, err))
|
sys.stderr.write("\n\033[1;31mCommandError: %s %s\033[m\n" % (msg, err))
|
||||||
if not silent:
|
if not silent:
|
||||||
|
@ -124,6 +120,14 @@ def run(command, display=False, error_codes=[0], silent=False, stdin=b'', async=
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def run(command, display=False, error_codes=[0], silent=False, stdin=b'', async=False):
|
||||||
|
iterator = runiterator(command, display, stdin)
|
||||||
|
next(iterator)
|
||||||
|
if async:
|
||||||
|
return iterator
|
||||||
|
return join(iterator, display=display, silent=silent, error_codes=error_codes)
|
||||||
|
|
||||||
|
|
||||||
def sshrun(addr, command, *args, **kwargs):
|
def sshrun(addr, command, *args, **kwargs):
|
||||||
command = command.replace("'", """'"'"'""")
|
command = command.replace("'", """'"'"'""")
|
||||||
cmd = "ssh -o stricthostkeychecking=no -C root@%s '%s'" % (addr, command)
|
cmd = "ssh -o stricthostkeychecking=no -C root@%s '%s'" % (addr, command)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
cracklib
|
cracklib
|
||||||
psycopg2
|
psycopg2
|
||||||
django==1.8
|
django==1.8.1
|
||||||
django-celery-email==1.0.4
|
django-celery-email==1.0.4
|
||||||
https://github.com/glic3rinu/django-fluent-dashboard/archive/master.zip
|
https://github.com/glic3rinu/django-fluent-dashboard/archive/master.zip
|
||||||
https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip
|
https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -27,7 +27,10 @@ setup(
|
||||||
"The goal of this project is to provide the tools for easily build a fully "
|
"The goal of this project is to provide the tools for easily build a fully "
|
||||||
"featured control panel that fits any service architecture."),
|
"featured control panel that fits any service architecture."),
|
||||||
include_package_data = True,
|
include_package_data = True,
|
||||||
scripts=['orchestra/bin/orchestra-admin'],
|
scripts=[
|
||||||
|
'orchestra/bin/orchestra-admin',
|
||||||
|
'orchestra/contrib/tasks/bin/orchestra-beat',
|
||||||
|
],
|
||||||
packages = packages,
|
packages = packages,
|
||||||
classifiers = [
|
classifiers = [
|
||||||
'Development Status :: 1 - Alpha',
|
'Development Status :: 1 - Alpha',
|
||||||
|
|
Loading…
Reference in a new issue