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