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