Home | History | Annotate | Download | only in common_lib
      1 #pylint: disable-msg=C0111
      2 
      3 """
      4 Internal global error types
      5 """
      6 
      7 import sys, traceback, threading
      8 from 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 
     17 def 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 
     60 ctx = threading.local()
     61 
     62 
     63 def _new_context(s=""):
     64     if not hasattr(ctx, "contexts"):
     65         ctx.contexts = []
     66     ctx.contexts.append(s)
     67 
     68 
     69 def _pop_context():
     70     ctx.contexts.pop()
     71 
     72 
     73 def 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 
     87 def 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 
    105 def 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 
    111 def 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 
    117 def set_exception_context(e, s):
    118     """Set the context of a given exception."""
    119     e._context = s
    120 
    121 
    122 def 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 
    133 def 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 
    154 def _context_message(e):
    155     s = exception_context(e)
    156     if s:
    157         return "    [context: %s]" % s
    158     else:
    159         return ""
    160 
    161 
    162 
    163 class TimeoutException(Exception):
    164     """
    165     Generic exception raised on retry timeouts.
    166     """
    167     pass
    168 
    169 
    170 class JobContinue(SystemExit):
    171     """Allow us to bail out requesting continuance."""
    172     pass
    173 
    174 
    175 class JobComplete(SystemExit):
    176     """Allow us to bail out indicating continuation not required."""
    177     pass
    178 
    179 
    180 class 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 
    186 class JobError(AutotestError):
    187     """Indicates an error which terminates and fails the whole job (ABORT)."""
    188     pass
    189 
    190 
    191 class 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 
    208 class 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 
    214 class TestError(TestBaseException):
    215     """Indicates that something went wrong with the test harness itself."""
    216     exit_status = "ERROR"
    217 
    218 
    219 class 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 
    225 class TestFail(TestBaseException):
    226     """Indicates that the test failed, but the job will not continue."""
    227     exit_status = "FAIL"
    228 
    229 
    230 class TestWarn(TestBaseException):
    231     """Indicates that bad things (may) have happened, but not an explicit
    232     failure."""
    233     exit_status = "WARN"
    234 
    235 
    236 class 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 
    242 class 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 
    259 class 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 
    276 class 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 
    299 class CmdTimeoutError(CmdError):
    300     """Indicates that a command timed out."""
    301     pass
    302 
    303 
    304 class PackageError(TestError):
    305     """Indicates an error trying to perform a package operation."""
    306     pass
    307 
    308 
    309 class BarrierError(JobError):
    310     """Indicates an error happened during a barrier operation."""
    311     pass
    312 
    313 
    314 class BarrierAbortError(BarrierError):
    315     """Indicate that the barrier was explicitly aborted by a member."""
    316     pass
    317 
    318 
    319 class InstallError(JobError):
    320     """Indicates an installation error which Terminates and fails the job."""
    321     pass
    322 
    323 
    324 class AutotestRunError(AutotestError):
    325     """Indicates a problem running server side control files."""
    326     pass
    327 
    328 
    329 class 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 
    336 class 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 
    352 class HostInstallTimeoutError(JobError):
    353     """
    354     Indicates the machine failed to be installed after the predetermined
    355     timeout.
    356     """
    357     pass
    358 
    359 
    360 class AutotestHostRunError(HostRunErrorMixIn, AutotestError):
    361     pass
    362 
    363 
    364 # server-specific errors
    365 
    366 class AutoservError(Exception):
    367     pass
    368 
    369 
    370 class AutoservSSHTimeout(AutoservError):
    371     """SSH experienced a connection timeout"""
    372     pass
    373 
    374 
    375 class AutoservRunError(HostRunErrorMixIn, AutoservError):
    376     pass
    377 
    378 
    379 class AutoservSshPermissionDeniedError(AutoservRunError):
    380     """Indicates that a SSH permission denied error was encountered."""
    381     pass
    382 
    383 
    384 class AutoservVirtError(AutoservError):
    385     """Vitualization related error"""
    386     pass
    387 
    388 
    389 class AutoservUnsupportedError(AutoservError):
    390     """Error raised when you try to use an unsupported optional feature"""
    391     pass
    392 
    393 
    394 class AutoservHostError(AutoservError):
    395     """Error reaching a host"""
    396     pass
    397 
    398 
    399 class AutoservHostIsShuttingDownError(AutoservHostError):
    400     """Host is shutting down"""
    401     pass
    402 
    403 
    404 class AutoservNotMountedHostError(AutoservHostError):
    405     """Found unmounted partitions that should be mounted"""
    406     pass
    407 
    408 
    409 class AutoservSshPingHostError(AutoservHostError):
    410     """SSH ping failed"""
    411     pass
    412 
    413 
    414 class 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 
    426 class 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 
    438 class AutoservHardwareHostError(AutoservHostError):
    439     """Found hardware problems with the host"""
    440     pass
    441 
    442 
    443 class AutoservRebootError(AutoservError):
    444     """Error occured while rebooting a machine"""
    445     pass
    446 
    447 
    448 class AutoservShutdownError(AutoservRebootError):
    449     """Error occured during shutdown of machine"""
    450     pass
    451 
    452 
    453 class AutoservSuspendError(AutoservRebootError):
    454     """Error occured while suspending a machine"""
    455     pass
    456 
    457 
    458 class 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 
    470 class AutoservRepairTotalFailure(AutoservError):
    471     """Raised if all attempts to repair the DUT failed."""
    472     pass
    473 
    474 
    475 class AutoservRepairFailure(AutoservError):
    476     """Raised by a repair method if it is unable to repair a DUT."""
    477     pass
    478 
    479 
    480 class AutoservRepairMethodNA(AutoservError):
    481     """Raised when for any reason a praticular repair method is NA."""
    482     pass
    483 
    484 
    485 class AutoservInstallError(AutoservError):
    486     """Error occured while installing autotest on a host"""
    487     pass
    488 
    489 
    490 class AutoservPidAlreadyDeadError(AutoservError):
    491     """Error occured by trying to kill a nonexistant PID"""
    492     pass
    493 
    494 
    495 class AutoservCrashLogCollectRequired(AutoservError):
    496     """Need to collect crash-logs first"""
    497     pass
    498 
    499 
    500 # packaging system errors
    501 
    502 class PackagingError(AutotestError):
    503     'Abstract error class for all packaging related errors.'
    504 
    505 
    506 class PackageUploadError(PackagingError):
    507     'Raised when there is an error uploading the package'
    508 
    509 
    510 class PackageFetchError(PackagingError):
    511     'Raised when there is an error fetching the package'
    512 
    513 
    514 class PackageRemoveError(PackagingError):
    515     'Raised when there is an error removing the package'
    516 
    517 
    518 class PackageInstallError(PackagingError):
    519     'Raised when there is an error installing the package'
    520 
    521 
    522 class RepoDiskFullError(PackagingError):
    523     'Raised when the destination for packages is full'
    524 
    525 
    526 class RepoWriteError(PackagingError):
    527     "Raised when packager cannot write to a repo's desitnation"
    528 
    529 
    530 class RepoUnknownError(PackagingError):
    531     "Raised when packager cannot write to a repo's desitnation"
    532 
    533 
    534 class RepoError(PackagingError):
    535     "Raised when a repo isn't working in some way"
    536 
    537 
    538 class StageControlFileFailure(Exception):
    539     """Exceptions encountered staging control files."""
    540     pass
    541 
    542 
    543 class CrosDynamicSuiteException(Exception):
    544     """
    545     Base class for exceptions coming from dynamic suite code in
    546     server/cros/dynamic_suite/*.
    547     """
    548     pass
    549 
    550 
    551 class StageBuildFailure(CrosDynamicSuiteException):
    552     """Raised when the dev server throws 500 while staging a build."""
    553     pass
    554 
    555 
    556 class ControlFileEmpty(CrosDynamicSuiteException):
    557     """Raised when the control file exists on the server, but can't be read."""
    558     pass
    559 
    560 
    561 class ControlFileMalformed(CrosDynamicSuiteException):
    562     """Raised when an invalid control file is read."""
    563     pass
    564 
    565 
    566 class AsynchronousBuildFailure(CrosDynamicSuiteException):
    567     """Raised when the dev server throws 500 while finishing staging of a build.
    568     """
    569     pass
    570 
    571 
    572 class SuiteArgumentException(CrosDynamicSuiteException):
    573     """Raised when improper arguments are used to run a suite."""
    574     pass
    575 
    576 
    577 class MalformedDependenciesException(CrosDynamicSuiteException):
    578     """Raised when a build has a malformed dependency_info file."""
    579     pass
    580 
    581 
    582 class InadequateHostsException(CrosDynamicSuiteException):
    583     """Raised when there are too few hosts to run a suite."""
    584     pass
    585 
    586 
    587 class NoHostsException(CrosDynamicSuiteException):
    588     """Raised when there are no healthy hosts to run a suite."""
    589     pass
    590 
    591 
    592 class ControlFileNotFound(CrosDynamicSuiteException):
    593     """Raised when a control file cannot be found and/or read."""
    594     pass
    595 
    596 
    597 class NoControlFileList(CrosDynamicSuiteException):
    598     """Raised to indicate that a listing can't be done."""
    599     pass
    600 
    601 
    602 class HostLockManagerReuse(CrosDynamicSuiteException):
    603     """Raised when a caller tries to re-use a HostLockManager instance."""
    604     pass
    605 
    606 
    607 class ReimageAbortedException(CrosDynamicSuiteException):
    608     """Raised when a Reimage job is aborted"""
    609     pass
    610 
    611 
    612 class UnknownReimageType(CrosDynamicSuiteException):
    613     """Raised when a suite passes in an invalid reimage type"""
    614     pass
    615 
    616 
    617 class NoUniquePackageFound(Exception):
    618     """Raised when an executable cannot be mapped back to a single package."""
    619     pass
    620 
    621 
    622 class RPCException(Exception):
    623     """Raised when an RPC encounters an error that a client might wish to
    624     handle specially."""
    625     pass
    626 
    627 
    628 class NoEligibleHostException(RPCException):
    629     """Raised when no host could satisfy the requirements of a job."""
    630     pass
    631 
    632 
    633 class InvalidBgJobCall(Exception):
    634     """Raised when an invalid call is made to a BgJob object."""
    635     pass
    636 
    637 
    638 class HeartbeatOnlyAllowedInShardModeException(Exception):
    639     """Raised when a heartbeat is attempted but not allowed."""
    640     pass
    641 
    642 
    643 class UnallowedRecordsSentToMaster(Exception):
    644     pass
    645 
    646 
    647 class InvalidDataError(Exception):
    648     """Exception raised when invalid data provided for database operation.
    649     """
    650     pass
    651 
    652 
    653 class ContainerError(Exception):
    654     """Exception raised when program runs into error using container.
    655     """
    656 
    657 
    658 class 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.
    664 for _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