From 0e7082e7d59b33f9068d0e5e13dc45126833d591 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Thu, 18 Apr 2024 13:31:28 +0200
Subject: [PATCH] Add NetBox check to IP trunk validation workflow

---
 gso/workflows/iptrunk/validate_iptrunk.py | 67 +++++++++++++++++------
 1 file changed, 51 insertions(+), 16 deletions(-)

diff --git a/gso/workflows/iptrunk/validate_iptrunk.py b/gso/workflows/iptrunk/validate_iptrunk.py
index a66cb419..ea80bb9c 100644
--- a/gso/workflows/iptrunk/validate_iptrunk.py
+++ b/gso/workflows/iptrunk/validate_iptrunk.py
@@ -8,6 +8,7 @@ from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, done, init, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from services.netbox_client import NetboxClient
 from workflows.iptrunk.deploy_twamp import deploy_twamp_dry
 from workflows.iptrunk.migrate_iptrunk import check_ip_trunk_isis
 from workflows.iptrunk.modify_trunk_interface import provision_ip_trunk_iface_dry
@@ -15,6 +16,8 @@ from workflows.iptrunk.modify_trunk_interface import provision_ip_trunk_iface_dr
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.services import infoblox
 from gso.services.lso_client import anonymous_lso_interaction, execute_playbook
+from gso.utils.helpers import get_router_vendor
+from gso.utils.shared_enums import Vendor
 from gso.utils.workflow_steps import detect_configuration_drift
 
 
@@ -46,8 +49,8 @@ def verify_ipam_records(subscription: Iptrunk) -> None:
         ipam_errors += [
             (
                 "Missing IP trunk IPAM records, found the following instead.\n"
-                f"IPv4 expected {subscription.iptrunk.iptrunk_ipv4_network}, actual: {ipam_v4_network.network}\n"
-                f"IPv6 expected {subscription.iptrunk.iptrunk_ipv6_network}, actual: {ipam_v6_network.network}"
+                f"IPv4 expected '{subscription.iptrunk.iptrunk_ipv4_network}', actual: '{ipam_v4_network.network}'\n"
+                f"IPv6 expected '{subscription.iptrunk.iptrunk_ipv6_network}', actual: '{ipam_v6_network.network}'"
             )
         ]
 
@@ -58,14 +61,14 @@ def verify_ipam_records(subscription: Iptrunk) -> None:
         #  Validate IPv4 address allocation
         record = infoblox.find_host_by_fqdn(lag_fqdn)
         if not record:
-            ipam_errors += [f"No IPv4 host record found with FQDN {lag_fqdn}"]
+            ipam_errors += [f"No IPv4 host record found with FQDN '{lag_fqdn}'"]
         else:
             #  Allocation inside IPv4 network must be correct
             if str(side_v4) != record.ipv4addr:
                 ipam_errors += [
                     (
-                        f"Incorrectly allocated host record for FQDN {lag_fqdn}.\n"
-                        f"Expected {side_v4}, actual: {record.ipv4addr}"
+                        f"Incorrectly allocated host record for FQDN '{lag_fqdn}'.\n"
+                        f"Expected '{side_v4}', actual: '{record.ipv4addr}'"
                     )
                 ]
 
@@ -73,22 +76,22 @@ def verify_ipam_records(subscription: Iptrunk) -> None:
             if record.comment != subscription.subscription_id:
                 ipam_errors += [
                     (
-                        f"Incorrect host record found for {lag_fqdn} at {side_v4}. Comment should have been equal to"
-                        f"subscription ID {subscription.subscription_id}."
+                        f"Incorrect host record found for '{lag_fqdn}' at '{side_v4}'. Comment should have been equal "
+                        f"to subscription ID '{subscription.subscription_id}'."
                     )
                 ]
 
         #  Validate IPv6 address allocation
         record = infoblox.find_v6_host_by_fqdn(lag_fqdn)
         if not record:
-            ipam_errors += [f"No IPv6 host record found with FQDN {lag_fqdn}"]
+            ipam_errors += [f"No IPv6 host record found with FQDN '{lag_fqdn}'"]
         else:
             #  Allocation inside IPv6 network must be correct
             if str(side_v6) != record.ipv6addr:
                 ipam_errors += [
                     (
-                        f"Incorrectly allocated host record for FQDN {lag_fqdn}.\n"
-                        f"Expected {side_v6}, actual: {record.ipv6addr}"
+                        f"Incorrectly allocated host record for FQDN '{lag_fqdn}'.\n"
+                        f"Expected '{side_v6}', actual: '{record.ipv6addr}'"
                     )
                 ]
 
@@ -96,8 +99,8 @@ def verify_ipam_records(subscription: Iptrunk) -> None:
             if record.comment != subscription.subscription_id:
                 ipam_errors += [
                     (
-                        f"Incorrect host record found for {lag_fqdn} at {side_v6}. Comment should have been equal to"
-                        f"subscription ID {subscription.subscription_id}."
+                        f"Incorrect host record found for '{lag_fqdn}' at '{side_v6}'. Comment should have been equal "
+                        f"to subscription ID '{subscription.subscription_id}'."
                     )
                 ]
 
@@ -105,9 +108,38 @@ def verify_ipam_records(subscription: Iptrunk) -> None:
         raise ProcessFailureError(message="IPAM misconfiguration(s) found", details=str(ipam_errors))
 
 
-@step("Verify Netbox entries")
-def verify_netbox_entries() -> None:
-    """Validate required entries for an IP trunk in Netbox."""
+@step("Verify NetBox entries")
+def verify_netbox_entries(subscription: Iptrunk):
+    """Validate required entries for an IP trunk in NetBox."""
+    nbclient = NetboxClient()
+    netbox_errors = []
+    for side in subscription.iptrunk.iptrunk_sides:
+        if get_router_vendor(side.iptrunk_side_node.owner_subscription_id) == Vendor.NOKIA:
+            #  Raises en exception when not found.
+            interface = nbclient.get_interface_by_name_and_device(
+                side.iptrunk_side_ae_iface, side.iptrunk_side_node.router_fqdn
+            )
+            if interface.description != str(subscription.subscription_id):
+                netbox_errors += [
+                    (
+                        f"Incorrect description for '{side.iptrunk_side_ae_iface}', expected "
+                        f"'{subscription.subscription_id}' but got '{interface.description}'"
+                    )
+                ]
+            for member in side.iptrunk_side_ae_members:
+                interface = nbclient.get_interface_by_name_and_device(
+                    member.interface_name, side.iptrunk_side_node.router_fqdn
+                )
+                if interface.description != str(subscription.subscription_id):
+                    netbox_errors += [
+                        (
+                            f"Incorrect description for '{member.interface_name}', expected "
+                            f"'{subscription.subscription_id}' but got '{interface.description}'"
+                        )
+                    ]
+
+    if netbox_errors:
+        raise ProcessFailureError(message="NetBox misconfiguration(s) found", details=str(netbox_errors))
 
 
 @workflow(
@@ -118,8 +150,11 @@ def verify_netbox_entries() -> None:
 def validate_iptrunk() -> StepList:
     """Validate an existing, active IP Trunk subscription.
 
-    * Run an Ansible playbook to verify the configuration is intact.
     * Verify that the :term:`LAG` interfaces are correctly configured in :term:`IPAM`.
+    * Check correct configuration of interfaces in NetBox.
+    * Verify the configuration on both sides of the trunk is intact.
+    * Check the ISIS metric of the trunk.
+    * Verify that TWAMP configuration is correct.
     """
     return (
         init
-- 
GitLab