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 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 import types 79 if type(ids) in types.StringTypes: 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 messaging.IMessageSender(self).sendToBrowser( 87 'Commands Deleted', 88 'User commands %s have been deleted.' % " ".join(ids) 89 ) 90 return self.redirectToUserCommands(REQUEST)
91 92 security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT, 93 'manage_editUserCommand')
94 - def manage_editUserCommand(self, commandId, REQUEST=None):
95 ''' Want to redirect back to management tab after a save 96 ''' 97 command = self.getUserCommand(commandId) 98 if command: 99 password = REQUEST.form.get('password', '') 100 userManager = self.acl_users.userManager 101 if not self.dmd.ZenUsers.authenticateCredentials( 102 self.dmd.ZenUsers.getUser().getId(), password): 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