error.py revision 60aec2423a2c2a14c46dc21757a6fd748931f428
1#pylint: disable-msg=C0111 2 3""" 4Internal global error types 5""" 6 7import sys, traceback, threading 8from traceback import format_exception 9 10# Add names you want to be imported by 'from errors import *' to this list. 11# This must be list not a tuple as we modify it to include all of our 12# the Exception classes we define below at the end of this file. 13__all__ = ['format_error', 'context_aware', 'context', 'get_context', 14 'exception_context'] 15 16 17def format_error(): 18 t, o, tb = sys.exc_info() 19 trace = format_exception(t, o, tb) 20 # Clear the backtrace to prevent a circular reference 21 # in the heap -- as per tutorial 22 tb = '' 23 24 return ''.join(trace) 25 26 27# Exception context information: 28# ------------------------------ 29# Every function can have some context string associated with it. 30# The context string can be changed by calling context(str) and cleared by 31# calling context() with no parameters. 32# get_context() joins the current context strings of all functions in the 33# provided traceback. The result is a brief description of what the test was 34# doing in the provided traceback (which should be the traceback of a caught 35# exception). 36# 37# For example: assume a() calls b() and b() calls c(). 38# 39# @error.context_aware 40# def a(): 41# error.context("hello") 42# b() 43# error.context("world") 44# error.get_context() ----> 'world' 45# 46# @error.context_aware 47# def b(): 48# error.context("foo") 49# c() 50# 51# @error.context_aware 52# def c(): 53# error.context("bar") 54# error.get_context() ----> 'hello --> foo --> bar' 55# 56# The current context is automatically inserted into exceptions raised in 57# context_aware functions, so usually test code doesn't need to call 58# error.get_context(). 59 60ctx = threading.local() 61 62 63def _new_context(s=""): 64 if not hasattr(ctx, "contexts"): 65 ctx.contexts = [] 66 ctx.contexts.append(s) 67 68 69def _pop_context(): 70 ctx.contexts.pop() 71 72 73def context(s="", log=None): 74 """ 75 Set the context for the currently executing function and optionally log it. 76 77 @param s: A string. If not provided, the context for the current function 78 will be cleared. 79 @param log: A logging function to pass the context message to. If None, no 80 function will be called. 81 """ 82 ctx.contexts[-1] = s 83 if s and log: 84 log("Context: %s" % get_context()) 85 86 87def base_context(s="", log=None): 88 """ 89 Set the base context for the currently executing function and optionally 90 log it. The base context is just another context level that is hidden by 91 default. Functions that require a single context level should not use 92 base_context(). 93 94 @param s: A string. If not provided, the base context for the current 95 function will be cleared. 96 @param log: A logging function to pass the context message to. If None, no 97 function will be called. 98 """ 99 ctx.contexts[-1] = "" 100 ctx.contexts[-2] = s 101 if s and log: 102 log("Context: %s" % get_context()) 103 104 105def get_context(): 106 """Return the current context (or None if none is defined).""" 107 if hasattr(ctx, "contexts"): 108 return " --> ".join([s for s in ctx.contexts if s]) 109 110 111def exception_context(e): 112 """Return the context of a given exception (or None if none is defined).""" 113 if hasattr(e, "_context"): 114 return e._context # pylint: disable=W0212 115 116 117def set_exception_context(e, s): 118 """Set the context of a given exception.""" 119 e._context = s 120 121 122def join_contexts(s1, s2): 123 """Join two context strings.""" 124 if s1: 125 if s2: 126 return "%s --> %s" % (s1, s2) 127 else: 128 return s1 129 else: 130 return s2 131 132 133def context_aware(fn): 134 """A decorator that must be applied to functions that call context().""" 135 def new_fn(*args, **kwargs): 136 _new_context() 137 _new_context("(%s)" % fn.__name__) 138 try: 139 try: 140 return fn(*args, **kwargs) 141 except Exception, e: 142 if not exception_context(e): 143 set_exception_context(e, get_context()) 144 raise 145 finally: 146 _pop_context() 147 _pop_context() 148 new_fn.__name__ = fn.__name__ 149 new_fn.__doc__ = fn.__doc__ 150 new_fn.__dict__.update(fn.__dict__) 151 return new_fn 152 153 154def _context_message(e): 155 s = exception_context(e) 156 if s: 157 return " [context: %s]" % s 158 else: 159 return "" 160 161 162 163class TimeoutException(Exception): 164 """ 165 Generic exception raised on retry timeouts. 166 """ 167 pass 168 169 170class JobContinue(SystemExit): 171 """Allow us to bail out requesting continuance.""" 172 pass 173 174 175class JobComplete(SystemExit): 176 """Allow us to bail out indicating continuation not required.""" 177 pass 178 179 180class AutotestError(Exception): 181 """The parent of all errors deliberatly thrown within the client code.""" 182 def __str__(self): 183 return Exception.__str__(self) + _context_message(self) 184 185 186class JobError(AutotestError): 187 """Indicates an error which terminates and fails the whole job (ABORT).""" 188 pass 189 190 191class UnhandledJobError(JobError): 192 """Indicates an unhandled error in a job.""" 193 def __init__(self, unhandled_exception): 194 if isinstance(unhandled_exception, JobError): 195 JobError.__init__(self, *unhandled_exception.args) 196 elif isinstance(unhandled_exception, str): 197 JobError.__init__(self, unhandled_exception) 198 else: 199 msg = "Unhandled %s: %s" 200 msg %= (unhandled_exception.__class__.__name__, 201 unhandled_exception) 202 if not isinstance(unhandled_exception, AutotestError): 203 msg += _context_message(unhandled_exception) 204 msg += "\n" + traceback.format_exc() 205 JobError.__init__(self, msg) 206 207 208class TestBaseException(AutotestError): 209 """The parent of all test exceptions.""" 210 # Children are required to override this. Never instantiate directly. 211 exit_status = "NEVER_RAISE_THIS" 212 213 214class TestError(TestBaseException): 215 """Indicates that something went wrong with the test harness itself.""" 216 exit_status = "ERROR" 217 218 219class TestNAError(TestBaseException): 220 """Indictates that the test is Not Applicable. Should be thrown 221 when various conditions are such that the test is inappropriate.""" 222 exit_status = "TEST_NA" 223 224 225class TestFail(TestBaseException): 226 """Indicates that the test failed, but the job will not continue.""" 227 exit_status = "FAIL" 228 229 230class TestWarn(TestBaseException): 231 """Indicates that bad things (may) have happened, but not an explicit 232 failure.""" 233 exit_status = "WARN" 234 235 236class TestFailRetry(TestFail): 237 """Indicates that the test failed, but in a manner that may be retried 238 if test retries are enabled for this test.""" 239 exit_status = "FAIL" 240 241 242class UnhandledTestError(TestError): 243 """Indicates an unhandled error in a test.""" 244 def __init__(self, unhandled_exception): 245 if isinstance(unhandled_exception, TestError): 246 TestError.__init__(self, *unhandled_exception.args) 247 elif isinstance(unhandled_exception, str): 248 TestError.__init__(self, unhandled_exception) 249 else: 250 msg = "Unhandled %s: %s" 251 msg %= (unhandled_exception.__class__.__name__, 252 unhandled_exception) 253 if not isinstance(unhandled_exception, AutotestError): 254 msg += _context_message(unhandled_exception) 255 msg += "\n" + traceback.format_exc() 256 TestError.__init__(self, msg) 257 258 259class UnhandledTestFail(TestFail): 260 """Indicates an unhandled fail in a test.""" 261 def __init__(self, unhandled_exception): 262 if isinstance(unhandled_exception, TestFail): 263 TestFail.__init__(self, *unhandled_exception.args) 264 elif isinstance(unhandled_exception, str): 265 TestFail.__init__(self, unhandled_exception) 266 else: 267 msg = "Unhandled %s: %s" 268 msg %= (unhandled_exception.__class__.__name__, 269 unhandled_exception) 270 if not isinstance(unhandled_exception, AutotestError): 271 msg += _context_message(unhandled_exception) 272 msg += "\n" + traceback.format_exc() 273 TestFail.__init__(self, msg) 274 275 276class CmdError(TestError): 277 """\ 278 Indicates that a command failed, is fatal to the test unless caught. 279 """ 280 def __init__(self, command, result_obj, additional_text=None): 281 TestError.__init__(self, command, result_obj, additional_text) 282 self.command = command 283 self.result_obj = result_obj 284 self.additional_text = additional_text 285 286 def __str__(self): 287 if self.result_obj.exit_status is None: 288 msg = "Command <%s> failed and is not responding to signals" 289 msg %= self.command 290 else: 291 msg = "Command <%s> failed, rc=%d" 292 msg %= (self.command, self.result_obj.exit_status) 293 294 if self.additional_text: 295 msg += ", " + self.additional_text 296 msg += _context_message(self) 297 msg += '\n' + repr(self.result_obj) 298 return msg 299 300 301class PackageError(TestError): 302 """Indicates an error trying to perform a package operation.""" 303 pass 304 305 306class BarrierError(JobError): 307 """Indicates an error happened during a barrier operation.""" 308 pass 309 310 311class BarrierAbortError(BarrierError): 312 """Indicate that the barrier was explicitly aborted by a member.""" 313 pass 314 315 316class InstallError(JobError): 317 """Indicates an installation error which Terminates and fails the job.""" 318 pass 319 320 321class AutotestRunError(AutotestError): 322 """Indicates a problem running server side control files.""" 323 pass 324 325 326class AutotestTimeoutError(AutotestError): 327 """This exception is raised when an autotest test exceeds the timeout 328 parameter passed to run_timed_test and is killed. 329 """ 330 pass 331 332 333class HostRunErrorMixIn(Exception): 334 """ 335 Indicates a problem in the host run() function raised from client code. 336 Should always be constructed with a tuple of two args (error description 337 (str), run result object). This is a common class mixed in to create the 338 client and server side versions of it. 339 """ 340 def __init__(self, description, result_obj): 341 self.description = description 342 self.result_obj = result_obj 343 Exception.__init__(self, description, result_obj) 344 345 def __str__(self): 346 return self.description + '\n' + repr(self.result_obj) 347 348 349class HostInstallTimeoutError(JobError): 350 """ 351 Indicates the machine failed to be installed after the predetermined 352 timeout. 353 """ 354 pass 355 356 357class AutotestHostRunError(HostRunErrorMixIn, AutotestError): 358 pass 359 360 361# server-specific errors 362 363class AutoservError(Exception): 364 pass 365 366 367class AutoservSSHTimeout(AutoservError): 368 """SSH experienced a connection timeout""" 369 pass 370 371 372class AutoservRunError(HostRunErrorMixIn, AutoservError): 373 pass 374 375 376class AutoservSshPermissionDeniedError(AutoservRunError): 377 """Indicates that a SSH permission denied error was encountered.""" 378 pass 379 380 381class AutoservVirtError(AutoservError): 382 """Vitualization related error""" 383 pass 384 385 386class AutoservUnsupportedError(AutoservError): 387 """Error raised when you try to use an unsupported optional feature""" 388 pass 389 390 391class AutoservHostError(AutoservError): 392 """Error reaching a host""" 393 pass 394 395 396class AutoservHostIsShuttingDownError(AutoservHostError): 397 """Host is shutting down""" 398 pass 399 400 401class AutoservNotMountedHostError(AutoservHostError): 402 """Found unmounted partitions that should be mounted""" 403 pass 404 405 406class AutoservSshPingHostError(AutoservHostError): 407 """SSH ping failed""" 408 pass 409 410 411class AutoservDiskFullHostError(AutoservHostError): 412 """Not enough free disk space on host""" 413 def __init__(self, path, want_gb, free_space_gb): 414 AutoservHostError.__init__(self, 415 'Not enough free space on %s - %.3fGB free, want %.3fGB' % 416 (path, free_space_gb, want_gb)) 417 418 self.path = path 419 self.want_gb = want_gb 420 self.free_space_gb = free_space_gb 421 422 423class AutoservHardwareHostError(AutoservHostError): 424 """Found hardware problems with the host""" 425 pass 426 427 428class AutoservRebootError(AutoservError): 429 """Error occured while rebooting a machine""" 430 pass 431 432 433class AutoservShutdownError(AutoservRebootError): 434 """Error occured during shutdown of machine""" 435 pass 436 437 438class AutoservSubcommandError(AutoservError): 439 """Indicates an error while executing a (forked) subcommand""" 440 def __init__(self, func, exit_code): 441 AutoservError.__init__(self, func, exit_code) 442 self.func = func 443 self.exit_code = exit_code 444 445 def __str__(self): 446 return ("Subcommand %s failed with exit code %d" % 447 (self.func, self.exit_code)) 448 449 450class AutoservRepairTotalFailure(AutoservError): 451 """Raised if all attempts to repair the DUT failed.""" 452 pass 453 454 455class AutoservRepairFailure(AutoservError): 456 """Raised by a repair method if it is unable to repair a DUT.""" 457 pass 458 459 460class AutoservRepairMethodNA(AutoservError): 461 """Raised when for any reason a praticular repair method is NA.""" 462 pass 463 464 465class AutoservHardwareRepairRequestedError(AutoservError): 466 """ 467 Exception class raised from Host.repair_full() (or overrides) when software 468 repair fails but it successfully managed to request a hardware repair (by 469 notifying the staff, sending mail, etc) 470 """ 471 pass 472 473 474class AutoservHardwareRepairRequiredError(AutoservError): 475 """ 476 Exception class raised during repairs to indicate that a hardware repair 477 is going to be necessary. 478 """ 479 pass 480 481 482class AutoservInstallError(AutoservError): 483 """Error occured while installing autotest on a host""" 484 pass 485 486 487class AutoservPidAlreadyDeadError(AutoservError): 488 """Error occured by trying to kill a nonexistant PID""" 489 pass 490 491 492# packaging system errors 493 494class PackagingError(AutotestError): 495 'Abstract error class for all packaging related errors.' 496 497 498class PackageUploadError(PackagingError): 499 'Raised when there is an error uploading the package' 500 501 502class PackageFetchError(PackagingError): 503 'Raised when there is an error fetching the package' 504 505 506class PackageRemoveError(PackagingError): 507 'Raised when there is an error removing the package' 508 509 510class PackageInstallError(PackagingError): 511 'Raised when there is an error installing the package' 512 513 514class RepoDiskFullError(PackagingError): 515 'Raised when the destination for packages is full' 516 517 518class RepoWriteError(PackagingError): 519 "Raised when packager cannot write to a repo's desitnation" 520 521 522class RepoUnknownError(PackagingError): 523 "Raised when packager cannot write to a repo's desitnation" 524 525 526class RepoError(PackagingError): 527 "Raised when a repo isn't working in some way" 528 529 530class CrosDynamicSuiteException(Exception): 531 """ 532 Base class for exceptions coming from dynamic suite code in 533 server/cros/dynamic_suite/*. 534 """ 535 pass 536 537 538class StageBuildFailure(CrosDynamicSuiteException): 539 """Raised when the dev server throws 500 while staging a build.""" 540 pass 541 542 543class ControlFileEmpty(CrosDynamicSuiteException): 544 """Raised when the control file exists on the server, but can't be read.""" 545 pass 546 547 548class ControlFileMalformed(CrosDynamicSuiteException): 549 """Raised when an invalid control file is read.""" 550 pass 551 552 553class AsynchronousBuildFailure(CrosDynamicSuiteException): 554 """Raised when the dev server throws 500 while finishing staging of a build. 555 """ 556 pass 557 558 559class SuiteArgumentException(CrosDynamicSuiteException): 560 """Raised when improper arguments are used to run a suite.""" 561 pass 562 563 564class MalformedDependenciesException(CrosDynamicSuiteException): 565 """Raised when a build has a malformed dependency_info file.""" 566 pass 567 568 569class InadequateHostsException(CrosDynamicSuiteException): 570 """Raised when there are too few hosts to run a suite.""" 571 pass 572 573 574class NoHostsException(CrosDynamicSuiteException): 575 """Raised when there are no healthy hosts to run a suite.""" 576 pass 577 578 579class ControlFileNotFound(CrosDynamicSuiteException): 580 """Raised when a control file cannot be found and/or read.""" 581 pass 582 583 584class NoControlFileList(CrosDynamicSuiteException): 585 """Raised to indicate that a listing can't be done.""" 586 pass 587 588 589class HostLockManagerReuse(CrosDynamicSuiteException): 590 """Raised when a caller tries to re-use a HostLockManager instance.""" 591 pass 592 593 594class ReimageAbortedException(CrosDynamicSuiteException): 595 """Raised when a Reimage job is aborted""" 596 pass 597 598 599class UnknownReimageType(CrosDynamicSuiteException): 600 """Raised when a suite passes in an invalid reimage type""" 601 pass 602 603 604class LabIsDownException(Exception): 605 """Raised when the Lab is Down""" 606 pass 607 608 609class BoardIsDisabledException(Exception): 610 """Raised when a certain board is disabled in the Lab""" 611 pass 612 613 614class NoUniquePackageFound(Exception): 615 """Raised when an executable cannot be mapped back to a single package.""" 616 pass 617 618 619# This MUST remain at the end of the file. 620# Limit 'from error import *' to only import the exception instances. 621for _name, _thing in locals().items(): 622 try: 623 if issubclass(_thing, Exception): 624 __all__.append(_name) 625 except TypeError: 626 pass # _thing not a class 627__all__ = tuple(__all__) 628