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