Package Products :: Package ZenModel :: Module ZenossInfo
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenModel.ZenossInfo

  1  ############################################################################## 
  2  #  
  3  # Copyright (C) Zenoss, Inc. 2007, 2009, all rights reserved. 
  4  #  
  5  # This content is made available according to terms specified in 
  6  # License.zenoss under the directory where your Zenoss product is installed. 
  7  #  
  8  ############################################################################## 
  9   
 10   
 11  import os 
 12  import os.path 
 13  import sys 
 14  import re 
 15  from urllib import unquote 
 16  from subprocess import Popen, PIPE, call 
 17  from xml.dom.minidom import parse 
 18  import shutil 
 19  import traceback 
 20  import logging 
 21  import commands 
 22  log = logging.getLogger("zen.ZenossInfo") 
 23   
 24  from Globals import InitializeClass 
 25  from OFS.SimpleItem import SimpleItem 
 26  from AccessControl import ClassSecurityInfo 
 27   
 28  from Products.ZenModel.ZenossSecurity import * 
 29  from Products.ZenModel.ZenModelItem import ZenModelItem 
 30  from Products.ZenCallHome.transport.methods.versioncheck import version_check 
 31  from Products.ZenUtils import Time 
 32  from Products.ZenUtils.Version import * 
 33  from Products.ZenUtils.Utils import zenPath, binPath, isZenBinFile 
 34  from Products.ZenWidgets import messaging 
 35  from Products.ZenMessaging.audit import audit 
 36   
37 -def manage_addZenossInfo(context, id='About', REQUEST=None):
38 """ 39 Provide an instance of ZenossInfo for the portal. 40 """ 41 about = ZenossInfo(id) 42 context._setObject(id, about) 43 if REQUEST is not None: 44 REQUEST.RESPONSE.redirect(context.absolute_url() +'/manage_main')
45
46 -class ZenossInfo(ZenModelItem, SimpleItem):
47 48 portal_type = meta_type = 'ZenossInfo' 49 50 security = ClassSecurityInfo() 51 52 _properties = ( 53 {'id':'id', 'type':'string'}, 54 {'id':'title', 'type':'string'}, 55 ) 56 57 factory_type_information = ( 58 { 59 'immediate_view' : 'zenossInfo', 60 'actions' : 61 ( 62 { 'id' : 'settings' 63 , 'name' : 'Settings' 64 , 'action' : '../dmd/editSettings' 65 , 'permissions' : ( "Manage DMD", ) 66 }, 67 { 'id' : 'manage' 68 , 'name' : 'Commands' 69 , 'action' : '../dmd/dataRootManage' 70 , 'permissions' : ('Manage DMD',) 71 }, 72 { 'id' : 'users' 73 , 'name' : 'Users' 74 , 'action' : '../dmd/ZenUsers/manageUserFolder' 75 , 'permissions' : ( 'Manage DMD', ) 76 }, 77 { 'id' : 'packs' 78 , 'name' : 'ZenPacks' 79 , 'action' : '../dmd/ZenPackManager/viewZenPacks' 80 , 'permissions' : ( "Manage DMD", ) 81 }, 82 { 'id' : 'portlets' 83 , 'name' : 'Portlets' 84 , 'action' : '../dmd/editPortletPerms' 85 , 'permissions' : ( "Manage DMD", ) 86 }, 87 { 'id' : 'daemons' 88 , 'name' : 'Daemons' 89 , 'action' : 'zenossInfo' 90 , 'permissions' : ( "Manage DMD", ) 91 }, 92 { 'id' : 'versions' 93 , 'name' : 'Versions' 94 , 'action' : 'zenossVersions' 95 , 'permissions' : ( "Manage DMD", ) 96 }, 97 { 'id' : 'backups' 98 , 'name' : 'Backups' 99 , 'action' : '../dmd/backupInfo' 100 , 'permissions' : ( "Manage DMD", ) 101 }, 102 { 'id' : 'eventConfig' 103 , 'name' : 'Events' 104 , 'action' : 'eventConfig' 105 , 'permissions' : ( "Manage DMD", ) 106 }, 107 { 'id' : 'userInterfaceConfig' 108 , 'name' : 'User Interface' 109 , 'action' : '../dmd/userInterfaceConfig' 110 , 'permissions' : ( "Manage DMD", ) 111 }, 112 ) 113 }, 114 ) 115 116
117 - def titleOrId(self):
118 return self.title or self.id
119
120 - def breadCrumbs(self, target='dmd'):
121 return []
122 123 security.declarePublic('getZenossVersion')
124 - def getZenossVersion(self):
125 from Products.ZenModel.ZVersion import VERSION 126 return Version.parse("Zenoss %s %s" % 127 (VERSION, self.getZenossRevision()))
128 129 130 security.declarePublic('getZenossVersionShort')
131 - def getZenossVersionShort(self):
132 return self.getZenossVersion().short()
133 134
135 - def getOSVersion(self):
136 """ 137 This function returns a Version-ready tuple. For use with the Version 138 object, use extended call syntax: 139 140 v = Version(*getOSVersion()) 141 v.full() 142 """ 143 if os.name == 'posix': 144 sysname, nodename, version, build, arch = os.uname() 145 name = "%s (%s)" % (sysname, arch) 146 major, minor, micro = getVersionTupleFromString(version) 147 comment = ' '.join(os.uname()) 148 elif os.name == 'nt': 149 from win32api import GetVersionEx 150 major, minor, micro, platformID, additional = GetVersionEx() 151 name = 'Windows %s (%s)' % (os.name.upper(), additional) 152 comment = '' 153 else: 154 raise VersionNotSupported 155 return Version(name, major, minor, micro, 0, comment)
156 157
158 - def getPythonVersion(self):
159 """ 160 This function returns a Version-ready tuple. For use with the Version 161 object, use extended call syntax: 162 163 v = Version(*getPythonVersion()) 164 v.full() 165 """ 166 name = 'Python' 167 major, minor, micro, releaselevel, serial = sys.version_info 168 return Version(name, major, minor, micro)
169
170 - def getMySQLVersion(self):
171 """ 172 This function returns a Version-ready tuple. For use with the Version 173 object, use extended call syntax: 174 175 v = Version(*getMySQLVersion()) 176 v.full() 177 178 The regex was tested against the following output strings: 179 mysql Ver 14.12 Distrib 5.0.24, for apple-darwin8.5.1 (i686) using readline 5.0 180 mysql Ver 12.22 Distrib 4.0.24, for pc-linux-gnu (i486) 181 mysql Ver 14.12 Distrib 5.0.24a, for Win32 (ia32) 182 /usr/local/zenoss/mysql/bin/mysql.bin Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using readline 5.0 183 """ 184 cmd = 'mysql --version' 185 fd = os.popen(cmd) 186 output = fd.readlines() 187 version = "0" 188 if fd.close() is None and len(output) > 0: 189 output = output[0].strip() 190 regexString = '.*(mysql).*Ver [0-9]{2}\.[0-9]{2} ' 191 regexString += 'Distrib ([0-9]+.[0-9]+.[0-9]+)(.*), for (.*\(.*\))' 192 regex = re.match(regexString, output) 193 if regex: 194 name, version, release, info = regex.groups() 195 comment = 'Ver %s' % version 196 # the name returned in the output is all lower case, so we'll make our own 197 if os.environ.get("USE_ZENDS", None): 198 name = 'ZenDS' 199 else: 200 name = 'MySQL' 201 major, minor, micro = getVersionTupleFromString(version) 202 return Version(name, major, minor, micro, 0, comment)
203 204
205 - def getRRDToolVersion(self):
206 """ 207 This function returns a Version-ready tuple. For use with the Version 208 object, use extended call syntax: 209 210 v = Version(*getRRDToolVersion()) 211 v.full() 212 """ 213 cmd = binPath('rrdtool') 214 if not os.path.exists(cmd): 215 cmd = 'rrdtool' 216 fd = os.popen(cmd) 217 output = fd.readlines()[0].strip() 218 fd.close() 219 name, version = output.split()[:2] 220 major, minor, micro = getVersionTupleFromString(version) 221 return Version(name, major, minor, micro)
222 223
224 - def getTwistedVersion(self):
225 """ 226 This function returns a Version-ready tuple. For use with the Version 227 object, use extended call syntax: 228 229 v = Version(*getTwistedVersion()) 230 v.full() 231 """ 232 from twisted._version import version as v 233 234 return Version('Twisted', v.major, v.minor, v.micro)
235 236
237 - def getZopeVersion(self):
238 """ 239 This function returns a Version-ready tuple. For use with the Version 240 object, use extended call syntax: 241 242 v = Version(*getZopeVersion()) 243 v.full() 244 """ 245 from App import version_txt as version 246 247 name = 'Zope' 248 major, minor, micro, status, release = version.getZopeVersion() 249 return Version(name, major, minor, micro)
250 251
252 - def getZenossRevision(self):
253 """ 254 Determine the Zenoss version number 255 256 @return: version number or '' 257 @rtype: string 258 """ 259 try: 260 products = zenPath("Products") 261 cmd = "svn info '%s' 2>/dev/null | awk '/Revision/ {print $2}'" % products 262 fd = os.popen(cmd) 263 return fd.readlines()[0].strip() 264 except: 265 return ''
266 267
268 - def getNetSnmpVersion(self):
269 from pynetsnmp.netsnmp import lib 270 return Version.parse('NetSnmp %s ' % lib.netsnmp_get_version())
271 272
273 - def getPyNetSnmpVersion(self):
274 from pynetsnmp.version import VERSION 275 return Version.parse('PyNetSnmp %s ' % VERSION)
276 277
278 - def getWmiVersion(self):
279 from pysamba.version import VERSION 280 return Version.parse('Wmi %s ' % VERSION)
281
282 - def getRabbitMQVersion(self):
283 from Products.ZenUtils.qverify import ZenAmqp 284 return Version.parse("RabbitMQ %s" % ZenAmqp().getVersion())
285
286 - def getErlangVersion(self):
287 retVal, output = commands.getstatusoutput('erl -noshell +V') 288 version = None 289 290 if not retVal: 291 try: 292 version = re.findall(r'version (\S+)', output)[0] 293 except Exception: 294 pass 295 296 return Version.parse("Erlang %s" % version)
297
298 - def getAllVersions(self):
299 """ 300 Return a list of version numbers for currently tracked component 301 software. 302 """ 303 versions = ( 304 {'header': 'Zenoss', 'data': self.getZenossVersion().full(), 305 'href': "http://www.zenoss.com" }, 306 {'header': 'OS', 'data': self.getOSVersion().full(), 307 'href': "http://www.tldp.org" }, 308 {'header': 'Zope', 'data': self.getZopeVersion().full(), 309 'href': "http://www.zope.org" }, 310 {'header': 'Python', 'data': self.getPythonVersion().full(), 311 'href': "http://www.python.org" }, 312 {'header': 'Database', 'data': self.getMySQLVersion().full(), 313 'href': "http://www.mysql.com" }, 314 {'header': 'RRD', 'data': self.getRRDToolVersion().full(), 315 'href': "http://oss.oetiker.ch/rrdtool" }, 316 {'header': 'Twisted', 'data': self.getTwistedVersion().full(), 317 'href': "http:///twistedmatrix.com/trac" }, 318 {'header': 'RabbitMQ', 'data': self.getRabbitMQVersion().full(), 319 'href': 'http://www.rabbitmq.com/'}, 320 {'header': 'Erlang', 'data': self.getErlangVersion().full(), 321 'href':'http://www.erlang.org/' }, 322 ) 323 try: 324 versions += ( 325 {'header': 'NetSnmp', 'data': self.getNetSnmpVersion().full(), 326 'href': "http://net-snmp.sourceforge.net" }, 327 ) 328 except: 329 pass 330 try: 331 versions += ( 332 {'header': 'PyNetSnmp', 'data': self.getPyNetSnmpVersion().full(), 333 'href': "http://www.zenoss.com" }, 334 ) 335 except: 336 pass 337 try: 338 versions += ( 339 {'header': 'WMI', 'data': self.getWmiVersion().full(), 340 'href': "http://www.zenoss.com" }, 341 ) 342 except: 343 pass 344 return versions
345 346 security.declareProtected('View','getAllVersions') 347 348
349 - def getAllUptimes(self):
350 """ 351 Return a list of daemons with their uptimes. 352 """ 353 app = self.getPhysicalRoot() 354 uptimes = [] 355 zope = { 356 'header': 'Zope', 357 'data': app.Control_Panel.process_time(), 358 } 359 uptimes.append(zope) 360 return uptimes
361 security.declareProtected('View','getAllUptimes') 362 363 364 365 daemon_tooltips= { 366 "zeoctl": "Zope Enterprise Objects server (shares database between Zope instances)", 367 "zopectl": "The Zope open source web application server", 368 "zenhub": "Broker between the data layer and the collection daemons", 369 "zenping": "ICMP ping status monitoring", 370 "zensyslog": "Collection of and classification of syslog events", 371 "zenstatus": "Active TCP connection testing of remote daemons", 372 "zenactiond": "Receives signals from processed events to execute notifications.", 373 "zentrap": "Receives SNMP traps and turns them into events", 374 "zenmodeler": "Configuration collection and configuration", 375 "zenperfsnmp": "High performance asynchronous SNMP performance collection", 376 "zencommand": "Runs plug-ins on the local box or on remote boxes through SSH", 377 "zenprocess": "Process monitoring using SNMP host resources MIB", 378 "zendisc": "Discover the network topology to find active IPs and devices", 379 "zenrrdcached": "Controls the write cache for performance data", 380 "zenmail": "Listen for e-mail and convert messages to Zenoss events", 381 "zenpop3": "Connect via pop3 to an e-mail server and convert messages to Zenoss events", 382 } 383 384
385 - def getZenossDaemonStates(self):
386 """ 387 Return a data structures representing the states of the supported 388 Zenoss daemons. 389 """ 390 states = [] 391 activeButtons = {'button1': 'Restart', 'button2': 'Stop', 'button2state': True} 392 inactiveButtons = {'button1': 'Start', 'button2': 'Stop', 'button2state': False} 393 alwaysOnButtons = {'button1': 'Restart', 'button2': 'Stop', 'button2state': False} 394 395 for daemon in self._getDaemonList(): 396 pid = self._getDaemonPID(daemon) 397 if pid: 398 if daemon == 'zopectl' or daemon == 'zenwebserver': 399 buttons = alwaysOnButtons 400 else: 401 buttons = activeButtons 402 msg = 'Up' 403 color = '#0F0' 404 else: 405 buttons = inactiveButtons 406 msg = 'Down' 407 color = '#F00' 408 409 if daemon in self.daemon_tooltips: 410 tooltip= self.daemon_tooltips[ daemon ] 411 else: 412 tooltip= '' 413 414 states.append({ 415 'name': daemon, 416 'pid': pid, 417 'msg': msg, 418 'tooltip': tooltip, 419 'color': color, 420 'buttons': buttons}) 421 422 return states
423 424
425 - def _pidRunning(self, pid):
426 try: 427 os.kill(pid, 0) 428 return pid 429 except OSError, ex: 430 import errno 431 errnum, msg = ex.args 432 if errnum == errno.EPERM: 433 return pid
434 435
436 - def _getDaemonPID(self, name):
437 """ 438 For a given daemon name, return its PID from a .pid file. 439 """ 440 if name == 'zenwebserver': 441 name = 'nginx' 442 elif name == 'zopectl': 443 name = 'Z2' 444 elif name == 'zeoctl': 445 name = 'ZEO' 446 elif '_' in name: 447 collector, daemon = name.split('_', 1) 448 name = '%s-%s' % (daemon, collector) 449 else: 450 name = "%s-localhost" % name 451 pidFile = zenPath('var', '%s.pid' % name) 452 if os.path.exists(pidFile): 453 pid = open(pidFile).read() 454 try: 455 pid = int(pid) 456 except ValueError: 457 return None 458 return self._pidRunning(int(pid)) 459 else: 460 pid = None 461 return pid
462 463
464 - def _getDaemonList(self):
465 """ 466 Get the list of supported Zenoss daemons. 467 """ 468 masterScript = binPath('zenoss') 469 daemons = [] 470 for line in os.popen("%s list" % masterScript).readlines(): 471 if 'zenrrdcache' not in line: 472 daemons.append(line.strip()) 473 return daemons
474 475
476 - def getZenossDaemonConfigs(self):
477 """ 478 Return a data structures representing the config infor for the 479 supported Zenoss daemons. 480 """ 481 return [ dict(name=x) for x in self._getDaemonList() ]
482
483 - def _readLogFile(self, filename, maxBytes):
484 fh = open(filename) 485 try: 486 size = os.path.getsize(filename) 487 if size > maxBytes: 488 fh.seek(-maxBytes, 2) 489 # the first line could be a partial line, so skip it 490 fh.readline() 491 return fh.read() 492 finally: 493 fh.close()
494
495 - def _getLogPath(self, daemon):
496 """ 497 Returns the path the log file for the daemon this is monkey-patched 498 in the distributed collector zenpack to support the localhost 499 subdirectory. 500 """ 501 if not isZenBinFile(daemon): 502 raise ValueError("%r is not a valid daemon name" % daemon) 503 return zenPath('log', "%s.log" % daemon)
504
505 - def getLogData(self, daemon, kb=500):
506 """ 507 Get the last kb kilobytes of a daemon's log file contents. 508 """ 509 if not isZenBinFile(daemon): 510 messaging.IMessageSender(self).sendToBrowser( 511 'Internal Error', 512 '%s is not a valid daemon name' % daemon, 513 priority=messaging.WARNING 514 ) 515 return ' ' 516 517 maxBytes = 1024 * int(kb) 518 if daemon in ('zopectl', 'zenwebserver'): 519 daemon = 'event' 520 elif daemon == 'zeoctl': 521 daemon = 'zeo' 522 if daemon in ('zopectl', 'zenwebserver'): 523 daemon = 'event' 524 elif daemon == 'zeoctl': 525 daemon = 'zeo' 526 filename = self._getLogPath(daemon) 527 # if there is no data read, we don't want to return something that can 528 # be interptreted as "None", so we make the default a single white 529 # space 530 data = ' ' 531 try: 532 data = self._readLogFile(filename, maxBytes) or ' ' 533 except Exception, ex: 534 data = "Error reading %s log file '%s':\n%s" % ( 535 daemon, filename, str(ex)) 536 return data
537 538
539 - def _getConfigFilename(self, daemon):
540 if daemon in ('zopectl', 'zenwebserver'): 541 daemon = 'zope' 542 elif daemon == 'zeoctl': 543 daemon = 'zeo' 544 return zenPath('etc', "%s.conf" % daemon)
545
546 - def _readConfigFile(self, filename):
547 fh = open(filename) 548 try: 549 return fh.read() 550 finally: 551 fh.close()
552
553 - def getConfigData(self, daemon):
554 """ 555 Return the contents of the daemon's config file. 556 """ 557 if not isZenBinFile(daemon): 558 return 'The daemon name is invalid.' 559 560 filename = self._getConfigFilename(daemon) 561 # if there is no data read, we don't want to return something that can 562 # be interptreted as "None", so we make the default a single white 563 # space 564 data = ' ' 565 try: 566 data = self._readConfigFile(filename) or ' ' 567 except IOError: 568 data = 'Unable to read config file' 569 return data
570 571
572 - def manage_saveConfigData(self, REQUEST):
573 """ 574 Save config data from REQUEST to the daemon's config file. 575 """ 576 daemon = REQUEST.form.get('daemon') 577 if not isZenBinFile(daemon): 578 messaging.IMessageSender(self).sendToBrowser( 579 'Internal Error', 580 'The daemon name %r is invalid' % daemon, 581 priority=messaging.WARNING 582 ) 583 return self.callZenScreen(REQUEST, redirect=True) 584 585 filename = self._getConfigFilename(daemon) 586 try: 587 fh = open(filename, 'w+') 588 data = REQUEST.form.get('data') 589 fh.write(data) 590 audit('UI.Daemon.EditConfig', daemon) 591 finally: 592 fh.close() 593 return self.callZenScreen(REQUEST, redirect=True)
594
595 - def parseconfig(self, filename=""):
596 """ 597 From the given configuration file construct a configuration object 598 """ 599 configs = {} 600 601 config_file = open(filename) 602 try: 603 for line in config_file: 604 line = line.strip() 605 if line.startswith('#'): continue 606 if line == '': continue 607 608 try: 609 key, value = line.split(None, 1) 610 except ValueError: 611 # Ignore errors 612 continue 613 configs[key] = value 614 finally: 615 config_file.close() 616 617 return configs
618
619 - def show_daemon_xml_configs(self, daemon, REQUEST=None ):
620 """ 621 Display the daemon configuration options in an XML format. 622 Merges the defaults with options in the config file. 623 """ 624 # Sanity check 625 if not daemon or daemon == '': 626 messaging.IMessageSender(self).sendToBrowser( 627 'Internal Error', 628 'Called without a daemon name', 629 priority=messaging.WARNING 630 ) 631 return [] 632 633 if daemon in [ 'zeoctl', 'zopectl' ]: 634 return [] 635 636 if not isZenBinFile(daemon): 637 messaging.IMessageSender(self).sendToBrowser( 638 'Internal Error', 639 '%s is not a valid daemon name' % daemon, 640 priority=messaging.WARNING 641 ) 642 return [] 643 644 xml_default_name = zenPath( "etc", daemon + ".xml" ) 645 try: 646 # Always recreate the defaults file in order to avoid caching issues 647 log.debug("Creating XML config file for %s" % daemon) 648 make_xml = ' '.join([binPath(daemon), "genxmlconfigs", ">", xml_default_name]) 649 proc = Popen(make_xml, shell=True, stdout=PIPE, stderr=PIPE) 650 output, errors = proc.communicate() 651 proc.wait() 652 if proc.returncode != 0: 653 log.error(errors) 654 messaging.IMessageSender(self).sendToBrowser( 655 'Internal Error', errors, 656 priority=messaging.CRITICAL 657 ) 658 return [["Output", output, errors, make_xml, "string"]] 659 except Exception, ex: 660 msg = "Unable to execute '%s'\noutput='%s'\nerrors='%s'\nex=%s" % ( 661 make_xml, output, errors, ex) 662 log.error(msg) 663 messaging.IMessageSender(self).sendToBrowser( 664 'Internal Error', msg, 665 priority=messaging.CRITICAL 666 ) 667 return [["Error in command", output, errors, make_xml, "string"]] 668 669 try: 670 xml_defaults = parse( xml_default_name ) 671 except: 672 info = traceback.format_exc() 673 msg = "Unable to parse XML file %s because %s" % ( 674 xml_default_name, info) 675 log.error(msg) 676 messaging.IMessageSender(self).sendToBrowser( 677 'Internal Error', msg, 678 priority=messaging.CRITICAL 679 ) 680 return [["Error parsing XML file", xml_default_name, "XML", info, "string"]] 681 682 configfile = self._getConfigFilename(daemon) 683 try: 684 # Grab the current configs 685 current_configs = self.parseconfig( configfile ) 686 except: 687 info = traceback.format_exc() 688 msg = "Unable to obtain current configuration from %s because %s" % ( 689 configfile, info) 690 log.error(msg) 691 messaging.IMessageSender(self).sendToBrowser( 692 'Internal Error', msg, 693 priority=messaging.CRITICAL 694 ) 695 return [["Configuration file issue", configfile, configfile, info, "string"]] 696 697 all_options = {} 698 ignore_options = ['configfile', 'cycle', 'daemon', 'weblog'] 699 try: 700 for option in xml_defaults.getElementsByTagName('option'): 701 id = option.attributes['id'].nodeValue 702 if id in ignore_options: 703 continue 704 try: 705 help = unquote(option.attributes['help'].nodeValue) 706 except: 707 help = '' 708 709 try: 710 default = unquote(option.attributes['default'].nodeValue) 711 except: 712 default = '' 713 if default == '[]': # Attempt at a list argument -- ignore 714 continue 715 716 all_options[id] = [ 717 id, 718 current_configs.get(id, default), 719 default, 720 help, 721 option.attributes['type'].nodeValue, 722 ] 723 724 except: 725 info = traceback.format_exc() 726 msg = "Unable to merge XML defaults with config file" \ 727 " %s because %s" % (configfile, info) 728 log.error(msg) 729 messaging.IMessageSender(self).sendToBrowser( 730 'Internal Error', msg, 731 priority=messaging.CRITICAL 732 ) 733 return [["XML file issue", daemon, xml_default_name, info, "string"]] 734 735 return [all_options[name] for name in sorted(all_options.keys())]
736 737
738 - def save_daemon_configs( self, REQUEST=None, **kwargs ):
739 """ 740 Save the updated daemon configuration to disk. 741 """ 742 if not REQUEST: 743 return 744 elif not hasattr(REQUEST, 'form'): 745 return 746 747 # Sanity check 748 formdata = REQUEST.form 749 ignore_names = ['save_daemon_configs', 'zenScreenName', 'daemon_name'] 750 751 daemon = formdata.get('daemon_name', '') 752 if not daemon or daemon in ['zeoctl', 'zopectl']: 753 return 754 for item in ignore_names: 755 del formdata[item] 756 757 if not isZenBinFile(daemon): 758 messaging.IMessageSender(self).sendToBrowser( 759 'Internal Error', "%r is not a valid daemon name" % daemon, 760 priority=messaging.CRITICAL 761 ) 762 return 763 764 if not formdata: # If empty, don't overwrite -- assume an error 765 msg = "Received empty form data for %s config -- ignoring" % ( 766 daemon) 767 log.error(msg) 768 messaging.IMessageSender(self).sendToBrowser( 769 'Internal Error', msg, 770 priority=messaging.CRITICAL 771 ) 772 return 773 774 configfile = self._getConfigFilename(daemon) 775 config_file_pre = configfile + ".pre" 776 try: 777 config = open( config_file_pre, 'w' ) 778 config.write("# Config file written out from GUI\n") 779 for key, value in formdata.items(): 780 if value == '': 781 continue 782 config.write('%s %s\n' % (key, value)) 783 config.close() 784 except Exception, ex: 785 msg = "Couldn't write to %s because %s" % (config_file_pre, ex) 786 log.error(msg) 787 messaging.IMessageSender(self).sendToBrowser( 788 'Internal Error', msg, 789 priority=messaging.CRITICAL 790 ) 791 config.close() 792 try: 793 os.unlink(config_file_pre) 794 except: 795 pass 796 return 797 798 # If we got here things succeeded 799 audit('UI.Daemon.EditConfig', daemon) 800 801 config_file_save = configfile + ".save" 802 try: 803 shutil.copy(configfile, config_file_save) 804 except: 805 log.error("Unable to make backup copy of %s" % configfile) 806 # Don't bother telling the user 807 try: 808 shutil.move(config_file_pre, configfile) 809 except: 810 msg = "Unable to save contents to %s" % configfile 811 log.error(msg) 812 messaging.IMessageSender(self).sendToBrowser( 813 'Internal Error', msg, 814 priority=messaging.CRITICAL 815 )
816 817 818 security.declareProtected(ZEN_MANAGE_DMD, 'manage_daemonAction')
819 - def manage_daemonAction(self, REQUEST):
820 """ 821 Start, stop, or restart Zenoss daemons from a web interface. 822 """ 823 legalValues = ['start', 'restart', 'stop'] 824 action = (REQUEST.form.get('action') or '').lower() 825 if action not in legalValues: 826 return self.callZenScreen(REQUEST) 827 daemonName = REQUEST.form.get('daemon') 828 if not isZenBinFile(daemonName): 829 messaging.IMessageSender(self).sendToBrowser( 830 'Internal Error', "%r is not a valid daemon name" % daemonName, 831 priority=messaging.CRITICAL 832 ) 833 return self.callZenScreen(REQUEST) 834 if self.doDaemonAction(daemonName, action): 835 audit(['UI.Daemon', action], daemonName) 836 return self.callZenScreen(REQUEST)
837 security.declareProtected('Manage DMD','manage_daemonAction') 838 839 840 security.declareProtected(ZEN_MANAGE_DMD, 'doDaemonAction')
841 - def doDaemonAction(self, daemonName, action):
842 """ 843 Do the given action (start, stop, restart) or the given daemon. 844 Block until the action is completed. 845 Returns False if an error was encountered, otherwise returns the action taken. 846 """ 847 import time 848 import subprocess 849 daemonPath = binPath(daemonName) 850 if not isZenBinFile(daemonName): 851 return 852 log.info('Telling %s to %s' % (daemonName, action)) 853 proc = subprocess.Popen([daemonPath, action], stdout=subprocess.PIPE, 854 stderr=subprocess.STDOUT) 855 output, _ = proc.communicate() 856 code = proc.wait() 857 if code: 858 log.info('Error from %s: %s (%s)' % (daemonName, output, code)) 859 if action in ('stop', 'restart'): 860 time.sleep(2) 861 return False if code else action
862 863
864 - def manage_checkVersion(self, optInOut=False, optInOutMetrics=False, REQUEST=None):
865 "Check for Zenoss updates on the Zenoss website" 866 self.dmd.versionCheckOptIn = optInOut 867 self.dmd.reportMetricsOptIn = optInOutMetrics 868 # There is a hidden field for manage_checkVersions in the form so that 869 # the javascript submit() calls will end up calling this method. 870 # That means that when user hits the Check Now button we will receive 871 # 2 values for that field. (button is that same field name.) 872 # We want to initiate only when the button is pressed. 873 #if self.dmd.versionCheckOptIn \ 874 # and REQUEST \ 875 if REQUEST and isinstance(REQUEST.form['manage_checkVersion'], list): 876 version_check(self.dmd) 877 return self.callZenScreen(REQUEST)
878 security.declareProtected('Manage DMD','manage_checkVersion') 879 880
881 - def lastVersionCheckedString(self):
882 if not self.dmd.lastVersionCheck: 883 return "Never" 884 return Time.LocalDateTime(self.dmd.lastVersionCheck)
885 886
887 - def versionBehind(self):
888 if not self.dmd.availableVersion: 889 return False 890 if Version.parse('Zenoss ' + self.dmd.availableVersion) > self.getZenossVersion(): 891 return True 892 return False
893 894 InitializeClass(ZenossInfo) 895