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 """Indicates that a command failed, is fatal to the test unless caught.""" 278 def __init__(self, command, result_obj, additional_text=None): 279 TestError.__init__(self, command, result_obj, additional_text) 280 self.command = command 281 self.result_obj = result_obj 282 self.additional_text = additional_text 283 284 def __str__(self): 285 if self.result_obj.exit_status is None: 286 msg = "Command <%s> failed and is not responding to signals" 287 msg %= self.command 288 else: 289 msg = "Command <%s> failed, rc=%d" 290 msg %= (self.command, self.result_obj.exit_status) 291 292 if self.additional_text: 293 msg += ", " + self.additional_text 294 msg += _context_message(self) 295 msg += '\n' + repr(self.result_obj) 296 return msg 297 298 299class CmdTimeoutError(CmdError): 300 """Indicates that a command timed out.""" 301 pass 302 303 304class PackageError(TestError): 305 """Indicates an error trying to perform a package operation.""" 306 pass 307 308 309class BarrierError(JobError): 310 """Indicates an error happened during a barrier operation.""" 311 pass 312 313 314class BarrierAbortError(BarrierError): 315 """Indicate that the barrier was explicitly aborted by a member.""" 316 pass 317 318 319class InstallError(JobError): 320 """Indicates an installation error which Terminates and fails the job.""" 321 pass 322 323 324class AutotestRunError(AutotestError): 325 """Indicates a problem running server side control files.""" 326 pass 327 328 329class AutotestTimeoutError(AutotestError): 330 """This exception is raised when an autotest test exceeds the timeout 331 parameter passed to run_timed_test and is killed. 332 """ 333 pass 334 335 336class HostRunErrorMixIn(Exception): 337 """ 338 Indicates a problem in the host run() function raised from client code. 339 Should always be constructed with a tuple of two args (error description 340 (str), run result object). This is a common class mixed in to create the 341 client and server side versions of it. 342 """ 343 def __init__(self, description, result_obj): 344 self.description = description 345 self.result_obj = result_obj 346 Exception.__init__(self, description, result_obj) 347 348 def __str__(self): 349 return self.description + '\n' + repr(self.result_obj) 350 351 352class HostInstallTimeoutError(JobError): 353 """ 354 Indicates the machine failed to be installed after the predetermined 355 timeout. 356 """ 357 pass 358 359 360class AutotestHostRunError(HostRunErrorMixIn, AutotestError): 361 pass 362 363 364# server-specific errors 365 366class AutoservError(Exception): 367 pass 368 369 370class AutoservSSHTimeout(AutoservError): 371 """SSH experienced a connection timeout""" 372 pass 373 374 375class AutoservRunError(HostRunErrorMixIn, AutoservError): 376 pass 377 378 379class AutoservSshPermissionDeniedError(AutoservRunError): 380 """Indicates that a SSH permission denied error was encountered.""" 381 pass 382 383 384class AutoservVirtError(AutoservError): 385 """Vitualization related error""" 386 pass 387 388 389class AutoservUnsupportedError(AutoservError): 390 """Error raised when you try to use an unsupported optional feature""" 391 pass 392 393 394class AutoservHostError(AutoservError): 395 """Error reaching a host""" 396 pass 397 398 399class AutoservHostIsShuttingDownError(AutoservHostError): 400 """Host is shutting down""" 401 pass 402 403 404class AutoservNotMountedHostError(AutoservHostError): 405 """Found unmounted partitions that should be mounted""" 406 pass 407 408 409class AutoservSshPingHostError(AutoservHostError): 410 """SSH ping failed""" 411 pass 412 413 414class AutoservDiskFullHostError(AutoservHostError): 415 """Not enough free disk space on host""" 416 417 def __init__(self, path, want_gb, free_space_gb): 418 super(AutoservDiskFullHostError, self).__init__( 419 'Not enough free space on %s - %.3fGB free, want %.3fGB' % 420 (path, free_space_gb, want_gb)) 421 self.path = path 422 self.want_gb = want_gb 423 self.free_space_gb = free_space_gb 424 425 426class AutoservNoFreeInodesError(AutoservHostError): 427 """Not enough free i-nodes on host""" 428 429 def __init__(self, path, want_inodes, free_inodes): 430 super(AutoservNoFreeInodesError, self).__init__( 431 'Not enough free inodes on %s - %d free, want %d' % 432 (path, free_inodes, want_inodes)) 433 self.path = path 434 self.want_inodes = want_inodes 435 self.free_inodes = free_inodes 436 437 438class AutoservHardwareHostError(AutoservHostError): 439 """Found hardware problems with the host""" 440 pass 441 442 443class AutoservRebootError(AutoservError): 444 """Error occured while rebooting a machine""" 445 pass 446 447 448class AutoservShutdownError(AutoservRebootError): 449 """Error occured during shutdown of machine""" 450 pass 451 452 453class AutoservSuspendError(AutoservRebootError): 454 """Error occured while suspending a machine""" 455 pass 456 457 458class AutoservSubcommandError(AutoservError): 459 """Indicates an error while executing a (forked) subcommand""" 460 def __init__(self, func, exit_code): 461 AutoservError.__init__(self, func, exit_code) 462 self.func = func 463 self.exit_code = exit_code 464 465 def __str__(self): 466 return ("Subcommand %s failed with exit code %d" % 467 (self.func, self.exit_code)) 468 469 470class AutoservRepairTotalFailure(AutoservError): 471 """Raised if all attempts to repair the DUT failed.""" 472 pass 473 474 475class AutoservRepairFailure(AutoservError): 476 """Raised by a repair method if it is unable to repair a DUT.""" 477 pass 478 479 480class AutoservRepairMethodNA(AutoservError): 481 """Raised when for any reason a praticular repair method is NA.""" 482 pass 483 484 485class AutoservInstallError(AutoservError): 486 """Error occured while installing autotest on a host""" 487 pass 488 489 490class AutoservPidAlreadyDeadError(AutoservError): 491 """Error occured by trying to kill a nonexistant PID""" 492 pass 493 494 495class AutoservCrashLogCollectRequired(AutoservError): 496 """Need to collect crash-logs first""" 497 pass 498 499 500# packaging system errors 501 502class PackagingError(AutotestError): 503 'Abstract error class for all packaging related errors.' 504 505 506class PackageUploadError(PackagingError): 507 'Raised when there is an error uploading the package' 508 509 510class PackageFetchError(PackagingError): 511 'Raised when there is an error fetching the package' 512 513 514class PackageRemoveError(PackagingError): 515 'Raised when there is an error removing the package' 516 517 518class PackageInstallError(PackagingError): 519 'Raised when there is an error installing the package' 520 521 522class RepoDiskFullError(PackagingError): 523 'Raised when the destination for packages is full' 524 525 526class RepoWriteError(PackagingError): 527 "Raised when packager cannot write to a repo's desitnation" 528 529 530class RepoUnknownError(PackagingError): 531 "Raised when packager cannot write to a repo's desitnation" 532 533 534class RepoError(PackagingError): 535 "Raised when a repo isn't working in some way" 536 537 538class StageControlFileFailure(Exception): 539 """Exceptions encountered staging control files.""" 540 pass 541 542 543class CrosDynamicSuiteException(Exception): 544 """ 545 Base class for exceptions coming from dynamic suite code in 546 server/cros/dynamic_suite/*. 547 """ 548 pass 549 550 551class StageBuildFailure(CrosDynamicSuiteException): 552 """Raised when the dev server throws 500 while staging a build.""" 553 pass 554 555 556class ControlFileEmpty(CrosDynamicSuiteException): 557 """Raised when the control file exists on the server, but can't be read.""" 558 pass 559 560 561class ControlFileMalformed(CrosDynamicSuiteException): 562 """Raised when an invalid control file is read.""" 563 pass 564 565 566class AsynchronousBuildFailure(CrosDynamicSuiteException): 567 """Raised when the dev server throws 500 while finishing staging of a build. 568 """ 569 pass 570 571 572class SuiteArgumentException(CrosDynamicSuiteException): 573 """Raised when improper arguments are used to run a suite.""" 574 pass 575 576 577class MalformedDependenciesException(CrosDynamicSuiteException): 578 """Raised when a build has a malformed dependency_info file.""" 579 pass 580 581 582class InadequateHostsException(CrosDynamicSuiteException): 583 """Raised when there are too few hosts to run a suite.""" 584 pass 585 586 587class NoHostsException(CrosDynamicSuiteException): 588 """Raised when there are no healthy hosts to run a suite.""" 589 pass 590 591 592class ControlFileNotFound(CrosDynamicSuiteException): 593 """Raised when a control file cannot be found and/or read.""" 594 pass 595 596 597class NoControlFileList(CrosDynamicSuiteException): 598 """Raised to indicate that a listing can't be done.""" 599 pass 600 601 602class HostLockManagerReuse(CrosDynamicSuiteException): 603 """Raised when a caller tries to re-use a HostLockManager instance.""" 604 pass 605 606 607class ReimageAbortedException(CrosDynamicSuiteException): 608 """Raised when a Reimage job is aborted""" 609 pass 610 611 612class UnknownReimageType(CrosDynamicSuiteException): 613 """Raised when a suite passes in an invalid reimage type""" 614 pass 615 616 617class NoUniquePackageFound(Exception): 618 """Raised when an executable cannot be mapped back to a single package.""" 619 pass 620 621 622class RPCException(Exception): 623 """Raised when an RPC encounters an error that a client might wish to 624 handle specially.""" 625 pass 626 627 628class NoEligibleHostException(RPCException): 629 """Raised when no host could satisfy the requirements of a job.""" 630 pass 631 632 633class InvalidBgJobCall(Exception): 634 """Raised when an invalid call is made to a BgJob object.""" 635 pass 636 637 638class HeartbeatOnlyAllowedInShardModeException(Exception): 639 """Raised when a heartbeat is attempted but not allowed.""" 640 pass 641 642 643class UnallowedRecordsSentToMaster(Exception): 644 pass 645 646 647class InvalidDataError(Exception): 648 """Exception raised when invalid data provided for database operation. 649 """ 650 pass 651 652 653class ContainerError(Exception): 654 """Exception raised when program runs into error using container. 655 """ 656 657 658class IllegalUser(Exception): 659 """Exception raise when a program runs as an illegal user.""" 660 661 662# This MUST remain at the end of the file. 663# Limit 'from error import *' to only import the exception instances. 664for _name, _thing in locals().items(): 665 try: 666 if issubclass(_thing, Exception): 667 __all__.append(_name) 668 except TypeError: 669 pass # _thing not a class 670__all__ = tuple(__all__) 671