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 time
20 from cStringIO import StringIO
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.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 from ZODB.transact import transact
34
35 from Products.AdvancedQuery import MatchGlob, Or, Eq, RankByQueries_Max
36 from Products.CMFCore.utils import getToolByName
37 from Products.ZenMessaging.ChangeEvents.events import DeviceClassMovedEvent
38 from Products.ZenModel.ZenossSecurity import *
39 from Products.ZenRelations.RelSchema import *
40 from Products.ZenRelations.ZenPropertyManager import Z_PROPERTIES
41 from Products.ZenUtils.Search import makeCaseInsensitiveFieldIndex, makeCaseInsensitiveFieldIndex, makeCaseSensitiveKeywordIndex
42 from Products.ZenUtils.Search import makeCaseInsensitiveKeywordIndex
43 from Products.ZenUtils.Search import makePathIndex, makeMultiPathIndex
44 from Products.ZenUtils.Utils import importClass, zenPath
45 from Products.ZenUtils.guid.interfaces import IGlobalIdentifier
46 from Products.ZenWidgets import messaging
47 from Products.ZenUtils.FakeRequest import FakeRequest
48 from Products.Zuul.catalog.events import IndexingEvent
49
50 import RRDTemplate
51 from DeviceOrganizer import DeviceOrganizer
52 from ZenPackable import ZenPackable
53 from TemplateContainer import TemplateContainer
54
55 _marker = "__MARKER___"
63
64
65 addDeviceClass = DTMLFile('dtml/addDeviceClass',globals())
66
67
68 -class DeviceClass(DeviceOrganizer, ZenPackable, TemplateContainer):
69 """
70 DeviceClass is a device organizer that manages the primary classification
71 of device objects within the Zenoss system. It manages properties
72 that are inherited through acquisition that modify the behavior of
73 many different sub systems within Zenoss.
74 It also handles the creation of new devices in the system.
75 """
76
77
78 dmdRootName = "Devices"
79
80 manageDeviceSearch = DTMLFile('dtml/manageDeviceSearch',globals())
81 manageDeviceSearchResults = DTMLFile('dtml/manageDeviceSearchResults',
82 globals())
83
84 portal_type = meta_type = event_key = "DeviceClass"
85
86 default_catalog = 'deviceSearch'
87
88 _properties = DeviceOrganizer._properties + (
89 {'id':'devtypes', 'type':'lines', 'mode':'w'},
90 )
91
92 _relations = DeviceOrganizer._relations + ZenPackable._relations + \
93 TemplateContainer._relations + (
94 ("devices", ToManyCont(ToOne,"Products.ZenModel.Device","deviceClass")),
95 )
96
97
98 factory_type_information = (
99 {
100 'id' : 'DeviceClass',
101 'meta_type' : 'DeviceClass',
102 'description' : """Base class for all devices""",
103 'icon' : 'DeviceClass_icon.gif',
104 'product' : 'ZenModel',
105 'factory' : 'manage_addDeviceClass',
106 'immediate_view' : 'deviceOrganizerStatus',
107 'actions' :
108 (
109 { 'name' : 'Classes'
110 , 'action' : 'deviceOrganizerStatus'
111 , 'permissions' : ( permissions.view, )
112 },
113 { 'name' : 'Events'
114 , 'action' : 'viewEvents'
115 , 'permissions' : ( permissions.view, )
116 },
117 { 'name' : 'Configuration Properties'
118 , 'action' : 'zPropertyEdit'
119 , 'permissions' : (permissions.view,)
120 },
121 { 'name' : 'Templates'
122 , 'action' : 'perfConfig'
123 , 'permissions' : ('Manage DMD',)
124 },
125 )
126 },
127 )
128
129 security = ClassSecurityInfo()
130
132 """
133 Return a list of all device paths that have the Python class pyclass
134
135 @param pyclass: Python class (default is this class)
136 @type pyclass: Python class
137 @return: list of device paths
138 @rtype: list of strings
139 """
140 dcnames = []
141 if pyclass == None:
142 pyclass = self.getPythonDeviceClass()
143 dclass = self.getDmdRoot("Devices")
144 for orgname in dclass.getOrganizerNames():
145 org = dclass.getOrganizer(orgname)
146 if issubclass(org.getPythonDeviceClass(), pyclass):
147 dcnames.append(orgname)
148 dcnames.sort(lambda a, b: cmp(a.lower(), b.lower()))
149 return dcnames
150
151 deviceMoveTargets = getPeerDeviceClassNames
152 childMoveTargets = getPeerDeviceClassNames
153
154
156 """
157 Create an instance based on its location in the device tree
158 walk up the primary aq path looking for a python instance class that
159 matches the name of the closest node in the device tree.
160
161 @param id: id in DMD path
162 @type id: string
163 @return: new device object
164 @rtype: device object
165 """
166 pyClass = self.getPythonDeviceClass()
167 dev = pyClass(id)
168 self.devices._setObject(id, dev)
169 return self.devices._getOb(id)
170
171
173 """
174 Return the Python class object to be used for device instances in this
175 device class. This is done by walking up the aq_chain of a deviceclass
176 to find a node that has the same name as a Python class or has an
177 attribute named zPythonClass that matches a Python class.
178
179 @return: device class
180 @rtype: device class
181 """
182 from Device import Device
183 cname = getattr(self, "zPythonClass", None)
184 if cname:
185 try:
186 return importClass(cname)
187 except ImportError:
188 log.exception("Unable to import class " + cname)
189 return Device
190
191 @transact
193 dev = self.findDeviceByIdExact(devname)
194 if not dev:
195 return
196 guid = IGlobalIdentifier(dev).create()
197 source = dev.deviceClass().primaryAq()
198
199 notify(DeviceClassMovedEvent(dev, dev.deviceClass().primaryAq(), target))
200
201 exported = False
202 oldPath = source.absolute_url_path() + '/'
203 if dev.__class__ != targetClass:
204 from Products.ZenRelations.ImportRM import NoLoginImportRM
205
206 def switchClass(o, module, klass):
207 """
208 Create an XML string representing the module in a
209 new class.
210
211 @param o: file-type object
212 @type o: file-type object
213 @param module: location in DMD
214 @type module: string
215 @param klass: class name
216 @type klass: string
217 @return: XML representation of the class
218 @rtype: string
219 """
220 from xml.dom.minidom import parse
221
222
223
224
225 o.seek(0)
226 dom = parse(o)
227 root = dom.childNodes[0]
228 root.setAttribute('module', module)
229 root.setAttribute('class', klass)
230 for obj in root.childNodes:
231 if obj.nodeType != obj.ELEMENT_NODE:
232 continue
233
234 name = obj.getAttribute('id')
235 if obj.tagName == 'property':
236
237
238 if name in ('zCollectorPlugins', 'zDeviceTemplates') or \
239 name.endswith('Ignore'):
240 root.removeChild(obj)
241
242 elif obj.tagName == 'toone' and \
243 name in ('perfServer', 'location'):
244 pass
245
246 elif obj.tagName == 'tomany' and \
247 name in ('systems', 'groups'):
248 pass
249
250 elif obj.tagName == 'tomanycont' and \
251 name in ('maintenanceWindows',
252 'adminRoles',
253 'userCommands'):
254 pass
255
256 else:
257 log.debug("Removing %s element id='%s'",
258 obj.tagName, name)
259 root.removeChild(obj)
260
261 importFile = StringIO()
262 dom.writexml(importFile)
263 importFile.seek(0)
264 return importFile
265
266 def devExport(d, module, klass):
267 """
268 Create an XML string representing the device d
269 at the DMD location module of type klass.
270
271 @param module: location in DMD
272 @type module: string
273 @param klass: class name
274 @type klass: string
275 @return: XML representation of the class
276 @rtype: string
277 """
278 o = StringIO()
279 d.exportXml(o)
280 return switchClass(o, module, klass)
281
282 def devImport(xmlfile):
283 """
284 Load a new device from a file.
285
286 @param xmlfile: file type object
287 @type xmlfile: file type object
288 """
289 im = NoLoginImportRM(target.devices)
290 im.loadObjectFromXML(xmlfile)
291 im.processLinks()
292
293 module = target.zPythonClass
294 if module:
295 klass = target.zPythonClass.split('.')[-1]
296 else:
297 module = 'Products.ZenModel.Device'
298 klass = 'Device'
299 log.debug('Exporting device %s from %s', devname, source)
300 xmlfile = devExport(dev, module, klass)
301 log.debug('Removing device %s from %s', devname, source)
302 source.devices._delObject(devname)
303 log.debug('Importing device %s to %s', devname, target)
304 devImport(xmlfile)
305 exported = True
306 else:
307 dev._operation = 1
308 source.devices._delObject(devname)
309 target.devices._setObject(devname, dev)
310 dev = target.devices._getOb(devname)
311 IGlobalIdentifier(dev).guid = guid
312 dev.setLastChange()
313 dev.setAdminLocalRoles()
314 dev.index_object()
315 notify(IndexingEvent(dev, idxs=('path', 'searchKeywords'),
316 update_metadata=True))
317
318 return exported
319
320 - def moveDevices(self, moveTarget, deviceNames=None, REQUEST=None):
321 """
322 Override default moveDevices because this is a contained relation.
323 If the Python class bound to a DeviceClass is different we convert to
324 the new Python class adding / removing relationships as needed.
325
326 @param moveTarget: organizer in DMD path
327 @type moveTarget: string
328 @param deviceNames: devices to move
329 @type deviceNames: list of stringa
330 @param REQUEST: Zope REQUEST object
331 @type REQUEST: Zope REQUEST object
332 """
333 if not moveTarget or not deviceNames: return self()
334 target = self.getDmdRoot(self.dmdRootName).getOrganizer(moveTarget)
335 if isinstance(deviceNames, basestring): deviceNames = (deviceNames,)
336 targetClass = target.getPythonDeviceClass()
337 numExports = 0
338 for devname in deviceNames:
339 devicewasExported = self._moveDevice(devname, target, targetClass)
340 if devicewasExported:
341 numExports += 1
342 return numExports
343
344
345 security.declareProtected(ZEN_DELETE_DEVICE, 'removeDevices')
346 - def removeDevices(self, deviceNames=None, deleteStatus=False,
347 deleteHistory=False, deletePerf=False,REQUEST=None):
348 """
349 See IManageDevice overrides DeviceManagerBase.removeDevices
350 """
351 if not deviceNames: return self()
352 if isinstance(deviceNames, basestring): deviceNames = (deviceNames,)
353 for devname in deviceNames:
354 dev = self.findDevice(devname)
355 dev.deleteDevice(deleteStatus=deleteStatus,
356 deleteHistory=deleteHistory, deletePerf=deletePerf)
357 if REQUEST:
358 messaging.IMessageSender(self).sendToBrowser(
359 'Devices Deleted',
360 "Devices were deleted: %s." % ', '.join(deviceNames)
361 )
362 if REQUEST.has_key('oneKeyValueSoInstanceIsntEmptyAndEvalToFalse'):
363 return 'Devices were deleted: %s.' % ', '.join(deviceNames)
364 else:
365 return self.callZenScreen(REQUEST)
366
367
368 security.declareProtected('View', 'getEventDeviceInfo')
385
386
387 security.declareProtected('View', 'getDeviceWinInfo')
389 """
390 Return list of (devname,user,passwd,url) for each device.
391 user and passwd are used to connect via wmi.
392 """
393 ffunc = None
394 starttime = time.time()
395 if lastPoll > 0:
396 lastPoll = DateTime.DateTime(lastPoll)
397 ffunc = lambda x: x.getSnmpLastCollection() > lastPoll
398 if eventlog:
399 ffunc = lambda x: x.zWinEventlog
400 devinfo = []
401 for dev in self.getSubDevices(devfilter=ffunc):
402 if not dev.monitorDevice(): continue
403 if dev.getProperty('zWmiMonitorIgnore', False): continue
404 user = dev.getProperty('zWinUser','')
405 passwd = dev.getProperty( 'zWinPassword', '')
406 sev = dev.getProperty( 'zWinEventlogMinSeverity', '')
407 devinfo.append((dev.id, str(user), str(passwd), sev, dev.absolute_url()))
408 return starttime, devinfo
409
410
412 """
413 Return a list of (devname, user, passwd, {'EvtSys':0,'Exchange':0})
414 """
415 svcinfo = []
416 allsvcs = {}
417 for s in self.getSubComponents("WinService"):
418 svcs=allsvcs.setdefault(s.hostname(),{})
419 name = s.name()
420 if isinstance(name, unicode):
421 name = name.encode(s.zCollectorDecoding)
422 svcs[name] = (s.getStatus(), s.getAqProperty('zFailSeverity'))
423 for dev in self.getSubDevices():
424 if not dev.monitorDevice(): continue
425 if dev.getProperty( 'zWmiMonitorIgnore', False): continue
426 svcs = allsvcs.get(dev.getId(), {})
427 if not svcs and not dev.zWinEventlog: continue
428 user = dev.getProperty('zWinUser','')
429 passwd = dev.getProperty( 'zWinPassword', '')
430 svcinfo.append((dev.id, str(user), str(passwd), svcs))
431 return svcinfo
432
433
434 security.declareProtected('View', 'searchDeviceSummary')
436 """
437 Search device summary index and return device objects
438 """
439 if not query: return []
440 zcatalog = self._getCatalog()
441 if not zcatalog: return []
442 results = zcatalog({'summary':query})
443 return self._convertResultsToObj(results)
444
445
446 security.declareProtected('View', 'searchInterfaces')
448 """
449 Search interfaces index and return interface objects
450 """
451 if not query: return []
452 zcatalog = getattr(self, 'interfaceSearch', None)
453 if not zcatalog: return []
454 results = zcatalog(query)
455 return self._convertResultsToObj(results)
456
457
459 devices = []
460 for brain in results:
461 try:
462 devobj = self.getObjByPath(brain.getPrimaryId)
463 devices.append(devobj)
464 except KeyError:
465 log.warn("bad path '%s' in index" % brain.getPrimaryId)
466
467 return devices
468
470 """
471 Returns all devices whose ip/id/title match devicename.
472 ip/id matches are at the front of the list.
473
474 @rtype: list of brains
475 """
476 idIpQuery = Or( MatchGlob('id', devicename),
477 Eq('getDeviceIp', devicename) )
478 if useTitle:
479 titleOrIdQuery = MatchGlob('titleOrId', devicename)
480 query = Or( idIpQuery, titleOrIdQuery )
481 rankSort = RankByQueries_Max( ( idIpQuery, 16 ),
482 ( titleOrIdQuery, 8 ) )
483 devices = self._getCatalog().evalAdvancedQuery(query, (rankSort,))
484 else:
485 devices = self._getCatalog().evalAdvancedQuery(idIpQuery)
486 return devices
487
489 """
490 Look up a device and return its path
491 """
492 ret = self._findDevice(devicename)
493 if not ret: return ""
494 return ret[0].getPrimaryId
495
497 """
498 Returns the first device whose ip/id matches devicename. If
499 there is no ip/id match, return the first device whose title
500 matches devicename.
501 """
502 ret = self._findDevice(devicename)
503 if ret: return ret[0].getObject()
504
506 """
507 Returns the first device that has an ip/id that matches devicename
508 """
509 ret = self._findDevice( devicename, False )
510 if ret: return ret[0].getObject()
511
513 """
514 Look up device in catalog and return it. devicename
515 must match device id exactly
516 """
517 for brains in self._getCatalog()(id=devicename):
518 dev = brains.getObject()
519 if dev.id == devicename:
520 return dev
521
523 """
524 look up device in catalog and return its pingStatus
525 """
526 dev = self.findDevice(devicename)
527 if dev: return dev.getPingStatusNumber()
528
529
531 """
532 Return generator of components, by meta_type if specified
533 """
534 zcat = self.componentSearch
535 res = zcat({'meta_type': meta_type, 'monitored': monitored})
536 for b in res:
537 try:
538 c = self.getObjByPath(b.getPrimaryId)
539 if self.checkRemotePerm("View", c):
540 yield c
541 except KeyError:
542 log.warn("bad path '%s' in index 'componentSearch'",
543 b.getPrimaryId)
544
545
546 security.declareProtected("ZenCommon", "getMonitoredComponents")
548 """
549 Return monitored components for devices within this DeviceDeviceClass
550 """
551 return self.getSubComponents()
552
553
554 security.declareProtected('View', 'getRRDTemplates')
556 """
557 Return the actual RRDTemplate instances.
558 """
559 templates = {}
560 if not context: context = self
561 mychain = aq_chain(context)
562 mychain.reverse()
563 for obj in mychain:
564 try:
565 templates.update(dict((t.id, t) for t in obj.rrdTemplates()))
566 except AttributeError:
567 pass
568 return templates.values()
569
570
572 """
573 Returns all available templates
574 """
575 def cmpTemplates(a, b):
576 return cmp(a.id.lower(), b.id.lower())
577 templates = self.getRRDTemplates()
578 templates.sort(cmpTemplates)
579 pdc = self.getPythonDeviceClass()
580 return [ t for t in templates
581 if issubclass(pdc, t.getTargetPythonClass()) ]
582
583
585 """
586 This will bind available templates to the zDeviceTemplates
587 """
588 return self.setZenProperty('zDeviceTemplates', ids, REQUEST)
589
603
604
606 """
607 Return all RRDTemplates at this level and below in the object tree.
608 If rrdts is provided then it must be a list of RRDTemplates which
609 will be extended with the templates from here and returned.
610
611 The original getAllRRDTemplates() method has been renamed
612 getAllRRDTemplatesPainfully(). It walks the object tree looking
613 for templates which is a very slow way of going about things.
614 The newer RRDTemplate.YieldAllRRDTemplate() method uses the
615 searchRRDTemplates catalog to speed things up dramatically.
616 YieldAllRRDTemplates is smart enough to revert to
617 getAllRRDTemplatesPainfully if the catalog is not present.
618
619 The searchRRDTemplates catalog was added in 2.2
620 """
621 if rrdts is None:
622 rrdts = []
623 rrdts.extend(RRDTemplate.YieldAllRRDTemplates(self))
624 return rrdts
625
626
628 """
629 RRDTemplate.YieldAllRRDTemplates() is probably what you want.
630 It takes advantage of the searchRRDTemplates catalog to get
631 much better performance. This method iterates over objects looking
632 for templates which is a slow, painful process.
633 """
634 if rrdts is None: rrdts = []
635 rrdts.extend(self.rrdTemplates())
636 for dev in self.devices():
637 rrdts += dev.objectValues('RRDTemplate')
638 for comps in dev.getDeviceComponents():
639 rrdts += comps.objectValues('RRDTemplate')
640 for child in self.children():
641 child.getAllRRDTemplatesPainfully(rrdts)
642 return rrdts
643
644
645 security.declareProtected('Add DMD Objects', 'manage_addRRDTemplate')
660
661
662 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
663 'manage_copyRRDTemplates')
682
683
684 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
685 'manage_pasteRRDTemplates')
723
724
725 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
726 'manage_copyAndPasteRRDTemplates')
747
748
749 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
750 'manage_deleteRRDTemplates')
774
775
777 """
778 Make the catalog for device searching
779 """
780 from Products.ZCatalog.ZCatalog import manage_addZCatalog
781
782
783 manage_addZCatalog(self, self.default_catalog,
784 self.default_catalog)
785 zcat = self._getOb(self.default_catalog)
786 cat = zcat._catalog
787 for idxname in ['id',
788 'getDeviceIp','getDeviceClassPath','getProdState','titleOrId']:
789 cat.addIndex(idxname, makeCaseInsensitiveFieldIndex(idxname))
790 cat.addIndex('getPhysicalPath', makePathIndex('getPhysicalPath'))
791 cat.addIndex('path', makeMultiPathIndex('path'))
792 zcat.addColumn('getPrimaryId')
793 zcat.addColumn('id')
794 zcat.addColumn('path')
795 zcat.addColumn('details')
796
797
798 fieldIndexes = ['getHWSerialNumber', 'getHWTag',
799 'getHWManufacturerName', 'getHWProductClass',
800 'getOSProductName', 'getOSManufacturerName',
801 'getPerformanceServerName', 'ipAddressAsInt',
802 'getProductionStateString', 'getPriorityString',
803 'getLocationName']
804 keywordIndexes = ['getSystemNames', 'getDeviceGroupNames']
805
806
807 for indexName in fieldIndexes:
808 cat.addIndex(indexName, makeCaseInsensitiveFieldIndex(indexName))
809
810
811 for indexName in keywordIndexes:
812 cat.addIndex(indexName, makeCaseInsensitiveKeywordIndex(indexName))
813
814
815 cat.addIndex('allowedRolesAndUsers', makeCaseSensitiveKeywordIndex('allowedRolesAndUsers'))
816
817
818 manage_addZCatalog(self, "componentSearch", "componentSearch")
819 zcat = self._getOb("componentSearch")
820 cat = zcat._catalog
821 cat.addIndex('meta_type', makeCaseInsensitiveFieldIndex('meta_type'))
822 cat.addIndex('getParentDeviceName',
823 makeCaseInsensitiveFieldIndex('getParentDeviceName'))
824 cat.addIndex('getCollectors',
825 makeCaseInsensitiveKeywordIndex('getCollectors'))
826
827
828 zcat.addIndex('monitored', 'FieldIndex')
829 zcat.addColumn('getPrimaryId')
830 zcat.addColumn('meta_type')
831
832
846
847
856
858 """
859 Provide a set of default options for a zProperty
860
861 @param propname: zProperty name
862 @type propname: string
863 @return: list of zProperty options
864 @rtype: list
865 """
866 if propname == 'zCollectorPlugins':
867 from Products.DataCollector.Plugins import loadPlugins
868 names = [ldr.pluginName for ldr in loadPlugins(self.dmd)]
869 names.sort()
870 return names
871 if propname == 'zCommandProtocol':
872 return ['ssh', 'telnet']
873 if propname == 'zSnmpVer':
874 return ['v1', 'v2c', 'v3']
875 if propname == 'zSnmpAuthType':
876 return ['', 'MD5', 'SHA']
877 if propname == 'zSnmpPrivType':
878 return ['', 'DES', 'AES']
879 return DeviceOrganizer.zenPropertyOptions(self, propname)
880
881
883 """
884 This will result in a push of all the devices to live collectors
885
886 @param REQUEST: Zope REQUEST object
887 @type REQUEST: Zope REQUEST object
888 """
889 self._p_changed = True
890 if REQUEST:
891 messaging.IMessageSender(self).sendToBrowser(
892 'Pushed Changes',
893 'Changes to %s were pushed to collectors.' % self.id
894 )
895 return self.callZenScreen(REQUEST)
896
897
898 security.declareProtected('Change Device', 'setLastChange')
900 """
901 Set the changed datetime for this device.
902
903 @param value: changed datetime. Default is now.
904 @type value: number
905 """
906 if value is None:
907 value = time.time()
908 self._lastChange = float(value)
909
911 """
912 Define this class in terms of a description of the devices it should
913 contain and the protocol by which they would normally be monitored.
914 """
915 t = (description, protocol)
916 if not self.isLocal('devtypes'):
917 self._setProperty('devtypes', [], 'lines')
918 if t not in self.devtypes:
919 self.devtypes.append(t)
920 self._p_changed = True
921
923 t = (description, protocol)
924 if hasattr(self, 'devtypes'):
925 if t in self.devtypes:
926 self.devtypes.remove(t)
927 self._p_changed = True
928
929
930 InitializeClass(DeviceClass)
931