#pylint: disable-msg=C0111 """ Internal global error types """ import sys, traceback, threading from traceback import format_exception # Add names you want to be imported by 'from errors import *' to this list. # This must be list not a tuple as we modify it to include all of our # the Exception classes we define below at the end of this file. __all__ = ['format_error', 'context_aware', 'context', 'get_context', 'exception_context'] def format_error(): t, o, tb = sys.exc_info() trace = format_exception(t, o, tb) # Clear the backtrace to prevent a circular reference # in the heap -- as per tutorial tb = '' return ''.join(trace) # Exception context information: # ------------------------------ # Every function can have some context string associated with it. # The context string can be changed by calling context(str) and cleared by # calling context() with no parameters. # get_context() joins the current context strings of all functions in the # provided traceback. The result is a brief description of what the test was # doing in the provided traceback (which should be the traceback of a caught # exception). # # For example: assume a() calls b() and b() calls c(). # # @error.context_aware # def a(): # error.context("hello") # b() # error.context("world") # error.get_context() ----> 'world' # # @error.context_aware # def b(): # error.context("foo") # c() # # @error.context_aware # def c(): # error.context("bar") # error.get_context() ----> 'hello --> foo --> bar' # # The current context is automatically inserted into exceptions raised in # context_aware functions, so usually test code doesn't need to call # error.get_context(). ctx = threading.local() def _new_context(s=""): if not hasattr(ctx, "contexts"): ctx.contexts = [] ctx.contexts.append(s) def _pop_context(): ctx.contexts.pop() def context(s="", log=None): """ Set the context for the currently executing function and optionally log it. @param s: A string. If not provided, the context for the current function will be cleared. @param log: A logging function to pass the context message to. If None, no function will be called. """ ctx.contexts[-1] = s if s and log: log("Context: %s" % get_context()) def base_context(s="", log=None): """ Set the base context for the currently executing function and optionally log it. The base context is just another context level that is hidden by default. Functions that require a single context level should not use base_context(). @param s: A string. If not provided, the base context for the current function will be cleared. @param log: A logging function to pass the context message to. If None, no function will be called. """ ctx.contexts[-1] = "" ctx.contexts[-2] = s if s and log: log("Context: %s" % get_context()) def get_context(): """Return the current context (or None if none is defined).""" if hasattr(ctx, "contexts"): return " --> ".join([s for s in ctx.contexts if s]) def exception_context(e): """Return the context of a given exception (or None if none is defined).""" if hasattr(e, "_context"): return e._context # pylint: disable=W0212 def set_exception_context(e, s): """Set the context of a given exception.""" e._context = s def join_contexts(s1, s2): """Join two context strings.""" if s1: if s2: return "%s --> %s" % (s1, s2) else: return s1 else: return s2 def context_aware(fn): """A decorator that must be applied to functions that call context().""" def new_fn(*args, **kwargs): _new_context() _new_context("(%s)" % fn.__name__) try: try: return fn(*args, **kwargs) except Exception, e: if not exception_context(e): set_exception_context(e, get_context()) raise finally: _pop_context() _pop_context() new_fn.__name__ = fn.__name__ new_fn.__doc__ = fn.__doc__ new_fn.__dict__.update(fn.__dict__) return new_fn def _context_message(e): s = exception_context(e) if s: return " [context: %s]" % s else: return "" class TimeoutException(Exception): """ Generic exception raised on retry timeouts. """ pass class JobContinue(SystemExit): """Allow us to bail out requesting continuance.""" pass class JobComplete(SystemExit): """Allow us to bail out indicating continuation not required.""" pass class AutotestError(Exception): """The parent of all errors deliberatly thrown within the client code.""" def __str__(self): return Exception.__str__(self) + _context_message(self) class JobError(AutotestError): """Indicates an error which terminates and fails the whole job (ABORT).""" pass class UnhandledJobError(JobError): """Indicates an unhandled error in a job.""" def __init__(self, unhandled_exception): if isinstance(unhandled_exception, JobError): JobError.__init__(self, *unhandled_exception.args) elif isinstance(unhandled_exception, str): JobError.__init__(self, unhandled_exception) else: msg = "Unhandled %s: %s" msg %= (unhandled_exception.__class__.__name__, unhandled_exception) if not isinstance(unhandled_exception, AutotestError): msg += _context_message(unhandled_exception) msg += "\n" + traceback.format_exc() JobError.__init__(self, msg) class TestBaseException(AutotestError): """The parent of all test exceptions.""" # Children are required to override this. Never instantiate directly. exit_status = "NEVER_RAISE_THIS" class TestError(TestBaseException): """Indicates that something went wrong with the test harness itself.""" exit_status = "ERROR" class TestNAError(TestBaseException): """Indictates that the test is Not Applicable. Should be thrown when various conditions are such that the test is inappropriate.""" exit_status = "TEST_NA" class TestFail(TestBaseException): """Indicates that the test failed, but the job will not continue.""" exit_status = "FAIL" class TestWarn(TestBaseException): """Indicates that bad things (may) have happened, but not an explicit failure.""" exit_status = "WARN" class TestFailRetry(TestFail): """Indicates that the test failed, but in a manner that may be retried if test retries are enabled for this test.""" exit_status = "FAIL" class UnhandledTestError(TestError): """Indicates an unhandled error in a test.""" def __init__(self, unhandled_exception): if isinstance(unhandled_exception, TestError): TestError.__init__(self, *unhandled_exception.args) elif isinstance(unhandled_exception, str): TestError.__init__(self, unhandled_exception) else: msg = "Unhandled %s: %s" msg %= (unhandled_exception.__class__.__name__, unhandled_exception) if not isinstance(unhandled_exception, AutotestError): msg += _context_message(unhandled_exception) msg += "\n" + traceback.format_exc() TestError.__init__(self, msg) class UnhandledTestFail(TestFail): """Indicates an unhandled fail in a test.""" def __init__(self, unhandled_exception): if isinstance(unhandled_exception, TestFail): TestFail.__init__(self, *unhandled_exception.args) elif isinstance(unhandled_exception, str): TestFail.__init__(self, unhandled_exception) else: msg = "Unhandled %s: %s" msg %= (unhandled_exception.__class__.__name__, unhandled_exception) if not isinstance(unhandled_exception, AutotestError): msg += _context_message(unhandled_exception) msg += "\n" + traceback.format_exc() TestFail.__init__(self, msg) class CmdError(TestError): """Indicates that a command failed, is fatal to the test unless caught.""" def __init__(self, command, result_obj, additional_text=None): TestError.__init__(self, command, result_obj, additional_text) self.command = command self.result_obj = result_obj self.additional_text = additional_text def __str__(self): if self.result_obj.exit_status is None: msg = "Command <%s> failed and is not responding to signals" msg %= self.command else: msg = "Command <%s> failed, rc=%d" msg %= (self.command, self.result_obj.exit_status) if self.additional_text: msg += ", " + self.additional_text msg += _context_message(self) msg += '\n' + repr(self.result_obj) return msg class CmdTimeoutError(CmdError): """Indicates that a command timed out.""" pass class PackageError(TestError): """Indicates an error trying to perform a package operation.""" pass class BarrierError(JobError): """Indicates an error happened during a barrier operation.""" pass class BarrierAbortError(BarrierError): """Indicate that the barrier was explicitly aborted by a member.""" pass class InstallError(JobError): """Indicates an installation error which Terminates and fails the job.""" pass class AutotestRunError(AutotestError): """Indicates a problem running server side control files.""" pass class AutotestTimeoutError(AutotestError): """This exception is raised when an autotest test exceeds the timeout parameter passed to run_timed_test and is killed. """ pass class HostRunErrorMixIn(Exception): """ Indicates a problem in the host run() function raised from client code. Should always be constructed with a tuple of two args (error description (str), run result object). This is a common class mixed in to create the client and server side versions of it. """ def __init__(self, description, result_obj): self.description = description self.result_obj = result_obj Exception.__init__(self, description, result_obj) def __str__(self): return self.description + '\n' + repr(self.result_obj) class HostInstallTimeoutError(JobError): """ Indicates the machine failed to be installed after the predetermined timeout. """ pass class AutotestHostRunError(HostRunErrorMixIn, AutotestError): pass # server-specific errors class AutoservError(Exception): pass class AutoservSSHTimeout(AutoservError): """SSH experienced a connection timeout""" pass class AutoservRunError(HostRunErrorMixIn, AutoservError): pass class AutoservSshPermissionDeniedError(AutoservRunError): """Indicates that a SSH permission denied error was encountered.""" pass class AutoservVirtError(AutoservError): """Vitualization related error""" pass class AutoservUnsupportedError(AutoservError): """Error raised when you try to use an unsupported optional feature""" pass class AutoservHostError(AutoservError): """Error reaching a host""" pass class AutoservHostIsShuttingDownError(AutoservHostError): """Host is shutting down""" pass class AutoservNotMountedHostError(AutoservHostError): """Found unmounted partitions that should be mounted""" pass class AutoservSshPingHostError(AutoservHostError): """SSH ping failed""" pass class AutoservDiskFullHostError(AutoservHostError): """Not enough free disk space on host""" def __init__(self, path, want_gb, free_space_gb): super(AutoservDiskFullHostError, self).__init__( 'Not enough free space on %s - %.3fGB free, want %.3fGB' % (path, free_space_gb, want_gb)) self.path = path self.want_gb = want_gb self.free_space_gb = free_space_gb class AutoservNoFreeInodesError(AutoservHostError): """Not enough free i-nodes on host""" def __init__(self, path, want_inodes, free_inodes): super(AutoservNoFreeInodesError, self).__init__( 'Not enough free inodes on %s - %d free, want %d' % (path, free_inodes, want_inodes)) self.path = path self.want_inodes = want_inodes self.free_inodes = free_inodes class AutoservHardwareHostError(AutoservHostError): """Found hardware problems with the host""" pass class AutoservRebootError(AutoservError): """Error occured while rebooting a machine""" pass class AutoservShutdownError(AutoservRebootError): """Error occured during shutdown of machine""" pass class AutoservSuspendError(AutoservRebootError): """Error occured while suspending a machine""" pass class AutoservSubcommandError(AutoservError): """Indicates an error while executing a (forked) subcommand""" def __init__(self, func, exit_code): AutoservError.__init__(self, func, exit_code) self.func = func self.exit_code = exit_code def __str__(self): return ("Subcommand %s failed with exit code %d" % (self.func, self.exit_code)) class AutoservRepairTotalFailure(AutoservError): """Raised if all attempts to repair the DUT failed.""" pass class AutoservRepairFailure(AutoservError): """Raised by a repair method if it is unable to repair a DUT.""" pass class AutoservRepairMethodNA(AutoservError): """Raised when for any reason a praticular repair method is NA.""" pass class AutoservInstallError(AutoservError): """Error occured while installing autotest on a host""" pass class AutoservPidAlreadyDeadError(AutoservError): """Error occured by trying to kill a nonexistant PID""" pass class AutoservCrashLogCollectRequired(AutoservError): """Need to collect crash-logs first""" pass # packaging system errors class PackagingError(AutotestError): 'Abstract error class for all packaging related errors.' class PackageUploadError(PackagingError): 'Raised when there is an error uploading the package' class PackageFetchError(PackagingError): 'Raised when there is an error fetching the package' class PackageRemoveError(PackagingError): 'Raised when there is an error removing the package' class PackageInstallError(PackagingError): 'Raised when there is an error installing the package' class RepoDiskFullError(PackagingError): 'Raised when the destination for packages is full' class RepoWriteError(PackagingError): "Raised when packager cannot write to a repo's desitnation" class RepoUnknownError(PackagingError): "Raised when packager cannot write to a repo's desitnation" class RepoError(PackagingError): "Raised when a repo isn't working in some way" class StageControlFileFailure(Exception): """Exceptions encountered staging control files.""" pass class CrosDynamicSuiteException(Exception): """ Base class for exceptions coming from dynamic suite code in server/cros/dynamic_suite/*. """ pass class StageBuildFailure(CrosDynamicSuiteException): """Raised when the dev server throws 500 while staging a build.""" pass class ControlFileEmpty(CrosDynamicSuiteException): """Raised when the control file exists on the server, but can't be read.""" pass class ControlFileMalformed(CrosDynamicSuiteException): """Raised when an invalid control file is read.""" pass class AsynchronousBuildFailure(CrosDynamicSuiteException): """Raised when the dev server throws 500 while finishing staging of a build. """ pass class SuiteArgumentException(CrosDynamicSuiteException): """Raised when improper arguments are used to run a suite.""" pass class MalformedDependenciesException(CrosDynamicSuiteException): """Raised when a build has a malformed dependency_info file.""" pass class InadequateHostsException(CrosDynamicSuiteException): """Raised when there are too few hosts to run a suite.""" pass class NoHostsException(CrosDynamicSuiteException): """Raised when there are no healthy hosts to run a suite.""" pass class ControlFileNotFound(CrosDynamicSuiteException): """Raised when a control file cannot be found and/or read.""" pass class NoControlFileList(CrosDynamicSuiteException): """Raised to indicate that a listing can't be done.""" pass class HostLockManagerReuse(CrosDynamicSuiteException): """Raised when a caller tries to re-use a HostLockManager instance.""" pass class ReimageAbortedException(CrosDynamicSuiteException): """Raised when a Reimage job is aborted""" pass class UnknownReimageType(CrosDynamicSuiteException): """Raised when a suite passes in an invalid reimage type""" pass class NoUniquePackageFound(Exception): """Raised when an executable cannot be mapped back to a single package.""" pass class RPCException(Exception): """Raised when an RPC encounters an error that a client might wish to handle specially.""" pass class NoEligibleHostException(RPCException): """Raised when no host could satisfy the requirements of a job.""" pass class InvalidBgJobCall(Exception): """Raised when an invalid call is made to a BgJob object.""" pass class HeartbeatOnlyAllowedInShardModeException(Exception): """Raised when a heartbeat is attempted but not allowed.""" pass class UnallowedRecordsSentToMaster(Exception): pass class InvalidDataError(Exception): """Exception raised when invalid data provided for database operation. """ pass class ContainerError(Exception): """Exception raised when program runs into error using container. """ class IllegalUser(Exception): """Exception raise when a program runs as an illegal user.""" # This MUST remain at the end of the file. # Limit 'from error import *' to only import the exception instances. for _name, _thing in locals().items(): try: if issubclass(_thing, Exception): __all__.append(_name) except TypeError: pass # _thing not a class __all__ = tuple(__all__)