Commit 88db72d0 authored by Jean-Paul Smets's avatar Jean-Paul Smets

Finalisation of the Alarm API and development. Alarm can now do everything...

Finalisation of the Alarm API and development. Alarm can now do everything there were meant to be. Sense problems through activities, report problems in a nice way, and solve problems. They can be used as a simple cron but not only.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@18563 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 9d1c0cab
......@@ -26,6 +26,8 @@
#
##############################################################################
import types
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
......@@ -228,10 +230,20 @@ class Alarm(XMLObject, PeriodicityMixin):
"""
An Alarm is in charge of checking anything (quantity of a certain
resource on the stock, consistency of some order,....) periodically.
It should also provide a solution if something wrong happens.
Some information should be displayed to the user, and also notifications.
This check can span over multiple activities through an active
process.
An Alarm is capable of displaying the last result of the check
process which was run in background. The result can be provided
either as a boolean value (alarm was raised or not) or
in the form of an HTML report which is intended to be
displayed in a control center. Moreover, user may be notified
automatically of alarm failures.
Alarm may also provide a solution if something wrong happens. This
solution takes the form of a script which can be invoked
by the administrator or by the user by clicking on a button
displayed in the Alarm control center.
"""
# CMF Type Definition
......@@ -261,95 +273,184 @@ class Alarm(XMLObject, PeriodicityMixin):
"""
This method returns only True or False.
It simply tells if this alarm is currently
active or not. It is activated when it is doing some calculation with
activeSense or solve.
active or not. An Alarm is said to be active whenever
some calculation is undergoing either as part
of the sensing process (activeSense) or as part
of the problem resolution process (solve).
"""
return self.hasActivity(only_valid=1)
security.declareProtected(Permissions.ModifyPortalContent, 'activeSense')
def activeSense(self):
def activeSense(self, fixit=0):
"""
This method checks if there is a problem. This method can launch a very long
activity. We don't care about the response, we just want to start
some calculations. Results should be read with the method 'sense'
later.
This method launches the sensing process as activities.
It is intended to launch a very long process made
of many activities. It returns nothing since the results
are collected in an active process.
The result of the sensing process can be obtained by invoking
the sense method or by requesting a report.
"""
# Set the new date
LOG('activeSense, self.getPath()',0,self.getPath())
# LOG('activeSense, self.getPath()',0,self.getPath())
# Set the next date at which this method should be invoked
self.setNextAlarmDate()
# Find the active sensing method and invoke it
# as an activity so that we can benefit from
# distribution of alarm processing as soon as possible
method_id = self.getActiveSenseMethodId()
if method_id is not None:
method = getattr(self.activate(),method_id)
return method()
if method_id not in (None, ''):
# A tag is provided as a parameter in order to be
# able to notify the user after all processes are ended
# We do some inspection to keep compatibility
# (because fixit and tag were not set previously)
tag='Alarm_activeSense_%s' % self.getId()
kw = {}
method = getattr(self, method_id)
name_list = method.func_code.co_varnames
if 'fixit' in name_list or (method.func_defaults is not None
and len(method.func_defaults) < len(name_list)):
# New API - also if variable number of named parameters
getattr(self.activate(tag=tag), method_id)(fixit=fixit, tag=tag)
else:
# Old API
getattr(self.activate(tag=tag), method_id)()
if self.isAlarmNotificationMode():
self.activate(after_tag=tag).notify()
security.declareProtected(Permissions.ModifyPortalContent, 'sense')
def sense(self):
def sense(self, process=None):
"""
This method returns True or False. False for no problem, True for problem.
This method should respond quickly. Basically the response depends on some
previous calculation made by activeSense.
This method should respond very quickly.
Complex alarms should use activity based calculations through
the activeSense method.
The process parameter can be used to retrive sense values for
past processes.
"""
value = False
method_id = self.getSenseMethodId()
process = self.getLastActiveProcess()
if process is None:
return value
if method_id is not None:
method = getattr(self,method_id)
value = method()
else:
for result in process.getResultList():
if result.severity > result.INFO:
value = True
break
process.setSenseValue(value)
return value
process = self.getLastActiveProcess()
# First case - simple cron style alarm
# with no results
if process is None and method_id in (None, ''):
return None
# Second case - this alarm does not use an
# active process. This is perfectly acceptable
# in some cases, whenever the sense calculation
# is really fast.
if process is None:
method = getattr(self, method_id)
return method()
# Third case - this alarm uses an
# active process and a method_id is defined
if method_id not in (None, ''):
method = getattr(self, method_id)
return method(process=process)
# Fourth case - this alarm uses an
# active process but no method_id is defined
for result in process.getResultList():
# This is useful is result is returned as a Return instance
if result.severity > result.INFO:
return True
# This is the default case
if getattr(result, 'result'):
return True
return False
# This comment here is kept for historical reasons
# There used to be a call to process.setSenseValue(value)
# This means that each time an alarm is displayed,
# we modify it to keep its latest sense result somewhere
# This was bad for two reasons: first of all, it is
# actually a caching problem, and if necesssary,
# this caching problem should be solved by using caches.
# Then, if caching is required, it may not only be
# at display time and not only for sense(). So, the
# baseline is to use caches and if necessary to develop
# a new cache plugin which uses ZODB to store values
# for a long time.
security.declareProtected(Permissions.View, 'report')
def report(self, process=None):
"""
This methods produces a report (HTML)
This generate the output of the results. It can be used to nicely
explain the problem. We don't do calculation at this time, it should
be made by activeSense.
"""
method_id = self.getReportMethodId(None)
#LOG('Alarm.report, method_id',0,method_id)
if method_id is None:
return ''
method = getattr(self,method_id)
process = self.getLastActiveProcess()
result = None
if process is not None:
result = method(process=process)
return result
def report(self, reset=0, process=None):
"""
This methods produces a report (HTML) to display
the results of the sensing process.
The report is intended to provide a nice visualisation
of the sensing process, of problems which may occur or
of the fact that there was no problem. No calculation
should be made normally at this time (or very fast calculation).
Complex alarms should implement calculation through
the invocation of activeSense.
Report implementation is normally made using an
ERP5 Form.
"""
if process is None:
process = self.getLastActiveProcess().getRelativeUrl()
elif not type(process) in types.StringTypes:
process = process.getRelativeUrl()
list_action = _getViewFor(self, view='report')
if getattr(aq_base(list_action), 'isDocTemp', 0):
return apply(list_action, (self, self.REQUEST),
process=process, reset=reset)
else:
return list_action(process=process, reset=reset)
security.declareProtected(Permissions.ModifyPortalContent, 'solve')
def solve(self):
"""
This method tries solves the problem detected by sense.
This method tries resolve a problems detected by an Alarm
within the sensing process. Problem resolution is
implemented by an external script.
This solve the problem if there is a problem detected by sense. If
no problems, then nothing to do here.
If no external script is dehfined, activeSense is invoked
with fixit=1
"""
pass
method_id = self.getSolveMethodId()
if method_id not in (None, ''):
method = getattr(self.activate(), method_id)
return method()
return self.activeSense(fixit=1)
security.declareProtected(Permissions.ModifyPortalContent, 'notify')
def _notify(self):
def notify(self):
"""
This method is called to notify people that some alarm has
been sensed.
for example we can send email.
been sensed. Notification consists of sending an email
to the system address if nothing was defined or to
notify all agents defined on the alarm if specified.
"""
notification_tool = getToolByName(self, 'portal_notifications')
candidate_list = self.getDestinationValueList()
if candidate_list:
recipient = candidate_list
else:
recipient = None
if self.sense():
prefix = 'ERROR'
else:
prefix = 'INFO'
notification_tool.sendMessage(recipient=candidate_list,
subject='[%s] ERP5 Alarm Notification: %s' %
(prefix, self.getTitle()),
message="""
Alarm Title: %s
We define nothing here, because we will use an interaction workflow.
"""
pass
Alarm Description:
%s
notify = WorkflowMethod(_notify, id='notify')
Alarm URL: %s
""" % (self.getTitle(), self.getDescription(), self.absolute_url()))
security.declareProtected(Permissions.View, 'getLastActiveProcess')
def getLastActiveProcess(self):
......@@ -359,13 +460,14 @@ class Alarm(XMLObject, PeriodicityMixin):
"""
active_process_list = self.getCausalityRelatedValueList(
portal_type='Active Process')
def sort_date(a, b):
return cmp(a.getStartDate(), b.getStartDate())
active_process_list.sort(sort_date)
active_process = None
if len(active_process_list)>0:
active_process = active_process_list[-1]
return active_process
if len(active_process_list) > 0:
return active_process_list[-1]
return None
security.declareProtected(Permissions.ModifyPortalContent,
'newActiveProcess')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment