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