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