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