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

Source Code for Module Products.ZenUtils.Utils

   1  ############################################################################## 
   2  #  
   3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved. 
   4  #  
   5  # This content is made available according to terms specified in 
   6  # License.zenoss under the directory where your Zenoss product is installed. 
   7  #  
   8  ############################################################################## 
   9   
  10   
  11  from Products.ZenUtils import Map 
  12   
  13  __doc__="""Utils 
  14   
  15  General utility functions module 
  16   
  17  """ 
  18   
  19  import sys 
  20  import select 
  21  import popen2 
  22  import fcntl 
  23  import time 
  24  import os 
  25  import types 
  26  import ctypes 
  27  import tempfile 
  28  import logging 
  29  import re 
  30  import socket 
  31  import inspect 
  32  import threading 
  33  import Queue 
  34  import warnings 
  35  import math 
  36  import contextlib 
  37  import string 
  38  from decimal import Decimal 
  39  import asyncore 
  40  import copy 
  41  from functools import partial 
  42  from decorator import decorator 
  43  from itertools import chain 
  44  import rrdtool 
  45  from subprocess import check_call, call, PIPE, STDOUT, CalledProcessError 
  46  from ZODB.POSException import ConflictError 
  47  log = logging.getLogger("zen.Utils") 
  48   
  49  from popen2 import Popen4 
  50  from twisted.internet import task, reactor 
  51  from Acquisition import aq_base, aq_inner, aq_parent 
  52  from zExceptions import NotFound 
  53  from AccessControl import getSecurityManager, Unauthorized 
  54  from AccessControl.ZopeGuards import guarded_getattr 
  55  from ZServer.HTTPServer import zhttp_channel 
  56   
  57  from Products.ZenUtils.Exceptions import ZenPathError, ZentinelException 
  58  from Products.ZenUtils.jsonutils import unjson 
  59   
  60   
  61  DEFAULT_SOCKET_TIMEOUT = 30 
62 63 64 -class HtmlFormatter(logging.Formatter):
65 """ 66 Formatter for the logging class 67 """ 68
69 - def __init__(self):
70 logging.Formatter.__init__(self, 71 """<tr class="loggingRow"> 72 <td>%(asctime)s</td> <td>%(levelname)s</td> 73 <td>%(name)s</td> <td>%(message)s</td> 74 </tr> 75 """, 76 "%Y-%m-%d %H:%M:%S")
77
78 - def formatException(self, exc_info):
79 """ 80 Format a Python exception 81 82 @param exc_info: Python exception containing a description of what went wrong 83 @type exc_info: Python exception class 84 @return: formatted exception 85 @rtype: string 86 """ 87 exc = logging.Formatter.formatException(self,exc_info) 88 return """<tr class="tablevalues"><td colspan="4">%s</td></tr>""" % exc
89
90 91 -def setWebLoggingStream(stream):
92 """ 93 Setup logging to log to a browser using a request object. 94 95 @param stream: IO stream 96 @type stream: stream class 97 @return: logging handler 98 @rtype: logging handler 99 """ 100 handler = logging.StreamHandler(stream) 101 handler.setFormatter(HtmlFormatter()) 102 rlog = logging.getLogger() 103 rlog.addHandler(handler) 104 rlog.setLevel(logging.ERROR) 105 zlog = logging.getLogger("zen") 106 zlog.setLevel(logging.INFO) 107 return handler
108
109 110 -def clearWebLoggingStream(handler):
111 """ 112 Clear our web logger. 113 114 @param handler: logging handler 115 @type handler: logging handler 116 """ 117 rlog = logging.getLogger() 118 rlog.removeHandler(handler)
119
120 121 -def convToUnits(number=0, divby=1024.0, unitstr="B"):
122 """ 123 Convert a number to its human-readable form. ie: 4GB, 4MB, etc. 124 125 >>> convToUnits() # Don't do this! 126 '0.0B' 127 >>> convToUnits(None) # Don't do this! 128 '' 129 >>> convToUnits(123456789) 130 '117.7MB' 131 >>> convToUnits(123456789, 1000, "Hz") 132 '123.5MHz' 133 134 @param number: base number 135 @type number: number 136 @param divby: divisor to use to convert to appropriate prefix 137 @type divby: number 138 @param unitstr: base unit of the number 139 @type unitstr: string 140 @return: number with appropriate units 141 @rtype: string 142 """ 143 units = map(lambda x:x + unitstr, ('','K','M','G','T','P')) 144 try: 145 numb = float(number) 146 except Exception: 147 return '' 148 149 sign = 1 150 if numb < 0: 151 numb = abs(numb) 152 sign = -1 153 for unit in units: 154 if numb < divby: break 155 numb /= divby 156 return "%.1f%s" % (numb * sign, unit)
157
158 159 -def travAndColl(obj, toonerel, collect, collectname):
160 """ 161 Walk a series of to one rels collecting collectname into collect 162 163 @param obj: object inside of Zope 164 @type obj: object 165 @param toonerel: a to-one relationship object 166 @type toonerel: toonerel object 167 @param collect: object list 168 @type collect: list 169 @param collectname: name inside of the to-one relation object 170 @type collectname: string 171 @return: list of objects 172 @rtype: list 173 """ 174 value = getattr(aq_base(obj), collectname, None) 175 if value: 176 collect.append(value) 177 rel = getattr(aq_base(obj), toonerel, None) 178 if callable(rel): 179 nobj = rel() 180 if nobj: 181 return travAndColl(nobj, toonerel, collect, collectname) 182 return collect
183
184 185 -def getObjByPath(base, path, restricted=0):
186 """ 187 Get a Zope object by its path (e.g. '/Devices/Server/Linux'). 188 Mostly a stripdown of unrestrictedTraverse method from Zope 2.8.8. 189 190 @param base: base part of a path 191 @type base: string 192 @param path: path to an object inside of the DMD 193 @type path: string 194 @param restricted: flag indicated whether to use securityManager 195 @type restricted: integer 196 @return: object pointed to by the path 197 @rtype: object 198 """ 199 if not path: 200 return base 201 202 _getattr = getattr 203 _none = None 204 marker = object() 205 206 if isinstance(path, str): 207 # Unicode paths are not allowed 208 path = path.split('/') 209 else: 210 path = list(path) 211 212 REQUEST = {'TraversalRequestNameStack': path} 213 path.reverse() 214 path_pop=path.pop 215 216 if len(path) > 1 and not path[0]: 217 # Remove trailing slash 218 path.pop(0) 219 220 if restricted: 221 securityManager = getSecurityManager() 222 else: 223 securityManager = _none 224 225 if not path[-1]: 226 # If the path starts with an empty string, go to the root first. 227 path_pop() 228 base = base.getPhysicalRoot() 229 if (restricted 230 and not securityManager.validate(None, None, None, base)): 231 raise Unauthorized( base ) 232 233 obj = base 234 while path: 235 name = path_pop() 236 237 if name[0] == '_': 238 # Never allowed in a URL. 239 raise NotFound( name ) 240 241 if name == '..': 242 next = aq_parent(obj) 243 if next is not _none: 244 if restricted and not securityManager.validate( 245 obj, obj,name, next): 246 raise Unauthorized( name ) 247 obj = next 248 continue 249 250 bobo_traverse = _getattr(obj, '__bobo_traverse__', _none) 251 if bobo_traverse is not _none: 252 next = bobo_traverse(REQUEST, name) 253 if restricted: 254 if aq_base(next) is not next: 255 # The object is wrapped, so the acquisition 256 # context is the container. 257 container = aq_parent(aq_inner(next)) 258 elif _getattr(next, 'im_self', _none) is not _none: 259 # Bound method, the bound instance 260 # is the container 261 container = next.im_self 262 elif _getattr(aq_base(obj), name, marker) == next: 263 # Unwrapped direct attribute of the object so 264 # object is the container 265 container = obj 266 else: 267 # Can't determine container 268 container = _none 269 try: 270 validated = securityManager.validate( 271 obj, container, name, next) 272 except Unauthorized: 273 # If next is a simple unwrapped property, it's 274 # parentage is indeterminate, but it may have been 275 # acquired safely. In this case validate will 276 # raise an error, and we can explicitly check that 277 # our value was acquired safely. 278 validated = 0 279 if container is _none and \ 280 guarded_getattr(obj, name, marker) is next: 281 validated = 1 282 if not validated: 283 raise Unauthorized( name ) 284 else: 285 if restricted: 286 next = guarded_getattr(obj, name, marker) 287 else: 288 next = _getattr(obj, name, marker) 289 ## Below this is a change from the standard traverse from zope 290 ## it allows a path to use acquisition which is not what 291 ## we want. Our version will fail if one element of the 292 ## path doesn't exist. -EAD 293 #if hasattr(aq_base(obj), name): 294 # next = _getattr(obj, name, marker) 295 #else: 296 # raise NotFound, name 297 if next is marker: 298 try: 299 next=obj[name] 300 except AttributeError: 301 # Raise NotFound for easier debugging 302 # instead of AttributeError: __getitem__ 303 raise NotFound( name ) 304 if restricted and not securityManager.validate( 305 obj, obj, _none, next): 306 raise Unauthorized( name ) 307 obj = next 308 return obj
309
310 311 -def capitalizeFirstLetter(s):
312 #Don't use .title or .capitalize, as those will lower-case a camel-cased type 313 return s[0].capitalize() + s[1:] if s else s
314 315 316 RENAME_DISPLAY_TYPES = { 317 'RRDTemplate': 'Template', 318 'ThresholdClass': 'Threshold', 319 'HoltWintersFailure': 'Threshold', # see Trac #29376 320 }
321 322 -def getDisplayType(obj):
323 """ 324 Get a printable string representing the type of this object 325 """ 326 # TODO: better implementation, like meta_display_type per class. 327 typename = str(getattr(obj, 'meta_type', None) or obj.__class__.__name__) if obj else 'None' 328 typename = capitalizeFirstLetter(typename) 329 return RENAME_DISPLAY_TYPES.get(typename, typename)
330
331 332 -def _getName(obj):
333 return getattr(obj, 'getName', None) or getattr(obj, 'name', None) or \ 334 getattr(obj, 'Name', None)
335
336 -def _getId(obj):
337 return getattr(obj, 'getId', None) or getattr(obj, 'id', None) or \ 338 getattr(obj, 'Id', None) or getattr(obj, 'ID', None)
339
340 -def _getUid(obj):
341 return getattr(obj, 'getPrimaryId', None) or getattr(obj, 'uid', None) \ 342 or getattr(obj, 'Uid', None) or getattr(obj, 'UID', None)
343
344 -def getDisplayName(obj):
345 """ 346 Get a printable string representing the name of this object. 347 Always returns something but it may not be pretty. 348 """ 349 # TODO: better implementation, like getDisplayName() per class. 350 name = _getName(obj) or _getId(obj) or _getUid(obj) 351 if name is None: 352 return str(obj) #we tried our best 353 return str(name() if callable(name) else name)
354
355 356 -def getDisplayId(obj):
357 """ 358 Get a printable string representing an ID of this object. 359 Always returns something but it may not be pretty. 360 """ 361 # TODO: better implementation, like getDisplayId() per class. 362 dispId = _getUid(obj) or _getId(obj) or _getName(obj) 363 if dispId is None: 364 return str(obj) #we tried our best 365 return re.sub(r'^/zport/dmd', '', str(dispId() if callable(dispId) else dispId))
366
367 368 -def checkClass(myclass, className):
369 """ 370 Perform issubclass using class name as string 371 372 @param myclass: generic object 373 @type myclass: object 374 @param className: name of a class 375 @type className: string 376 @return: the value 1 if found or None 377 @rtype: integer or None 378 """ 379 if myclass.__name__ == className: 380 return 1 381 for mycl in myclass.__bases__: 382 if checkClass(mycl, className): 383 return 1
384
385 386 -def lookupClass(productName, classname=None):
387 """ 388 look in sys.modules for our class 389 390 @param productName: object in Products 391 @type productName: string 392 @param classname: class name 393 @type classname: string 394 @return: object at the classname in Products 395 @rtype: object or None 396 """ 397 if productName in sys.modules: 398 mod = sys.modules[productName] 399 400 elif "Products."+productName in sys.modules: 401 mod = sys.modules["Products."+productName] 402 403 else: 404 return None 405 406 if not classname: 407 classname = productName.split('.')[-1] 408 409 return getattr(mod,classname)
410
411 412 -def importClass(modulePath, classname=""):
413 """ 414 Import a class from the module given. 415 416 @param modulePath: path to module in sys.modules 417 @type modulePath: string 418 @param classname: name of a class 419 @type classname: string 420 @return: the class in the module 421 @rtype: class 422 """ 423 try: 424 if not classname: classname = modulePath.split(".")[-1] 425 try: 426 __import__(modulePath, globals(), locals(), classname) 427 mod = sys.modules[modulePath] 428 except (ValueError, ImportError, KeyError), ex: 429 raise ex 430 431 return getattr(mod, classname) 432 except AttributeError: 433 raise ImportError("Failed while importing class %s from module %s" % ( 434 classname, modulePath))
435
436 437 -def cleanstring(value):
438 """ 439 Take the trailing \x00 off the end of a string 440 441 @param value: sample string 442 @type value: string 443 @return: cleaned string 444 @rtype: string 445 """ 446 if isinstance(value, basestring) and value.endswith('\0'): 447 value = value[:-1] 448 return value
449
450 451 -def getSubObjects(base, filter=None, descend=None, retobjs=None):
452 """ 453 Do a depth-first search looking for objects that the function filter 454 returns as True. If descend is passed it will check to see if we 455 should keep going down or not 456 457 @param base: base object to start search 458 @type base: object 459 @param filter: filter to apply to each object to determine if it gets added to the returned list 460 @type filter: function or None 461 @param descend: function to apply to each object to determine whether or not to continue searching 462 @type descend: function or None 463 @param retobjs: list of objects found 464 @type retobjs: list 465 @return: list of objects found 466 @rtype: list 467 """ 468 if not retobjs: retobjs = [] 469 for obj in base.objectValues(): 470 if not filter or filter(obj): 471 retobjs.append(obj) 472 if not descend or descend(obj): 473 retobjs = getSubObjects(obj, filter, descend, retobjs) 474 return retobjs
475
476 477 -def getSubObjectsMemo(base, filter=None, descend=None, memo={}):
478 """ 479 Do a depth-first search looking for objects that the function filter 480 returns as True. If descend is passed it will check to see if we 481 should keep going down or not. 482 483 This is a Python iterable. 484 485 @param base: base object to start search 486 @type base: object 487 @param filter: filter to apply to each object to determine if it gets added to the returned list 488 @type filter: function or None 489 @param descend: function to apply to each object to determine whether or not to continue searching 490 @type descend: function or None 491 @param memo: dictionary of objects found (unused) 492 @type memo: dictionary 493 @return: list of objects found 494 @rtype: list 495 """ 496 from Products.ZenRelations.RelationshipManager \ 497 import RelationshipManager 498 if base.meta_type == "To One Relationship": 499 objs = [base.obj] 500 else: 501 objs = base.objectValues() 502 for obj in objs: 503 if (isinstance(obj, RelationshipManager) and 504 not obj.getPrimaryDmdId().startswith(base.getPrimaryDmdId())): 505 continue 506 if not filter or filter(obj): 507 yield obj 508 if not descend or descend(obj): 509 for x in getSubObjectsMemo(obj, filter, descend, memo): 510 yield x
511
512 513 -def getAllConfmonObjects(base):
514 """ 515 Get all ZenModelRM objects in database 516 517 @param base: base object to start searching 518 @type base: object 519 @return: list of objects 520 @rtype: list 521 """ 522 from Products.ZenModel.ZenModelRM import ZenModelRM 523 from Products.ZenModel.ZenModelBase import ZenModelBase 524 from Products.ZenRelations.ToManyContRelationship \ 525 import ToManyContRelationship 526 from Products.ZenRelations.ToManyRelationship \ 527 import ToManyRelationship 528 from Products.ZenRelations.ToOneRelationship \ 529 import ToOneRelationship 530 531 def descend(obj): 532 """ 533 Function to determine whether or not to continue searching 534 @param obj: object 535 @type obj: object 536 @return: True if we want to keep searching 537 @rtype: boolean 538 """ 539 return ( 540 isinstance(obj, ZenModelBase) or 541 isinstance(obj, ToManyContRelationship) or 542 isinstance(obj, ToManyRelationship) or 543 isinstance(obj, ToOneRelationship))
544 545 def filter(obj): 546 """ 547 Filter function to decide whether it's an object we 548 want to know about or not. 549 550 @param obj: object 551 @type obj: object 552 @return: True if we want to keep it 553 @rtype: boolean 554 """ 555 return isinstance(obj, ZenModelRM) and obj.id != "dmd" 556 557 return getSubObjectsMemo(base, filter=filter, descend=descend) 558
559 560 -def zenpathsplit(pathstring):
561 """ 562 Split a zen path and clean up any blanks or bogus spaces in it 563 564 @param pathstring: a path inside of ZENHOME 565 @type pathstring: string 566 @return: a path 567 @rtype: string 568 """ 569 path = pathstring.split("/") 570 path = filter(lambda x: x, path) 571 path = map(lambda x: x.strip(), path) 572 return path
573
574 575 576 -def zenpathjoin(pathar):
577 """ 578 Build a zenpath in its string form 579 580 @param pathar: a path 581 @type pathar: string 582 @return: a path 583 @rtype: string 584 """ 585 return "/" + "/".join(pathar)
586
587 588 -def createHierarchyObj(root, name, factory, relpath="", llog=None):
589 """ 590 Create a hierarchy object from its path we use relpath to skip down 591 any missing relations in the path and factory is the constructor for 592 this object. 593 594 @param root: root from which to start 595 @type root: object 596 @param name: path to object 597 @type name: string 598 @param factory: factory object to create 599 @type factory: factory object 600 @param relpath: relationship within which we will recurse as objects are created, if any 601 @type relpath: object 602 @param llog: unused 603 @type llog: object 604 @return: root object of a hierarchy 605 @rtype: object 606 """ 607 unused(llog) 608 rootName = root.id 609 for id in zenpathsplit(name): 610 if id == rootName: continue 611 if id == relpath or getattr(aq_base(root), relpath, False): 612 root = getattr(root, relpath) 613 if not getattr(aq_base(root), id, False): 614 if id == relpath: 615 raise AttributeError("relpath %s not found" % relpath) 616 log.debug("Creating object with id %s in object %s",id,root.getId()) 617 newobj = factory(id) 618 root._setObject(id, newobj) 619 root = getattr(root, id) 620 621 return root
622
623 624 -def getHierarchyObj(root, name, relpath=None):
625 """ 626 Return an object using its path relations are optional in the path. 627 628 @param root: root from which to start 629 @type root: object 630 @param name: path to object 631 @type name: string 632 @param relpath: relationship within which we will recurse as objects are created, if any 633 @type relpath: object 634 @return: root object of a hierarchy 635 @rtype: object 636 """ 637 for id in zenpathsplit(name): 638 if id == relpath or getattr(aq_base(root), relpath, False): 639 root = getattr(root, relpath) 640 if not getattr(root, id, False): 641 raise ZenPathError("Path %s id %s not found on object %s" % 642 (name, id, root.getPrimaryId())) 643 root = getattr(root, id, None) 644 645 return root
646
647 648 649 -def basicAuthUrl(username, password, url):
650 """ 651 Add the username and password to a url in the form 652 http://username:password@host/path 653 654 @param username: username 655 @type username: string 656 @param password: password 657 @type password: string 658 @param url: base URL to add username/password info 659 @type url: string 660 @return: URL with auth information incorporated 661 @rtype: string 662 """ 663 urlar = url.split('/') 664 if not username or not password or urlar[2].find('@') > -1: 665 return url 666 urlar[2] = "%s:%s@%s" % (username, password, urlar[2]) 667 return "/".join(urlar)
668
669 670 671 -def prepId(id, subchar='_'):
672 """ 673 Make an id with valid url characters. Subs [^a-zA-Z0-9-_,.$\(\) ] 674 with subchar. If id then starts with subchar it is removed. 675 676 @param id: user-supplied id 677 @type id: string 678 @return: valid id 679 @rtype: string 680 """ 681 _prepId = re.compile(r'[^a-zA-Z0-9-_,.$\(\) ]').sub 682 _cleanend = re.compile(r"%s+$" % subchar).sub 683 if id is None: 684 raise ValueError('Ids can not be None') 685 if not isinstance(id, basestring): 686 id = str(id) 687 id = _prepId(subchar, id) 688 while id.startswith(subchar): 689 if len(id) > 1: id = id[1:] 690 else: id = "-" 691 id = _cleanend("",id) 692 id = id.lstrip(string.whitespace + '_').rstrip() 693 return str(id)
694
695 696 -def sendEmail(emsg, host, port=25, usetls=0, usr='', pwd=''):
697 """ 698 Send an email. Return a tuple: 699 (sucess, message) where sucess is True or False. 700 701 @param emsg: message to send 702 @type emsg: email.MIMEText 703 @param host: name of e-mail server 704 @type host: string 705 @param port: port number to communicate to the e-mail server 706 @type port: integer 707 @param usetls: boolean-type integer to specify whether to use TLS 708 @type usetls: integer 709 @param usr: username for TLS 710 @type usr: string 711 @param pwd: password for TLS 712 @type pwd: string 713 @return: (sucess, message) where sucess is True or False. 714 @rtype: tuple 715 """ 716 import smtplib 717 fromaddr = emsg['From'] 718 toaddr = map(lambda x: x.strip(), emsg['To'].split(',')) 719 try: 720 server = smtplib.SMTP(host, port, timeout=DEFAULT_SOCKET_TIMEOUT) 721 if usetls: 722 server.ehlo() 723 server.starttls() 724 server.ehlo() 725 if len(usr): server.login(usr, pwd) 726 server.sendmail(fromaddr, toaddr, emsg.as_string()) 727 # Need to catch the quit because some servers using TLS throw an 728 # EOF error on quit, so the email gets sent over and over 729 try: server.quit() 730 except Exception: pass 731 except (smtplib.SMTPException, socket.error, socket.timeout): 732 result = (False, '%s - %s' % tuple(sys.exc_info()[:2])) 733 else: 734 result = (True, '') 735 return result
736 737 738 from twisted.internet.protocol import ProcessProtocol
739 -class SendPageProtocol(ProcessProtocol):
740 out = '' 741 err = '' 742 code = None 743
744 - def __init__(self, msg):
745 self.msg = msg 746 self.out = '' 747 self.err = ''
748
749 - def connectionMade(self):
750 self.transport.write(self.msg) 751 self.transport.closeStdin()
752
753 - def outReceived(self, data):
754 self.out += data
755
756 - def errReceived(self, data):
757 self.err += data
758
759 - def processEnded(self, reason):
760 self.code = reason.value.exitCode
761
762 763 -def sendPage(recipient, msg, pageCommand, deferred=False):
764 """ 765 Send a page. Return a tuple: (success, message) where 766 sucess is True or False. 767 768 @param recipient: name to where a page should be sent 769 @type recipient: string 770 @param msg: message to send 771 @type msg: string 772 @param pageCommand: command that will send a page 773 @type pageCommand: string 774 @return: (sucess, message) where sucess is True or False. 775 @rtype: tuple 776 """ 777 import subprocess 778 env = dict(os.environ) 779 env["RECIPIENT"] = recipient 780 msg = str(msg) 781 if deferred: 782 from twisted.internet import reactor 783 protocol = SendPageProtocol(msg) 784 d = reactor.spawnProcess( 785 protocol, '/bin/sh', ('/bin/sh', '-c', pageCommand), env) 786 787 # Bad practice to block on a deferred. This is done because our call 788 # chain is not asynchronous and we need to behave like a blocking call. 789 while protocol.code is None: 790 reactor.iterate(0.1) 791 792 return (not protocol.code, protocol.out) 793 else: 794 p = subprocess.Popen(pageCommand, 795 stdin=subprocess.PIPE, 796 stdout=subprocess.PIPE, 797 shell=True, 798 env=env) 799 p.stdin.write(msg) 800 p.stdin.close() 801 response = p.stdout.read() 802 return (not p.wait(), response)
803
804 805 -def zdecode(context, value):
806 """ 807 Convert a string using the decoding found in zCollectorDecoding 808 809 @param context: Zope object 810 @type context: object 811 @param value: input string 812 @type value: string 813 @return: converted string 814 @rtype: string 815 """ 816 if isinstance(value, str): 817 decoding = getattr(context, 'zCollectorDecoding', 'latin-1') 818 value = value.decode(decoding) 819 return value
820
821 822 -def localIpCheck(context, ip):
823 """ 824 Test to see if an IP should not be included in the network map. 825 Uses the zLocalIpAddresses to decide. 826 827 @param context: Zope object 828 @type context: object 829 @param ip: IP address 830 @type ip: string 831 @return: regular expression match or None (if not found) 832 @rtype: re match object 833 """ 834 return re.search(getattr(context, 'zLocalIpAddresses', '^$'), ip)
835
836 -def localInterfaceCheck(context, intname):
837 """ 838 Test to see if an interface should not be included in the network map. 839 Uses the zLocalInterfaceNames to decide. 840 841 @param context: Zope object 842 @type context: object 843 @param intname: network interface name 844 @type intname: string 845 @return: regular expression match or None (if not found) 846 @rtype: re match object 847 """ 848 return re.search(getattr(context, 'zLocalInterfaceNames', '^$'), intname)
849
850 851 -def cmpClassNames(obj, classnames):
852 """ 853 Check to see if any of an object's base classes 854 are in a list of class names. Like isinstance(), 855 but without requiring a class to compare against. 856 857 @param obj: object 858 @type obj: object 859 @param classnames: class names 860 @type classnames: list of strings 861 @return: result of the comparison 862 @rtype: boolean 863 """ 864 finalnames = set() 865 x = [obj.__class__] 866 while x: 867 thisclass = x.pop() 868 x.extend(thisclass.__bases__) 869 finalnames.add(thisclass.__name__) 870 return bool( set(classnames).intersection(finalnames) )
871
872 873 -def resequence(context, objects, seqmap, origseq, REQUEST):
874 """ 875 Resequence a seqmap 876 877 @param context: Zope object 878 @type context: object 879 @param objects: objects 880 @type objects: list 881 @param seqmap: sequence map 882 @type seqmap: list 883 @param origseq: sequence map 884 @type origseq: list 885 @param REQUEST: Zope REQUEST object 886 @type REQUEST: Zope REQUEST object 887 @return: 888 @rtype: string 889 """ 890 if seqmap and origseq: 891 try: 892 origseq = tuple(long(s) for s in origseq) 893 seqmap = tuple(float(s) for s in seqmap) 894 except ValueError: 895 origseq = () 896 seqmap = () 897 orig = dict((o.sequence, o) for o in objects) 898 if origseq: 899 for oldSeq, newSeq in zip(origseq, seqmap): 900 orig[oldSeq].sequence = newSeq 901 for i, obj in enumerate(sorted(objects, key=lambda a: a.sequence)): 902 obj.sequence = i 903 904 if REQUEST: 905 return context.callZenScreen(REQUEST)
906
907 908 -def cleanupSkins(dmd):
909 """ 910 Prune out objects 911 912 @param dmd: Device Management Database 913 @type dmd: DMD object 914 """ 915 ps = dmd.getPhysicalRoot().zport.portal_skins 916 layers = ps._objects 917 layers = filter(lambda x:getattr(ps, x['id'], False), layers) 918 ps._objects = tuple(layers)
919
920 921 -def edgesToXML(edges, start=()):
922 """ 923 Convert edges to an XML file 924 925 @param edges: edges 926 @type edges: list 927 @return: XML-formatted string 928 @rtype: string 929 """ 930 nodet = '<Node id="%s" prop="%s" icon="%s" color="%s"/>' 931 edget = '<Edge fromID="%s" toID="%s"/>' 932 xmlels = ['<Start name="%s" url="%s"/>' % start] 933 nodeels = [] 934 edgeels = [] 935 for a, b in edges: 936 node1 = nodet % (a[0], a[0], a[1], a[2]) 937 node2 = nodet % (b[0], b[0], b[1], b[2]) 938 edge1 = edget % (a[0], b[0]) 939 if node1 not in nodeels: nodeels.append(node1) 940 if node2 not in nodeels: nodeels.append(node2) 941 if edge1 not in edgeels: edgeels.append(edge1) 942 943 xmlels.extend(nodeels) 944 xmlels.extend(edgeels) 945 xmldoc = "<graph>%s</graph>" % ''.join(list(xmlels)) 946 947 return xmldoc
948
949 950 -def sane_pathjoin(base_path, *args ):
951 """ 952 Joins paths in a saner manner than os.path.join() 953 954 @param base_path: base path to assume everything is rooted from 955 @type base_path: string 956 @param *args: path components starting from $ZENHOME 957 @type *args: strings 958 @return: sanitized path 959 @rtype: string 960 """ 961 path = base_path 962 if args: 963 # Hugely bizarre (but documented!) behaviour with os.path.join() 964 # >>> import os.path 965 # >>> os.path.join( '/blue', 'green' ) 966 # '/blue/green' 967 # >>> os.path.join( '/blue', '/green' ) 968 # '/green' 969 # Work around the brain damage... 970 base = args[0] 971 if base.startswith( base_path ): 972 path_args = [ base ] + [a.strip('/') for a in args[1:] if a != '' ] 973 else: 974 path_args = [a.strip('/') for a in args if a != '' ] 975 976 # Empty strings get thrown out so we may not have anything 977 if len(path_args) > 0: 978 # What if the user splits up base_path and passes it in? 979 pathological_case = os.path.join( *path_args ) 980 if pathological_case.startswith( base_path ): 981 pass 982 983 elif not base.startswith( base_path ): 984 path_args.insert( 0, base_path ) 985 986 # Note: passing in a list to os.path.join() returns a list, 987 # again completely unlike string join() 988 path = os.path.join( *path_args ) 989 990 # os.path.join( '/blue', '' ) returns '/blue/' -- egads! 991 return path.rstrip('/')
992
993 994 -def zenPath(*args):
995 """ 996 Return a path relative to $ZENHOME specified by joining args. The path 997 is not guaranteed to exist on the filesystem. 998 999 >>> import os 1000 >>> zenHome = os.environ['ZENHOME'] 1001 >>> zenPath() == zenHome 1002 True 1003 >>> zenPath( '' ) == zenHome 1004 True 1005 >>> zenPath('Products') == os.path.join(zenHome, 'Products') 1006 True 1007 >>> zenPath('/Products/') == zenPath('Products') 1008 True 1009 >>> 1010 >>> zenPath('Products', 'foo') == zenPath('Products/foo') 1011 True 1012 1013 # NB: The following is *NOT* true for os.path.join() 1014 >>> zenPath('/Products', '/foo') == zenPath('Products/foo') 1015 True 1016 >>> zenPath(zenPath('Products')) == zenPath('Products') 1017 True 1018 >>> zenPath(zenPath('Products'), 'orange', 'blue' ) == zenPath('Products', 'orange', 'blue' ) 1019 True 1020 1021 # Pathological case 1022 # NB: need to expand out the array returned by split() 1023 >>> zenPath() == zenPath( *'/'.split(zenPath()) ) 1024 True 1025 1026 @param *args: path components starting from $ZENHOME 1027 @type *args: strings 1028 @todo: determine what the correct behaviour should be if $ZENHOME is a symlink! 1029 """ 1030 zenhome = os.environ.get( 'ZENHOME', '' ) 1031 1032 path = sane_pathjoin( zenhome, *args ) 1033 1034 #test if ZENHOME based path exists and if not try bitrock-style path. 1035 #if neither exists return the ZENHOME-based path 1036 if not os.path.exists(path): 1037 brPath = os.path.realpath(os.path.join(zenhome, '..', 'common')) 1038 testPath = sane_pathjoin(brPath, *args) 1039 if os.path.exists(testPath): 1040 path = testPath 1041 return path
1042
1043 1044 -def zopePath(*args):
1045 """ 1046 Similar to zenPath() except that this constructs a path based on 1047 ZOPEHOME rather than ZENHOME. This is useful on the appliance. 1048 If ZOPEHOME is not defined or is empty then return ''. 1049 NOTE: A non-empty return value does not guarantee that the path exists, 1050 just that ZOPEHOME is defined. 1051 1052 >>> import os 1053 >>> zopeHome = os.environ.setdefault('ZOPEHOME', '/something') 1054 >>> zopePath('bin') == os.path.join(zopeHome, 'bin') 1055 True 1056 >>> zopePath(zopePath('bin')) == zopePath('bin') 1057 True 1058 1059 @param *args: path components starting from $ZOPEHOME 1060 @type *args: strings 1061 """ 1062 zopehome = os.environ.get('ZOPEHOME', '') 1063 return sane_pathjoin( zopehome, *args )
1064
1065 1066 -def binPath(fileName):
1067 """ 1068 Search for the given file in a list of possible locations. Return 1069 either the full path to the file or '' if the file was not found. 1070 1071 >>> len(binPath('zenoss')) > 0 1072 True 1073 >>> len(binPath('zeoup.py')) > 0 # This doesn't exist in Zope 2.12 1074 False 1075 >>> binPath('Idontexistreally') == '' 1076 True 1077 1078 @param fileName: name of executable 1079 @type fileName: string 1080 @return: path to file or '' if not found 1081 @rtype: string 1082 """ 1083 # bin and libexec are the usual suspect locations 1084 paths = [zenPath(d, fileName) for d in ('bin', 'libexec')] 1085 # $ZOPEHOME/bin is an additional option for appliance 1086 paths.append(zopePath('bin', fileName)) 1087 # also check the standard locations for Nagios plugins (/usr/lib(64)/nagios/plugins) 1088 paths.extend(sane_pathjoin(d, fileName) for d in ('/usr/lib/nagios/plugins', 1089 '/usr/lib64/nagios/plugins')) 1090 # and fallback to checking the PATH 1091 paths.extend(sane_pathjoin(d, fileName) for d in os.environ.get('PATH','').split(':')) 1092 for path in paths: 1093 if os.path.isfile(path): 1094 return path 1095 return ''
1096
1097 -def extractPostContent(REQUEST):
1098 """ 1099 IE puts the POST content in one place in the REQUEST object, and Firefox in 1100 another. Thus we need to try both. 1101 1102 @param REQUEST: Zope REQUEST object 1103 @type REQUEST: Zope REQUEST object 1104 @return: POST content 1105 @rtype: string 1106 """ 1107 try: 1108 # Firefox 1109 return REQUEST._file.read() 1110 except Exception: 1111 try: 1112 # IE 1113 return REQUEST.form.keys()[0] 1114 except Exception: 1115 return ''
1116
1117 1118 -def unused(*args):
1119 """ 1120 A no-op function useful for shutting up pychecker 1121 1122 @param *args: arbitrary arguments 1123 @type *args: objects 1124 @return: count of the objects 1125 @rtype: integer 1126 """ 1127 return len(args)
1128
1129 1130 -def isXmlRpc(REQUEST):
1131 """ 1132 Did we receive a XML-RPC call? 1133 1134 @param REQUEST: Zope REQUEST object 1135 @type REQUEST: Zope REQUEST object 1136 @return: True if REQUEST is an XML-RPC call 1137 @rtype: boolean 1138 """ 1139 if REQUEST and REQUEST['CONTENT_TYPE'].find('xml') > -1: 1140 return True 1141 else: 1142 return False
1143
1144 1145 -def setupLoggingHeader(context, REQUEST):
1146 """ 1147 Extract out the 2nd outermost table 1148 1149 @param context: Zope object 1150 @type context: Zope object 1151 @param REQUEST: Zope REQUEST object 1152 @type REQUEST: Zope REQUEST object 1153 @return: response 1154 @rtype: string 1155 """ 1156 response = REQUEST.RESPONSE 1157 dlh = context.discoverLoggingHeader() 1158 idx = dlh.rindex("</table>") 1159 dlh = dlh[:idx] 1160 idx = dlh.rindex("</table>") 1161 dlh = dlh[:idx] 1162 response.write(str(dlh[:idx])) 1163 1164 return setWebLoggingStream(response)
1165
1166 1167 -def executeCommand(cmd, REQUEST, write=None):
1168 """ 1169 Execute the command and return the output 1170 1171 @param cmd: command to execute 1172 @type cmd: string 1173 @param REQUEST: Zope REQUEST object 1174 @type REQUEST: Zope REQUEST object 1175 @return: result of executing the command 1176 @rtype: string 1177 """ 1178 xmlrpc = isXmlRpc(REQUEST) 1179 result = 0 1180 try: 1181 if REQUEST: 1182 response = REQUEST.RESPONSE 1183 else: 1184 response = sys.stdout 1185 if write is None: 1186 def _write(s): 1187 response.write(s) 1188 response.flush()
1189 write = _write 1190 log.info('Executing command: %s' % ' '.join(cmd)) 1191 f = Popen4(cmd) 1192 while 1: 1193 s = f.fromchild.readline() 1194 if not s: 1195 break 1196 elif write: 1197 write(s) 1198 else: 1199 log.info(s) 1200 except (SystemExit, KeyboardInterrupt): 1201 if xmlrpc: return 1 1202 raise 1203 except ZentinelException, e: 1204 if xmlrpc: return 1 1205 log.critical(e) 1206 except Exception: 1207 if xmlrpc: return 1 1208 raise 1209 else: 1210 result = f.wait() 1211 result = int(hex(result)[:-2], 16) 1212 return result 1213
1214 1215 -def ipsort(a, b):
1216 """ 1217 Compare (cmp()) a + b's IP addresses 1218 These addresses may contain subnet mask info. 1219 1220 @param a: IP address 1221 @type a: string 1222 @param b: IP address 1223 @type b: string 1224 @return: result of cmp(a.ip,b.ip) 1225 @rtype: boolean 1226 """ 1227 # Use 0.0.0.0 instead of blank string 1228 if not a: a = "0.0.0.0" 1229 if not b: b = "0.0.0.0" 1230 1231 # Strip off netmasks 1232 a, b = map(lambda x:x.rsplit("/")[0], (a, b)) 1233 return cmp(*map(socket.inet_aton, (a, b)))
1234
1235 -def ipsortKey(a):
1236 """ 1237 Key function to replace cmp version of ipsort 1238 @param a: IP address 1239 @type a: string 1240 @return: result of socket.inet_aton(a.ip) 1241 @rtype: int 1242 """ 1243 if not a: 1244 a = "0.0.0.0" 1245 a = a.rsplit('/')[0] 1246 return socket.inet_aton(a)
1247
1248 -def unsigned(v):
1249 """ 1250 Convert negative 32-bit values into the 2's complement unsigned value 1251 1252 >>> str(unsigned(-1)) 1253 '4294967295' 1254 >>> unsigned(1) 1255 1L 1256 >>> unsigned(1e6) 1257 1000000L 1258 >>> unsigned(1e10) 1259 10000000000L 1260 1261 @param v: number 1262 @type v: negative 32-bit number 1263 @return: 2's complement unsigned value 1264 @rtype: unsigned int 1265 """ 1266 v = long(v) 1267 if v < 0: 1268 import ctypes 1269 return int(ctypes.c_uint32(v).value) 1270 return v
1271
1272 1273 -def nanToNone(value):
1274 try: 1275 if math.isnan(value): 1276 return None 1277 except TypeError: 1278 pass 1279 return value
1280
1281 1282 -def executeStreamCommand(cmd, writefunc, timeout=30):
1283 """ 1284 Execute cmd in the shell and send the output to writefunc. 1285 1286 @param cmd: command to execute 1287 @type cmd: string 1288 @param writefunc: output function 1289 @type writefunc: function 1290 @param timeout: maxium number of seconds to wait for the command to execute 1291 @type timeout: number 1292 """ 1293 child = popen2.Popen4(cmd) 1294 flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL) 1295 fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY) 1296 pollPeriod = 1 1297 endtime = time.time() + timeout 1298 firstPass = True 1299 while time.time() < endtime and ( 1300 firstPass or child.poll()==-1): 1301 firstPass = False 1302 r,w,e = select.select([child.fromchild],[],[],pollPeriod) 1303 if r: 1304 t = child.fromchild.read() 1305 if t: 1306 writefunc(t) 1307 if child.poll()==-1: 1308 writefunc('Command timed out') 1309 import signal 1310 os.kill(child.pid, signal.SIGKILL)
1311
1312 1313 -def monkeypatch(target):
1314 """ 1315 A decorator to patch the decorated function into the given class. 1316 1317 >>> @monkeypatch('Products.ZenModel.DataRoot.DataRoot') 1318 ... def do_nothing_at_all(self): 1319 ... print "I do nothing at all." 1320 ... 1321 >>> from Products.ZenModel.DataRoot import DataRoot 1322 >>> hasattr(DataRoot, 'do_nothing_at_all') 1323 True 1324 >>> DataRoot('dummy').do_nothing_at_all() 1325 I do nothing at all. 1326 1327 You can also call the original within the new method 1328 using a special variable available only locally. 1329 1330 >>> @monkeypatch('Products.ZenModel.DataRoot.DataRoot') 1331 ... def getProductName(self): 1332 ... print "Doing something additional." 1333 ... return 'core' or original(self) 1334 ... 1335 >>> from Products.ZenModel.DataRoot import DataRoot 1336 >>> DataRoot('dummy').getProductName() 1337 Doing something additional. 1338 'core' 1339 1340 You can also stack monkeypatches. 1341 1342 ### @monkeypatch('Products.ZenModel.System.System') 1343 ... @monkeypatch('Products.ZenModel.DeviceGroup.DeviceGroup') 1344 ... @monkeypatch('Products.ZenModel.Location.Location') 1345 ... def foo(self): 1346 ... print "bar!" 1347 ... 1348 ### dmd.Systems.foo() 1349 bar! 1350 ### dmd.Groups.foo() 1351 bar! 1352 ### dmd.Locations.foo() 1353 bar! 1354 1355 @param target: class 1356 @type target: class object 1357 @return: decorator function return 1358 @rtype: function 1359 """ 1360 if isinstance(target, basestring): 1361 mod, klass = target.rsplit('.', 1) 1362 target = importClass(mod, klass) 1363 def patcher(func): 1364 original = getattr(target, func.__name__, None) 1365 if original is None: 1366 setattr(target, func.__name__, func) 1367 return func 1368 1369 new_globals = copy.copy(func.func_globals) 1370 new_globals['original'] = original 1371 new_func = types.FunctionType(func.func_code, 1372 globals=new_globals, 1373 name=func.func_name, 1374 argdefs=func.func_defaults, 1375 closure=func.func_closure) 1376 setattr(target, func.__name__, new_func) 1377 return func
1378 return patcher 1379
1380 -def nocache(f):
1381 """ 1382 Decorator to set headers which force browser to not cache request 1383 1384 This is intended to decorate methods of BrowserViews. 1385 1386 @param f: class 1387 @type f: class object 1388 @return: decorator function return 1389 @rtype: function 1390 """ 1391 def inner(self, *args, **kwargs): 1392 """ 1393 Inner portion of the decorator 1394 1395 @param *args: arguments 1396 @type *args: possible list 1397 @param **kwargs: keyword arguments 1398 @type **kwargs: possible list 1399 @return: decorator function return 1400 @rtype: function 1401 """ 1402 self.request.response.setHeader('Cache-Control', 'no-cache, must-revalidate') 1403 self.request.response.setHeader('Pragma', 'no-cache') 1404 self.request.response.setHeader('Expires', 'Sat, 13 May 2006 18:02:00 GMT') 1405 # Get rid of kw used to prevent browser caching 1406 kwargs.pop('_dc', None) 1407 return f(self, *args, **kwargs)
1408 1409 return inner 1410
1411 -def formreq(f):
1412 """ 1413 Decorator to pass in request.form information as arguments to a method. 1414 1415 These are intended to decorate methods of BrowserViews. 1416 1417 @param f: class 1418 @type f: class object 1419 @return: decorator function return 1420 @rtype: function 1421 """ 1422 def inner(self, *args, **kwargs): 1423 """ 1424 Inner portion of the decorator 1425 1426 @param *args: arguments 1427 @type *args: possible list 1428 @param **kwargs: keyword arguments 1429 @type **kwargs: possible list 1430 @return: decorator function return 1431 @rtype: function 1432 """ 1433 if self.request.REQUEST_METHOD=='POST': 1434 content = extractPostContent(self.request) 1435 try: 1436 args += (unjson(content),) 1437 except ValueError: 1438 kwargs.update(self.request.form) 1439 else: 1440 kwargs.update(self.request.form) 1441 # Get rid of useless Zope thing that appears when no querystring 1442 kwargs.pop('-C', None) 1443 # Get rid of kw used to prevent browser caching 1444 kwargs.pop('_dc', None) 1445 return f(self, *args, **kwargs)
1446 1447 return inner 1448
1449 1450 -class Singleton(type):
1451 """ 1452 Metaclass that ensures only a single instance of a class is ever created. 1453 1454 This is accomplished by storing the first instance created as an attribute 1455 of the class itself, then checking that attribute for later constructor 1456 calls. 1457 """
1458 - def __init__(cls, *args, **kwargs):
1459 super(Singleton, cls).__init__(*args, **kwargs) 1460 cls._singleton_instance = None
1461
1462 - def __call__(cls, *args, **kwargs):
1463 if cls._singleton_instance is None: 1464 cls._singleton_instance = super( 1465 Singleton, cls).__call__(*args, **kwargs) 1466 return cls._singleton_instance
1467
1468 1469 -def readable_time(seconds, precision=1):
1470 """ 1471 Convert some number of seconds into a human-readable string. 1472 1473 @param seconds: The number of seconds to convert 1474 @type seconds: int 1475 @param precision: The maximum number of time units to include. 1476 @type precision: int 1477 @rtype: str 1478 1479 >>> readable_time(None) 1480 '0 seconds' 1481 >>> readable_time(0) 1482 '0 seconds' 1483 >>> readable_time(0.12) 1484 '0 seconds' 1485 >>> readable_time(1) 1486 '1 second' 1487 >>> readable_time(1.5) 1488 '1 second' 1489 >>> readable_time(60) 1490 '1 minute' 1491 >>> readable_time(60*60*3+12) 1492 '3 hours' 1493 >>> readable_time(60*60*3+12, 2) 1494 '3 hours 12 seconds' 1495 1496 """ 1497 if seconds is None: 1498 return '0 seconds' 1499 remaining = abs(seconds) 1500 if remaining < 1: 1501 return '0 seconds' 1502 1503 names = ('year', 'month', 'week', 'day', 'hour', 'minute', 'second') 1504 mults = (60*60*24*365, 60*60*24*30, 60*60*24*7, 60*60*24, 60*60, 60, 1) 1505 result = [] 1506 for name, div in zip(names, mults): 1507 num = Decimal(str(math.floor(remaining/div))) 1508 remaining -= int(num)*div 1509 num = int(num) 1510 if num: 1511 result.append('%d %s%s' %(num, name, num>1 and 's' or '')) 1512 if len(result)==precision: 1513 break 1514 return ' '.join(result)
1515
1516 1517 -def relative_time(t, precision=1, cmptime=None):
1518 """ 1519 Return a human-readable string describing time relative to C{cmptime} 1520 (defaulted to now). 1521 1522 @param t: The time to convert, in seconds since the epoch. 1523 @type t: int 1524 @param precision: The maximum number of time units to include. 1525 @type t: int 1526 @param cmptime: The time from which to compute the difference, in seconds 1527 since the epoch 1528 @type cmptime: int 1529 @rtype: str 1530 1531 >>> relative_time(time.time() - 60*10) 1532 '10 minutes ago' 1533 >>> relative_time(time.time() - 60*10-3, precision=2) 1534 '10 minutes 3 seconds ago' 1535 >>> relative_time(time.time() - 60*60*24*10, precision=2) 1536 '1 week 3 days ago' 1537 >>> relative_time(time.time() - 60*60*24*365-1, precision=2) 1538 '1 year 1 second ago' 1539 >>> relative_time(time.time() + 1 + 60*60*24*7*2) # Add 1 for rounding 1540 'in 2 weeks' 1541 1542 """ 1543 if cmptime is None: 1544 cmptime = time.time() 1545 seconds = Decimal(str(t - cmptime)) 1546 result = readable_time(seconds, precision) 1547 if seconds < 0: 1548 result += ' ago' 1549 else: 1550 result = 'in ' + result 1551 return result
1552
1553 1554 -def is_browser_connection_open(request):
1555 """ 1556 Check to see if the TCP connection to the browser is still open. 1557 1558 This might be used to interrupt an infinite while loop, which would 1559 preclude the thread from being destroyed even though the connection has 1560 been closed. 1561 """ 1562 creation_time = request.environ['channel.creation_time'] 1563 for cnxn in asyncore.socket_map.values(): 1564 if (isinstance(cnxn, zhttp_channel) and 1565 cnxn.creation_time==creation_time): 1566 return True 1567 return False
1568 1569 1570 EXIT_CODE_MAPPING = { 1571 0:'Success', 1572 1:'General error', 1573 2:'Misuse of shell builtins', 1574 126:'Command invoked cannot execute, permissions problem or command is not an executable', 1575 127:'Command not found', 1576 128:'Invalid argument to exit, exit takes only integers in the range 0-255', 1577 130:'Fatal error signal: 2, Command terminated by Control-C' 1578 }
1579 1580 -def getExitMessage(exitCode):
1581 """ 1582 Return a nice exit message that corresponds to the given exit status code 1583 1584 @param exitCode: process exit code 1585 @type exitCode: integer 1586 @return: human-readable version of the exit code 1587 @rtype: string 1588 """ 1589 if exitCode in EXIT_CODE_MAPPING.keys(): 1590 return EXIT_CODE_MAPPING[exitCode] 1591 elif exitCode >= 255: 1592 return 'Exit status out of range, exit takes only integer arguments in the range 0-255' 1593 elif exitCode > 128: 1594 return 'Fatal error signal: %s' % (exitCode-128) 1595 return 'Unknown error code: %s' % exitCode
1596
1597 1598 -def set_context(ob):
1599 """ 1600 Wrap an object in a REQUEST context. 1601 """ 1602 from ZPublisher.HTTPRequest import HTTPRequest 1603 from ZPublisher.HTTPResponse import HTTPResponse 1604 from ZPublisher.BaseRequest import RequestContainer 1605 resp = HTTPResponse(stdout=None) 1606 env = { 1607 'SERVER_NAME':'localhost', 1608 'SERVER_PORT':'8080', 1609 'REQUEST_METHOD':'GET' 1610 } 1611 req = HTTPRequest(None, env, resp) 1612 return ob.__of__(RequestContainer(REQUEST = req))
1613
1614 -def dumpCallbacks(deferred):
1615 """ 1616 Dump the callback chain of a Twisted Deferred object. The chain will be 1617 displayed on standard output. 1618 1619 @param deferred: the twisted Deferred object to dump 1620 @type deferred: a Deferred object 1621 """ 1622 callbacks = deferred.callbacks 1623 print "%-39s %-39s" % ("Callbacks", "Errbacks") 1624 print "%-39s %-39s" % ("-" * 39, "-" * 39) 1625 for cbs in callbacks: 1626 callback = cbs[0][0] 1627 callbackName = "%s.%s" % (callback.__module__, callback.func_name) 1628 errback = cbs[1][0] 1629 errbackName = "%s.%s" % (errback.__module__, errback.func_name) 1630 print "%-39.39s %-39.39s" % (callbackName, errbackName)
1631 1632 1633 # add __iter__ method to LazyMap (used to implement catalog queries) to handle 1634 # errors while iterating over the query results using __getitem__ 1635 from Products.ZCatalog.Lazy import LazyMap
1636 -def LazyMap__iter__(self):
1637 for i in range(len(self._seq)): 1638 try: 1639 brain = self[i] 1640 yield brain 1641 except (NotFound, KeyError, AttributeError): 1642 if log: 1643 log.warn("Stale record in catalog: (key) %s", self._seq[i]) 1644 except IndexError: 1645 break
1646 1647 LazyMap.__iter__ = LazyMap__iter__
1648 1649 -def getObjectsFromCatalog(catalog, query=None, log=None):
1650 """ 1651 Generator that can be used to load all objects of out a catalog and skip 1652 any objects that are no longer able to be loaded. 1653 """ 1654 1655 for brain in catalog(query): 1656 try: 1657 ob = brain.getObject() 1658 yield ob 1659 except (NotFound, KeyError, AttributeError): 1660 if log: 1661 log.warn("Stale %s record: %s", catalog.id, brain.getPath())
1662 1663 1664 _LOADED_CONFIGS = set()
1665 1666 1667 -def load_config(file, package=None, execute=True):
1668 """ 1669 Load a ZCML file into the context (and avoids duplicate imports). 1670 """ 1671 global _LOADED_CONFIGS 1672 key = (file, package) 1673 if not key in _LOADED_CONFIGS: 1674 from Zope2.App import zcml 1675 zcml.load_config(file, package, execute) 1676 _LOADED_CONFIGS.add(key)
1677
1678 1679 -def load_config_override(file, package=None, execute=True):
1680 """Load an additional ZCML file into the context, overriding others. 1681 1682 Use with extreme care. 1683 """ 1684 global _LOADED_CONFIGS 1685 key = (file, package) 1686 if not key in _LOADED_CONFIGS: 1687 from zope.configuration import xmlconfig 1688 from Products.Five.zcml import _context 1689 xmlconfig.includeOverrides(_context, file, package=package) 1690 if execute: 1691 _context.execute_actions() 1692 _LOADED_CONFIGS.add(key)
1693 1694 1695 CACHE_TIME = 30 1696 _rrdDaemonStatus = Map.Locked(Map.Timed({}, CACHE_TIME)) 1697 _RRD_DAEMON_STATUS_KEY = "RRD_daemon_running"
1698 1699 -def rrd_daemon_running():
1700 """ 1701 Look to see if the rrdcached daemon is running. Use a timed map so that we don't 1702 have to actually *look* every time an RRD function is called - instead we only check every 1703 CACHE_TIME seconds. Function callers should still handle rrdtool.error exceptions if the 1704 daemon turns out *not* to be running, and should call rrd_daemon_reset() in that event. 1705 (Or just wrap rrd behavior in an internal function, and decorate that function with 1706 the rrd_daemon_retry decorator.) 1707 """ 1708 try: 1709 return _rrdDaemonStatus[_RRD_DAEMON_STATUS_KEY] 1710 except KeyError: 1711 sockfile = zenPath('var', 'rrdcached.sock') 1712 if not os.path.exists(sockfile): 1713 sockfile = None 1714 _rrdDaemonStatus[_RRD_DAEMON_STATUS_KEY] = sockfile 1715 return sockfile
1716
1717 -def rrd_daemon_args():
1718 """ 1719 Return tuple of RRD arguments for rrdcached access, depending on whether the 1720 daemon is running or not. 1721 """ 1722 daemon = rrd_daemon_running() 1723 if daemon: 1724 return '--daemon', daemon 1725 else: 1726 return tuple()
1727
1728 -def rrd_daemon_reset():
1729 """ 1730 Exception handlers should call this method to clear the rrdcached daemon socketfile, 1731 in case the daemon has shutdown since the last time we looked. 1732 """ 1733 _rrdDaemonStatus.pop(_RRD_DAEMON_STATUS_KEY, None)
1734
1735 -def rrd_daemon_retry(fn):
1736 def _inner(*args, **kwargs): 1737 for tries in range(2): 1738 try: 1739 return fn(*args,**kwargs) 1740 except rrdtool.error: 1741 if not tries and rrd_daemon_running(): 1742 rrd_daemon_reset() 1743 else: 1744 raise
1745 return _inner 1746
1747 @contextlib.contextmanager 1748 -def get_temp_dir():
1749 import tempfile 1750 import shutil 1751 1752 dirname = tempfile.mkdtemp() 1753 try: 1754 yield dirname 1755 finally: 1756 shutil.rmtree(dirname)
1757
1758 -def getDefaultZopeUrl():
1759 """ 1760 Returns the default Zope URL. 1761 """ 1762 return 'http://%s:%d' % (socket.getfqdn(), 8080)
1763
1764 1765 -def swallowExceptions(log, msg=None, showTraceback=True, returnValue=None):
1766 """ 1767 USE THIS CAUTIOUSLY. Don't hide exceptions carelessly. 1768 1769 Decorator to safely call a method, logging exceptions without raising them. 1770 1771 Example: 1772 @swallowExceptions(myLogger, 'Error while closing files') 1773 def closeFilesBeforeExit(): 1774 ... 1775 1776 @param log Which logger to use, or None to not log. 1777 @param msg The error message. 1778 @param showTraceback True to include the stacktrace (the default). 1779 @param returnValue The return value on error. 1780 """ 1781 @decorator 1782 def callSafely(func, *args, **kwargs): 1783 try: 1784 return func(*args, **kwargs) 1785 except ConflictError: 1786 raise 1787 except Exception, e: 1788 if log is not None: 1789 if showTraceback: 1790 log.exception(msg if msg else str(e)) 1791 else: 1792 log.warn(msg if msg else str(e)) 1793 return returnValue
1794 1795 return callSafely 1796
1797 -def getAllParserOptionsGen(parser):
1798 """ 1799 Returns a generator of all valid options for the optparse.OptionParser. 1800 1801 @param parser The parser to retrieve options for. 1802 @type parser optparse.OptionParser 1803 @return A generator returning all options for the parser. 1804 @rtype generator of optparse.Option 1805 """ 1806 for optContainer in chain((parser,), parser.option_groups): 1807 for option in optContainer.option_list: 1808 yield option
1809
1810 1811 -def ipv6_available():
1812 try: 1813 socket.socket(socket.AF_INET6).close() 1814 return True 1815 except socket.error: 1816 return False
1817
1818 -def atomicWrite(filename, data, raiseException=True, createDir=False):
1819 """ 1820 atomicWrite writes data in an atmomic manner to filename. 1821 1822 @param filename Complete path of file to write to. 1823 @type filename string 1824 @param data Data to write to destination file. 1825 @type data writeable object (eg, string) 1826 @param raiseException Raise errors that occur during atomicWrite 1827 @type raiseException Exception 1828 @param createDir Create the destination directory if it does not exists. 1829 @type createDir Bool 1830 @rtype a suppressed exception, if any 1831 """ 1832 dirName = os.path.dirname(filename) 1833 if createDir and not os.path.exists(dirName): 1834 os.makedirs(dirName) 1835 tfile = None 1836 ex = None 1837 try: 1838 # create a file in the same directory as the destination file 1839 with tempfile.NamedTemporaryFile(dir=dirName, delete=False) as tfile: 1840 tfile.write(data) 1841 os.rename(tfile.name, filename) # atomic operation on POSIX systems 1842 except Exception as ex: 1843 if tfile is not None and os.path.exists(tfile.name): 1844 try: 1845 os.remove(tfile.name) 1846 except Exception: 1847 pass 1848 if raiseException: 1849 raise ex 1850 return ex
1851
1852 -def setLogLevel(level=logging.DEBUG):
1853 """ 1854 Change the logging level to allow for more insight into the 1855 in-flight mechanics of Zenoss. 1856 1857 @parameter level: logging level at which messages display (eg logging.INFO) 1858 @type level: integer 1859 """ 1860 log = logging.getLogger() 1861 log.setLevel(level) 1862 for handler in log.handlers: 1863 if isinstance(handler, logging.StreamHandler): 1864 handler.setLevel(level)
1865
1866 -def isRunning(daemon):
1867 """ 1868 Determines whether a specific daemon is running by calling 'daemon status' 1869 """ 1870 return call([daemon, 'status'], stdout=PIPE, stderr=STDOUT) == 0
1871
1872 -def requiresDaemonShutdown(daemon, logger=log):
1873 """ 1874 Performs an operation while the requested daemon is not running. Will stop 1875 and restart the daemon automatically. Throws a CalledProcessError if either 1876 shutdown or restart fails. 1877 1878 @param daemon Which daemon to bring down for the operation. 1879 @param logger Which logger to use, or None to not log. 1880 """ 1881 @decorator 1882 def callWithShutdown(func, *args, **kwargs): 1883 cmd = binPath(daemon) 1884 running = isRunning(cmd) 1885 if running: 1886 if logger: logger.info('Shutting down %s for %s operation...', daemon, func.__name__) 1887 check_call([cmd, 'stop']) 1888 1889 # make sure the daemon is actually shut down 1890 for i in range(30): 1891 nowrunning = isRunning(cmd) 1892 if not nowrunning: break 1893 time.sleep(1) 1894 else: 1895 raise Exception('Failed to terminate daemon %s with command %s' % (daemon, cmd + ' stop')) 1896 1897 try: 1898 return func(*args, **kwargs) 1899 1900 except Exception as ex: 1901 if logger: logger.error('Error performing %s operation: %s', func.__name__, ex) 1902 raise 1903 1904 finally: 1905 if running: 1906 if logger: logger.info('Starting %s after %s operation...', daemon, func.__name__) 1907 check_call([cmd, 'start'])
1908 1909 return callWithShutdown 1910
1911 -def isZenBinFile(name):
1912 """ 1913 Check if given name is a valid file in $ZENHOME/bin. 1914 """ 1915 if os.path.sep in name: 1916 return False 1917 return os.path.isfile(binPath(name))
1918
1919 1920 1921 -class ThreadInterrupt(Exception):
1922 """ 1923 An exception that can be raised in a thread from another thread. 1924 """
1925
1926 1927 -class InterruptableThread(threading.Thread):
1928 """ 1929 A thread class that supports being interrupted. Target functions should 1930 catch ThreadInterrupt to perform cleanup. 1931 1932 Code is a somewhat modified version of Bluebird75's solution found at 1933 http://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python 1934 """
1935 - def _raise(self, exception_type=ThreadInterrupt):
1936 threadid = ctypes.c_long(self.ident) 1937 exception = ctypes.py_object(exception_type) 1938 result = ctypes.pythonapi.PyThreadState_SetAsyncExc(threadid, exception) 1939 if result == 0: 1940 raise ValueError("Invalid thread id: %s" % self.ident) 1941 elif result != 1: 1942 ctypes.pythonapi.PyThreadState_SetAsyncExc(threadid, None) 1943 raise SystemError("Failed to interrupt thread")
1944
1945 - def interrupt(self, exception_type=ThreadInterrupt):
1946 if not inspect.isclass(exception_type): 1947 raise TypeError("Can't raise exception instances into a thread.") 1948 self._raise(exception_type)
1949
1950 - def kill(self):
1951 self.interrupt(SystemExit)
1952
1953 1954 -class LineReader(threading.Thread):
1955 """ 1956 Simulate non-blocking readline() behavior. 1957 """ 1958 1959 daemon = True 1960
1961 - def __init__(self, stream):
1962 """ 1963 @param stream {File-like object} input data stream 1964 """ 1965 super(LineReader, self).__init__() 1966 self._stream = stream 1967 self._queue = Queue.Queue()
1968
1969 - def run(self):
1970 for line in iter(self._stream.readline, b''): 1971 self._queue.put(line) 1972 self._stream.close() 1973 self._stream = None
1974
1975 - def readline(self, timeout=0):
1976 try: 1977 return self._queue.get(timeout=timeout) 1978 except Queue.Empty: 1979 return ''
1980 1981 1982 giveTimeToReactor = partial(task.deferLater, reactor, 0) 1983