Added orchestrate management command
This commit is contained in:
parent
9e59346042
commit
b29c554878
84
ROADMAP.md
84
ROADMAP.md
|
@ -2,11 +2,54 @@
|
|||
|
||||
Note `*` _for sustancial progress_
|
||||
|
||||
### 1.0a1 Milestone (first alpha release on ~~Oct~~ Nov '14)
|
||||
|
||||
### 2.0 Milestone (unscheduled)
|
||||
|
||||
1. [ ] Integration with third-party service providers, e.g. Gandi
|
||||
2. [ ] Scheduling of service cancellations and deactivations
|
||||
1. [ ] Object-level permission system
|
||||
2. [ ] REST API functionality for superusers
|
||||
3. [ ] Responsive user interface, based on a JS framework.
|
||||
4. [ ] Full development documentation
|
||||
5. [ ] [Ansible](http://www.ansible.com/home) orchestration method, which synchronizes the whole service config everytime instead of incremental changes.
|
||||
|
||||
|
||||
### 1.0 Milestone (first stable release on Sep '15)
|
||||
|
||||
1. [ ] Stabilize data model, internal APIs and REST API
|
||||
3. [ ] Spanish and Catalan translations
|
||||
1. [ ] Complete documentation for developers
|
||||
|
||||
|
||||
### 1.0b1 Milestone (first beta release on ~~Dec '14~~ Jun '15)
|
||||
|
||||
1. [x] Resource allocation and monitoring
|
||||
1. [x] Order tracking
|
||||
2. [x] Service definition framework, service plans and pricing
|
||||
3. [ ] *Billing
|
||||
3. [x] Invoice
|
||||
3. [x] Membership fee
|
||||
3. [ ] *Amendment invoice
|
||||
3. [ ] *Amendment fee
|
||||
3. [x] Pro Forma
|
||||
3. [ ] *Advanced bill handling (move lines, undo billing, ...)
|
||||
1. [x] Payment methods
|
||||
1. [x] SEPA Direct Debit
|
||||
2. [x] SEPA Credit Transfer
|
||||
2. [ ] *Additional services
|
||||
2. [ ] *VPS with Proxmox/OpenVZ
|
||||
2. [ ] *SaaS (Software as a Service) Redmine/phpList/BSCW/Wordpress/Moodle/Drupal
|
||||
2. [ ] *Wordpress/Python webapps
|
||||
2. [x] Miscellaneous services
|
||||
2. [x] Issue tracking system
|
||||
|
||||
|
||||
|
||||
### 1.0a1 Milestone (first alpha release on ~~Oct '14~~ Apr '15)
|
||||
|
||||
1. [x] Automated deployment of the development environment
|
||||
2. [x] Automated installation and upgrading
|
||||
2. [ ] Testing framework for running unittests and functional tests with LXC containers
|
||||
2. ~~[ ] Testing framework for running unittests and functional tests with LXC containers~~
|
||||
2. [ ] Continuous integration with Jenkins
|
||||
2. [x] Admin interface based on django.contrib.admin
|
||||
3. [x] REST API for users
|
||||
|
@ -26,41 +69,4 @@ Note `*` _for sustancial progress_
|
|||
1. [ ] Initial documentation
|
||||
|
||||
|
||||
### 1.0b1 Milestone (first beta release on Dec '14)
|
||||
|
||||
1. [x] Resource allocation and monitoring
|
||||
1. [x] Order tracking
|
||||
2. [x] Service definition framework, service plans and pricing
|
||||
3. [ ] *Billing
|
||||
3. [x] Invoice
|
||||
3. [x] Membership fee
|
||||
3. [ ] *Amendment invoice
|
||||
3. [ ] *Amendment fee
|
||||
3. [x] Pro Forma
|
||||
3. [ ] *Advanced bill handling (move lines, undo billing, ...)
|
||||
1. [x] Payment methods
|
||||
1. [x] SEPA Direct Debit
|
||||
2. [x] SEPA Credit Transfer
|
||||
2. [ ] *Additional services
|
||||
2. [ ] *VPS with Proxmox/OpenVZ
|
||||
2. [ ] *SaaS (Software as a Service) Redmine/phpList/BSCW/Wordpress/Moodle/Drupal
|
||||
2. [x] Miscellaneous services
|
||||
2. [x] Issue tracking system
|
||||
|
||||
|
||||
### 1.0 Milestone (first stable release on Apr '15)
|
||||
|
||||
1. [ ] Stabilize data model, internal APIs and REST API
|
||||
3. [ ] Spanish and Catalan translations
|
||||
1. [ ] Complete documentation for developers
|
||||
|
||||
|
||||
### 2.0 Milestone
|
||||
|
||||
1. [ ] Integration with third-party service providers, e.g. Gandi
|
||||
2. [ ] Scheduling of service cancellations and deactivations
|
||||
1. [ ] Object-level permission system
|
||||
2. [ ] REST API functionality for superusers
|
||||
3. [ ] Responsive user interface, based on a JS framework.
|
||||
4. [ ] Full documentation
|
||||
5. [ ] [Ansible](http://www.ansible.com/home) orchestration method, which synchronizes the whole service config everytime instead of incremental changes.
|
||||
|
|
23
TODO.md
23
TODO.md
|
@ -243,6 +243,10 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
|||
* line 513: change threshold and one time service metric change should update last value if not billed, only record for recurring invoicing. postpay services should store the last metric for pricing period.
|
||||
* add ini, end dates on bill lines and breakup quanity into size(defaut:1) and metric
|
||||
* threshold for significative metric accountancy on services.handler
|
||||
* http://orchestra.pangea.org/admin/orders/order/6418/
|
||||
* http://orchestra.pangea.org/admin/orders/order/6495/bill_selected_orders/
|
||||
* >>> round(float(decimal.Decimal('2.63'))/0.5)*0.5
|
||||
* >>> round(float(str(decimal.Decimal('2.99')).split('.')[0]))/1*1
|
||||
|
||||
* move normurlpath to orchestra.utils from websites.utils
|
||||
|
||||
|
@ -261,6 +265,12 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
|||
* Base price: domini propi (all domains) + extra for other domains
|
||||
|
||||
|
||||
* prepend ORCHESTRA_ to orchestra/settings.py
|
||||
|
||||
|
||||
* rename backends with generic names to concrete services.. eg VsFTPdTraffic, UNIXSystemUser
|
||||
|
||||
|
||||
Translation
|
||||
-----------
|
||||
|
||||
|
@ -290,3 +300,16 @@ xxxxx -- 0 20M 22M 7 200 300
|
|||
* saas validate_creation generic approach, for all backends. standard output
|
||||
|
||||
* html code x: ×
|
||||
|
||||
|
||||
* cleanup backendlogs, monitor data and metricstorage
|
||||
* create orchestrate databases.Database pk=1 -n --dry-run | --noinput --action save (default)|delete --backend name (limit to this backend) --help
|
||||
|
||||
* uwsgi --max-requests=5000 \ # respawn processes after serving 5000 requests and
|
||||
celery max-tasks-per-child
|
||||
|
||||
* generate settings.py more like django (installed_apps, middlewares, etc,,,)
|
||||
|
||||
* postupgradeorchestra send signals in order to hook custom stuff
|
||||
|
||||
* make base home for systemusers that ara homed into main account systemuser
|
||||
|
|
|
@ -74,7 +74,7 @@ class Contact(models.Model):
|
|||
elif self.zipcode and self.country:
|
||||
try:
|
||||
validators.validate_zipcode(self.zipcode, self.country)
|
||||
except ValidationError, error:
|
||||
except ValidationError as error:
|
||||
errors['zipcode'] = error
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -247,7 +247,7 @@ class Record(models.Model):
|
|||
}
|
||||
try:
|
||||
choices[self.type](self.value)
|
||||
except ValidationError, error:
|
||||
except ValidationError as error:
|
||||
raise ValidationError({'value': error})
|
||||
|
||||
def get_ttl(self):
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models.loading import get_model
|
||||
from django.utils.six.moves import input
|
||||
|
||||
from orchestra.apps.orchestration import manager
|
||||
from orchestra.apps.orchestration.models import BackendOperation as Operation
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Runs orchestration backends.'
|
||||
option_list = BaseCommand.option_list
|
||||
args = "[app_label] [filter]"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
model_label = args[0]
|
||||
model = get_model(*model_label.split('.'))
|
||||
# TODO options
|
||||
action = options.get('action', 'save')
|
||||
interactive = options.get('interactive', True)
|
||||
kwargs = {}
|
||||
for comp in args[1:]:
|
||||
comps = iter(comp.split('='))
|
||||
for arg in comps:
|
||||
kwargs[arg] = next(comps).strip().rstrip(',')
|
||||
operations = []
|
||||
operations = set()
|
||||
route_cache = {}
|
||||
for instance in model.objects.filter(**kwargs):
|
||||
manager.collect(instance, action, operations=operations, route_cache=route_cache)
|
||||
scripts, block = manager.generate(operations)
|
||||
servers = []
|
||||
# Print scripts
|
||||
for key, value in scripts.iteritems():
|
||||
server, __ = key
|
||||
backend, operations = value
|
||||
servers.append(server.name)
|
||||
sys.stdout.write('# Execute on %s\n' % server.name)
|
||||
for method, commands in backend.scripts:
|
||||
sys.stdout.write('\n'.join(commands) + '\n')
|
||||
if interactive:
|
||||
context = {
|
||||
'servers': ', '.join(servers),
|
||||
}
|
||||
msg = ("\n\nAre your sure to execute the previous scripts on %(servers)s (yes/no)? " % context)
|
||||
confirm = input(msg)
|
||||
while 1:
|
||||
if confirm not in ('yes', 'no'):
|
||||
confirm = input('Please enter either "yes" or "no": ')
|
||||
continue
|
||||
if confirm == 'no':
|
||||
return
|
||||
break
|
||||
# manager.execute(scripts, block=block)
|
||||
|
|
@ -9,8 +9,9 @@ from django.core.mail import mail_admins
|
|||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import settings
|
||||
from .backends import ServiceBackend
|
||||
from .helpers import send_report
|
||||
from .models import BackendLog
|
||||
from .models import BackendLog, BackendOperation as Operation
|
||||
from .signals import pre_action, post_action
|
||||
|
||||
|
||||
|
@ -55,8 +56,7 @@ def close_connection(execute):
|
|||
return wrapper
|
||||
|
||||
|
||||
def execute(operations, async=False):
|
||||
""" generates and executes the operations on the servers """
|
||||
def generate(operations):
|
||||
scripts = OrderedDict()
|
||||
cache = {}
|
||||
block = False
|
||||
|
@ -86,13 +86,20 @@ def execute(operations, async=False):
|
|||
post_action.send(**kwargs)
|
||||
if backend.block:
|
||||
block = True
|
||||
for value in scripts.itervalues():
|
||||
backend, operations = value
|
||||
backend.commit()
|
||||
return scripts, block
|
||||
|
||||
|
||||
def execute(scripts, block=False, async=False):
|
||||
""" executes the operations on the servers """
|
||||
# Execute scripts on each server
|
||||
threads = []
|
||||
executions = []
|
||||
for key, value in scripts.iteritems():
|
||||
server, __ = key
|
||||
backend, operations = value
|
||||
backend.commit()
|
||||
execute = as_task(backend.execute)
|
||||
logger.debug('%s is going to be executed on %s' % (backend, server))
|
||||
if block:
|
||||
|
@ -125,3 +132,66 @@ def execute(operations, async=False):
|
|||
mocked_log = BackendLog(state=BackendLog.EXCEPTION)
|
||||
logs.append(mocked_log)
|
||||
return logs
|
||||
|
||||
|
||||
def collect(instance, action, **kwargs):
|
||||
""" collect operations """
|
||||
operations = kwargs.get('operations', set())
|
||||
route_cache = kwargs.get('route_cache', {})
|
||||
for backend_cls in ServiceBackend.get_backends():
|
||||
# Check if there exists a related instance to be executed for this backend
|
||||
instances = []
|
||||
if backend_cls.is_main(instance):
|
||||
instances = [(instance, action)]
|
||||
else:
|
||||
candidate = backend_cls.get_related(instance)
|
||||
if candidate:
|
||||
if candidate.__class__.__name__ == 'ManyRelatedManager':
|
||||
if 'pk_set' in kwargs:
|
||||
# m2m_changed signal
|
||||
candidates = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
else:
|
||||
candidates = candidate.all()
|
||||
else:
|
||||
candidates = [candidate]
|
||||
for candidate in candidates:
|
||||
# Check if a delete for candidate is in operations
|
||||
delete_mock = Operation.create(backend_cls, candidate, Operation.DELETE)
|
||||
if delete_mock not in operations:
|
||||
# related objects with backend.model trigger save()
|
||||
instances.append((candidate, Operation.SAVE))
|
||||
for selected, iaction in instances:
|
||||
# Maintain consistent state of operations based on save/delete behaviour
|
||||
# Prevent creating a deleted selected by deleting existing saves
|
||||
if iaction == Operation.DELETE:
|
||||
save_mock = Operation.create(backend_cls, selected, Operation.SAVE)
|
||||
try:
|
||||
operations.remove(save_mock)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
update_fields = kwargs.get('update_fields', None)
|
||||
if update_fields is not None:
|
||||
# "update_fileds=[]" is a convention for explicitly executing backend
|
||||
# i.e. account.disable()
|
||||
if update_fields != []:
|
||||
execute = False
|
||||
for field in update_fields:
|
||||
if field not in backend_cls.ignore_fields:
|
||||
execute = True
|
||||
break
|
||||
if not execute:
|
||||
continue
|
||||
operation = Operation.create(backend_cls, selected, iaction)
|
||||
# Only schedule operations if the router gives servers to execute into
|
||||
servers = router.get_servers(operation, cache=route_cache)
|
||||
if servers:
|
||||
operation.servers = servers
|
||||
if iaction != Operation.DELETE:
|
||||
# usually we expect to be using last object state,
|
||||
# except when we are deleting it
|
||||
operations.discard(operation)
|
||||
elif iaction == Operation.DELETE:
|
||||
operation.preload_context()
|
||||
operations.add(operation)
|
||||
return operations
|
||||
|
|
|
@ -51,7 +51,7 @@ def SSH(backend, log, server, cmds, async=False):
|
|||
key = settings.ORCHESTRATION_SSH_KEY_PATH
|
||||
try:
|
||||
ssh.connect(addr, username='root', key_filename=key, timeout=10)
|
||||
except socket.error, e:
|
||||
except socket.error as e:
|
||||
logger.error('%s timed out on %s' % (backend, addr))
|
||||
log.state = log.TIMEOUT
|
||||
log.stderr = str(e)
|
||||
|
|
|
@ -7,11 +7,9 @@ from django.http.response import HttpResponseServerError
|
|||
|
||||
from orchestra.utils.python import OrderedSet
|
||||
|
||||
from .backends import ServiceBackend
|
||||
from . import manager
|
||||
from .helpers import message_user
|
||||
from .manager import router
|
||||
from .models import BackendLog
|
||||
from .models import BackendOperation as Operation
|
||||
from .models import BackendLog, BackendOperation as Operation
|
||||
|
||||
|
||||
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
||||
|
@ -68,64 +66,10 @@ class OperationsMiddleware(object):
|
|||
request = getattr(cls.thread_locals, 'request', None)
|
||||
if request is None:
|
||||
return
|
||||
pending_operations = cls.get_pending_operations()
|
||||
route_cache = cls.get_route_cache()
|
||||
for backend_cls in ServiceBackend.get_backends():
|
||||
# Check if there exists a related instance to be executed for this backend
|
||||
instances = []
|
||||
if backend_cls.is_main(kwargs['instance']):
|
||||
instances = [(kwargs['instance'], action)]
|
||||
else:
|
||||
candidate = backend_cls.get_related(kwargs['instance'])
|
||||
if candidate:
|
||||
if candidate.__class__.__name__ == 'ManyRelatedManager':
|
||||
if 'pk_set' in kwargs:
|
||||
# m2m_changed signal
|
||||
candidates = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
else:
|
||||
candidates = candidate.all()
|
||||
else:
|
||||
candidates = [candidate]
|
||||
for candidate in candidates:
|
||||
# Check if a delete for candidate is in pending_operations
|
||||
delete_mock = Operation.create(backend_cls, candidate, Operation.DELETE)
|
||||
if delete_mock not in pending_operations:
|
||||
# related objects with backend.model trigger save()
|
||||
instances.append((candidate, Operation.SAVE))
|
||||
for instance, iaction in instances:
|
||||
# Maintain consistent state of pending_operations based on save/delete behaviour
|
||||
# Prevent creating a deleted instance by deleting existing saves
|
||||
if iaction == Operation.DELETE:
|
||||
save_mock = Operation.create(backend_cls, instance, Operation.SAVE)
|
||||
try:
|
||||
pending_operations.remove(save_mock)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
update_fields = kwargs.get('update_fields', None)
|
||||
if update_fields is not None:
|
||||
# "update_fileds=[]" is a convention for explicitly executing backend
|
||||
# i.e. account.disable()
|
||||
if update_fields != []:
|
||||
execute = False
|
||||
for field in update_fields:
|
||||
if field not in backend_cls.ignore_fields:
|
||||
execute = True
|
||||
break
|
||||
if not execute:
|
||||
continue
|
||||
operation = Operation.create(backend_cls, instance, iaction)
|
||||
# Only schedule operations if the router gives servers to execute into
|
||||
servers = router.get_servers(operation, cache=route_cache)
|
||||
if servers:
|
||||
operation.servers = servers
|
||||
if iaction != Operation.DELETE:
|
||||
# usually we expect to be using last object state,
|
||||
# except when we are deleting it
|
||||
pending_operations.discard(operation)
|
||||
elif iaction == Operation.DELETE:
|
||||
operation.preload_context()
|
||||
pending_operations.add(operation)
|
||||
kwargs['operations'] = cls.get_pending_operations()
|
||||
kwargs['route_cache'] = cls.get_route_cache()
|
||||
instance = kwargs.pop('instance')
|
||||
manager.collect(instance, action, **kwargs)
|
||||
|
||||
def process_request(self, request):
|
||||
""" Store request on a thread local variable """
|
||||
|
|
|
@ -141,7 +141,8 @@ class BackendOperation(models.Model):
|
|||
@classmethod
|
||||
def execute(cls, operations, async=False):
|
||||
from . import manager
|
||||
return manager.execute(operations, async=async)
|
||||
scripts, block = manager.generate(operations)
|
||||
return manager.execute(scripts, block=block, async=async)
|
||||
|
||||
@classmethod
|
||||
def execute_action(cls, instance, action):
|
||||
|
@ -224,7 +225,7 @@ class Route(models.Model):
|
|||
return
|
||||
try:
|
||||
bool(self.matches(obj))
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
name = type(exception).__name__
|
||||
message = exception.message
|
||||
raise ValidationError(': '.join((name, message)))
|
||||
|
|
|
@ -13,6 +13,7 @@ from .forms import BillSelectedOptionsForm, BillSelectConfirmationForm, BillSele
|
|||
class BillSelectedOrders(object):
|
||||
""" Form wizard for billing orders admin action """
|
||||
short_description = _("Bill selected orders")
|
||||
verbose_name = _("Bill")
|
||||
template = 'admin/orders/order/bill_selected_options.html'
|
||||
__name__ = 'bill_selected_orders'
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
inlines = (MetricStorageInline,)
|
||||
add_inlines = ()
|
||||
search_fields = ('account__username', 'description')
|
||||
list_prefetch_related = ('metrics', 'content_object')
|
||||
list_select_related = ('account', 'service')
|
||||
|
||||
service_link = admin_link('service')
|
||||
content_object_link = admin_link('content_object', order=False)
|
||||
|
@ -78,13 +80,13 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_billed_until.admin_order_field = 'billed_until'
|
||||
|
||||
def display_metric(self, order):
|
||||
metric = order.metrics.latest()
|
||||
return metric.value if metric else ''
|
||||
""" dispalys latest metric value, don't uses latest() because not loosing prefetch_related """
|
||||
try:
|
||||
metric = order.metrics.all()[0]
|
||||
except IndexError:
|
||||
return ''
|
||||
return metric.value
|
||||
display_metric.short_description = _("Metric")
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(OrderAdmin, self).get_queryset(request)
|
||||
return qs.select_related('service').prefetch_related('content_object')
|
||||
|
||||
|
||||
class MetricStorageAdmin(admin.ModelAdmin):
|
||||
|
|
|
@ -258,8 +258,8 @@ class MetricStorage(models.Model):
|
|||
except cls.DoesNotExist:
|
||||
cls.objects.create(order=order, value=value, updated_on=now)
|
||||
else:
|
||||
error = decimal.Decimal(settings.ORDERS_METRIC_ERROR)
|
||||
if last.value*(1+error) > value or last.value*error < value:
|
||||
error = decimal.Decimal(str(settings.ORDERS_METRIC_ERROR))
|
||||
if value > last.value+error or value < last.value-error:
|
||||
cls.objects.create(order=order, value=value, updated_on=now)
|
||||
else:
|
||||
last.updated_on = now
|
||||
|
|
|
@ -44,6 +44,7 @@ class ResourceAdmin(ExtendedModelAdmin):
|
|||
change_view_actions = actions
|
||||
change_readonly_fields = ('name', 'content_type')
|
||||
prepopulated_fields = {'name': ('verbose_name',)}
|
||||
list_select_related = ('content_type', 'crontab',)
|
||||
|
||||
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||
""" Remaind user when monitor routes are not configured """
|
||||
|
@ -243,6 +244,7 @@ def resource_inline_factory(resources):
|
|||
return '%s %s %s' % (data.used, data.resource.unit, update_link)
|
||||
return _("Unknonw %s") % update_link
|
||||
display_used.short_description = _("Used")
|
||||
display_used.allow_tags = True
|
||||
|
||||
def has_add_permission(self, *args, **kwargs):
|
||||
""" Hidde add another """
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import decimal
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -21,7 +22,7 @@ class DataMethod(plugins.Plugin):
|
|||
|
||||
class Last(DataMethod):
|
||||
name = 'last'
|
||||
verbose_name = _("Last")
|
||||
verbose_name = _("Last value")
|
||||
|
||||
def filter(self, dataset):
|
||||
try:
|
||||
|
@ -71,7 +72,7 @@ class MonthlyAvg(MonthlySum):
|
|||
result = 0
|
||||
for data in dataset:
|
||||
slot = (data.created_at-ini).total_seconds()
|
||||
result += data.value * slot/total
|
||||
result += data.value * decimal.Decimal(str(slot/total))
|
||||
ini = data.created_at
|
||||
return result
|
||||
|
||||
|
|
|
@ -199,25 +199,24 @@ class ResourceData(models.Model):
|
|||
content_type=ct,
|
||||
object_id=obj.pk,
|
||||
resource=resource
|
||||
)
|
||||
), False
|
||||
except cls.DoesNotExist:
|
||||
return cls.objects.create(
|
||||
content_object=obj,
|
||||
resource=resource,
|
||||
allocated=resource.default_allocation
|
||||
)
|
||||
), True
|
||||
|
||||
@property
|
||||
def unit(self):
|
||||
return self.resource.unit
|
||||
|
||||
def get_used(self):
|
||||
resource = data.resource
|
||||
resource = self.resource
|
||||
total = 0
|
||||
has_result = False
|
||||
today = datetime.date.today()
|
||||
for dataset in data.get_monitor_datasets():
|
||||
usage = data.method_instance.compute_usage(dataset)
|
||||
for dataset in self.get_monitor_datasets():
|
||||
usage = resource.method_instance.compute_usage(dataset)
|
||||
if usage is not None:
|
||||
has_result = True
|
||||
total += usage
|
||||
|
|
|
@ -38,7 +38,7 @@ def monitor(resource_id, ids=None, async=True):
|
|||
triggers = []
|
||||
model = resource.content_type.model_class()
|
||||
for obj in model.objects.filter(**kwargs):
|
||||
data = ResourceData.get_or_create(obj, resource)
|
||||
data, __ = ResourceData.get_or_create(obj, resource)
|
||||
data.update()
|
||||
if not resource.disable_trigger:
|
||||
a = data.used
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
def validate_scale(value):
|
||||
try:
|
||||
int(eval(value))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
raise ValidationError(
|
||||
_("'%s' is not a valid scale expression. (%s)") % (value, str(e))
|
||||
)
|
||||
|
|
|
@ -54,7 +54,7 @@ class GitLabSaaSBackend(ServiceController):
|
|||
saas.data['user_id'] = user['id']
|
||||
# Using queryset update to avoid triggering backends with the post_save signal
|
||||
type(saas).objects.filter(pk=saas.pk).update(data=saas.data)
|
||||
print json.dumps(user, indent=4)
|
||||
print(json.dumps(user, indent=4))
|
||||
|
||||
def change_password(self, saas, server):
|
||||
self.authenticate()
|
||||
|
@ -65,7 +65,7 @@ class GitLabSaaSBackend(ServiceController):
|
|||
user['password'] = saas.password
|
||||
response = requests.put(user_url, data=user, headers=self.headers)
|
||||
user = self.validate_response(response, 200)
|
||||
print json.dumps(user, indent=4)
|
||||
print(json.dumps(user, indent=4))
|
||||
|
||||
def set_state(self, saas, server):
|
||||
# TODO http://feedback.gitlab.com/forums/176466-general/suggestions/4098632-add-administrative-api-call-to-block-users
|
||||
|
@ -77,7 +77,7 @@ class GitLabSaaSBackend(ServiceController):
|
|||
user['state'] = 'active' if saas.active else 'blocked',
|
||||
response = requests.patch(user_url, data=user, headers=self.headers)
|
||||
user = self.validate_response(response, 200)
|
||||
print json.dumps(user, indent=4)
|
||||
print(json.dumps(user, indent=4))
|
||||
|
||||
def delete_user(self, saas, server):
|
||||
self.authenticate()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django import forms
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -35,10 +37,22 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
|||
'on_cancel', 'payment_style', 'tax', 'nominal_price')
|
||||
}),
|
||||
)
|
||||
actions = [update_orders, clone]
|
||||
change_view_actions = actions + [view_help]
|
||||
actions = (update_orders, clone)
|
||||
change_view_actions = actions + (view_help,)
|
||||
change_form_template = 'admin/services/service/change_form.html'
|
||||
|
||||
def get_urls(self):
|
||||
"""Returns the additional urls for the change view links"""
|
||||
urls = super(ServiceAdmin, self).get_urls()
|
||||
admin_site = self.admin_site
|
||||
opts = self.model._meta
|
||||
return patterns('',
|
||||
url('^add/help/$',
|
||||
admin_site.admin_view(self.help_view),
|
||||
name='%s_%s_help' % (opts.app_label, opts.model_name)
|
||||
)
|
||||
) + urls
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" Improve performance of account field and filter by account """
|
||||
if db_field.name == 'content_type':
|
||||
|
@ -72,6 +86,20 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
|||
)
|
||||
})
|
||||
return qs
|
||||
|
||||
def help_view(self, request, *args):
|
||||
opts = self.model._meta
|
||||
context = {
|
||||
'add': True,
|
||||
'title': _("Need some help?"),
|
||||
'opts': opts,
|
||||
'obj': args[0].get() if args else None,
|
||||
'action_name': _("help"),
|
||||
'app_label': opts.app_label,
|
||||
}
|
||||
return TemplateResponse(request, 'admin/services/service/help.html', context)
|
||||
help_view.url_name = 'help'
|
||||
help_view.verbose_name = _("Help")
|
||||
|
||||
|
||||
admin.site.register(Service, ServiceAdmin)
|
||||
|
|
|
@ -52,7 +52,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
return
|
||||
try:
|
||||
bool(self.matches(obj))
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
name = type(exception).__name__
|
||||
message = exception.message
|
||||
raise ValidationError(': '.join((name, message)))
|
||||
|
@ -64,7 +64,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
return
|
||||
try:
|
||||
bool(self.get_metric(obj))
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
name = type(exception).__name__
|
||||
message = exception.message
|
||||
raise ValidationError(': '.join((name, message)))
|
||||
|
@ -187,17 +187,17 @@ class ServiceHandler(plugins.Plugin):
|
|||
size = rdelta.years * 12
|
||||
size += rdelta.months
|
||||
days = calendar.monthrange(end.year, end.month)[1]
|
||||
size += decimal.Decimal(rdelta.days)/days
|
||||
size += decimal.Decimal(str(rdelta.days))/days
|
||||
elif self.billing_period == self.ANUAL:
|
||||
size = rdelta.years
|
||||
size += decimal.Decimal(rdelta.months)/12
|
||||
size += decimal.Decimal(str(rdelta.months))/12
|
||||
days = 366 if calendar.isleap(end.year) else 365
|
||||
size += decimal.Decimal(rdelta.days)/days
|
||||
size += decimal.Decimal(str(rdelta.days))/days
|
||||
elif self.billing_period == self.NEVER:
|
||||
size = 1
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return decimal.Decimal(size)
|
||||
return decimal.Decimal(str(size))
|
||||
|
||||
def get_pricing_slots(self, ini, end):
|
||||
day = 1
|
||||
|
|
|
@ -211,14 +211,14 @@ class Service(models.Model):
|
|||
if counter >= metric:
|
||||
counter = metric
|
||||
accumulated += (counter - ant_counter) * rate['price']
|
||||
return decimal.Decimal(accumulated)
|
||||
return decimal.Decimal(str(accumulated))
|
||||
ant_counter = counter
|
||||
accumulated += rate['price'] * rate['quantity']
|
||||
else:
|
||||
for rate in rates:
|
||||
counter += rate['quantity']
|
||||
if counter >= position:
|
||||
return decimal.Decimal(rate['price'])
|
||||
return decimal.Decimal(str(rate['price']))
|
||||
|
||||
def get_rates(self, account, cache=True):
|
||||
# rates are cached per account
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
payment_style=PREPAY">Database</option>
|
||||
</select></li>
|
||||
<li>
|
||||
<a href="./help" class="historylink">{% trans "Help" %}</a>
|
||||
<a href="./help/" class="historylink">{% trans "Help" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
{% block content %}
|
||||
<div>
|
||||
<div style="margin:20px;">
|
||||
Enjoy my friend.
|
||||
<img src="{% static "services/img/services.png" %}"</img>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -69,8 +69,7 @@ class MailboxBillingTest(BaseBillingTest):
|
|||
return self.resource
|
||||
|
||||
def allocate_disk(self, mailbox, value):
|
||||
# TODO get_or_Create return created
|
||||
data = ResourceData.get_or_create(mailbox, self.resource)
|
||||
data, __ = ResourceData.get_or_create(mailbox, self.resource)
|
||||
data.allocated = value
|
||||
data.save()
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class BaseTrafficBillingTest(BaseBillingTest):
|
|||
|
||||
def report_traffic(self, account, value):
|
||||
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.systemusers.get(), value=value)
|
||||
data = ResourceData.get_or_create(account, self.resource)
|
||||
data, __ = ResourceData.get_or_create(account, self.resource)
|
||||
data.update()
|
||||
|
||||
|
||||
|
|
|
@ -123,7 +123,9 @@ class PHPApp(AppType):
|
|||
|
||||
def get_php_version_number(self):
|
||||
php_version = self.get_php_version()
|
||||
number = re.findall(r'[0-9]+\.?[0-9]+', php_version)
|
||||
number = re.findall(r'[0-9]+\.?[0-9]?', php_version)
|
||||
if not number:
|
||||
raise ValueError("No version number matches for '%s'" % php_version)
|
||||
if len(number) > 1:
|
||||
raise ValueError("Multiple version number matches for '%'" % php_version)
|
||||
raise ValueError("Multiple version number matches for '%s'" % php_version)
|
||||
return number[0]
|
||||
|
|
|
@ -18,7 +18,7 @@ def all_valid(kwargs):
|
|||
for field, validator in kwargs.iteritems():
|
||||
try:
|
||||
validator[0](*validator[1:])
|
||||
except ValidationError, error:
|
||||
except ValidationError as error:
|
||||
errors[field] = error
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
@ -91,7 +91,7 @@ def validate_username(value):
|
|||
def validate_password(value):
|
||||
try:
|
||||
crack.VeryFascistCheck(value)
|
||||
except ValueError, message:
|
||||
except ValueError as message:
|
||||
raise ValidationError("Password %s." % str(message)[3:])
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ def check(codeString, filename):
|
|||
try:
|
||||
with BlackHole():
|
||||
tree = ast.parse(codeString, filename)
|
||||
except SyntaxError, e:
|
||||
except SyntaxError as e:
|
||||
return [PySyntaxError(filename, e)]
|
||||
else:
|
||||
# Okay, it's syntactically valid. Now parse it into an ast and check it
|
||||
|
@ -67,7 +67,7 @@ def checkPath(filename):
|
|||
"""
|
||||
try:
|
||||
return check(file(filename, 'U').read() + '\n', filename)
|
||||
except IOError, msg:
|
||||
except IOError as msg:
|
||||
return ["%s: %s" % (filename, msg.args[1])]
|
||||
except TypeError:
|
||||
pass
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
{% if obj %}
|
||||
› <a href="{% url opts|admin_urlname:'change' obj.pk %}">{{ obj }}</a>
|
||||
› {{ action_name }}
|
||||
{% elif add %}
|
||||
› <a href="../">{% trans "Add" %} {{ opts.verbose_name }}</a>
|
||||
› {{ action_name }}
|
||||
{% else %}
|
||||
› {{ action_name }} multiple objects
|
||||
{% endif %}
|
||||
|
|
|
@ -38,7 +38,7 @@ def read_async(fd):
|
|||
"""
|
||||
try:
|
||||
return fd.read()
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
if e.errno != errno.EAGAIN:
|
||||
raise e
|
||||
else:
|
||||
|
@ -74,7 +74,7 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='',
|
|||
try:
|
||||
stdout += unicode(stdoutPiece.decode("utf8")) if force_unicode else stdoutPiece
|
||||
sdterr += unicode(stderrPiece.decode("utf8")) if force_unicode else stderrPiece
|
||||
except UnicodeDecodeError, e:
|
||||
except UnicodeDecodeError as e:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
|
Loading…
Reference in New Issue