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

# Source Code for Module Products.ZenModel.Commandable

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  __doc__="""Commandable
     15
     16  Mixin class for classes that need a relationship back from UserCommand.
     17
     18  """
     19
     20  from Globals import InitializeClass
     21  from AccessControl import ClassSecurityInfo
     22  from ZenossSecurity import *
     23  from UserCommand import UserCommand
     24  from Acquisition import aq_base, aq_chain
     25  from Products.PageTemplates.Expressions import getEngine
     26  from Products.ZenUtils.ZenTales import talesCompile
     27  from Products.ZenUtils.Utils import unused
     28  from Products.ZenWidgets import messaging
     29  from DateTime import DateTime
     30  import os
     31  import popen2
     32  import fcntl
     33  import select
     34  import signal
     35  import time
     36  import cgi
     37  import sys
     38
     39  import logging
     40  log = logging.getLogger("zen.Device")
     41
     42 -class Commandable:
     43
     44      defaultTimeout = 60 # seconds
     45
     46      security = ClassSecurityInfo()
     47
     48      security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT, 'manage_addUserCommand')
     49 -    def manage_addUserCommand(self, newId=None, desc='', cmd='', REQUEST=None):
     50          "Add a UserCommand to this device"
     51          unused(desc, cmd)
     52          uc = None
     53          if newId:
     54              uc = UserCommand(newId)
     55              self.userCommands._setObject(newId, uc)
     56              uc = self.userCommands._getOb(newId)
     57              if self.meta_type == 'Device':
     58                  self.setLastChange()
     59              uc.description = desc
     60              uc.command = cmd
     61          if REQUEST:
     62              if uc:
     63                  messaging.IMessageSender(self).sendToBrowser(
     64                      'Command Added',
     65                      'User command %s has been created.' % newId
     66                  )
     67                  REQUEST['RESPONSE'].redirect(uc.getPrimaryUrlPath())
     68              return self.callZenScreen(REQUEST)
     69          return uc
     70
     71
     72      security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT,
     73          'manage_deleteUserCommand')
     74 -    def manage_deleteUserCommand(self, ids=(), REQUEST=None):
     75          "Delete User Command(s) to this device"
     76          import types
     77          if type(ids) in types.StringTypes:
     78              ids = [ids]
     79          for id in ids:
     80              self.userCommands._delObject(id)
     81          if self.meta_type == 'Device':
     82              self.setLastChange()
     83          if REQUEST:
     84              messaging.IMessageSender(self).sendToBrowser(
     85                  'Commands Deleted',
     86                  'User commands %s have been deleted.' % " ".join(ids)
     87              )
     88              return self.callZenScreen(REQUEST)
     89
     90      security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT,
     91          'manage_editUserCommand')
     92 -    def manage_editUserCommand(self, commandId, REQUEST=None):
     93          ''' Want to redirect back to management tab after a save
     94          '''
     95          command = self.getUserCommand(commandId)
     96          if command:
     97              password = REQUEST.form.get('password', '')
     98              userManager = self.acl_users.userManager
     99              if not userManager.authenticateCredentials({
    100                  'login':self.dmd.ZenUsers.getUser().getId(),
    101                  'password':password
    102              }):
    103                  messaging.IMessageSender(self).sendToBrowser(
    104                      'Password Error',
    105                      'Invalid or empty password.',
    106                      priority=messaging.WARNING
    107                  )
    108                  return REQUEST.RESPONSE.redirect(command.absolute_url_path())
    109              del REQUEST.form['password']
    110              command.manage_changeProperties(**REQUEST.form)
    111          return self.redirectToUserCommands(REQUEST)
    112
    113
    114      security.declareProtected(ZEN_RUN_COMMANDS, 'manage_doUserCommand')
    115 -    def manage_doUserCommand(self, commandId=None, REQUEST=None):
    116          ''' Execute a UserCommand. If REQUEST then
    117          wrap output in proper zenoss html page.
    118          '''
    119          # This could be changed so that output is sent through a
    120          # logger so that non web-based code can produce output.
    121          # Not necessary for now.
    122          command = self.getUserCommands(asDict=True).get(commandId,None)
    123          if not command:
    124              if REQUEST:
    125                  return self.redirectToUserCommands(REQUEST)
    126          if REQUEST:
    127              REQUEST['cmd'] = command
    128              header, footer = self.commandOutputTemplate().split('OUTPUT_TOKEN')
    129              REQUEST.RESPONSE.write(str(header))
    130              out = REQUEST.RESPONSE
    131          else:
    132              out = None
    133
    134          startTime = time.time()
    135          numTargets = 0
    136          for target in self.getUserCommandTargets():
    137              numTargets += 1
    138              try:
    139                  self.write(out, '')
    140                  self.write(out, '==== %s ====' % target.id)
    141                  self.doCommandForTarget(command, target, out)
    142              except:
    143                  self.write(out,
    144                      'exception while performing command for %s' % target.id)
    145                  self.write(
    146                      out, 'type: %s  value: %s' % tuple(sys.exc_info()[:2]))
    147              self.write(out, '')
    148          self.write(out, '')
    149          self.write(out, 'DONE in %s seconds on %s targets' %
    150                      (long(time.time() - startTime), numTargets))
    151          REQUEST.RESPONSE.write(footer)
    152
    153
    154 -    def doCommandForTarget(self, cmd, target, out):
    155          ''' Execute the given UserCommand on the given target
    156          '''
    157          compiled = self.compile(cmd, target)
    158          child = popen2.Popen4(compiled)
    159          flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL)
    160          fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY)
    161          timeout = getattr(target, 'zCommandCommandTimeout', self.defaultTimeout)
    162          timeout = max(timeout, 1)
    163          endtime = time.time() + timeout
    164          self.write(out, '%s' % compiled)
    165          self.write(out, '')
    166          pollPeriod = 1
    167          firstPass = True
    168          while time.time() < endtime and (firstPass or child.poll() == -1):
    169              firstPass = False
    170              r, w, e = select.select([child.fromchild], [], [], pollPeriod)
    171              if r:
    172                  t = child.fromchild.read()
    173                  # We are sometimes getting to this point without any data
    174                  # from child.fromchild.  I don't think that should happen
    175                  # but the conditional below seems to be necessary.
    176                  if t:
    177                      self.write(out, t)
    178
    179          if child.poll() == -1:
    180              self.write(out, 'Command timed out for %s' % target.id +
    181                              ' (timeout is %s seconds)' % timeout)
    182              os.kill(child.pid, signal.SIGKILL)
    183
    184
    185 -    def compile(self, cmd, target):
    186          ''' Evaluate command as a tales expression
    187          '''
    188          exp = "string:"+ cmd.command
    189          compiled = talesCompile(exp)
    190          environ = target.getUserCommandEnvironment()
    191          res = compiled(getEngine().getContext(environ))
    192          if isinstance(res, Exception):
    193              raise res
    194          return res
    195
    196
    197      security.declareProtected(ZEN_VIEW, 'getUserCommandIds')
    198 -    def getUserCommandIds(self):
    199          ''' Get the user command ids available in this context
    200          '''
    201          commandIds = []
    202          mychain = self.getAqChainForUserCommands()
    203          mychain.reverse()
    204          for obj in mychain:
    205              if getattr(aq_base(obj), 'userCommands', None):
    206                  for c in obj.userCommands():
    207                      commandIds.append(c.id)
    208          return commandIds
    209
    210      security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommands')
    211 -    def getUserCommands(self, asDict=False):
    212          ''' Get the user commands available in this context
    213          '''
    214          commands = {}
    215          mychain = self.getAqChainForUserCommands()
    216          mychain.reverse()
    217          for obj in mychain:
    218              if getattr(aq_base(obj), 'userCommands', None):
    219                  for c in obj.userCommands():
    220                      commands[c.id] = c
    221          def cmpCommands(a, b):
    222              return cmp(a.getId(), b.getId())
    223          if not asDict:
    224              commands = commands.values()
    225              commands.sort(cmpCommands)
    226          return commands
    227
    228
    229 -    def getAqChainForUserCommands(self):
    230          return aq_chain(self.primaryAq())
    231
    232
    233 -    def redirectToUserCommands(self, REQUEST, commandId=None):
    234          ''' Redirect to the page which lists UserCommands
    235          for this Commandable object.
    236          '''
    237          unused(commandId)
    238          url = self.getUrlForUserCommands()
    239          if url:
    240              return REQUEST.RESPONSE.redirect(url)
    241          return self.callZenScreen(REQUEST)
    242
    243
    244 -    def getUrlForUserCommands(self):
    245          ''' Return url for page which manages user commands
    246          '''
    247          # This should be overridden by subclasses of Commandable
    248          return self.getPrimaryUrlPath()
    249
    250      security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommand')
    251 -    def getUserCommand(self, commandId):
    252          ''' Returns the command from the current context if it exists
    253          '''
    254          return self.getUserCommands(asDict=True).get(commandId, None)
    255
    256
    257 -    def getUserCommandEnvironment(self):
    258          ''' Get the environment that provides context for the tales
    259          evaluation of a UserCommand.
    260          '''
    261          # Overridden by Service and Device
    262          return {
    263                  'target': self,
    264                  'here': self,
    265                  'nothing': None,
    266                  'now': DateTime()
    267                  }
    268
    269
    270 -    def getUserCommandTargets(self):
    271          ''' Called by Commandable.doCommand() to ascertain objects on which
    272          a UserCommand should be executed.
    273          '''
    274          raise NotImplemented
    275
    276
    277 -    def write(self, out, lines):
    278          ''' Output (maybe partial) result text from a UserCommand.
    279          '''
    280          # Looks like firefox renders progressive output more smoothly
    281          # if each line is stuck into a table row.
    282
    283          # I doubt the above statement, as tested on Firefox 3
    284          # this only generates a larger DOM object and does nothing
    285          # for smoothness. It actually slows things down for large command
    286          # output. Should maybe retested especially now that we are
    287          # using CSS pre
    288          startLine = '<tr><td class="commandoutput">'
    289          endLine = '\n</td></tr>\n'
    290          if out:
    291              if not isinstance(lines, list):
    292                  lines = [lines]
    293              for l in lines:
    294                  if not isinstance(l, str):
    295                      l = str(l)
    296                  l = l.strip()
    297                  l = cgi.escape(l)
    298                  l = l.replace('\n', endLine + startLine)
    299                  out.write(startLine + l + endLine)
    300
    301
    302
    303  InitializeClass(Commandable)
    304