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

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