xref: /openbmc/openbmc/poky/bitbake/lib/bb/event.py (revision 12fc939c)
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 occassionally."""
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_thread_lock_enabled = False
72
73if hasattr(__builtins__, '__setitem__'):
74    builtins = __builtins__
75else:
76    builtins = __builtins__.__dict__
77
78def enable_threadlock():
79    global _thread_lock_enabled
80    _thread_lock_enabled = True
81
82def disable_threadlock():
83    global _thread_lock_enabled
84    _thread_lock_enabled = False
85
86def execute_handler(name, handler, event, d):
87    event.data = d
88    addedd = False
89    if 'd' not in builtins:
90        builtins['d'] = d
91        addedd = True
92    try:
93        ret = handler(event)
94    except (bb.parse.SkipRecipe, bb.BBHandledException):
95        raise
96    except Exception:
97        etype, value, tb = sys.exc_info()
98        logger.error("Execution of event handler '%s' failed" % name,
99                        exc_info=(etype, value, tb.tb_next))
100        raise
101    except SystemExit as exc:
102        if exc.code != 0:
103            logger.error("Execution of event handler '%s' failed" % name)
104        raise
105    finally:
106        del event.data
107        if addedd:
108            del builtins['d']
109
110def fire_class_handlers(event, d):
111    if isinstance(event, logging.LogRecord):
112        return
113
114    eid = str(event.__class__)[8:-2]
115    evt_hmap = _event_handler_map.get(eid, {})
116    for name, handler in list(_handlers.items()):
117        if name in _catchall_handlers or name in evt_hmap:
118            if _eventfilter:
119                if not _eventfilter(name, handler, event, d):
120                    continue
121            if d is not None and not name in (d.getVar("__BBHANDLERS_MC") or set()):
122                continue
123            execute_handler(name, handler, event, d)
124
125ui_queue = []
126@atexit.register
127def print_ui_queue():
128    global ui_queue
129    """If we're exiting before a UI has been spawned, display any queued
130    LogRecords to the console."""
131    logger = logging.getLogger("BitBake")
132    if not _uiready:
133        from bb.msg import BBLogFormatter
134        # Flush any existing buffered content
135        sys.stdout.flush()
136        sys.stderr.flush()
137        stdout = logging.StreamHandler(sys.stdout)
138        stderr = logging.StreamHandler(sys.stderr)
139        formatter = BBLogFormatter("%(levelname)s: %(message)s")
140        stdout.setFormatter(formatter)
141        stderr.setFormatter(formatter)
142
143        # First check to see if we have any proper messages
144        msgprint = False
145        msgerrs = False
146
147        # Should we print to stderr?
148        for event in ui_queue[:]:
149            if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING:
150                msgerrs = True
151                break
152
153        if msgerrs:
154            logger.addHandler(stderr)
155        else:
156            logger.addHandler(stdout)
157
158        for event in ui_queue[:]:
159            if isinstance(event, logging.LogRecord):
160                if event.levelno > logging.DEBUG:
161                    logger.handle(event)
162                    msgprint = True
163
164        # Nope, so just print all of the messages we have (including debug messages)
165        if not msgprint:
166            for event in ui_queue[:]:
167                if isinstance(event, logging.LogRecord):
168                    logger.handle(event)
169        if msgerrs:
170            logger.removeHandler(stderr)
171        else:
172            logger.removeHandler(stdout)
173        ui_queue = []
174
175def fire_ui_handlers(event, d):
176    global _thread_lock
177    global _thread_lock_enabled
178
179    if not _uiready:
180        # No UI handlers registered yet, queue up the messages
181        ui_queue.append(event)
182        return
183
184    if _thread_lock_enabled:
185        _thread_lock.acquire()
186
187    errors = []
188    for h in _ui_handlers:
189        #print "Sending event %s" % event
190        try:
191             if not _ui_logfilters[h].filter(event):
192                 continue
193             # We use pickle here since it better handles object instances
194             # which xmlrpc's marshaller does not. Events *must* be serializable
195             # by pickle.
196             if hasattr(_ui_handlers[h].event, "sendpickle"):
197                _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
198             else:
199                _ui_handlers[h].event.send(event)
200        except:
201            errors.append(h)
202    for h in errors:
203        del _ui_handlers[h]
204
205    if _thread_lock_enabled:
206        _thread_lock.release()
207
208def fire(event, d):
209    """Fire off an Event"""
210
211    # We can fire class handlers in the worker process context and this is
212    # desired so they get the task based datastore.
213    # UI handlers need to be fired in the server context so we defer this. They
214    # don't have a datastore so the datastore context isn't a problem.
215
216    fire_class_handlers(event, d)
217    if worker_fire:
218        worker_fire(event, d)
219    else:
220        # If messages have been queued up, clear the queue
221        global _uiready, ui_queue
222        if _uiready and ui_queue:
223            for queue_event in ui_queue:
224                fire_ui_handlers(queue_event, d)
225            ui_queue = []
226        fire_ui_handlers(event, d)
227
228def fire_from_worker(event, d):
229    fire_ui_handlers(event, d)
230
231noop = lambda _: None
232def register(name, handler, mask=None, filename=None, lineno=None, data=None):
233    """Register an Event handler"""
234
235    if data is not None and data.getVar("BB_CURRENT_MC"):
236        mc = data.getVar("BB_CURRENT_MC")
237        name = '%s%s' % (mc.replace('-', '_'), name)
238
239    # already registered
240    if name in _handlers:
241        if data is not None:
242            bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
243            bbhands_mc.add(name)
244            data.setVar("__BBHANDLERS_MC", bbhands_mc)
245        return AlreadyRegistered
246
247    if handler is not None:
248        # handle string containing python code
249        if isinstance(handler, str):
250            tmp = "def %s(e):\n%s" % (name, handler)
251            try:
252                code = bb.methodpool.compile_cache(tmp)
253                if not code:
254                    if filename is None:
255                        filename = "%s(e)" % name
256                    code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
257                    if lineno is not None:
258                        ast.increment_lineno(code, lineno-1)
259                    code = compile(code, filename, "exec")
260                    bb.methodpool.compile_cache_add(tmp, code)
261            except SyntaxError:
262                logger.error("Unable to register event handler '%s':\n%s", name,
263                             ''.join(traceback.format_exc(limit=0)))
264                _handlers[name] = noop
265                return
266            env = {}
267            bb.utils.better_exec(code, env)
268            func = bb.utils.better_eval(name, env)
269            _handlers[name] = func
270        else:
271            _handlers[name] = handler
272
273        if not mask or '*' in mask:
274            _catchall_handlers[name] = True
275        else:
276            for m in mask:
277                if _event_handler_map.get(m, None) is None:
278                    _event_handler_map[m] = {}
279                _event_handler_map[m][name] = True
280
281        if data is not None:
282            bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
283            bbhands_mc.add(name)
284            data.setVar("__BBHANDLERS_MC", bbhands_mc)
285
286        return Registered
287
288def remove(name, handler, data=None):
289    """Remove an Event handler"""
290    if data is not None:
291        if data.getVar("BB_CURRENT_MC"):
292            mc = data.getVar("BB_CURRENT_MC")
293            name = '%s%s' % (mc.replace('-', '_'), name)
294
295    _handlers.pop(name)
296    if name in _catchall_handlers:
297        _catchall_handlers.pop(name)
298    for event in _event_handler_map.keys():
299        if name in _event_handler_map[event]:
300            _event_handler_map[event].pop(name)
301
302    if data is not None:
303        bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
304        if name in bbhands_mc:
305            bbhands_mc.remove(name)
306            data.setVar("__BBHANDLERS_MC", bbhands_mc)
307
308def get_handlers():
309    return _handlers
310
311def set_handlers(handlers):
312    global _handlers
313    _handlers = handlers
314
315def set_eventfilter(func):
316    global _eventfilter
317    _eventfilter = func
318
319def register_UIHhandler(handler, mainui=False):
320    bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
321    _ui_handlers[_ui_handler_seq] = handler
322    level, debug_domains = bb.msg.constructLogOptions()
323    _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
324    if mainui:
325        global _uiready
326        _uiready = _ui_handler_seq
327    return _ui_handler_seq
328
329def unregister_UIHhandler(handlerNum, mainui=False):
330    if mainui:
331        global _uiready
332        _uiready = False
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 aborted"""
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 asynchrous 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