1
2
3
4
5
6
7
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
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
44 zope.interface.implements(ICollectorPreferences)
45
47 """
48 Constructs a new preferences instance and
49 provides default values for needed attributes.
50 """
51 self.collectorName = "zenrender"
52 self.configCycleInterval = 20
53 self.cycleInterval = 5 * 60
54 self.configurationService = 'Products.ZenHub.services.RenderConfig'
55
56
57 self.options = None
58
60
61 parser.remove_option('--device')
62
63
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
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
86 for name in dir(renderer):
87 if name.startswith('remote_'):
88 func = getattr(renderer, name)
89 setattr(self._daemon, name, func)
90
91
93 isLeaf = True
94
100
102 return self.rs.render(*args, **kw)
103
106
109
112
115
118
121
123 return self.rs.plugin(*args, **kw)
124
126 return self.rs.summary(*args, **kw)
127
130
133
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
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
173 helpText.append("""</body></html>""")
174 return '\n'.join(helpText)
175
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
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