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

# Source Code for Module Products.ZenModel.ZenossInfo

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