Fixes on task state

This commit is contained in:
Marc Aymerich 2015-05-09 15:37:35 +00:00
parent 6fa29a15b8
commit 13cf3a41ed
9 changed files with 72 additions and 23 deletions

View file

@ -355,5 +355,3 @@ make django admin taskstate uncollapse fucking traceback, ( if exists ?)
# backend.context and backned.instance provided when an action is called? like forms.cleaned_data: do it on manager.generation(backend.context = backend.get_context()) or in backend.__getattr__ ? also backend.head,tail,content switching on manager.generate()?
# replace return_code by exit_code everywhere
# plan.rate registry

View file

@ -105,7 +105,7 @@ def execute(scripts, serialize=False, async=None):
async: do not join threads (overrides route.async)
"""
if settings.ORCHESTRATION_DISABLE_EXECUTION:
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.')
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION.')
return []
# Execute scripts on each server
executions = []
@ -122,6 +122,7 @@ def execute(scripts, serialize=False, async=None):
kwargs = {
'async': async,
}
# we clone the connection just in case we are isolated inside a transaction
with db.clone(model=BackendLog) as handle:
log = backend.create_log(*args, using=handle.target)
log._state.db = handle.origin

View file

@ -30,6 +30,7 @@ class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin):
list_select_related = ('plan', 'account')
search_fields = ('account__username', 'plan__name', 'id')
admin.site.register(Plan, PlanAdmin)
admin.site.register(ContractedPlan, ContractedPlanAdmin)

View file

@ -6,8 +6,10 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_name
from orchestra.models import queryset
from orchestra.utils.functional import cached
from orchestra.utils.python import import_class
from . import rating
from . import settings
class Plan(models.Model):
@ -66,15 +68,6 @@ class RateQuerySet(models.QuerySet):
class Rate(models.Model):
STEP_PRICE = 'STEP_PRICE'
MATCH_PRICE = 'MATCH_PRICE'
BEST_PRICE = 'BEST_PRICE'
RATE_METHODS = {
STEP_PRICE: rating.step_price,
MATCH_PRICE: rating.match_price,
BEST_PRICE: rating.best_price,
}
service = models.ForeignKey('services.Service', verbose_name=_("service"),
related_name='rates')
plan = models.ForeignKey(Plan, verbose_name=_("plan"), related_name='rates')
@ -91,12 +84,18 @@ class Rate(models.Model):
return "{}-{}".format(str(self.price), self.quantity)
@classmethod
@cached
def get_methods(cls):
return cls.RATE_METHODS
return dict((method, import_class(method)) for method in settings.PLANS_RATE_METHODS)
@classmethod
@cached
def get_choices(cls):
choices = []
for name, method in cls.RATE_METHODS.items():
for name, method in cls.get_methods().items():
choices.append((name, method.verbose_name))
return choices
@classmethod
def get_default(cls):
return settings.PLANS_DEFAULT_RATE_METHOD

View file

@ -0,0 +1,15 @@
from orchestra.contrib.settings import Setting
PLANS_RATE_METHODS = Setting('PLANS_RATE_METHODS',
(
'orchestra.contrib.plans.rating.step_price',
'orchestra.contrib.plans.rating.match_price',
'orchestra.contrib.plans.rating.best_price',
)
)
PLANS_DEFAULT_RATE_METHOD = Setting('PLANS_DEFAULT_RATE_METHOD',
'orchestra.contrib.plans.rating.step_price',
)

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='service',
name='rate_algorithm',
field=models.CharField(choices=[('orchestra.contrib.plans.rating.best_price', 'Best price'), ('orchestra.contrib.plans.rating.step_price', 'Step price'), ('orchestra.contrib.plans.rating.match_price', 'Match price')], help_text='Algorithm used to interprete the rating table.<br>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines.<br>&nbsp;&nbsp;Step price: All rates with a quantity lower than the metric are applied. Nominal price will be used when initial block is missing.<br>&nbsp;&nbsp;Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm', default='orchestra.contrib.plans.rating.step_price'),
),
migrations.AlterField(
model_name='service',
name='tax',
field=models.PositiveIntegerField(choices=[(0, 'Duty free'), (21, '21%')], verbose_name='tax', default=21),
),
]

View file

@ -127,11 +127,13 @@ class Service(models.Model):
(ANUAL, _("Anual data")),
),
default=BILLING_PERIOD)
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
rate_algorithm = models.CharField(_("rate algorithm"), max_length=64,
choices=rate_class.get_choices(),
default=rate_class.get_default(),
help_text=string_concat(_("Algorithm used to interprete the rating table."), *[
string_concat('<br>&nbsp;&nbsp;', method.verbose_name, ': ', method.help_text)
for name, method in rate_class.get_methods().items()
]), choices=rate_class.get_choices(), default=rate_class.get_choices()[0][0])
]))
on_cancel = models.CharField(_("on cancel"), max_length=16,
help_text=_("Defines the cancellation behaviour of this service."),
choices=(

View file

@ -1,3 +1,4 @@
import logging
import traceback
from functools import partial, wraps, update_wrapper
from multiprocessing import Process
@ -15,6 +16,9 @@ from orchestra.utils.python import AttrDict, OrderedSet
from .utils import get_name, get_id
logger = logging.getLogger(__name__)
def keep_state(fn):
""" logs task on djcelery's TaskState model """
@wraps(fn)
@ -30,14 +34,14 @@ def keep_state(fn):
try:
result = fn(*args, **kwargs)
except:
trace = traceback.format_exc()
subject = 'EXCEPTION executing task %s(args=%s, kwargs=%s)' % (_name, str(args), str(kwargs))
logger.error(subject)
logger.error(trace)
state.state = states.FAILURE
state.traceback = trace
state.runtime = (timezone.now()-now).total_seconds()
state.save()
subject = 'EXCEPTION executing task %s(args=%s, kwargs=%s)' % (name, str(args), str(kwargs))
trace = traceback.format_exc()
logger.error(subject)
logger.error(trace)
mail_admins(subject, trace)
raise
else:

View file

@ -6,6 +6,7 @@ import re
import select
import subprocess
import sys
import time
from django.core.management.base import CommandError
@ -165,6 +166,10 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs):
dir_fd=None if os.supports_fd else dir_fd, **kwargs)
class OperationLocked(Exception):
pass
class LockFile(object):
""" File-based lock mechanism used for preventing concurrency problems """
def __init__(self, lockfile, expire=5*60, unlocked=False):
@ -188,8 +193,8 @@ class LockFile(object):
def __enter__(self):
if not self.unlocked:
if not self.acquire():
raise OperationLocked('%s lock file exists and its mtime is less '
'than %s seconds' % (self.lockfile, self.expire))
raise OperationLocked("%s lock file exists and its mtime is less than %s seconds" %
(self.lockfile, self.expire))
return True
def __exit__(self, type, value, traceback):