1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__="""DataRoot
15
16 DataRoot is the object manager which contains all confmon
17 data objects. It can be used as a global acquisition
18 name space.
19 """
20
21 import re
22 from zope.interface import implements
23 from AccessControl import ClassSecurityInfo
24 from AccessControl import getSecurityManager
25 from OFS.OrderedFolder import OrderedFolder
26 from Globals import DTMLFile
27 from Globals import InitializeClass
28 from Globals import DevelopmentMode
29 from Products.ZenModel.SiteError import SiteError
30 from Products.ZenModel.ZenModelBase import ZenModelBase
31 from Products.ZenModel.ZenMenuable import ZenMenuable
32 from Products.ZenRelations.RelSchema import *
33 from Products.ZenUtils.IpUtil import IpAddressError
34 from Products.ZenWidgets import messaging
35 from Products.ZenUtils.Security import activateSessionBasedAuthentication, activateCookieBasedAuthentication
36 from Commandable import Commandable
37 import socket
38 import os
39 import sys
40 from sets import Set
41 import string
42
43 from Products.ZenUtils.Utils import zenPath, binPath
44 from Products.ZenUtils.Utils import extractPostContent
45 from Products.ZenUtils.jsonutils import json
46
47 from Products.ZenEvents.Exceptions import *
48
49 from ZenModelRM import ZenModelRM
50 from ZenossSecurity import ZEN_COMMON, ZEN_MANAGE_DMD, ZEN_VIEW
51 from interfaces import IDataRoot
60
61
62 addDataRoot = DTMLFile('dtml/addDataRoot',globals())
63
64 __pychecker__='no-override'
65
66 -class DataRoot(ZenModelRM, OrderedFolder, Commandable, ZenMenuable):
67 implements(IDataRoot)
68
69 meta_type = portal_type = 'DataRoot'
70
71 manage_main = OrderedFolder.manage_main
72
73 manage_options = OrderedFolder.manage_options
74
75
76
77 _rq = True
78 uuid = None
79 availableVersion = None
80 lastVersionCheck = 0
81 lastVersionCheckAttempt = 0
82 versionCheckOptIn = True
83 reportMetricsOptIn = True
84 acceptedTerms = True
85 instanceIdentifier = 'Zenoss'
86 smtpHost = 'localhost'
87 pageCommand = '$ZENHOME/bin/zensnpp localhost 444 $RECIPIENT'
88 smtpPort = 25
89 smtpUser = ''
90 smtpPass = ''
91 smtpUseTLS = 0
92 emailFrom = ''
93 iconMap = {}
94 geomapapikey = ''
95 geocache = ''
96 version = ""
97 enableLiveSearch = True
98
99 AUTH_TYPE_SESSION = "session"
100 AUTH_TYPE_COOKIE = "cookie"
101 userAuthType = AUTH_TYPE_SESSION
102 pauseHubNotifications = False
103
104
105 incrementalTreeLoad = False
106
107 _properties=(
108 {'id':'title', 'type': 'string', 'mode':'w'},
109 {'id':'prodStateDashboardThresh','type':'int','mode':'w'},
110 {'id':'prodStateConversions','type':'lines','mode':'w'},
111 {'id':'priorityConversions','type':'lines','mode':'w'},
112 {'id':'priorityDashboardThresh','type':'int','mode':'w'},
113 {'id':'statusConversions','type':'lines','mode':'w'},
114 {'id':'interfaceStateConversions','type':'lines','mode':'w'},
115 {'id':'administrativeRoles','type':'lines','mode':'w'},
116 {'id':'uuid', 'type': 'string', 'mode':'w'},
117 {'id':'availableVersion', 'type': 'string', 'mode':'w'},
118 {'id':'lastVersionCheck', 'type': 'long', 'mode':'w'},
119 {'id':'lastVersionCheckAttempt', 'type': 'long', 'mode':'w'},
120 {'id':'versionCheckOptIn', 'type': 'boolean', 'mode':'w'},
121 {'id':'reportMetricsOptIn', 'type': 'boolean', 'mode':'w'},
122 {'id':'instanceIdentifier', 'type': 'string', 'mode':'w'},
123 {'id':'smtpHost', 'type': 'string', 'mode':'w'},
124 {'id':'smtpPort', 'type': 'int', 'mode':'w'},
125 {'id':'pageCommand', 'type': 'string', 'mode':'w'},
126 {'id':'smtpUser', 'type': 'string', 'mode':'w'},
127 {'id':'smtpPass', 'type': 'string', 'mode':'w'},
128 {'id':'smtpUseTLS', 'type': 'int', 'mode':'w'},
129 {'id':'emailFrom', 'type': 'string', 'mode':'w'},
130 {'id':'geomapapikey', 'type': 'string', 'mode':'w'},
131 {'id':'userAuthType', 'type': 'string', 'mode':'w'},
132 {'id':'geocache', 'type': 'string', 'mode':'w'},
133 {'id':'enableLiveSearch', 'type': 'boolean', 'mode':'w'},
134 {'id':'incrementalTreeLoad', 'type': 'boolean', 'mode':'w'},
135 {'id':'pauseHubNotifications', 'type': 'boolean', 'mode':'w'},
136 )
137
138 _relations = (
139 ('userCommands', ToManyCont(ToOne, 'Products.ZenModel.UserCommand', 'commandable')),
140
141
142
143 ('packs', ToManyCont(ToOne, 'Products.ZenModel.ZenPack', 'root')),
144 ('zenMenus', ToManyCont(
145 ToOne, 'Products.ZenModel.ZenMenu', 'menuable')),
146 )
147
148
149 factory_type_information = (
150 {
151 'id' : 'DataRoot',
152 'meta_type' : 'DataRoot',
153 'description' : """Arbitrary device grouping class""",
154 'icon' : 'DataRoot_icon.gif',
155 'product' : 'ZenModel',
156 'factory' : 'manage_addStatusMonitorconf',
157 'immediate_view' : 'Dashboard',
158 'actions' :
159 (
160 { 'id' : 'settings'
161 , 'name' : 'Settings'
162 , 'action' : 'editSettings'
163 , 'permissions' : ( "Manage DMD", )
164 },
165 { 'id' : 'manage'
166 , 'name' : 'Commands'
167 , 'action' : 'dataRootManage'
168 , 'permissions' : ('Manage DMD',)
169 },
170 { 'id' : 'users'
171 , 'name' : 'Users'
172 , 'action' : 'ZenUsers/manageUserFolder'
173 , 'permissions' : ( 'Manage DMD', )
174 },
175 { 'id' : 'packs'
176 , 'name' : 'ZenPacks'
177 , 'action' : 'ZenPackManager/viewZenPacks'
178 , 'permissions' : ( "Manage DMD", )
179 },
180 { 'id' : 'jobs'
181 , 'name' : 'Jobs'
182 , 'action' : 'joblist'
183 , 'permissions' : ( "Manage DMD", )
184 },
185 { 'id' : 'portlets'
186 , 'name' : 'Portlets'
187 , 'action' : 'editPortletPerms'
188 , 'permissions' : ( "Manage DMD", )
189 },
190 { 'id' : 'daemons'
191 , 'name' : 'Daemons'
192 , 'action' : '../About/zenossInfo'
193 , 'permissions' : ( "Manage DMD", )
194 },
195 { 'id' : 'versions'
196 , 'name' : 'Versions'
197 , 'action' : '../About/zenossVersions'
198 , 'permissions' : ( "Manage DMD", )
199 },
200 { 'id' : 'backups'
201 , 'name' : 'Backups'
202 , 'action' : 'backupInfo'
203 , 'permissions' : ( "Manage DMD", )
204 },
205 { 'id' : 'eventConfig'
206 , 'name' : 'Events'
207 , 'action' : 'eventConfig'
208 , 'permissions' : ( "Manage DMD", )
209 },
210 )
211 },
212 )
213
214 security = ClassSecurityInfo()
215
216
217 prodStateDashboardThresh = 1000
218
219
220 priorityDashboardThresh = 2
221
222 prodStateConversions = [
223 'Production:1000',
224 'Pre-Production:500',
225 'Test:400',
226 'Maintenance:300',
227 'Decommissioned:-1',
228 ]
229
230 priorityConversions = [
231 'Highest:5',
232 'High:4',
233 'Normal:3',
234 'Low:2',
235 'Lowest:1',
236 'Trivial:0',
237 ]
238
239 statusConversions = [
240 'Up:0',
241 'None:-1',
242 'No DNS:-2',
243 ]
244
245 interfaceStateConversions = [
246 'up:1',
247 'down:2',
248 'testing:3',
249 'unknown:4',
250 'dormant:5',
251 'notPresent:6',
252 'lowerLayerDown:7',
253 ]
254
255 administrativeRoles = (
256 "Administrator",
257 "Analyst",
258 "Engineer",
259 "Tester",
260 )
261
262 defaultDateRange = 129600
263 performanceDateRanges = [
264 ('Hourly',129600,),
265 ('Daily',864000,),
266 ('Weekly',3628800,),
267 ('Monthly',41472000,),
268 ('Yearly',62208000,)
269 ]
270
271
272
273 zPrimaryBasePath = ("", "zport")
274
275
280
282 """
283 Override to force redirection to quickstart.
284 """
285 if not self._rq:
286 return self.unrestrictedTraverse('quickstart')()
287 return self()
288
290 """Return the current event list for this managed entity.
291 """
292 return self.ZenEventManager.getEventCount(**kwargs)
293
294 security.declareProtected(ZEN_COMMON, 'getEventClassNames')
296 """
297 Get a list of all event class names within the permission scope.
298 """
299 return self.Events.getOrganizerNames()
300
301
304
305
308
309
310 security.declareProtected(ZEN_COMMON, 'getProdStateConversions')
315
316
317 security.declareProtected(ZEN_COMMON, 'convertProdState')
323
324
325 security.declareProtected(ZEN_COMMON, 'getStatusConversions')
329
330
331 security.declareProtected(ZEN_COMMON, 'convertStatus')
335
336 security.declareProtected(ZEN_COMMON, 'getPriorityConversions')
339
340 security.declareProtected(ZEN_COMMON, 'convertPriority')
343
344 security.declareProtected(ZEN_COMMON, 'getInterfaceStateConversions')
349
350
351 security.declareProtected(ZEN_COMMON, 'convertAttribute')
353 '''convert a numeric production state to a
354 textual representation using the prodStateConversions
355 map'''
356 numbValue = int(numbValue)
357 for line in conversions:
358 line = line.rstrip()
359 (name, number) = line.split(':')
360 if int(number) == numbValue:
361 return name
362 return numbValue
363
364 security.declareProtected(ZEN_COMMON, 'convertStatusToDot')
366 colors = ['green', 'yellow', 'orange', 'red']
367 try:
368 return colors[status]
369 except IndexError:
370 return 'grey'
371
372 security.declareProtected(ZEN_COMMON, 'getConversions')
374 """get the text list of itmes that convert to ints"""
375 convs = []
376 for item in attribute:
377 tup = item.split(':')
378 try:
379 tup[1] = int(tup[1])
380 except (IndexError, ValueError):
381 continue
382 convs.append(tup)
383 return convs
384
385 security.declarePublic('filterObjectsRegex')
388 """filter a list of objects based on a regex"""
389 filter = re.compile(filter).search
390 filteredObjects = []
391 for obj in objects:
392 value = getattr(obj, filteratt, None)
393 if callable(value):
394 value = value()
395 fvalue = filter(value)
396 if (fvalue and not negatefilter) or (not fvalue and negatefilter):
397 filteredObjects.append(obj)
398 return filteredObjects
399
400
401 security.declareProtected('View', 'myUserGroups')
403 user = self.REQUEST.get('AUTHENTICATED_USER')
404 if hasattr(user, 'getGroups'):
405 return user.getGroups()
406 else:
407 return ()
408
409
410 security.declareProtected('View', 'getAllUserGroups')
412 return self.acl_users.getGroups()
413
414
415 security.declareProtected(ZEN_VIEW, 'zenoss_error_message')
418 """Return an error page that is more friendly then the standard stack
419 trace + feedback page for ConflictErrors and MySQL errors (we need to
420 add out of disk space errors). If one of these is not found we return
421 the old stacktrace page
422 """
423 from ZODB.POSException import ConflictError
424 from Products.ZenEvents.Exceptions import MySQLConnectionError
425 from _mysql_exceptions import MySQLError
426 if isinstance(error_value, ConflictError):
427 return self.zenoss_conflict_error_message()
428 elif isinstance(error_value, MySQLConnectionError) \
429 or isinstance(error_value, MySQLError):
430 return self.zenoss_mysql_error_message(error_value=error_value)
431
432 from traceback import format_exception
433 error_formatted = ''.join(format_exception(error_type, error_value, error_traceback))
434 return self.zenoss_feedback_error_message(error_type=error_type,
435 error_value=error_value,
436 error_traceback=error_traceback,
437 error_formatted=error_formatted)
438
439
441 ''' send an email to the zenoss error email address
442 then send user to a thankyou page or an email error page.
443 '''
444 if self.smtpHost: host = self.smtpHost
445 else: host = None
446 port = self.smtpPort and self.smtpPort or 25
447 usetls = self.smtpUseTLS
448 usr = self.smtpUser
449 pwd = self.smtpPass
450
451 mailSent = SiteError.sendErrorEmail(
452 self.REQUEST.errorType,
453 self.REQUEST.errorValue,
454 self.REQUEST.errorTrace,
455 self.REQUEST.errorUrl,
456 self.About.getZenossRevision(),
457 self.About.getZenossVersionShort(),
458 self.REQUEST.contactName,
459 self.REQUEST.contactEmail,
460 self.REQUEST.comments,
461 host, port, usetls, usr, pwd)
462 if not mailSent:
463 body = SiteError.createReport(
464 self.REQUEST.errorType,
465 self.REQUEST.errorValue,
466 self.REQUEST.errorTrace,
467 self.REQUEST.errorUrl,
468 self.About.getZenossRevision(),
469 self.About.getZenossVersionShort(),
470 True,
471 self.REQUEST.contactName,
472 self.REQUEST.contactEmail,
473 self.REQUEST.comments)
474 return self.errorEmailFailure(toAddress=SiteError.ERRORS_ADDRESS,
475 body=body)
476 return self.errorEmailThankYou()
477
478
479
481 '''Write out csv rows with the given objects and fields.
482 If out is not None then call out.write() with the result and return None
483 otherwise return the result.
484 Each item in fieldsAndLabels is either a string representing a
485 field/key/index (see getDataField) or it is a tuple of (field, label)
486 where label is the string to be used in the first row as label
487 for that column.
488 Objects can be either dicts, lists/tuples or other objects. Field
489 is interpreted as a key, index or attribute depending on what
490 object is.
491 Method names can be passed instead of attribute/key/indices as field.
492 In this case the method is called and the return value is used in
493 the export.
494 '''
495 import csv
496 import StringIO
497 if out:
498 buffer = out
499 else:
500 buffer = StringIO.StringIO()
501 fields = []
502 labels = []
503 if not fieldsAndLabels:
504 fieldsAndLabels = []
505 if not objects:
506 objects = []
507 for p in fieldsAndLabels:
508 if isinstance(p, tuple):
509 fields.append(p[0])
510 labels.append(p[1])
511 else:
512 fields.append(p)
513 labels.append(p)
514 writer = csv.writer(buffer)
515 writer.writerow(labels)
516 def getDataField(thing, field):
517 if isinstance(thing, dict):
518 value = thing.get(field, '')
519 elif isinstance(thing, list) or isinstance(thing, tuple):
520 value = thing[int(field)]
521 else:
522 value = getattr(thing, field, '')
523 if isinstance(value, ZenModelBase):
524 value = value.id
525 elif callable(value):
526 value = value()
527 if value == None:
528 value = ''
529 return str(value)
530 for o in objects:
531 writer.writerow([getDataField(o,f) for f in fields])
532 if out:
533 result = None
534 else:
535 result = buffer.getvalue()
536 return result
537
538
540 ''' Called by Commandable.doCommand() to ascertain objects on which
541 a UserCommand should be executed.
542 '''
543 raise NotImplemented
544
545
548
549
551 ''' Return self.emailFrom or a suitable default
552 '''
553 return self.emailFrom or 'zenossuser_%s@%s' % (
554 getSecurityManager().getUser().getId(), socket.getfqdn())
555
556
558 """Checks a valid id
559 """
560 if len(id) > 128:
561 return 'ZenPack names can not be longer than 128 characters.'
562 allowed = Set(list(string.ascii_letters)
563 + list(string.digits)
564 + ['_'])
565 attempted = Set(list(id))
566 if not attempted.issubset(allowed):
567 return 'Only letters, digits and underscores are allowed' + \
568 ' in ZenPack names.'
569 return ZenModelRM.checkValidId(self, id, prep_id)
570
571
581
583 """
584 Clear the Google Maps cache.
585 """
586 self.geocache = ''
587
588 security.declareProtected(ZEN_COMMON, 'getGeoCache')
589 @json
591 cachestr = self.geocache
592 for char in ('\\r', '\\n'):
593 cachestr = cachestr.replace(char, ' ')
594 return cachestr
595
596 - def goToStatusPage(self, objid, REQUEST=None):
597 """ Find a device or network and redirect
598 to its status page.
599 """
600 import urllib
601 objid = urllib.unquote(objid)
602 try:
603 devid = objid
604 if not devid.endswith('*'): devid += '*'
605 obj = self.Devices.findDevice(devid)
606 except:
607 obj=None
608 if not obj:
609 try:
610 obj = self.Networks.getNet(objid)
611 except IpAddressError:
612 return None
613 if not obj: return None
614 if REQUEST is not None:
615 REQUEST['RESPONSE'].redirect(obj.getPrimaryUrlPath())
616
617
619 """ Get the XML representation of network nodes
620 and edges using the obj with objid as a root
621 """
622 import urllib
623 objid = urllib.unquote(objid)
624 try:
625 devid = objid
626 if not devid.endswith('*'): devid += '*'
627 obj = self.Devices.findDevice(devid)
628 except: obj=None
629 if not obj:
630 obj = self.Networks.getNet(objid)
631 if not obj:
632 return '<graph><Start name="%s"/></graph>' % objid
633 return obj.getXMLEdges(int(depth), filter,
634 start=(obj.id,obj.getPrimaryUrlPath()))
635
636
637 security.declareProtected(ZEN_MANAGE_DMD, 'getBackupFilesInfo')
639 """
640 Retrieve a list of dictionaries describing the files in
641 $ZENHOME/backups.
642 """
643 import stat
644 import os
645 import datetime
646 import operator
647
648 def FmtFileSize(size):
649 for power, units in ((3, 'GB'), (2, 'MB'), (1, 'KB')):
650 if size > pow(1024, power):
651 fmt = '%.2f %s' % ((size * 1.0)/pow(1024, power), units)
652 break
653 else:
654 fmt = '%s bytes' % size
655 return fmt
656
657 backupsDir = zenPath('backups')
658 fileInfo = []
659 if os.path.isdir(backupsDir):
660 for dirPath, dirNames, fileNames in os.walk(backupsDir):
661 dirNames[:] = []
662 for fileName in fileNames:
663 filePath = os.path.join(backupsDir, fileName)
664 info = os.stat(filePath)
665 fileInfo.append({
666 'fileName': fileName,
667 'size': info[stat.ST_SIZE],
668 'sizeFormatted': FmtFileSize(info[stat.ST_SIZE]),
669 'modDate': info[stat.ST_MTIME],
670 'modDateFormatted': datetime.datetime.fromtimestamp(
671 info[stat.ST_MTIME]).strftime(
672 '%c'),
673 })
674 fileInfo.sort(key=operator.itemgetter('modDate'))
675 return fileInfo
676
677
678 security.declareProtected(ZEN_MANAGE_DMD, 'manage_createBackup')
679 - def manage_createBackup(self, includeEvents=None, includeMysqlLogin=None,
680 timeout=120, REQUEST=None, writeMethod=None):
681 """
682 Create a new backup file using zenbackup and the options specified
683 in the request.
684
685 This method makes use of the fact that DataRoot is a Commandable
686 in order to use Commandable.write
687 """
688 import popen2
689 import fcntl
690 import time
691 import select
692
693 def write(s):
694 if writeMethod:
695 writeMethod(s)
696 elif REQUEST:
697 self.write(REQUEST.RESPONSE, s)
698
699 footer = None
700 if REQUEST and not writeMethod:
701 header, footer = self.commandOutputTemplate().split('OUTPUT_TOKEN')
702 REQUEST.RESPONSE.write(str(header))
703 write('')
704 try:
705 cmd = binPath('zenbackup') + ' -v10'
706 if not includeEvents:
707 cmd += ' --no-eventsdb'
708 if not includeMysqlLogin:
709 cmd += ' --no-save-mysql-access'
710 try:
711 timeout = int(timeout)
712 except ValueError:
713 timeout = 120
714 timeout = max(timeout, 1)
715 child = popen2.Popen4(cmd)
716 flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL)
717 fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY)
718 endtime = time.time() + timeout
719 write('%s' % cmd)
720 write('')
721 pollPeriod = 1
722 firstPass = True
723 while time.time() < endtime and (firstPass or child.poll() == -1):
724 firstPass = False
725 r, w, e = select.select([child.fromchild], [], [], pollPeriod)
726 if r:
727 t = child.fromchild.read()
728
729
730
731 if t:
732 write(t)
733
734 if child.poll() == -1:
735 write('Backup timed out after %s seconds.' % timeout)
736 import signal
737 os.kill(child.pid, signal.SIGKILL)
738
739 write('DONE')
740 except:
741 write('Exception while performing backup.')
742 write('type: %s value: %s' % tuple(sys.exc_info()[:2]))
743 write('')
744 if REQUEST and footer:
745 REQUEST.RESPONSE.write(footer)
746
747
748 security.declareProtected(ZEN_MANAGE_DMD, 'manage_deleteBackups')
777
778
780 """
781 Return a string that represents the Zenoss product that is installed.
782 Currently this is something like 'core' or 'enterprise'. This is
783 used in the version check code to retrieve the available version
784 for the correct product.
785 """
786 return getattr(self, 'productName', 'core')
787
788
790 """
791 Returns pretty messages when errors are raised in templates.
792
793 Access this method from a template like so:
794 <div tal:content="..."
795 ...
796 tal:on-error="structure python:here.dmd.error_handler(error)">
797
798 @param error: A TALES.ErrorInfo instance with attributes type, value
799 and traceback.
800 @return: HTML fragment with an error message
801 """
802 if error.type==MySQLConnectionError:
803 msg = "Unable to connect to the MySQL server."
804
805 elif error.type in [ pythonThresholdException, rpnThresholdException ]:
806 msg= error.value
807
808 else:
809 raise
810
811 return '<b class="errormsg">%s</b>' % msg
812
813 @json
815 """
816 Whether we're in debug mode, so that javascript will behave accordingly
817 """
818 return DevelopmentMode
819
821 """
822 Get a string representative of the code version, to override JS
823 caching.
824 """
825 return self.About.getZenossVersion().full().replace(
826 'Zenoss','').replace(' ','').replace('.','')
827
828 security.declareProtected('Manage DMD', 'zmanage_editProperties')
841
842
843 InitializeClass(DataRoot)
844