Skip to content

ANTA catalog for IS-IS tests

VerifyISISInterfaceMode

Verifies ISIS Interfaces are running in correct mode.

Expected Results
  • Success: The test will pass if all listed interfaces are running in correct mode.
  • Failure: The test will fail if any of the listed interfaces is not running in correct mode.
  • Skipped: The test will be skipped if no ISIS neighbor is found.
Examples
anta.tests.routing:
  isis:
    - VerifyISISInterfaceMode:
        interfaces:
          - name: Loopback0
            mode: passive
            # vrf is set to default by default
          - name: Ethernet2
            mode: passive
            level: 2
            # vrf is set to default by default
          - name: Ethernet1
            mode: point-to-point
            vrf: default
            # level is set to 2 by default

Inputs

Name Type Description Default
interfaces list[InterfaceState]
list of interfaces with their information.
-

InterfaceState

Name Type Description Default
name Interface
Interface name to check.
-
level Literal[1, 2]
ISIS level configured for interface. Default is 2.
2
mode Literal['point-to-point', 'broadcast', 'passive']
Number of IS-IS neighbors.
-
vrf str
VRF where the interface should be configured
'default'
Source code in anta/tests/routing/isis.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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
287
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
321
322
323
324
325
class VerifyISISInterfaceMode(AntaTest):
    """Verifies ISIS Interfaces are running in correct mode.

    Expected Results
    ----------------
    * Success: The test will pass if all listed interfaces are running in correct mode.
    * Failure: The test will fail if any of the listed interfaces is not running in correct mode.
    * Skipped: The test will be skipped if no ISIS neighbor is found.

    Examples
    --------
    ```yaml
    anta.tests.routing:
      isis:
        - VerifyISISInterfaceMode:
            interfaces:
              - name: Loopback0
                mode: passive
                # vrf is set to default by default
              - name: Ethernet2
                mode: passive
                level: 2
                # vrf is set to default by default
              - name: Ethernet1
                mode: point-to-point
                vrf: default
                # level is set to 2 by default
    ```
    """

    name = "VerifyISISInterfaceMode"
    description = "Verifies interface mode for IS-IS"
    categories: ClassVar[list[str]] = ["isis"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis interface brief", revision=1)]

    class Input(AntaTest.Input):
        """Input model for the VerifyISISNeighborCount test."""

        interfaces: list[InterfaceState]
        """list of interfaces with their information."""

        class InterfaceState(BaseModel):
            """Input model for the VerifyISISNeighborCount test."""

            name: Interface
            """Interface name to check."""
            level: Literal[1, 2] = 2
            """ISIS level configured for interface. Default is 2."""
            mode: Literal["point-to-point", "broadcast", "passive"]
            """Number of IS-IS neighbors."""
            vrf: str = "default"
            """VRF where the interface should be configured"""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISInterfaceMode."""
        command_output = self.instance_commands[0].json_output
        self.result.is_success()

        if len(command_output["vrfs"]) == 0:
            self.result.is_skipped("IS-IS is not configured on device")
            return

        # Check for p2p interfaces
        for interface in self.inputs.interfaces:
            interface_data = _get_interface_data(
                interface=interface.name,
                vrf=interface.vrf,
                command_output=command_output,
            )
            # Check for correct VRF
            if interface_data is not None:
                interface_type = get_value(dictionary=interface_data, key="interfaceType", default="unset")
                # Check for interfaceType
                if interface.mode == "point-to-point" and interface.mode != interface_type:
                    self.result.is_failure(f"Interface {interface.name} in VRF {interface.vrf} is not running in {interface.mode} reporting {interface_type}")
                # Check for passive
                elif interface.mode == "passive":
                    json_path = f"intfLevels.{interface.level}.passive"
                    if interface_data is None or get_value(dictionary=interface_data, key=json_path, default=False) is False:
                        self.result.is_failure(f"Interface {interface.name} in VRF {interface.vrf} is not running in passive mode")
            else:
                self.result.is_failure(f"Interface {interface.name} not found in VRF {interface.vrf}")

VerifyISISNeighborCount

Verifies number of IS-IS neighbors per level and per interface.

Expected Results
  • Success: The test will pass if the number of neighbors is correct.
  • Failure: The test will fail if the number of neighbors is incorrect.
  • Skipped: The test will be skipped if no IS-IS neighbor is found.
Examples
anta.tests.routing:
  isis:
    - VerifyISISNeighborCount:
        interfaces:
          - name: Ethernet1
            level: 1
            count: 2
          - name: Ethernet2
            level: 2
            count: 1
          - name: Ethernet3
            count: 2
            # level is set to 2 by default

Inputs

Name Type Description Default
interfaces list[InterfaceCount]
list of interfaces with their information.
-

InterfaceCount

Name Type Description Default
name Interface
Interface name to check.
-
level int
IS-IS level to check.
2
count int
Number of IS-IS neighbors.
-
Source code in anta/tests/routing/isis.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
class VerifyISISNeighborCount(AntaTest):
    """Verifies number of IS-IS neighbors per level and per interface.

    Expected Results
    ----------------
    * Success: The test will pass if the number of neighbors is correct.
    * Failure: The test will fail if the number of neighbors is incorrect.
    * Skipped: The test will be skipped if no IS-IS neighbor is found.

    Examples
    --------
    ```yaml
    anta.tests.routing:
      isis:
        - VerifyISISNeighborCount:
            interfaces:
              - name: Ethernet1
                level: 1
                count: 2
              - name: Ethernet2
                level: 2
                count: 1
              - name: Ethernet3
                count: 2
                # level is set to 2 by default
    ```
    """

    name = "VerifyISISNeighborCount"
    description = "Verifies count of IS-IS interface per level"
    categories: ClassVar[list[str]] = ["isis"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis interface brief", revision=1)]

    class Input(AntaTest.Input):
        """Input model for the VerifyISISNeighborCount test."""

        interfaces: list[InterfaceCount]
        """list of interfaces with their information."""

        class InterfaceCount(BaseModel):
            """Input model for the VerifyISISNeighborCount test."""

            name: Interface
            """Interface name to check."""
            level: int = 2
            """IS-IS level to check."""
            count: int
            """Number of IS-IS neighbors."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISNeighborCount."""
        command_output = self.instance_commands[0].json_output
        self.result.is_success()
        isis_neighbor_count = _get_isis_neighbors_count(command_output)
        if len(isis_neighbor_count) == 0:
            self.result.is_skipped("No IS-IS neighbor detected")
            return
        for interface in self.inputs.interfaces:
            eos_data = [ifl_data for ifl_data in isis_neighbor_count if ifl_data["interface"] == interface.name and ifl_data["level"] == interface.level]
            if not eos_data:
                self.result.is_failure(f"No neighbor detected for interface {interface.name}")
                continue
            if eos_data[0]["count"] != interface.count:
                self.result.is_failure(
                    f"Interface {interface.name}: "
                    f"expected Level {interface.level}: count {interface.count}, "
                    f"got Level {eos_data[0]['level']}: count {eos_data[0]['count']}"
                )

VerifyISISNeighborState

Verifies all IS-IS neighbors are in UP state.

Expected Results
  • Success: The test will pass if all IS-IS neighbors are in UP state.
  • Failure: The test will fail if some IS-IS neighbors are not in UP state.
  • Skipped: The test will be skipped if no IS-IS neighbor is found.
Examples
anta.tests.routing:
  isis:
    - VerifyISISNeighborState:
Source code in anta/tests/routing/isis.py
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
class VerifyISISNeighborState(AntaTest):
    """Verifies all IS-IS neighbors are in UP state.

    Expected Results
    ----------------
    * Success: The test will pass if all IS-IS neighbors are in UP state.
    * Failure: The test will fail if some IS-IS neighbors are not in UP state.
    * Skipped: The test will be skipped if no IS-IS neighbor is found.

    Examples
    --------
    ```yaml
    anta.tests.routing:
      isis:
        - VerifyISISNeighborState:
    ```
    """

    name = "VerifyISISNeighborState"
    description = "Verifies all IS-IS neighbors are in UP state."
    categories: ClassVar[list[str]] = ["isis"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis neighbors", revision=1)]

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISNeighborState."""
        command_output = self.instance_commands[0].json_output
        if _count_isis_neighbor(command_output) == 0:
            self.result.is_skipped("No IS-IS neighbor detected")
            return
        self.result.is_success()
        not_full_neighbors = _get_not_full_isis_neighbors(command_output)
        if not_full_neighbors:
            self.result.is_failure(f"Some neighbors are not in the correct state (UP): {not_full_neighbors}.")

VerifyISISSegmentRoutingAdjacencySegments

Verifies ISIS Segment Routing Adjacency Segments.

Verify that all expected Adjacency segments are correctly visible for each interface.

Expected Results
  • Success: The test will pass if all listed interfaces have correct adjacencies.
  • Failure: The test will fail if any of the listed interfaces has not expected list of adjacencies.
  • Skipped: The test will be skipped if no ISIS SR Adjacency is found.
Examples
anta.tests.routing:
  isis:
    - VerifyISISSegmentRoutingAdjacencySegments:
        instances:
          - name: CORE-ISIS
            vrf: default
            segments:
              - interface: Ethernet2
                address: 10.0.1.3
                sid_origin: dynamic

Inputs

Name Type Description Default

IsisInstance

Name Type Description Default
name str
ISIS instance name.
-
vrf str
VRF name where ISIS instance is configured.
'default'
segments list[Segment]
List of Adjacency segments configured in this instance.
-
Segment
Name Type Description Default
interface Interface
Interface name to check.
-
level Literal[1, 2]
ISIS level configured for interface. Default is 2.
2
sid_origin Literal['dynamic']
Adjacency type
'dynamic'
address IPv4Address
IP address of remote end of segment.
-
Source code in anta/tests/routing/isis.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
class VerifyISISSegmentRoutingAdjacencySegments(AntaTest):
    """Verifies ISIS Segment Routing Adjacency Segments.

    Verify that all expected Adjacency segments are correctly visible for each interface.

    Expected Results
    ----------------
    * Success: The test will pass if all listed interfaces have correct adjacencies.
    * Failure: The test will fail if any of the listed interfaces has not expected list of adjacencies.
    * Skipped: The test will be skipped if no ISIS SR Adjacency is found.

    Examples
    --------
    ```yaml
    anta.tests.routing:
      isis:
        - VerifyISISSegmentRoutingAdjacencySegments:
            instances:
              - name: CORE-ISIS
                vrf: default
                segments:
                  - interface: Ethernet2
                    address: 10.0.1.3
                    sid_origin: dynamic

    ```
    """

    name = "VerifyISISSegmentRoutingAdjacencySegments"
    description = "Verify expected Adjacency segments are correctly visible for each interface."
    categories: ClassVar[list[str]] = ["isis", "segment-routing"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing adjacency-segments", ofmt="json")]

    class Input(AntaTest.Input):
        """Input model for the VerifyISISSegmentRoutingAdjacencySegments test."""

        instances: list[IsisInstance]

        class IsisInstance(BaseModel):
            """ISIS Instance model definition."""

            name: str
            """ISIS instance name."""
            vrf: str = "default"
            """VRF name where ISIS instance is configured."""
            segments: list[Segment]
            """List of Adjacency segments configured in this instance."""

            class Segment(BaseModel):
                """Segment model definition."""

                interface: Interface
                """Interface name to check."""
                level: Literal[1, 2] = 2
                """ISIS level configured for interface. Default is 2."""
                sid_origin: Literal["dynamic"] = "dynamic"
                """Adjacency type"""
                address: IPv4Address
                """IP address of remote end of segment."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISSegmentRoutingAdjacencySegments."""
        command_output = self.instance_commands[0].json_output
        self.result.is_success()

        if len(command_output["vrfs"]) == 0:
            self.result.is_skipped("IS-IS is not configured on device")
            return

        # initiate defaults
        failure_message = []
        skip_vrfs = []
        skip_instances = []

        # Check if VRFs and instances are present in output.
        for instance in self.inputs.instances:
            vrf_data = get_value(
                dictionary=command_output,
                key=f"vrfs.{instance.vrf}",
                default=None,
            )
            if vrf_data is None:
                skip_vrfs.append(instance.vrf)
                failure_message.append(f"VRF {instance.vrf} is not configured to run segment routging.")

            elif get_value(dictionary=vrf_data, key=f"isisInstances.{instance.name}", default=None) is None:
                skip_instances.append(instance.name)
                failure_message.append(f"Instance {instance.name} is not found in vrf {instance.vrf}.")

        # Check Adjacency segments
        for instance in self.inputs.instances:
            if instance.vrf not in skip_vrfs and instance.name not in skip_instances:
                for input_segment in instance.segments:
                    eos_segment = _get_adjacency_segment_data_by_neighbor(
                        neighbor=str(input_segment.address),
                        instance=instance.name,
                        vrf=instance.vrf,
                        command_output=command_output,
                    )
                    if eos_segment is None:
                        failure_message.append(f"Your segment has not been found: {input_segment}.")

                    elif (
                        eos_segment["localIntf"] != input_segment.interface
                        or eos_segment["level"] != input_segment.level
                        or eos_segment["sidOrigin"] != input_segment.sid_origin
                    ):
                        failure_message.append(f"Your segment is not correct: Expected: {input_segment} - Found: {eos_segment}.")
        if failure_message:
            self.result.is_failure("\n".join(failure_message))

VerifyISISSegmentRoutingDataplane

Verify dataplane of a list of ISIS-SR instances.

Expected Results
  • Success: The test will pass if all instances have correct dataplane configured
  • Failure: The test will fail if one of the instances has incorrect dataplane configured
  • Skipped: The test will be skipped if ISIS is not running
Examples
anta.tests.routing:
  isis:
    - VerifyISISSegmentRoutingDataplane:
        instances:
          - name: CORE-ISIS
            vrf: default
            dataplane: MPLS

Inputs

Name Type Description Default

IsisInstance

Name Type Description Default
name str
ISIS instance name.
-
vrf str
VRF name where ISIS instance is configured.
'default'
dataplane Literal['MPLS', 'mpls', 'unset']
Configured dataplane for the instance.
'MPLS'
Source code in anta/tests/routing/isis.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
class VerifyISISSegmentRoutingDataplane(AntaTest):
    """
    Verify dataplane of a list of ISIS-SR instances.

    Expected Results
    ----------------
    * Success: The test will pass if all instances have correct dataplane configured
    * Failure: The test will fail if one of the instances has incorrect dataplane configured
    * Skipped: The test will be skipped if ISIS is not running

    Examples
    --------
    ```yaml
    anta.tests.routing:
      isis:
        - VerifyISISSegmentRoutingDataplane:
            instances:
              - name: CORE-ISIS
                vrf: default
                dataplane: MPLS
    ```
    """

    name = "VerifyISISSegmentRoutingDataplane"
    description = "Verify dataplane of a list of ISIS-SR instances"
    categories: ClassVar[list[str]] = ["isis", "segment-routing"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing", ofmt="json")]

    class Input(AntaTest.Input):
        """Input model for the VerifyISISSegmentRoutingDataplane test."""

        instances: list[IsisInstance]

        class IsisInstance(BaseModel):
            """ISIS Instance model definition."""

            name: str
            """ISIS instance name."""
            vrf: str = "default"
            """VRF name where ISIS instance is configured."""
            dataplane: Literal["MPLS", "mpls", "unset"] = "MPLS"
            """Configured dataplane for the instance."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISSegmentRoutingDataplane."""
        command_output = self.instance_commands[0].json_output
        self.result.is_success()

        if len(command_output["vrfs"]) == 0:
            self.result.is_skipped("IS-IS-SR is not running on device.")
            return

        # initiate defaults
        failure_message = []
        skip_vrfs = []
        skip_instances = []

        # Check if VRFs and instances are present in output.
        for instance in self.inputs.instances:
            vrf_data = get_value(
                dictionary=command_output,
                key=f"vrfs.{instance.vrf}",
                default=None,
            )
            if vrf_data is None:
                skip_vrfs.append(instance.vrf)
                failure_message.append(f"VRF {instance.vrf} is not configured to run segment routing.")

            elif get_value(dictionary=vrf_data, key=f"isisInstances.{instance.name}", default=None) is None:
                skip_instances.append(instance.name)
                failure_message.append(f"Instance {instance.name} is not found in vrf {instance.vrf}.")

        # Check Adjacency segments
        for instance in self.inputs.instances:
            if instance.vrf not in skip_vrfs and instance.name not in skip_instances:
                eos_dataplane = get_value(dictionary=command_output, key=f"vrfs.{instance.vrf}.isisInstances.{instance.name}.dataPlane", default=None)
                if instance.dataplane.upper() != eos_dataplane:
                    failure_message.append(f"ISIS instance {instance.name} is not running dataplane {instance.dataplane} ({eos_dataplane})")

        if failure_message:
            self.result.is_failure("\n".join(failure_message))

VerifyISISSegmentRoutingTunnels

Verify ISIS-SR tunnels computed by device.

Expected Results
  • Success: The test will pass if all listed tunnels are computed on device.
  • Failure: The test will fail if one of the listed tunnels is missing.
  • Skipped: The test will be skipped if ISIS-SR is not configured.
Examples
anta.tests.routing:
isis:
    - VerifyISISSegmentRoutingTunnels:
        entries:
        # Check only endpoint
        - endpoint: 1.0.0.122/32
        # Check endpoint and via TI-LFA
        - endpoint: 1.0.0.13/32
          vias:
            - type: tunnel
              tunnel_id: ti-lfa
        # Check endpoint and via IP routers
        - endpoint: 1.0.0.14/32
          vias:
            - type: ip
              nexthop: 1.1.1.1

Inputs

Name Type Description Default
entries list[Entry]
List of tunnels to check on device.
-

Entry

Name Type Description Default
endpoint IPv4Network
Endpoint IP of the tunnel.
-
vias list[Vias] | None
Optional list of path to reach endpoint.
None
Vias
Name Type Description Default
nexthop IPv4Address | None
Nexthop of the tunnel. If None, then it is not tested. Default: None
None
type Literal['ip', 'tunnel'] | None
Type of the tunnel. If None, then it is not tested. Default: None
None
interface Interface | None
Interface of the tunnel. If None, then it is not tested. Default: None
None
tunnel_id Literal['TI-LFA', 'ti-lfa', 'unset'] | None
Computation method of the tunnel. If None, then it is not tested. Default: None
None

_check_tunnel_id

_check_tunnel_id(via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool

Check if the tunnel ID matches any of the tunnel IDs in the EOS entry’s vias.

Args: via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input vias to check. eos_entry (dict[str, Any]): The EOS entry to compare against.

Returns:

Type Description
bool: True if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias, False otherwise.
Source code in anta/tests/routing/isis.py
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
def _check_tunnel_id(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
    """
    Check if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias.

    Args:
        via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input vias to check.
        eos_entry (dict[str, Any]): The EOS entry to compare against.

    Returns
    -------
        bool: True if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias, False otherwise.
    """
    if via_input.tunnel_id is not None:
        return any(
            via_input.tunnel_id.upper()
            == get_value(
                dictionary=eos_via,
                key="tunnelId.type",
                default="undefined",
            ).upper()
            for eos_via in eos_entry["vias"]
        )
    return True

_check_tunnel_interface

_check_tunnel_interface(via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool

Check if the tunnel interface exists in the given EOS entry.

Args: via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object. eos_entry (dict[str, Any]): The EOS entry dictionary.

Returns:

Type Description
bool: True if the tunnel interface exists, False otherwise.
Source code in anta/tests/routing/isis.py
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
def _check_tunnel_interface(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
    """
    Check if the tunnel interface exists in the given EOS entry.

    Args:
        via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object.
        eos_entry (dict[str, Any]): The EOS entry dictionary.

    Returns
    -------
        bool: True if the tunnel interface exists, False otherwise.
    """
    if via_input.interface is not None:
        return any(
            via_input.interface
            == get_value(
                dictionary=eos_via,
                key="interface",
                default="undefined",
            )
            for eos_via in eos_entry["vias"]
        )
    return True

_check_tunnel_nexthop

_check_tunnel_nexthop(via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool

Check if the tunnel nexthop matches the given input.

Args: via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object. eos_entry (dict[str, Any]): The EOS entry dictionary.

Returns:

Type Description
bool: True if the tunnel nexthop matches, False otherwise.
Source code in anta/tests/routing/isis.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
def _check_tunnel_nexthop(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
    """
    Check if the tunnel nexthop matches the given input.

    Args:
        via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object.
        eos_entry (dict[str, Any]): The EOS entry dictionary.

    Returns
    -------
        bool: True if the tunnel nexthop matches, False otherwise.
    """
    if via_input.nexthop is not None:
        return any(
            str(via_input.nexthop)
            == get_value(
                dictionary=eos_via,
                key="nexthop",
                default="undefined",
            )
            for eos_via in eos_entry["vias"]
        )
    return True

_check_tunnel_type

_check_tunnel_type(via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool

Check if the tunnel type specified in via_input matches any of the tunnel types in eos_entry.

Args: via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input tunnel type to check. eos_entry (dict[str, Any]): The EOS entry containing the tunnel types.

Returns:

Type Description
bool: True if the tunnel type matches any of the tunnel types in `eos_entry`, False otherwise.
Source code in anta/tests/routing/isis.py
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
def _check_tunnel_type(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
    """
    Check if the tunnel type specified in `via_input` matches any of the tunnel types in `eos_entry`.

    Args:
        via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input tunnel type to check.
        eos_entry (dict[str, Any]): The EOS entry containing the tunnel types.

    Returns
    -------
        bool: True if the tunnel type matches any of the tunnel types in `eos_entry`, False otherwise.
    """
    if via_input.type is not None:
        return any(
            via_input.type
            == get_value(
                dictionary=eos_via,
                key="type",
                default="undefined",
            )
            for eos_via in eos_entry["vias"]
        )
    return True
Source code in anta/tests/routing/isis.py
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
class VerifyISISSegmentRoutingTunnels(AntaTest):
    """
    Verify ISIS-SR tunnels computed by device.

    Expected Results
    ----------------
    * Success: The test will pass if all listed tunnels are computed on device.
    * Failure: The test will fail if one of the listed tunnels is missing.
    * Skipped: The test will be skipped if ISIS-SR is not configured.

    Examples
    --------
    ```yaml
    anta.tests.routing:
    isis:
        - VerifyISISSegmentRoutingTunnels:
            entries:
            # Check only endpoint
            - endpoint: 1.0.0.122/32
            # Check endpoint and via TI-LFA
            - endpoint: 1.0.0.13/32
              vias:
                - type: tunnel
                  tunnel_id: ti-lfa
            # Check endpoint and via IP routers
            - endpoint: 1.0.0.14/32
              vias:
                - type: ip
                  nexthop: 1.1.1.1
    ```
    """

    name = "VerifyISISSegmentRoutingTunnels"
    description = "Verify ISIS-SR tunnels computed by device"
    categories: ClassVar[list[str]] = ["isis", "segment-routing"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing tunnel", ofmt="json")]

    class Input(AntaTest.Input):
        """Input model for the VerifyISISSegmentRoutingTunnels test."""

        entries: list[Entry]
        """List of tunnels to check on device."""

        class Entry(BaseModel):
            """Definition of a tunnel entry."""

            endpoint: IPv4Network
            """Endpoint IP of the tunnel."""
            vias: list[Vias] | None = None
            """Optional list of path to reach endpoint."""

            class Vias(BaseModel):
                """Definition of a tunnel path."""

                nexthop: IPv4Address | None = None
                """Nexthop of the tunnel. If None, then it is not tested. Default: None"""
                type: Literal["ip", "tunnel"] | None = None
                """Type of the tunnel. If None, then it is not tested. Default: None"""
                interface: Interface | None = None
                """Interface of the tunnel. If None, then it is not tested. Default: None"""
                tunnel_id: Literal["TI-LFA", "ti-lfa", "unset"] | None = None
                """Computation method of the tunnel. If None, then it is not tested. Default: None"""

    def _eos_entry_lookup(self, search_value: IPv4Network, entries: dict[str, Any], search_key: str = "endpoint") -> dict[str, Any] | None:
        return next(
            (entry_value for entry_id, entry_value in entries.items() if str(entry_value[search_key]) == str(search_value)),
            None,
        )

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyISISSegmentRoutingTunnels.

        This method performs the main test logic for verifying ISIS Segment Routing tunnels.
        It checks the command output, initiates defaults, and performs various checks on the tunnels.

        Returns
        -------
            None
        """
        command_output = self.instance_commands[0].json_output
        self.result.is_success()

        # initiate defaults
        failure_message = []

        if len(command_output["entries"]) == 0:
            self.result.is_skipped("IS-IS-SR is not running on device.")
            return

        for input_entry in self.inputs.entries:
            eos_entry = self._eos_entry_lookup(search_value=input_entry.endpoint, entries=command_output["entries"])
            if eos_entry is None:
                failure_message.append(f"Tunnel to {input_entry} is not found.")
            elif input_entry.vias is not None:
                failure_src = []
                for via_input in input_entry.vias:
                    if not self._check_tunnel_type(via_input, eos_entry):
                        failure_src.append("incorrect tunnel type")
                    if not self._check_tunnel_nexthop(via_input, eos_entry):
                        failure_src.append("incorrect nexthop")
                    if not self._check_tunnel_interface(via_input, eos_entry):
                        failure_src.append("incorrect interface")
                    if not self._check_tunnel_id(via_input, eos_entry):
                        failure_src.append("incorrect tunnel ID")

                if failure_src:
                    failure_message.append(f"Tunnel to {input_entry.endpoint!s} is incorrect: {', '.join(failure_src)}")

        if failure_message:
            self.result.is_failure("\n".join(failure_message))

    def _check_tunnel_type(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
        """
        Check if the tunnel type specified in `via_input` matches any of the tunnel types in `eos_entry`.

        Args:
            via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input tunnel type to check.
            eos_entry (dict[str, Any]): The EOS entry containing the tunnel types.

        Returns
        -------
            bool: True if the tunnel type matches any of the tunnel types in `eos_entry`, False otherwise.
        """
        if via_input.type is not None:
            return any(
                via_input.type
                == get_value(
                    dictionary=eos_via,
                    key="type",
                    default="undefined",
                )
                for eos_via in eos_entry["vias"]
            )
        return True

    def _check_tunnel_nexthop(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
        """
        Check if the tunnel nexthop matches the given input.

        Args:
            via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object.
            eos_entry (dict[str, Any]): The EOS entry dictionary.

        Returns
        -------
            bool: True if the tunnel nexthop matches, False otherwise.
        """
        if via_input.nexthop is not None:
            return any(
                str(via_input.nexthop)
                == get_value(
                    dictionary=eos_via,
                    key="nexthop",
                    default="undefined",
                )
                for eos_via in eos_entry["vias"]
            )
        return True

    def _check_tunnel_interface(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
        """
        Check if the tunnel interface exists in the given EOS entry.

        Args:
            via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object.
            eos_entry (dict[str, Any]): The EOS entry dictionary.

        Returns
        -------
            bool: True if the tunnel interface exists, False otherwise.
        """
        if via_input.interface is not None:
            return any(
                via_input.interface
                == get_value(
                    dictionary=eos_via,
                    key="interface",
                    default="undefined",
                )
                for eos_via in eos_entry["vias"]
            )
        return True

    def _check_tunnel_id(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool:
        """
        Check if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias.

        Args:
            via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input vias to check.
            eos_entry (dict[str, Any]): The EOS entry to compare against.

        Returns
        -------
            bool: True if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias, False otherwise.
        """
        if via_input.tunnel_id is not None:
            return any(
                via_input.tunnel_id.upper()
                == get_value(
                    dictionary=eos_via,
                    key="tunnelId.type",
                    default="undefined",
                ).upper()
                for eos_via in eos_entry["vias"]
            )
        return True

_count_isis_neighbor

_count_isis_neighbor(isis_neighbor_json: dict[str, Any]) -> int

Count the number of isis neighbors.

Args

isis_neighbor_json: The JSON output of the show isis neighbors command.

Returns:

Type Description
int: The number of isis neighbors.
Source code in anta/tests/routing/isis.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def _count_isis_neighbor(isis_neighbor_json: dict[str, Any]) -> int:
    """Count the number of isis neighbors.

    Args
    ----
      isis_neighbor_json: The JSON output of the `show isis neighbors` command.

    Returns
    -------
      int: The number of isis neighbors.

    """
    count = 0
    for vrf_data in isis_neighbor_json["vrfs"].values():
        for instance_data in vrf_data["isisInstances"].values():
            count += len(instance_data.get("neighbors", {}))
    return count

_get_adjacency_segment_data_by_neighbor

_get_adjacency_segment_data_by_neighbor(neighbor: str, instance: str, vrf: str, command_output: dict[str, Any]) -> dict[str, Any] | None

Extract data related to an IS-IS interface for testing.

Source code in anta/tests/routing/isis.py
122
123
124
125
126
127
128
129
130
131
132
133
def _get_adjacency_segment_data_by_neighbor(neighbor: str, instance: str, vrf: str, command_output: dict[str, Any]) -> dict[str, Any] | None:
    """Extract data related to an IS-IS interface for testing."""
    search_path = f"vrfs.{vrf}.isisInstances.{instance}.adjacencySegments"
    if get_value(dictionary=command_output, key=search_path, default=None) is None:
        return None

    isis_instance = get_value(dictionary=command_output, key=search_path, default=None)

    return next(
        (segment_data for segment_data in isis_instance if neighbor == segment_data["ipAddress"]),
        None,
    )

_get_full_isis_neighbors

_get_full_isis_neighbors(isis_neighbor_json: dict[str, Any], neighbor_state: Literal['up', 'down'] = 'up') -> list[dict[str, Any]]

Return the isis neighbors whose adjacency state is up.

Args

isis_neighbor_json: The JSON output of the show isis neighbors command. neighbor_state: Value of the neihbor state we are looking for. Default up

Returns:

Type Description
list[dict[str, Any]]: A list of isis neighbors whose adjacency state is not `UP`.
Source code in anta/tests/routing/isis.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def _get_full_isis_neighbors(isis_neighbor_json: dict[str, Any], neighbor_state: Literal["up", "down"] = "up") -> list[dict[str, Any]]:
    """Return the isis neighbors whose adjacency state is `up`.

    Args
    ----
      isis_neighbor_json: The JSON output of the `show isis neighbors` command.
      neighbor_state: Value of the neihbor state we are looking for. Default up

    Returns
    -------
      list[dict[str, Any]]: A list of isis neighbors whose adjacency state is not `UP`.

    """
    return [
        {
            "vrf": vrf,
            "instance": instance,
            "neighbor": adjacency["hostname"],
            "neighbor_address": adjacency["routerIdV4"],
            "interface": adjacency["interfaceName"],
            "state": state,
        }
        for vrf, vrf_data in isis_neighbor_json["vrfs"].items()
        for instance, instance_data in vrf_data.get("isisInstances").items()
        for neighbor, neighbor_data in instance_data.get("neighbors").items()
        for adjacency in neighbor_data.get("adjacencies")
        if (state := adjacency["state"]) == neighbor_state
    ]

_get_interface_data

_get_interface_data(interface: str, vrf: str, command_output: dict[str, Any]) -> dict[str, Any] | None

Extract data related to an IS-IS interface for testing.

Source code in anta/tests/routing/isis.py
108
109
110
111
112
113
114
115
116
117
118
119
def _get_interface_data(interface: str, vrf: str, command_output: dict[str, Any]) -> dict[str, Any] | None:
    """Extract data related to an IS-IS interface for testing."""
    if (vrf_data := get_value(command_output, f"vrfs.{vrf}")) is None:
        return None

    for instance_data in vrf_data.get("isisInstances").values():
        if (intf_dict := get_value(dictionary=instance_data, key="interfaces")) is not None:
            try:
                return next(ifl_data for ifl, ifl_data in intf_dict.items() if ifl == interface)
            except StopIteration:
                return None
    return None

_get_isis_neighbors_count

_get_isis_neighbors_count(isis_neighbor_json: dict[str, Any]) -> list[dict[str, Any]]

Count number of IS-IS neighbor of the device.

Source code in anta/tests/routing/isis.py
 96
 97
 98
 99
100
101
102
103
104
105
def _get_isis_neighbors_count(isis_neighbor_json: dict[str, Any]) -> list[dict[str, Any]]:
    """Count number of IS-IS neighbor of the device."""
    return [
        {"vrf": vrf, "interface": interface, "mode": mode, "count": int(level_data["numAdjacencies"]), "level": int(level)}
        for vrf, vrf_data in isis_neighbor_json["vrfs"].items()
        for instance, instance_data in vrf_data.get("isisInstances").items()
        for interface, interface_data in instance_data.get("interfaces").items()
        for level, level_data in interface_data.get("intfLevels").items()
        if (mode := level_data["passive"]) is not True
    ]

_get_not_full_isis_neighbors

_get_not_full_isis_neighbors(isis_neighbor_json: dict[str, Any]) -> list[dict[str, Any]]

Return the isis neighbors whose adjacency state is not up.

Args

isis_neighbor_json: The JSON output of the show isis neighbors command.

Returns:

Type Description
list[dict[str, Any]]: A list of isis neighbors whose adjacency state is not `UP`.
Source code in anta/tests/routing/isis.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def _get_not_full_isis_neighbors(isis_neighbor_json: dict[str, Any]) -> list[dict[str, Any]]:
    """Return the isis neighbors whose adjacency state is not `up`.

    Args
    ----
      isis_neighbor_json: The JSON output of the `show isis neighbors` command.

    Returns
    -------
      list[dict[str, Any]]: A list of isis neighbors whose adjacency state is not `UP`.

    """
    return [
        {
            "vrf": vrf,
            "instance": instance,
            "neighbor": adjacency["hostname"],
            "state": state,
        }
        for vrf, vrf_data in isis_neighbor_json["vrfs"].items()
        for instance, instance_data in vrf_data.get("isisInstances").items()
        for neighbor, neighbor_data in instance_data.get("neighbors").items()
        for adjacency in neighbor_data.get("adjacencies")
        if (state := adjacency["state"]) != "up"
    ]