xref: /openbmc/openbmc/poky/bitbake/lib/bb/event.py (revision 517393d9)
1"""
2BitBake 'Event' implementation
3
4Classes and functions for manipulating 'events' in the
5BitBake build tools.
6"""
7
8# Copyright (C) 2003, 2004  Chris Larson
9#
10# SPDX-License-Identifier: GPL-2.0-only
11#
12
13import ast
14import atexit
15import collections
16import logging
17import pickle
18import sys
19import threading
20import traceback
21
22import bb.exceptions
23import bb.utils
24
25# This is the pid for which we should generate the event. This is set when
26# the runqueue forks off.
27worker_pid = 0
28worker_fire = None
29
30logger = logging.getLogger('BitBake.Event')
31
32class Event(object):
33    """Base class for events"""
34
35    def __init__(self):
36        self.pid = worker_pid
37
38
39class HeartbeatEvent(Event):
40    """Triggered at regular time intervals of 10 seconds. Other events can fire much more often
41       (runQueueTaskStarted when there are many short tasks) or not at all for long periods
42       of time (again runQueueTaskStarted, when there is just one long-running task), so this
43       event is more suitable for doing some task-independent work occasionally."""
44    def __init__(self, time):
45        Event.__init__(self)
46        self.time = time
47
48Registered        = 10
49AlreadyRegistered = 14
50
51def get_class_handlers():
52    return _handlers
53
54def set_class_handlers(h):
55    global _handlers
56    _handlers = h
57
58def clean_class_handlers():
59    return collections.OrderedDict()
60
61# Internal
62_handlers = clean_class_handlers()
63_ui_handlers = {}
64_ui_logfilters = {}
65_ui_handler_seq = 0
66_event_handler_map = {}
67_catchall_handlers = {}
68_eventfilter = None
69_uiready = False
70_thread_lock = threading.Lock()
71_heartbeat_enabled = False
72
73def enable_threadlock():
74    # Always needed now
75    return
76
77def disable_threadlock():
78    # Always needed now
79    return
80
81def enable_heartbeat():
82    global _heartbeat_enabled
83    _heartbeat_enabled = True
84
85def disable_heartbeat():
86    global _heartbeat_enabled
87    _heartbeat_enabled = False
88
89def execute_handler(name, handler, event, d):
90    event.data = d
91    try:
92        ret = handler(event, d)
93    except (bb.parse.SkipRecipe, bb.BBHandledException):
94        raise
95    except Exception:
96        etype, value, tb = sys.exc_info()
97        logger.error("Execution of event handler '%s' failed" % name,
98                        exc_info=(etype, value, tb.tb_next))
99        raise
100    except SystemExit as exc:
101        if exc.code != 0:
102            logger.error("Execution of event handler '%s' failed" % name)
103        raise
104    finally:
105        del event.data
106
107
108def fire_class_handlers(event, d):
109    if isinstance(event, logging.LogRecord):
110        return
111
112    eid = str(event.__class__)[8:-2]
113    evt_hmap = _event_handler_map.get(eid, {})
114    for name, handler in list(_handlers.items()):
115        if name in _catchall_handlers or name in evt_hmap:
116            if _eventfilter:
117                if not _eventfilter(name, handler, event, d):
118                    continue
119            if d is not None and not name in (d.getVar("__BBHANDLERS_MC") or set()):
120                continue
121            execute_handler(name, handler, event, d)
122
123ui_queue = []
124@atexit.register
125def print_ui_queue():
126    global ui_queue
127    """If we're exiting before a UI has been spawned, display any queued
128    LogRecords to the console."""
129    logger = logging.getLogger("BitBake")
130    if not _uiready:
131        from bb.msg import BBLogFormatter
132        # Flush any existing buffered content
133        try:
134            sys.stdout.flush()
135        except:
136            pass
137        try:
138            sys.stderr.flush()
139        except:
140            pass
141        stdout = logging.StreamHandler(sys.stdout)
142        stderr = logging.StreamHandler(sys.stderr)
143        formatter = BBLogFormatter("%(levelname)s: %(message)s")
144        stdout.setFormatter(formatter)
145        stderr.setFormatter(formatter)
146
147        # First check to see if we have any proper messages
148        msgprint = False
149        msgerrs = False
150
151        # Should we print to stderr?
152        for event in ui_queue[:]:
153            if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING:
154                msgerrs = True
155                break
156
157        if msgerrs:
158            logger.addHandler(stderr)
159        else:
160            logger.addHandler(stdout)
161
162        for event in ui_queue[:]:
163            if isinstance(event, logging.LogRecord):
164                if event.levelno > logging.DEBUG:
165                    logger.handle(event)
166                    msgprint = True
167
168        # Nope, so just print all of the messages we have (including debug messages)
169        if not msgprint:
170            for event in ui_queue[:]:
171                if isinstance(event, logging.LogRecord):
172                    logger.handle(event)
173        if msgerrs:
174            logger.removeHandler(stderr)
175        else:
176            logger.removeHandler(stdout)
177        ui_queue = []
178
179def fire_ui_handlers(event, d):
180    global _thread_lock
181
182    if not _uiready:
183        # No UI handlers registered yet, queue up the messages
184        ui_queue.append(event)
185        return
186
187    with bb.utils.lock_timeout(_thread_lock):
188        errors = []
189        for h in _ui_handlers:
190            #print "Sending event %s" % event
191            try:
192                 if not _ui_logfilters[h].filter(event):
193                     continue
194                 # We use pickle here since it better handles object instances
195                 # which xmlrpc's marshaller does not. Events *must* be serializable
196                 # by pickle.
197                 if hasattr(_ui_handlers[h].event, "sendpickle"):
198                    _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
199                 else:
200                    _ui_handlers[h].event.send(event)
201            except:
202                errors.append(h)
203        for h in errors:
204            del _ui_handlers[h]
205
206def fire(event, d):
207    """Fire off an Event"""
208
209    # We can fire class handlers in the worker process context and this is
210    # desired so they get the task based datastore.
211    # UI handlers need to be fired in the server context so we defer this. They
212    # don't have a datastore so the datastore context isn't a problem.
213
214    fire_class_handlers(event, d)
215    if worker_fire:
216        worker_fire(event, d)
217    else:
218        # If messages have been queued up, clear the queue
219        global _uiready, ui_queue
220        if _uiready and ui_queue:
221            for queue_event in ui_queue:
222                fire_ui_handlers(queue_event, d)
223            ui_queue = []
224        fire_ui_handlers(event, d)
225
226def fire_from_worker(event, d):
227    fire_ui_handlers(event, d)
228
229noop = lambda _: None
230def register(name, handler, mask=None, filename=None, lineno=None, data=None):
231    """Register an Event handler"""
232
233    if data is not None and data.getVar("BB_CURRENT_MC"):
234        mc = data.getVar("BB_CURRENT_MC")
235        name = '%s%s' % (mc.replace('-', '_'), name)
236
237    # already registered
238    if name in _handlers:
239        if data is not None:
240            bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
241            bbhands_mc.add(name)
242            data.setVar("__BBHANDLERS_MC", bbhands_mc)
243        return AlreadyRegistered
244
245    if handler is not None:
246        # handle string containing python code
247        if isinstance(handler, str):
248            tmp = "def %s(e, d):\n%s" % (name, handler)
249            try:
250                code = bb.methodpool.compile_cache(tmp)
251                if not code:
252                    if filename is None:
253                        filename = "%s(e, d)" % name
254                    code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
255                    if lineno is not None:
256                        ast.increment_lineno(code, lineno-1)
257                    code = compile(code, filename, "exec")
258                    bb.methodpool.compile_cache_add(tmp, code)
259            except SyntaxError:
260                logger.error("Unable to register event handler '%s':\n%s", name,
261                             ''.join(traceback.format_exc(limit=0)))
262                _handlers[name] = noop
263                return
264            env = {}
265            bb.utils.better_exec(code, env)
266            func = bb.utils.better_eval(name, env)
267            _handlers[name] = func
268        else:
269            _handlers[name] = handler
270
271        if not mask or '*' in mask:
272            _catchall_handlers[name] = True
273        else:
274            for m in mask:
275                if _event_handler_map.get(m, None) is None:
276                    _event_handler_map[m] = {}
277                _event_handler_map[m][name] = True
278
279        if data is not None:
280            bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
281            bbhands_mc.add(name)
282            data.setVar("__BBHANDLERS_MC", bbhands_mc)
283
284        return Registered
285
286def remove(name, handler, data=None):
287    """Remove an Event handler"""
288    if data is not None:
289        if data.getVar("BB_CURRENT_MC"):
290            mc = data.getVar("BB_CURRENT_MC")
291            name = '%s%s' % (mc.replace('-', '_'), name)
292
293    _handlers.pop(name)
294    if name in _catchall_handlers:
295        _catchall_handlers.pop(name)
296    for event in _event_handler_map.keys():
297        if name in _event_handler_map[event]:
298            _event_handler_map[event].pop(name)
299
300    if data is not None:
301        bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
302        if name in bbhands_mc:
303            bbhands_mc.remove(name)
304            data.setVar("__BBHANDLERS_MC", bbhands_mc)
305
306def get_handlers():
307    return _handlers
308
309def set_handlers(handlers):
310    global _handlers
311    _handlers = handlers
312
313def set_eventfilter(func):
314    global _eventfilter
315    _eventfilter = func
316
317def register_UIHhandler(handler, mainui=False):
318    with bb.utils.lock_timeout(_thread_lock):
319        bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
320        _ui_handlers[_ui_handler_seq] = handler
321        level, debug_domains = bb.msg.constructLogOptions()
322        _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
323        if mainui:
324            global _uiready
325            _uiready = _ui_handler_seq
326        return _ui_handler_seq
327
328def unregister_UIHhandler(handlerNum, mainui=False):
329    if mainui:
330        global _uiready
331        _uiready = False
332    with bb.utils.lock_timeout(_thread_lock):
333        if handlerNum in _ui_handlers:
334            del _ui_handlers[handlerNum]
335    return
336
337def get_uihandler():
338    if _uiready is False:
339        return None
340    return _uiready
341
342# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC
343class UIEventFilter(object):
344    def __init__(self, level, debug_domains):
345        self.update(None, level, debug_domains)
346
347    def update(self, eventmask, level, debug_domains):
348        self.eventmask = eventmask
349        self.stdlevel = level
350        self.debug_domains = debug_domains
351
352    def filter(self, event):
353        if isinstance(event, logging.LogRecord):
354            if event.levelno >= self.stdlevel:
355                return True
356            if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]:
357                return True
358            return False
359        eid = str(event.__class__)[8:-2]
360        if self.eventmask and eid not in self.eventmask:
361            return False
362        return True
363
364def set_UIHmask(handlerNum, level, debug_domains, mask):
365    if not handlerNum in _ui_handlers:
366        return False
367    if '*' in mask:
368        _ui_logfilters[handlerNum].update(None, level, debug_domains)
369    else:
370        _ui_logfilters[handlerNum].update(mask, level, debug_domains)
371    return True
372
373def getName(e):
374    """Returns the name of a class or class instance"""
375    if getattr(e, "__name__", None) is None:
376        return e.__class__.__name__
377    else:
378        return e.__name__
379
380class OperationStarted(Event):
381    """An operation has begun"""
382    def __init__(self, msg = "Operation Started"):
383        Event.__init__(self)
384        self.msg = msg
385
386class OperationCompleted(Event):
387    """An operation has completed"""
388    def __init__(self, total, msg = "Operation Completed"):
389        Event.__init__(self)
390        self.total = total
391        self.msg = msg
392
393class OperationProgress(Event):
394    """An operation is in progress"""
395    def __init__(self, current, total, msg = "Operation in Progress"):
396        Event.__init__(self)
397        self.current = current
398        self.total = total
399        self.msg = msg + ": %s/%s" % (current, total);
400
401class ConfigParsed(Event):
402    """Configuration Parsing Complete"""
403
404class MultiConfigParsed(Event):
405    """Multi-Config Parsing Complete"""
406    def __init__(self, mcdata):
407        self.mcdata = mcdata
408        Event.__init__(self)
409
410class RecipeEvent(Event):
411    def __init__(self, fn):
412        self.fn = fn
413        Event.__init__(self)
414
415class RecipePreFinalise(RecipeEvent):
416    """ Recipe Parsing Complete but not yet finalised"""
417
418class RecipePostKeyExpansion(RecipeEvent):
419    """ Recipe Parsing Complete but not yet finalised"""
420
421
422class RecipeTaskPreProcess(RecipeEvent):
423    """
424    Recipe Tasks about to be finalised
425    The list of tasks should be final at this point and handlers
426    are only able to change interdependencies
427    """
428    def __init__(self, fn, tasklist):
429        self.fn = fn
430        self.tasklist = tasklist
431        Event.__init__(self)
432
433class RecipeParsed(RecipeEvent):
434    """ Recipe Parsing Complete """
435
436class BuildBase(Event):
437    """Base class for bitbake build events"""
438
439    def __init__(self, n, p, failures = 0):
440        self._name = n
441        self._pkgs = p
442        Event.__init__(self)
443        self._failures = failures
444
445    def getPkgs(self):
446        return self._pkgs
447
448    def setPkgs(self, pkgs):
449        self._pkgs = pkgs
450
451    def getName(self):
452        return self._name
453
454    def setName(self, name):
455        self._name = name
456
457    def getFailures(self):
458        """
459        Return the number of failed packages
460        """
461        return self._failures
462
463    pkgs = property(getPkgs, setPkgs, None, "pkgs property")
464    name = property(getName, setName, None, "name property")
465
466class BuildInit(BuildBase):
467    """buildFile or buildTargets was invoked"""
468    def __init__(self, p=[]):
469        name = None
470        BuildBase.__init__(self, name, p)
471
472class BuildStarted(BuildBase, OperationStarted):
473    """Event when builds start"""
474    def __init__(self, n, p, failures = 0):
475        OperationStarted.__init__(self, "Building Started")
476        BuildBase.__init__(self, n, p, failures)
477
478class BuildCompleted(BuildBase, OperationCompleted):
479    """Event when builds have completed"""
480    def __init__(self, total, n, p, failures=0, interrupted=0):
481        if not failures:
482            OperationCompleted.__init__(self, total, "Building Succeeded")
483        else:
484            OperationCompleted.__init__(self, total, "Building Failed")
485        self._interrupted = interrupted
486        BuildBase.__init__(self, n, p, failures)
487
488class DiskFull(Event):
489    """Disk full case build halted"""
490    def __init__(self, dev, type, freespace, mountpoint):
491        Event.__init__(self)
492        self._dev = dev
493        self._type = type
494        self._free = freespace
495        self._mountpoint = mountpoint
496
497class DiskUsageSample:
498    def __init__(self, available_bytes, free_bytes, total_bytes):
499        # Number of bytes available to non-root processes.
500        self.available_bytes = available_bytes
501        # Number of bytes available to root processes.
502        self.free_bytes = free_bytes
503        # Total capacity of the volume.
504        self.total_bytes = total_bytes
505
506class MonitorDiskEvent(Event):
507    """If BB_DISKMON_DIRS is set, then this event gets triggered each time disk space is checked.
508       Provides information about devices that are getting monitored."""
509    def __init__(self, disk_usage):
510        Event.__init__(self)
511        # hash of device root path -> DiskUsageSample
512        self.disk_usage = disk_usage
513
514class NoProvider(Event):
515    """No Provider for an Event"""
516
517    def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None):
518        Event.__init__(self)
519        self._item = item
520        self._runtime = runtime
521        self._dependees = dependees
522        self._reasons = reasons
523        self._close_matches = close_matches
524
525    def getItem(self):
526        return self._item
527
528    def isRuntime(self):
529        return self._runtime
530
531    def __str__(self):
532        msg = ''
533        if self._runtime:
534            r = "R"
535        else:
536            r = ""
537
538        extra = ''
539        if not self._reasons:
540            if self._close_matches:
541                extra = ". Close matches:\n  %s" % '\n  '.join(sorted(set(self._close_matches)))
542
543        if self._dependees:
544            msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, self._item, ", ".join(self._dependees), r, extra)
545        else:
546            msg = "Nothing %sPROVIDES '%s'%s" % (r, self._item, extra)
547        if self._reasons:
548            for reason in self._reasons:
549                msg += '\n' + reason
550        return msg
551
552
553class MultipleProviders(Event):
554    """Multiple Providers"""
555
556    def  __init__(self, item, candidates, runtime = False):
557        Event.__init__(self)
558        self._item = item
559        self._candidates = candidates
560        self._is_runtime = runtime
561
562    def isRuntime(self):
563        """
564        Is this a runtime issue?
565        """
566        return self._is_runtime
567
568    def getItem(self):
569        """
570        The name for the to be build item
571        """
572        return self._item
573
574    def getCandidates(self):
575        """
576        Get the possible Candidates for a PROVIDER.
577        """
578        return self._candidates
579
580    def __str__(self):
581        msg = "Multiple providers are available for %s%s (%s)" % (self._is_runtime and "runtime " or "",
582                            self._item,
583                            ", ".join(self._candidates))
584        rtime = ""
585        if self._is_runtime:
586            rtime = "R"
587        msg += "\nConsider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, self._item)
588        return msg
589
590class ParseStarted(OperationStarted):
591    """Recipe parsing for the runqueue has begun"""
592    def __init__(self, total):
593        OperationStarted.__init__(self, "Recipe parsing Started")
594        self.total = total
595
596class ParseCompleted(OperationCompleted):
597    """Recipe parsing for the runqueue has completed"""
598    def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
599        OperationCompleted.__init__(self, total, "Recipe parsing Completed")
600        self.cached = cached
601        self.parsed = parsed
602        self.skipped = skipped
603        self.virtuals = virtuals
604        self.masked = masked
605        self.errors = errors
606        self.sofar = cached + parsed
607
608class ParseProgress(OperationProgress):
609    """Recipe parsing progress"""
610    def __init__(self, current, total):
611        OperationProgress.__init__(self, current, total, "Recipe parsing")
612
613
614class CacheLoadStarted(OperationStarted):
615    """Loading of the dependency cache has begun"""
616    def __init__(self, total):
617        OperationStarted.__init__(self, "Loading cache Started")
618        self.total = total
619
620class CacheLoadProgress(OperationProgress):
621    """Cache loading progress"""
622    def __init__(self, current, total):
623        OperationProgress.__init__(self, current, total, "Loading cache")
624
625class CacheLoadCompleted(OperationCompleted):
626    """Cache loading is complete"""
627    def __init__(self, total, num_entries):
628        OperationCompleted.__init__(self, total, "Loading cache Completed")
629        self.num_entries = num_entries
630
631class TreeDataPreparationStarted(OperationStarted):
632    """Tree data preparation started"""
633    def __init__(self):
634        OperationStarted.__init__(self, "Preparing tree data Started")
635
636class TreeDataPreparationProgress(OperationProgress):
637    """Tree data preparation is in progress"""
638    def __init__(self, current, total):
639        OperationProgress.__init__(self, current, total, "Preparing tree data")
640
641class TreeDataPreparationCompleted(OperationCompleted):
642    """Tree data preparation completed"""
643    def __init__(self, total):
644        OperationCompleted.__init__(self, total, "Preparing tree data Completed")
645
646class DepTreeGenerated(Event):
647    """
648    Event when a dependency tree has been generated
649    """
650
651    def __init__(self, depgraph):
652        Event.__init__(self)
653        self._depgraph = depgraph
654
655class TargetsTreeGenerated(Event):
656    """
657    Event when a set of buildable targets has been generated
658    """
659    def __init__(self, model):
660        Event.__init__(self)
661        self._model = model
662
663class ReachableStamps(Event):
664    """
665    An event listing all stamps reachable after parsing
666    which the metadata may use to clean up stale data
667    """
668
669    def __init__(self, stamps):
670        Event.__init__(self)
671        self.stamps = stamps
672
673class StaleSetSceneTasks(Event):
674    """
675    An event listing setscene tasks which are 'stale' and will
676    be rerun. The metadata may use to clean up stale data.
677    tasks is a mapping of tasks and matching stale stamps.
678    """
679
680    def __init__(self, tasks):
681        Event.__init__(self)
682        self.tasks = tasks
683
684class FilesMatchingFound(Event):
685    """
686    Event when a list of files matching the supplied pattern has
687    been generated
688    """
689    def __init__(self, pattern, matches):
690        Event.__init__(self)
691        self._pattern = pattern
692        self._matches = matches
693
694class ConfigFilesFound(Event):
695    """
696    Event when a list of appropriate config files has been generated
697    """
698    def __init__(self, variable, values):
699        Event.__init__(self)
700        self._variable = variable
701        self._values = values
702
703class ConfigFilePathFound(Event):
704    """
705    Event when a path for a config file has been found
706    """
707    def __init__(self, path):
708        Event.__init__(self)
709        self._path = path
710
711class MsgBase(Event):
712    """Base class for messages"""
713
714    def __init__(self, msg):
715        self._message = msg
716        Event.__init__(self)
717
718class MsgDebug(MsgBase):
719    """Debug Message"""
720
721class MsgNote(MsgBase):
722    """Note Message"""
723
724class MsgWarn(MsgBase):
725    """Warning Message"""
726
727class MsgError(MsgBase):
728    """Error Message"""
729
730class MsgFatal(MsgBase):
731    """Fatal Message"""
732
733class MsgPlain(MsgBase):
734    """General output"""
735
736class LogExecTTY(Event):
737    """Send event containing program to spawn on tty of the logger"""
738    def __init__(self, msg, prog, sleep_delay, retries):
739        Event.__init__(self)
740        self.msg = msg
741        self.prog = prog
742        self.sleep_delay = sleep_delay
743        self.retries = retries
744
745class LogHandler(logging.Handler):
746    """Dispatch logging messages as bitbake events"""
747
748    def emit(self, record):
749        if record.exc_info:
750            etype, value, tb = record.exc_info
751            if hasattr(tb, 'tb_next'):
752                tb = list(bb.exceptions.extract_traceback(tb, context=3))
753            # Need to turn the value into something the logging system can pickle
754            record.bb_exc_info = (etype, value, tb)
755            record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
756            value = str(value)
757            record.exc_info = None
758        fire(record, None)
759
760    def filter(self, record):
761        record.taskpid = worker_pid
762        return True
763
764class MetadataEvent(Event):
765    """
766    Generic event that target for OE-Core classes
767    to report information during asynchronous execution
768    """
769    def __init__(self, eventtype, eventdata):
770        Event.__init__(self)
771        self.type = eventtype
772        self._localdata = eventdata
773
774class ProcessStarted(Event):
775    """
776    Generic process started event (usually part of the initial startup)
777    where further progress events will be delivered
778    """
779    def __init__(self, processname, total):
780        Event.__init__(self)
781        self.processname = processname
782        self.total = total
783
784class ProcessProgress(Event):
785    """
786    Generic process progress event (usually part of the initial startup)
787    """
788    def __init__(self, processname, progress):
789        Event.__init__(self)
790        self.processname = processname
791        self.progress = progress
792
793class ProcessFinished(Event):
794    """
795    Generic process finished event (usually part of the initial startup)
796    """
797    def __init__(self, processname):
798        Event.__init__(self)
799        self.processname = processname
800
801class SanityCheck(Event):
802    """
803    Event to run sanity checks, either raise errors or generate events as return status.
804    """
805    def __init__(self, generateevents = True):
806        Event.__init__(self)
807        self.generateevents = generateevents
808
809class SanityCheckPassed(Event):
810    """
811    Event to indicate sanity check has passed
812    """
813
814class SanityCheckFailed(Event):
815    """
816    Event to indicate sanity check has failed
817    """
818    def __init__(self, msg, network_error=False):
819        Event.__init__(self)
820        self._msg = msg
821        self._network_error = network_error
822
823class NetworkTest(Event):
824    """
825    Event to run network connectivity tests, either raise errors or generate events as return status.
826    """
827    def __init__(self, generateevents = True):
828        Event.__init__(self)
829        self.generateevents = generateevents
830
831class NetworkTestPassed(Event):
832    """
833    Event to indicate network test has passed
834    """
835
836class NetworkTestFailed(Event):
837    """
838    Event to indicate network test has failed
839    """
840
841class FindSigInfoResult(Event):
842    """
843    Event to return results from findSigInfo command
844    """
845    def __init__(self, result):
846        Event.__init__(self)
847        self.result = result
848