1# SPDX-License-Identifier: BSD-3-Clause 2# Copyright(c) 2010-2014 Intel Corporation 3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 4# Copyright(c) 2022-2023 University of New Hampshire 5# Copyright(c) 2024 Arm Limited 6 7"""DTS exceptions. 8 9The exceptions all have different severities expressed as an integer. 10The highest severity of all raised exceptions is used as the exit code of DTS. 11""" 12 13from enum import IntEnum, unique 14from typing import ClassVar 15 16 17@unique 18class ErrorSeverity(IntEnum): 19 """The severity of errors that occur during DTS execution. 20 21 All exceptions are caught and the most severe error is used as return code. 22 """ 23 24 #: 25 NO_ERR = 0 26 #: 27 GENERIC_ERR = 1 28 #: 29 CONFIG_ERR = 2 30 #: 31 REMOTE_CMD_EXEC_ERR = 3 32 #: 33 SSH_ERR = 4 34 #: 35 INTERNAL_ERR = 5 36 #: 37 DPDK_BUILD_ERR = 10 38 #: 39 TESTCASE_VERIFY_ERR = 20 40 #: 41 BLOCKING_TESTSUITE_ERR = 25 42 43 44class DTSError(Exception): 45 """The base exception from which all DTS exceptions are subclassed. 46 47 Do not use this exception, only use subclassed exceptions. 48 """ 49 50 #: 51 severity: ClassVar[ErrorSeverity] = ErrorSeverity.GENERIC_ERR 52 53 54class SSHConnectionError(DTSError): 55 """An unsuccessful SSH connection.""" 56 57 #: 58 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 59 _host: str 60 _errors: list[str] 61 62 def __init__(self, host: str, errors: list[str] | None = None): 63 """Define the meaning of the first two arguments. 64 65 Args: 66 host: The hostname to which we're trying to connect. 67 errors: Any errors that occurred during the connection attempt. 68 """ 69 self._host = host 70 self._errors = [] if errors is None else errors 71 72 def __str__(self) -> str: 73 """Include the errors in the string representation.""" 74 message = f"Error trying to connect with {self._host}." 75 if self._errors: 76 message += f" Errors encountered while retrying: {', '.join(self._errors)}" 77 78 return message 79 80 81class _SSHTimeoutError(DTSError): 82 """The execution of a command via SSH timed out. 83 84 This class is private and meant to be raised as its interactive and non-interactive variants. 85 """ 86 87 #: 88 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 89 _command: str 90 91 def __init__(self, command: str): 92 """Define the meaning of the first argument. 93 94 Args: 95 command: The executed command. 96 """ 97 self._command = command 98 99 def __str__(self) -> str: 100 """Add some context to the string representation.""" 101 return f"{self._command} execution timed out." 102 103 104class SSHTimeoutError(_SSHTimeoutError): 105 """The execution of a command on a non-interactive SSH session timed out.""" 106 107 108class InteractiveSSHTimeoutError(_SSHTimeoutError): 109 """The execution of a command on an interactive SSH session timed out.""" 110 111 112class _SSHSessionDeadError(DTSError): 113 """The SSH session is no longer alive. 114 115 This class is private and meant to be raised as its interactive and non-interactive variants. 116 """ 117 118 #: 119 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 120 _host: str 121 122 def __init__(self, host: str): 123 """Define the meaning of the first argument. 124 125 Args: 126 host: The hostname of the disconnected node. 127 """ 128 self._host = host 129 130 def __str__(self) -> str: 131 """Add some context to the string representation.""" 132 return f"SSH session with {self._host} has died." 133 134 135class SSHSessionDeadError(_SSHSessionDeadError): 136 """Non-interactive SSH session has died.""" 137 138 139class InteractiveSSHSessionDeadError(_SSHSessionDeadError): 140 """Interactive SSH session as died.""" 141 142 143class ConfigurationError(DTSError): 144 """An invalid configuration.""" 145 146 #: 147 severity: ClassVar[ErrorSeverity] = ErrorSeverity.CONFIG_ERR 148 149 150class RemoteCommandExecutionError(DTSError): 151 """An unsuccessful execution of a remote command.""" 152 153 #: 154 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 155 #: The executed command. 156 command: str 157 _command_stderr: str 158 _command_return_code: int 159 160 def __init__(self, command: str, command_stderr: str, command_return_code: int): 161 """Define the meaning of the first two arguments. 162 163 Args: 164 command: The executed command. 165 command_stderr: The stderr of the executed command. 166 command_return_code: The return code of the executed command. 167 """ 168 self.command = command 169 self._command_stderr = command_stderr 170 self._command_return_code = command_return_code 171 172 def __str__(self) -> str: 173 """Include the command, its return code and stderr in the string representation.""" 174 return ( 175 f"Command '{self.command}' returned a non-zero exit code: " 176 f"{self._command_return_code}\nStderr: {self._command_stderr}" 177 ) 178 179 180class InteractiveCommandExecutionError(DTSError): 181 """An unsuccessful execution of a remote command in an interactive environment.""" 182 183 #: 184 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 185 186 187class RemoteFileNotFoundError(DTSError): 188 """A remote file or directory is requested but doesn’t exist.""" 189 190 #: 191 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 192 193 194class DPDKBuildError(DTSError): 195 """A DPDK build failure.""" 196 197 #: 198 severity: ClassVar[ErrorSeverity] = ErrorSeverity.DPDK_BUILD_ERR 199 200 201class TestCaseVerifyError(DTSError): 202 """A test case failure.""" 203 204 #: 205 severity: ClassVar[ErrorSeverity] = ErrorSeverity.TESTCASE_VERIFY_ERR 206 207 208class BlockingTestSuiteError(DTSError): 209 """A failure in a blocking test suite.""" 210 211 #: 212 severity: ClassVar[ErrorSeverity] = ErrorSeverity.BLOCKING_TESTSUITE_ERR 213 _suite_name: str 214 215 def __init__(self, suite_name: str) -> None: 216 """Define the meaning of the first argument. 217 218 Args: 219 suite_name: The blocking test suite. 220 """ 221 self._suite_name = suite_name 222 223 def __str__(self) -> str: 224 """Add some context to the string representation.""" 225 return f"Blocking suite {self._suite_name} failed." 226 227 228class InternalError(DTSError): 229 """An internal error or bug has occurred in DTS.""" 230 231 #: 232 severity: ClassVar[ErrorSeverity] = ErrorSeverity.INTERNAL_ERR 233