From c7198e3285a4c7459562c82782076c34f7ba4510 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 13:53:31 +0200
Subject: [PATCH 01/19] Add R&E Peer and LHCONE product types and blocks

---
 gso/products/__init__.py                      | 16 ++++
 gso/products/product_blocks/r_and_e_lhcone.py | 30 +++++++
 gso/products/product_blocks/r_and_e_peer.py   | 30 +++++++
 gso/products/product_types/r_and_e_lhcone.py  | 81 +++++++++++++++++++
 gso/products/product_types/r_and_e_peer.py    | 81 +++++++++++++++++++
 5 files changed, 238 insertions(+)
 create mode 100644 gso/products/product_blocks/r_and_e_lhcone.py
 create mode 100644 gso/products/product_blocks/r_and_e_peer.py
 create mode 100644 gso/products/product_types/r_and_e_lhcone.py
 create mode 100644 gso/products/product_types/r_and_e_peer.py

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index fff581523..0834df751 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -16,6 +16,8 @@ from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect
 from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
 from gso.products.product_types.lhcone import ImportedLHCOne, LHCOne
+from gso.products.product_types.r_and_e_peer import ImportedRAndEPeer, RAndEPeer
+from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOne, RAndELHCOne
 from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter
 from gso.products.product_types.opengear import ImportedOpengear, Opengear
 from gso.products.product_types.pop_vlan import PopVlan
@@ -67,6 +69,12 @@ class ProductName(strEnum):
     LHCONE = "LHCOne"
     """LHCOne."""
     IMPORTED_LHCONE = "Imported LHCOne"
+    R_AND_E_PEER = "R&E Peer"
+    """R&E Peer products."""
+    IMPORTED_R_AND_E_PEER = "Imported R&E Peer"
+    R_AND_E_LHCONE = "R&E LHCOne"
+    """R&E LHCOne products."""
+    IMPORTED_R_AND_E_LHCONE = "Imported R&E LHCOne"
     COPERNICUS = "Copernicus"
     """Copernicus."""
     IMPORTED_COPERNICUS = "Imported Copernicus"
@@ -116,6 +124,10 @@ class ProductType(strEnum):
     IMPORTED_GEANT_IP = ImportedGeantIP.__name__
     LHCONE = LHCOne.__name__
     IMPORTED_LHCONE = ImportedLHCOne.__name__
+    R_AND_E_PEER = RAndEPeer.__name__
+    IMPORTED_R_AND_E_PEER = ImportedRAndEPeer.__name__
+    R_AND_E_LHCONE = RAndELHCOne.__name__
+    IMPORTED_R_AND_E_LHCONE = ImportedRAndELHCOne.__name__
     COPERNICUS = Copernicus.__name__
     IMPORTED_COPERNICUS = ImportedCopernicus.__name__
 
@@ -147,6 +159,10 @@ SUBSCRIPTION_MODEL_REGISTRY.update(
         ProductName.IMPORTED_IAS.value: ImportedIAS,
         ProductName.LHCONE.value: LHCOne,
         ProductName.IMPORTED_LHCONE.value: ImportedLHCOne,
+        ProductName.R_AND_E_PEER.value: RAndEPeer,
+        ProductName.IMPORTED_R_AND_E_PEER.value: ImportedRAndEPeer,
+        ProductName.R_AND_E_LHCONE.value: RAndELHCOne,
+        ProductName.IMPORTED_R_AND_E_LHCONE.value: ImportedRAndELHCOne,
         ProductName.COPERNICUS.value: Copernicus,
         ProductName.IMPORTED_COPERNICUS.value: ImportedCopernicus,
         ProductName.GEANT_PLUS.value: Layer2Circuit,
diff --git a/gso/products/product_blocks/r_and_e_lhcone.py b/gso/products/product_blocks/r_and_e_lhcone.py
new file mode 100644
index 000000000..61118fbf6
--- /dev/null
+++ b/gso/products/product_blocks/r_and_e_lhcone.py
@@ -0,0 +1,30 @@
+"""Product blocks for R&E LHCONE products."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.l3_core_service import (
+    L3CoreServiceBlock,
+    L3CoreServiceBlockInactive,
+    L3CoreServiceBlockProvisioning,
+)
+
+
+class RAndELHCOneBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="RAndELHCOneBlock"
+):
+    """An inactive R&E LHCONE product block. See `RAndELHCOneBlock`."""
+
+    l3_core: L3CoreServiceBlockInactive
+
+
+class RAndELHCOneBlockProvisioning(RAndELHCOneBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A provisioning R&E LHCONE product block. See `RAndELHCOneBlock`."""
+
+    l3_core: L3CoreServiceBlockProvisioning
+
+
+class RAndELHCOneBlock(RAndELHCOneBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active R&E LHCONE product block."""
+
+    l3_core: L3CoreServiceBlock
\ No newline at end of file
diff --git a/gso/products/product_blocks/r_and_e_peer.py b/gso/products/product_blocks/r_and_e_peer.py
new file mode 100644
index 000000000..21e66af5c
--- /dev/null
+++ b/gso/products/product_blocks/r_and_e_peer.py
@@ -0,0 +1,30 @@
+"""Product blocks for R&E Peer products."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.l3_core_service import (
+    L3CoreServiceBlock,
+    L3CoreServiceBlockInactive,
+    L3CoreServiceBlockProvisioning,
+)
+
+
+class RAndEPeerBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="RAndEPeerBlock"
+):
+    """An inactive R&E Peer product block. See `RAndEPeerBlock`."""
+
+    l3_core: L3CoreServiceBlockInactive
+
+
+class RAndEPeerBlockProvisioning(RAndEPeerBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A provisioning R&E Peer product block. See `RAndEPeerBlock`."""
+
+    l3_core: L3CoreServiceBlockProvisioning
+
+
+class RAndEPeerBlock(RAndEPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active R&E Peer product block."""
+
+    l3_core: L3CoreServiceBlock
\ No newline at end of file
diff --git a/gso/products/product_types/r_and_e_lhcone.py b/gso/products/product_types/r_and_e_lhcone.py
new file mode 100644
index 000000000..5f99ee58d
--- /dev/null
+++ b/gso/products/product_types/r_and_e_lhcone.py
@@ -0,0 +1,81 @@
+"""Product type for R&E LHCONE."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.r_and_e_lhcone import (
+    RAndELHCOneBlock,
+    RAndELHCOneBlockInactive,
+    RAndELHCOneBlockProvisioning,
+)
+from gso.products.product_types.l3_core_service import BaseL3SubscriptionModel
+
+if TYPE_CHECKING:
+    from gso.products.product_blocks.l3_core_service import (
+        L3CoreServiceBlockInactive,
+    )
+
+
+class RAndELHCOneInactive(BaseL3SubscriptionModel, is_base=True):
+    """An R&E LHCONE product that is inactive."""
+
+    r_and_e_lhcone: RAndELHCOneBlockInactive
+
+    @property
+    def l3_core(self) -> L3CoreServiceBlockInactive:
+        """Getter: Retrieve the l3_core from the r_and_e_lhcone attribute."""
+        return self.r_and_e_lhcone.l3_core
+
+    @l3_core.setter
+    def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
+        """Setter: Set the l3_core on the r_and_e_lhcone attribute."""
+        self.r_and_e_lhcone.l3_core = value
+
+    @property
+    def service_name_attribute(self) -> str:
+        """Get the service name."""
+        return "r_and_e_lhcone"
+
+
+class RAndELHCOneProvisioning(RAndELHCOneInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An R&E LHCONE product that is being provisioned."""
+
+    r_and_e_lhcone: RAndELHCOneBlockProvisioning
+
+
+class RAndELHCOne(RAndELHCOneProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An R&E LHCONE product that is active."""
+
+    r_and_e_lhcone: RAndELHCOneBlock
+
+
+class ImportedRAndELHCOneInactive(BaseL3SubscriptionModel, is_base=True):
+    """An imported R&E LHCONE product that is inactive."""
+
+    r_and_e_lhcone: RAndELHCOneBlockInactive
+
+    @property
+    def l3_core(self) -> L3CoreServiceBlockInactive:
+        """Getter: Retrieve the l3_core from the r_and_e_lhcone attribute."""
+        return self.r_and_e_lhcone.l3_core
+
+    @l3_core.setter
+    def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
+        """Setter: Set the l3_core on the r_and_e_lhcone attribute."""
+        self.r_and_e_lhcone.l3_core = value
+
+    @property
+    def service_name_attribute(self) -> str:
+        """Get the service name."""
+        return "r_and_e_lhcone"
+
+
+class ImportedRAndELHCOne(
+    ImportedRAndELHCOneInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported R&E LHCONE product that is active."""
+
+    r_and_e_lhcone: RAndELHCOneBlock
\ No newline at end of file
diff --git a/gso/products/product_types/r_and_e_peer.py b/gso/products/product_types/r_and_e_peer.py
new file mode 100644
index 000000000..7eba906d2
--- /dev/null
+++ b/gso/products/product_types/r_and_e_peer.py
@@ -0,0 +1,81 @@
+"""Product type for R&E Peer."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.r_and_e_peer import (
+    RAndEPeerBlock,
+    RAndEPeerBlockInactive,
+    RAndEPeerBlockProvisioning,
+)
+from gso.products.product_types.l3_core_service import BaseL3SubscriptionModel
+
+if TYPE_CHECKING:
+    from gso.products.product_blocks.l3_core_service import (
+        L3CoreServiceBlockInactive,
+    )
+
+
+class RAndEPeerInactive(BaseL3SubscriptionModel, is_base=True):
+    """An R&E Peer product that is inactive."""
+
+    r_and_e_peer: RAndEPeerBlockInactive
+
+    @property
+    def l3_core(self) -> L3CoreServiceBlockInactive:
+        """Getter: Retrieve the l3_core from the r_and_e_peer attribute."""
+        return self.r_and_e_peer.l3_core
+
+    @l3_core.setter
+    def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
+        """Setter: Set the l3_core on the r_and_e_peer attribute."""
+        self.r_and_e_peer.l3_core = value
+
+    @property
+    def service_name_attribute(self) -> str:
+        """Get the service name."""
+        return "r_and_e_peer"
+
+
+class RAndEPeerProvisioning(RAndEPeerInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An R&E Peer product that is being provisioned."""
+
+    r_and_e_peer: RAndEPeerBlockProvisioning
+
+
+class RAndEPeer(RAndEPeerProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An R&E Peer product that is active."""
+
+    r_and_e_peer: RAndEPeerBlock
+
+
+class ImportedRAndEPeerInactive(BaseL3SubscriptionModel, is_base=True):
+    """An imported R&E Peer product that is inactive."""
+
+    r_and_e_peer: RAndEPeerBlockInactive
+
+    @property
+    def l3_core(self) -> L3CoreServiceBlockInactive:
+        """Getter: Retrieve the l3_core from the r_and_e_peer attribute."""
+        return self.r_and_e_peer.l3_core
+
+    @l3_core.setter
+    def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
+        """Setter: Set the l3_core on the r_and_e_peer attribute."""
+        self.r_and_e_peer.l3_core = value
+
+    @property
+    def service_name_attribute(self) -> str:
+        """Get the service name."""
+        return "r_and_e_peer"
+
+
+class ImportedRAndEPeer(
+    ImportedRAndEPeerInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported R&E Peer product that is active."""
+
+    r_and_e_peer: RAndEPeerBlock
\ No newline at end of file
-- 
GitLab


From c00bea2df29cad8f6d8a6c6573ad727a6c691c8d Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:09:14 +0200
Subject: [PATCH 02/19] Add R&E Peer subscription workflow

---
 gso/workflows/__init__.py                     |  10 ++
 .../l3_core_service/r_and_e_peer/__init__.py  |   1 +
 .../create_imported_r_and_e_peer.py           |  43 ++++++
 .../r_and_e_peer/create_r_and_e_peer.py       |  61 ++++++++
 .../r_and_e_peer/import_r_and_e_peer.py       |  30 ++++
 .../r_and_e_peer/migrate_r_and_e_peer.py      |  69 +++++++++
 .../r_and_e_peer/modify_r_and_e_peer.py       |  38 +++++
 .../r_and_e_peer/terminate_r_and_e_peer.py    |  42 ++++++
 .../r_and_e_peer/validate_r_and_e_peer.py     |  30 ++++
 .../validate_r_and_e_peer_prefix_list.py      | 141 ++++++++++++++++++
 10 files changed, 465 insertions(+)
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/__init__.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/migrate_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/terminate_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py

diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 2633b5cd2..e734e9580 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -173,3 +173,13 @@ LazyWorkflowInstance("gso.workflows.vrf.create_vrf", "create_vrf")
 LazyWorkflowInstance("gso.workflows.vrf.modify_vrf_router_list", "modify_vrf_router_list")
 LazyWorkflowInstance("gso.workflows.vrf.redeploy_vrf", "redeploy_vrf")
 LazyWorkflowInstance("gso.workflows.vrf.terminate_vrf", "terminate_vrf")
+
+#  R&E Peer workflows
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_r_and_e_peer", "create_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.modify_r_and_e_peer", "modify_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.terminate_r_and_e_peer", "terminate_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.migrate_r_and_e_peer", "migrate_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_imported_r_and_e_peer", "create_imported_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.import_r_and_e_peer", "import_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer", "validate_r_and_e_peer")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_prefix_list", "validate_r_and_e_peer_prefix_list")
\ No newline at end of file
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/__init__.py b/gso/workflows/l3_core_service/r_and_e_peer/__init__.py
new file mode 100644
index 000000000..3f833e7f1
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/__init__.py
@@ -0,0 +1 @@
+"""R&E Peer service workflows."""
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py
new file mode 100644
index 000000000..6e56e435e
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py
@@ -0,0 +1,43 @@
+"""A creation workflow for adding an existing Imported R&E Peer to the service database."""
+
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+
+from gso.products import ProductName
+from gso.products.product_types.r_and_e_peer import ImportedRAndEPeerInactive
+from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import get_product_id_by_name
+from gso.workflows.l3_core_service.base_create_imported_l3_core_service import (
+    initial_input_form_generator,
+    initialize_subscription,
+)
+
+
+@step("Create subscription")
+def create_subscription(partner: str) -> dict:
+    """Create a new subscription object in the database."""
+    partner_id = get_partner_by_name(partner).partner_id
+    product_id = get_product_id_by_name(ProductName.IMPORTED_R_AND_E_PEER)
+    subscription = ImportedRAndEPeerInactive.from_product_id(product_id, partner_id)
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@workflow(
+    "Create Imported R&E Peer",
+    initial_input_form=initial_input_form_generator,
+    target=Target.CREATE,
+)
+def create_imported_r_and_e_peer() -> StepList:
+    """Import an R&E Peer without provisioning it."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py
new file mode 100644
index 000000000..93493e9d3
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py
@@ -0,0 +1,61 @@
+"""Create R&E Peer subscription workflow."""
+
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+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_forms.types import State, UUIDstr
+
+from gso.products.product_types.r_and_e_peer import RAndEPeerInactive
+from gso.services.lso_client import lso_interaction
+from gso.utils.workflow_steps import prompt_sharepoint_checklist_url, start_moodi, stop_moodi
+from gso.workflows.l3_core_service.base_create_l3_core_service import (
+    check_bgp_peers,
+    check_sbp_functionality,
+    create_new_sharepoint_checklist,
+    deploy_bgp_peers_dry,
+    deploy_bgp_peers_real,
+    initial_input_form_generator,
+    initialize_subscription,
+    provision_sbp_dry,
+    provision_sbp_real,
+    update_dns_records,
+)
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr, partner: str) -> State:
+    """Create a new subscription object in the database."""
+    subscription = RAndEPeerInactive.from_product_id(product, partner)
+
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@workflow(
+    "Create R&E Peer",
+    initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_r_and_e_peer() -> StepList:
+    """Create a new R&E Peer subscription."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> start_moodi()
+        >> lso_interaction(provision_sbp_dry)
+        >> lso_interaction(provision_sbp_real)
+        >> lso_interaction(check_sbp_functionality)
+        >> lso_interaction(deploy_bgp_peers_dry)
+        >> lso_interaction(deploy_bgp_peers_real)
+        >> lso_interaction(check_bgp_peers)
+        >> update_dns_records
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> create_new_sharepoint_checklist
+        >> prompt_sharepoint_checklist_url
+        >> stop_moodi()
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
new file mode 100644
index 000000000..0fea1892e
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
@@ -0,0 +1,30 @@
+"""A modification workflow for migrating an `ImportedRAndEPeer` to a `RAndEPeer` subscription."""
+
+from orchestrator.targets import Target
+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 pydantic_forms.types import State, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_types.r_and_e_peer import RAndEPeer, ImportedRAndEPeer
+from gso.services.partners import get_partner_by_id
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create an R&E Peer subscription")
+def import_r_and_e_peer_subscription(subscription_id: UUIDstr) -> State:
+    """Take an imported subscription, and turn it into an R&E Peer subscription."""
+    old_l3_core_service = ImportedRAndEPeer.from_subscription(subscription_id)
+    new_product_id = get_product_id_by_name(ProductName.R_AND_E_PEER)
+    new_subscription = RAndEPeer.from_other_product(old_l3_core_service, new_product_id)  # type: ignore[arg-type]
+    new_subscription.description = (
+        f"{new_subscription.product.name} service for {get_partner_by_id(new_subscription.customer_id).name}"
+    )
+    return {"subscription": new_subscription}
+
+
+@workflow("Import R&E Peer", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_r_and_e_peer() -> StepList:
+    """Modify an imported subscription into an R&E Peer subscription to complete the import."""
+    return init >> store_process_subscription(Target.MODIFY) >> unsync >> import_r_and_e_peer_subscription >> resync >> done
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/migrate_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/migrate_r_and_e_peer.py
new file mode 100644
index 000000000..a72a76768
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/migrate_r_and_e_peer.py
@@ -0,0 +1,69 @@
+"""A modification workflow that migrates an R&E Peer Service to a new Edge Port.
+
+In one run of a migration workflow, only a single Access Port can be replaced. This is due to the nature of these
+services. When a service is dually homed, for example, only one of the Access Ports should be migrated. The other one
+will remain the way it is.
+
+At the start of the workflow, the operator will select one Access Port that is to be migrated, and will then select a
+destination Edge Port that this service should be placed on. All other Access Ports will be left as-is.
+"""
+
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, conditional, done
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.services.lso_client import lso_interaction
+from gso.utils.workflow_steps import IS_HUMAN_INITIATED_WF_KEY, start_moodi, stop_moodi
+from gso.workflows.l3_core_service.base_migrate_l3_core_service import (
+    deactivate_bgp_dry,
+    deactivate_bgp_real,
+    deactivate_sbp_dry,
+    deactivate_sbp_real,
+    deploy_bgp_session_dry,
+    deploy_bgp_session_real,
+    deploy_destination_ep_dry,
+    deploy_destination_ep_real,
+    generate_scoped_subscription_model,
+    inform_operator_traffic_check,
+    initial_input_form,
+    inject_partner_name,
+    show_bgp_neighbors,
+    update_dns_records,
+    update_subscription_model,
+)
+
+
+@workflow(
+    "Migrate R&E Peer",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form),
+    target=Target.MODIFY,
+)
+def migrate_r_and_e_peer() -> StepList:
+    """Migrate an R&E Peer Service to a destination Edge Port."""
+    is_human_initiated_wf = conditional(lambda state: bool(state.get(IS_HUMAN_INITIATED_WF_KEY)))
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> inject_partner_name
+        >> is_human_initiated_wf(lso_interaction(show_bgp_neighbors))
+        >> is_human_initiated_wf(lso_interaction(deactivate_bgp_dry))
+        >> is_human_initiated_wf(lso_interaction(deactivate_bgp_real))
+        >> is_human_initiated_wf(lso_interaction(deactivate_sbp_dry))
+        >> is_human_initiated_wf(lso_interaction(deactivate_sbp_real))
+        >> is_human_initiated_wf(inform_operator_traffic_check)
+        >> unsync
+        >> generate_scoped_subscription_model
+        >> start_moodi()
+        >> lso_interaction(deploy_destination_ep_dry)
+        >> lso_interaction(deploy_destination_ep_real)
+        >> lso_interaction(deploy_bgp_session_dry)
+        >> lso_interaction(deploy_bgp_session_real)
+        >> update_dns_records
+        >> update_subscription_model
+        >> resync
+        >> stop_moodi()
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py
new file mode 100644
index 000000000..f04009dfe
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py
@@ -0,0 +1,38 @@
+"""Modification workflow for an R&E Peer subscription."""
+
+from orchestrator import begin, conditional, done, workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.workflows.l3_core_service.base_modify_l3_core_service import (
+    Operation,
+    create_new_sbp,
+    initial_input_form_generator,
+    modify_existing_sbp,
+    remove_old_sbp,
+)
+
+
+@workflow(
+    "Modify R&E Peer",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_r_and_e_peer() -> StepList:
+    """Modify R&E Peer subscription."""
+    access_port_is_added = conditional(lambda state: state["operation"] == Operation.ADD)
+    access_port_is_removed = conditional(lambda state: state["operation"] == Operation.REMOVE)
+    access_port_is_modified = conditional(lambda state: state["operation"] == Operation.EDIT)
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> access_port_is_added(create_new_sbp)
+        >> access_port_is_removed(remove_old_sbp)
+        >> access_port_is_modified(modify_existing_sbp)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/terminate_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/terminate_r_and_e_peer.py
new file mode 100644
index 000000000..f9dc33549
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/terminate_r_and_e_peer.py
@@ -0,0 +1,42 @@
+"""Workflow for terminating an R&E Peer subscription."""
+
+from orchestrator import begin, workflow
+from orchestrator.forms import SubmitFormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, done
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic_forms.types import FormGenerator, UUIDstr
+
+from gso.products.product_types.r_and_e_peer import RAndEPeer
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Initial input form generator for terminating an R&E Peer subscription."""
+    subscription = RAndEPeer.from_subscription(subscription_id)
+
+    class TerminateForm(SubmitFormPage):
+        tt_number: TTNumber
+
+    user_input = yield TerminateForm
+
+    return {"subscription": subscription} | user_input.model_dump()
+
+
+@workflow(
+    "Terminate R&E Peer",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.TERMINATE,
+)
+def terminate_r_and_e_peer() -> StepList:
+    """Terminate an R&E Peer subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer.py
new file mode 100644
index 000000000..08cca1f64
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer.py
@@ -0,0 +1,30 @@
+"""Validation workflow for R&E Peer subscription objects."""
+
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.services.lso_client import anonymous_lso_interaction
+from gso.workflows.l3_core_service.base_validate_l3_core_service import (
+    build_fqdn_list,
+    validate_bgp_peers,
+    validate_dns_records,
+    validate_sbp_config,
+)
+
+
+@workflow("Validate R&E Peer", target=Target.SYSTEM, initial_input_form=(wrap_modify_initial_input_form(None)))
+def validate_r_and_e_peer() -> StepList:
+    """Validate an existing R&E Peer subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.SYSTEM)
+        >> unsync
+        >> build_fqdn_list
+        >> anonymous_lso_interaction(validate_sbp_config)
+        >> anonymous_lso_interaction(validate_bgp_peers)
+        >> validate_dns_records
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py
new file mode 100644
index 000000000..5a024e096
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py
@@ -0,0 +1,141 @@
+"""Prefix Validation workflow for R&E Peer subscription objects."""
+
+from typing import Any
+
+from orchestrator.config.assignee import Assignee
+from orchestrator.domain import SubscriptionModel
+from orchestrator.forms import SubmitFormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, conditional, done, inputstep, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync_unchecked
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import Field
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Label
+
+from gso.services.lso_client import LSOState, anonymous_lso_interaction, lso_interaction
+from gso.services.partners import get_partner_by_id
+from gso.utils.shared_enums import Vendor
+
+
+@step("Prepare list of all Access Ports")
+def build_fqdn_list(subscription_id: UUIDstr) -> State:
+    """Build the list of all FQDNs in the access ports of L3 Core Service subscription, excluding Juniper devices."""
+    subscription = SubscriptionModel.from_subscription(subscription_id)
+    ap_list = subscription.l3_core.ap_list  # type: ignore[attr-defined]
+    ap_fqdn_list = [
+        ap.sbp.edge_port.node.router_fqdn for ap in ap_list if ap.sbp.edge_port.node.vendor != Vendor.JUNIPER
+    ]
+    return {"ap_fqdn_list": ap_fqdn_list, "subscription": subscription, "subscription_was_in_sync": subscription.insync}
+
+
+@step("[DRY RUN] Validate Prefix-Lists")
+def validate_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState:
+    """Workflow step for running a playbook that validates prefix-lists in dry run mode."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": get_partner_by_id(subscription["customer_id"]).name,
+        "dry_run": True,
+        "verb": "deploy",
+        "object": "prefix_list",
+        "is_verification_workflow": "true",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription['description']}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/validate_prefix_list.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Evaluate validation of Prefix-Lists")
+def evaluate_result_has_diff(callback_result: dict) -> State:
+    """Evaluate the result of the playbook that validates prefix-lists."""
+    return {"callback_result": callback_result, "prefix_list_drift": bool(callback_result["return_code"] != 0)}
+
+
+@inputstep("Await operator confirmation", assignee=Assignee.SYSTEM)
+def await_operator() -> FormGenerator:
+    """Show a form for the operator to start redeploying the prefix list that has drifted."""
+
+    class AwaitOperatorForm(SubmitFormPage):
+        info_label_a: Label = Field("A drift has been detected for this prefix list!", exclude=True)
+        info_label_b: Label = Field("Please continue this workflow to redeploy the drifted prefix list.", exclude=True)
+
+    yield AwaitOperatorForm
+
+    return {}
+
+
+@step("[DRY RUN] Deploy Prefix-Lists")
+def deploy_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState:
+    """Workflow step for running a playbook that deploys prefix-lists in dry run mode."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": get_partner_by_id(subscription["customer_id"]).name,
+        "dry_run": True,
+        "verb": "deploy",
+        "object": "prefix_list",
+        "is_verification_workflow": "false",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription['description']}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[REAL] Deploy Prefix-Lists")
+def deploy_prefix_lists_real(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState:
+    """Workflow step for running a playbook that deploys prefix-lists."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": get_partner_by_id(subscription["customer_id"]).name,
+        "dry_run": False,
+        "verb": "deploy",
+        "object": "prefix_list",
+        "is_verification_workflow": "false",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription['description']}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@workflow(
+    "Validate R&E Peer Prefix-List", target=Target.SYSTEM, initial_input_form=(wrap_modify_initial_input_form(None))
+)
+def validate_r_and_e_peer_prefix_list() -> StepList:
+    """Validate prefix-lists for an existing R&E Peer subscription."""
+    fqdn_list_is_empty = conditional(lambda state: state["ap_fqdn_list"] == [])
+    prefix_list_has_drifted = conditional(lambda state: bool(state["prefix_list_drift"]))
+    subscription_was_in_sync = conditional(lambda state: bool(state["subscription_was_in_sync"]))
+
+    redeploy_prefix_list_steps = (
+        begin
+        >> unsync_unchecked
+        >> await_operator
+        >> lso_interaction(deploy_prefix_lists_dry)
+        >> lso_interaction(deploy_prefix_lists_real)
+        >> subscription_was_in_sync(resync)
+    )
+    prefix_list_validation_steps = (
+        begin
+        >> anonymous_lso_interaction(validate_prefix_lists_dry, evaluate_result_has_diff)
+        >> prefix_list_has_drifted(redeploy_prefix_list_steps)
+    )
+
+    return (
+        begin
+        >> store_process_subscription(Target.SYSTEM)
+        >> build_fqdn_list
+        >> fqdn_list_is_empty(done)
+        >> prefix_list_validation_steps
+        >> done
+    )
-- 
GitLab


From 0a1d2af8a5eec9701390411974bc4975cb9e88cd Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:18:00 +0200
Subject: [PATCH 03/19] Add R&E model migrations

---
 ...233b83e1124_add_r_e_peer_and_r_e_lhcone.py | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py

diff --git a/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py b/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py
new file mode 100644
index 000000000..00a20f107
--- /dev/null
+++ b/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py
@@ -0,0 +1,101 @@
+"""Add R&E peer and R&E LHCONE.
+
+Revision ID: 1233b83e1124
+Revises: 90547df711c3
+Create Date: 2025-06-04 13:37:22.122645
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '1233b83e1124'
+down_revision = '90547df711c3'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PeeringConnectionBlock', 'CommercialPeerBlock', 'IXPortBlock', 'PrivatePeerPortBlock', 'L3InterfacePortBlock', 'TransitProviderPortBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('PeeringConnectionBlock', 'CommercialPeerBlock', 'IXPortBlock', 'PrivatePeerPortBlock', 'L3InterfacePortBlock', 'TransitProviderPortBlock')
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes WHERE processes.pid IN (SELECT processes_subscriptions.pid FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported Transit Provider Port', 'Private Peer Port', 'Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'IX Port', 'Imported IX Port', 'Imported Commercial Peer'))))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported Transit Provider Port', 'Private Peer Port', 'Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'IX Port', 'Imported IX Port', 'Imported Commercial Peer')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported Transit Provider Port', 'Private Peer Port', 'Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'IX Port', 'Imported IX Port', 'Imported Commercial Peer')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported Transit Provider Port', 'Private Peer Port', 'Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'IX Port', 'Imported IX Port', 'Imported Commercial Peer'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('Imported Transit Provider Port', 'Private Peer Port', 'Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'IX Port', 'Imported IX Port', 'Imported Commercial Peer')
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('R&E Peer', 'R&E Peer', 'RAndEPeer', 'RE_PEER', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported R&E Peer', 'Imported R&E Peer', 'ImportedRAndEPeer', 'IMP_RE_PEER', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('R&E LHCOne', 'R&E LHCOne', 'RAndELHCOne', 'RE_LHCONE', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported R&E LHCOne', 'Imported R&E LHCOne', 'ImportedRAndELHCOne', 'IMP_RE_LHCONE', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('RAndEPeerBlock', 'R&E Peer Product Block', 'RE_PEER_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('RAndELHCOneBlock', 'R&E LLHCONE Prodcut Block', 'RE_LHCONE_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('R&E Peer')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported R&E Peer')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndELHCOneBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported R&E LHCOne')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndELHCOneBlock')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndELHCOneBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock')))
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E Peer', 'Imported R&E Peer')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne', 'Imported R&E LHCOne')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndELHCOneBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock', 'RAndELHCOneBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock', 'RAndELHCOneBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('RAndEPeerBlock', 'RAndELHCOneBlock')
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes WHERE processes.pid IN (SELECT processes_subscriptions.pid FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne', 'R&E Peer', 'Imported R&E Peer', 'Imported R&E LHCOne'))))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne', 'R&E Peer', 'Imported R&E Peer', 'Imported R&E LHCOne')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne', 'R&E Peer', 'Imported R&E Peer', 'Imported R&E LHCOne')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('R&E LHCOne', 'R&E Peer', 'Imported R&E Peer', 'Imported R&E LHCOne'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('R&E LHCOne', 'R&E Peer', 'Imported R&E Peer', 'Imported R&E LHCOne')
+    """))
-- 
GitLab


From 4a398951facecbf3901021b0baba58cbaa4c98f7 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:18:25 +0200
Subject: [PATCH 04/19] Fix validate prefix list workflow instance name and add
 migrations for WFs

---
 gso/workflows/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index e734e9580..9b8d21ca4 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -182,4 +182,4 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.migrate_r_and_e
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_imported_r_and_e_peer", "create_imported_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.import_r_and_e_peer", "import_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer", "validate_r_and_e_peer")
-LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_prefix_list", "validate_r_and_e_peer_prefix_list")
\ No newline at end of file
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list")
\ No newline at end of file
-- 
GitLab


From b8827b5fcec8608f644675cfb12ad996a54966e0 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:22:01 +0200
Subject: [PATCH 05/19] Fix validate prefix list workflow instance name and add
 migrations for WFs

---
 ...9813f5b2c95_add_r_and_e_peers_workflows.py | 81 +++++++++++++++++++
 gso/workflows/__init__.py                     |  2 +-
 2 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py

diff --git a/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py b/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py
new file mode 100644
index 000000000..4da549af4
--- /dev/null
+++ b/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py
@@ -0,0 +1,81 @@
+"""Add R and E peers workflows.
+
+Revision ID: 29813f5b2c95
+Revises: 1233b83e1124
+Create Date: 2025-06-04 14:17:20.069790
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '29813f5b2c95'
+down_revision = '1233b83e1124'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_r_and_e_peer",
+        "target": "CREATE",
+        "description": "Create R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "modify_r_and_e_peer",
+        "target": "MODIFY",
+        "description": "Modify R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "terminate_r_and_e_peer",
+        "target": "TERMINATE",
+        "description": "Terminate R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "migrate_r_and_e_peer",
+        "target": "MODIFY",
+        "description": "Migrate R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "create_imported_r_and_e_peer",
+        "target": "CREATE",
+        "description": "Create Imported R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "import_r_and_e_peer",
+        "target": "MODIFY",
+        "description": "Import R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "validate_r_and_e_peer",
+        "target": "SYSTEM",
+        "description": "Validate R&E Peer",
+        "product_type": "RAndEPeer"
+    },
+    {
+        "name": "validate_r_and_e_peer_prefix_list",
+        "target": "SYSTEM",
+        "description": "Validate R&E Peer Prefix-List",
+        "product_type": "RAndEPeer"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 9b8d21ca4..440a5de65 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -182,4 +182,4 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.migrate_r_and_e
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_imported_r_and_e_peer", "create_imported_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.import_r_and_e_peer", "import_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer", "validate_r_and_e_peer")
-LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list")
\ No newline at end of file
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list")
-- 
GitLab


From 2ea99c81baf2d0fd15f3ef0cbba03b34dee0b21f Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:28:10 +0200
Subject: [PATCH 06/19] Refactor R&E Peer and LHCONE imports and workflows for
 consistency

---
 gso/products/__init__.py                              |  4 ++--
 gso/products/product_blocks/r_and_e_lhcone.py         |  2 +-
 gso/products/product_blocks/r_and_e_peer.py           |  2 +-
 gso/products/product_types/r_and_e_lhcone.py          |  2 +-
 gso/products/product_types/r_and_e_peer.py            |  2 +-
 gso/workflows/__init__.py                             |  8 ++++++--
 .../r_and_e_peer/import_r_and_e_peer.py               | 11 +++++++++--
 .../r_and_e_peer/validate_r_and_e_peer_prefix_list.py |  6 +++---
 8 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 0834df751..8ad0e6327 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -16,11 +16,11 @@ from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect
 from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
 from gso.products.product_types.lhcone import ImportedLHCOne, LHCOne
-from gso.products.product_types.r_and_e_peer import ImportedRAndEPeer, RAndEPeer
-from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOne, RAndELHCOne
 from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter
 from gso.products.product_types.opengear import ImportedOpengear, Opengear
 from gso.products.product_types.pop_vlan import PopVlan
+from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOne, RAndELHCOne
+from gso.products.product_types.r_and_e_peer import ImportedRAndEPeer, RAndEPeer
 from gso.products.product_types.router import ImportedRouter, Router
 from gso.products.product_types.site import ImportedSite, Site
 from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitch, SuperPopSwitch
diff --git a/gso/products/product_blocks/r_and_e_lhcone.py b/gso/products/product_blocks/r_and_e_lhcone.py
index 61118fbf6..e4dda2e22 100644
--- a/gso/products/product_blocks/r_and_e_lhcone.py
+++ b/gso/products/product_blocks/r_and_e_lhcone.py
@@ -27,4 +27,4 @@ class RAndELHCOneBlockProvisioning(RAndELHCOneBlockInactive, lifecycle=[Subscrip
 class RAndELHCOneBlock(RAndELHCOneBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """An active R&E LHCONE product block."""
 
-    l3_core: L3CoreServiceBlock
\ No newline at end of file
+    l3_core: L3CoreServiceBlock
diff --git a/gso/products/product_blocks/r_and_e_peer.py b/gso/products/product_blocks/r_and_e_peer.py
index 21e66af5c..8fbbeb84e 100644
--- a/gso/products/product_blocks/r_and_e_peer.py
+++ b/gso/products/product_blocks/r_and_e_peer.py
@@ -27,4 +27,4 @@ class RAndEPeerBlockProvisioning(RAndEPeerBlockInactive, lifecycle=[Subscription
 class RAndEPeerBlock(RAndEPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """An active R&E Peer product block."""
 
-    l3_core: L3CoreServiceBlock
\ No newline at end of file
+    l3_core: L3CoreServiceBlock
diff --git a/gso/products/product_types/r_and_e_lhcone.py b/gso/products/product_types/r_and_e_lhcone.py
index 5f99ee58d..820a9a844 100644
--- a/gso/products/product_types/r_and_e_lhcone.py
+++ b/gso/products/product_types/r_and_e_lhcone.py
@@ -78,4 +78,4 @@ class ImportedRAndELHCOne(
 ):
     """An imported R&E LHCONE product that is active."""
 
-    r_and_e_lhcone: RAndELHCOneBlock
\ No newline at end of file
+    r_and_e_lhcone: RAndELHCOneBlock
diff --git a/gso/products/product_types/r_and_e_peer.py b/gso/products/product_types/r_and_e_peer.py
index 7eba906d2..8cd221875 100644
--- a/gso/products/product_types/r_and_e_peer.py
+++ b/gso/products/product_types/r_and_e_peer.py
@@ -78,4 +78,4 @@ class ImportedRAndEPeer(
 ):
     """An imported R&E Peer product that is active."""
 
-    r_and_e_peer: RAndEPeerBlock
\ No newline at end of file
+    r_and_e_peer: RAndEPeerBlock
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 440a5de65..586857872 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -179,7 +179,11 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_r_and_e_
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.modify_r_and_e_peer", "modify_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.terminate_r_and_e_peer", "terminate_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.migrate_r_and_e_peer", "migrate_r_and_e_peer")
-LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.create_imported_r_and_e_peer", "create_imported_r_and_e_peer")
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_peer.create_imported_r_and_e_peer", "create_imported_r_and_e_peer"
+)
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.import_r_and_e_peer", "import_r_and_e_peer")
 LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer", "validate_r_and_e_peer")
-LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list")
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list"
+)
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
index 0fea1892e..2348d6e9b 100644
--- a/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
+++ b/gso/workflows/l3_core_service/r_and_e_peer/import_r_and_e_peer.py
@@ -7,7 +7,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic_forms.types import State, UUIDstr
 
 from gso.products import ProductName
-from gso.products.product_types.r_and_e_peer import RAndEPeer, ImportedRAndEPeer
+from gso.products.product_types.r_and_e_peer import ImportedRAndEPeer, RAndEPeer
 from gso.services.partners import get_partner_by_id
 from gso.services.subscriptions import get_product_id_by_name
 
@@ -27,4 +27,11 @@ def import_r_and_e_peer_subscription(subscription_id: UUIDstr) -> State:
 @workflow("Import R&E Peer", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
 def import_r_and_e_peer() -> StepList:
     """Modify an imported subscription into an R&E Peer subscription to complete the import."""
-    return init >> store_process_subscription(Target.MODIFY) >> unsync >> import_r_and_e_peer_subscription >> resync >> done
+    return (
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> import_r_and_e_peer_subscription
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py
index 5a024e096..7d830113a 100644
--- a/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py
+++ b/gso/workflows/l3_core_service/r_and_e_peer/validate_r_and_e_peer_prefix_list.py
@@ -39,7 +39,7 @@ def validate_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr,
         "verb": "deploy",
         "object": "prefix_list",
         "is_verification_workflow": "true",
-        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription['description']}",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription["description"]}",
     }
 
     return {
@@ -78,7 +78,7 @@ def deploy_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, a
         "verb": "deploy",
         "object": "prefix_list",
         "is_verification_workflow": "false",
-        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription['description']}",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}",
     }
 
     return {
@@ -98,7 +98,7 @@ def deploy_prefix_lists_real(subscription: dict[str, Any], process_id: UUIDstr,
         "verb": "deploy",
         "object": "prefix_list",
         "is_verification_workflow": "false",
-        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription['description']}",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}",
     }
 
     return {
-- 
GitLab


From fdf25cb16b5017d75e89f0186b6f9371b5e5577e Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:31:44 +0200
Subject: [PATCH 07/19] Add R&E Peer product type to imports

---
 gso/cli/imports.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/gso/cli/imports.py b/gso/cli/imports.py
index 66c864594..9830ee218 100644
--- a/gso/cli/imports.py
+++ b/gso/cli/imports.py
@@ -700,6 +700,7 @@ def import_l3_core_service(filepath: str = common_filepath_option) -> None:
             ProductType.IMPORTED_IAS,
             ProductType.IMPORTED_LHCONE,
             ProductType.IMPORTED_COPERNICUS,
+            ProductType.IMPORTED_R_AND_E_PEER,
         ],
         lifecycles=[SubscriptionLifecycle.ACTIVE],
         includes=["subscription_id", "product_id"],
-- 
GitLab


From b7b0eac26fc6b7d421c08ee025554c5cd5916c8c Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 14:45:19 +0200
Subject: [PATCH 08/19] Add R&E LHCONE workflows for creation, modification,
 migration, termination, and validation

---
 gso/cli/imports.py                            |  1 +
 gso/workflows/__init__.py                     | 25 +++++++
 .../r_and_e_lhcone/__init__.py                |  1 +
 .../r_and_e_lhcone/create_imported_lhcone.py  | 43 ++++++++++++
 .../r_and_e_lhcone/create_r_and_e_lhcone.py   | 61 ++++++++++++++++
 .../r_and_e_lhcone/import_r_and_e_lhcone.py   | 37 ++++++++++
 .../r_and_e_lhcone/migrate_r_and_e_lhcone.py  | 69 +++++++++++++++++++
 .../r_and_e_lhcone/modify_r_and_e_lhcone.py   | 38 ++++++++++
 .../terminate_r_and_e_lhcone.py               | 42 +++++++++++
 .../r_and_e_lhcone/validate_r_and_e_lhcone.py | 30 ++++++++
 10 files changed, 347 insertions(+)
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/__init__.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/migrate_r_and_e_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/terminate_r_and_e_lhcone.py
 create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/validate_r_and_e_lhcone.py

diff --git a/gso/cli/imports.py b/gso/cli/imports.py
index 9830ee218..700a63b42 100644
--- a/gso/cli/imports.py
+++ b/gso/cli/imports.py
@@ -701,6 +701,7 @@ def import_l3_core_service(filepath: str = common_filepath_option) -> None:
             ProductType.IMPORTED_LHCONE,
             ProductType.IMPORTED_COPERNICUS,
             ProductType.IMPORTED_R_AND_E_PEER,
+            ProductType.IMPORTED_R_AND_E_LHCONE,
         ],
         lifecycles=[SubscriptionLifecycle.ACTIVE],
         includes=["subscription_id", "product_id"],
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 586857872..920823a5c 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -187,3 +187,28 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_
 LazyWorkflowInstance(
     "gso.workflows.l3_core_service.r_and_e_peer.validate_r_and_e_peer_prefix_list", "validate_r_and_e_peer_prefix_list"
 )
+
+#  R&E LHCONE workflows
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.create_r_and_e_lhcone", "create_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.modify_r_and_e_lhcone", "modify_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.terminate_r_and_e_lhcone",
+    "terminate_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.migrate_r_and_e_lhcone", "migrate_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.create_imported_r_and_e_lhcone",
+    "create_imported_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.import_r_and_e_lhcone", "import_r_and_e_lhcone"
+)
+LazyWorkflowInstance(
+    "gso.workflows.l3_core_service.r_and_e_lhcone.validate_r_and_e_lhcone", "validate_r_and_e_lhcone"
+)
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/__init__.py b/gso/workflows/l3_core_service/r_and_e_lhcone/__init__.py
new file mode 100644
index 000000000..01212adc1
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/__init__.py
@@ -0,0 +1 @@
+"""R&E LHCONE service workflows."""
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py
new file mode 100644
index 000000000..adc0fb663
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py
@@ -0,0 +1,43 @@
+"""A creation workflow for adding an existing Imported LHCOne to the service database."""
+
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+
+from gso.products import ProductName
+from gso.products.product_types.lhcone import ImportedLHCOneInactive
+from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import get_product_id_by_name
+from gso.workflows.l3_core_service.base_create_imported_l3_core_service import (
+    initial_input_form_generator,
+    initialize_subscription,
+)
+
+
+@step("Create subscription")
+def create_subscription(partner: str) -> dict:
+    """Create a new subscription object in the database."""
+    partner_id = get_partner_by_name(partner).partner_id
+    product_id = get_product_id_by_name(ProductName.IMPORTED_LHCONE)
+    subscription = ImportedLHCOneInactive.from_product_id(product_id, partner_id)
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@workflow(
+    "Create Imported LHCOne",
+    initial_input_form=initial_input_form_generator,
+    target=Target.CREATE,
+)
+def create_imported_lhcone() -> StepList:
+    """Import a LHCOne without provisioning it."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py
new file mode 100644
index 000000000..2063c415f
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py
@@ -0,0 +1,61 @@
+"""Create R&E LHCONE subscription workflow."""
+
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+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_forms.types import State, UUIDstr
+
+from gso.products.product_types.r_and_e_lhcone import RAndELHCOneInactive
+from gso.services.lso_client import lso_interaction
+from gso.utils.workflow_steps import prompt_sharepoint_checklist_url, start_moodi, stop_moodi
+from gso.workflows.l3_core_service.base_create_l3_core_service import (
+    check_bgp_peers,
+    check_sbp_functionality,
+    create_new_sharepoint_checklist,
+    deploy_bgp_peers_dry,
+    deploy_bgp_peers_real,
+    initial_input_form_generator,
+    initialize_subscription,
+    provision_sbp_dry,
+    provision_sbp_real,
+    update_dns_records,
+)
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr, partner: str) -> State:
+    """Create a new subscription object in the database."""
+    subscription = RAndELHCOneInactive.from_product_id(product, partner)
+
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@workflow(
+    "Create R&E LHCONE",
+    initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_r_and_e_lhcone() -> StepList:
+    """Create a new R&E LHCONE subscription."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> start_moodi()
+        >> lso_interaction(provision_sbp_dry)
+        >> lso_interaction(provision_sbp_real)
+        >> lso_interaction(check_sbp_functionality)
+        >> lso_interaction(deploy_bgp_peers_dry)
+        >> lso_interaction(deploy_bgp_peers_real)
+        >> lso_interaction(check_bgp_peers)
+        >> update_dns_records
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> create_new_sharepoint_checklist
+        >> prompt_sharepoint_checklist_url
+        >> stop_moodi()
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
new file mode 100644
index 000000000..94e6cf587
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
@@ -0,0 +1,37 @@
+"""A modification workflow for migrating an `ImportedRAndELHCOne` to an `RAndELHCOne` subscription."""
+
+from orchestrator.targets import Target
+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 pydantic_forms.types import State, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOne, RAndELHCOne
+from gso.services.partners import get_partner_by_id
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create an R&E LHCONE subscription")
+def import_r_and_e_lhcone_subscription(subscription_id: UUIDstr) -> State:
+    """Take an imported subscription, and turn it into an R&E LHCONE subscription."""
+    old_l3_core_service = ImportedRAndELHCOne.from_subscription(subscription_id)
+    new_product_id = get_product_id_by_name(ProductName.R_AND_E_LHCONE)
+    new_subscription = RAndELHCOne.from_other_product(old_l3_core_service, new_product_id)  # type: ignore[arg-type]
+    new_subscription.description = (
+        f"{new_subscription.product.name} service for {get_partner_by_id(new_subscription.customer_id).name}"
+    )
+    return {"subscription": new_subscription}
+
+
+@workflow("Import R&E LHCONE", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_r_and_e_lhcone() -> StepList:
+    """Modify an imported subscription into an R&E LHCONE subscription to complete the import."""
+    return (
+            init
+            >> store_process_subscription(Target.MODIFY)
+            >> unsync
+            >> import_r_and_e_lhcone_subscription
+            >> resync
+            >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/migrate_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/migrate_r_and_e_lhcone.py
new file mode 100644
index 000000000..d5d5a611a
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/migrate_r_and_e_lhcone.py
@@ -0,0 +1,69 @@
+"""A modification workflow that migrates an R&E LHCONE Service to a new Edge Port.
+
+In one run of a migration workflow, only a single Access Port can be replaced. This is due to the nature of these
+services. When a service is dually homed, for example, only one of the Access Ports should be migrated. The other one
+will remain the way it is.
+
+At the start of the workflow, the operator will select one Access Port that is to be migrated, and will then select a
+destination Edge Port that this service should be placed on. All other Access Ports will be left as-is.
+"""
+
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, conditional, done
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.services.lso_client import lso_interaction
+from gso.utils.workflow_steps import IS_HUMAN_INITIATED_WF_KEY, start_moodi, stop_moodi
+from gso.workflows.l3_core_service.base_migrate_l3_core_service import (
+    deactivate_bgp_dry,
+    deactivate_bgp_real,
+    deactivate_sbp_dry,
+    deactivate_sbp_real,
+    deploy_bgp_session_dry,
+    deploy_bgp_session_real,
+    deploy_destination_ep_dry,
+    deploy_destination_ep_real,
+    generate_scoped_subscription_model,
+    inform_operator_traffic_check,
+    initial_input_form,
+    inject_partner_name,
+    show_bgp_neighbors,
+    update_dns_records,
+    update_subscription_model,
+)
+
+
+@workflow(
+    "Migrate R&E LHCONE",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form),
+    target=Target.MODIFY,
+)
+def migrate_r_and_e_lhcone() -> StepList:
+    """Migrate an R&E LHCONE Service to a destination Edge Port."""
+    is_human_initiated_wf = conditional(lambda state: bool(state.get(IS_HUMAN_INITIATED_WF_KEY)))
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> inject_partner_name
+        >> is_human_initiated_wf(lso_interaction(show_bgp_neighbors))
+        >> is_human_initiated_wf(lso_interaction(deactivate_bgp_dry))
+        >> is_human_initiated_wf(lso_interaction(deactivate_bgp_real))
+        >> is_human_initiated_wf(lso_interaction(deactivate_sbp_dry))
+        >> is_human_initiated_wf(lso_interaction(deactivate_sbp_real))
+        >> is_human_initiated_wf(inform_operator_traffic_check)
+        >> unsync
+        >> generate_scoped_subscription_model
+        >> start_moodi()
+        >> lso_interaction(deploy_destination_ep_dry)
+        >> lso_interaction(deploy_destination_ep_real)
+        >> lso_interaction(deploy_bgp_session_dry)
+        >> lso_interaction(deploy_bgp_session_real)
+        >> update_dns_records
+        >> update_subscription_model
+        >> resync
+        >> stop_moodi()
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py
new file mode 100644
index 000000000..7182a48b2
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py
@@ -0,0 +1,38 @@
+"""Modification workflow for an R&E LHCONE subscription."""
+
+from orchestrator import begin, conditional, done, workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.workflows.l3_core_service.base_modify_l3_core_service import (
+    Operation,
+    create_new_sbp,
+    initial_input_form_generator,
+    modify_existing_sbp,
+    remove_old_sbp,
+)
+
+
+@workflow(
+    "Modify R&E LHCONE",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_r_and_e_lhcone() -> StepList:
+    """Modify R&E LHCONE subscription."""
+    access_port_is_added = conditional(lambda state: state["operation"] == Operation.ADD)
+    access_port_is_removed = conditional(lambda state: state["operation"] == Operation.REMOVE)
+    access_port_is_modified = conditional(lambda state: state["operation"] == Operation.EDIT)
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> access_port_is_added(create_new_sbp)
+        >> access_port_is_removed(remove_old_sbp)
+        >> access_port_is_modified(modify_existing_sbp)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/terminate_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/terminate_r_and_e_lhcone.py
new file mode 100644
index 000000000..5ac0216ce
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/terminate_r_and_e_lhcone.py
@@ -0,0 +1,42 @@
+"""Workflow for terminating an R&E LHCONE subscription."""
+
+from orchestrator import begin, workflow
+from orchestrator.forms import SubmitFormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, done
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic_forms.types import FormGenerator, UUIDstr
+
+from gso.products.product_types.r_and_e_lhcone import RAndELHCOne
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Initial input form generator for terminating an R&E LHCONE subscription."""
+    subscription = RAndELHCOne.from_subscription(subscription_id)
+
+    class TerminateForm(SubmitFormPage):
+        tt_number: TTNumber
+
+    user_input = yield TerminateForm
+
+    return {"subscription": subscription} | user_input.model_dump()
+
+
+@workflow(
+    "Terminate R&E LHCONE",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.TERMINATE,
+)
+def terminate_r_and_e_lhcone() -> StepList:
+    """Terminate an R&E LHCONE subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/validate_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/validate_r_and_e_lhcone.py
new file mode 100644
index 000000000..435188401
--- /dev/null
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/validate_r_and_e_lhcone.py
@@ -0,0 +1,30 @@
+"""Validation workflow for R&E LHCONE subscription objects."""
+
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.services.lso_client import anonymous_lso_interaction
+from gso.workflows.l3_core_service.base_validate_l3_core_service import (
+    build_fqdn_list,
+    validate_bgp_peers,
+    validate_dns_records,
+    validate_sbp_config,
+)
+
+
+@workflow("Validate R&E LHCONE", target=Target.SYSTEM, initial_input_form=(wrap_modify_initial_input_form(None)))
+def validate_r_and_e_lhcone() -> StepList:
+    """Validate an existing R&E LHCONE subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.SYSTEM)
+        >> unsync
+        >> build_fqdn_list
+        >> anonymous_lso_interaction(validate_sbp_config)
+        >> anonymous_lso_interaction(validate_bgp_peers)
+        >> validate_dns_records
+        >> resync
+        >> done
+    )
-- 
GitLab


From ae50fb408915f0cba29ac992016f8095f81a4f69 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 15:16:19 +0200
Subject: [PATCH 09/19] Make ruff happy

---
 gso/workflows/__init__.py                     | 26 +++++--------------
 .../r_and_e_lhcone/import_r_and_e_lhcone.py   | 12 ++++-----
 2 files changed, 13 insertions(+), 25 deletions(-)

diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 920823a5c..3456115a4 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -189,26 +189,14 @@ LazyWorkflowInstance(
 )
 
 #  R&E LHCONE workflows
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_lhcone.create_r_and_e_lhcone", "create_r_and_e_lhcone")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_lhcone.modify_r_and_e_lhcone", "modify_r_and_e_lhcone")
 LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.create_r_and_e_lhcone", "create_r_and_e_lhcone"
+    "gso.workflows.l3_core_service.r_and_e_lhcone.terminate_r_and_e_lhcone", "terminate_r_and_e_lhcone"
 )
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_lhcone.migrate_r_and_e_lhcone", "migrate_r_and_e_lhcone")
 LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.modify_r_and_e_lhcone", "modify_r_and_e_lhcone"
-)
-LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.terminate_r_and_e_lhcone",
-    "terminate_r_and_e_lhcone"
-)
-LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.migrate_r_and_e_lhcone", "migrate_r_and_e_lhcone"
-)
-LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.create_imported_r_and_e_lhcone",
-    "create_imported_r_and_e_lhcone"
-)
-LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.import_r_and_e_lhcone", "import_r_and_e_lhcone"
-)
-LazyWorkflowInstance(
-    "gso.workflows.l3_core_service.r_and_e_lhcone.validate_r_and_e_lhcone", "validate_r_and_e_lhcone"
+    "gso.workflows.l3_core_service.r_and_e_lhcone.create_imported_r_and_e_lhcone", "create_imported_r_and_e_lhcone"
 )
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_lhcone.import_r_and_e_lhcone", "import_r_and_e_lhcone")
+LazyWorkflowInstance("gso.workflows.l3_core_service.r_and_e_lhcone.validate_r_and_e_lhcone", "validate_r_and_e_lhcone")
diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
index 94e6cf587..d298e00a4 100644
--- a/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/import_r_and_e_lhcone.py
@@ -28,10 +28,10 @@ def import_r_and_e_lhcone_subscription(subscription_id: UUIDstr) -> State:
 def import_r_and_e_lhcone() -> StepList:
     """Modify an imported subscription into an R&E LHCONE subscription to complete the import."""
     return (
-            init
-            >> store_process_subscription(Target.MODIFY)
-            >> unsync
-            >> import_r_and_e_lhcone_subscription
-            >> resync
-            >> done
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> import_r_and_e_lhcone_subscription
+        >> resync
+        >> done
     )
-- 
GitLab


From 7e6d600fa2de59b0ab75ec8300e445e6adca3014 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 15:53:21 +0200
Subject: [PATCH 10/19] Add R&E Peer and LHCONE product types and workflows to
 shared.py

---
 gso/workflows/l3_core_service/shared.py | 27 ++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/gso/workflows/l3_core_service/shared.py b/gso/workflows/l3_core_service/shared.py
index 8370cb2b6..7489a23d3 100644
--- a/gso/workflows/l3_core_service/shared.py
+++ b/gso/workflows/l3_core_service/shared.py
@@ -7,14 +7,25 @@ from gso.products.product_types.copernicus import Copernicus
 from gso.products.product_types.geant_ip import GeantIP
 from gso.products.product_types.ias import IAS
 from gso.products.product_types.lhcone import LHCOne
+from gso.products.product_types.r_and_e_lhcone import RAndELHCOne
+from gso.products.product_types.r_and_e_peer import RAndEPeer
 
 L3_PRODUCT_NAMES = [
     ProductName.GEANT_IP,
     ProductName.IAS,
     ProductName.LHCONE,
     ProductName.COPERNICUS,
+    ProductName.R_AND_E_PEER,
+    ProductName.R_AND_E_LHCONE,
+]
+L3_CORE_SERVICE_PRODUCT_TYPES = [
+    GeantIP.__name__,
+    IAS.__name__,
+    LHCOne.__name__,
+    Copernicus.__name__,
+    RAndELHCOne.__name__,
+    RAndEPeer.__name__,
 ]
-L3_CORE_SERVICE_PRODUCT_TYPES = [GeantIP.__name__, IAS.__name__, LHCOne.__name__, Copernicus.__name__]
 assert len(L3_PRODUCT_NAMES) == len(  # noqa: S101
     L3_CORE_SERVICE_PRODUCT_TYPES
 ), "The number of L3 product names does not match the number of L3 core service product types."
@@ -26,6 +37,8 @@ L3_CREAT_IMPORTED_WF_MAP = {
     ProductName.GEANT_IP: "create_imported_geant_ip",
     ProductName.IAS: "create_imported_ias",
     ProductName.LHCONE: "create_imported_lhcone",
+    ProductName.R_AND_E_PEER: "create_imported_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "create_imported_r_and_e_lhcone",
 }
 
 L3_CREATION_WF_MAP = {
@@ -33,6 +46,8 @@ L3_CREATION_WF_MAP = {
     ProductName.GEANT_IP: "create_geant_ip",
     ProductName.IAS: "create_ias",
     ProductName.LHCONE: "create_lhcone",
+    ProductName.R_AND_E_PEER: "create_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "create_r_and_e_lhcone",
 }
 
 L3_MIGRATION_WF_MAP = {
@@ -40,6 +55,8 @@ L3_MIGRATION_WF_MAP = {
     ProductName.GEANT_IP: "migrate_geant_ip",
     ProductName.IAS: "migrate_ias",
     ProductName.LHCONE: "migrate_lhcone",
+    ProductName.R_AND_E_PEER: "migrate_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "migrate_r_and_e_lhcone",
 }
 
 
@@ -48,6 +65,8 @@ L3_MODIFICATION_WF_MAP = {
     ProductName.GEANT_IP: "modify_geant_ip",
     ProductName.IAS: "modify_ias",
     ProductName.LHCONE: "modify_lhcone",
+    ProductName.R_AND_E_PEER: "modify_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "modify_r_and_e_lhcone",
 }
 
 
@@ -56,6 +75,8 @@ L3_IMPORT_WF_MAP = {
     ProductName.IMPORTED_GEANT_IP: "import_geant_ip",
     ProductName.IMPORTED_IAS: "import_ias",
     ProductName.IMPORTED_LHCONE: "import_lhcone",
+    ProductName.IMPORTED_R_AND_E_PEER: "import_r_and_e_peer",
+    ProductName.IMPORTED_R_AND_E_LHCONE: "import_r_and_e_lhcone",
 }
 
 L3_VALIDATION_WF_MAP = {
@@ -63,6 +84,8 @@ L3_VALIDATION_WF_MAP = {
     ProductName.IAS: "validate_ias",
     ProductName.LHCONE: "validate_lhcone",
     ProductName.COPERNICUS: "validate_copernicus",
+    ProductName.R_AND_E_PEER: "validate_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "validate_r_and_e_lhcone",
 }
 
 L3_TERMINATION_WF_MAP = {
@@ -70,4 +93,6 @@ L3_TERMINATION_WF_MAP = {
     ProductName.GEANT_IP: "terminate_geant_ip",
     ProductName.IAS: "terminate_ias",
     ProductName.LHCONE: "terminate_lhcone",
+    ProductName.R_AND_E_PEER: "terminate_r_and_e_peer",
+    ProductName.R_AND_E_LHCONE: "terminate_r_and_e_lhcone",
 }
-- 
GitLab


From 06fb3eb9330b31f167ff58ce1d987a56dfa2f48f Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 16:00:31 +0200
Subject: [PATCH 11/19] Add R&E Peer and LHCONE entries to translation file

---
 gso/translations/en-GB.json | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 1a05f72f7..e3ea7bc6c 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -98,6 +98,8 @@
         "create_imported_site": "NOT FOR HUMANS -- Import existing site",
         "create_imported_super_pop_switch": "NOT FOR HUMANS -- Import existing super PoP switch",
         "create_imported_switch": "NOT FOR HUMANS -- Import existing Switch",
+        "create_imported_r_and_e_peer": "NOT FOR HUMANS -- Import existing R&E Peer",
+        "create_imported_r_and_e_lhcone": "NOT FOR HUMANS -- Import existing R&E LHCONE",
         "create_iptrunk": "Create IP Trunk",
         "create_geant_ip": "Create GÉANT IP",
         "create_lhcone": "Create LHCOne",
@@ -109,6 +111,8 @@
         "create_site": "Create Site",
         "create_switch": "Create Switch",
         "create_vrf": "Create VRF",
+        "create_r_and_e_peer": "Create R&E Peer",
+        "create_r_and_e_lhcone": "Create R&E LHCONE",
         "deploy_twamp": "Deploy TWAMP",
         "import_edge_port": "NOT FOR HUMANS -- Finalize import into an Edge Port",
         "import_iptrunk": "NOT FOR HUMANS -- Finalize import into an IP trunk product",
@@ -124,11 +128,15 @@
         "import_site": "NOT FOR HUMANS -- Finalize import into a Site product",
         "import_super_pop_switch": "NOT FOR HUMANS -- Finalize import into a Super PoP switch",
         "import_switch": "NOT FOR HUMANS -- Finalize import into a Switch",
+        "import_r_and_e_peer": "NOT FOR HUMANS -- Finalize import into a R&E Peer",
+        "import_r_and_e_lhcone": "NOT FOR HUMANS -- Finalize import into a R&E LHCONE",
         "migrate_edge_port": "Migrate Edge Port",
         "migrate_iptrunk": "Migrate IP Trunk",
         "migrate_layer_2_circuit": "Migrate Layer 2 Circuit",
         "migrate_ias": "Migrate IAS",
         "migrate_lhcone": "Migrate LHCOne",
+        "migrate_r_and_e_peer": "Migrate R&E Peer",
+        "migrate_r_and_e_lhcone": "Migrate R&E LHCOne",
         "migrate_copernicus": "Migrate Copernicus",
         "migrate_geant_ip": "Migrate GÉANT IP",
         "modify_connection_strategy": "Modify connection strategy",
@@ -143,6 +151,8 @@
         "modify_site": "Modify Site",
         "modify_trunk_interface": "Modify IP Trunk interface",
         "modify_vrf_router_list": "Modify VRF router list",
+        "modify_r_and_e_peer": "Modify R&E Peer",
+        "modify_r_and_e_lhcone": "Modify R&E LHCONE",
         "promote_p_to_pe": "Promote P to PE",
         "redeploy_base_config": "Redeploy base config",
         "redeploy_vrf": "Redeploy VRF router list",
@@ -166,6 +176,8 @@
         "terminate_site": "Terminate Site",
         "terminate_switch": "Terminate Switch",
         "terminate_vrf": "Terminate VRF",
+        "terminate_r_and_e_peer": "Terminate R&E Peer",
+        "terminate_r_and_e_lhcone": "Terminate R&E LHCONE",
         "update_ibgp_mesh": "Update iBGP mesh",
         "validate_edge_port": "Validate Edge Port",
         "validate_iptrunk": "Validate IP Trunk configuration",
@@ -176,6 +188,9 @@
         "validate_lan_switch_interconnect": "Validate LAN Switch Interconnect",
         "validate_geant_ip_prefix_list": "Validate GÉANT IP Prefix-List",
         "validate_router": "Validate Router configuration",
-        "validate_switch": "Validate Switch configuration"
+        "validate_switch": "Validate Switch configuration",
+        "validate_r_and_e_peer": "Validate R&E Peer",
+        "validate_r_and_e_lhcone": "Validate R&E LHCONE",
+        "validate_r_and_e_peer_prefix_list": "Validate R&E Peer Prefix-List"
     }
 }
-- 
GitLab


From 17934c66217c79ba42a37100f89a37caaa94b962 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 16:11:29 +0200
Subject: [PATCH 12/19] Rename LHCOne workflow to R&E LHCONE and update related
 references

---
 ...lhcone.py => create_imported_r_and_e_lhcone.py} | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
 rename gso/workflows/l3_core_service/r_and_e_lhcone/{create_imported_lhcone.py => create_imported_r_and_e_lhcone.py} (71%)

diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py
similarity index 71%
rename from gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py
rename to gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py
index adc0fb663..a4447da01 100644
--- a/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_lhcone.py
+++ b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py
@@ -1,4 +1,4 @@
-"""A creation workflow for adding an existing Imported LHCOne to the service database."""
+"""A creation workflow for adding an existing Imported R&E LHCONE to the service database."""
 
 from orchestrator import workflow
 from orchestrator.targets import Target
@@ -7,7 +7,7 @@ from orchestrator.workflow import StepList, begin, done, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
 from gso.products import ProductName
-from gso.products.product_types.lhcone import ImportedLHCOneInactive
+from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOneInactive
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import get_product_id_by_name
 from gso.workflows.l3_core_service.base_create_imported_l3_core_service import (
@@ -20,18 +20,18 @@ from gso.workflows.l3_core_service.base_create_imported_l3_core_service import (
 def create_subscription(partner: str) -> dict:
     """Create a new subscription object in the database."""
     partner_id = get_partner_by_name(partner).partner_id
-    product_id = get_product_id_by_name(ProductName.IMPORTED_LHCONE)
-    subscription = ImportedLHCOneInactive.from_product_id(product_id, partner_id)
+    product_id = get_product_id_by_name(ProductName.IMPORTED_R_AND_E_LHCONE)
+    subscription = ImportedRAndELHCOneInactive.from_product_id(product_id, partner_id)
     return {"subscription": subscription, "subscription_id": subscription.subscription_id}
 
 
 @workflow(
-    "Create Imported LHCOne",
+    "Create Imported R&E LHCONE",
     initial_input_form=initial_input_form_generator,
     target=Target.CREATE,
 )
-def create_imported_lhcone() -> StepList:
-    """Import a LHCOne without provisioning it."""
+def create_imported_r_and_e_lhcone() -> StepList:
+    """Import an R&E LHCONE without provisioning it."""
     return (
         begin
         >> create_subscription
-- 
GitLab


From 85baac0a524cfc469590191375ccc0e8dac562ac Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 4 Jun 2025 16:12:15 +0200
Subject: [PATCH 13/19] Add R&E LHCONE workflows migration

---
 ...5-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py

diff --git a/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py b/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py
new file mode 100644
index 000000000..a394ae108
--- /dev/null
+++ b/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py
@@ -0,0 +1,75 @@
+"""Add R&E LHCONE WFs.
+
+Revision ID: d23b59abc6a5
+Revises: 29813f5b2c95
+Create Date: 2025-06-04 16:08:56.288635
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'd23b59abc6a5'
+down_revision = '29813f5b2c95'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_r_and_e_lhcone",
+        "target": "CREATE",
+        "description": "Create R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "modify_r_and_e_lhcone",
+        "target": "MODIFY",
+        "description": "Modify R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "terminate_r_and_e_lhcone",
+        "target": "TERMINATE",
+        "description": "Terminate R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "migrate_r_and_e_lhcone",
+        "target": "MODIFY",
+        "description": "Migrate R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "create_imported_r_and_e_lhcone",
+        "target": "CREATE",
+        "description": "Create Imported R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "import_r_and_e_lhcone",
+        "target": "MODIFY",
+        "description": "Import R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    },
+    {
+        "name": "validate_r_and_e_lhcone",
+        "target": "SYSTEM",
+        "description": "Validate R&E LHCONE",
+        "product_type": "RAndELHCOne"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
-- 
GitLab


From d5d81b298df629b7052663a250a765f618a9f27c Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 6 Jun 2025 09:47:23 +0200
Subject: [PATCH 14/19] Add R&E Peer and R&E LHCONE to L3 product name types

---
 gso/workflows/l3_core_service/shared.py | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/gso/workflows/l3_core_service/shared.py b/gso/workflows/l3_core_service/shared.py
index 7489a23d3..28ec9cf2b 100644
--- a/gso/workflows/l3_core_service/shared.py
+++ b/gso/workflows/l3_core_service/shared.py
@@ -30,7 +30,14 @@ assert len(L3_PRODUCT_NAMES) == len(  # noqa: S101
     L3_CORE_SERVICE_PRODUCT_TYPES
 ), "The number of L3 product names does not match the number of L3 core service product types."
 
-L3ProductNameType = Literal[ProductName.IAS, ProductName.LHCONE, ProductName.COPERNICUS, ProductName.GEANT_IP]
+L3ProductNameType = Literal[
+    ProductName.IAS,
+    ProductName.LHCONE,
+    ProductName.COPERNICUS,
+    ProductName.GEANT_IP,
+    ProductName.R_AND_E_PEER,
+    ProductName.R_AND_E_LHCONE,
+]
 
 L3_CREAT_IMPORTED_WF_MAP = {
     ProductName.COPERNICUS: "create_imported_copernicus",
-- 
GitLab


From 627ee59230945016c7eca6e82d985621e712b9cf Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 6 Jun 2025 10:14:23 +0200
Subject: [PATCH 15/19] Add R&E Peer and R&E LHCONE mappings to service
 fixtures

---
 gso/workflows/l3_core_service/shared.py   | 2 +-
 test/fixtures/l3_core_service_fixtures.py | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/gso/workflows/l3_core_service/shared.py b/gso/workflows/l3_core_service/shared.py
index 28ec9cf2b..cf5320d26 100644
--- a/gso/workflows/l3_core_service/shared.py
+++ b/gso/workflows/l3_core_service/shared.py
@@ -23,8 +23,8 @@ L3_CORE_SERVICE_PRODUCT_TYPES = [
     IAS.__name__,
     LHCOne.__name__,
     Copernicus.__name__,
-    RAndELHCOne.__name__,
     RAndEPeer.__name__,
+    RAndELHCOne.__name__,
 ]
 assert len(L3_PRODUCT_NAMES) == len(  # noqa: S101
     L3_CORE_SERVICE_PRODUCT_TYPES
diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py
index d23bc4297..c81bb1ea0 100644
--- a/test/fixtures/l3_core_service_fixtures.py
+++ b/test/fixtures/l3_core_service_fixtures.py
@@ -28,6 +28,8 @@ PRODUCT_IMPORTED_MAP = {
     ProductName.GEANT_IP: ProductName.IMPORTED_GEANT_IP,
     ProductName.COPERNICUS: ProductName.IMPORTED_COPERNICUS,
     ProductName.LHCONE: ProductName.IMPORTED_LHCONE,
+    ProductName.R_AND_E_PEER: ProductName.IMPORTED_R_AND_E_PEER,
+    ProductName.R_AND_E_LHCONE: ProductName.IMPORTED_R_AND_E_LHCONE,
 }
 
 
-- 
GitLab


From 8fa4ad0c36ca67872bb7d553d3100be75256d8b1 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 6 Jun 2025 10:52:31 +0200
Subject: [PATCH 16/19] Add R&E Peer and R&E LHCONE subscription factories to
 service fixtures

---
 test/fixtures/__init__.py                 |  4 +++
 test/fixtures/l3_core_service_fixtures.py | 38 +++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/test/fixtures/__init__.py b/test/fixtures/__init__.py
index b2ee468ff..5fa18f013 100644
--- a/test/fixtures/__init__.py
+++ b/test/fixtures/__init__.py
@@ -11,6 +11,8 @@ from test.fixtures.l3_core_service_fixtures import (
     l3_core_service_subscription_factory,
     lhcone_subscription_factory,
     make_subscription_factory,
+    r_and_e_lhcone_subscription_factory,
+    r_and_e_peer_subscription_factory,
     save_l3_core_subscription,
     service_binding_port_factory,
 )
@@ -42,6 +44,8 @@ __all__ = [
     "make_subscription_factory",
     "office_router_subscription_factory",
     "opengear_subscription_factory",
+    "r_and_e_lhcone_subscription_factory",
+    "r_and_e_peer_subscription_factory",
     "router_subscription_factory",
     "save_l3_core_subscription",
     "service_binding_port_factory",
diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py
index c81bb1ea0..bac0fbc3c 100644
--- a/test/fixtures/l3_core_service_fixtures.py
+++ b/test/fixtures/l3_core_service_fixtures.py
@@ -19,6 +19,8 @@ from gso.products.product_types.edge_port import EdgePort
 from gso.products.product_types.geant_ip import GeantIPInactive, ImportedGeantIPInactive
 from gso.products.product_types.ias import IASInactive, ImportedIASInactive
 from gso.products.product_types.lhcone import ImportedLHCOneInactive, LHCOneInactive
+from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOneInactive, RAndELHCOneInactive
+from gso.products.product_types.r_and_e_peer import ImportedRAndEPeerInactive, RAndEPeerInactive
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.shared_enums import APType, SBPType
 from gso.utils.types.ip_address import IPAddress
@@ -290,12 +292,42 @@ def lhcone_subscription_factory(make_subscription_factory):
     return factory
 
 
+@pytest.fixture()
+def r_and_e_peer_subscription_factory(make_subscription_factory):
+    def factory(*args, **kwargs):
+        return make_subscription_factory(
+            product_name=ProductName.R_AND_E_PEER,
+            imported_class=ImportedRAndEPeerInactive,
+            native_class=RAndEPeerInactive,
+            *args,  # noqa: B026
+            **kwargs,
+        )
+
+    return factory
+
+
+@pytest.fixture()
+def r_and_e_lhcone_subscription_factory(make_subscription_factory):
+    def factory(*args, **kwargs):
+        return make_subscription_factory(
+            product_name=ProductName.R_AND_E_LHCONE,
+            imported_class=ImportedRAndELHCOneInactive,
+            native_class=RAndELHCOneInactive,
+            *args,  # noqa: B026
+            **kwargs,
+        )
+
+    return factory
+
+
 @pytest.fixture()
 def l3_core_service_subscription_factory(
     ias_subscription_factory,
     geant_ip_subscription_factory,
     copernicus_subscription_factory,
     lhcone_subscription_factory,
+    r_and_e_peer_subscription_factory,
+    r_and_e_lhcone_subscription_factory,
 ) -> callable:
     def factory(product_name: ProductName, *args, **kwargs):
         if product_name == ProductName.IAS:
@@ -307,6 +339,12 @@ def l3_core_service_subscription_factory(
         if product_name == ProductName.COPERNICUS:
             return copernicus_subscription_factory(*args, **kwargs)
 
+        if product_name == ProductName.R_AND_E_PEER:
+            return r_and_e_peer_subscription_factory(*args, **kwargs)
+
+        if product_name == ProductName.R_AND_E_LHCONE:
+            return r_and_e_lhcone_subscription_factory(*args, **kwargs)
+
         return lhcone_subscription_factory(*args, **kwargs)
 
     return factory
-- 
GitLab


From fb538fe941f53aadfb27017177ad2685866a3e86 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 6 Jun 2025 13:10:14 +0200
Subject: [PATCH 17/19] Fix migrations for R&E Peer and R&E LHCONE WFs

---
 .../2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py    | 4 ++--
 .../versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py    | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py b/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py
index 4da549af4..2615003ea 100644
--- a/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py
+++ b/gso/migrations/versions/2025-06-04_29813f5b2c95_add_r_and_e_peers_workflows.py
@@ -46,13 +46,13 @@ new_workflows = [
         "name": "create_imported_r_and_e_peer",
         "target": "CREATE",
         "description": "Create Imported R&E Peer",
-        "product_type": "RAndEPeer"
+        "product_type": "ImportedRAndEPeer"
     },
     {
         "name": "import_r_and_e_peer",
         "target": "MODIFY",
         "description": "Import R&E Peer",
-        "product_type": "RAndEPeer"
+        "product_type": "ImportedRAndEPeer"
     },
     {
         "name": "validate_r_and_e_peer",
diff --git a/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py b/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py
index a394ae108..5924c3abf 100644
--- a/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py
+++ b/gso/migrations/versions/2025-06-04_d23b59abc6a5_add_r_e_lhcone_wfs.py
@@ -46,13 +46,13 @@ new_workflows = [
         "name": "create_imported_r_and_e_lhcone",
         "target": "CREATE",
         "description": "Create Imported R&E LHCONE",
-        "product_type": "RAndELHCOne"
+        "product_type": "ImportedRAndELHCOne"
     },
     {
         "name": "import_r_and_e_lhcone",
         "target": "MODIFY",
         "description": "Import R&E LHCONE",
-        "product_type": "RAndELHCOne"
+        "product_type": "ImportedRAndELHCOne"
     },
     {
         "name": "validate_r_and_e_lhcone",
-- 
GitLab


From 367c748ad7e3d438e2ba7f7837ec9673adc53c4a Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 16 Jun 2025 14:10:04 +0200
Subject: [PATCH 18/19] Update docstrings to reference R&E Peer attribute for
 l3_core property

---
 gso/products/product_types/r_and_e_peer.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/gso/products/product_types/r_and_e_peer.py b/gso/products/product_types/r_and_e_peer.py
index 8cd221875..6ea04f374 100644
--- a/gso/products/product_types/r_and_e_peer.py
+++ b/gso/products/product_types/r_and_e_peer.py
@@ -26,12 +26,12 @@ class RAndEPeerInactive(BaseL3SubscriptionModel, is_base=True):
 
     @property
     def l3_core(self) -> L3CoreServiceBlockInactive:
-        """Getter: Retrieve the l3_core from the r_and_e_peer attribute."""
+        """Getter: Retrieve the l3_core from the R&E Peer attribute."""
         return self.r_and_e_peer.l3_core
 
     @l3_core.setter
     def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
-        """Setter: Set the l3_core on the r_and_e_peer attribute."""
+        """Setter: Set the l3_core on the R&E Peer attribute."""
         self.r_and_e_peer.l3_core = value
 
     @property
@@ -59,12 +59,12 @@ class ImportedRAndEPeerInactive(BaseL3SubscriptionModel, is_base=True):
 
     @property
     def l3_core(self) -> L3CoreServiceBlockInactive:
-        """Getter: Retrieve the l3_core from the r_and_e_peer attribute."""
+        """Getter: Retrieve the l3_core from the R&E Peer attribute."""
         return self.r_and_e_peer.l3_core
 
     @l3_core.setter
     def l3_core(self, value: L3CoreServiceBlockInactive) -> None:
-        """Setter: Set the l3_core on the r_and_e_peer attribute."""
+        """Setter: Set the l3_core on the R&E Peer attribute."""
         self.r_and_e_peer.l3_core = value
 
     @property
-- 
GitLab


From a201214729db735e1e1038ec2c797b003ed6ff9e Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 16 Jun 2025 14:13:09 +0200
Subject: [PATCH 19/19] Updated migrations order

---
 .../2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py b/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py
index 00a20f107..801929137 100644
--- a/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py
+++ b/gso/migrations/versions/2025-06-04_1233b83e1124_add_r_e_peer_and_r_e_lhcone.py
@@ -1,7 +1,7 @@
 """Add R&E peer and R&E LHCONE.
 
 Revision ID: 1233b83e1124
-Revises: 90547df711c3
+Revises: 9a7bae1f6438
 Create Date: 2025-06-04 13:37:22.122645
 
 """
@@ -10,7 +10,7 @@ from alembic import op
 
 # revision identifiers, used by Alembic.
 revision = '1233b83e1124'
-down_revision = '90547df711c3'
+down_revision = '9a7bae1f6438'
 branch_labels = None
 depends_on = None
 
-- 
GitLab