From 16db8ff6400c0034e577a0ad1f66f8b12135e3a6 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Thu, 9 Nov 2023 15:51:49 +0100
Subject: [PATCH] Initial update for the router model

---
 gso/api/v1/imports.py                         |  3 +-
 ...-14_a6eefd32c4f7_add_ip_trunk_workflows.py |  2 +-
 ...0bc55e5be3e_modify_router_product_model.py | 83 +++++++++++++++++++
 ...1-09_7f46df0f4f95_add_router_workflows.py} | 12 +--
 gso/products/__init__.py                      | 10 ++-
 gso/services/subscriptions.py                 |  4 +-
 gso/utils/helpers.py                          | 19 ++---
 gso/workflows/iptrunk/create_iptrunk.py       |  2 +-
 gso/workflows/iptrunk/migrate_iptrunk.py      | 14 ++--
 .../iptrunk/modify_trunk_interface.py         |  7 +-
 gso/workflows/iptrunk/terminate_iptrunk.py    |  7 +-
 gso/workflows/router/create_router.py         | 10 +--
 gso/workflows/router/terminate_router.py      |  5 +-
 gso/workflows/tasks/import_router.py          | 16 ++--
 test/fixtures.py                              | 12 ++-
 test/imports/conftest.py                      |  2 +-
 test/imports/test_imports.py                  |  9 +-
 test/subscriptions/conftest.py                |  2 +-
 test/subscriptions/test_subscriptions.py      | 12 +--
 test/workflows/conftest.py                    |  2 +-
 test/workflows/iptrunk/test_create_iptrunk.py |  6 +-
 .../workflows/iptrunk/test_migrate_iptrunk.py |  4 +-
 test/workflows/router/test_create_router.py   | 14 ++--
 .../workflows/router/test_terminate_router.py |  4 +-
 24 files changed, 175 insertions(+), 86 deletions(-)
 create mode 100644 gso/migrations/versions/2023-11-08_30bc55e5be3e_modify_router_product_model.py
 rename gso/migrations/versions/{2023-08-14_3657611f0dfc_add_router_workflows.py => 2023-11-09_7f46df0f4f95_add_router_workflows.py} (82%)

diff --git a/gso/api/v1/imports.py b/gso/api/v1/imports.py
index 12524e1e..1d14b689 100644
--- a/gso/api/v1/imports.py
+++ b/gso/api/v1/imports.py
@@ -12,8 +12,9 @@ from pydantic import BaseModel, root_validator, validator
 from pydantic.fields import ModelField
 
 from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity
-from gso.products.product_blocks.router import RouterRole, RouterVendor
+from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
+from gso.products.product_types.router import RouterVendor
 from gso.services import subscriptions
 from gso.services.crm import CustomerNotFoundError, get_customer_by_name
 from gso.utils.helpers import (
diff --git a/gso/migrations/versions/2023-08-14_a6eefd32c4f7_add_ip_trunk_workflows.py b/gso/migrations/versions/2023-08-14_a6eefd32c4f7_add_ip_trunk_workflows.py
index b341eb7c..80050dc7 100644
--- a/gso/migrations/versions/2023-08-14_a6eefd32c4f7_add_ip_trunk_workflows.py
+++ b/gso/migrations/versions/2023-08-14_a6eefd32c4f7_add_ip_trunk_workflows.py
@@ -10,7 +10,7 @@ from alembic import op
 
 # revision identifiers, used by Alembic.
 revision = 'a6eefd32c4f7'
-down_revision = '3657611f0dfc'
+down_revision = '91047dd30b40'
 branch_labels = None
 depends_on = None
 
diff --git a/gso/migrations/versions/2023-11-08_30bc55e5be3e_modify_router_product_model.py b/gso/migrations/versions/2023-11-08_30bc55e5be3e_modify_router_product_model.py
new file mode 100644
index 00000000..db514e11
--- /dev/null
+++ b/gso/migrations/versions/2023-11-08_30bc55e5be3e_modify_router_product_model.py
@@ -0,0 +1,83 @@
+"""Modify Router product model.
+
+Revision ID: 30bc55e5be3e
+Revises: 259c320235f5
+Create Date: 2023-11-08 13:58:21.149708
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '30bc55e5be3e'
+down_revision = '259c320235f5'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('router_vendor'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('router_vendor'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('router_vendor'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('router_vendor')
+    """))
+    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 ('Router'))))
+    """))
+    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 ('Router')))
+    """))
+    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 ('Router')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Router'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('Router')
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Nokia router', 'A Nokia router', 'Router', 'NOKIA_RT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Juniper router', 'A Juniper router', 'Router', 'JUNIPER_RT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO fixed_inputs (name, value, product_id) VALUES ('vendor', 'nokia', (SELECT products.product_id FROM products WHERE products.name IN ('Nokia router'))), ('vendor', 'juniper', (SELECT products.product_id FROM products WHERE products.name IN ('Juniper router')))
+    """))
+    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 ('Nokia router')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Juniper router')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock')))
+    """))
+
+
+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 ('Nokia router', 'Juniper router')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM fixed_inputs WHERE fixed_inputs.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Nokia router', 'Juniper router')) AND fixed_inputs.name = 'vendor'
+    """))
+    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 ('Nokia router', 'Juniper router'))))
+    """))
+    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 ('Nokia router', 'Juniper router')))
+    """))
+    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 ('Nokia router', 'Juniper router')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Nokia router', 'Juniper router'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('Nokia router', 'Juniper router')
+    """))
diff --git a/gso/migrations/versions/2023-08-14_3657611f0dfc_add_router_workflows.py b/gso/migrations/versions/2023-11-09_7f46df0f4f95_add_router_workflows.py
similarity index 82%
rename from gso/migrations/versions/2023-08-14_3657611f0dfc_add_router_workflows.py
rename to gso/migrations/versions/2023-11-09_7f46df0f4f95_add_router_workflows.py
index 153d5433..df3dbf78 100644
--- a/gso/migrations/versions/2023-08-14_3657611f0dfc_add_router_workflows.py
+++ b/gso/migrations/versions/2023-11-09_7f46df0f4f95_add_router_workflows.py
@@ -1,16 +1,16 @@
-"""Add Router workflows.
+"""Add router workflows.
 
-Revision ID: 3657611f0dfc
-Revises: 91047dd30b40
-Create Date: 2023-08-14 15:44:25.616608
+Revision ID: 7f46df0f4f95
+Revises: 30bc55e5be3e
+Create Date: 2023-11-09 14:18:38.705753
 
 """
 import sqlalchemy as sa
 from alembic import op
 
 # revision identifiers, used by Alembic.
-revision = '3657611f0dfc'
-down_revision = '91047dd30b40'
+revision = '7f46df0f4f95'
+down_revision = '30bc55e5be3e'
 branch_labels = None
 depends_on = None
 
diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 74f8fa15..37cf18d2 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -1,8 +1,8 @@
 """Module that updates the domain model of :term:`GSO`. Should contain all types of subscriptions.
 
 .. warning::
-   Whenever a new product type is added, this should be reflected in the :py:class:`gso.products.ProductType`
-   enumerator.
+   Whenever a new product is added, this should be reflected in the :py:class:`gso.products.ProductType` enumerator.
+   This does not hold for adding a new type of already existing product.
 """
 
 from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY
@@ -17,14 +17,16 @@ class ProductType(strEnum):
     """An enumerator of available products in :term:`GSO`."""
 
     SITE = "Site"
-    ROUTER = "Router"
+    NOKIA_ROUTER = "Nokia router"
+    JUNIPER_ROUTER = "Juniper router"
     IP_TRUNK = "IP trunk"
 
 
 SUBSCRIPTION_MODEL_REGISTRY.update(
     {
         "Site": Site,
-        "Router": Router,
+        "Nokia router": Router,
+        "Juniper router": Router,
         "IP trunk": Iptrunk,
     },
 )
diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py
index 3f3bcc8a..a60ef997 100644
--- a/gso/services/subscriptions.py
+++ b/gso/services/subscriptions.py
@@ -82,7 +82,9 @@ def get_active_router_subscriptions(
     :return: A list of Subscription objects for routers.
     :rtype: list[Subscription]
     """
-    return get_active_subscriptions(product_type=ProductType.ROUTER, includes=includes)
+    active_nokia_routers = get_active_subscriptions(product_type=ProductType.NOKIA_ROUTER, includes=includes)
+    active_juniper_routers = get_active_subscriptions(product_type=ProductType.JUNIPER_ROUTER, includes=includes)
+    return active_nokia_routers + active_juniper_routers
 
 
 def get_product_id_by_name(product_name: ProductType) -> UUID:
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index 7cd8540a..93f77f9e 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -12,9 +12,8 @@ from pydantic import BaseModel
 from pydantic_forms.validators import Choice
 
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
-from gso.products.product_blocks.router import RouterVendor
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.products.product_types.router import Router
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services import provisioning_proxy
 from gso.services.netbox_client import NetboxClient
 from gso.services.subscriptions import get_active_subscriptions_by_field_and_value
@@ -63,7 +62,7 @@ def available_interfaces_choices(router_id: UUID, speed: str) -> Choice | None:
     For Nokia routers, return a list of available interfaces.
     For Juniper routers, return a string.
     """
-    if Router.from_subscription(router_id).router.router_vendor != RouterVendor.NOKIA:
+    if get_router_vendor(router_id) != RouterVendor.NOKIA:
         return None
     interfaces = {
         interface["name"]: f"{interface['name']} - {interface['module']['display']} - {interface['description']}"
@@ -82,7 +81,7 @@ def available_interfaces_choices_including_current_members(
     For Nokia routers, return a list of available interfaces.
     For Juniper routers, return a string.
     """
-    if Router.from_subscription(router_id).router.router_vendor != RouterVendor.NOKIA:
+    if get_router_vendor(router_id) != RouterVendor.NOKIA:
         return None
 
     available_interfaces = list(NetboxClient().get_available_interfaces(router_id, speed))
@@ -106,9 +105,9 @@ def available_lags_choices(router_id: UUID) -> Choice | None:
     """Return a list of available lags for a given router.
 
     For Nokia routers, return a list of available lags.
-    For Juniper routers, return a string.
+    For Juniper routers, return ``None``.
     """
-    if Router.from_subscription(router_id).router.router_vendor != RouterVendor.NOKIA:
+    if get_router_vendor(router_id) != RouterVendor.NOKIA:
         return None
     side_a_ae_iface_list = NetboxClient().get_available_lags(router_id)
     return Choice("ae iface", zip(side_a_ae_iface_list, side_a_ae_iface_list, strict=True))  # type: ignore[arg-type]
@@ -123,7 +122,7 @@ def get_router_vendor(router_id: UUID) -> str:
     :return: The vendor of the router.
     :rtype: str:
     """
-    return Router.from_subscription(router_id).router.router_vendor
+    return Router.from_subscription(router_id).vendor
 
 
 def iso_from_ipv4(ipv4_address: IPv4Address) -> str:
@@ -149,9 +148,9 @@ def validate_router_in_netbox(subscription_id: UUIDstr) -> UUIDstr:
     :return: The :term:`UUID` of the router subscription.
     :rtype: :class:`UUIDstr`
     """
-    router = Router.from_subscription(subscription_id).router
-    if router.router_vendor == RouterVendor.NOKIA:
-        device = NetboxClient().get_device_by_name(router.router_fqdn)
+    router_type = Router.from_subscription(subscription_id)
+    if router_type.vendor == RouterVendor.NOKIA:
+        device = NetboxClient().get_device_by_name(router_type.router.router_fqdn)
         if not device:
             msg = "The selected router does not exist in Netbox."
             raise ValueError(msg)
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index 169f940e..2f3cd527 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -19,7 +19,7 @@ from gso.products.product_blocks.iptrunk import (
 )
 from gso.products.product_blocks.router import RouterVendor
 from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning
-from gso.products.product_types.router import Router
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services import infoblox, provisioning_proxy, subscriptions
 from gso.services.crm import customer_selector
 from gso.services.netbox_client import NetboxClient
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index f5fb625f..38374103 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -24,9 +24,8 @@ from pydantic_forms.core import ReadOnlyField
 from pynetbox.models.dcim import Interfaces
 
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
-from gso.products.product_blocks.router import RouterVendor
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.products.product_types.router import Router
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services import provisioning_proxy
 from gso.services.netbox_client import NetboxClient
 from gso.services.provisioning_proxy import pp_interaction
@@ -494,15 +493,16 @@ def reserve_interfaces_in_netbox(
     new_lag_member_interfaces: list[dict],
 ) -> State:
     """Reserve new interfaces in Netbox."""
-    new_side = Router.from_subscription(new_node).router
+    new_side_type = Router.from_subscription(new_node)
+    new_side_router = new_side_type.router
 
     nbclient = NetboxClient()
-    if new_side.router_vendor == RouterVendor.NOKIA:
+    if new_side_type.vendor == RouterVendor.NOKIA:
         # Create :term:`LAG` interfaces
         lag_interface: Interfaces = nbclient.create_interface(
             iface_name=new_lag_interface,
             interface_type="lag",
-            device_name=new_side.router_fqdn,
+            device_name=new_side_router.router_fqdn,
             description=str(subscription.subscription_id),
             enabled=True,
         )
@@ -510,13 +510,13 @@ def reserve_interfaces_in_netbox(
         # Reserve interfaces
         for interface in new_lag_member_interfaces:
             nbclient.attach_interface_to_lag(
-                device_name=new_side.router_fqdn,
+                device_name=new_side_router.router_fqdn,
                 lag_name=lag_interface.name,
                 iface_name=interface["interface_name"],
                 description=str(subscription.subscription_id),
             )
             nbclient.reserve_interface(
-                device_name=new_side.router_fqdn,
+                device_name=new_side_router.router_fqdn,
                 iface_name=interface["interface_name"],
             )
     return {"subscription": subscription}
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index 64265dde..d568ba12 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -18,8 +18,8 @@ from gso.products.product_blocks.iptrunk import (
     IptrunkType,
     PhyPortCapacity,
 )
-from gso.products.product_blocks.router import RouterVendor
 from gso.products.product_types.iptrunk import Iptrunk
+from gso.products.product_types.router import RouterVendor
 from gso.services import provisioning_proxy
 from gso.services.netbox_client import NetboxClient
 from gso.services.provisioning_proxy import pp_interaction
@@ -27,15 +27,16 @@ from gso.utils.helpers import (
     LAGMember,
     available_interfaces_choices,
     available_interfaces_choices_including_current_members,
-    validate_iptrunk_unique_interface,
+    validate_iptrunk_unique_interface, get_router_vendor,
 )
 
 
 def initialize_ae_members(subscription: Iptrunk, initial_user_input: dict, side_index: int) -> type[LAGMember]:
     """Initialize the list of AE members."""
     router = subscription.iptrunk.iptrunk_sides[side_index].iptrunk_side_node
+    router_vendor = get_router_vendor(router.owner_subscription_id)
     iptrunk_minimum_link = initial_user_input["iptrunk_minimum_links"]
-    if router.router_vendor == RouterVendor.NOKIA:
+    if router_vendor == RouterVendor.NOKIA:
         iptrunk_speed = initial_user_input["iptrunk_speed"]
 
         class NokiaLAGMember(LAGMember):
diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py
index 22046fb6..b86d17cd 100644
--- a/gso/workflows/iptrunk/terminate_iptrunk.py
+++ b/gso/workflows/iptrunk/terminate_iptrunk.py
@@ -15,12 +15,12 @@ from orchestrator.workflows.steps import (
 )
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
-from gso.products.product_blocks.router import RouterVendor
 from gso.products.product_types.iptrunk import Iptrunk
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services import infoblox, provisioning_proxy
 from gso.services.netbox_client import NetboxClient
 from gso.services.provisioning_proxy import pp_interaction
-from gso.utils.helpers import set_isis_to_90000
+from gso.utils.helpers import set_isis_to_90000, get_router_vendor
 
 
 def initial_input_form_generator() -> FormGenerator:
@@ -86,8 +86,9 @@ def free_interfaces_in_netbox(subscription: Iptrunk) -> State:
     """
     for side in [0, 1]:
         router = subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node
+        router_vendor = get_router_vendor(router.owner_subscription_id)
         router_fqdn = router.router_fqdn
-        if router.router_vendor == RouterVendor.NOKIA:
+        if router_vendor == RouterVendor.NOKIA:
             nbclient = NetboxClient()
             # Remove physical interfaces from LAGs
             for member in subscription.iptrunk.iptrunk_sides[side].iptrunk_side_ae_members:
diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py
index 9b493134..9c74245e 100644
--- a/gso/workflows/router/create_router.py
+++ b/gso/workflows/router/create_router.py
@@ -16,10 +16,9 @@ from pydantic import validator
 from gso.products.product_blocks.router import (
     PortNumber,
     RouterRole,
-    RouterVendor,
     generate_fqdn,
 )
-from gso.products.product_types.router import RouterInactive, RouterProvisioning
+from gso.products.product_types.router import RouterInactive, RouterProvisioning, RouterVendor
 from gso.products.product_types.site import Site
 from gso.services import infoblox, provisioning_proxy, subscriptions
 from gso.services.crm import customer_selector
@@ -49,7 +48,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         router_site: _site_selector()  # type: ignore[valid-type]
         hostname: str
         ts_port: PortNumber
-        router_vendor: RouterVendor
         router_role: RouterRole
         is_ias_connected: bool | None = False
 
@@ -89,13 +87,13 @@ def initialize_subscription(
     subscription: RouterInactive,
     hostname: str,
     ts_port: PortNumber,
-    router_vendor: RouterVendor,
+    vendor: RouterVendor,
     router_site: str,
     router_role: RouterRole,
 ) -> State:
     """Initialise the subscription object in the service database."""
     subscription.router.router_ts_port = ts_port
-    subscription.router.router_vendor = router_vendor
+    subscription.vendor = vendor
     subscription.router.router_site = Site.from_subscription(router_site).site
     fqdn = generate_fqdn(
         hostname,
@@ -179,7 +177,7 @@ def create_netbox_device(subscription: RouterProvisioning) -> State:
 
     HACK: use a conditional instead for execution of this step
     """
-    if subscription.router.router_vendor == RouterVendor.NOKIA:
+    if subscription.vendor == RouterVendor.NOKIA:
         NetboxClient().create_device(
             subscription.router.router_fqdn,
             str(subscription.router.router_site.site_tier),  # type: ignore[union-attr]
diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py
index 04583ffc..18734430 100644
--- a/gso/workflows/router/terminate_router.py
+++ b/gso/workflows/router/terminate_router.py
@@ -16,8 +16,7 @@ from orchestrator.workflows.steps import (
 )
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
-from gso.products.product_blocks.router import RouterVendor
-from gso.products.product_types.router import Router
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services import infoblox
 from gso.services.netbox_client import NetboxClient
 
@@ -78,7 +77,7 @@ def remove_config_from_router() -> None:
 @step("Remove Device from NetBox")
 def remove_device_from_netbox(subscription: Router) -> dict[str, Router]:
     """Remove the device from Netbox."""
-    if subscription.router.router_vendor == RouterVendor.NOKIA:
+    if subscription.vendor == RouterVendor.NOKIA:
         NetboxClient().delete_device(subscription.router.router_fqdn)
     return {"subscription": subscription}
 
diff --git a/gso/workflows/tasks/import_router.py b/gso/workflows/tasks/import_router.py
index ff5492b4..2b5829bd 100644
--- a/gso/workflows/tasks/import_router.py
+++ b/gso/workflows/tasks/import_router.py
@@ -12,9 +12,9 @@ from orchestrator.workflows.steps import resync, set_status, store_process_subsc
 
 from gso.products import ProductType
 from gso.products.product_blocks import router as router_pb
-from gso.products.product_blocks.router import PortNumber, RouterRole, RouterVendor, generate_fqdn
+from gso.products.product_blocks.router import PortNumber, RouterRole, generate_fqdn
 from gso.products.product_types import router
-from gso.products.product_types.router import RouterInactive
+from gso.products.product_types.router import RouterInactive, RouterVendor
 from gso.products.product_types.site import Site
 from gso.services import subscriptions
 from gso.services.crm import get_customer_by_name
@@ -35,10 +35,16 @@ def _get_site_by_name(site_name: str) -> Site:
 
 
 @step("Create subscription")
-def create_subscription(customer: str) -> State:
+def create_subscription(customer: str, router_vendor: RouterVendor) -> State:
     """Create a new subscription object."""
     customer_id = get_customer_by_name(customer)["id"]
-    product_id: UUID = subscriptions.get_product_id_by_name(ProductType.ROUTER)
+    if router_vendor == RouterVendor.NOKIA:
+        product_id: UUID = subscriptions.get_product_id_by_name(ProductType.NOKIA_ROUTER)
+    elif router_vendor == RouterVendor.JUNIPER:
+        product_id: UUID = subscriptions.get_product_id_by_name(ProductType.JUNIPER_ROUTER)
+    else:
+        raise ValueError(f"Unknown router vendor: {router_vendor}")
+
     subscription = RouterInactive.from_product_id(product_id, customer_id)
 
     return {
@@ -78,7 +84,6 @@ def initialize_subscription(
     subscription: RouterInactive,
     hostname: str,
     ts_port: PortNumber,
-    router_vendor: router_pb.RouterVendor,
     router_site: str,
     router_role: router_pb.RouterRole,
     is_ias_connected: bool | None = None,
@@ -91,7 +96,6 @@ def initialize_subscription(
 ) -> State:
     """Initialise the router subscription using input data."""
     subscription.router.router_ts_port = ts_port
-    subscription.router.router_vendor = router_vendor
     router_site_obj = _get_site_by_name(router_site).site
     subscription.router.router_site = router_site_obj
     fqdn = generate_fqdn(hostname, router_site_obj.site_name, router_site_obj.site_country_code)
diff --git a/test/fixtures.py b/test/fixtures.py
index 86927813..8a8db797 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -12,7 +12,7 @@ from gso.products.product_blocks.iptrunk import (
     IptrunkType,
     PhyPortCapacity,
 )
-from gso.products.product_blocks.router import RouterRole, RouterVendor
+from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_types.iptrunk import IptrunkInactive
 from gso.products.product_types.router import Router, RouterInactive
@@ -74,7 +74,7 @@ def site_subscription_factory(faker):
 
 
 @pytest.fixture()
-def router_subscription_factory(site_subscription_factory, faker):
+def nokia_router_subscription_factory(site_subscription_factory, faker):
     def subscription_create(
         description=None,
         start_date="2023-05-24T00:00:00+00:00",
@@ -87,7 +87,6 @@ def router_subscription_factory(site_subscription_factory, faker):
         router_si_ipv4_network=None,
         router_ias_lt_ipv4_network=None,
         router_ias_lt_ipv6_network=None,
-        router_vendor=RouterVendor.NOKIA,
         router_role=RouterRole.PE,
         router_site=None,
         router_is_ias_connected=True,  # noqa: FBT002
@@ -105,7 +104,7 @@ def router_subscription_factory(site_subscription_factory, faker):
         router_ias_lt_ipv6_network = router_ias_lt_ipv6_network or faker.ipv6_network()
         router_site = router_site or site_subscription_factory()
 
-        product_id = subscriptions.get_product_id_by_name(ProductType.ROUTER)
+        product_id = subscriptions.get_product_id_by_name(ProductType.NOKIA_ROUTER)
         router_subscription = RouterInactive.from_product_id(product_id, customer_id=CUSTOMER_ID, insync=True)
         router_subscription.router.router_fqdn = router_fqdn
         router_subscription.router.router_ts_port = router_ts_port
@@ -116,7 +115,6 @@ def router_subscription_factory(site_subscription_factory, faker):
         router_subscription.router.router_si_ipv4_network = router_si_ipv4_network
         router_subscription.router.router_ias_lt_ipv4_network = router_ias_lt_ipv4_network
         router_subscription.router.router_ias_lt_ipv6_network = router_ias_lt_ipv6_network
-        router_subscription.router.router_vendor = router_vendor
         router_subscription.router.router_role = router_role
         router_subscription.router.router_site = Site.from_subscription(router_site).site
         router_subscription.router.router_is_ias_connected = router_is_ias_connected
@@ -137,7 +135,7 @@ def router_subscription_factory(site_subscription_factory, faker):
 
 
 @pytest.fixture()
-def iptrunk_side_subscription_factory(router_subscription_factory, faker):
+def iptrunk_side_subscription_factory(nokia_router_subscription_factory, faker):
     def subscription_create(
         iptrunk_side_node=None,
         iptrunk_side_ae_iface=None,
@@ -145,7 +143,7 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker):
         iptrunk_side_ae_members=None,
         iptrunk_side_ae_members_description=None,
     ) -> IptrunkSideBlock:
-        iptrunk_side_node_id = iptrunk_side_node or router_subscription_factory()
+        iptrunk_side_node_id = iptrunk_side_node or nokia_router_subscription_factory()
         iptrunk_side_node = Router.from_subscription(iptrunk_side_node_id).router
         iptrunk_side_ae_iface = iptrunk_side_ae_iface or faker.pystr()
         iptrunk_side_ae_geant_a_sid = iptrunk_side_ae_geant_a_sid or faker.geant_sid()
diff --git a/test/imports/conftest.py b/test/imports/conftest.py
index 3583feca..e015f147 100644
--- a/test/imports/conftest.py
+++ b/test/imports/conftest.py
@@ -1,6 +1,6 @@
 from test.fixtures import (  # noqa: F401
     iptrunk_side_subscription_factory,
     iptrunk_subscription_factory,
-    router_subscription_factory,
+    nokia_router_subscription_factory,
     site_subscription_factory,
 )
diff --git a/test/imports/test_imports.py b/test/imports/test_imports.py
index 9a70dfdf..274ee634 100644
--- a/test/imports/test_imports.py
+++ b/test/imports/test_imports.py
@@ -6,8 +6,9 @@ from orchestrator.db import SubscriptionTable
 from orchestrator.services import subscriptions
 
 from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity
-from gso.products.product_blocks.router import RouterRole, RouterVendor
+from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
+from gso.products.product_types.router import RouterVendor
 from gso.utils.helpers import iso_from_ipv4
 
 SITE_IMPORT_ENDPOINT = "/api/v1/imports/sites"
@@ -16,9 +17,9 @@ IPTRUNK_IMPORT_API_URL = "/api/v1/imports/iptrunks"
 
 
 @pytest.fixture()
-def iptrunk_data(router_subscription_factory, faker):
-    router_side_a = router_subscription_factory()
-    router_side_b = router_subscription_factory()
+def iptrunk_data(nokia_router_subscription_factory, faker):
+    router_side_a = nokia_router_subscription_factory()
+    router_side_b = nokia_router_subscription_factory()
     return {
         "customer": "GÉANT",
         "geant_s_sid": faker.geant_sid(),
diff --git a/test/subscriptions/conftest.py b/test/subscriptions/conftest.py
index 59674dfa..428b0a14 100644
--- a/test/subscriptions/conftest.py
+++ b/test/subscriptions/conftest.py
@@ -1 +1 @@
-from test.fixtures import router_subscription_factory, site_subscription_factory  # noqa: F401
+from test.fixtures import nokia_router_subscription_factory, site_subscription_factory  # noqa: F401
diff --git a/test/subscriptions/test_subscriptions.py b/test/subscriptions/test_subscriptions.py
index 8e998099..d56d2d58 100644
--- a/test/subscriptions/test_subscriptions.py
+++ b/test/subscriptions/test_subscriptions.py
@@ -3,12 +3,12 @@ from orchestrator.types import SubscriptionLifecycle
 ROUTER_SUBSCRIPTION_ENDPOINT = "/api/v1/subscriptions/routers"
 
 
-def test_router_subscriptions_endpoint(test_client, router_subscription_factory):
-    router_subscription_factory()
-    router_subscription_factory()
-    router_subscription_factory()
-    router_subscription_factory(status=SubscriptionLifecycle.TERMINATED)
-    router_subscription_factory(status=SubscriptionLifecycle.INITIAL)
+def test_router_subscriptions_endpoint(test_client, nokia_router_subscription_factory):
+    nokia_router_subscription_factory()
+    nokia_router_subscription_factory()
+    nokia_router_subscription_factory()
+    nokia_router_subscription_factory(status=SubscriptionLifecycle.TERMINATED)
+    nokia_router_subscription_factory(status=SubscriptionLifecycle.INITIAL)
 
     response = test_client.get(ROUTER_SUBSCRIPTION_ENDPOINT)
 
diff --git a/test/workflows/conftest.py b/test/workflows/conftest.py
index a3d301f2..56bff61c 100644
--- a/test/workflows/conftest.py
+++ b/test/workflows/conftest.py
@@ -4,7 +4,7 @@ from urllib3_mock import Responses
 from test.fixtures import (  # noqa: F401
     iptrunk_side_subscription_factory,
     iptrunk_subscription_factory,
-    router_subscription_factory,
+    nokia_router_subscription_factory,
     site_subscription_factory,
 )
 
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index bc6cf64d..33af4110 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -42,9 +42,9 @@ def _netbox_client_mock():
 
 
 @pytest.fixture()
-def input_form_wizard_data(router_subscription_factory, faker):
-    router_side_a = router_subscription_factory()
-    router_side_b = router_subscription_factory()
+def input_form_wizard_data(nokia_router_subscription_factory, faker):
+    router_side_a = nokia_router_subscription_factory()
+    router_side_b = nokia_router_subscription_factory()
 
     create_ip_trunk_step = {
         "tt_number": faker.tt_number(),
diff --git a/test/workflows/iptrunk/test_migrate_iptrunk.py b/test/workflows/iptrunk/test_migrate_iptrunk.py
index d1383e41..49769de4 100644
--- a/test/workflows/iptrunk/test_migrate_iptrunk.py
+++ b/test/workflows/iptrunk/test_migrate_iptrunk.py
@@ -40,7 +40,7 @@ def test_migrate_iptrunk_success(
     mock_provision_ip_trunk,
     mock_migrate_ip_trunk,
     iptrunk_subscription_factory,
-    router_subscription_factory,
+        nokia_router_subscription_factory,
     faker,
     data_config_filename: PathLike,
 ):
@@ -57,7 +57,7 @@ def test_migrate_iptrunk_success(
 
     product_id = iptrunk_subscription_factory()
     old_subscription = Iptrunk.from_subscription(product_id)
-    new_router = router_subscription_factory()
+    new_router = nokia_router_subscription_factory()
 
     #  Run workflow
     migrate_form_input = [
diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py
index 67c6496c..b39782a7 100644
--- a/test/workflows/router/test_create_router.py
+++ b/test/workflows/router/test_create_router.py
@@ -4,8 +4,8 @@ import pytest
 from infoblox_client import objects
 
 from gso.products import ProductType, Site
-from gso.products.product_blocks.router import RouterRole, RouterVendor
-from gso.products.product_types.router import Router
+from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.router import Router, RouterVendor
 from gso.services.crm import customer_selector, get_customer_by_name
 from gso.services.subscriptions import get_product_id_by_name
 from test.workflows import (
@@ -27,7 +27,7 @@ def router_creation_input_form_data(site_subscription_factory, faker):
         "router_site": router_site,
         "hostname": faker.pystr(),
         "ts_port": faker.pyint(),
-        "router_vendor": RouterVendor.NOKIA,
+        "vendor": RouterVendor.NOKIA,
         "router_role": faker.random_choices(elements=(RouterRole.P, RouterRole.PE, RouterRole.AMT), length=1)[0],
         "is_ias_connected": True,
     }
@@ -42,7 +42,7 @@ def router_creation_input_form_data(site_subscription_factory, faker):
 @patch("gso.workflows.router.create_router.infoblox.allocate_v6_network")
 @patch("gso.workflows.router.create_router.infoblox.allocate_v4_network")
 @patch("gso.workflows.router.create_router.infoblox.allocate_host")
-def test_create_router_success(
+def test_create_nokia_router_success(
     mock_allocate_host,
     mock_allocate_v4_network,
     mock_allocate_v6_network,
@@ -56,7 +56,7 @@ def test_create_router_success(
     data_config_filename,
 ):
     #  Set up mock return values
-    product_id = get_product_id_by_name(ProductType.ROUTER)
+    product_id = get_product_id_by_name(ProductType.NOKIA_ROUTER)
     mock_site = Site.from_subscription(router_creation_input_form_data["router_site"]).site
     mock_v4 = faker.ipv4()
     mock_v4_net = faker.ipv4_network()
@@ -132,7 +132,7 @@ def test_create_router_success(
 @patch("gso.workflows.router.create_router.infoblox.allocate_v6_network")
 @patch("gso.workflows.router.create_router.infoblox.allocate_v4_network")
 @patch("gso.workflows.router.create_router.infoblox.allocate_host")
-def test_create_router_lso_failure(
+def test_create_nokia_router_lso_failure(
     mock_allocate_host,
     mock_allocate_v4_network,
     mock_allocate_v6_network,
@@ -181,7 +181,7 @@ def test_create_router_lso_failure(
     )
 
     #  Run workflow
-    product_id = get_product_id_by_name(ProductType.ROUTER)
+    product_id = get_product_id_by_name(ProductType.NOKIA_ROUTER)
     initial_router_data = [{"product": product_id}, router_creation_input_form_data]
     result, process_stat, step_log = run_workflow("create_router", initial_router_data)
 
diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py
index 0b8af2b2..0ff22d96 100644
--- a/test/workflows/router/test_terminate_router.py
+++ b/test/workflows/router/test_terminate_router.py
@@ -24,12 +24,12 @@ def test_terminate_router_success(
     mock_delete_host_by_ip,
     mock_delete_device,
     router_termination_input_form_data,
-    router_subscription_factory,
+        nokia_router_subscription_factory,
     faker,
     data_config_filename,
 ):
     #  Set up active subscription in database
-    product_id = router_subscription_factory()
+    product_id = nokia_router_subscription_factory()
 
     #  Run workflow
     initial_router_data = [
-- 
GitLab