| Trees | Indices | Help |
|
|---|
|
|
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # ##########################################################################
4 #
5 # This program is part of Zenoss Core, an open source monitoring platform.
6 # Copyright (C) 2006-2009 Zenoss Inc.
7 #
8 # This program is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License version 2 or (at your
10 # option) any later version as published by the Free Software Foundation.
11 #
12 # For complete information please visit: http://www.zenoss.com/oss/
13 #
14 # ##########################################################################
15
16 __doc__ = """PerformanceConf
17 The configuration object for Performance servers
18 """
19
20 import os
21 import zlib
22 import socket
23 from urllib import urlencode
24 from ipaddr import IPAddress
25 import logging
26 log = logging.getLogger('zen.PerformanceConf')
27
28 from Products.ZenUtils.IpUtil import ipwrap
29
30 try:
31 from base64 import urlsafe_b64encode
32 raise ImportError
33 except ImportError:
34
35
37 """
38 Encode a string so that it's okay to be used in an URL
39
40 @param s: possibly unsafe string passed in by the user
41 @type s: string
42 @return: sanitized, url-safe version of the string
43 @rtype: string
44 """
45
46 import base64
47 s = base64.encodestring(s)
48 s = s.replace('+', '-')
49 s = s.replace('/', '_')
50 s = s.replace('\n', '')
51 return s
52
53
54 import xmlrpclib
55
56 from AccessControl import ClassSecurityInfo
57 from AccessControl import Permissions as permissions
58 from Globals import DTMLFile
59 from Globals import InitializeClass
60 from Monitor import Monitor
61 from Products.PythonScripts.standard import url_quote
62 from Products.Jobber.jobs import ShellCommandJob
63 from Products.ZenModel.ZenossSecurity import *
64 from Products.ZenRelations.RelSchema import *
65 from Products.ZenUtils.Utils import basicAuthUrl, zenPath, binPath
66 from Products.ZenUtils.Utils import unused
67 from Products.ZenUtils.Utils import isXmlRpc
68 from Products.ZenUtils.Utils import setupLoggingHeader
69 from Products.ZenUtils.Utils import executeCommand
70 from Products.ZenUtils.Utils import clearWebLoggingStream
71 from Products.ZenModel.Device import manage_createDevice
72 from Products.ZenModel.ZDeviceLoader import DeviceCreationJob
73 from Products.ZenWidgets import messaging
74 from Products.ZenMessaging.actions import sendUserAction
75 from Products.ZenMessaging.actions.constants import ActionTargetType, ActionName
76 from StatusColor import StatusColor
77
78 PERF_ROOT = None
79
80
82 """
83 Return the base directory where RRD performance files are kept.
84
85 @param target: path to performance file
86 @type target: string
87 @return: sanitized path to performance file
88 @rtype: string
89 """
90 global PERF_ROOT
91 if PERF_ROOT is None:
92 PERF_ROOT = zenPath('perf')
93 if target.startswith('/'):
94 target = target[1:]
95 return os.path.join(PERF_ROOT, target)
96
97
99 """
100 Make a device class
101
102 @param context: Where you are in the Zope acquisition path
103 @type context: Zope context object
104 @param id: unique identifier
105 @type id: string
106 @param title: user readable label (unused)
107 @type title: string
108 @param REQUEST: Zope REQUEST object
109 @type REQUEST: Zope REQUEST object
110 @return:
111 @rtype:
112 """
113 unused(title)
114 dc = PerformanceConf(id)
115 context._setObject(id, dc)
116
117 if REQUEST is not None:
118 REQUEST['RESPONSE'].redirect(context.absolute_url()
119 + '/manage_main')
120
121
122 addPerformanceConf = DTMLFile('dtml/addPerformanceConf', globals())
123
124
126 """
127 Configuration for Performance servers
128 """
129 portal_type = meta_type = 'PerformanceConf'
130
131 monitorRootName = 'Performance'
132
133 security = ClassSecurityInfo()
134 security.setDefaultAccess('allow')
135
136 eventlogCycleInterval = 60
137 perfsnmpCycleInterval = 300
138 processCycleInterval = 180
139 statusCycleInterval = 60
140 winCycleInterval = 60
141 wmibatchSize = 10
142 wmiqueryTimeout = 100
143 configCycleInterval = 6 * 60
144
145 zenProcessParallelJobs = 10
146
147 pingTimeOut = 1.5
148 pingTries = 2
149 pingChunk = 75
150 pingCycleInterval = 60
151 maxPingFailures = 1440
152
153 modelerCycleInterval = 720
154
155 renderurl = '/zport/RenderServer'
156 renderuser = ''
157 renderpass = ''
158
159 discoveryNetworks = ()
160
161 # make the default rrdfile size smaller
162 # we need the space to live within the disk cache
163 defaultRRDCreateCommand = (
164 'RRA:AVERAGE:0.5:1:600', # every 5 mins for 2 days
165 'RRA:AVERAGE:0.5:6:600', # every 30 mins for 12 days
166 'RRA:AVERAGE:0.5:24:600', # every 2 hours for 50 days
167 'RRA:AVERAGE:0.5:288:600', # every day for 600 days
168 'RRA:MAX:0.5:6:600',
169 'RRA:MAX:0.5:24:600',
170 'RRA:MAX:0.5:288:600',
171 )
172
173 _properties = (
174 {'id': 'eventlogCycleInterval', 'type': 'int', 'mode': 'w'},
175 {'id': 'perfsnmpCycleInterval', 'type': 'int', 'mode': 'w'},
176 {'id': 'processCycleInterval', 'type': 'int', 'mode': 'w'},
177 {'id': 'statusCycleInterval', 'type': 'int', 'mode': 'w'},
178 {'id': 'winCycleInterval', 'type': 'int', 'mode': 'w'},
179 {'id': 'wmibatchSize', 'type': 'int', 'mode': 'w',
180 'description':"Number of data objects to retrieve in a single WMI query",},
181 {'id': 'wmiqueryTimeout', 'type': 'int', 'mode': 'w',
182 'description':"Number of milliseconds to wait for WMI query to respond",},
183 {'id': 'configCycleInterval', 'type': 'int', 'mode': 'w'},
184 {'id': 'renderurl', 'type': 'string', 'mode': 'w'},
185 {'id': 'renderuser', 'type': 'string', 'mode': 'w'},
186 {'id': 'renderpass', 'type': 'string', 'mode': 'w'},
187 {'id': 'defaultRRDCreateCommand', 'type': 'lines', 'mode': 'w'
188 },
189 {'id': 'zenProcessParallelJobs', 'type': 'int', 'mode': 'w'},
190 {'id': 'pingTimeOut', 'type': 'float', 'mode': 'w'},
191 {'id': 'pingTries', 'type': 'int', 'mode': 'w'},
192 {'id': 'pingChunk', 'type': 'int', 'mode': 'w'},
193 {'id': 'pingCycleInterval', 'type': 'int', 'mode': 'w'},
194 {'id': 'maxPingFailures', 'type': 'int', 'mode': 'w'},
195 {'id': 'modelerCycleInterval', 'type': 'int', 'mode': 'w'},
196 {'id': 'discoveryNetworks', 'type': 'lines', 'mode': 'w'},
197 )
198
199 _relations = Monitor._relations + (
200 ("devices", ToMany(ToOne,"Products.ZenModel.Device","perfServer")),
201 )
202
203 # Screen action bindings (and tab definitions)
204 factory_type_information = (
205 {
206 'immediate_view' : 'viewPerformanceConfOverview',
207 'actions' :
208 (
209 { 'id' : 'overview'
210 , 'name' : 'Overview'
211 , 'action' : 'viewPerformanceConfOverview'
212 , 'permissions' : (
213 permissions.view, )
214 },
215 { 'id' : 'edit'
216 , 'name' : 'Edit'
217 , 'action' : 'editPerformanceConf'
218 , 'permissions' : ("Manage DMD",)
219 },
220 { 'id' : 'performance'
221 , 'name' : 'Performance'
222 , 'action' : 'viewDaemonPerformance'
223 , 'permissions' : (permissions.view,)
224 },
225 )
226 },
227 )
228
229
230 security.declareProtected('View', 'getDefaultRRDCreateCommand')
232 """
233 Get the default RRD Create Command, as a string.
234 For example:
235 '''RRA:AVERAGE:0.5:1:600
236 RRA:AVERAGE:0.5:6:600
237 RRA:AVERAGE:0.5:24:600
238 RRA:AVERAGE:0.5:288:600
239 RRA:MAX:0.5:288:600'''
240
241 @return: RRD create command
242 @rtype: string
243 """
244 return '\n'.join(self.defaultRRDCreateCommand)
245
246
248 """
249 Return the object given the name
250
251 @param deviceName: Name of a device
252 @type deviceName: string
253 @return: device corresponding to the name, or None
254 @rtype: device object
255 """
256 brains = self.dmd.Devices._findDevice(deviceName)
257 if brains:
258 return brains[0].getObject()
259
260
262 """
263 Get the root of the Network object in the DMD
264
265 @return: base DMD Network object
266 @rtype: Network object
267 """
268 return self.dmd.Networks.getNetworkRoot(version)
269
270
272 """
273 Return an URL for the given graph options and date range
274
275 @param gopts: graph options
276 @type gopts: string
277 @param drange: time range to use
278 @type drange: string
279 @return: URL to a graphic
280 @rtype: string
281 """
282 newOpts = []
283 width = 0
284 for o in gopts:
285 if o.startswith('--width'):
286 width = o.split('=')[1].strip()
287 continue
288 newOpts.append(o)
289
290 encodedOpts = urlsafe_b64encode(
291 zlib.compress('|'.join(newOpts), 9))
292 params = {
293 'gopts': encodedOpts,
294 'drange': drange,
295 'width': width,
296 }
297 if self.renderurl.startswith('proxy'):
298 url = url.replace('proxy', 'http', 1)
299 params['remoteUrl'] = url
300 return '/zport/RenderServer/render?%s' % (urlencode(params),)
301 else:
302 return '%s/render?%s' % (self.renderurl, urlencode(params),)
303
304
306 """
307 Set the full path of the target and send to view
308
309 @param context: Where you are in the Zope acquisition path
310 @type context: Zope context object
311 @param targetpath: device path of performance metric
312 @type targetpath: string
313 @param targettype: unused
314 @type targettype: string
315 @param view: view object
316 @type view: Zope object
317 @param drange: date range
318 @type drange: string
319 @return: URL to graph
320 @rtype: string
321 """
322 unused(targettype)
323 targetpath = performancePath(targetpath)
324 gopts = view.getGraphCmds(context, targetpath)
325 return self.buildGraphUrlFromCommands(gopts, drange)
326
327
329 """
330 Set the full paths for all targts in map and send to view
331
332 @param context: Where you are in the Zope acquisition path
333 @type context: Zope context object
334 @param targetsmap: list of (target, targettype) tuples
335 @type targetsmap: list
336 @param view: view object
337 @type view: Zope object
338 @param drange: date range
339 @type drange: string
340 @return: URL to graph
341 @rtype: string
342 """
343 ntm = []
344 for (target, targettype) in targetsmap:
345 if target.find('.rrd') == -1:
346 target += '.rrd'
347 fulltarget = performancePath(target)
348 ntm.append((fulltarget, targettype))
349 gopts = view.multiGraphOpts(context, ntm)
350 gopts = url_quote('|'.join(gopts))
351 url = '%s/render?gopts=%s&drange=%d' % (self.renderurl, gopts, drange)
352 if self.renderurl.startswith('http'):
353 return '/zport/RenderServer/render?remoteUrl=%s&gopts=%s&drange=%d' % (
354 url_quote(url), gopts, drange)
355 else:
356 return url
357
358
360 """
361 Return the URL for a list of custom gopts for a graph
362
363 @param gopts: graph options
364 @type gopts: string
365 @param drange: date range
366 @type drange: string
367 @return: URL to graph
368 @rtype: string
369 """
370 gopts = self._fullPerformancePath(gopts)
371 gopts = url_quote('|'.join(gopts))
372 url = '%s/render?gopts=%s&drange=%d' % (self.renderurl, gopts,
373 drange)
374 if self.renderurl.startswith('http'):
375 return '/zport/RenderServer/render?remoteUrl=%s&gopts=%s&drange=%d'\
376 % (url_quote(url), gopts, drange)
377 else:
378 return url
379
380
382 """
383 Fill out full path for custom gopts and call to server
384
385 @param gopts: graph options
386 @type gopts: string
387 @return: URL
388 @rtype: string
389 """
390 gopts = self._fullPerformancePath(gopts)
391 renderurl = str(self.renderurl)
392 if renderurl.startswith('proxy'):
393 renderurl = self.renderurl.replace('proxy', 'http')
394 if renderurl.startswith('http'):
395 url = basicAuthUrl(str(self.renderuser),
396 str(self.renderpass), renderurl)
397 server = xmlrpclib.Server(url)
398 else:
399 server = self.getObjByPath(renderurl)
400 return server.summary(gopts)
401
402
404 """
405 Return values
406
407 @param paths: paths to performance metrics
408 @type paths: list
409 @param cf: RRD CF
410 @type cf: string
411 @param resolution: resolution
412 @type resolution: string
413 @param start: start time
414 @type start: string
415 @param end: end time
416 @type end: string
417 @return: values
418 @rtype: list
419 """
420 url = self.renderurl
421 if url.startswith("http"):
422 url = basicAuthUrl(self.renderuser, self.renderpass, self.renderurl)
423 server = xmlrpclib.Server(url, allow_none=True)
424 else:
425 if not self.renderurl:
426 raise KeyError
427 server = self.getObjByPath(self.renderurl)
428 return server.fetchValues(map(performancePath, paths), cf,
429 resolution, start, end)
430
431
433 """
434 Fill out full path and call to server
435
436 @param paths: paths to performance metrics
437 @type paths: list
438 @return: values
439 @rtype: list
440 """
441 url = self.renderurl
442 if url.startswith('proxy'):
443 url = self.renderurl.replace('proxy', 'http')
444 if url.startswith('http'):
445 url = basicAuthUrl(self.renderuser, self.renderpass,
446 self.renderurl)
447 server = xmlrpclib.Server(url)
448 else:
449 if not self.renderurl:
450 raise KeyError
451 server = self.getObjByPath(self.renderurl)
452 return server.currentValues(map(performancePath, paths))
453
454
456 """
457 Add full path to a list of custom graph options
458
459 @param gopts: graph options
460 @type gopts: string
461 @return: full path + graph options
462 @rtype: string
463 """
464 for i in range(len(gopts)):
465 opt = gopts[i]
466 if opt.find('DEF') == 0:
467 opt = opt.split(':')
468 (var, file) = opt[1].split('=')
469 file = performancePath(file)
470 opt[1] = '%s=%s' % (var, file)
471 opt = ':'.join(opt)
472 gopts[i] = opt
473 return gopts
474
475
476 security.declareProtected('View', 'performanceDeviceList')
478 """
479 Return a list of URLs that point to our managed devices
480
481 @param force: unused
482 @type force: boolean
483 @return: list of device objects
484 @rtype: list
485 """
486 unused(force)
487 devlist = []
488 for dev in self.devices():
489 dev = dev.primaryAq()
490 if not dev.pastSnmpMaxFailures() and dev.monitorDevice():
491 devlist.append(dev.getPrimaryUrlPath(full=True))
492 return devlist
493
494
495 security.declareProtected('View', 'performanceDataSources')
497 """
498 Return a string that has all the definitions for the performance DS's.
499
500 @return: list of Data Sources
501 @rtype: string
502 """
503 dses = []
504 oidtmpl = 'OID %s %s'
505 dstmpl = """datasource %s
506 rrd-ds-type = %s
507 ds-source = snmp://%%snmp%%/%s%s
508 """
509 rrdconfig = self.getDmdRoot('Devices').rrdconfig
510 for ds in rrdconfig.objectValues(spec='RRDDataSource'):
511 if ds.isrow:
512 inst = '.%inst%'
513 else:
514 inst = ''
515 dses.append(oidtmpl % (ds.getName(), ds.oid))
516 dses.append(dstmpl % (ds.getName(), ds.rrdtype,
517 ds.getName(), inst))
518 return '\n'.join(dses)
519
521 """
522 Remove RRD performance data files
523
524 @param device: Name of a device or entry in DMD
525 @type device: string
526 @param datasource: datasource name
527 @type datasource: string
528 @param datapoint: datapoint name
529 @type datapoint: string
530 """
531 remoteUrl = None
532 if self.renderurl.startswith('http'):
533 if datapoint:
534 remoteUrl = '%s/deleteRRDFiles?device=%s&datapoint=%s' % (
535 self.renderurl, device, datapoint)
536 elif datasource:
537 remoteUrl = '%s/deleteRRDFiles?device=%s&datasource=%s' % (
538 self.renderurl, device, datasource)
539 else:
540 remoteUrl = '%s/deleteRRDFiles?device=%s' % (
541 self.renderurl, device)
542 rs = self.getDmd().getParentNode().RenderServer
543 rs.deleteRRDFiles(device, datasource, datapoint, remoteUrl)
544
545
547 """
548 Provide a method to set performance monitor from any organizer
549
550 @param performanceMonitor: DMD object that collects from a device
551 @type performanceMonitor: DMD object
552 @param deviceNames: list of device names
553 @type deviceNames: list
554 @param REQUEST: Zope REQUEST object
555 @type REQUEST: Zope REQUEST object
556 """
557 if not performanceMonitor:
558 if REQUEST:
559 messaging.IMessageSender(self).sendToBrowser('Error',
560 'No monitor was selected.',
561 priority=messaging.WARNING)
562 return self.callZenScreen(REQUEST)
563 if deviceNames is None:
564 if REQUEST:
565 messaging.IMessageSender(self).sendToBrowser('Error',
566 'No devices were selected.',
567 priority=messaging.WARNING)
568 return self.callZenScreen(REQUEST)
569 for devName in deviceNames:
570 dev = self.devices._getOb(devName)
571 dev = dev.primaryAq()
572 dev.setPerformanceMonitor(performanceMonitor)
573 if sendUserAction and REQUEST:
574 sendUserAction(ActionTargetType.Device,
575 'ChangeCollector',
576 device=dev.getPrimaryId(),
577 collector=performanceMonitor)
578 if REQUEST:
579 messaging.IMessageSender(self).sendToBrowser('Monitor Set',
580 'Performance monitor was set to %s.'
581 % performanceMonitor)
582 if REQUEST.has_key('oneKeyValueSoInstanceIsntEmptyAndEvalToFalse'):
583 return REQUEST['message']
584 else:
585 return self.callZenScreen(REQUEST)
586
587
588 security.declareProtected('View', 'getPingDevices')
590 """
591 Return devices associated with this monitor configuration.
592
593 @return: list of devices for this monitor
594 @rtype: list
595 """
596 devices = []
597 for dev in self.devices.objectValuesAll():
598 dev = dev.primaryAq()
599 if dev.monitorDevice() and not dev.zPingMonitorIgnore:
600 devices.append(dev)
601 return devices
602
603 - def addDeviceCreationJob(self, deviceName, devicePath, title=None,
604 discoverProto="none",
605 performanceMonitor='localhost',
606 rackSlot=0, productionState=1000, comments="",
607 hwManufacturer="", hwProductName="",
608 osManufacturer="", osProductName="", priority = 3,
609 locationPath="", systemPaths=[], groupPaths=[],
610 tag="", serialNumber="", zProperties={}):
611
612 # Check to see if we got passed in an IPv6 address
613 try:
614 ipv6addr = IPAddress(deviceName)
615 if not title:
616 title = deviceName
617 deviceName = ipwrap(deviceName)
618 except ValueError:
619 pass
620
621 zendiscCmd = self._getZenDiscCommand(deviceName, devicePath,
622 performanceMonitor)
623
624 jobStatus = self.dmd.JobManager.addJob(DeviceCreationJob,
625 deviceName=deviceName,
626 devicePath=devicePath,
627 title=title,
628 discoverProto=discoverProto,
629 performanceMonitor=performanceMonitor,
630 rackSlot=rackSlot,
631 productionState=productionState,
632 comments=comments,
633 hwManufacturer=hwManufacturer,
634 hwProductName=hwProductName,
635 osManufacturer=osManufacturer,
636 osProductName=osProductName,
637 priority=priority,
638 tag=tag,
639 serialNumber=serialNumber,
640 locationPath=locationPath,
641 systemPaths=systemPaths,
642 groupPaths=groupPaths,
643 zProperties=zProperties,
644 zendiscCmd=zendiscCmd)
645 return jobStatus
646
647 - def _executeZenDiscCommand(self, deviceName, devicePath= "/Discovered",
648 performanceMonitor="localhost",
649 background=False, REQUEST=None):
650 """
651 Execute zendisc on the new device and return result
652
653 @param deviceName: Name of a device
654 @type deviceName: string
655 @param devicePath: DMD path to create the new device in
656 @type devicePath: string
657 @param performanceMonitor: DMD object that collects from a device
658 @type performanceMonitor: DMD object
659 @param background: should command be scheduled job?
660 @type background: boolean
661 @param REQUEST: Zope REQUEST object
662 @type REQUEST: Zope REQUEST object
663 @return:
664 @rtype:
665 """
666 zendiscCmd = self._getZenDiscCommand(deviceName, devicePath,
667 performanceMonitor, REQUEST)
668 if background:
669 log.info('queued job: %s', " ".join(zendiscCmd))
670 result = self.dmd.JobManager.addJob(ShellCommandJob,
671 zendiscCmd)
672 else:
673 result = executeCommand(zendiscCmd, REQUEST)
674 return result
675
678
679 zm = binPath('zendisc')
680 zendiscCmd = [zm]
681 zendiscOptions = ['run', '--now','-d', deviceName,
682 '--monitor', performanceMonitor,
683 '--deviceclass', devicePath]
684 if REQUEST:
685 zendiscOptions.append("--weblog")
686 zendiscCmd.extend(zendiscOptions)
687 log.info('local zendiscCmd is "%s"' % ' '.join(zendiscCmd))
688 return zendiscCmd
689
692
694 """
695 Executes the collector based daemon command.
696
697 @param command: the collector daemon to run, should not include path
698 @type command: string
699 @param args: list of arguments for the command
700 @type args: list of strings
701 @param REQUEST: Zope REQUEST object
702 @type REQUEST: Zope REQUEST object
703 @return: result of the command
704 @rtype: string
705 """
706 cmd = binPath(command)
707 daemonCmd = [cmd]
708 daemonCmd.extend(args)
709 result = executeCommand(daemonCmd, REQUEST)
710 return result
711
712
713 - def collectDevice(self, device=None, setlog=True, REQUEST=None,
714 generateEvents=False, background=False, write=None):
715 """
716 Collect the configuration of this device AKA Model Device
717
718 @permission: ZEN_MANAGE_DEVICE
719 @param device: Name of a device or entry in DMD
720 @type device: string
721 @param setlog: If true, set up the output log of this process
722 @type setlog: boolean
723 @param REQUEST: Zope REQUEST object
724 @type REQUEST: Zope REQUEST object
725 @param generateEvents: unused
726 @type generateEvents: string
727 """
728 xmlrpc = isXmlRpc(REQUEST)
729 zenmodelerOpts = ['run', '--now', '--monitor', self.id,
730 '-F', '-d', device.id]
731 result = self._executeZenModelerCommand(zenmodelerOpts, background,
732 REQUEST, write)
733 if result and xmlrpc:
734 return result
735 log.info('configuration collected')
736
737 if xmlrpc:
738 return 0
739
740
741 - def _executeZenModelerCommand(self, zenmodelerOpts, background=False,
742 REQUEST=None, write=None):
743 """
744 Execute zenmodeler and return result
745
746 @param zenmodelerOpts: zenmodeler command-line options
747 @type zenmodelerOpts: string
748 @param REQUEST: Zope REQUEST object
749 @type REQUEST: Zope REQUEST object
750 @return: results of command
751 @rtype: string
752 """
753 zm = binPath('zenmodeler')
754 zenmodelerCmd = [zm]
755 zenmodelerCmd.extend(zenmodelerOpts)
756 if background:
757 log.info('queued job: %s', " ".join(zenmodelerCmd))
758 result = self.dmd.JobManager.addJob(ShellCommandJob,zenmodelerCmd)
759 else:
760 result = executeCommand(zenmodelerCmd, REQUEST, write)
761 return result
762
763
764 InitializeClass(PerformanceConf)
765
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1.1812 on Tue Oct 11 12:51:45 2011 | http://epydoc.sourceforge.net |