Package Products :: Package ZenModel :: Module actions |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenModel.actions

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  import re
     12  from socket import getaddrinfo
     13  from traceback import format_exc
     14  from zope.interface import implements
     15  from zope.component import getUtilitiesFor
     16
     17  from pynetsnmp import netsnmp
     18
     19  from twisted.internet.protocol import ProcessProtocol
     20
     21  from email.MIMEText import MIMEText
     22  from email.MIMEMultipart import MIMEMultipart
     23  from email.Utils import formatdate
     24
     25  from zenoss.protocols.protobufs import zep_pb2
     26  from Products.ZenEvents.events2.proxy import EventSummaryProxy
     27  from Products.ZenUtils.Utils import sendEmail
     28  from Products.Zuul.interfaces.actions import IEmailActionContentInfo, IPageActionContentInfo, ICommandActionContentInfo, ISnmpTrapActionContentInfo
     29  from Products.Zuul.form.interfaces import IFormBuilder
     30  from Products.ZenModel.UserSettings import GroupSettings
     31  from Products.ZenModel.interfaces import IAction, IProvidesEmailAddresses, IProvidesPagerAddresses, IProcessSignal, INotificationContextProvider
     32  from Products.ZenModel.NotificationSubscription import NotificationEventContextWrapper
     33  from Products.ZenEvents.Event import Event
     34  from Products.ZenUtils import Utils
     35  from Products.ZenUtils.guid.guid import GUIDManager
     36  from Products.ZenUtils.ProcessQueue import ProcessQueue
     37  from Products.ZenEvents.ZenEventClasses import Warning as SEV_WARNING
     38  from Products.ZenUtils.ZenTales import talEval
     39
     40  import logging
     41
     42  log = logging.getLogger("zen.actions")
     43
     44
     45
     46
     47 -class ActionExecutionException(Exception): pass
     48
     49
     50 -class ActionMissingException(Exception): pass
     51
     52
     53 -class TargetableActionException(ActionExecutionException):
     54 -    def __init__(self, action, notification, exceptionTargets):
     55          self.action = action
     56          self.notificationId = notification.id
     57          self.exceptionTargets = exceptionTargets
     58 -    def __str__(self):
     59          return "Failed {action} for notification {notification} on targets {targets}".format(
     60              action=self.action.name,
     61              notification=self.notificationId,
     62              targets = ','.join(self.exceptionTargets)
     63          )
     64
     65 -def processTalSource(source, **kwargs):
     66      """
     67      This function is used to parse fields made available to actions that allow
     68      for TAL expressions.
     69      """
     70      sourceStr = source
     71      context = kwargs.get('here', {})
     72      context.update(kwargs)
     73      return talEval(sourceStr, context, kwargs)
     74
     75
     76 -def _signalToContextDict(signal, zopeurl, notification=None, guidManager=None):
     77      summary = signal.event
     78      # build basic event context wrapper for notifications
     79      if signal.clear:
     80          # aged and closed events have clear == True, but they don't have an associated clear event
     81          #   spoof a clear event in those cases, so the notification messages contain useful info
     82          if summary.status == zep_pb2.STATUS_AGED:
     83              occur = signal.clear_event.occurrence.add()
     84              occur.summary = "Event aging task aged out the event."
     85              summary.cleared_by_event_uuid = "Event aging task"
     86          elif summary.status == zep_pb2.STATUS_CLOSED:
     87              occur = signal.clear_event.occurrence.add()
     88              occur.summary = "User '" + summary.current_user_name + "' closed the event in the Zenoss event console."
     89              summary.cleared_by_event_uuid = "User action"
     90          data = NotificationEventContextWrapper(summary, signal.clear_event)
     91      else:
     92          data = NotificationEventContextWrapper(summary)
     93
     94      # add urls to event context
     95      data['urls']['eventUrl'] = getEventUrl(zopeurl, summary.uuid)
     96      data['urls']['ackUrl'] = getAckUrl(zopeurl, summary.uuid)
     97      data['urls']['closeUrl'] = getCloseUrl(zopeurl, summary.uuid)
     98      proxy = EventSummaryProxy(summary)
     99      data['urls']['deviceUrl'] = _getBaseDeviceUrl(zopeurl, proxy.DeviceClass, proxy.device)
    100      data['urls']['eventsUrl'] = getEventsUrl(zopeurl, proxy.DeviceClass, proxy.device)
    101      data['urls']['reopenUrl'] = getReopenUrl(zopeurl, summary.uuid)
    102      data['urls']['baseUrl'] = zopeurl
    103      # now process all custom processors that might be registered to enhance
    104      # the event context
    105      for key, processor in getUtilitiesFor(IProcessSignal):
    106          data[key] = processor.process(signal)
    107
    108      # Process INotificationContextProvider
    109      for key, contextProvider in getUtilitiesFor(INotificationContextProvider):
    110          contextProvider.updateContext(signal, data)
    111
    112      # Add trigger and notification info
    113      if notification:
    114          data['notification']['name'] = notification.titleOrId()
    115      if guidManager:
    116          trigger = guidManager.getObject(signal.trigger_uuid)
    117          if trigger:
    118              data['trigger']['name'] = trigger.titleOrId()
    119
    120      return data
    121
    122
    123 -def _getBaseUrl(zopeurl):
    124      if not zopeurl:
    125          zopeurl = Utils.getDefaultZopeUrl()
    126      return '%s/zport/dmd' % zopeurl
    127
    128
    129 -def _getBaseEventUrl(zopeurl):
    130      return '%s/Events' % _getBaseUrl(zopeurl)
    131
    132
    133 -def _getBaseDeviceUrl(zopeurl, device_class, device_name):
    134      """
    135      Builds the URL for a device.
    136      Example: "http://.../Devices/Server/Linux/devices/localhost/devicedetail"
    137      """
    138      return '%s/Devices%s/devices/%s/devicedetail' % (_getBaseUrl(zopeurl), device_class, device_name)
    139
    140
    141 -def getEventUrl(zopeurl, evid):
    142      return "%s/viewDetail?evid=%s" % (_getBaseEventUrl(zopeurl), evid)
    143
    144
    145 -def getEventsUrl(zopeurl, device_class=None, device_name=None):
    146      if device_class and device_name:
    147          # events for a specific device
    148          return "%s#deviceDetailNav:device_events" % _getBaseDeviceUrl(zopeurl, device_class, device_name)
    149      else:
    150          #events on all devices
    151          return "%s/viewEvents" % _getBaseUrl(zopeurl)
    152
    153
    154 -def getAckUrl(zopeurl, evid):
    155      return "%s/manage_ackEvents?evids=%s&zenScreenName=viewEvents" %\
    156             (_getBaseEventUrl(zopeurl), evid)
    157
    158
    159 -def getCloseUrl(zopeurl, evid):
    160      return "%s/manage_deleteEvents?evids=%s&zenScreenName=viewHistoryEvents" %\
    161             (_getBaseEventUrl(zopeurl), evid)
    162
    163
    164 -def getReopenUrl(zopeurl, evid):
    165      return "%s/manage_undeleteEvents?evids=%s&zenScreenName=viewEvents" %\
    166             (_getBaseEventUrl(zopeurl), evid)
    167
    168
    169 -class IActionBase(object):
    170      """
    171      Mixin class for provided some common, necessary, methods.
    172      """
    173
    174 -    def configure(self, options):
    175          self.options = options
    176
    177 -    def getInfo(self, notification):
    178          return self.actionContentInfo(notification)
    179
    180 -    def generateJavascriptContent(self, notification):
    181          content = self.getInfo(notification)
    182          return IFormBuilder(content).render(fieldsets=False)
    183
    184 -    def getDefaultData(self, dmd):
    185          return {}
    186
    187
    188 -class TargetableAction(object):
    189
    190      shouldExecuteInBatch = False
    191
    192 -    def setupAction(self, dmd):
    193          """
    194          Some actions need to configure themselves with properties from the dmd.
    195          This is their opportunity to do so.
    196          """
    197          pass
    198
    199 -    def getTargets(self, notification):
    200          targets = set()
    201          for recipient in notification.recipients:
    202              if recipient['type'] in ['group', 'user']:
    203                  guid = recipient['value']
    204                  target_obj = self.guidManager.getObject(guid)
    205                  if target_obj:
    206                      for target in self.getActionableTargets(target_obj):
    207                          targets.add(target)
    208              else:
    209                  targets.add(recipient['value'])
    210          return targets
    211
    212 -    def handleExecuteError(self, exception, notification, target):
    213          # If there is an error executing this action on a target,
    214          # we need to handle it, but we don't want to prevent other
    215          # actions from executing on any other targets that may be
    216          # about to be acted on.
    217          msg = 'Error executing action {notification} on {target}'.format(
    218              notification=notification.id,
    219              target=target,
    220          )
    221          log.error(exception)
    222          log.error(msg)
    223          traceback = format_exc()
    224          event = Event(device="localhost",
    225                        eventClass="/App/Failed",
    226                        summary=msg,
    227                        message=traceback,
    228                        severity=SEV_WARNING, component="zenactiond")
    229          notification.dmd.ZenEventManager.sendEvent(event)
    230
    231
    232 -    def executeBatch(self, notification, signal, targets):
    233          raise NotImplemented()
    234
    235
    236 -    def execute(self, notification, signal):
    237          self.setupAction(notification.dmd)
    238
    239          exceptionTargets = []
    240          targets = self.getTargets(notification)
    241          if self.shouldExecuteInBatch:
    242              try:
    243                  log.debug("Executing batch action for targets.")
    244                  self.executeBatch(notification, signal, targets)
    245              except Exception, e:
    246                  self.handleExecuteError(e, notification, targets)
    247                  exceptionTargets.extend(targets)
    248          else:
    249              log.debug("Executing action serially for targets.")
    250              for target in targets:
    251                  try:
    252                      self.executeOnTarget(notification, signal, target)
    253                      log.debug('Done executing action for target: %s' % target)
    254                  except Exception, e:
    255                      self.handleExecuteError(e, notification, target)
    256                      exceptionTargets.append(target)
    257
    258          if exceptionTargets:
    259              raise TargetableActionException(self, notification, exceptionTargets)
    260
    261 -class EmailAction(IActionBase, TargetableAction):
    262      implements(IAction)
    263      id = 'email'
    264      name = 'Email'
    265      actionContentInfo = IEmailActionContentInfo
    266
    267      shouldExecuteInBatch = True
    268
    269 -    def __init__(self):
    270          super(EmailAction, self).__init__()
    271
    272 -    def getDefaultData(self, dmd):
    273          return dict(host=dmd.smtpHost,
    274                      port=dmd.smtpPort,
    275                      user=dmd.smtpUser,
    276                      password=dmd.smtpPass,
    277                      useTls=dmd.smtpUseTLS,
    278                      email_from=dmd.getEmailFrom())
    279
    280 -    def setupAction(self, dmd):
    281          self.guidManager = GUIDManager(dmd)
    282
    283 -    def executeBatch(self, notification, signal, targets):
    284          log.debug("Executing %s action for targets: %s", self.name, targets)
    285          self.setupAction(notification.dmd)
    286
    287          data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager)
    288          if signal.clear:
    289              log.debug('This is a clearing signal.')
    290              subject = processTalSource(notification.content['clear_subject_format'], **data)
    291              body = processTalSource(notification.content['clear_body_format'], **data)
    292          else:
    293              subject = processTalSource(notification.content['subject_format'], **data)
    294              body = processTalSource(notification.content['body_format'], **data)
    295
    296          log.debug('Sending this subject: %s' % subject)
    297          log.debug('Sending this body: %s' % body)
    298
    299          plain_body = MIMEText(self._stripTags(body))
    300          email_message = plain_body
    301
    302          if notification.content['body_content_type'] == 'html':
    303              email_message = MIMEMultipart('related')
    304              email_message_alternative = MIMEMultipart('alternative')
    305              email_message_alternative.attach(plain_body)
    306
    307              html_body = MIMEText(body.replace('\n', '<br />\n'))
    308              html_body.set_type('text/html')
    309              email_message_alternative.attach(html_body)
    310
    311              email_message.attach(email_message_alternative)
    312
    313          host = notification.content['host']
    314          port = notification.content['port']
    315          user = notification.content['user']
    316          password = notification.content['password']
    317          useTls = notification.content['useTls']
    318          email_from = notification.content['email_from']
    319
    320          email_message['Subject'] = subject
    321          email_message['From'] = email_from
    322          email_message['To'] = ','.join(targets)
    323          email_message['Date'] = formatdate(None, True)
    324
    325          result, errorMsg = sendEmail(
    326              email_message,
    327              host, port,
    328              useTls,
    329              user, password
    330          )
    331
    332          if result:
    333              log.debug("Notification '%s' sent emails to: %s",
    334                       notification.id, targets)
    335          else:
    336              raise ActionExecutionException(
    337                  "Notification '%s' FAILED to send emails to %s: %s" %
    338                  (notification.id, targets, errorMsg)
    339              )
    340
    341 -    def getActionableTargets(self, target):
    342          """
    343          @param target: This is an object that implements the IProvidesEmailAddresses
    344              interface.
    345          @type target: UserSettings or GroupSettings.
    346          """
    347          if IProvidesEmailAddresses.providedBy(target):
    348              return target.getEmailAddresses()
    349
    350 -    def _stripTags(self, data):
    351          """A quick html => plaintext converter
    352             that retains and displays anchor hrefs
    353
    354             stolen from the old zenactions.
    355             @todo: needs to be updated for the new data structure?
    356          """
    357          tags = re.compile(r'<(.|\n)+?>', re.I | re.M)
    358          aattrs = re.compile(r'<a(.|\n)+?href=["\']([^"\']*)[^>]*?>([^<>]*?)</a>', re.I | re.M)
    359          anchors = re.finditer(aattrs, data)
    360          for x in anchors: data = data.replace(x.group(), "%s: %s" % (x.groups()[2], x.groups()[1]))
    361          data = re.sub(tags, '', data)
    362          return data
    363
    364 -    def updateContent(self, content=None, data=None):
    365          updates = dict()
    366          updates['body_content_type'] = data.get('body_content_type', 'html')
    367
    368          properties = ['subject_format', 'body_format', 'clear_subject_format', 'clear_body_format']
    369          properties.extend(['host', 'port', 'user', 'password', 'useTls', 'email_from'])
    370          for k in properties:
    371              updates[k] = data.get(k)
    372
    373          content.update(updates)
    374
    375
    376 -class PageAction(IActionBase, TargetableAction):
    377      implements(IAction)
    378
    379      id = 'page'
    380      name = 'Page'
    381      actionContentInfo = IPageActionContentInfo
    382
    383 -    def __init__(self):
    384          super(PageAction, self).__init__()
    385
    386 -    def setupAction(self, dmd):
    387          self.guidManager = GUIDManager(dmd)
    388          self.page_command = dmd.pageCommand
    389
    390 -    def executeOnTarget(self, notification, signal, target):
    391          """
    392          @TODO: handle the deferred parameter on the sendPage call.
    393          """
    394          log.debug('Executing action: Page')
    395
    396          data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager)
    397          if signal.clear:
    398              log.debug('This is a clearing signal.')
    399              subject = processTalSource(notification.content['clear_subject_format'], **data)
    400          else:
    401              subject = processTalSource(notification.content['subject_format'], **data)
    402
    403          success, errorMsg = Utils.sendPage(
    404              target, subject, self.page_command,
    405              #deferred=self.options.cycle)
    406              deferred=False)
    407
    408          if success:
    409              log.debug("Notification '%s' sent page to %s." % (notification, target))
    410          else:
    411              raise ActionExecutionException(
    412                  "Notification '%s' failed to send page to %s. (%s)" % (notification, target, errorMsg))
    413
    414 -    def getActionableTargets(self, target):
    415          """
    416          @param target: This is an object that implements the IProvidesPagerAddresses
    417              interface.
    418          @type target: UserSettings or GroupSettings.
    419          """
    420          if IProvidesPagerAddresses.providedBy(target):
    421              return target.getPagerAddresses()
    422
    423 -    def updateContent(self, content=None, data=None):
    424          updates = dict()
    425
    426          properties = ['subject_format', 'clear_subject_format', ]
    427          for k in properties:
    428              updates[k] = data.get(k)
    429
    430          content.update(updates)
    431
    432
    433 -class EventCommandProtocol(ProcessProtocol):
    434 -    def __init__(self, cmd):
    435          self.cmd = cmd
    436          self.data = ''
    437          self.error = ''
    438
    439 -    def timedOut(self, value):
    440          log.error("Command '%s' timed out" % self.cmd.id)
    441          # FIXME: send an event or something?
    442          return value
    443
    444 -    def processEnded(self, reason):
    445          log.debug("Command finished: '%s'" % reason.getErrorMessage())
    446
    447          # FIXME: send an event or something?
    448          #
    449          # code = 1
    450          # try:
    451          #     code = reason.value.exitCode
    452          # except AttributeError:
    453          #     pass
    454          # code = reason.value.exitCode
    455          # if code == 0:
    456          #     cmdData = self.data or "<command produced no output>"
    457          # else:
    458          #     cmdError = self.error or "<command produced no output>"
    459
    460 -    def outReceived(self, text):
    461          self.data += text
    462
    463 -    def errReceived(self, text):
    464          self.error += text
    465
    466
    467 -class CommandAction(IActionBase, TargetableAction):
    468      implements(IAction)
    469
    470      id = 'command'
    471      name = 'Command'
    472      actionContentInfo = ICommandActionContentInfo
    473
    474      shouldExecuteInBatch = False
    475
    476 -    def configure(self, options):
    477          super(CommandAction, self).configure(options)
    478          self.processQueue = ProcessQueue(options.get('maxCommands', 10))
    479          self.processQueue.start()
    480
    481 -    def setupAction(self, dmd):
    482          self.guidManager = GUIDManager(dmd)
    483          self.dmd = dmd
    484
    485 -    def execute(self, notification, signal):
    486          # check to see if we have any targets
    487          if notification.recipients:
    488              return super(CommandAction, self).execute(notification, signal)
    489          else:
    490              self._execute(notification, signal)
    491
    492 -    def executeOnTarget(self, notification, signal, target):
    493          log.debug('Executing command action: %s on %s', self.name, target)
    494          user_env_format = notification.content.get('user_env_format', '')
    495          environ ={}
    496          env = dict( envvar.split('=') for envvar in user_env_format.split(';') if '=' in envvar)
    497          environ['env'] = env
    498          environ['user'] = getattr(self.dmd.ZenUsers, target, None)
    499          self._execute(notification, signal, environ)
    500
    501 -    def _execute(self, notification, signal, extra_env= {}):
    502          self.setupAction(notification.dmd)
    503          log.debug('Executing command action: %s', self.name)
    504
    505          if signal.clear:
    506              command = notification.content['clear_body_format']
    507          else:
    508              command = notification.content['body_format']
    509
    510          log.debug('Executing this command: %s', command)
    511
    512          actor = signal.event.occurrence[0].actor
    513          device = None
    514          if actor.element_uuid:
    515              device = self.guidManager.getObject(actor.element_uuid)
    516
    517          component = None
    518          if actor.element_sub_uuid:
    519              component = self.guidManager.getObject(actor.element_sub_uuid)
    520
    521          environ = {'dev': device, 'component': component, 'dmd': notification.dmd, 'env':None}
    522          data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager)
    523          environ.update(data)
    524
    525          if environ.get('evt', None):
    526              environ['evt'] = self._escapeEvent(environ['evt'])
    527
    528          if environ.get('clearEvt', None):
    529              environ['clearEvt'] = self._escapeEvent(environ['clearEvt'])
    530
    531          environ.update(extra_env)
    532          try:
    533              command = processTalSource(command, **environ)
    534          except Exception:
    535              raise ActionExecutionException('Unable to perform TALES evaluation on "%s" -- is there an unescaped $?' % command)
    536
    537          log.debug('Executing this compiled command: "%s"' % command)
    538          _protocol = EventCommandProtocol(command)
    539
    540          log.debug('Queueing up command action process.')
    541          self.processQueue.queueProcess(
    542              '/bin/sh',
    543                  ('/bin/sh', '-c', command),
    544              env=environ['env'],
    545              processProtocol=_protocol,
    546              timeout=int(notification.content['action_timeout']),
    547              timeout_callback=_protocol.timedOut
    548          )
    549
    550 -    def getActionableTargets(self, target):
    551          ids = [target.id]
    552          if isinstance(target, GroupSettings):
    553              ids = [x.id for x in target.getMemberUserSettings()]
    554          return ids
    555
    556 -    def updateContent(self, content=None, data=None):
    557          updates = dict()
    558
    559          properties = ['body_format', 'clear_body_format', 'action_timeout', 'user_env_format']
    560          for k in properties:
    561              updates[k] = data.get(k)
    562
    563          content.update(updates)
    564
    565 -    def _escapeEvent(self, evt):
    566          """
    567          Escapes the relavent fields of an event context for event commands.
    568          """
    569          if evt.message:
    570              evt.message = self._wrapInQuotes(evt.message)
    571          if evt.summary:
    572              evt.summary = self._wrapInQuotes(evt.summary)
    573          return evt
    574
    575 -    def _wrapInQuotes(self, msg):
    576          """
    577          Wraps the message in quotes, escaping any existing quote.
    578
    579          Before:  How do you pronounce "Zenoss"?
    580          After:  "How do you pronounce \"Zenoss\"?"
    581          """
    582          QUOTE = '"'
    583          BACKSLASH = '\\'
    584          return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))
    585
    586 -class SNMPTrapAction(IActionBase):
    587      implements(IAction)
    588
    589      id = 'trap'
    590      name = 'SNMP Trap'
    591      actionContentInfo = ISnmpTrapActionContentInfo
    592
    593      _sessions = {}
    594
    595 -    def setupAction(self, dmd):
    596          self.guidManager = GUIDManager(dmd)
    597
    598 -    def execute(self, notification, signal):
    599          """
    600          Send out an SNMP trap according to the definition in ZENOSS-MIB.
    601          """
    602          log.debug('Processing SNMP Trap action.')
    603          self.setupAction(notification.dmd)
    604          data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager)
    605          self._sendTrap(notification, data, data['eventSummary'])
    606          if signal.clear:
    607              if 'clearEvt' in data and data['clearEvt'].severity is not None:
    608                  self._sendTrap(notification, data, data['clearEvt'])
    609
    610 -    def _sendTrap(self, notification, data, event):
    611          actor = getattr(event, "actor", None)
    612          details = event.details
    613          baseOID = '1.3.6.1.4.1.14296.1.100'
    614
    615          fields = {
    616             'uuid' :                         ( 1, event),
    617             'fingerprint' :                  ( 2, event),
    618             'element_identifier' :           ( 3, actor),
    619             'element_sub_identifier' :       ( 4, actor),
    620             'event_class' :                  ( 5, event),
    621             'event_key' :                    ( 6, event),
    622             'summary' :                      ( 7, event),
    623             'message' :                      ( 8, event),
    624             'severity' :                     ( 9, event),
    625             'status' :                       (10, event),
    626             'event_class_key' :              (11, event),
    627             'event_group' :                  (12, event),
    628             'state_change_time' :            (13, event),
    629             'first_seen_time' :              (14, event),
    630             'last_seen_time' :               (15, event),
    631             'count' :                        (16, event),
    632             'zenoss.device.production_state':(17, details),
    633             'agent':                         (20, event),
    634             'zenoss.device.device_class':    (21, details),
    635             'zenoss.device.location' :       (22, details),
    636             'zenoss.device.systems' :        (23, details),
    637             'zenoss.device.groups' :         (24, details),
    638             'zenoss.device.ip_address':      (25, details),
    639             'syslog_facility' :              (26, event),
    640             'syslog_priority' :              (27, event),
    641             'nt_event_code' :                (28, event),
    642             'current_user_name' :            (29, event),
    643             'cleared_by_event_uuid' :        (31, event),
    644             'zenoss.device.priority' :       (32, details),
    645             'event_class_mapping_uuid':      (33, event)
    646             }
    647
    648          eventDict = self.createEventDict(fields, event)
    649          self.processEventDict(eventDict, data, notification.dmd)
    650          varbinds = self.makeVarBinds(baseOID, fields, eventDict)
    651
    652          session = self._getSession(notification.content)
    653
    654          for v in varbinds:
    655              log.debug(v)
    656          session.sendTrap(baseOID + '.0.0.1', varbinds=varbinds)
    657
    658 -    def createEventDict(self, fields, event):
    659          """
    660          Create an event dictionary suitable for Python evaluation.
    661          """
    662          eventDict = {}
    663          for field, oidspec in fields.items():
    664              i, source = oidspec
    665              if source is event.details:
    666                  val = source.get(field, '')
    667              else:
    668                  val = getattr(source, field, '')
    669              eventDict[field] = val
    670          return eventDict
    671
    672 -    def processEventDict(self, eventDict, data, dmd):
    673          """
    674          Integration hook
    675          """
    676          pass
    677
    678 -    def makeVarBinds(self, baseOID, fields, eventDict):
    679          """
    680          Make the SNMP variable bindings in numeric order.
    681          """
    682          intValues = (9, 10, 26, 27)
    683          varbinds = []
    684          for field, oidspec in sorted(fields.items(), key=lambda x: x[1][0]):
    685              i, source = oidspec
    686              val = eventDict.get(field, '')
    687              if isinstance(val, (list, tuple, set)):
    688                  val = '|'.join(val)
    689
    690              # Create the binding
    691              oid = "%s.%d" % (baseOID, i)
    692              oidType = 's' if i not in intValues else 'i'
    693              # No matter what the OID data type, send in strings as that's what is expected
    694              val = str(val)
    695
    696              varbinds.append( (oid, oidType, val) )
    697          return varbinds
    698
    699 -    def updateContent(self, content=None, data=None):
    700          content['action_destination'] = data.get('action_destination')
    701          content['community'] = data.get('community')
    702          content['version'] = data.get('version')
    703          content['port'] = int(data.get('port'))
    704
    705 -    def _getSession(self, content):
    706          traphost = content['action_destination']
    707          port = content.get('port', 162)
    708          destination = '%s:%s' % (traphost, port)
    709
    710          if not traphost or port <= 0:
    711              log.error("%s: SNMP trap host information %s is incorrect ", destination)
    712              return None
    713
    714          community = content.get('community', 'public')
    715          version = content.get('version', 'v2c')
    716
    717          session = self._sessions.get(destination, None)
    718          if session is None:
    719              log.debug("Creating SNMP trap session to %s", destination)
    720
    721              # Test that the hostname and port are sane.
    722              try:
    723                  getaddrinfo(traphost, port)
    724              except Exception:
    725                  raise ActionExecutionException("The destination %s is not resolvable." % destination)
    726
    727              session = netsnmp.Session((
    728                  '-%s' % version,
    729                  '-c', community,
    730                  destination)
    731              )
    732              session.open()
    733              self._sessions[destination] = session
    734
    735          return session
    736