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

# Source Code for Module Products.ZenModel.Commandable

      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  from Products.ZenMessaging.audit import audit
     12  from Products.ZenUtils.deprecated import deprecated
     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                  audit('UI.Command.Add', newId, action=uc.command, description=uc.description)
     64                  messaging.IMessageSender(self).sendToBrowser(
     65                      'Command Added',
     66                      'User command %s has been created.' % newId
     67                  )
     68                  screenName = REQUEST.get("editScreenName", "")
     69                  return REQUEST.RESPONSE.redirect(uc.getPrimaryUrlPath() +
     70                          '/%s' % screenName if screenName else '')
     71              return self.callZenScreen(REQUEST, True)
     72          return uc
     73
     74
     75      security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT,
     76          'manage_deleteUserCommand')
     77 -    def manage_deleteUserCommand(self, ids=(), REQUEST=None):
     78          "Delete User Command(s) to this device"
     79          if isinstance(ids, basestring):
     80              ids = [ids]
     81          for id in ids:
     82              self.userCommands._delObject(id)
     83          if self.meta_type == 'Device':
     84              self.setLastChange()
     85          if REQUEST:
     86              for id in ids:
     87                  audit('UI.Command.Delete', id)
     88              messaging.IMessageSender(self).sendToBrowser(
     89                  'Commands Deleted',
     90                  'User commands %s have been deleted.' % " ".join(ids)
     91              )
     92              return self.redirectToUserCommands(REQUEST)
     93
     94      security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT,
     95          'manage_editUserCommand')
     96 -    def manage_editUserCommand(self, commandId, REQUEST):
     97          ''' Want to redirect back to management tab after a save
     98          '''
     99          command = self.getUserCommand(commandId)
    100          if command:
    101              password = REQUEST.form.get('password', '')
    102              if not self.dmd.ZenUsers.authenticateCredentials(
    103                  self.dmd.ZenUsers.getUser().getId(), password):
    104                  messaging.IMessageSender(self).sendToBrowser(
    105                      'Password Error',
    106                      'Invalid or empty password.',
    107                      priority=messaging.WARNING
    108                  )
    109                  return REQUEST.RESPONSE.redirect(command.absolute_url_path())
    110              del REQUEST.form['password']
    111              command.manage_changeProperties(**REQUEST.form)
    112              audit('UI.Command.Edit', commandId, action=REQUEST.form.get('command',''))
    113          return self.redirectToUserCommands(REQUEST)
    114
    115
    116      security.declareProtected(ZEN_RUN_COMMANDS, 'manage_doUserCommand')
    117      @deprecated
    118 -    def manage_doUserCommand(self, commandId=None, REQUEST=None):
    119          ''' Execute a UserCommand. If REQUEST then
    120          wrap output in proper zenoss html page.
    121          '''
    122          # This could be changed so that output is sent through a
    123          # logger so that non web-based code can produce output.
    124          # Not necessary for now.
    125
    126          command = self.getUserCommands(asDict=True).get(commandId,None)
    127          if not command:
    128              if REQUEST:
    129                  return self.redirectToUserCommands(REQUEST)
    130          if REQUEST:
    131              REQUEST['cmd'] = command
    132              header, footer = self.commandOutputTemplate().split('OUTPUT_TOKEN')
    133              REQUEST.RESPONSE.write(str(header))
    134              out = REQUEST.RESPONSE
    135          else:
    136              out = None
    137
    138          startTime = time.time()
    139          numTargets = 0
    140          for target in self.getUserCommandTargets():
    141              numTargets += 1
    142              try:
    143                  self.write(out, '')
    144                  self.write(out, '==== %s ====' % target.id)
    145                  self.doCommandForTarget(command, target, out)
    146                  #untested, method call cannot be found
    147                  audit('UI.Command.Invoke', commandId, target=target)
    148              except:
    149                  self.write(out,
    150                      'exception while performing command for %s' % target.id)
    151                  self.write(
    152                      out, 'type: %s  value: %s' % tuple(sys.exc_info()[:2]))
    153              self.write(out, '')
    154          self.write(out, '')
    155          self.write(out, 'DONE in %s seconds on %s targets' %
    156                      (long(time.time() - startTime), numTargets))
    157          REQUEST.RESPONSE.write(str(footer))
    158
    159
    160 -    def doCommandForTarget(self, cmd, target, out):
    161          ''' Execute the given UserCommand on the given target
    162          '''
    163          compiled = self.compile(cmd, target)
    164          child = popen2.Popen4(compiled)
    165          flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL)
    166          fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY)
    167          timeout = getattr(target, 'zCommandCommandTimeout', self.defaultTimeout)
    168          timeout = max(timeout, 1)
    169          endtime = time.time() + timeout
    170          self.write(out, '%s' % compiled)
    171          self.write(out, '')
    172          pollPeriod = 1
    173          firstPass = True
    174          while time.time() < endtime and (firstPass or child.poll() == -1):
    175              firstPass = False
    176              r, w, e = select.select([child.fromchild], [], [], pollPeriod)
    177              if r:
    178                  t = child.fromchild.read()
    179                  # We are sometimes getting to this point without any data
    180                  # from child.fromchild.  I don't think that should happen
    181                  # but the conditional below seems to be necessary.
    182                  if t:
    183                      self.write(out, t)
    184
    185          if child.poll() == -1:
    186              self.write(out, 'Command timed out for %s' % target.id +
    187                              ' (timeout is %s seconds)' % timeout)
    188              os.kill(child.pid, signal.SIGKILL)
    189
    190
    191 -    def compile(self, cmd, target):
    192          ''' Evaluate command as a tales expression
    193          '''
    194          command = cmd.command
    195          # make sure we are targeting the right collector
    196          if not command.startswith("dcsh") and hasattr(target, "getPerformanceServerName"):
    197              collector = target.getPerformanceServer()
    198              # if there isn't a collector just run it locally
    199              if collector and hasattr(collector, 'isLocalHost') and not collector.isLocalHost():
    200                  command = 'dcsh --collector=${device/getPerformanceServerName} -n "%s"' % (command)
    201          exp = "string:"+ command
    202          compiled = talesCompile(exp)
    203          environ = target.getUserCommandEnvironment()
    204          res = compiled(getEngine().getContext(environ))
    205          if isinstance(res, Exception):
    206              raise res
    207          return res
    208
    209
    210      security.declareProtected(ZEN_VIEW, 'getUserCommandIds')
    211 -    def getUserCommandIds(self):
    212          ''' Get the user command ids available in this context
    213          '''
    214          commandIds = []
    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                      commandIds.append(c.id)
    221          return commandIds
    222
    223      security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommands')
    224 -    def getUserCommands(self, asDict=False):
    225          ''' Get the user commands available in this context
    226          '''
    227          commands = {}
    228          mychain = self.getAqChainForUserCommands()
    229          mychain.reverse()
    230          for obj in mychain:
    231              if getattr(aq_base(obj), 'userCommands', None):
    232                  for c in obj.userCommands():
    233                      commands[c.id] = c
    234          if not asDict:
    235              commands = sorted(commands.itervalues(), key=lambda cmd: cmd.getId())
    236          return commands
    237
    238
    239 -    def getAqChainForUserCommands(self):
    240          return aq_chain(self.primaryAq())
    241
    242
    243 -    def redirectToUserCommands(self, REQUEST, commandId=None):
    244          ''' Redirect to the page which lists UserCommands
    245          for this Commandable object.
    246          '''
    247          unused(commandId)
    248          url = self.getUrlForUserCommands()
    249          if url:
    250              return REQUEST.RESPONSE.redirect(url)
    251          return self.callZenScreen(REQUEST)
    252
    253
    254 -    def getUrlForUserCommands(self):
    255          ''' Return url for page which manages user commands
    256          '''
    257          # This should be overridden by subclasses of Commandable
    258          return self.getPrimaryUrlPath()
    259
    260      security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommand')
    261 -    def getUserCommand(self, commandId):
    262          ''' Returns the command from the current context if it exists
    263          '''
    264          return self.getUserCommands(asDict=True).get(commandId, None)
    265
    266
    267 -    def getUserCommandEnvironment(self):
    268          ''' Get the environment that provides context for the tales
    269          evaluation of a UserCommand.
    270          '''
    271          # Overridden by Service and Device
    272          return {
    273                  'target': self,
    274                  'here': self,
    275                  'nothing': None,
    276                  'now': DateTime()
    277                  }
    278
    279
    280 -    def getUserCommandTargets(self):
    281          ''' Called by Commandable.doCommand() to ascertain objects on which
    282          a UserCommand should be executed.
    283          '''
    284          raise NotImplemented
    285
    286
    287 -    def write(self, out, lines):
    288          ''' Output (maybe partial) result text from a UserCommand.
    289          '''
    290          # Looks like firefox renders progressive output more smoothly
    291          # if each line is stuck into a table row.
    292
    293          # I doubt the above statement, as tested on Firefox 3
    294          # this only generates a larger DOM object and does nothing
    295          # for smoothness. It actually slows things down for large command
    296          # output. Should maybe retested especially now that we are
    297          # using CSS pre
    298          startLine = '<tr><td class="commandoutput">'
    299          endLine = '\n</td></tr>\n'
    300          if out:
    301              if not isinstance(lines, list):
    302                  lines = [lines]
    303              for l in lines:
    304                  if not isinstance(l, str):
    305                      l = str(l)
    306                  l = l.strip()
    307                  l = cgi.escape(l)
    308                  l = l.replace('\n', endLine + startLine)
    309                  out.write(startLine + l + endLine)
    310
    311
    312
    313  InitializeClass(Commandable)
    314