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