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 DPDK_BUILD_ERR = 10 36 #: 37 TESTCASE_VERIFY_ERR = 20 38 #: 39 BLOCKING_TESTSUITE_ERR = 25 40 41 42class DTSError(Exception): 43 """The base exception from which all DTS exceptions are subclassed. 44 45 Do not use this exception, only use subclassed exceptions. 46 """ 47 48 #: 49 severity: ClassVar[ErrorSeverity] = ErrorSeverity.GENERIC_ERR 50 51 52class SSHTimeoutError(DTSError): 53 """The SSH execution of a command timed out.""" 54 55 #: 56 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 57 _command: str 58 59 def __init__(self, command: str): 60 """Define the meaning of the first argument. 61 62 Args: 63 command: The executed command. 64 """ 65 self._command = command 66 67 def __str__(self) -> str: 68 """Add some context to the string representation.""" 69 return f"{self._command} execution timed out." 70 71 72class SSHConnectionError(DTSError): 73 """An unsuccessful SSH connection.""" 74 75 #: 76 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 77 _host: str 78 _errors: list[str] 79 80 def __init__(self, host: str, errors: list[str] | None = None): 81 """Define the meaning of the first two arguments. 82 83 Args: 84 host: The hostname to which we're trying to connect. 85 errors: Any errors that occurred during the connection attempt. 86 """ 87 self._host = host 88 self._errors = [] if errors is None else errors 89 90 def __str__(self) -> str: 91 """Include the errors in the string representation.""" 92 message = f"Error trying to connect with {self._host}." 93 if self._errors: 94 message += f" Errors encountered while retrying: {', '.join(self._errors)}" 95 96 return message 97 98 99class SSHSessionDeadError(DTSError): 100 """The SSH session is no longer alive.""" 101 102 #: 103 severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR 104 _host: str 105 106 def __init__(self, host: str): 107 """Define the meaning of the first argument. 108 109 Args: 110 host: The hostname of the disconnected node. 111 """ 112 self._host = host 113 114 def __str__(self) -> str: 115 """Add some context to the string representation.""" 116 return f"SSH session with {self._host} has died." 117 118 119class ConfigurationError(DTSError): 120 """An invalid configuration.""" 121 122 #: 123 severity: ClassVar[ErrorSeverity] = ErrorSeverity.CONFIG_ERR 124 125 126class RemoteCommandExecutionError(DTSError): 127 """An unsuccessful execution of a remote command.""" 128 129 #: 130 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 131 #: The executed command. 132 command: str 133 _command_stderr: str 134 _command_return_code: int 135 136 def __init__(self, command: str, command_stderr: str, command_return_code: int): 137 """Define the meaning of the first two arguments. 138 139 Args: 140 command: The executed command. 141 command_stderr: The stderr of the executed command. 142 command_return_code: The return code of the executed command. 143 """ 144 self.command = command 145 self._command_stderr = command_stderr 146 self._command_return_code = command_return_code 147 148 def __str__(self) -> str: 149 """Include the command, its return code and stderr in the string representation.""" 150 return ( 151 f"Command '{self.command}' returned a non-zero exit code: " 152 f"{self._command_return_code}\nStderr: {self._command_stderr}" 153 ) 154 155 156class InteractiveCommandExecutionError(DTSError): 157 """An unsuccessful execution of a remote command in an interactive environment.""" 158 159 #: 160 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 161 162 163class RemoteDirectoryExistsError(DTSError): 164 """A directory that exists on a remote node.""" 165 166 #: 167 severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR 168 169 170class DPDKBuildError(DTSError): 171 """A DPDK build failure.""" 172 173 #: 174 severity: ClassVar[ErrorSeverity] = ErrorSeverity.DPDK_BUILD_ERR 175 176 177class TestCaseVerifyError(DTSError): 178 """A test case failure.""" 179 180 #: 181 severity: ClassVar[ErrorSeverity] = ErrorSeverity.TESTCASE_VERIFY_ERR 182 183 184class BlockingTestSuiteError(DTSError): 185 """A failure in a blocking test suite.""" 186 187 #: 188 severity: ClassVar[ErrorSeverity] = ErrorSeverity.BLOCKING_TESTSUITE_ERR 189 _suite_name: str 190 191 def __init__(self, suite_name: str) -> None: 192 """Define the meaning of the first argument. 193 194 Args: 195 suite_name: The blocking test suite. 196 """ 197 self._suite_name = suite_name 198 199 def __str__(self) -> str: 200 """Add some context to the string representation.""" 201 return f"Blocking suite {self._suite_name} failed." 202