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