Package Products :: Package ZenRRD :: Module zenrender |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenRRD.zenrender

      1  #! /usr/bin/env python
      2  ##############################################################################
      3  #
      4  # Copyright (C) Zenoss, Inc. 2007, 2011, all rights reserved.
      5  #
      6  # This content is made available according to terms specified in
      7  # License.zenoss under the directory where your Zenoss product is installed.
      8  #
      9  ##############################################################################
     10
     11
     12  __doc__ = """zenrender
     13
     14  Listens in order to process RRD files to generate graphs
     15  or retrieve data from a remote collector.
     16  """
     17
     18  import logging
     19  log = logging.getLogger("zen.zenrender")
     20
     21  import mimetypes
     22  import socket
     23  import xmlrpclib
     24
     25  import Globals
     26  import zope.interface
     27
     28  from twisted.web import resource, server
     29  from twisted.internet import reactor
     30
     31  from Products.ZenCollector.daemon import CollectorDaemon
     32  from Products.ZenCollector.interfaces import ICollectorPreferences,\
     33                                               ICollector
     34  from Products.ZenCollector.tasks import NullTaskSplitter
     35
     36  # Invalidation issues arise if we don't import
     37  from Products.ZenCollector.services.config import DeviceProxy
     38
     39  from Products.ZenRRD.RenderServer import RenderServer
     40  from Products.ZenUtils.Utils import ipv6_available
     41
     42
     43 -class ZenRenderPreferences(object):
     44      zope.interface.implements(ICollectorPreferences)
     45
     46 -    def __init__(self):
     47          """
     48          Constructs a new preferences instance and
     49          provides default values for needed attributes.
     50          """
     51          self.collectorName = "zenrender"
     52          self.configCycleInterval = 20 # minutes
     53          self.cycleInterval = 5 * 60 # seconds
     54          self.configurationService = 'Products.ZenHub.services.RenderConfig'
     55
     56          # Will be filled in based on buildOptions
     57          self.options = None
     58
     59 -    def buildOptions(self, parser):
     60          # Remove device option
     61          parser.remove_option('--device')
     62
     63          # Add options
     64          parser.add_option('--http-port', type='int',
     65                            dest='httpport',
     66                            default= 8091,
     67                            help='Port zenrender listens on for HTTP'
     68                            'render requests. Default is %default.')
     69
     70 -    def postStartup(self):
     71          """
     72          Listen for HTTP requests for RRD data or graphs.
     73          """
     74          self._daemon = zope.component.getUtility(ICollector)
     75
     76          # Start listening for HTTP requests
     77          httpPort = self. _daemon.options.httpport
     78          collector = self._daemon.options.monitor
     79          log.info("Starting %s zenrender webserver on port %s",
     80                        collector, httpPort)
     81          renderer = HttpRender(collector)
     82          interface = '::' if ipv6_available() else ''
     83          reactor.listenTCP(httpPort, server.Site(renderer), interface=interface)
     84
     85          # Add remote_ methods from renderer directly to the daemon
     86          for name in dir(renderer):
     87              if name.startswith('remote_'):
     88                  func = getattr(renderer, name)
     89                  setattr(self._daemon, name, func)
     90
     91
     92 -class HttpRender(resource.Resource):
     93      isLeaf = True
     94
     95 -    def __init__(self, collectorName):
     96          self.log = log
     97          self._daemon = zope.component.getUtility(ICollector)
     98          self.collectorName = collectorName
     99          self.rs = RenderServer(collectorName)
    100
    101 -    def remote_render(self, *args, **kw):
    102          return self.rs.render(*args, **kw)
    103
    104 -    def remote_packageRRDFiles(self, *args, **kw):
    105          return self.rs.packageRRDFiles(*args, **kw)
    106
    107 -    def remote_unpackageRRDFiles(self, *args, **kw):
    108          return self.rs.unpackageRRDFiles(*args, **kw)
    109
    110 -    def remote_receiveRRDFiles(self, *args, **kw):
    111          return self.rs.receiveRRDFiles(*args, **kw)
    112
    113 -    def remote_sendRRDFiles(self, *args, **kw):
    114          return self.rs.sendRRDFiles(*args, **kw)
    115
    116 -    def remote_moveRRDFiles(self, *args, **kw):
    117          return self.rs.moveRRDFiles(*args, **kw)
    118
    119 -    def remote_deleteRRDFiles(self, *args, **kw):
    120          return self.rs.deleteRRDFiles(*args, **kw)
    121
    122 -    def remote_plugin(self, *args, **kw):
    123          return self.rs.plugin(*args, **kw)
    124
    125 -    def remote_summary(self, *args, **kw):
    126          return self.rs.summary(*args, **kw)
    127
    128 -    def remote_fetchValues(self, *args, **kw):
    129          return self.rs.fetchValues(*args, **kw)
    130
    131 -    def remote_currentValues(self, *args, **kw):
    132          return self.rs.currentValues(*args, **kw)
    133
    134 -    def _showHelp(self):
    135          """
    136          When someone hits the HTTP port directly, give them
    137          something other than a traceback.
    138          """
    139          helpText = [ """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    140  <html><title>zenrender Help</title>
    141  <body>
    142  <h3>This zenrender is for collector: %s</h3>
    143  <h3>About zenrender</h3>
    144  <p>The zenrender daemon receives calls from zenhub (or in some
    145  special cases, by a browser directly) and given a request,
    146  creates a graph of RRD data or returns back RRD information.
    147  This daemon is not meant to be browsed directly by users.</p>
    148  <p>A zenrender daemon should only respond to requests for
    149  the remote collector with which it is associated.  This
    150  zenrender daemon is registered with the '%s' collector.</p>
    151  """ % (self.collectorName, self.collectorName)]
    152
    153          methods = []
    154          for name in dir(self):
    155              if not name.startswith('remote_'):
    156                  continue
    157
    158              name = name.replace('remote_', '')
    159              docs = getattr(self.rs, name).__doc__
    160              docs = docs if docs is not None else ''
    161              methods.append( (name, docs) )
    162
    163          # Insert table of methods
    164          helpText.append("""<table border='1'>
    165  <caption>zenrender Methods</caption>
    166  <tr><th>Method Name</th><th>Description</th></tr>""")
    167          for name, docs in sorted(methods):
    168              helpText.append("<tr><td>%s</td> <td><pre>%s</pre></td></tr>" % (
    169                              name, docs))
    170          helpText.append("</table>")
    171
    172          # Drop in the trailer
    173          helpText.append("""</body></html>""")
    174          return '\n'.join(helpText)
    175
    176 -    def render_GET(self, request):
    177          """
    178          Respond to HTTP GET requests
    179          """
    180          args = request.args.copy()
    181          for k, v in args.items():
    182              if len(v) == 1:
    183                  args[k] = v[0]
    184          command = request.postpath[-1]
    185          self.log.debug("Processing %s request from %s", command,
    186                         request.getClientIP())
    187          if command == '':
    188              return self._showHelp()
    189
    190          args.setdefault('ftype', 'PNG')
    191          ftype = args['ftype']
    192          del args['ftype']
    193          mimetype = mimetypes.guess_type('x.%s' % ftype)[0]
    194          if mimetype is None:
    195              mimetype = 'image/%s' % ftype.lower()
    196          request.setHeader('Content-type', mimetype)
    197          functor = getattr(self._daemon, 'remote_' + command, None)
    198          if functor:
    199              return functor(**args)
    200
    201          # Ignore trash and log error messages
    202          if command not in ('favicon.ico',):
    203              self.log.error("Received a bad request: %s", command)
    204          return ''
    205
    206 -    def render_POST(self, request):
    207          """
    208          Respond to HTTP POST requests (eg XML-RPC requests)
    209          """
    210          content = request.content.read()
    211          args, command = xmlrpclib.loads(content)
    212          self.log.debug("Processing %s request from %s" % (command,request.getClientIP()))
    213          request.setHeader('Content-type', 'text/xml')
    214          functor = getattr(self._daemon, 'remote_' + command, None)
    215          if functor and isinstance(args, (tuple, list, dict)):
    216              if isinstance(args, (tuple, list)):
    217                  result = functor(*args)
    218              elif isinstance(args, dict):
    219                  result = functor(**args)
    220              response = xmlrpclib.dumps((result,),
    221                  methodresponse=True, allow_none=True)
    222              return response
    223
    224          self.log.error("Received a bad request: %s", command)
    225          return ''
    226
    227
    228  if __name__ == '__main__':
    229      myPreferences = ZenRenderPreferences()
    230      myTaskSplitter = NullTaskSplitter()
    231      daemon = CollectorDaemon(myPreferences, myTaskSplitter)
    232      daemon.run()
    233