[Erp5-report] r18563 - /erp5/trunk/products/ERP5/Document/Alarm.py

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Dec 31 11:08:37 CET 2007


Author: jp
Date: Mon Dec 31 11:08:37 2007
New Revision: 18563

URL: http://svn.erp5.org?rev=18563&view=rev
Log:
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.

Modified:
    erp5/trunk/products/ERP5/Document/Alarm.py

Modified: erp5/trunk/products/ERP5/Document/Alarm.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/Alarm.py?rev=18563&r1=18562&r2=18563&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/Alarm.py (original)
+++ erp5/trunk/products/ERP5/Document/Alarm.py Mon Dec 31 11:08:37 2007
@@ -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 @@
   """
   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 @@
     """
     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):
-    """
-    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.
-
-    """
-    # Set the new date
-    LOG('activeSense, self.getPath()',0,self.getPath())
-
+  def activeSense(self, fixit=0):
+    """
+    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.
+    """
+    # 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)
+    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, process=None):
+    """
+    This method returns True or False. False for no problem, True for problem.
+
+    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.
+    """
+    method_id = self.getSenseMethodId()
+    if process is None:
+      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()
 
-  security.declareProtected(Permissions.ModifyPortalContent, 'sense')
-  def sense(self):
-    """
-    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.
-    """
-    value = False
-    method_id = self.getSenseMethodId()
-    process = self.getLastActiveProcess()
+    # 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, 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:
-      return value
-    if method_id is not None:
-      method = getattr(self,method_id)
-      value = method()
+      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:
-      for result in process.getResultList():
-        if result.severity > result.INFO:
-          value = True
-          break
-    process.setSenseValue(value)
-    return value
-
-  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
+      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 solve the problem if there is a problem detected by sense. If
-    no problems, then nothing to do here.
-    """
-    pass
+    This method tries resolve a problems detected by an Alarm
+    within the sensing process. Problem resolution is
+    implemented by an external script.
+
+    If no external script is dehfined, activeSense is invoked 
+    with fixit=1
+    """
+    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.
-
-    We define nothing here, because we will use an interaction workflow.
-    """
-    pass
-
-  notify = WorkflowMethod(_notify, id='notify')
+    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
+
+Alarm Description:
+%s
+
+Alarm URL: %s
+""" % (self.getTitle(), self.getDescription(), self.absolute_url()))
 
   security.declareProtected(Permissions.View, 'getLastActiveProcess')
   def getLastActiveProcess(self):
@@ -359,13 +460,14 @@
     """
     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')




More information about the Erp5-report mailing list