From e7ffd8bce62d42cff00363c805736f5032911708 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Wed, 25 Sep 2024 14:55:15 +0200
Subject: [PATCH] =?UTF-8?q?Update=20input=20form=20of=20G=C3=89ANT=20IP=20?=
 =?UTF-8?q?creation,=20update=20model=20of=20BGP=20peer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ..._f4239c9361b4_update_bgp_session_model.py} | 26 ++++++++---
 gso/products/product_blocks/bgp_session.py    |  4 ++
 gso/translations/en-GB.json                   |  9 +---
 gso/workflows/geant_ip/create_geant_ip.py     | 46 +++++--------------
 4 files changed, 36 insertions(+), 49 deletions(-)
 rename gso/migrations/versions/{2024-09-25_c3e98d657b36_add_passive_boolean_to_bgp_session_model.py => 2024-09-25_f4239c9361b4_update_bgp_session_model.py} (53%)

diff --git a/gso/migrations/versions/2024-09-25_c3e98d657b36_add_passive_boolean_to_bgp_session_model.py b/gso/migrations/versions/2024-09-25_f4239c9361b4_update_bgp_session_model.py
similarity index 53%
rename from gso/migrations/versions/2024-09-25_c3e98d657b36_add_passive_boolean_to_bgp_session_model.py
rename to gso/migrations/versions/2024-09-25_f4239c9361b4_update_bgp_session_model.py
index ef499051..a3247fa3 100644
--- a/gso/migrations/versions/2024-09-25_c3e98d657b36_add_passive_boolean_to_bgp_session_model.py
+++ b/gso/migrations/versions/2024-09-25_f4239c9361b4_update_bgp_session_model.py
@@ -1,15 +1,15 @@
-"""Add passive boolean to BGP session model.
+"""Update BGP session model.
 
-Revision ID: c3e98d657b36
+Revision ID: f4239c9361b4
 Revises: f900cbaa47d7
-Create Date: 2024-09-25 14:05:57.072597
+Create Date: 2024-09-25 14:20:55.540546
 
 """
 import sqlalchemy as sa
 from alembic import op
 
 # revision identifiers, used by Alembic.
-revision = 'c3e98d657b36'
+revision = 'f4239c9361b4'
 down_revision = 'f900cbaa47d7'
 branch_labels = None
 depends_on = None
@@ -18,7 +18,13 @@ depends_on = None
 def upgrade() -> None:
     conn = op.get_bind()
     conn.execute(sa.text("""
-INSERT INTO resource_types (resource_type, description) VALUES ('is_passive', 'This BGP session is passive') RETURNING resource_types.resource_type_id
+INSERT INTO resource_types (resource_type, description) VALUES ('rtbh_enabled', 'Remote Triggered Blackhole is enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('is_passive', 'BGP session is passive') RETURNING resource_types.resource_type_id
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive')))
@@ -28,14 +34,20 @@ INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VA
 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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive'))
     """))
     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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive'))
     """))
     conn.execute(sa.text("""
-DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive'))
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_enabled', 'is_passive'))
     """))
     conn.execute(sa.text("""
-DELETE FROM resource_types WHERE resource_types.resource_type IN ('is_passive')
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('rtbh_enabled', 'is_passive')
     """))
diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py
index 8da070c0..b952f181 100644
--- a/gso/products/product_blocks/bgp_session.py
+++ b/gso/products/product_blocks/bgp_session.py
@@ -33,6 +33,7 @@ class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INI
     send_default_route: bool | None = None
     is_multi_hop: bool = False
     is_passive: bool = False
+    rtbh_enabled: bool = False
 
 
 class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
@@ -49,6 +50,7 @@ class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycl
     send_default_route: bool
     is_multi_hop: bool
     is_passive: bool
+    rtbh_enabled: bool
 
 
 class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -76,3 +78,5 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE
     is_multi_hop: bool
     #: Whether this is a passive session.
     is_passive: bool
+    #: Whether Remote Triggered Blackhole is enabled
+    rtbh_enabled: bool
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 67f4bfeb..900b1af0 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -19,6 +19,7 @@
             "router_role": "Router role",
 
             "geant_s_sid": "GÉANT S-SID",
+            "geant_sid": "GÉANT S-SID",
             "iptrunk_description": "IPtrunk description",
             "iptrunk_type": "IPtrunk type",
             "iptrunk_speed": "Capacity per port (in Gbits/s)",
@@ -32,13 +33,7 @@
             "migrate_to_different_site": "Migrating to a different Site",
             "remove_configuration": "Remove configuration from the router",
             "clean_up_ipam": "Clean up related entries in IPAM",
-            "restore_isis_metric": "Restore ISIS metric to original value",
-
-            "bgp_peers": {
-                "bfd_enabled": "BFD enabled",
-                "bfd_interval": "BFD interval",
-                "bfd_multiplier": "BFD multiplier"
-            }
+            "restore_isis_metric": "Restore ISIS metric to original value"
         }
     },
     "workflow": {
diff --git a/gso/workflows/geant_ip/create_geant_ip.py b/gso/workflows/geant_ip/create_geant_ip.py
index 23153305..a55ebd2e 100644
--- a/gso/workflows/geant_ip/create_geant_ip.py
+++ b/gso/workflows/geant_ip/create_geant_ip.py
@@ -12,7 +12,6 @@ from orchestrator.workflow import StepList, begin, done, step, workflow
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import AfterValidator, BaseModel, ConfigDict, Field, computed_field
-from pydantic_forms.types import strEnum
 from pydantic_forms.validators import Divider
 
 from gso.products.product_blocks.bgp_session import BGPSession, IPFamily
@@ -26,7 +25,7 @@ from gso.utils.helpers import (
     partner_choice,
 )
 from gso.utils.shared_enums import APType, SBPType
-from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPv6AddressType
+from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType
 from gso.utils.types.tt_number import TTNumber
 
 
@@ -41,15 +40,9 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
 
     initial_user_input = yield CreateGeantIPForm
 
-    class BGPPeerFamily(strEnum):
-        V4 = "IPv4 only"
-        V6 = "IPv6 only"
-        DUAL_STACK = "Dual stack"
-
     class EdgePortSelection(BaseModel):
         edge_port: active_edge_port_selector(partner_id=initial_user_input.partner)  # type: ignore[valid-type]
         ap_type: APType
-        ip_family: BGPPeerFamily
 
     def validate_edge_ports_are_unique(edge_ports: list[EdgePortSelection]) -> list[EdgePortSelection]:
         """Verify if interfaces are unique."""
@@ -76,13 +69,15 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         bfd_enabled: bool = False
         bfd_interval: int | None = None
         bfd_multiplier: int | None = None
+        rtbh_enabled: bool = False
         has_custom_policies: bool = False
         authentication_key: str
         multipath_enabled: bool = False
         send_default_route: bool = False
         is_multi_hop: bool = False
+        is_passive: bool = False
 
-    class V4OnlyBGPPeer(BaseBGPPeer):
+    class IPv4BGPPeer(BaseBGPPeer):
         peer_address: IPv4AddressType
         add_v4_multicast: bool = Field(default=False, exclude=True)
 
@@ -91,7 +86,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         def families(self) -> list[IPFamily]:
             return [IPFamily.V4UNICAST, IPFamily.V4MULTICAST] if self.add_v4_multicast else [IPFamily.V4UNICAST]
 
-    class V6OnlyBGPPeer(BaseBGPPeer):
+    class IPv6BGPPeer(BaseBGPPeer):
         peer_address: IPv6AddressType
         add_v6_multicast: bool = Field(default=False, exclude=True)
 
@@ -100,33 +95,9 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         def families(self) -> list[IPFamily]:
             return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST]
 
-    class DualStackBGPPeer(V4OnlyBGPPeer, V6OnlyBGPPeer):
-        peer_address: IPAddress  # type: ignore[assignment]
-
-        @computed_field  # type: ignore[misc]
-        @property
-        def families(self) -> list[IPFamily]:
-            result = [IPFamily.V4UNICAST, IPFamily.V6UNICAST]
-            if self.add_v4_multicast:
-                result.append(IPFamily.V4MULTICAST)
-            if self.add_v6_multicast:
-                result.append(IPFamily.V6MULTICAST)
-            return result
-
     binding_port_inputs = []
     while current_ep_index < total_ep_count:
         current_edge_port = EdgePort.from_subscription(ep_list[current_ep_index].edge_port)
-        bgp_peer_type: type[object]
-        match ep_list[current_ep_index].ip_family:
-            case BGPPeerFamily.V4:
-                bgp_peer_type = V4OnlyBGPPeer
-            case BGPPeerFamily.V6:
-                bgp_peer_type = V6OnlyBGPPeer
-            case BGPPeerFamily.DUAL_STACK:
-                bgp_peer_type = DualStackBGPPeer
-            case _:
-                msg = f"Invalid IP family selected: {ep_list[current_ep_index].ip_family}"
-                raise ValueError(msg)
 
         class BindingPortsInputForm(FormPage):
             model_config = ConfigDict(
@@ -146,12 +117,17 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
             ipv6_address: IPv6AddressType
             custom_firewall_filters: bool = False
             divider: Divider = Field(None, exclude=True)
-            bgp_peers: list[bgp_peer_type]  # type: ignore[valid-type]
+            v4_bgp_peer: IPv4BGPPeer
+            v6_bgp_peer: IPv6BGPPeer
 
         binding_port_input_form = yield BindingPortsInputForm
         binding_port_input = binding_port_input_form.model_dump()
 
         binding_port_input["sbp_type"] = SBPType.L3
+        binding_port_input["bgp_peers"] = [
+            binding_port_input_form.v4_bgp_peer.model_dump(),
+            binding_port_input_form.v6_bgp_peer.model_dump(),
+        ]
         binding_port_inputs.append(binding_port_input)
         current_ep_index += 1
 
-- 
GitLab