1
2
3
4
5
6
7
8
9
10
11 __doc__="""DeviceClass
12 The primary organizer of device objects, managing zProperties and
13 their acquisition.
14 """
15
16 import time
17 from cStringIO import StringIO
18 import transaction
19 import logging
20 log = logging.getLogger('zen.DeviceClass')
21
22 import DateTime
23 from zope.event import notify
24 from zope.container.contained import ObjectMovedEvent
25 from Globals import DTMLFile
26 from Globals import InitializeClass
27 from Acquisition import aq_base, aq_chain
28 from AccessControl import ClassSecurityInfo
29 from AccessControl import Permissions as permissions
30 from ZODB.transact import transact
31
32 from Products.AdvancedQuery import MatchGlob, Or, Eq, RankByQueries_Max, And
33 from Products.CMFCore.utils import getToolByName
34 from Products.ZenMessaging.ChangeEvents.events import DeviceClassMovedEvent
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, makeCaseInsensitiveFieldIndex, makeCaseSensitiveKeywordIndex
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.ZenUtils.guid.interfaces import IGlobalIdentifier
43 from Products.ZenWidgets import messaging
44 from Products.ZenUtils.FakeRequest import FakeRequest
45 from Products.Zuul.catalog.events import IndexingEvent
46 from Products.Zuul.interfaces import ICatalogTool
47
48 import RRDTemplate
49 from DeviceOrganizer import DeviceOrganizer
50 from ZenPackable import ZenPackable
51 from TemplateContainer import TemplateContainer
52
53 _marker = "__MARKER___"
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(key=lambda a: a.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 @transact
191 dev = self.findDeviceByIdExact(devname)
192 if not dev:
193 return
194 guid = IGlobalIdentifier(dev).create()
195 source = dev.deviceClass().primaryAq()
196
197 notify(DeviceClassMovedEvent(dev, dev.deviceClass().primaryAq(), target))
198
199 exported = False
200 oldPath = source.absolute_url_path() + '/'
201 if dev.__class__ != targetClass:
202 from Products.ZenRelations.ImportRM import NoLoginImportRM
203
204 def switchClass(o, module, klass):
205 """
206 Create an XML string representing the module in a
207 new class.
208
209 @param o: file-type object
210 @type o: file-type object
211 @param module: location in DMD
212 @type module: string
213 @param klass: class name
214 @type klass: string
215 @return: XML representation of the class
216 @rtype: string
217 """
218 from xml.dom.minidom import parse
219
220
221
222
223 o.seek(0)
224 dom = parse(o)
225 root = dom.childNodes[0]
226 root.setAttribute('module', module)
227 root.setAttribute('class', klass)
228 for obj in root.childNodes:
229 if obj.nodeType != obj.ELEMENT_NODE:
230 continue
231
232 name = obj.getAttribute('id')
233 if obj.tagName == 'property':
234
235
236 if name in ('zCollectorPlugins', 'zDeviceTemplates') or \
237 name.endswith('Ignore'):
238 root.removeChild(obj)
239
240 elif obj.tagName == 'toone' and \
241 name in ('perfServer', 'location'):
242 pass
243
244 elif obj.tagName == 'tomany' and \
245 name in ('systems', 'groups'):
246 pass
247
248 elif obj.tagName == 'tomanycont' and \
249 name in ('maintenanceWindows',
250 'adminRoles',
251 'userCommands'):
252 pass
253
254 else:
255 log.debug("Removing %s element id='%s'",
256 obj.tagName, name)
257 root.removeChild(obj)
258
259 importFile = StringIO()
260 dom.writexml(importFile)
261 importFile.seek(0)
262 return importFile
263
264 def devExport(d, module, klass):
265 """
266 Create an XML string representing the device d
267 at the DMD location module of type klass.
268
269 @param module: location in DMD
270 @type module: string
271 @param klass: class name
272 @type klass: string
273 @return: XML representation of the class
274 @rtype: string
275 """
276 o = StringIO()
277 d.exportXml(o, exportPasswords=True)
278 return switchClass(o, module, klass)
279
280 def devImport(xmlfile):
281 """
282 Load a new device from a file.
283
284 @param xmlfile: file type object
285 @type xmlfile: file type object
286 """
287 im = NoLoginImportRM(target.devices)
288 im.loadObjectFromXML(xmlfile)
289 im.processLinks()
290
291 module = target.zPythonClass
292 if module:
293 klass = target.zPythonClass.split('.')[-1]
294 else:
295 module = 'Products.ZenModel.Device'
296 klass = 'Device'
297 log.debug('Exporting device %s from %s', devname, source)
298 xmlfile = devExport(dev, module, klass)
299 log.debug('Removing device %s from %s', devname, source)
300 source.devices._delObject(devname)
301 log.debug('Importing device %s to %s', devname, target)
302 devImport(xmlfile)
303 exported = True
304 else:
305 dev._operation = 1
306 source.devices._delObject(devname)
307 target.devices._setObject(devname, dev)
308 dev = target.devices._getOb(devname)
309 IGlobalIdentifier(dev).guid = guid
310 dev.setLastChange()
311 dev.setAdminLocalRoles()
312 dev.index_object()
313 notify(IndexingEvent(dev, idxs=('path', 'searchKeywords'),
314 update_metadata=True))
315
316 return exported
317
318 - def moveDevices(self, moveTarget, deviceNames=None, REQUEST=None):
319 """
320 Override default moveDevices because this is a contained relation.
321 If the Python class bound to a DeviceClass is different we convert to
322 the new Python class adding / removing relationships as needed.
323
324 @param moveTarget: organizer in DMD path
325 @type moveTarget: string
326 @param deviceNames: devices to move
327 @type deviceNames: list of stringa
328 @param REQUEST: Zope REQUEST object
329 @type REQUEST: Zope REQUEST object
330 """
331 if not moveTarget or not deviceNames: return self()
332 target = self.getDmdRoot(self.dmdRootName).getOrganizer(moveTarget)
333 if isinstance(deviceNames, basestring): deviceNames = (deviceNames,)
334 targetClass = target.getPythonDeviceClass()
335 numExports = 0
336 for devname in deviceNames:
337 devicewasExported = self._moveDevice(devname, target, targetClass)
338 if devicewasExported:
339 numExports += 1
340 return numExports
341
342
343 security.declareProtected(ZEN_DELETE_DEVICE, 'removeDevices')
344 - def removeDevices(self, deviceNames=None, deleteStatus=False,
345 deleteHistory=False, deletePerf=False,REQUEST=None):
346 """
347 See IManageDevice overrides DeviceManagerBase.removeDevices
348 """
349 if not deviceNames: return self()
350 if isinstance(deviceNames, basestring): deviceNames = (deviceNames,)
351 for devname in deviceNames:
352 dev = self.findDevice(devname)
353 dev.deleteDevice(deleteStatus=deleteStatus,
354 deleteHistory=deleteHistory, deletePerf=deletePerf)
355 if REQUEST:
356 messaging.IMessageSender(self).sendToBrowser(
357 'Devices Deleted',
358 "Devices were deleted: %s." % ', '.join(deviceNames)
359 )
360 if REQUEST.has_key('oneKeyValueSoInstanceIsntEmptyAndEvalToFalse'):
361 return 'Devices were deleted: %s.' % ', '.join(deviceNames)
362 else:
363 return self.callZenScreen(REQUEST)
364
365
366 security.declareProtected('View', 'getEventDeviceInfo')
383
384
385 security.declareProtected('View', 'getDeviceWinInfo')
387 """
388 Return list of (devname,user,passwd,url) for each device.
389 user and passwd are used to connect via wmi.
390 """
391 ffunc = None
392 starttime = time.time()
393 if lastPoll > 0:
394 lastPoll = DateTime.DateTime(lastPoll)
395 ffunc = lambda x: x.getSnmpLastCollection() > lastPoll
396 if eventlog:
397 ffunc = lambda x: x.getProperty('zWinEventlog', False)
398 devinfo = []
399 for dev in self.getSubDevices(devfilter=ffunc):
400 if not dev.monitorDevice(): continue
401 if dev.getProperty('zWmiMonitorIgnore', False): continue
402 user = dev.getProperty('zWinUser','')
403 passwd = dev.getProperty( 'zWinPassword', '')
404 sev = dev.getProperty( 'zWinEventlogMinSeverity', '')
405 devinfo.append((dev.id, str(user), str(passwd), sev, dev.absolute_url()))
406 return starttime, devinfo
407
408
410 """
411 Return a list of (devname, user, passwd, {'EvtSys':0,'Exchange':0})
412 """
413 svcinfo = []
414 allsvcs = {}
415 for s in self.getSubComponents("WinService"):
416 svcs=allsvcs.setdefault(s.hostname(),{})
417 name = s.name()
418 if isinstance(name, unicode):
419 name = name.encode(s.zCollectorDecoding)
420 svcs[name] = (s.getStatus(), s.getAqProperty('zFailSeverity'))
421 for dev in self.getSubDevices():
422 if not dev.monitorDevice(): continue
423 if dev.getProperty( 'zWmiMonitorIgnore', False): continue
424 svcs = allsvcs.get(dev.getId(), {})
425 if not svcs and not dev.getProperty('zWinEventlog', False): continue
426 user = dev.getProperty('zWinUser','')
427 passwd = dev.getProperty( 'zWinPassword', '')
428 svcinfo.append((dev.id, str(user), str(passwd), svcs))
429 return svcinfo
430
431
432 security.declareProtected('View', 'searchDeviceSummary')
434 """
435 Search device summary index and return device objects
436 """
437 if not query: return []
438 zcatalog = self._getCatalog()
439 if not zcatalog: return []
440 results = zcatalog({'summary':query})
441 return self._convertResultsToObj(results)
442
443
444 security.declareProtected('View', 'searchInterfaces')
446 """
447 Search interfaces index and return interface objects
448 """
449 if not query: return []
450 zcatalog = getattr(self, 'interfaceSearch', None)
451 if not zcatalog: return []
452 results = zcatalog(query)
453 return self._convertResultsToObj(results)
454
455
457 devices = []
458 for brain in results:
459 try:
460 devobj = self.getObjByPath(brain.getPrimaryId)
461 devices.append(devobj)
462 except KeyError:
463 log.warn("bad path '%s' in index" % brain.getPrimaryId)
464
465 return devices
466
468 """
469 Returns all devices whose ip/id/title match devicename.
470 ip/id matches are at the front of the list.
471
472 @rtype: list of brains
473 """
474 idIpQuery = Or( MatchGlob('id', devicename),
475 Eq('getDeviceIp', devicename) )
476 if useTitle:
477 titleOrIdQuery = MatchGlob('titleOrId', devicename)
478 query = Or( idIpQuery, titleOrIdQuery )
479 rankSort = RankByQueries_Max( ( idIpQuery, 16 ),
480 ( titleOrIdQuery, 8 ) )
481 devices = self._getCatalog().evalAdvancedQuery(query, (rankSort,))
482 else:
483 devices = self._getCatalog().evalAdvancedQuery(idIpQuery)
484 return devices
485
487 """
488 Look up a device and return its path
489 """
490 ret = self._findDevice(devicename)
491 if not ret: return ""
492 return ret[0].getPrimaryId
493
495 """
496 Returns the first device whose ip/id matches devicename. If
497 there is no ip/id match, return the first device whose title
498 matches devicename.
499 """
500 ret = self._findDevice(devicename)
501 if ret: return ret[0].getObject()
502
504 """
505 Returns the first device that has an ip/id that matches devicename
506 """
507 ret = self._findDevice( devicename, False )
508 if ret: return ret[0].getObject()
509
511 """
512 Look up device in catalog and return it. devicename
513 must match device id exactly
514 """
515 for brains in self._getCatalog()(id=devicename):
516 dev = brains.getObject()
517 if dev.id == devicename:
518 return dev
519
521 """
522 look up device in catalog and return its pingStatus
523 """
524 dev = self.findDevice(devicename)
525 if dev: return dev.getPingStatusNumber()
526
527
529 """
530 Return generator of components, by meta_type if specified
531 """
532 catalog = ICatalogTool(self)
533 COMPONENT = 'Products.ZenModel.DeviceComponent.DeviceComponent'
534 monitorq, typeq = None, None
535 if monitored:
536 monitorq = Eq('monitored', '1')
537 if meta_type:
538 typeq = Eq('meta_type', meta_type)
539 queries = filter(None, (monitorq, typeq))
540 if queries:
541 query = And(*queries) if len(queries) > 1 else queries[0]
542 else:
543 query = None
544 for brain in catalog.search(COMPONENT, query=query):
545 try:
546 yield brain.getObject()
547 except KeyError:
548 log.warn("bad path '%s' in global catalog", brain.getPath())
549
550
551 security.declareProtected("ZenCommon", "getMonitoredComponents")
553 """
554 Return monitored components for devices within this DeviceDeviceClass
555 """
556 return self.getSubComponents()
557
558
559 security.declareProtected('View', 'getRRDTemplates')
561 """
562 Return the actual RRDTemplate instances.
563 """
564 templates = {}
565 if not context: context = self
566 mychain = aq_chain(context)
567 mychain.reverse()
568 for obj in mychain:
569 try:
570 templates.update(dict((t.id, t) for t in obj.rrdTemplates()))
571 except AttributeError:
572 pass
573 return templates.values()
574
575
584
585
587 """
588 This will bind available templates to the zDeviceTemplates
589 """
590 return self.setZenProperty('zDeviceTemplates', ids, REQUEST)
591
605
606
608 """
609 Return all RRDTemplates at this level and below in the object tree.
610 If rrdts is provided then it must be a list of RRDTemplates which
611 will be extended with the templates from here and returned.
612
613 The original getAllRRDTemplates() method has been renamed
614 getAllRRDTemplatesPainfully(). It walks the object tree looking
615 for templates which is a very slow way of going about things.
616 The newer RRDTemplate.YieldAllRRDTemplate() method uses the
617 searchRRDTemplates catalog to speed things up dramatically.
618 YieldAllRRDTemplates is smart enough to revert to
619 getAllRRDTemplatesPainfully if the catalog is not present.
620
621 The searchRRDTemplates catalog was added in 2.2
622 """
623 if rrdts is None:
624 rrdts = []
625 rrdts.extend(RRDTemplate.YieldAllRRDTemplates(self))
626 return rrdts
627
628
630 """
631 RRDTemplate.YieldAllRRDTemplates() is probably what you want.
632 It takes advantage of the searchRRDTemplates catalog to get
633 much better performance. This method iterates over objects looking
634 for templates which is a slow, painful process.
635 """
636 if rrdts is None: rrdts = []
637 rrdts.extend(self.rrdTemplates())
638 for dev in self.devices():
639 rrdts += dev.objectValues('RRDTemplate')
640 for comps in dev.getDeviceComponents():
641 rrdts += comps.objectValues('RRDTemplate')
642 for child in self.children():
643 child.getAllRRDTemplatesPainfully(rrdts)
644 return rrdts
645
646
647 security.declareProtected('Add DMD Objects', 'manage_addRRDTemplate')
662
663
664 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
665 'manage_copyRRDTemplates')
684
685
686 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
687 'manage_pasteRRDTemplates')
725
726
727 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
728 'manage_copyAndPasteRRDTemplates')
749
750
751 security.declareProtected(ZEN_EDIT_LOCAL_TEMPLATES,
752 'manage_deleteRRDTemplates')
776
777
779 """
780 Make the catalog for device searching
781 """
782 from Products.ZCatalog.ZCatalog import manage_addZCatalog
783
784
785 manage_addZCatalog(self, self.default_catalog,
786 self.default_catalog)
787 zcat = self._getOb(self.default_catalog)
788 cat = zcat._catalog
789 for idxname in ['id',
790 'getDeviceIp','getDeviceClassPath','getProdState','titleOrId']:
791 cat.addIndex(idxname, makeCaseInsensitiveFieldIndex(idxname))
792 cat.addIndex('getPhysicalPath', makePathIndex('getPhysicalPath'))
793 cat.addIndex('path', makeMultiPathIndex('path'))
794 zcat.addColumn('getPrimaryId')
795 zcat.addColumn('id')
796 zcat.addColumn('path')
797
812
813
822
824 """
825 Provide a set of default options for a zProperty
826
827 @param propname: zProperty name
828 @type propname: string
829 @return: list of zProperty options
830 @rtype: list
831 """
832 if propname == 'zCollectorPlugins':
833 from Products.DataCollector.Plugins import loadPlugins
834 return sorted(ldr.pluginName for ldr in loadPlugins(self.dmd))
835 if propname == 'zCommandProtocol':
836 return ['ssh', 'telnet']
837 if propname == 'zSnmpVer':
838 return ['v1', 'v2c', 'v3']
839 if propname == 'zSnmpAuthType':
840 return ['', 'MD5', 'SHA']
841 if propname == 'zSnmpPrivType':
842 return ['', 'DES', 'AES']
843 return DeviceOrganizer.zenPropertyOptions(self, propname)
844
845
847 """
848 This will result in a push of all the devices to live collectors
849
850 @param REQUEST: Zope REQUEST object
851 @type REQUEST: Zope REQUEST object
852 """
853 self._p_changed = True
854 if REQUEST:
855 messaging.IMessageSender(self).sendToBrowser(
856 'Pushed Changes',
857 'Changes to %s were pushed to collectors.' % self.id
858 )
859 return self.callZenScreen(REQUEST)
860
861
862 security.declareProtected('Change Device', 'setLastChange')
864 """
865 Set the changed datetime for this device.
866
867 @param value: changed datetime. Default is now.
868 @type value: number
869 """
870 if value is None:
871 value = time.time()
872 self._lastChange = float(value)
873
875 """
876 Define this class in terms of a description of the devices it should
877 contain and the protocol by which they would normally be monitored.
878 """
879 t = (description, protocol)
880 if not self.isLocal('devtypes'):
881 self._setProperty('devtypes', [], 'lines')
882 if t not in self.devtypes:
883 self.devtypes.append(t)
884 self._p_changed = True
885
887 t = (description, protocol)
888 if hasattr(self, 'devtypes'):
889 if t in self.devtypes:
890 self.devtypes.remove(t)
891 self._p_changed = True
892
893
894 InitializeClass(DeviceClass)
895