From 6116db5147e4c0e96cf745e2bae0cc208d7f9a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Wed, 30 Mar 2016 11:23:23 +0200 Subject: [PATCH] basic scheduled actions working (needs backedn worker to be done) --- .../REST/methods/services_pool_calendars.py | 33 +++- server/src/uds/core/util/calendar/__init__.py | 22 ++- ...316_0806.py => 0021_auto_20160330_0938.py} | 4 +- server/src/uds/models/CalendarAction.py | 52 +++++- .../adm/js/gui-d-servicespools-actions.coffee | 173 +++++++++++------- .../js/gui-d-servicespools-calendars.coffee | 2 +- .../gui-d-servicespools-publications.coffee | 4 +- 7 files changed, 198 insertions(+), 92 deletions(-) rename server/src/uds/migrations/{0021_auto_20160316_0806.py => 0021_auto_20160330_0938.py} (91%) diff --git a/server/src/uds/REST/methods/services_pool_calendars.py b/server/src/uds/REST/methods/services_pool_calendars.py index db873c652..43e7f66e7 100644 --- a/server/src/uds/REST/methods/services_pool_calendars.py +++ b/server/src/uds/REST/methods/services_pool_calendars.py @@ -65,7 +65,7 @@ class AccessCalendars(DetailHandler): return { 'id': item.uuid, 'calendarId': item.calendar.uuid, - 'name': item.calendar.name, + 'calendar': item.calendar.name, 'access': item.access, 'priority': item.priority, } @@ -87,7 +87,7 @@ class AccessCalendars(DetailHandler): def getFields(self, parent): return [ {'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}}, - {'name': {'title': _('Name')}}, + {'calendar': {'title': _('Calendar')}}, {'access': {'title': _('Access')}}, ] @@ -120,16 +120,21 @@ class ActionsCalendars(DetailHandler): ''' Processes the transports detail requests of a Service Pool ''' + custom_methods = ('execute') + @staticmethod def as_dict(item): return { 'id': item.uuid, 'calendarId': item.calendar.uuid, - 'name': item.calendar.name, - 'action': CALENDAR_ACTION_DICT[item.action]['description'], + 'calendar': item.calendar.name, + 'action': item.action, + 'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'], 'atStart': item.atStart, - 'offset': item.eventsOffset, - 'params': json.loads(item.params) + 'eventsOffset': item.eventsOffset, + 'params': json.loads(item.params), + 'nextExecution': item.nextExecution, + 'lastExecution': item.lastExecution } def getItems(self, parent, item): @@ -148,11 +153,13 @@ class ActionsCalendars(DetailHandler): def getFields(self, parent): return [ - {'name': {'title': _('Name')}}, - {'action': {'title': _('Action')}}, + {'calendar': {'title': _('Calendar')}}, + {'actionDescription': {'title': _('Action')}}, {'params': {'title': _('Parameters')}}, {'atStart': {'title': _('Relative to')}}, - {'offset': {'title': _('Time offset')}}, + {'eventsOffset': {'title': _('Time offset')}}, + {'nextExecution': {'title': _('Next execution'), 'type': 'datetime'}}, + {'lastExecution': {'title': _('Last execution'), 'type': 'datetime'}}, ] def saveItem(self, parent, item): @@ -183,3 +190,11 @@ class ActionsCalendars(DetailHandler): def deleteItem(self, parent, item): CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete() + + def execute(self, parent, item): + logger.debug('Launching action') + uuid = processUuid(item) + calAction = CalendarAction.objects.get(uuid=uuid) + calAction.execute() + + return self.success() diff --git a/server/src/uds/core/util/calendar/__init__.py b/server/src/uds/core/util/calendar/__init__.py index 20486dfaf..2773fb763 100644 --- a/server/src/uds/core/util/calendar/__init__.py +++ b/server/src/uds/core/util/calendar/__init__.py @@ -41,11 +41,12 @@ from uds.models.Calendar import Calendar from uds.core.util.Cache import Cache import datetime +import time import six import bitarray import logging -__updated__ = '2016-03-14' +__updated__ = '2016-03-30' logger = logging.getLogger(__name__) @@ -113,6 +114,9 @@ class CalendarChecker(object): next_event = None for rule in self.calendar.rules.all(): + if rule.start > checkFrom or (rule.end is not None and rule.end < checkFrom.date()): + continue + if startEvent: event = rule.as_rrule().after(checkFrom) # At start else: @@ -149,22 +153,28 @@ class CalendarChecker(object): return data[dtime.hour * 60 + dtime.minute] - def nextEvent(self, checkFrom=None, startEvent=True): + def nextEvent(self, checkFrom=None, startEvent=True, offset=None): ''' Returns next event for this interval Returns a list of two elements. First is datetime of event begining, second is timedelta of duration ''' + logger.debug('Obtainint nextEvent') if checkFrom is None: checkFrom = getSqlDatetime() - cacheKey = six.text_type(self.calendar.modified.toordinal()) + self.calendar.uuid + six.text_type(checkFrom.toordinal()) + 'event' + ('x' if startEvent is True else '_') - print cacheKey + if offset is None: + offset = datetime.timedelta(minutes=0) + + cacheKey = six.text_type(self.calendar.modified.toordinal()) + self.calendar.uuid + six.text_type(offset.seconds) + six.text_type(int(time.mktime(checkFrom.timetuple()))) + 'event' + ('x' if startEvent is True else '_') next_event = CalendarChecker.cache.get(cacheKey, None) - print next_event if next_event is None: - next_event = self._updateEvents(checkFrom, startEvent) + logger.debug('Regenerating cached nextEvent') + next_event = self._updateEvents(checkFrom - offset, startEvent) # We substract on checkin, so we can take into account for next execution the "offset" on start & end (just the inverse of current, so we substract it) + if next_event is not None: + next_event += offset CalendarChecker.cache.put(cacheKey, next_event, 3600) else: + logger.debug('nextEvent cache hit') CalendarChecker.hits += 1 return next_event diff --git a/server/src/uds/migrations/0021_auto_20160316_0806.py b/server/src/uds/migrations/0021_auto_20160330_0938.py similarity index 91% rename from server/src/uds/migrations/0021_auto_20160316_0806.py rename to server/src/uds/migrations/0021_auto_20160330_0938.py index 08a9fece8..082e05c8a 100644 --- a/server/src/uds/migrations/0021_auto_20160316_0806.py +++ b/server/src/uds/migrations/0021_auto_20160330_0938.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-03-16 08:06 +# Generated by Django 1.9.2 on 2016-03-30 09:38 from __future__ import unicode_literals from django.db import migrations, models @@ -36,6 +36,8 @@ class Migration(migrations.Migration): ('atStart', models.BooleanField(default=False)), ('eventsOffset', models.IntegerField(default=0)), ('params', models.CharField(default='', max_length=1024)), + ('lastExecution', models.DateTimeField(blank=True, db_index=True, default=None, null=True)), + ('nextExecution', models.DateTimeField(blank=True, db_index=True, default=None, null=True)), ('calendar', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uds.Calendar')), ], options={ diff --git a/server/src/uds/models/CalendarAction.py b/server/src/uds/models/CalendarAction.py index e4d215e69..4f618c901 100644 --- a/server/src/uds/models/CalendarAction.py +++ b/server/src/uds/models/CalendarAction.py @@ -34,16 +34,20 @@ from __future__ import unicode_literals -__updated__ = '2016-03-29' +__updated__ = '2016-03-30' from django.utils.translation import ugettext_lazy as _ from django.db import models from uds.models.Calendar import Calendar from uds.models.UUIDModel import UUIDModel +from uds.models.Util import NEVER, getSqlDatetime +from uds.core.util import calendar from uds.models.ServicesPool import ServicePool from django.utils.encoding import python_2_unicode_compatible # from django.utils.translation import ugettext_lazy as _, ugettext +import datetime +import json import logging logger = logging.getLogger(__name__) @@ -52,10 +56,10 @@ logger = logging.getLogger(__name__) # Each line describes: # CALENDAR_ACTION_PUBLISH = { 'id' : 'PUBLISH', 'description': _('Publish'), 'params': () } -CALENDAR_ACTION_CACHE_L1 = { 'id': 'CACHEL1', 'description': _('Sets cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache size') },) } -CALENDAR_ACTION_CACHE_L2 = { 'id': 'CACHEL2', 'description': _('Sets L2 cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache L2 size') },) } -CALENDAR_ACTION_INITIAL = { 'id': 'INITIAL', 'description': _('Set initial services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Initial services') },) } -CALENDAR_ACTION_MAX = { 'id': 'MAX', 'description': _('Change maximum number of services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Maximum services') },) } +CALENDAR_ACTION_CACHE_L1 = { 'id': 'CACHEL1', 'description': _('Sets cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache size'), 'default': '1' },) } +CALENDAR_ACTION_CACHE_L2 = { 'id': 'CACHEL2', 'description': _('Sets L2 cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache L2 size'), 'default': '1' },) } +CALENDAR_ACTION_INITIAL = { 'id': 'INITIAL', 'description': _('Set initial services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Initial services'), 'default': '1' },) } +CALENDAR_ACTION_MAX = { 'id': 'MAX', 'description': _('Change maximum number of services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Maximum services'), 'default': '10' },) } CALENDAR_ACTION_DICT = dict(list((c['id'], c) for c in (CALENDAR_ACTION_PUBLISH, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_MAX))) @@ -66,6 +70,9 @@ class CalendarAction(UUIDModel): atStart = models.BooleanField(default=False) # If false, action is done at end of event eventsOffset = models.IntegerField(default=0) # In minutes params = models.CharField(max_length=1024, default='') + # Not to be edited, just to be used as indicators for executions + lastExecution = models.DateTimeField(default=None, db_index=True, null=True, blank=True) + nextExecution = models.DateTimeField(default=None, db_index=True, null=True, blank=True) class Meta: ''' @@ -74,3 +81,38 @@ class CalendarAction(UUIDModel): db_table = 'uds_cal_action' app_label = 'uds' + @property + def offset(self): + return datetime.timedelta(minutes=self.eventsOffset) + + def execute(self, save=True): + logger.debug('Executing action') + self.lastExecution = getSqlDatetime() + params = json.loads(self.params) + + saveServicePool = save + + if CALENDAR_ACTION_CACHE_L1['id'] == self.action: + self.servicePool.cache_l1_srvs = int(params['size']) + elif CALENDAR_ACTION_CACHE_L2['id'] == self.action: + self.servicePool.cache_l1_srvs = int(params['size']) + elif CALENDAR_ACTION_INITIAL['id'] == self.action: + self.servicePool.initial_srvs = int(params['size']) + elif CALENDAR_ACTION_MAX['id'] == self.action: + self.servicePool.max_srvs = int(params['size']) + elif CALENDAR_ACTION_PUBLISH['id'] == self.action: + self.servicePool.publish(changeLog='Scheduled publication action') + saveServicePool = False + + # On save, will regenerate nextExecution + if save: + self.save() + + if saveServicePool: + self.servicePool.save() + + + def save(self, *args, **kwargs): + self.nextExecution = calendar.CalendarChecker(self.calendar).nextEvent(checkFrom=self.lastExecution, startEvent=self.atStart, offset=self.offset) + + return UUIDModel.save(self, *args, **kwargs) diff --git a/server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee b/server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee index 72ac295b7..a10821e54 100644 --- a/server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee +++ b/server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee @@ -1,5 +1,32 @@ +readParamsFromInputs = (modalId) -> + a = {} + a[$(v).attr('name')] = $(v).val() for v in $(modalId + ' .action_parameters') + return a + +actionSelectChangeFnc = (modalId, actionsList) -> + gui.doLog "onChange" + action = $(modalId + " #id_action_select").val() + if action == '-1' + return + $(modalId + " #parameters").empty() + for i in actionsList + if i['id'] == action + if i['params'].length > 0 + html = '' + for j in i['params'] + html += '
' + $(modalId + " #parameters").html(html) + gui.tools.applyCustoms modalId + return + + gui.servicesPools.actionsCalendars = (servPool, info) -> - actionsCalendars = new GuiElement(api.servicesPools.detail(servPool.id, "actions", { permission: servPool.permission }), "actions") + actionsApi = api.servicesPools.detail(servPool.id, "actions", { permission: servPool.permission }) + actionsCalendars = new GuiElement(actionsApi, "actions") actionsCalendarsTable = actionsCalendars.table( doNotLoadData: true icon: 'assigned' @@ -8,6 +35,34 @@ gui.servicesPools.actionsCalendars = (servPool, info) -> buttons: [ "new" "edit" + { + text: gettext("Launch Now") + css: "disabled" + disabled: true + click: (val, value, btn, tbl, refreshFnc) -> + if val.length != 1 + return + + gui.doLog val, val[0] + gui.forms.confirmModal gettext("Execute action"), gettext("Launch action execution right now?"), + onYes: -> + actionsApi.invoke val[0].id + "/execute", -> + refreshFnc() + return + return + + select: (vals, self, btn, tbl, refreshFnc) -> + unless vals.length == 1 + $(btn).addClass "disabled" + $(btn).prop('disabled', true) + return + + val = vals[0] + + $(btn).removeClass("disabled").prop('disabled', false) + # $(btn).addClass("disabled").prop('disabled', true) + return + } "delete" "xls" ] @@ -27,17 +82,12 @@ gui.servicesPools.actionsCalendars = (servPool, info) -> value.atStart = if value.atStart then gettext('Beginning') else gettext('Ending') onNew: (value, table, refreshFnc) -> - readParamsFromInputs = (modalId) -> - a = {} - a[$(v).attr('name')] = $(v).val() for v in $(modalId + ' .action_parameters') - return a api.templates.get "pool_add_action", (tmpl) -> api.calendars.overview (data) -> api.servicesPools.actionsList servPool.id, (actionsList) -> modalId = gui.launchModal(gettext("Add scheduled action"), api.templates.evaluate(tmpl, calendars: data - priority: 1 calendarId: '' actionsList: actionsList action: '' @@ -64,27 +114,7 @@ gui.servicesPools.actionsCalendars = (servPool, info) -> return $(modalId + ' #id_action_select').on "change", (event) -> - action = $(modalId + " #id_action_select").val() - if action == '-1' - return - $(modalId + " #parameters").empty() - for i in actionsList - if i['id'] == action - if i['params'].length > 0 - html = '' - for j in i['params'] - if j['type'] == 'numeric' - defval = '1' - else - defval = '' - html += '
' - $(modalId + " #parameters").html(html) - gui.tools.applyCustoms modalId - return + actionSelectChangeFnc(modalId, actionsList) # Makes form "beautyfull" :-) gui.tools.applyCustoms modalId return @@ -93,49 +123,56 @@ gui.servicesPools.actionsCalendars = (servPool, info) -> return onEdit: (value, event, table, refreshFnc) -> - if value.id == -1 - api.templates.get "pool_access_default", (tmpl) -> - modalId = gui.launchModal(gettext("Default fallback access"), api.templates.evaluate(tmpl, - accessList: accessList - access: servPool.fallbackAccess - )) - $(modalId + " .button-accept").on "click", (event) -> - access = $(modalId + " #id_access_select").val() - servPool.fallbackAccess = access - gui.servicesPools.rest.setFallbackAccess servPool.id, access, (data) -> - $(modalId).modal "hide" - refreshFnc() - return - # Makes form "beautyfull" :-) - gui.tools.applyCustoms modalId - return - api.templates.get "pool_add_access", (tmpl) -> - actionsCalendars.rest.item value.id, (item) -> - api.calendars.overview (data) -> - gui.doLog "Item: ", item - modalId = gui.launchModal(gettext("Edit access calendar"), api.templates.evaluate(tmpl, - calendars: data - priority: item.priority - calendarId: item.calendarId - accessList: accessList - access: item.access - )) - $(modalId + " .button-accept").on "click", (event) -> - priority = $(modalId + " #id_priority").val() - calendar = $(modalId + " #id_calendar_select").val() - access = $(modalId + " #id_access_select").val() - actionsCalendars.rest.save - id: item.id - calendarId: calendar - access: access - priority: priority - , (data) -> - $(modalId).modal "hide" - refreshFnc() + api.templates.get "pool_add_action", (tmpl) -> + api.servicesPools.actionsList servPool.id, (actionsList) -> + actionsCalendars.rest.item value.id, (item) -> + for i in actionsList + if i['id'] == item.action + gui.doLog "Found ", i + for j in Object.keys(item.params) + gui.doLog "Testing ", j + for k in i['params'] + gui.doLog 'Checking ', k + if k['name'] == j + gui.doLog 'Setting value' + k['default'] = item.params[j] + + api.calendars.overview (data) -> + gui.doLog "Item: ", item + modalId = gui.launchModal(gettext("Edit access calendar"), api.templates.evaluate(tmpl, + calendars: data + calendarId: item.calendarId + actionsList: actionsList + action: item.action + eventsOffset: item.eventsOffset + atStart: item.atStart + )) + $(modalId + " .button-accept").on "click", (event) -> + offset = $(modalId + " #id_offset").val() + calendar = $(modalId + " #id_calendar_select").val() + action = $(modalId + " #id_action_select").val() + atStart = $(modalId + " #atStart_field").is(":checked") + actionsCalendars.rest.save + id: item.id + calendarId: calendar + action: action + eventsOffset: offset + atStart: atStart + action: action + params: readParamsFromInputs(modalId) + , (data) -> + $(modalId).modal "hide" + refreshFnc() + return return + $(modalId + ' #id_action_select').on "change", (event) -> + actionSelectChangeFnc(modalId, actionsList) + + # Triggers the event manually + actionSelectChangeFnc(modalId, actionsList) + # Makes form "beautyfull" :-) + gui.tools.applyCustoms modalId return - # Makes form "beautyfull" :-) - gui.tools.applyCustoms modalId return return return diff --git a/server/src/uds/static/adm/js/gui-d-servicespools-calendars.coffee b/server/src/uds/static/adm/js/gui-d-servicespools-calendars.coffee index 4676d285a..f56f15745 100644 --- a/server/src/uds/static/adm/js/gui-d-servicespools-calendars.coffee +++ b/server/src/uds/static/adm/js/gui-d-servicespools-calendars.coffee @@ -25,7 +25,7 @@ gui.servicesPools.accessCalendars = (servPool, info) -> onData: (data) -> data.push id: -1, - name: 'DEFAULT', + calendar: '-', priority: '10000000FallBack', access: servPool.fallbackAccess gui.doLog data diff --git a/server/src/uds/static/adm/js/gui-d-servicespools-publications.coffee b/server/src/uds/static/adm/js/gui-d-servicespools-publications.coffee index f297c911a..f09d735ad 100644 --- a/server/src/uds/static/adm/js/gui-d-servicespools-publications.coffee +++ b/server/src/uds/static/adm/js/gui-d-servicespools-publications.coffee @@ -1,6 +1,6 @@ gui.servicesPools.publications = (servPool, info) -> - pubApi = api.servicesPools.detail(servPool.id, "publications") - publications = new GuiElement(pubApi, "publications", { permission: servPool.permission }) + pubApi = api.servicesPools.detail(servPool.id, "publications", { permission: servPool.permission }) + publications = new GuiElement(pubApi, "publications") # Publications table publicationsTable = publications.table(