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