From 7e409757010987954db91addf8ba8fe6cb08da2e Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Wed, 30 Oct 2024 14:47:51 +0100
Subject: [PATCH] Add BFD configuration to Service binding ports

---
 .../2024-10-30_c6b82e23297c_add_bfd_to_sbp.py | 50 +++++++++++++++++++
 .../product_blocks/service_binding_port.py    | 12 +++++
 .../create_nren_l3_core_service.py            |  3 ++
 .../modify_nren_l3_core_service.py            | 11 +++-
 .../fixtures/nren_l3_core_service_fixtures.py |  6 +++
 ...st_create_imported_nren_l3_core_service.py |  5 +-
 .../test_create_nren_l3_core_service.py       |  3 ++
 .../test_modify_nren_l3_core_service.py       |  6 +++
 8 files changed, 94 insertions(+), 2 deletions(-)
 create mode 100644 gso/migrations/versions/2024-10-30_c6b82e23297c_add_bfd_to_sbp.py

diff --git a/gso/migrations/versions/2024-10-30_c6b82e23297c_add_bfd_to_sbp.py b/gso/migrations/versions/2024-10-30_c6b82e23297c_add_bfd_to_sbp.py
new file mode 100644
index 00000000..d8fc7e61
--- /dev/null
+++ b/gso/migrations/versions/2024-10-30_c6b82e23297c_add_bfd_to_sbp.py
@@ -0,0 +1,50 @@
+"""Add BFD to SBP.
+
+Revision ID: c6b82e23297c
+Revises: 7412c5b7ebe4
+Create Date: 2024-10-30 14:39:30.047934
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'c6b82e23297c'
+down_revision = '7412c5b7ebe4'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier')))
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier'))
+    """))
diff --git a/gso/products/product_blocks/service_binding_port.py b/gso/products/product_blocks/service_binding_port.py
index bb90fcc1..19452b2e 100644
--- a/gso/products/product_blocks/service_binding_port.py
+++ b/gso/products/product_blocks/service_binding_port.py
@@ -30,6 +30,9 @@ class ServiceBindingPortInactive(
     geant_sid: str | None = None
     bgp_session_list: list[BGPSessionInactive] = Field(default_factory=list)
     edge_port: EdgePortBlockInactive | None = None
+    bfd_enabled: bool = False
+    bfd_interval: int | None = None
+    bfd_multiplier: int | None = None
 
 
 class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
@@ -46,6 +49,9 @@ class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[Subs
     geant_sid: str
     bgp_session_list: list[BGPSessionProvisioning]  # type: ignore[assignment]
     edge_port: EdgePortBlockProvisioning
+    bfd_enabled: bool
+    bfd_interval: int | None
+    bfd_multiplier: int | None
 
 
 class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -73,3 +79,9 @@ class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[Subscription
     bgp_session_list: list[BGPSession]  # type: ignore[assignment]
     #: The Edge Port on which this :term:`SBP` resides.
     edge_port: EdgePortBlock
+    #: Whether :term:`BFD` is enabled.
+    bfd_enabled: bool
+    #: The :term:`BFD` interval, if enabled.
+    bfd_interval: int | None
+    #: The :term:`BFD` multiplier, if enabled.
+    bfd_multiplier: int | None
diff --git a/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
index be55ae6a..5c3d2a92 100644
--- a/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
+++ b/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
@@ -105,6 +105,9 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
 
             geant_sid: str
             is_tagged: bool = False
+            bfd_enabled: bool = False
+            bfd_interval: int | None = None
+            bfd_multiplier: int | None = None
             vlan_id: VLAN_ID
             ipv4_address: IPv4AddressType
             ipv4_mask: IPV4Netmask
diff --git a/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
index afb7d69b..1d092aaf 100644
--- a/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
+++ b/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
@@ -136,7 +136,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
             geant_sid: str = current_sbp.geant_sid
             is_tagged: bool = current_sbp.is_tagged
-            # The SBP model doesn't require these three fields, but in the case of GÉANT IP OR IAS this will never
+            # The SBP model doesn't require these five fields, but in the case of GÉANT IP OR IAS this will never
             # occur since it's a layer 3 service. The ignore statements are there to put our type checker at ease.
             vlan_id: VLAN_ID = current_sbp.vlan_id  # type: ignore[assignment]
             ipv4_address: IPv4AddressType = current_sbp.ipv4_address  # type: ignore[assignment]
@@ -144,6 +144,9 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             ipv6_address: IPv6AddressType = current_sbp.ipv6_address  # type: ignore[assignment]
             ipv6_mask: IPV6Netmask = current_sbp.ipv6_mask  # type: ignore[assignment]
             custom_firewall_filters: bool = current_sbp.custom_firewall_filters
+            bfd_enabled: bool = current_sbp.bfd_enabled
+            bfd_interval: int | None = current_sbp.bfd_interval
+            bfd_multiplier: int | None = current_sbp.bfd_multiplier
             divider: Divider = Field(None, exclude=True)
             v4_bgp_peer: IPv4BGPPeer = IPv4BGPPeer(
                 **v4_peer.model_dump(exclude=set("families")),
@@ -188,6 +191,9 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             ipv4_address: IPv4AddressType
             ipv6_address: IPv6AddressType
             custom_firewall_filters: bool = False
+            bfd_enabled: bool = False
+            bfd_interval: int | None = None
+            bfd_multiplier: int | None = None
             divider: Divider = Field(None, exclude=True)
             v4_bgp_peer: IPv4BGPPeer
             v6_bgp_peer: IPv6BGPPeer
@@ -248,6 +254,9 @@ def modify_existing_sbp_blocks(subscription: NRENL3CoreService, modified_sbp_lis
         current_sbp.ipv4_address = modified_sbp_data["ipv4_address"]
         current_sbp.ipv6_address = modified_sbp_data["ipv6_address"]
         current_sbp.custom_firewall_filters = modified_sbp_data["custom_firewall_filters"]
+        current_sbp.bfd_enabled = modified_sbp_data["bfd_enabled"]
+        current_sbp.bfd_interval = modified_sbp_data["bfd_interval"]
+        current_sbp.bfd_multiplier = modified_sbp_data["bfd_multiplier"]
         access_port.ap_type = modified_sbp_data["new_ap_type"]
 
     return {"subscription": subscription}
diff --git a/test/fixtures/nren_l3_core_service_fixtures.py b/test/fixtures/nren_l3_core_service_fixtures.py
index d0fd069a..b3622cfd 100644
--- a/test/fixtures/nren_l3_core_service_fixtures.py
+++ b/test/fixtures/nren_l3_core_service_fixtures.py
@@ -69,7 +69,10 @@ def service_binding_port_factory(faker, bgp_session_subscription_factory, edge_p
         ipv6_mask: int | None = None,
         vlan_id: int | None = None,
         edge_port: EdgePort | None = None,
+        bfd_interval: int = 2,
+        bfd_multiplier: int = 2,
         *,
+        bfd_enabled: bool = False,
         custom_firewall_filters: bool = False,
         is_tagged: bool = False,
     ):
@@ -90,6 +93,9 @@ def service_binding_port_factory(faker, bgp_session_subscription_factory, edge_p
                 bgp_session_subscription_factory(families=[IPFamily.V6UNICAST], peer_address=faker.ipv6()),
             ],
             edge_port=edge_port or EdgePort.from_subscription(edge_port_subscription_factory()).edge_port,
+            bfd_enabled=bfd_enabled,
+            bfd_interval=bfd_interval,
+            bfd_multiplier=bfd_multiplier,
         )
 
     return create_service_binding_port
diff --git a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
index 63aab373..bcc0c068 100644
--- a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
+++ b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
@@ -27,9 +27,12 @@ def test_create_imported_nren_l3_core_service_success(
                 "ipv6_address": faker.ipv6(),
                 "ipv6_mask": faker.ipv6_netmask(),
                 "custom_firewall_filters": faker.boolean(),
+                "bfd_enabled": True,
+                "bfd_interval": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
                 "bgp_peers": [
                     {
-                        "bfd_enabled": faker.boolean(),
+                        "bfd_enabled": True,
                         "bfd_interval": faker.pyint(),
                         "bfd_multiplier": faker.pyint(),
                         "has_custom_policies": faker.boolean(),
diff --git a/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
index d8eac84e..cd8d123a 100644
--- a/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
+++ b/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
@@ -57,6 +57,9 @@ def test_create_nren_l3_core_service_success(
             "ipv6_address": faker.ipv6(),
             "ipv6_mask": faker.ipv6_netmask(),
             "custom_firewall_filters": faker.boolean(),
+            "bfd_enabled": True,
+            "bfd_interval": faker.pyint(),
+            "bfd_multiplier": faker.pyint(),
             "v4_bgp_peer": base_bgp_peer_input() | {"add_v4_multicast": faker.boolean(), "peer_address": faker.ipv4()},
             "v6_bgp_peer": base_bgp_peer_input() | {"add_v6_multicast": faker.boolean(), "peer_address": faker.ipv6()},
         },
diff --git a/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
index 197be36c..d2976494 100644
--- a/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
+++ b/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
@@ -102,6 +102,9 @@ def sbp_input_form_data(faker):
             "ipv4_address": faker.ipv4(),
             "ipv6_address": faker.ipv6(),
             "custom_firewall_filters": True,
+            "bfd_enabled": True,
+            "bfd_interval": faker.pyint(),
+            "bfd_multiplier": faker.pyint(),
             "v4_bgp_peer": {
                 "bfd_enabled": True,
                 "bfd_interval": faker.pyint(),
@@ -262,3 +265,6 @@ def test_modify_nren_l3_core_service_modify_edge_port_success(
             )
             == new_sbp_data[i]["v6_bgp_peer"]["add_v6_multicast"]
         )
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.bfd_enabled == new_sbp_data[i]["bfd_enabled"]
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.bfd_interval == new_sbp_data[i]["bfd_interval"]
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.bfd_multiplier == new_sbp_data[i]["bfd_multiplier"]
-- 
GitLab