xref: /openbmc/openbmc/poky/bitbake/lib/bb/event.py (revision 8460358c)
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.utils
23
24# This is the pid for which we should generate the event. This is set when
25# the runqueue forks off.
26worker_pid = 0
27worker_fire = None
28
29logger = logging.getLogger('BitBake.Event')
30
31class Event(object):
32    """Base class for events"""
33
34    def __init__(self):
35        self.pid = worker_pid
36
37
38class HeartbeatEvent(Event):
39    """Triggered at regular time intervals of 10 seconds. Other events can fire much more often
40       (runQueueTaskStarted when there are many short tasks) or not at all for long periods
41       of time (again runQueueTaskStarted, when there is just one long-running task), so this
42       event is more suitable for doing some task-independent work occasionally."""
43    def __init__(self, time):
44        Event.__init__(self)
45        self.time = time
46
47Registered        = 10
48AlreadyRegistered = 14
49
50def get_class_handlers():
51    return _handlers
52
53def set_class_handlers(h):
54    global _handlers
55    _handlers = h
56
57def clean_class_handlers():
58    return collections.OrderedDict()
59
60# Internal
61_handlers = clean_class_handlers()
62_ui_handlers = {}
63_ui_logfilters = {}
64_ui_handler_seq = 0
65_event_handler_map = {}
66_catchall_handlers = {}
67_eventfilter = None
68_uiready = False
69_thread_lock = threading.Lock()
70_heartbeat_enabled = False
71_should_exit = threading.Event()
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
89#
90# In long running code, this function should be called periodically
91# to check if we should exit due to an interuption (.e.g Ctrl+C from the UI)
92#
93def check_for_interrupts(d):
94    global _should_exit
95    if _should_exit.is_set():
96        bb.warn("Exiting due to interrupt.")
97        raise bb.BBHandledException()
98
99def execute_handler(name, handler, event, d):
100    event.data = d
101    try:
102        ret = handler(event, d)
103    except (bb.parse.SkipRecipe, bb.BBHandledException):
104        raise
105    except Exception:
106        etype, value, tb = sys.exc_info()
107        logger.error("Execution of event handler '%s' failed" % name,
108                        exc_info=(etype, value, tb.tb_next))
109        raise
110    except SystemExit as exc:
111        if exc.code != 0:
112            logger.error("Execution of event handler '%s' failed" % name)
113        raise
114    finally:
115        del event.data
116
117
118def fire_class_handlers(event, d):
119    if isinstance(event, logging.LogRecord):
120        return
121
122    eid = str(event.__class__)[8:-2]
123    evt_hmap = _event_handler_map.get(eid, {})
124    for name, handler in list(_handlers.items()):
125        if name in _catchall_handlers or name in evt_hmap:
126            if _eventfilter:
127                if not _eventfilter(name, handler, event, d):
128                    continue
129            if d is not None and not name in (d.getVar("__BBHANDLERS_MC") or set()):
130                continue
131            execute_handler(name, handler, event, d)
132
133ui_queue = []
134@atexit.register
135def print_ui_queue():
136    global ui_queue
137    """If we're exiting before a UI has been spawned, display any queued
138    LogRecords to the console."""
139    logger = logging.getLogger("BitBake")
140    if not _uiready:
141        from bb.msg import BBLogFormatter
142        # Flush any existing buffered content
143        try:
144            sys.stdout.flush()
145        except:
146            pass
147        try:
148            sys.stderr.flush()
149        except:
150            pass
151        stdout = logging.StreamHandler(sys.stdout)
152        stderr = logging.StreamHandler(sys.stderr)
153        formatter = BBLogFormatter("%(levelname)s: %(message)s")
154        stdout.setFormatter(formatter)
155        stderr.setFormatter(formatter)
156
157        # First check to see if we have any proper messages
158        msgprint = False
159        msgerrs = False
160
161        # Should we print to stderr?
162        for event in ui_queue[:]:
163            if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING:
164                msgerrs = True
165                break
166
167        if msgerrs:
168            logger.addHandler(stderr)
169        else:
170            logger.addHandler(stdout)
171
172        for event in ui_queue[:]:
173            if isinstance(event, logging.LogRecord):
174                if event.levelno > logging.DEBUG:
175                    logger.handle(event)
176                    msgprint = True
177
178        # Nope, so just print all of the messages we have (including debug messages)
179        if not msgprint:
180            for event in ui_queue[:]:
181                if isinstance(event, logging.LogRecord):
182                    logger.handle(event)
183        if msgerrs:
184            logger.removeHandler(stderr)
185        else:
186            logger.removeHandler(stdout)
187        ui_queue = []
188
189def fire_ui_handlers(event, d):
190    global _thread_lock
191
192    if not _uiready:
193        # No UI handlers registered yet, queue up the messages
194        ui_queue.append(event)
195        return
196
197    with bb.utils.lock_timeout(_thread_lock):
198        errors = []
199        for h in _ui_handlers:
200            #print "Sending event %s" % event
201            try:
202                 if not _ui_logfilters[h].filter(event):
203                     continue
204                 # We use pickle here since it better handles object instances
205                 # which xmlrpc's marshaller does not. Events *must* be serializable
206                 # by pickle.
207                 if hasattr(_ui_handlers[h].event, "sendpickle"):
208                    _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
209                 else:
210                    _ui_handlers[h].event.send(event)
211            except:
212                errors.append(h)
213        for h in errors:
214            del _ui_handlers[h]
215
216def fire(event, d):
217    """Fire off an Event"""
218
219    # We can fire class handlers in the worker process context and this is
220    # desired so they get the task based datastore.
221    # UI handlers need to be fired in the server context so we defer this. They
222    # don't have a datastore so the datastore context isn't a problem.
223
224    fire_class_handlers(event, d)
225    if worker_fire:
226        worker_fire(event, d)
227    else:
228        # If messages have been queued up, clear the queue
229        global _uiready, ui_queue
230        if _uiready and ui_queue:
231            for queue_event in ui_queue:
232                fire_ui_handlers(queue_event, d)
233            ui_queue = []
234        fire_ui_handlers(event, d)
235
236def fire_from_worker(event, d):
237    fire_ui_handlers(event, d)
238
239noop = lambda _: None
240def register(name, handler, mask=None, filename=None, lineno=None, data=None):
241    """Register an Event handler"""
242
243    if data is not None and data.getVar("BB_CURRENT_MC"):
244        mc = data.getVar("BB_CURRENT_MC")
245        name = '%s%s' % (mc.replace('-', '_'), name)
246
247    # already registered
248    if name in _handlers:
249        if data is not None:
250            bbhands_mc = (data.getVar("__BBHANDLERS_MC") or set())
251            bbhands_mc.add(name)
252            data.setVar("__BBHANDLERS_MC", bbhands_mc)
253        return AlreadyRegistered
254
255    if handler is not None:
256        # handle string containing python code
257        if isinstance(handler, str):
258            tmp = "def %s(e, d):\n%s" % (name, handler)
259            # Inject empty lines to make code match lineno in filename
260            if lineno is not None:
261                tmp = "\n" * (lineno-1) + tmp
262            try:
263                code = bb.methodpool.compile_cache(tmp)
264                if not code:
265                    if filename is None:
266                        filename = "%s(e, d)" % name
267                    code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
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            record.bb_exc_formatted = traceback.format_exception(*record.exc_info)
762            record.exc_info = None
763        fire(record, None)
764
765    def filter(self, record):
766        record.taskpid = worker_pid
767        return True
768
769class MetadataEvent(Event):
770    """
771    Generic event that target for OE-Core classes
772    to report information during asynchronous execution
773    """
774    def __init__(self, eventtype, eventdata):
775        Event.__init__(self)
776        self.type = eventtype
777        self._localdata = eventdata
778
779class ProcessStarted(Event):
780    """
781    Generic process started event (usually part of the initial startup)
782    where further progress events will be delivered
783    """
784    def __init__(self, processname, total):
785        Event.__init__(self)
786        self.processname = processname
787        self.total = total
788
789class ProcessProgress(Event):
790    """
791    Generic process progress event (usually part of the initial startup)
792    """
793    def __init__(self, processname, progress):
794        Event.__init__(self)
795        self.processname = processname
796        self.progress = progress
797
798class ProcessFinished(Event):
799    """
800    Generic process finished event (usually part of the initial startup)
801    """
802    def __init__(self, processname):
803        Event.__init__(self)
804        self.processname = processname
805
806class SanityCheck(Event):
807    """
808    Event to run sanity checks, either raise errors or generate events as return status.
809    """
810    def __init__(self, generateevents = True):
811        Event.__init__(self)
812        self.generateevents = generateevents
813
814class SanityCheckPassed(Event):
815    """
816    Event to indicate sanity check has passed
817    """
818
819class SanityCheckFailed(Event):
820    """
821    Event to indicate sanity check has failed
822    """
823    def __init__(self, msg, network_error=False):
824        Event.__init__(self)
825        self._msg = msg
826        self._network_error = network_error
827
828class NetworkTest(Event):
829    """
830    Event to run network connectivity tests, either raise errors or generate events as return status.
831    """
832    def __init__(self, generateevents = True):
833        Event.__init__(self)
834        self.generateevents = generateevents
835
836class NetworkTestPassed(Event):
837    """
838    Event to indicate network test has passed
839    """
840
841class NetworkTestFailed(Event):
842    """
843    Event to indicate network test has failed
844    """
845
846class FindSigInfoResult(Event):
847    """
848    Event to return results from findSigInfo command
849    """
850    def __init__(self, result):
851        Event.__init__(self)
852        self.result = result
853
854class GetTaskSignatureResult(Event):
855    """
856    Event to return results from GetTaskSignatures command
857    """
858    def __init__(self, sig):
859        Event.__init__(self)
860        self.sig = sig
861
862class ParseError(Event):
863    """
864    Event to indicate parse failed
865    """
866    def __init__(self, msg):
867        super().__init__()
868        self._msg = msg
869