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