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