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