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