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

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