1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__="""DeviceClass
15 The primary organizer of device objects, managing zProperties and
16 their acquisition.
17 """
18
19 import types
20 import time
21 import transaction
22 import logging
23 log = logging.getLogger('zen.DeviceClass')
24
25 import DateTime
26 from Globals import DTMLFile
27 from Globals import InitializeClass
28 from Acquisition import aq_base, aq_chain
29 from AccessControl import ClassSecurityInfo
30 from AccessControl import Permissions as permissions
31
32 from Products.AdvancedQuery import MatchGlob, Or, Eq, RankByQueries_Max
33 from Products.CMFCore.utils import getToolByName
34
35 from Products.ZenModel.ZenossSecurity import *
36 from Products.ZenRelations.RelSchema import *
37 from Products.ZenRelations.ZenPropertyManager import Z_PROPERTIES
38 from Products.ZenUtils.Search import makeCaseInsensitiveFieldIndex
39 from Products.ZenUtils.Search import makeCaseInsensitiveKeywordIndex
40 from Products.ZenUtils.Search import makePathIndex, makeMultiPathIndex
41 from Products.ZenUtils.Utils import importClass, zenPath
42 from Products.ZenWidgets import messaging
43
44 from Products.ZenUtils.FakeRequest import FakeRequest
45
46 import RRDTemplate
47 from DeviceOrganizer import DeviceOrganizer
48 from ZenPackable import ZenPackable
49 from TemplateContainer import TemplateContainer
50
51 _marker = "__MARKER___"
52
59
60
61 addDeviceClass = DTMLFile('dtml/addDeviceClass',globals())
62
63
64 -class DeviceClass(DeviceOrganizer, ZenPackable, TemplateContainer):
65 """
66 DeviceClass is a device organizer that manages the primary classification
67 of device objects within the Zenoss system. It manages properties
68 that are inherited through acquisition that modify the behavior of
69 many different sub systems within Zenoss.
70 It also handles the creation of new devices in the system.
71 """
72
73
74 dmdRootName = "Devices"
75
76 manageDeviceSearch = DTMLFile('dtml/manageDeviceSearch',globals())
77 manageDeviceSearchResults = DTMLFile('dtml/manageDeviceSearchResults',
78 globals())
79
80 portal_type = meta_type = event_key = "DeviceClass"
81
82 default_catalog = 'deviceSearch'
83
84 _properties = DeviceOrganizer._properties + (
85 {'id':'devtypes', 'type':'lines', 'mode':'w'},
86 )
87
88 _relations = DeviceOrganizer._relations + ZenPackable._relations + \
89 TemplateContainer._relations + (
90 ("devices", ToManyCont(ToOne,"Products.ZenModel.Device","deviceClass")),
91 )
92
93
94 factory_type_information = (
95 {
96 'id' : 'DeviceClass',
97 'meta_type' : 'DeviceClass',
98 'description' : """Base class for all devices""",
99 'icon' : 'DeviceClass_icon.gif',
100 'product' : 'ZenModel',
101 'factory' : 'manage_addDeviceClass',
102 'immediate_view' : 'deviceOrganizerStatus',
103 'actions' :
104 (
105 { 'name' : 'Classes'
106 , 'action' : 'deviceOrganizerStatus'
107 , 'permissions' : ( permissions.view, )
108 },
109 { 'name' : 'Events'
110 , 'action' : 'viewEvents'
111 , 'permissions' : ( permissions.view, )
112 },
113 { 'name' : 'zProperties'
114 , 'action' : 'zPropertyEdit'
115 , 'permissions' : (permissions.view,)
116 },
117 { 'name' : 'Templates'
118 , 'action' : 'perfConfig'
119 , 'permissions' : ('Manage DMD',)
120 },
121 )
122 },
123 )
124
125 security = ClassSecurityInfo()
126
128 """
129 Return a list of all device paths that have the Python class pyclass
130
131 @param pyclass: Python class (default is this class)
132 @type pyclass: Python class
133 @return: list of device paths
134 @rtype: list of strings
135 """
136 dcnames = []
137 if pyclass == None:
138 pyclass = self.getPythonDeviceClass()
139 dclass = self.getDmdRoot("Devices")
140 for orgname in dclass.getOrganizerNames():
141 org = dclass.getOrganizer(orgname)
142 if pyclass == org.getPythonDeviceClass():
143 dcnames.append(orgname)
144 dcnames.sort(lambda a, b: cmp(a.lower(), b.lower()))
145 return dcnames
146
147 deviceMoveTargets = getPeerDeviceClassNames
148 childMoveTargets = getPeerDeviceClassNames
149
150
152 """
153 Create an instance based on its location in the device tree
154 walk up the primary aq path looking for a python instance class that
155 matches the name of the closest node in the device tree.
156
157 @param id: id in DMD path
158 @type id: string
159 @return: new device object
160 @rtype: device object
161 """
162 pyClass = self.getPythonDeviceClass()
163 dev = pyClass(id)
164 self.devices._setObject(id, dev)
165 return self.devices._getOb(id)
166
167
169 """
170 Return the Python class object to be used for device instances in this
171 device class. This is done by walking up the aq_chain of a deviceclass
172 to find a node that has the same name as a Python class or has an
173 attribute named zPythonClass that matches a Python class.
174
175 @return: device class
176 @rtype: device class
177 """
178 from Device import Device
179 cname = getattr(self, "zPythonClass", None)
180 if cname:
181 try:
182 return importClass(cname)
183 except ImportError:
184 log.exception("Unable to import class " + cname)
185 return Device
186
187
188 - def moveDevices(self, moveTarget, deviceNames=None, REQUEST=None):
189 """
190 Override default moveDevices because this is a contained relation.
191 If the Python class bound to a DeviceClass is different we convert to
192 the new Python class adding / removing relationships as needed.
193
194 @param moveTarget: organizer in DMD path
195 @type moveTarget: string
196 @param deviceNames: devices to move
197 @type deviceNames: list of stringa
198 @param REQUEST: Zope REQUEST object
199 @type REQUEST: Zope REQUEST object
200 """
201 if not moveTarget or not deviceNames: return self()
202 target = self.getDmdRoot(self.dmdRootName).getOrganizer(moveTarget)
203 if type(deviceNames) == types.StringType: deviceNames = (deviceNames,)
204 for devname in deviceNames:
205 dev = self.findDeviceByIdExact(devname)
206 if not dev: continue
207 source = dev.deviceClass().primaryAq()
208 if dev.__class__ != target.getPythonDeviceClass():
209 import StringIO
210 from Products.ZenRelations.ImportRM import NoLoginImportRM
211
212 def switchClass(o, module, klass):
213 """
214 Create an XML string representing the module in a
215 new class.
216
217 @param o: file-type object
218 @type o: file-type object
219 @param module: location in DMD
220 @type module: string
221 @param klass: class name
222 @type klass: string
223 @return: XML representation of the class
224 @rtype: string
225 """
226 o.seek(0)
227 l = o.readline()
228 al = l[1:-2].split()
229 for i in range(len(al)):
230 if al[i].startswith('module'):
231 al[i] = "module='%s'" % module
232 elif al[i].startswith('class'):
233 al[i] = "class='%s'" % klass
234 nl = "<" + " ".join(al) + ">\n"
235 o.seek(0)
236 nf = ["<objects>", nl]
237 nf.extend(o.readlines()[1:])
238 nf.append('</objects>')
239 return StringIO.StringIO("".join(nf))
240
241 def devExport(d, module, klass):
242 """
243 Create an XML string representing the device d
244 at the DMD location module of type klass.
245
246 @param module: location in DMD
247 @type module: string
248 @param klass: class name
249 @type klass: string
250 @return: XML representation of the class
251 @rtype: string
252 """
253 o = StringIO.StringIO()
254 d.exportXml(o)
255 return switchClass(o, module, klass)
256
257 def devImport(xmlfile):
258 """
259 Load a new device from a file.
260
261 @param xmlfile: file type object
262 @type xmlfile: file type object
263 """
264 im = NoLoginImportRM(target.devices)
265 im.loadObjectFromXML(xmlfile)
266
267 module = target.zPythonClass
268 if module:
269 klass = target.zPythonClass.split('.')[-1]
270 else:
271 module = 'Products.ZenModel.Device'
272 klass = 'Device'
273 xmlfile = devExport(dev, module,klass)
274 source.devices._delObject(devname)
275 devImport(xmlfile)
276 else:
277 dev._operation = 1
278 source.devices._delObject(devname)
279 target.devices._setObject(devname, dev)
280 dev = target.devices._getOb(devname)
281 dev.setLastChange()
282 dev.setAdminLocalRoles()
283 dev.index_object()
284 transaction.commit()
285 if REQUEST:
286 messaging.IMessageSender(self).sendToBrowser(title='Devices Moved',
287 body="Devices were moved to %s." % moveTarget)
288 REQUEST['message'] = "Devices moved to %s" % moveTarget
289 if not isinstance(REQUEST, FakeRequest):
290 REQUEST['RESPONSE'].redirect(target.getPrimaryUrlPath())
291 else:
292 if REQUEST.has_key('oneKeyValueSoInstanceIsntEmptyAndEvalToFalse'):
293 return REQUEST['message']
294 else:
295 return self.callZenScreen(REQUEST)
296
297
298 security.declareProtected(ZEN_DELETE_DEVICE, 'removeDevices')
299 - def removeDevices(self, deviceNames=None, deleteStatus=False,
300 deleteHistory=False, deletePerf=False,REQUEST=None):
301 """
302 See IManageDevice overrides DeviceManagerBase.removeDevices
303 """
304 if not deviceNames: return self()
305 if type(deviceNames) in types.StringTypes: deviceNames = (deviceNames,)
306 for devname in deviceNames:
307 dev = self.findDevice(devname)
308 dev.deleteDevice(deleteStatus=deleteStatus,
309 deleteHistory=deleteHistory, deletePerf=deletePerf)
310 if REQUEST:
311 messaging.IMessageSender(self).sendToBrowser(
312 'Devices Deleted',
313 "Devices were deleted: %s." % ', '.join(deviceNames)
314 )
315 if REQUEST.has_key('oneKeyValueSoInstanceIsntEmptyAndEvalToFalse'):
316 return 'Devices were deleted: %s.' % ', '.join(deviceNames)
317 else:
318 return self.callZenScreen(REQUEST)
319
320
321 security.declareProtected('View', 'getEventDeviceInfo')
338
339
340 security.declareProtected('View', 'getDeviceWinInfo')
342 """
343 Return list of (devname,user,passwd,url) for each device.
344 user and passwd are used to connect via wmi.
345 """
346 ffunc = None
347 starttime = time.time()
348 if lastPoll > 0:
349 lastPoll = DateTime.DateTime(lastPoll)
350 ffunc = lambda x: x.getSnmpLastCollection() > lastPoll
351 if eventlog:
352 ffunc = lambda x: x.zWinEventlog
353 devinfo = []
354 for dev in self.getSubDevices(devfilter=ffunc):
355 if not dev.monitorDevice(): continue
356 if dev.getProperty('zWmiMonitorIgnore', False): continue
357 user = dev.getProperty('zWinUser','')
358 passwd = dev.getProperty( 'zWinPassword', '')
359 sev = dev.getProperty( 'zWinEventlogMinSeverity', '')
360 devinfo.append((dev.id, str(user), str(passwd), sev, dev.absolute_url()))
361 return starttime, devinfo
362
363
365 """
366 Return a list of (devname, user, passwd, {'EvtSys':0,'Exchange':0})
367 """
368 svcinfo = []
369 allsvcs = {}
370 for s in self.getSubComponents("WinService"):
371 svcs=allsvcs.setdefault(s.hostname(),{})
372 name = s.name()
373 if type(name) == type(u''):
374 name = name.encode(s.zCollectorDecoding)
375 svcs[name] = (s.getStatus(), s.getAqProperty('zFailSeverity'))
376 for dev in self.getSubDevices():
377 if not dev.monitorDevice(): continue
378 if dev.getProperty( 'zWmiMonitorIgnore', False): continue
379 svcs = allsvcs.get(dev.getId(), {})
380 if not svcs and not dev.zWinEventlog: continue
381 user = dev.getProperty('zWinUser','')
382 passwd = dev.getProperty( 'zWinPassword', '')
383 svcinfo.append((dev.id, str(user), str(passwd), svcs))
384 return svcinfo
385
386
387 security.declareProtected('View', 'searchDeviceSummary')
389 """
390 Search device summary index and return device objects
391 """
392 if not query: return []
393 zcatalog = self._getCatalog()
394 if not zcatalog: return []
395 results = zcatalog({'summary':query})
396 return self._convertResultsToObj(results)
397
398
399 security.declareProtected('View', 'searchInterfaces')
401 """
402 Search interfaces index and return interface objects
403 """
404 if not query: return []
405 zcatalog = getattr(self, 'interfaceSearch', None)
406 if not zcatalog: return []
407 results = zcatalog(query)
408 return self._convertResultsToObj(results)
409
410
412 devices = []
413 for brain in results:
414 try:
415 devobj = self.getObjByPath(brain.getPrimaryId)
416 devices.append(devobj)
417 except KeyError:
418 log.warn("bad path '%s' in index" % brain.getPrimaryId)
419
420 return devices
421
423 """
424 Returns all devices whose ip/id/title match devicename.
425 ip/id matches are at the front of the list.
426
427 @rtype: list of brains
428 """
429 idIpQuery = Or( MatchGlob('id', devicename),
430 Eq('getDeviceIp', devicename) )
431 if useTitle:
432 titleOrIdQuery = MatchGlob('titleOrId', devicename)
433 query = Or( idIpQuery, titleOrIdQuery )
434 rankSort = RankByQueries_Max( ( idIpQuery, 16 ),
435 ( titleOrIdQuery, 8 ) )
436 devices = self._getCatalog().evalAdvancedQuery(query, (rankSort,))
437 else:
438 devices = self._getCatalog().evalAdvancedQuery(idIpQuery)
439 return devices
440
442 """
443 Look up a device and return its path
444 """
445 ret = self._findDevice(devicename)
446 if not ret: return ""
447 return ret[0].getPrimaryId
448
450 """
451 Returns the first device whose ip/id matches devicename. If
452 there is no ip/id match, return the first device whose title
453 matches devicename.
454 """
455 ret = self._findDevice(devicename)
456 if ret: return ret[0].getObject()
457
459 """
460 Returns the first device that has an ip/id that matches devicename
461 """
462 ret = self._findDevice( devicename, False )
463 if ret: return ret[0].getObject()
464
466 """
467 Look up device in catalog and return it. devicename
468 must match device id exactly
469 """
470 for brains in self._getCatalog()(id=devicename):
471 dev = brains.getObject()
472 if dev.id == devicename:
473 return dev
474
476 """
477 look up device in catalog and return its pingStatus
478 """
479 dev = self.findDevice(devicename)
480 if dev: return dev.getPingStatusNumber()
481
482
484 """
485 Return generator of components, by meta_type if specified
486 """
487 zcat = self.componentSearch
488 res = zcat({'meta_type': meta_type, 'monitored': monitored})
489 for b in res:
490 try:
491 c = self.getObjByPath(b.getPrimaryId)
492 if self.checkRemotePerm("View", c):
493 yield c
494 except KeyError:
495 log.warn("bad path '%s' in index 'componentSearch'",
496 b.getPrimaryId)
497
498
499 security.declareProtected("ZenCommon", "getMonitoredComponents")
501 """
502 Return monitored components for devices within this DeviceDeviceClass
503 """
504 return self.getSubComponents()
505
506
507 security.declareProtected('View', 'getRRDTemplates')
509 """
510 Return the actual RRDTemplate instances.
511 """
512 templates = {}
513 if not context: context = self
514 mychain = aq_chain(context)
515 mychain.reverse()
516 for obj in mychain:
517 try:
518 templates.update(dict([(t.id, t) for t in obj.rrdTemplates()]))
519 except AttributeError:
520 pass
521 return templates.values()
522
523
525 """
526 Returns all available templates
527 """
528 def cmpTemplates(a, b):
529 return cmp(a.id.lower(), b.id.lower())
530 templates = self.getRRDTemplates()
531 templates.sort(cmpTemplates)
532 pdc = self.getPythonDeviceClass()
533 return [ t for t in templates
534 if issubclass(pdc, t.getTargetPythonClass()) ]
535
536
538 """
539 This will bind available templates to the zDeviceTemplates
540 """
541 return self.setZenProperty('zDeviceTemplates', ids, REQUEST)
542
556
557
559 """
560 Return all RRDTemplates at this level and below in the object tree.
561 If rrdts is provided then it must be a list of RRDTemplates which
562 will be extended with the templates from here and returned.
563
564 The original getAllRRDTemplates() method has been renamed
565 getAllRRDTemplatesPainfully(). It walks the object tree looking
566 for templates which is a very slow way of going about things.
567 The newer RRDTemplate.YieldAllRRDTemplate() method uses the
568 searchRRDTemplates catalog to speed things up dramatically.
569 YieldAllRRDTemplates is smart enough to revert to
570 getAllRRDTemplatesPainfully if the catalog is not present.
571
572 The searchRRDTemplates catalog was added in 2.2
573 """
574 if rrdts == None:
575 rrdts = []
576 rrdts.extend(RRDTemplate.YieldAllRRDTemplates(self))
577 return rrdts
578
579
581 """
582 RRDTemplate.YieldAllRRDTemplates() is probably what you want.
583 It takes advantage of the searchRRDTemplates catalog to get
584 much better performance. This method iterates over objects looking
585 for templates which is a slow, painful process.
586 """
587 if rrdts is None: rrdts = []
588 rrdts.extend(self.rrdTemplates())
589 for dev in self.devices():
590 rrdts += dev.objectValues('RRDTemplate')
591 for comps in dev.getDeviceComponents():
592 rrdts += comps.objectValues('RRDTemplate')
593 for child in self.children():
594 child.getAllRRDTemplatesPainfully(rrdts)
595 return rrdts
596
597
598 security.declareProtected('Add DMD Objects', 'manage_addRRDTemplate')
613
614
615 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
616 'manage_copyRRDTemplates')
635
636
637 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
638 'manage_pasteRRDTemplates')
676
677
678 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
679 'manage_copyAndPasteRRDTemplates')
700
701
702 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
703 'manage_deleteRRDTemplates')
727
728
730 """
731 Make the catalog for device searching
732 """
733 from Products.ZCatalog.ZCatalog import manage_addZCatalog
734
735
736 manage_addZCatalog(self, self.default_catalog,
737 self.default_catalog)
738 zcat = self._getOb(self.default_catalog)
739 cat = zcat._catalog
740 for idxname in ['id',
741 'getDeviceIp','getDeviceClassPath','getProdState','titleOrId']:
742 cat.addIndex(idxname, makeCaseInsensitiveFieldIndex(idxname))
743 cat.addIndex('getPhysicalPath', makePathIndex('getPhysicalPath'))
744 cat.addIndex('path', makeMultiPathIndex('path'))
745 zcat.addColumn('getPrimaryId')
746 zcat.addColumn('id')
747 zcat.addColumn('path')
748
749
750 manage_addZCatalog(self, "componentSearch", "componentSearch")
751 zcat = self._getOb("componentSearch")
752 cat = zcat._catalog
753 cat.addIndex('meta_type', makeCaseInsensitiveFieldIndex('meta_type'))
754 cat.addIndex('getParentDeviceName',
755 makeCaseInsensitiveFieldIndex('getParentDeviceName'))
756 cat.addIndex('getCollectors',
757 makeCaseInsensitiveKeywordIndex('getCollectors'))
758
759
760 zcat.addIndex('monitored', 'FieldIndex')
761 zcat.addColumn('getPrimaryId')
762 zcat.addColumn('meta_type')
763
764
778
779
788
790 """
791 Provide a set of default options for a zProperty
792
793 @param propname: zProperty name
794 @type propname: string
795 @return: list of zProperty options
796 @rtype: list
797 """
798 if propname == 'zCollectorPlugins':
799 from Products.DataCollector.Plugins import loadPlugins
800 names = [ldr.pluginName for ldr in loadPlugins(self.dmd)]
801 names.sort()
802 return names
803 if propname == 'zCommandProtocol':
804 return ['ssh', 'telnet']
805 if propname == 'zSnmpVer':
806 return ['v1', 'v2c', 'v3']
807 if propname == 'zSnmpAuthType':
808 return ['', 'MD5', 'SHA']
809 if propname == 'zSnmpPrivType':
810 return ['', 'DES', 'AES']
811 return DeviceOrganizer.zenPropertyOptions(self, propname)
812
813
815 """
816 This will result in a push of all the devices to live collectors
817
818 @param REQUEST: Zope REQUEST object
819 @type REQUEST: Zope REQUEST object
820 """
821 self._p_changed = True
822 if REQUEST:
823 messaging.IMessageSender(self).sendToBrowser(
824 'Pushed Changes',
825 'Changes to %s were pushed to collectors.' % self.id
826 )
827 return self.callZenScreen(REQUEST)
828
829
830 security.declareProtected('Change Device', 'setLastChange')
832 """
833 Set the changed datetime for this device.
834
835 @param value: changed datetime. Default is now.
836 @type value: number
837 """
838 if value is None:
839 value = time.time()
840 self._lastChange = float(value)
841
843 """
844 Define this class in terms of a description of the devices it should
845 contain and the protocol by which they would normally be monitored.
846 """
847 t = (description, protocol)
848 if not hasattr(self, 'devtypes'):
849 self._setProperty('devtypes', [], 'lines')
850 if t not in self.devtypes:
851 self.devtypes.append(t)
852 self._p_changed = True
853
855 t = (description, protocol)
856 if hasattr(self, 'devtypes'):
857 if t in self.devtypes:
858 self.devtypes.remove(t)
859 self._p_changed = True
860
861
862 InitializeClass(DeviceClass)
863