AlarmTool.py 8.38 KB
Newer Older
Sebastien Robin's avatar
Sebastien Robin committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
##############################################################################
#
# Copyright (c) 2004 Nexedi SARL and Contributors. All Rights Reserved.
#                    Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

29
import time
30 31
import threading

Sebastien Robin's avatar
Sebastien Robin committed
32
from AccessControl import ClassSecurityInfo
33
from AccessControl.SecurityManagement import newSecurityManager
Sebastien Robin's avatar
Sebastien Robin committed
34
from Globals import InitializeClass, DTMLFile, PersistentMapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35
from Products.ERP5Type.Core.Folder import Folder
Sebastien Robin's avatar
Sebastien Robin committed
36 37 38 39
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions
from Products.ERP5 import _dtmldir
from DateTime import DateTime
40 41
import urllib
import socket
Sebastien Robin's avatar
Sebastien Robin committed
42

43
from zLOG import LOG, INFO
Sebastien Robin's avatar
Sebastien Robin committed
44

45 46 47 48
import re
# minimal IP:Port regexp
NODE_RE = re.compile('^\d+\.\d+\.\d+\.\d+:\d+$')

49 50 51
try:
  from Products.TimerService import getTimerService
except ImportError:
52
  def getTimerService(self):
53
    pass
Sebastien Robin's avatar
Sebastien Robin committed
54

55 56 57
last_tic = time.time()
last_tic_lock = threading.Lock()

Sebastien Robin's avatar
Sebastien Robin committed
58 59
class AlarmTool(BaseTool):
  """
60 61
    This tool manages alarms.

Vincent Pelletier's avatar
Vincent Pelletier committed
62
    It is used as a central managment point for all alarms.
Sebastien Robin's avatar
Sebastien Robin committed
63

Vincent Pelletier's avatar
Vincent Pelletier committed
64 65
    Inside this tool we have a way to retrieve all reports comings
    from Alarms,...
Sebastien Robin's avatar
Sebastien Robin committed
66 67 68 69 70 71 72 73 74 75 76
  """
  id = 'portal_alarms'
  meta_type = 'ERP5 Alarm Tool'
  portal_type = 'Alarm Tool'

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainAlarmTool', _dtmldir )

77 78
  security.declareProtected( Permissions.ManagePortal , 'manageAlarmNode' )
  manageAlarmNode = DTMLFile( 'manageAlarmNode', _dtmldir )
79 80


Sebastien Robin's avatar
Sebastien Robin committed
81
  manage_options = ( ( { 'label'   : 'Overview'
Vincent Pelletier's avatar
Vincent Pelletier committed
82 83
                       , 'action'   : 'manage_overview'
                       }
84 85
                     , { 'label'   : 'Alarm Node'
                       , 'action'   : 'manageAlarmNode'
86 87
                       }
                     ,
Vincent Pelletier's avatar
Vincent Pelletier committed
88 89 90
                     )
                     + Folder.manage_options
                   )
Sebastien Robin's avatar
Sebastien Robin committed
91

92
  _properties = ( {'id': 'interval', 'type': 'int', 'mode': 'w', }, )
93
  interval = 60 # Default interval for alarms is 60 seconds
94
  alarmNode = ''
Vincent Pelletier's avatar
Vincent Pelletier committed
95

Sebastien Robin's avatar
Sebastien Robin committed
96
  # API to manage alarms
Vincent Pelletier's avatar
Vincent Pelletier committed
97 98 99 100 101 102 103
  # Aim of this API:
  #-- see all alarms stored everywhere
  #-- defines global alarms
  #-- activate an alarm
  #-- see reports
  #-- see active alarms
  #-- retrieve all alarms
Sebastien Robin's avatar
Sebastien Robin committed
104 105

  security.declareProtected(Permissions.ModifyPortalContent, 'getAlarmList')
Vincent Pelletier's avatar
Vincent Pelletier committed
106
  def getAlarmList(self, to_active = 0):
Sebastien Robin's avatar
Sebastien Robin committed
107
    """
Vincent Pelletier's avatar
Vincent Pelletier committed
108
      We retrieve thanks to the catalog the full list of alarms
Sebastien Robin's avatar
Sebastien Robin committed
109
    """
Sebastien Robin's avatar
Sebastien Robin committed
110
    if to_active:
111
      now = DateTime()
112 113
      catalog_search = self.portal_catalog.unrestrictedSearchResults(
        portal_type = self.getPortalAlarmTypeList(),
114
        alarm_date={'query':now,'range':'ngt'}
Vincent Pelletier's avatar
Vincent Pelletier committed
115
      )
116
      # check again the alarm date in case the alarm was not yet reindexed
Vincent Pelletier's avatar
Vincent Pelletier committed
117 118
      alarm_list = [x.getObject() for x in catalog_search \
          if x.getObject().getAlarmDate()<=now]
Sebastien Robin's avatar
Sebastien Robin committed
119
    else:
120
      catalog_search = self.portal_catalog.unrestrictedSearchResults(
Vincent Pelletier's avatar
Vincent Pelletier committed
121 122
        portal_type = self.getPortalAlarmTypeList()
      )
123
      alarm_list = [x.getObject() for x in catalog_search]
Sebastien Robin's avatar
Sebastien Robin committed
124 125 126 127 128
    return alarm_list

  security.declareProtected(Permissions.ModifyPortalContent, 'tic')
  def tic(self):
    """
Vincent Pelletier's avatar
Vincent Pelletier committed
129 130
      We will look at all alarms and see if they should be activated,
      if so then we will activate them.
Sebastien Robin's avatar
Sebastien Robin committed
131 132
    """
    current_date = DateTime()
133
    for alarm in self.getAlarmList(to_active=1):
134
      if alarm is not None:
135
        user = alarm.getWrappedOwner()
136
        newSecurityManager(self.REQUEST, user)
137
        if alarm.isActive() or not alarm.isEnabled():
138 139
          # do nothing if already active, or not enabled
          continue
140
        alarm.activeSense()
141

142 143
  security.declareProtected(Permissions.ManageProperties, 'isSubscribed')
  def isSubscribed(self):
144 145 146 147 148 149
    """ return True, if we are subscribed to TimerService.
    Otherwise return False.
    """
    service = getTimerService(self)
    if not service:
      LOG('AlarmTool', INFO, 'TimerService not available')
150 151
      return False

152 153 154 155 156
    path = '/'.join(self.getPhysicalPath())
    if path in service.lisSubscriptions():
      return True
    return False

157 158
  security.declareProtected(Permissions.ManageProperties, 'subscribe')
  def subscribe(self):
Vincent Pelletier's avatar
Vincent Pelletier committed
159 160 161
    """
      Subscribe to the global Timer Service.
    """
162 163
    service = getTimerService(self)
    if not service:
164 165
      LOG('AlarmTool', INFO, 'TimerService not available')
      return
166 167 168 169 170
    service.subscribe(self)
    return "Subscribed to Timer Service"

  security.declareProtected(Permissions.ManageProperties, 'unsubscribe')
  def unsubscribe(self):
Vincent Pelletier's avatar
Vincent Pelletier committed
171 172 173
    """
      Unsubscribe from the global Timer Service.
    """
174 175
    service = getTimerService(self)
    if not service:
176 177
      LOG('AlarmTool', INFO, 'TimerService not available')
      return
178 179 180 181 182
    service.unsubscribe(self)
    return "Usubscribed from Timer Service"

  def manage_beforeDelete(self, item, container):
    self.unsubscribe()
183
    BaseTool.inheritedAttribute('manage_beforeDelete')(self, item, container)
184

185 186
  def manage_afterAdd(self, item, container):
    self.subscribe()
187
    BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container)
188

189
  security.declarePrivate('process_timer')
190
  def process_timer(self, interval, tick, prev="", next=""):
191
    """
Vincent Pelletier's avatar
Vincent Pelletier committed
192 193 194
      Call tic() every x seconds. x is defined in self.interval
      This method is called by TimerService in the interval given
      in zope.conf. The Default is every 5 seconds.
195
    """
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    # only start when we are the alarmNode or if it's empty
    if (self.alarmNode == self.getCurrentNode()) \
      or not self.alarmNode:
      global last_tic
      last_tic_lock.acquire(1)
      try:
        if tick.timeTime() - last_tic >= self.interval:
          self.tic()
          last_tic = tick.timeTime()
      finally:
        last_tic_lock.release()

  def getCurrentNode(self):
      """ Return current node in form ip:port """
      port = ''
      from asyncore import socket_map
      for k, v in socket_map.items():
          if hasattr(v, 'port'):
              # see Zope/lib/python/App/ApplicationManager.py: def getServers(self)
              type = str(getattr(v, '__class__', 'unknown'))
              if type == 'ZServer.HTTPServer.zhttp_server':
                  port = v.port
                  break
      ip = socket.gethostbyname(socket.gethostname())
      currentNode = '%s:%s' %(ip, port)
      return currentNode
      
  security.declarePublic('getAlarmNode')
  def getAlarmNode(self):
      """ Return the alarmNode """
      return self.alarmNode

  def _isValidNodeName(self, node_name) :
    """Check we have been provided a good node name"""
    return isinstance(node_name, str) and NODE_RE.match(node_name)
      
  security.declarePublic('manage_setAlarmNode')
  def manage_setAlarmNode(self, alarmNode, REQUEST=None):
      """ set the alarm node """   
      if not alarmNode or self._isValidNodeName(alarmNode):
        self.alarmNode = alarmNode
        if REQUEST is not None:
            REQUEST.RESPONSE.redirect(
                REQUEST.URL1 +
                '/manageAlarmNode?manage_tabs_message=' +
                urllib.quote("Distributing Node successfully changed."))
      else :
        if REQUEST is not None:
            REQUEST.RESPONSE.redirect(
                REQUEST.URL1 +
                '/manageAlarmNode?manage_tabs_message=' +
                urllib.quote("Malformed Distributing Node."))
Sebastien Robin's avatar
Sebastien Robin committed
248