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