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