1
2
3
4
5
6
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
89
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
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
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
218 path.pop(0)
219
220 if restricted:
221 securityManager = getSecurityManager()
222 else:
223 securityManager = _none
224
225 if not path[-1]:
226
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
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
256
257 container = aq_parent(aq_inner(next))
258 elif _getattr(next, 'im_self', _none) is not _none:
259
260
261 container = next.im_self
262 elif _getattr(aq_base(obj), name, marker) == next:
263
264
265 container = obj
266 else:
267
268 container = _none
269 try:
270 validated = securityManager.validate(
271 obj, container, name, next)
272 except Unauthorized:
273
274
275
276
277
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
290
291
292
293
294
295
296
297 if next is marker:
298 try:
299 next=obj[name]
300 except AttributeError:
301
302
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
312
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',
320 }
323 """
324 Get a printable string representing the type of this object
325 """
326
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
333 return getattr(obj, 'getName', None) or getattr(obj, 'name', None) or \
334 getattr(obj, 'Name', None)
335
337 return getattr(obj, 'getId', None) or getattr(obj, 'id', None) or \
338 getattr(obj, 'Id', None) or getattr(obj, 'ID', None)
339
341 return getattr(obj, 'getPrimaryId', None) or getattr(obj, 'uid', None) \
342 or getattr(obj, 'Uid', None) or getattr(obj, 'UID', None)
343
345 """
346 Get a printable string representing the name of this object.
347 Always returns something but it may not be pretty.
348 """
349
350 name = _getName(obj) or _getId(obj) or _getUid(obj)
351 if name is None:
352 return str(obj)
353 return str(name() if callable(name) else name)
354
357 """
358 Get a printable string representing an ID of this object.
359 Always returns something but it may not be pretty.
360 """
361
362 dispId = _getUid(obj) or _getId(obj) or _getName(obj)
363 if dispId is None:
364 return str(obj)
365 return re.sub(r'^/zport/dmd', '', str(dispId() if callable(dispId) else dispId))
366
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
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
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
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
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
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
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
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
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
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
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
728
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):
755
756 - def errReceived(self, 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
788
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
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
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
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
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
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
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
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
964
965
966
967
968
969
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
977 if len(path_args) > 0:
978
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
987
988 path = os.path.join( *path_args )
989
990
991 return path.rstrip('/')
992
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
1035
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
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
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
1084 paths = [zenPath(d, fileName) for d in ('bin', 'libexec')]
1085
1086 paths.append(zopePath('bin', fileName))
1087
1088 paths.extend(sane_pathjoin(d, fileName) for d in ('/usr/lib/nagios/plugins',
1089 '/usr/lib64/nagios/plugins'))
1090
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
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
1109 return REQUEST._file.read()
1110 except Exception:
1111 try:
1112
1113 return REQUEST.form.keys()[0]
1114 except Exception:
1115 return ''
1116
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
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
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
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
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
1228 if not a: a = "0.0.0.0"
1229 if not b: b = "0.0.0.0"
1230
1231
1232 a, b = map(lambda x:x.rsplit("/")[0], (a, b))
1233 return cmp(*map(socket.inet_aton, (a, b)))
1234
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
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
1274 try:
1275 if math.isnan(value):
1276 return None
1277 except TypeError:
1278 pass
1279 return value
1280
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
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
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
1406 kwargs.pop('_dc', None)
1407 return f(self, *args, **kwargs)
1408
1409 return inner
1410
1446
1447 return inner
1448
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 """
1459 super(Singleton, cls).__init__(*args, **kwargs)
1460 cls._singleton_instance = None
1461
1463 if cls._singleton_instance is None:
1464 cls._singleton_instance = super(
1465 Singleton, cls).__call__(*args, **kwargs)
1466 return cls._singleton_instance
1467
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
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
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 }
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
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
1634
1635 from Products.ZCatalog.Lazy import LazyMap
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__
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):
1677
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"
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
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
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
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
1749 import tempfile
1750 import shutil
1751
1752 dirname = tempfile.mkdtemp()
1753 try:
1754 yield dirname
1755 finally:
1756 shutil.rmtree(dirname)
1757
1759 """
1760 Returns the default Zope URL.
1761 """
1762 return 'http://%s:%d' % (socket.getfqdn(), 8080)
1763
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
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
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
1839 with tempfile.NamedTemporaryFile(dir=dirName, delete=False) as tfile:
1840 tfile.write(data)
1841 os.rename(tfile.name, filename)
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
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
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
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
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
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
1922 """
1923 An exception that can be raised in a thread from another thread.
1924 """
1925
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 """
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
1946 if not inspect.isclass(exception_type):
1947 raise TypeError("Can't raise exception instances into a thread.")
1948 self._raise(exception_type)
1949
1952
1955 """
1956 Simulate non-blocking readline() behavior.
1957 """
1958
1959 daemon = True
1960
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
1970 for line in iter(self._stream.readline, b''):
1971 self._queue.put(line)
1972 self._stream.close()
1973 self._stream = None
1974
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