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_parent, aq_chain 
 25  from Products.PageTemplates.Expressions import getEngine 
 26  from Products.ZenUtils.ZenTales import talesCompile 
 27  from DateTime import DateTime 
 28  import os 
 29  import popen2 
 30  import fcntl 
 31  import select 
 32  import signal 
 33  import time 
 34  import cgi 
 35  import sys 
 36  import traceback 
 37   
 38  import logging 
 39  log = logging.getLogger("zen.Device") 
 40   
41 -class Commandable:
42 43 defaultTimeout = 60 # seconds 44 45 security = ClassSecurityInfo() 46 47 security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT, 'manage_addUserCommand')
48 - def manage_addUserCommand(self, newId=None, desc='', cmd='', REQUEST=None):
49 "Add a UserCommand to this device" 50 uc = None 51 if newId: 52 uc = UserCommand(newId) 53 self.userCommands._setObject(newId, uc) 54 if self.meta_type == 'Device': 55 self.setLastChange() 56 uc.description = desc 57 uc.command = cmd 58 if REQUEST: 59 if uc: 60 url = '%s/userCommands/%s?message=%s' % ( 61 self.getPrimaryUrlPath(), uc.id, 'Command Added') 62 REQUEST['RESPONSE'].redirect(url) 63 REQUEST['message'] = 'Command Added' 64 return self.callZenScreen(REQUEST) 65 return uc
66 67 68 security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT, 69 'manage_deleteUserCommand')
70 - def manage_deleteUserCommand(self, ids=(), REQUEST=None):
71 "Delete User Command(s) to this device" 72 import types 73 if type(ids) in types.StringTypes: 74 ids = [ids] 75 for id in ids: 76 self.userCommands._delObject(id) 77 if self.meta_type == 'Device': 78 self.setLastChange() 79 if REQUEST: 80 REQUEST['message'] = "Command(s) Deleted" 81 return self.callZenScreen(REQUEST)
82 83 security.declareProtected(ZEN_DEFINE_COMMANDS_EDIT, 84 'manage_editUserCommand')
85 - def manage_editUserCommand(self, commandId, REQUEST=None):
86 ''' Want to redirect back to management tab after a save 87 ''' 88 command = self.getUserCommand(commandId) 89 if command: 90 command.manage_changeProperties(**REQUEST.form) 91 return self.redirectToUserCommands(REQUEST)
92 93 94 security.declareProtected(ZEN_CHANGE_DEVICE, 'manage_doUserCommand')
95 - def manage_doUserCommand(self, commandId=None, REQUEST=None):
96 ''' Execute a UserCommand. If REQUEST then 97 wrap output in proper zenoss html page. 98 ''' 99 # This could be changed so that output is sent through a 100 # logger so that non web-based code can produce output. 101 # Not necessary for now. 102 command = self.getUserCommands(asDict=True).get(commandId,None) 103 if not command: 104 if REQUEST: 105 return self.redirectToUserCommands(REQUEST) 106 if REQUEST: 107 REQUEST['cmd'] = command 108 header, footer = self.commandOutputTemplate().split('OUTPUT_TOKEN') 109 REQUEST.RESPONSE.write(str(header)) 110 out = REQUEST.RESPONSE 111 else: 112 out = None 113 114 startTime = time.time() 115 numTargets = 0 116 for target in self.getUserCommandTargets(): 117 numTargets += 1 118 try: 119 self.write(out, '') 120 self.write(out, '==== %s ====' % target.id) 121 self.doCommandForTarget(command, target, out) 122 except: 123 self.write(out, 124 'exception while performing command for %s' % target.id) 125 self.write( 126 out, 'type: %s value: %s' % tuple(sys.exc_info()[:2])) 127 self.write(out, '') 128 self.write(out, '') 129 self.write(out, 'DONE in %s seconds on %s targets' % 130 (long(time.time() - startTime), numTargets)) 131 REQUEST.RESPONSE.write(footer)
132 133
134 - def doCommandForTarget(self, cmd, target, out):
135 ''' Execute the given UserCommand on the given target 136 ''' 137 compiled = self.compile(cmd, target) 138 child = popen2.Popen4(compiled) 139 flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL) 140 fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY) 141 timeout = getattr(target, 'zCommandCommandTimeout', self.defaultTimeout) 142 timeout = max(timeout, 1) 143 endtime = time.time() + timeout 144 self.write(out, '%s' % compiled) 145 self.write(out, '') 146 pollPeriod = 1 147 firstPass = True 148 while time.time() < endtime and (firstPass or child.poll() == -1): 149 firstPass = False 150 r, w, e = select.select([child.fromchild], [], [], pollPeriod) 151 if r: 152 t = child.fromchild.read() 153 # We are sometimes getting to this point without any data 154 # from child.fromchild. I don't think that should happen 155 # but the conditional below seems to be necessary. 156 if t: 157 self.write(out, t) 158 159 if child.poll() == -1: 160 self.write(out, 'Command timed out for %s' % target.id + 161 ' (timeout is %s seconds)' % timeout) 162 os.kill(child.pid, signal.SIGKILL)
163 164
165 - def compile(self, cmd, target):
166 ''' Evaluate command as a tales expression 167 ''' 168 exp = "string:"+ cmd.command 169 compiled = talesCompile(exp) 170 environ = target.getUserCommandEnvironment() 171 res = compiled(getEngine().getContext(environ)) 172 if isinstance(res, Exception): 173 raise res 174 return res
175 176 177 security.declareProtected(ZEN_VIEW, 'getUserCommandIds')
178 - def getUserCommandIds(self):
179 ''' Get the user command ids available in this context 180 ''' 181 commandIds = [] 182 mychain = self.getAqChainForUserCommands() 183 mychain.reverse() 184 for obj in mychain: 185 if getattr(aq_base(obj), 'userCommands', None): 186 for c in obj.userCommands(): 187 commandIds.append(c.id) 188 return commandIds
189 190 security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommands')
191 - def getUserCommands(self, asDict=False):
192 ''' Get the user commands available in this context 193 ''' 194 commands = {} 195 mychain = self.getAqChainForUserCommands() 196 mychain.reverse() 197 for obj in mychain: 198 if getattr(aq_base(obj), 'userCommands', None): 199 for c in obj.userCommands(): 200 commands[c.id] = c 201 def cmpCommands(a, b): 202 return cmp(a.getId(), b.getId())
203 if not asDict: 204 commands = commands.values() 205 commands.sort(cmpCommands) 206 return commands 207 208
209 - def getAqChainForUserCommands(self):
210 return aq_chain(self.primaryAq())
211 212
213 - def redirectToUserCommands(self, REQUEST, commandId=None):
214 ''' Redirect to the page which lists UserCommands 215 for this Commandable object. 216 ''' 217 url = self.getUrlForUserCommands() 218 if url: 219 return REQUEST.RESPONSE.redirect(url) 220 return self.callZenScreen(REQUEST)
221 222
223 - def getUrlForUserCommands(self):
224 ''' Return url for page which manages user commands 225 ''' 226 # This should be overridden by subclasses of Commandable 227 return self.getPrimaryUrlPath() 228 229 candidates = [a for a in self.factory_type_information[0]['actions'] 230 if a['name'] == 'Administration'] 231 if candidates: 232 action = candidates[0] 233 url = '%s/%s' % (self.getPrimaryUrlPath(), action['action']) 234 if commandId: 235 url += '?commandId=%s' % commandId 236 else: 237 url = '' 238 return url
239 240 security.declareProtected(ZEN_DEFINE_COMMANDS_VIEW, 'getUserCommand')
241 - def getUserCommand(self, commandId):
242 ''' Returns the command from the current context if it exists 243 ''' 244 return self.getUserCommands(asDict=True).get(commandId, None)
245 246
247 - def getUserCommandEnvironment(self):
248 ''' Get the environment that provides context for the tales 249 evaluation of a UserCommand. 250 ''' 251 # Overridden by Service and Device 252 return { 253 'target': self, 254 'here': self, 255 'nothing': None, 256 'now': DateTime() 257 }
258 259
260 - def getUserCommandTargets(self):
261 ''' Called by Commandable.doCommand() to ascertain objects on which 262 a UserCommand should be executed. 263 ''' 264 raise 'must be implemented by subclass'
265 266
267 - def write(self, out, lines):
268 ''' Output (maybe partial) result text from a UserCommand. 269 ''' 270 # Looks like firefox renders progressive output more smoothly 271 # if each line is stuck into a table row. 272 startLine = '<tr><td class="tablevalues">' 273 endLine = '</td></tr>\n' 274 if out: 275 if not isinstance(lines, list): 276 lines = [lines] 277 for l in lines: 278 if not isinstance(l, str): 279 l = str(l) 280 l = l.strip() 281 l = cgi.escape(l) 282 l = l.replace('\n', endLine + startLine) 283 out.write(startLine + l + endLine)
284 285 InitializeClass(Commandable) 286