Skip to content

Device models

AntaDevice

AntaDevice(name: str, tags: Optional[List[str]] = None)

Bases: ABC

Abstract class representing a device in ANTA. An implementation of this class needs must override the abstract coroutines collect() and refresh().

Attributes:

Name Type Description
name str

Device name

is_online bool

True if the device IP is reachable and a port can be open

established bool

True if remote command execution succeeds

hw_model Optional[str]

Hardware model of the device

tags List[str]

List of tags for this device

Parameters:

Name Type Description Default
name str

Device name

required
tags Optional[List[str]]

List of tags for this device

None
Source code in anta/device.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(self, name: str, tags: Optional[List[str]] = None) -> None:
    """
    Constructor of AntaDevice

    Args:
        name: Device name
        tags: List of tags for this device
    """
    self.name: str = name
    self.hw_model: Optional[str] = None
    self.tags: List[str] = tags if tags is not None else []
    self.is_online: bool = False
    self.established: bool = False

    # Ensure tag 'all' is always set
    if DEFAULT_TAG not in self.tags:
        self.tags.append(DEFAULT_TAG)

collect abstractmethod async

collect(command: AntaCommand) -> None

Collect device command output. This abstract coroutine can be used to implement any command collection method for a device in ANTA.

The collect() implementation needs to populate the output attribute of the AntaCommand object passed as argument.

If a failure occurs, the collect() implementation is expected to catch the exception and implement proper logging, the output attribute of the AntaCommand object passed as argument would be None in this case.

Parameters:

Name Type Description Default
command AntaCommand

the command to collect

required
Source code in anta/device.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@abstractmethod
async def collect(self, command: AntaCommand) -> None:
    """
    Collect device command output.
    This abstract coroutine can be used to implement any command collection method
    for a device in ANTA.

    The `collect()` implementation needs to populate the `output` attribute
    of the `AntaCommand` object passed as argument.

    If a failure occurs, the `collect()` implementation is expected to catch the
    exception and implement proper logging, the `output` attribute of the
    `AntaCommand` object passed as argument would be `None` in this case.

    Args:
        command: the command to collect
    """

collect_commands async

collect_commands(commands: List[AntaCommand]) -> None

Collect multiple commands.

Parameters:

Name Type Description Default
commands List[AntaCommand]

the commands to collect

required
Source code in anta/device.py
89
90
91
92
93
94
95
96
async def collect_commands(self, commands: List[AntaCommand]) -> None:
    """
    Collect multiple commands.

    Args:
        commands: the commands to collect
    """
    await asyncio.gather(*(self.collect(command=command) for command in commands))

copy async

copy(sources: List[Path], destination: Path, direction: Literal['to', 'from'] = 'from') -> None

Copy files to and from the device, usually through SCP. It is not mandatory to implement this for a valid AntaDevice subclass.

Parameters:

Name Type Description Default
sources List[Path]

List of files to copy to or from the device.

required
destination Path

Local or remote destination when copying the files. Can be a folder.

required
direction Literal['to', 'from']

Defines if this coroutine copies files to or from the device.

'from'
Source code in anta/device.py
109
110
111
112
113
114
115
116
117
118
119
async def copy(self, sources: List[Path], destination: Path, direction: Literal["to", "from"] = "from") -> None:
    """
    Copy files to and from the device, usually through SCP.
    It is not mandatory to implement this for a valid AntaDevice subclass.

    Args:
        sources: List of files to copy to or from the device.
        destination: Local or remote destination when copying the files. Can be a folder.
        direction: Defines if this coroutine copies files to or from the device.
    """
    raise NotImplementedError(f"copy() method has not been implemented in {self.__class__.__name__} definition")

refresh abstractmethod async

refresh() -> None

Update attributes of an AntaDevice instance.

This coroutine must update the following attributes of AntaDevice
  • is_online: When the device IP is reachable and a port can be open
  • established: When a command execution succeeds
  • hw_model: The hardware model of the device
Source code in anta/device.py
 98
 99
100
101
102
103
104
105
106
107
@abstractmethod
async def refresh(self) -> None:
    """
    Update attributes of an AntaDevice instance.

    This coroutine must update the following attributes of AntaDevice:
        - `is_online`: When the device IP is reachable and a port can be open
        - `established`: When a command execution succeeds
        - `hw_model`: The hardware model of the device
    """

AsyncEOSDevice

AsyncEOSDevice(host: str, username: str, password: str, name: Optional[str] = None, enable: bool = False, enable_password: Optional[str] = None, port: Optional[int] = None, ssh_port: Optional[int] = 22, tags: Optional[List[str]] = None, timeout: Optional[float] = None, insecure: bool = False, proto: Literal['http', 'https'] = 'https')

Bases: AntaDevice

Implementation of AntaDevice for EOS using aio-eapi.

Attributes:

Name Type Description
name

Device name

is_online

True if the device IP is reachable and a port can be open

established

True if remote command execution succeeds

hw_model

Hardware model of the device

tags

List of tags for this device

Parameters:

Name Type Description Default
host str

Device FQDN or IP

required
username str

Username to connect to eAPI and SSH

required
password str

Password to connect to eAPI and SSH

required
name Optional[str]

Device name

None
enable bool

Device needs privileged access

False
enable_password Optional[str]

Password used to gain privileged access on EOS

None
port Optional[int]

eAPI port. Defaults to 80 is proto is ‘http’ or 443 if proto is ‘https’.

None
ssh_port Optional[int]

SSH port

22
tags Optional[List[str]]

List of tags for this device

None
timeout Optional[float]

Timeout value in seconds for outgoing connections. Default to 10 secs.

None
insecure bool

Disable SSH Host Key validation

False
proto Literal['http', 'https']

eAPI protocol. Value can be ‘http’ or ‘https’

'https'
Source code in anta/device.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def __init__(  # pylint: disable=R0913
    self,
    host: str,
    username: str,
    password: str,
    name: Optional[str] = None,
    enable: bool = False,
    enable_password: Optional[str] = None,
    port: Optional[int] = None,
    ssh_port: Optional[int] = 22,
    tags: Optional[List[str]] = None,
    timeout: Optional[float] = None,
    insecure: bool = False,
    proto: Literal["http", "https"] = "https",
) -> None:
    """
    Constructor of AsyncEOSDevice

    Args:
        host: Device FQDN or IP
        username: Username to connect to eAPI and SSH
        password: Password to connect to eAPI and SSH
        name: Device name
        enable: Device needs privileged access
        enable_password: Password used to gain privileged access on EOS
        port: eAPI port. Defaults to 80 is proto is 'http' or 443 if proto is 'https'.
        ssh_port: SSH port
        tags: List of tags for this device
        timeout: Timeout value in seconds for outgoing connections. Default to 10 secs.
        insecure: Disable SSH Host Key validation
        proto: eAPI protocol. Value can be 'http' or 'https'
    """
    if name is None:
        name = f"{host}{f':{port}' if port else ''}"
    super().__init__(name, tags)
    self.enable = enable
    self._enable_password = enable_password
    self._session: Device = Device(host=host, port=port, username=username, password=password, proto=proto, timeout=timeout)
    ssh_params: Dict[str, Any] = {}
    if insecure:
        ssh_params.update({"known_hosts": None})
    self._ssh_opts: SSHClientConnectionOptions = SSHClientConnectionOptions(host=host, port=ssh_port, username=username, password=password, **ssh_params)

collect async

collect(command: AntaCommand) -> None

Collect device command output from EOS using aio-eapi.

Supports outformat json and text as output structure. Gain privileged access using the enable_password attribute of the AntaDevice instance if populated.

Parameters:

Name Type Description Default
command AntaCommand

the command to collect

required
Source code in anta/device.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
async def collect(self, command: AntaCommand) -> None:
    """
    Collect device command output from EOS using aio-eapi.

    Supports outformat `json` and `text` as output structure.
    Gain privileged access using the `enable_password` attribute
    of the `AntaDevice` instance if populated.

    Args:
        command: the command to collect
    """
    try:
        commands = []
        if self.enable and self._enable_password is not None:
            commands.append(
                {
                    "cmd": "enable",
                    "input": str(self._enable_password),
                }
            )
        elif self.enable:
            # No password
            commands.append({"cmd": "enable"})
        if command.revision:
            commands.append({"cmd": command.command, "revision": command.revision})
        else:
            commands.append({"cmd": command.command})
        response = await self._session.cli(
            commands=commands,
            ofmt=command.ofmt,
            version=command.version,
        )
        # remove first dict related to enable command
        # only applicable to json output
        if command.ofmt in ["json", "text"]:
            # selecting only our command output
            response = response[-1]
        command.output = response
        logger.debug(f"{self.name}: {command}")

    except EapiCommandError as e:
        message = f"Command '{command.command}' failed on {self.name}"
        anta_log_exception(e, message, logger)
        command.failed = e
    except (HTTPError, ConnectError) as e:
        message = f"Cannot connect to device {self.name}"
        anta_log_exception(e, message, logger)
        command.failed = e
    except Exception as e:  # pylint: disable=broad-exception-caught
        message = f"Exception raised while collecting command '{command.command}' on device {self.name}"
        anta_log_exception(e, message, logger)
        command.failed = e
        logger.debug(command)

copy async

copy(sources: List[Path], destination: Path, direction: Literal['to', 'from'] = 'from') -> None

Copy files to and from the device using asyncssh.scp().

Parameters:

Name Type Description Default
sources List[Path]

List of files to copy to or from the device.

required
destination Path

Local or remote destination when copying the files. Can be a folder.

required
direction Literal['to', 'from']

Defines if this coroutine copies files to or from the device.

'from'
Source code in anta/device.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
async def copy(self, sources: List[Path], destination: Path, direction: Literal["to", "from"] = "from") -> None:
    """
    Copy files to and from the device using asyncssh.scp().

    Args:
        sources: List of files to copy to or from the device.
        destination: Local or remote destination when copying the files. Can be a folder.
        direction: Defines if this coroutine copies files to or from the device.
    """
    async with asyncssh.connect(
        host=self._ssh_opts.host,
        port=self._ssh_opts.port,
        tunnel=self._ssh_opts.tunnel,
        family=self._ssh_opts.family,
        local_addr=self._ssh_opts.local_addr,
        options=self._ssh_opts,
    ) as conn:
        src: Union[List[Tuple[SSHClientConnection, Path]], List[Path]]
        dst: Union[Tuple[SSHClientConnection, Path], Path]
        if direction == "from":
            src = [(conn, file) for file in sources]
            dst = destination
            for file in sources:
                logger.info(f"Copying '{file}' from device {self.name} to '{destination}' locally")
        elif direction == "to":
            src = sources
            dst = (conn, destination)
            for file in sources:
                logger.info(f"Copying '{file}' to device {self.name} to '{destination}' remotely")
        else:
            logger.critical(f"'direction' argument to copy() fonction is invalid: {direction}")
            return
        await asyncssh.scp(src, dst)

refresh async

refresh() -> None

Update attributes of an AsyncEOSDevice instance.

This coroutine must update the following attributes of AsyncEOSDevice: - is_online: When a device IP is reachable and a port can be open - established: When a command execution succeeds - hw_model: The hardware model of the device

Source code in anta/device.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
async def refresh(self) -> None:
    """
    Update attributes of an AsyncEOSDevice instance.

    This coroutine must update the following attributes of AsyncEOSDevice:
    - is_online: When a device IP is reachable and a port can be open
    - established: When a command execution succeeds
    - hw_model: The hardware model of the device
    """
    # Refresh command
    COMMAND: str = "show version"
    # Hardware model definition in show version
    HW_MODEL_KEY: str = "modelName"
    logger.debug(f"Refreshing device {self.name}")
    self.is_online = await self._session.check_connection()
    if self.is_online:
        try:
            response = await self._session.cli(command=COMMAND)
        except EapiCommandError as e:
            logger.warning(f"Cannot get hardware information from device {self.name}: {e.errmsg}")
        except (HTTPError, ConnectError) as e:
            logger.warning(f"Cannot get hardware information from device {self.name}: {exc_to_str(e)}")
        else:
            if HW_MODEL_KEY in response:
                self.hw_model = response[HW_MODEL_KEY]
            else:
                logger.warning(f"Cannot get hardware information from device {self.name}: cannot parse '{COMMAND}'")
    else:
        logger.warning(f"Could not connect to device {self.name}: cannot open eAPI port")
    self.established = bool(self.is_online and self.hw_model)

Last update: July 19, 2023