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