Package Products :: Package ZenUtils :: Module Utils
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenUtils.Utils

   1  ########################################################################### 
   2  # 
   3  # This program is part of Zenoss Core, an open source monitoring platform. 
   4  # Copyright (C) 2007, Zenoss Inc. 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify it 
   7  # under the terms of the GNU General Public License version 2 as published by 
   8  # the Free Software Foundation. 
   9  # 
  10  # For complete information please visit: http://www.zenoss.com/oss/ 
  11  # 
  12  ########################################################################### 
  13   
  14  __doc__="""Utils 
  15   
  16  General utility functions module 
  17   
  18  """ 
  19   
  20  import sys 
  21  import select 
  22  import popen2 
  23  import fcntl 
  24  import time 
  25  import os 
  26  import types 
  27  import logging 
  28  import re 
  29  import socket 
  30  import warnings 
  31  import math 
  32  from decimal import Decimal 
  33  from sets import Set 
  34  import asyncore 
  35  log = logging.getLogger("zen.Utils") 
  36   
  37  from popen2 import Popen4 
  38   
  39  from Acquisition import aq_base 
  40  from zExceptions import NotFound 
  41  from AccessControl import getSecurityManager 
  42  from AccessControl import Unauthorized 
  43  from AccessControl.ZopeGuards import guarded_getattr 
  44  from Acquisition import aq_inner, aq_parent 
  45  from ZServer.HTTPServer import zhttp_channel 
  46   
  47  from Products.ZenUtils.Exceptions import ZenPathError, ZentinelException 
  48  from Products.ZenUtils.jsonutils import unjson 
  49   
  50   
  51  DEFAULT_SOCKET_TIMEOUT = 30 
  52   
  53   
54 -class HtmlFormatter(logging.Formatter):
55 """ 56 Formatter for the logging class 57 """ 58
59 - def __init__(self):
60 logging.Formatter.__init__(self, 61 """<tr class="loggingRow"> 62 <td>%(asctime)s</td> <td>%(levelname)s</td> 63 <td>%(name)s</td> <td>%(message)s</td> 64 </tr> 65 """, 66 "%Y-%m-%d %H:%M:%S")
67
68 - def formatException(self, exc_info):
69 """ 70 Format a Python exception 71 72 @param exc_info: Python exception containing a description of what went wrong 73 @type exc_info: Python exception class 74 @return: formatted exception 75 @rtype: string 76 """ 77 exc = logging.Formatter.formatException(self,exc_info) 78 return """<tr class="tablevalues"><td colspan="4">%s</td></tr>""" % exc
79 80
81 -def setWebLoggingStream(stream):
82 """ 83 Setup logging to log to a browser using a request object. 84 85 @param stream: IO stream 86 @type stream: stream class 87 @return: logging handler 88 @rtype: logging handler 89 """ 90 handler = logging.StreamHandler(stream) 91 handler.setFormatter(HtmlFormatter()) 92 rlog = logging.getLogger() 93 rlog.addHandler(handler) 94 rlog.setLevel(logging.ERROR) 95 zlog = logging.getLogger("zen") 96 zlog.setLevel(logging.INFO) 97 return handler
98 99
100 -def clearWebLoggingStream(handler):
101 """ 102 Clear our web logger. 103 104 @param handler: logging handler 105 @type handler: logging handler 106 """ 107 rlog = logging.getLogger() 108 rlog.removeHandler(handler)
109 110
111 -def convToUnits(number=0, divby=1024.0, unitstr="B"):
112 """ 113 Convert a number to its human-readable form. ie: 4GB, 4MB, etc. 114 115 >>> convToUnits() # Don't do this! 116 '0.0B' 117 >>> convToUnits(None) # Don't do this! 118 '' 119 >>> convToUnits(123456789) 120 '117.7MB' 121 >>> convToUnits(123456789, 1000, "Hz") 122 '123.5MHz' 123 124 @param number: base number 125 @type number: number 126 @param divby: divisor to use to convert to appropriate prefix 127 @type divby: number 128 @param unitstr: base unit of the number 129 @type unitstr: string 130 @return: number with appropriate units 131 @rtype: string 132 """ 133 units = map(lambda x:x + unitstr, ('','K','M','G','T','P')) 134 try: 135 numb = float(number) 136 except: 137 return '' 138 139 sign = 1 140 if numb < 0: 141 numb = abs(numb) 142 sign = -1 143 for unit in units: 144 if numb < divby: break 145 numb /= divby 146 return "%.1f%s" % (numb * sign, unit)
147 148
149 -def travAndColl(obj, toonerel, collect, collectname):
150 """ 151 Walk a series of to one rels collecting collectname into collect 152 153 @param obj: object inside of Zope 154 @type obj: object 155 @param toonerel: a to-one relationship object 156 @type toonerel: toonerel object 157 @param collect: object list 158 @type collect: list 159 @param collectname: name inside of the to-one relation object 160 @type collectname: string 161 @return: list of objects 162 @rtype: list 163 """ 164 #from Acquisition import aq_base 165 value = getattr(aq_base(obj), collectname, None) 166 if value: 167 collect.append(value) 168 rel = getattr(aq_base(obj), toonerel, None) 169 if callable(rel): 170 nobj = rel() 171 if nobj: 172 return travAndColl(nobj, toonerel, collect, collectname) 173 return collect
174 175
176 -def getObjByPath(base, path, restricted=0):
177 """ 178 Get a Zope object by its path (e.g. '/Devices/Server/Linux'). 179 Mostly a stripdown of unrestrictedTraverse method from Zope 2.8.8. 180 181 @param base: base part of a path 182 @type base: string 183 @param path: path to an object inside of the DMD 184 @type path: string 185 @param restricted: flag indicated whether to use securityManager 186 @type restricted: integer 187 @return: object pointed to by the path 188 @rtype: object 189 """ 190 if not path: 191 return base 192 193 _getattr = getattr 194 _none = None 195 marker = object() 196 197 if isinstance(path, str): 198 # Unicode paths are not allowed 199 path = path.split('/') 200 else: 201 path = list(path) 202 203 REQUEST = {'TraversalRequestNameStack': path} 204 path.reverse() 205 path_pop=path.pop 206 207 if len(path) > 1 and not path[0]: 208 # Remove trailing slash 209 path.pop(0) 210 211 if restricted: 212 securityManager = getSecurityManager() 213 else: 214 securityManager = _none 215 216 if not path[-1]: 217 # If the path starts with an empty string, go to the root first. 218 path_pop() 219 base = base.getPhysicalRoot() 220 if (restricted 221 and not securityManager.validate(None, None, None, base)): 222 raise Unauthorized( base ) 223 224 obj = base 225 while path: 226 name = path_pop() 227 228 if name[0] == '_': 229 # Never allowed in a URL. 230 raise NotFound( name ) 231 232 if name == '..': 233 next = aq_parent(obj) 234 if next is not _none: 235 if restricted and not securityManager.validate( 236 obj, obj,name, next): 237 raise Unauthorized( name ) 238 obj = next 239 continue 240 241 bobo_traverse = _getattr(obj, '__bobo_traverse__', _none) 242 if bobo_traverse is not _none: 243 next = bobo_traverse(REQUEST, name) 244 if restricted: 245 if aq_base(next) is not next: 246 # The object is wrapped, so the acquisition 247 # context is the container. 248 container = aq_parent(aq_inner(next)) 249 elif _getattr(next, 'im_self', _none) is not _none: 250 # Bound method, the bound instance 251 # is the container 252 container = next.im_self 253 elif _getattr(aq_base(obj), name, marker) == next: 254 # Unwrapped direct attribute of the object so 255 # object is the container 256 container = obj 257 else: 258 # Can't determine container 259 container = _none 260 try: 261 validated = securityManager.validate( 262 obj, container, name, next) 263 except Unauthorized: 264 # If next is a simple unwrapped property, it's 265 # parentage is indeterminate, but it may have been 266 # acquired safely. In this case validate will 267 # raise an error, and we can explicitly check that 268 # our value was acquired safely. 269 validated = 0 270 if container is _none and \ 271 guarded_getattr(obj, name, marker) is next: 272 validated = 1 273 if not validated: 274 raise Unauthorized( name ) 275 else: 276 if restricted: 277 next = guarded_getattr(obj, name, marker) 278 else: 279 next = _getattr(obj, name, marker) 280 ## Below this is a change from the standard traverse from zope 281 ## it allows a path to use acquisition which is not what 282 ## we want. Our version will fail if one element of the 283 ## path doesn't exist. -EAD 284 #if hasattr(aq_base(obj), name): 285 # next = _getattr(obj, name, marker) 286 #else: 287 # raise NotFound, name 288 if next is marker: 289 try: 290 next=obj[name] 291 except AttributeError: 292 # Raise NotFound for easier debugging 293 # instead of AttributeError: __getitem__ 294 raise NotFound( name ) 295 if restricted and not securityManager.validate( 296 obj, obj, _none, next): 297 raise Unauthorized( name ) 298 obj = next 299 return obj
300 301 302
303 -def checkClass(myclass, className):
304 """ 305 Perform issubclass using class name as string 306 307 @param myclass: generic object 308 @type myclass: object 309 @param className: name of a class 310 @type className: string 311 @return: the value 1 if found or None 312 @rtype: integer or None 313 """ 314 if myclass.__name__ == className: 315 return 1 316 for mycl in myclass.__bases__: 317 if checkClass(mycl, className): 318 return 1
319 320
321 -def lookupClass(productName, classname=None):
322 """ 323 look in sys.modules for our class 324 325 @param productName: object in Products 326 @type productName: string 327 @param classname: class name 328 @type classname: string 329 @return: object at the classname in Products 330 @rtype: object or None 331 """ 332 if sys.modules.has_key(productName): 333 mod = sys.modules[productName] 334 335 elif sys.modules.has_key("Products."+productName): 336 mod = sys.modules["Products."+productName] 337 338 else: 339 return None 340 341 if not classname: 342 classname = productName.split('.')[-1] 343 344 return getattr(mod,classname)
345 346
347 -def importClass(modulePath, classname=""):
348 """ 349 Import a class from the module given. 350 351 @param modulePath: path to module in sys.modules 352 @type modulePath: string 353 @param classname: name of a class 354 @type classname: string 355 @return: the class in the module 356 @rtype: class 357 """ 358 try: 359 if not classname: classname = modulePath.split(".")[-1] 360 try: 361 __import__(modulePath, globals(), locals(), classname) 362 mod = sys.modules[modulePath] 363 except (ValueError, ImportError, KeyError), ex: 364 raise ex 365 366 return getattr(mod, classname) 367 except AttributeError: 368 raise ImportError("Failed while importing class %s from module %s" % ( 369 classname, modulePath))
370 371
372 -def cleanstring(value):
373 """ 374 Take the trailing \x00 off the end of a string 375 376 @param unitstr: sample string 377 @type unitstr: string 378 @return: cleaned string 379 @rtype: string 380 """ 381 if type(value) in types.StringTypes: 382 value = value.split('\0')[0] 383 return value
384 385
386 -def getSubObjects(base, filter=None, descend=None, retobjs=None):
387 """ 388 Do a depth-first search looking for objects that the function filter 389 returns as True. If descend is passed it will check to see if we 390 should keep going down or not 391 392 @param base: base object to start search 393 @type base: object 394 @param filter: filter to apply to each object to determine if it gets added to the returned list 395 @type filter: function or None 396 @param descend: function to apply to each object to determine whether or not to continue searching 397 @type descend: function or None 398 @param retobjs: list of objects found 399 @type retobjs: list 400 @return: list of objects found 401 @rtype: list 402 """ 403 if not retobjs: retobjs = [] 404 for obj in base.objectValues(): 405 if not filter or filter(obj): 406 retobjs.append(obj) 407 if not descend or descend(obj): 408 retobjs = getSubObjects(obj, filter, descend, retobjs) 409 return retobjs
410 411
412 -def getSubObjectsMemo(base, filter=None, descend=None, memo={}):
413 """ 414 Do a depth-first search looking for objects that the function filter 415 returns as True. If descend is passed it will check to see if we 416 should keep going down or not. 417 418 This is a Python iterable. 419 420 @param base: base object to start search 421 @type base: object 422 @param filter: filter to apply to each object to determine if it gets added to the returned list 423 @type filter: function or None 424 @param descend: function to apply to each object to determine whether or not to continue searching 425 @type descend: function or None 426 @param memo: dictionary of objects found (unused) 427 @type memo: dictionary 428 @return: list of objects found 429 @rtype: list 430 """ 431 from Products.ZenRelations.RelationshipManager \ 432 import RelationshipManager 433 if base.meta_type == "To One Relationship": 434 objs = [base.obj] 435 else: 436 objs = base.objectValues() 437 for obj in objs: 438 if (isinstance(obj, RelationshipManager) and 439 not obj.getPrimaryDmdId().startswith(base.getPrimaryDmdId())): 440 continue 441 if not filter or filter(obj): 442 yield obj 443 if not descend or descend(obj): 444 for x in getSubObjectsMemo(obj, filter, descend, memo): 445 yield x
446 447
448 -def getAllConfmonObjects(base):
449 """ 450 Get all ZenModelRM objects in database 451 452 @param base: base object to start searching 453 @type base: object 454 @return: list of objects 455 @rtype: list 456 """ 457 from Products.ZenModel.ZenModelRM import ZenModelRM 458 from Products.ZenModel.ZenModelBase import ZenModelBase 459 from Products.ZenRelations.ToManyContRelationship \ 460 import ToManyContRelationship 461 from Products.ZenRelations.ToManyRelationship \ 462 import ToManyRelationship 463 from Products.ZenRelations.ToOneRelationship \ 464 import ToOneRelationship 465 466 def descend(obj): 467 """ 468 Function to determine whether or not to continue searching 469 @param obj: object 470 @type obj: object 471 @return: True if we want to keep searching 472 @rtype: boolean 473 """ 474 return ( 475 isinstance(obj, ZenModelBase) or 476 isinstance(obj, ToManyContRelationship) or 477 isinstance(obj, ToManyRelationship) or 478 isinstance(obj, ToOneRelationship))
479 480 def filter(obj): 481 """ 482 Filter function to decide whether it's an object we 483 want to know about or not. 484 485 @param obj: object 486 @type obj: object 487 @return: True if we want to keep it 488 @rtype: boolean 489 """ 490 return isinstance(obj, ZenModelRM) and obj.id != "dmd" 491 492 return getSubObjectsMemo(base, filter=filter, descend=descend) 493 494
495 -def zenpathsplit(pathstring):
496 """ 497 Split a zen path and clean up any blanks or bogus spaces in it 498 499 @param pathstring: a path inside of ZENHOME 500 @type pathstring: string 501 @return: a path 502 @rtype: string 503 """ 504 path = pathstring.split("/") 505 path = filter(lambda x: x, path) 506 path = map(lambda x: x.strip(), path) 507 return path
508 509 510
511 -def zenpathjoin(pathar):
512 """ 513 Build a zenpath in its string form 514 515 @param pathstring: a path 516 @type pathstring: string 517 @return: a path 518 @rtype: string 519 """ 520 return "/" + "/".join(pathar)
521 522
523 -def createHierarchyObj(root, name, factory, relpath="", llog=None):
524 """ 525 Create a hierarchy object from its path we use relpath to skip down 526 any missing relations in the path and factory is the constructor for 527 this object. 528 529 @param root: root from which to start 530 @type root: object 531 @param name: path to object 532 @type name: string 533 @param factory: factory object to create 534 @type factory: factory object 535 @param relpath: relationship within which we will recurse as objects are created, if any 536 @type relpath: object 537 @param llog: unused 538 @type llog: object 539 @return: root object of a hierarchy 540 @rtype: object 541 """ 542 unused(llog) 543 rootName = root.id 544 for id in zenpathsplit(name): 545 if id == rootName: continue 546 if id == relpath or getattr(aq_base(root), relpath, False): 547 root = getattr(root, relpath) 548 if not getattr(aq_base(root), id, False): 549 if id == relpath: 550 raise AttributeError("relpath %s not found" % relpath) 551 log.debug("Creating object with id %s in object %s",id,root.getId()) 552 newobj = factory(id) 553 root._setObject(id, newobj) 554 root = getattr(root, id) 555 556 return root
557 558
559 -def getHierarchyObj(root, name, relpath=None):
560 """ 561 Return an object using its path relations are optional in the path. 562 563 @param root: root from which to start 564 @type root: object 565 @param name: path to object 566 @type name: string 567 @param relpath: relationship within which we will recurse as objects are created, if any 568 @type relpath: object 569 @return: root object of a hierarchy 570 @rtype: object 571 """ 572 for id in zenpathsplit(name): 573 if id == relpath or getattr(aq_base(root), relpath, False): 574 root = getattr(root, relpath) 575 if not getattr(root, id, False): 576 raise ZenPathError("Path %s id %s not found on object %s" % 577 (name, id, root.getPrimaryId())) 578 root = getattr(root, id, None) 579 580 return root
581 582 583
584 -def basicAuthUrl(username, password, url):
585 """ 586 Add the username and password to a url in the form 587 http://username:password@host/path 588 589 @param username: username 590 @type username: string 591 @param password: password 592 @type password: string 593 @param url: base URL to add username/password info 594 @type url: string 595 @return: URL with auth information incorporated 596 @rtype: string 597 """ 598 urlar = url.split('/') 599 if not username or not password or urlar[2].find('@') > -1: 600 return url 601 urlar[2] = "%s:%s@%s" % (username, password, urlar[2]) 602 return "/".join(urlar)
603 604 605
606 -def prepId(id, subchar='_'):
607 """ 608 Make an id with valid url characters. Subs [^a-zA-Z0-9-_,.$\(\) ] 609 with subchar. If id then starts with subchar it is removed. 610 611 @param id: user-supplied id 612 @type id: string 613 @return: valid id 614 @rtype: string 615 """ 616 _prepId = re.compile(r'[^a-zA-Z0-9-_,.$\(\) ]').sub 617 _cleanend = re.compile(r"%s+$" % subchar).sub 618 if id is None: 619 raise ValueError('Ids can not be None') 620 if type(id) not in types.StringTypes: 621 id = str(id) 622 id = _prepId(subchar, id) 623 while id.startswith(subchar): 624 if len(id) > 1: id = id[1:] 625 else: id = "-" 626 id = _cleanend("",id) 627 id = id.strip() 628 return str(id)
629 630
631 -def sendEmail(emsg, host, port=25, usetls=0, usr='', pwd=''):
632 """ 633 Send an email. Return a tuple: 634 (sucess, message) where sucess is True or False. 635 636 @param emsg: message to send 637 @type emsg: string 638 @param host: name of e-mail server 639 @type host: string 640 @param port: port number to communicate to the e-mail server 641 @type port: integer 642 @param usetls: boolean-type integer to specify whether to use TLS 643 @type usetls: integer 644 @param usr: username for TLS 645 @type usr: string 646 @param pwd: password for TLS 647 @type pwd: string 648 @return: (sucess, message) where sucess is True or False. 649 @rtype: tuple 650 """ 651 import smtplib 652 socket.setdefaulttimeout(DEFAULT_SOCKET_TIMEOUT) 653 fromaddr = emsg['From'] 654 toaddr = map(lambda x: x.strip(), emsg['To'].split(',')) 655 try: 656 server = smtplib.SMTP(host, port) 657 if usetls: 658 server.ehlo() 659 server.starttls() 660 server.ehlo() 661 if len(usr): server.login(usr, pwd) 662 server.sendmail(fromaddr, toaddr, emsg.as_string()) 663 # Need to catch the quit because some servers using TLS throw an 664 # EOF error on quit, so the email gets sent over and over 665 try: server.quit() 666 except: pass 667 except (smtplib.SMTPException, socket.error, socket.timeout): 668 result = (False, '%s - %s' % tuple(sys.exc_info()[:2])) 669 else: 670 result = (True, '') 671 return result
672 673 674 from twisted.internet.protocol import ProcessProtocol
675 -class SendPageProtocol(ProcessProtocol):
676 out = '' 677 err = '' 678 code = None 679
680 - def __init__(self, msg):
681 self.msg = msg 682 self.out = '' 683 self.err = ''
684
685 - def connectionMade(self):
686 self.transport.write(self.msg) 687 self.transport.closeStdin()
688
689 - def outReceived(self, data):
690 self.out += data
691
692 - def errReceived(self, data):
693 self.err += data
694
695 - def processEnded(self, reason):
696 self.code = reason.value.exitCode
697 698
699 -def sendPage(recipient, msg, pageCommand, deferred=False):
700 """ 701 Send a page. Return a tuple: (success, message) where 702 sucess is True or False. 703 704 @param recipient: name to where a page should be sent 705 @type recipient: string 706 @param msg: message to send 707 @type msg: string 708 @param pageCommand: command that will send a page 709 @type pageCommand: string 710 @return: (sucess, message) where sucess is True or False. 711 @rtype: tuple 712 """ 713 import subprocess 714 env = dict(os.environ) 715 env["RECIPIENT"] = recipient 716 if deferred: 717 from twisted.internet import reactor 718 protocol = SendPageProtocol(msg) 719 d = reactor.spawnProcess( 720 protocol, '/bin/sh', ('/bin/sh', '-c', pageCommand), env) 721 722 # Bad practice to block on a deferred. This is done because our call 723 # chain is not asynchronous and we need to behave like a blocking call. 724 while protocol.code is None: 725 reactor.iterate(0.1) 726 727 return (not protocol.code, protocol.out) 728 else: 729 p = subprocess.Popen(pageCommand, 730 stdin=subprocess.PIPE, 731 stdout=subprocess.PIPE, 732 shell=True, 733 env=env) 734 p.stdin.write(msg) 735 p.stdin.close() 736 response = p.stdout.read() 737 return (not p.wait(), response)
738 739
740 -def zdecode(context, value):
741 """ 742 Convert a string using the decoding found in zCollectorDecoding 743 744 @param context: Zope object 745 @type context: object 746 @param value: input string 747 @type value: string 748 @return: converted string 749 @rtype: string 750 """ 751 if type(value) == type(''): 752 decoding = getattr(context, 'zCollectorDecoding', 'latin-1') 753 value = value.decode(decoding) 754 return value
755 756
757 -def localIpCheck(context, ip):
758 """ 759 Test to see if an IP should not be included in the network map. 760 Uses the zLocalIpAddresses to decide. 761 762 @param context: Zope object 763 @type context: object 764 @param ip: IP address 765 @type ip: string 766 @return: regular expression match or None (if not found) 767 @rtype: re match object 768 """ 769 return re.search(getattr(context, 'zLocalIpAddresses', '^$'), ip)
770
771 -def localInterfaceCheck(context, intname):
772 """ 773 Test to see if an interface should not be included in the network map. 774 Uses the zLocalInterfaceNames to decide. 775 776 @param context: Zope object 777 @type context: object 778 @param intname: network interface name 779 @type intname: string 780 @return: regular expression match or None (if not found) 781 @rtype: re match object 782 """ 783 return re.search(getattr(context, 'zLocalInterfaceNames', '^$'), intname)
784 785
786 -def cmpClassNames(obj, classnames):
787 """ 788 Check to see if any of an object's base classes 789 are in a list of class names. Like isinstance(), 790 but without requiring a class to compare against. 791 792 @param obj: object 793 @type obj: object 794 @param classnames: class names 795 @type classnames: list of strings 796 @return: result of the comparison 797 @rtype: boolean 798 """ 799 finalnames = Set() 800 x = [obj.__class__] 801 while x: 802 thisclass = x.pop() 803 x.extend(thisclass.__bases__) 804 finalnames.add(thisclass.__name__) 805 return bool( Set(classnames).intersection(finalnames) )
806 807
808 -def resequence(context, objects, seqmap, origseq, REQUEST):
809 """ 810 Resequence a seqmap 811 812 @param context: Zope object 813 @type context: object 814 @param objects: objects 815 @type objects: list 816 @param seqmap: sequence map 817 @type seqmap: list 818 @param origseq: sequence map 819 @type origseq: list 820 @param REQUEST: Zope REQUEST object 821 @type REQUEST: Zope REQUEST object 822 @return: 823 @rtype: string 824 """ 825 if seqmap and origseq: 826 try: 827 origseq = tuple([long(s) for s in origseq]) 828 seqmap = tuple([float(s) for s in seqmap]) 829 except ValueError: 830 origseq = () 831 seqmap = () 832 orig = dict([(o.sequence, o) for o in objects]) 833 if origseq: 834 for oldSeq, newSeq in zip(origseq, seqmap): 835 orig[oldSeq].sequence = newSeq 836 def sort(x): 837 """ 838 @param x: unordered sequence items 839 @type x: list 840 @return: ordered sequence items 841 @rtype: list 842 """ 843 x = list(x) 844 x.sort(lambda a, b: cmp(a.sequence, b.sequence)) 845 return x
846 847 for i, obj in enumerate(sort(objects)): 848 obj.sequence = i 849 850 if REQUEST: 851 return context.callZenScreen(REQUEST) 852 853
854 -def cleanupSkins(dmd):
855 """ 856 Prune out objects 857 858 @param dmd: Device Management Database 859 @type dmd: DMD object 860 """ 861 ps = dmd.getPhysicalRoot().zport.portal_skins 862 layers = ps._objects 863 layers = filter(lambda x:getattr(ps, x['id'], False), layers) 864 ps._objects = tuple(layers)
865 866
867 -def edgesToXML(edges, start=()):
868 """ 869 Convert edges to an XML file 870 871 @param edges: edges 872 @type edges: list 873 @return: XML-formatted string 874 @rtype: string 875 """ 876 nodet = '<Node id="%s" prop="%s" icon="%s" color="%s"/>' 877 edget = '<Edge fromID="%s" toID="%s"/>' 878 xmlels = ['<Start name="%s" url="%s"/>' % start] 879 nodeels = [] 880 edgeels = [] 881 for a, b in edges: 882 node1 = nodet % (a[0], a[0], a[1], a[2]) 883 node2 = nodet % (b[0], b[0], b[1], b[2]) 884 edge1 = edget % (a[0], b[0]) 885 if node1 not in nodeels: nodeels.append(node1) 886 if node2 not in nodeels: nodeels.append(node2) 887 if edge1 not in edgeels: edgeels.append(edge1) 888 889 xmlels.extend(nodeels) 890 xmlels.extend(edgeels) 891 xmldoc = "<graph>%s</graph>" % ''.join(list(xmlels)) 892 893 return xmldoc
894 895
896 -def sane_pathjoin(base_path, *args ):
897 """ 898 Joins paths in a saner manner than os.path.join() 899 900 @param base_path: base path to assume everything is rooted from 901 @type base_path: string 902 @param *args: path components starting from $ZENHOME 903 @type *args: strings 904 @return: sanitized path 905 @rtype: string 906 """ 907 path = base_path 908 if args: 909 # Hugely bizarre (but documented!) behaviour with os.path.join() 910 # >>> import os.path 911 # >>> os.path.join( '/blue', 'green' ) 912 # '/blue/green' 913 # >>> os.path.join( '/blue', '/green' ) 914 # '/green' 915 # Work around the brain damage... 916 base = args[0] 917 if base.startswith( base_path ): 918 path_args = [ base ] + [a.strip('/') for a in args[1:] if a != '' ] 919 else: 920 path_args = [a.strip('/') for a in args if a != '' ] 921 922 # Empty strings get thrown out so we may not have anything 923 if len(path_args) > 0: 924 # What if the user splits up base_path and passes it in? 925 pathological_case = os.path.join( *path_args ) 926 if pathological_case.startswith( base_path ): 927 pass 928 929 elif not base.startswith( base_path ): 930 path_args.insert( 0, base_path ) 931 932 # Note: passing in a list to os.path.join() returns a list, 933 # again completely unlike string join() 934 path = os.path.join( *path_args ) 935 936 # os.path.join( '/blue', '' ) returns '/blue/' -- egads! 937 return path.rstrip('/')
938 939
940 -def zenPath(*args):
941 """ 942 Return a path relative to $ZENHOME specified by joining args. The path 943 is not guaranteed to exist on the filesystem. 944 945 >>> import os 946 >>> zenHome = os.environ['ZENHOME'] 947 >>> zenPath() == zenHome 948 True 949 >>> zenPath( '' ) == zenHome 950 True 951 >>> zenPath('Products') == os.path.join(zenHome, 'Products') 952 True 953 >>> zenPath('/Products/') == zenPath('Products') 954 True 955 >>> 956 >>> zenPath('Products', 'foo') == zenPath('Products/foo') 957 True 958 959 # NB: The following is *NOT* true for os.path.join() 960 >>> zenPath('/Products', '/foo') == zenPath('Products/foo') 961 True 962 >>> zenPath(zenPath('Products')) == zenPath('Products') 963 True 964 >>> zenPath(zenPath('Products'), 'orange', 'blue' ) == zenPath('Products', 'orange', 'blue' ) 965 True 966 967 # Pathological case 968 # NB: need to expand out the array returned by split() 969 >>> zenPath() == zenPath( *'/'.split(zenPath()) ) 970 True 971 972 @param *args: path components starting from $ZENHOME 973 @type *args: strings 974 @todo: determine what the correct behaviour should be if $ZENHOME is a symlink! 975 """ 976 zenhome = os.environ.get( 'ZENHOME', '' ) 977 978 path = sane_pathjoin( zenhome, *args ) 979 980 #test if ZENHOME based path exists and if not try bitrock-style path. 981 #if neither exists return the ZENHOME-based path 982 if not os.path.exists(path): 983 brPath = os.path.realpath(os.path.join(zenhome, '..', 'common')) 984 testPath = sane_pathjoin(brPath, *args) 985 if(os.path.exists(testPath)): 986 path = testPath 987 return path
988 989
990 -def zopePath(*args):
991 """ 992 Similar to zenPath() except that this constructs a path based on 993 ZOPEHOME rather than ZENHOME. This is useful on the appliance. 994 If ZOPEHOME is not defined or is empty then return ''. 995 NOTE: A non-empty return value does not guarantee that the path exists, 996 just that ZOPEHOME is defined. 997 998 >>> import os 999 >>> zopeHome = os.environ.setdefault('ZOPEHOME', '/something') 1000 >>> zopePath('bin') == os.path.join(zopeHome, 'bin') 1001 True 1002 >>> zopePath(zopePath('bin')) == zopePath('bin') 1003 True 1004 1005 @param *args: path components starting from $ZOPEHOME 1006 @type *args: strings 1007 """ 1008 zopehome = os.environ.get('ZOPEHOME', '') 1009 return sane_pathjoin( zopehome, *args )
1010 1011
1012 -def binPath(fileName):
1013 """ 1014 Search for the given file in a list of possible locations. Return 1015 either the full path to the file or '' if the file was not found. 1016 1017 >>> len(binPath('zenoss')) > 0 1018 True 1019 >>> len(binPath('zeoup.py')) > 0 # This doesn't exist in Zope 2.12 1020 False 1021 >>> len(binPath('check_http')) > 0 1022 True 1023 >>> binPath('Idontexistreally') == '' 1024 True 1025 1026 @param fileName: name of executable 1027 @type fileName: string 1028 @return: path to file or '' if not found 1029 @rtype: string 1030 """ 1031 # bin and libexec are the usual suspect locations. 1032 # ../common/bin and ../common/libexec are additional options for bitrock 1033 # $ZOPEHOME/bin is an additional option for appliance 1034 for path in (zenPath(d, fileName) for d in ( 1035 'bin', 'libexec', '../common/bin', '../common/libexec')): 1036 if os.path.isfile(path): 1037 return path 1038 path = zopePath('bin', fileName) 1039 if os.path.isfile(path): 1040 return path 1041 return ''
1042 1043
1044 -def extractPostContent(REQUEST):
1045 """ 1046 IE puts the POST content in one place in the REQUEST object, and Firefox in 1047 another. Thus we need to try both. 1048 1049 @param REQUEST: Zope REQUEST object 1050 @type REQUEST: Zope REQUEST object 1051 @return: POST content 1052 @rtype: string 1053 """ 1054 try: 1055 try: 1056 # Firefox 1057 result = REQUEST._file.read() 1058 except: 1059 # IE 1060 result = REQUEST.form.keys()[0] 1061 except: result = '' 1062 return result
1063 1064
1065 -def unused(*args):
1066 """ 1067 A no-op function useful for shutting up pychecker 1068 1069 @param *args: arbitrary arguments 1070 @type *args: objects 1071 @return: count of the objects 1072 @rtype: integer 1073 """ 1074 return len(args)
1075 1076
1077 -def isXmlRpc(REQUEST):
1078 """ 1079 Did we receive a XML-RPC call? 1080 1081 @param REQUEST: Zope REQUEST object 1082 @type REQUEST: Zope REQUEST object 1083 @return: True if REQUEST is an XML-RPC call 1084 @rtype: boolean 1085 """ 1086 if REQUEST and REQUEST['CONTENT_TYPE'].find('xml') > -1: 1087 return True 1088 else: 1089 return False
1090 1091
1092 -def setupLoggingHeader(context, REQUEST):
1093 """ 1094 Extract out the 2nd outermost table 1095 1096 @param context: Zope object 1097 @type context: Zope object 1098 @param REQUEST: Zope REQUEST object 1099 @type REQUEST: Zope REQUEST object 1100 @return: response 1101 @rtype: string 1102 """ 1103 response = REQUEST.RESPONSE 1104 dlh = context.discoverLoggingHeader() 1105 idx = dlh.rindex("</table>") 1106 dlh = dlh[:idx] 1107 idx = dlh.rindex("</table>") 1108 dlh = dlh[:idx] 1109 response.write(str(dlh[:idx])) 1110 1111 return setWebLoggingStream(response)
1112 1113
1114 -def executeCommand(cmd, REQUEST, write=None):
1115 """ 1116 Execute the command and return the output 1117 1118 @param cmd: command to execute 1119 @type cmd: string 1120 @param REQUEST: Zope REQUEST object 1121 @type REQUEST: Zope REQUEST object 1122 @return: result of executing the command 1123 @rtype: string 1124 """ 1125 xmlrpc = isXmlRpc(REQUEST) 1126 result = 0 1127 try: 1128 if REQUEST: 1129 response = REQUEST.RESPONSE 1130 else: 1131 response = sys.stdout 1132 if write is None: 1133 def _write(s): 1134 response.write(s) 1135 response.flush()
1136 write = _write 1137 log.info('Executing command: %s' % ' '.join(cmd)) 1138 f = Popen4(cmd) 1139 while 1: 1140 s = f.fromchild.readline() 1141 if not s: 1142 break 1143 elif write: 1144 write(s) 1145 else: 1146 log.info(s) 1147 except (SystemExit, KeyboardInterrupt): 1148 if xmlrpc: return 1 1149 raise 1150 except ZentinelException, e: 1151 if xmlrpc: return 1 1152 log.critical(e) 1153 except: 1154 if xmlrpc: return 1 1155 raise 1156 else: 1157 result = f.wait() 1158 result = int(hex(result)[:-2], 16) 1159 return result 1160 1161
1162 -def ipsort(a, b):
1163 """ 1164 Compare (cmp()) a + b's IP addresses 1165 These addresses may contain subnet mask info. 1166 1167 @param a: IP address 1168 @type a: string 1169 @param b: IP address 1170 @type b: string 1171 @return: result of cmp(a.ip,b.ip) 1172 @rtype: boolean 1173 """ 1174 # Use 0.0.0.0 instead of blank string 1175 if not a: a = "0.0.0.0" 1176 if not b: b = "0.0.0.0" 1177 1178 # Strip off netmasks 1179 a, b = map(lambda x:x.rsplit("/")[0], (a, b)) 1180 return cmp(*map(socket.inet_aton, (a, b)))
1181 1182
1183 -def unsigned(v):
1184 """ 1185 Convert negative 32-bit values into the 2's complement unsigned value 1186 1187 >>> str(unsigned(-1)) 1188 '4294967295' 1189 >>> unsigned(1) 1190 1L 1191 >>> unsigned(1e6) 1192 1000000L 1193 >>> unsigned(1e10) 1194 10000000000L 1195 1196 @param v: number 1197 @type v: negative 32-bit number 1198 @return: 2's complement unsigned value 1199 @rtype: unsigned int 1200 """ 1201 v = long(v) 1202 if v < 0: 1203 import ctypes 1204 return int(ctypes.c_uint32(v).value) 1205 return v
1206 1207
1208 -def nanToNone(value):
1209 import cPickle 1210 try: 1211 cPickle.dumps(value) 1212 except SystemError: 1213 return None 1214 return value
1215 1216
1217 -def executeStreamCommand(cmd, writefunc, timeout=30):
1218 """ 1219 Execute cmd in the shell and send the output to writefunc. 1220 1221 @param cmd: command to execute 1222 @type cmd: string 1223 @param writefunc: output function 1224 @type writefunc: function 1225 @param timeout: maxium number of seconds to wait for the command to execute 1226 @type timeout: number 1227 """ 1228 child = popen2.Popen4(cmd) 1229 flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL) 1230 fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY) 1231 pollPeriod = 1 1232 endtime = time.time() + timeout 1233 firstPass = True 1234 while time.time() < endtime and ( 1235 firstPass or child.poll()==-1): 1236 firstPass = False 1237 r,w,e = select.select([child.fromchild],[],[],pollPeriod) 1238 if r: 1239 t = child.fromchild.read() 1240 if t: 1241 writefunc(t) 1242 if child.poll()==-1: 1243 writefunc('Command timed out') 1244 import signal 1245 os.kill(child.pid, signal.SIGKILL)
1246 1247
1248 -def monkeypatch(target):
1249 """ 1250 A decorator to patch the decorated function into the given class. 1251 1252 >>> @monkeypatch('Products.ZenModel.DataRoot.DataRoot') 1253 ... def do_nothing_at_all(self): 1254 ... print "I do nothing at all." 1255 ... 1256 >>> from Products.ZenModel.DataRoot import DataRoot 1257 >>> hasattr(DataRoot, 'do_nothing_at_all') 1258 True 1259 >>> DataRoot('dummy').do_nothing_at_all() 1260 I do nothing at all. 1261 1262 1263 @param target: class 1264 @type target: class object 1265 @return: decorator function return 1266 @rtype: function 1267 """ 1268 if isinstance(target, basestring): 1269 mod, klass = target.rsplit('.', 1) 1270 target = importClass(mod, klass) 1271 def patcher(func): 1272 setattr(target, func.__name__, func) 1273 return func
1274 return patcher 1275
1276 -def nocache(f):
1277 """ 1278 Decorator to set headers which force browser to not cache request 1279 1280 This is intended to decorate methods of BrowserViews. 1281 1282 @param f: class 1283 @type f: class object 1284 @return: decorator function return 1285 @rtype: function 1286 """ 1287 def inner(self, *args, **kwargs): 1288 """ 1289 Inner portion of the decorator 1290 1291 @param *args: arguments 1292 @type *args: possible list 1293 @param **kwargs: keyword arguments 1294 @type **kwargs: possible list 1295 @return: decorator function return 1296 @rtype: function 1297 """ 1298 self.request.response.setHeader('Cache-Control', 'no-cache, must-revalidate') 1299 self.request.response.setHeader('Pragma', 'no-cache') 1300 self.request.response.setHeader('Expires', 'Sat, 13 May 2006 18:02:00 GMT') 1301 # Get rid of kw used to prevent browser caching 1302 if kwargs.has_key('_dc'): del kwargs['_dc'] 1303 return f(self, *args, **kwargs)
1304 1305 return inner 1306
1307 -def formreq(f):
1308 """ 1309 Decorator to pass in request.form information as arguments to a method. 1310 1311 These are intended to decorate methods of BrowserViews. 1312 1313 @param f: class 1314 @type f: class object 1315 @return: decorator function return 1316 @rtype: function 1317 """ 1318 def inner(self, *args, **kwargs): 1319 """ 1320 Inner portion of the decorator 1321 1322 @param *args: arguments 1323 @type *args: possible list 1324 @param **kwargs: keyword arguments 1325 @type **kwargs: possible list 1326 @return: decorator function return 1327 @rtype: function 1328 """ 1329 if self.request.REQUEST_METHOD=='POST': 1330 content = extractPostContent(self.request) 1331 try: 1332 args += (unjson(content),) 1333 except ValueError: 1334 kwargs.update(self.request.form) 1335 else: 1336 kwargs.update(self.request.form) 1337 # Get rid of useless Zope thing that appears when no querystring 1338 if kwargs.has_key('-C'): del kwargs['-C'] 1339 # Get rid of kw used to prevent browser caching 1340 if kwargs.has_key('_dc'): del kwargs['_dc'] 1341 return f(self, *args, **kwargs)
1342 1343 return inner 1344 1345
1346 -class Singleton(type):
1347 """ 1348 Metaclass that ensures only a single instance of a class is ever created. 1349 1350 This is accomplished by storing the first instance created as an attribute 1351 of the class itself, then checking that attribute for later constructor 1352 calls. 1353 """
1354 - def __init__(cls, *args, **kwargs):
1355 super(Singleton, cls).__init__(*args, **kwargs) 1356 cls._singleton_instance = None
1357
1358 - def __call__(cls, *args, **kwargs):
1359 if cls._singleton_instance is None: 1360 cls._singleton_instance = super( 1361 Singleton, cls).__call__(*args, **kwargs) 1362 return cls._singleton_instance
1363 1364
1365 -def readable_time(seconds, precision=1):
1366 """ 1367 Convert some number of seconds into a human-readable string. 1368 1369 @param t: The number of seconds to convert 1370 @type t: int 1371 @param precision: The maximum number of time units to include. 1372 @type t: int 1373 @rtype: str 1374 1375 >>> readable_time(None) 1376 '0 seconds' 1377 >>> readable_time(0) 1378 '0 seconds' 1379 >>> readable_time(0.12) 1380 '0 seconds' 1381 >>> readable_time(1) 1382 '1 second' 1383 >>> readable_time(1.5) 1384 '1 second' 1385 >>> readable_time(60) 1386 '1 minute' 1387 >>> readable_time(60*60*3+12) 1388 '3 hours' 1389 >>> readable_time(60*60*3+12, 2) 1390 '3 hours 12 seconds' 1391 1392 """ 1393 if seconds is None: 1394 return '0 seconds' 1395 remaining = abs(seconds) 1396 if remaining < 1: 1397 return '0 seconds' 1398 1399 names = ('year', 'month', 'week', 'day', 'hour', 'minute', 'second') 1400 mults = (60*60*24*365, 60*60*24*30, 60*60*24*7, 60*60*24, 60*60, 60, 1) 1401 result = [] 1402 for name, div in zip(names, mults): 1403 num = Decimal(str(math.floor(remaining/div))) 1404 remaining -= int(num)*div 1405 num = int(num) 1406 if num: 1407 result.append('%d %s%s' %(num, name, num>1 and 's' or '')) 1408 if len(result)==precision: 1409 break 1410 return ' '.join(result)
1411 1412
1413 -def relative_time(t, precision=1, cmptime=None):
1414 """ 1415 Return a human-readable string describing time relative to C{cmptime} 1416 (defaulted to now). 1417 1418 @param t: The time to convert, in seconds since the epoch. 1419 @type t: int 1420 @param precision: The maximum number of time units to include. 1421 @type t: int 1422 @param cmptime: The time from which to compute the difference, in seconds 1423 since the epoch 1424 @type cmptime: int 1425 @rtype: str 1426 1427 >>> relative_time(time.time() - 60*10) 1428 '10 minutes ago' 1429 >>> relative_time(time.time() - 60*10-3, precision=2) 1430 '10 minutes 3 seconds ago' 1431 >>> relative_time(time.time() - 60*60*24*10, precision=2) 1432 '1 week 3 days ago' 1433 >>> relative_time(time.time() - 60*60*24*365-1, precision=2) 1434 '1 year 1 second ago' 1435 >>> relative_time(time.time() + 1 + 60*60*24*7*2) # Add 1 for rounding 1436 'in 2 weeks' 1437 1438 """ 1439 if cmptime is None: 1440 cmptime = time.time() 1441 seconds = Decimal(str(t - cmptime)) 1442 result = readable_time(seconds, precision) 1443 if seconds < 0: 1444 result += ' ago' 1445 else: 1446 result = 'in ' + result 1447 return result
1448 1449
1450 -def is_browser_connection_open(request):
1451 """ 1452 Check to see if the TCP connection to the browser is still open. 1453 1454 This might be used to interrupt an infinite while loop, which would 1455 preclude the thread from being destroyed even though the connection has 1456 been closed. 1457 """ 1458 creation_time = request.environ['channel.creation_time'] 1459 for cnxn in asyncore.socket_map.values(): 1460 if (isinstance(cnxn, zhttp_channel) and 1461 cnxn.creation_time==creation_time): 1462 return True 1463 return False
1464 1465 1466 EXIT_CODE_MAPPING = { 1467 0:'Success', 1468 1:'General error', 1469 2:'Misuse of shell builtins', 1470 126:'Command invoked cannot execute, permissions problem or command is not an executable', 1471 127:'Command not found', 1472 128:'Invalid argument to exit, exit takes only integers in the range 0-255', 1473 130:'Fatal error signal: 2, Command terminated by Control-C' 1474 } 1475
1476 -def getExitMessage(exitCode):
1477 """ 1478 Return a nice exit message that corresponds to the given exit status code 1479 1480 @param exitCode: process exit code 1481 @type exitCode: integer 1482 @return: human-readable version of the exit code 1483 @rtype: string 1484 """ 1485 if exitCode in EXIT_CODE_MAPPING.keys(): 1486 return EXIT_CODE_MAPPING[exitCode] 1487 elif exitCode >= 255: 1488 return 'Exit status out of range, exit takes only integer arguments in the range 0-255' 1489 elif exitCode > 128: 1490 return 'Fatal error signal: %s' % (exitCode-128) 1491 return 'Unknown error code: %s' % exitCode
1492 1493
1494 -def set_context(ob):
1495 """ 1496 Wrap an object in a REQUEST context. 1497 """ 1498 from ZPublisher.HTTPRequest import HTTPRequest 1499 from ZPublisher.HTTPResponse import HTTPResponse 1500 from ZPublisher.BaseRequest import RequestContainer 1501 resp = HTTPResponse(stdout=None) 1502 env = { 1503 'SERVER_NAME':'localhost', 1504 'SERVER_PORT':'8080', 1505 'REQUEST_METHOD':'GET' 1506 } 1507 req = HTTPRequest(None, env, resp) 1508 return ob.__of__(RequestContainer(REQUEST = req))
1509
1510 -def dumpCallbacks(deferred):
1511 """ 1512 Dump the callback chain of a Twisted Deferred object. The chain will be 1513 displayed on standard output. 1514 1515 @param deferred: the twisted Deferred object to dump 1516 @type deferred: a Deferred object 1517 """ 1518 callbacks = deferred.callbacks 1519 print "%-39s %-39s" % ("Callbacks", "Errbacks") 1520 print "%-39s %-39s" % ("-" * 39, "-" * 39) 1521 for cbs in callbacks: 1522 callback = cbs[0][0] 1523 callbackName = "%s.%s" % (callback.__module__, callback.func_name) 1524 errback = cbs[1][0] 1525 errbackName = "%s.%s" % (errback.__module__, errback.func_name) 1526 print "%-39.39s %-39.39s" % (callbackName, errbackName)
1527 1528
1529 -def getObjectsFromCatalog(catalog, query=None, log=None):
1530 """ 1531 Generator that can be used to load all objects of out a catalog and skip 1532 any objects that are no longer able to be loaded. 1533 """ 1534 1535 for brain in catalog(query): 1536 try: 1537 ob = brain.getObject() 1538 yield ob 1539 except (NotFound, KeyError, AttributeError): 1540 if log: 1541 log.warn("Stale %s record: %s", catalog.id, brain.getPath())
1542 1543
1544 -def load_config_override(file, package=None, execute=True):
1545 """Load an additional ZCML file into the context, overriding others. 1546 1547 Use with extreme care. 1548 """ 1549 from zope.configuration import xmlconfig 1550 from Products.Five.zcml import _context 1551 xmlconfig.includeOverrides(_context, file, package=package) 1552 if execute: 1553 _context.execute_actions()
1554