xref: /dpdk/dts/framework/exception.py (revision f995766758403e13a7b97e6e3e863ac900716844)
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