diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e93f467b6258adcc6a462bcaffdc592d020c62df..4aa7c82146a7b3e627d678bc7d8034e89c9267eb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ run-tox-pipeline: stage: tox tags: - docker-executor - image: python:3.11 + image: python:3.10 services: - postgres:15.4 diff --git a/docs/vale/styles/Vocab/geant-jargon/accept.txt b/docs/vale/styles/Vocab/geant-jargon/accept.txt index 8d0a6acf97307cbbe2cd7cd87e1dabfcbac5b6aa..b4ae8b012b00eadd3f7108bba81ecbd642057d13 100644 --- a/docs/vale/styles/Vocab/geant-jargon/accept.txt +++ b/docs/vale/styles/Vocab/geant-jargon/accept.txt @@ -9,3 +9,4 @@ API dry_run Dark_fiber [A|a]ddress +[I|i]ptrunk diff --git a/gso/products/product_blocks/__init__.py b/gso/products/product_blocks/__init__.py index d4c0aa2767e27d8f6580b3b16949ed1b78dbb5e1..de5db46c80c80ee31acfdf549159cde5f4e862b2 100644 --- a/gso/products/product_blocks/__init__.py +++ b/gso/products/product_blocks/__init__.py @@ -3,10 +3,10 @@ In this file, some enumerators may be declared that are available for use across all subscriptions. """ -from enum import StrEnum +from orchestrator.types import strEnum -class PhyPortCapacity(StrEnum): +class PhyPortCapacity(strEnum): """Physical port capacity enumerator. An enumerator that has the different possible capacities of ports that are available to use in subscriptions. diff --git a/gso/repository.py b/gso/repository.py deleted file mode 100644 index 68d6807bb0cf3bd5d9f72454f395e74d7d50cdf0..0000000000000000000000000000000000000000 --- a/gso/repository.py +++ /dev/null @@ -1,61 +0,0 @@ -from uuid import UUID - -from asyncio_redis import Subscription -from orchestrator.db import ( - ProductTable, - ResourceTypeTable, - SubscriptionInstanceTable, - SubscriptionInstanceValueTable, - SubscriptionTable, -) - -from gso.schemas.enums import ProductType, SubscriptionStatus - - -def all_active_subscriptions( - product_type: str, - fields: list[str], -) -> list[Subscription]: - dynamic_fields = [getattr(SubscriptionTable, field) for field in fields] - - return ( - SubscriptionTable.query.join(ProductTable) - .filter( - ProductTable.product_type == product_type, - SubscriptionTable.status == SubscriptionStatus.ACTIVE, - ) - .with_entities(*dynamic_fields) - .all() - ) - - -def all_active_site_subscriptions(fields: list[str]) -> list[Subscription]: - return all_active_subscriptions(ProductType.SITE, fields) - - -def site_product_id() -> UUID: - return ProductTable.query.filter_by(name=ProductType.SITE).first().product_id - - -def active_site_subscription_by_name(site_name: str) -> Subscription: - return ( - SubscriptionTable.query.join( - ProductTable, SubscriptionInstanceTable, SubscriptionInstanceValueTable, ResourceTypeTable - ) - .filter(SubscriptionInstanceValueTable.value == site_name) - .filter(ResourceTypeTable.resource_type == "site_name") - .filter(SubscriptionTable.status == SubscriptionStatus.ACTIVE) - .first() - ) - - -def iptrunk_product_id() -> UUID: - return ProductTable.query.filter_by(name=ProductType.IP_TRUNK).first().product_id - - -def all_active_router_subscriptions(fields: list[str]) -> list[Subscription]: - return all_active_subscriptions(product_type=ProductType.ROUTER, fields=fields) - - -def router_product_id() -> UUID: - return ProductTable.query.filter_by(name=ProductType.ROUTER).first().product_id diff --git a/gso/schemas/enums.py b/gso/schemas/enums.py index 248e87eb6de8052b0a980f46fad6c260f20c69b5..c803ede4acf8757199d5f5e28fe37576378a9dea 100644 --- a/gso/schemas/enums.py +++ b/gso/schemas/enums.py @@ -1,11 +1,11 @@ -from enum import StrEnum +from orchestrator.types import strEnum -class ProductType(StrEnum): +class ProductType(strEnum): SITE = "Site" ROUTER = "Router" IP_TRUNK = "IP trunk" -class SubscriptionStatus(StrEnum): +class SubscriptionStatus(strEnum): ACTIVE = "active" diff --git a/gso/schemas/imports.py b/gso/schemas/imports.py index ae8a84cb4529590928a1835e965d387d9ad503c3..da1e242aab47d85abb7bc01d18cf1a51eaf034d8 100644 --- a/gso/schemas/imports.py +++ b/gso/schemas/imports.py @@ -4,12 +4,12 @@ from uuid import UUID from pydantic import BaseModel, root_validator, validator -from gso import repository from gso.products.product_blocks import PhyPortCapacity from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_blocks.router import RouterRole, RouterVendor from gso.products.product_blocks.site import SiteTier -from gso.services.crm import CustomerNotFoundError, get_customer_id_by_name +from gso.services import subscriptions +from gso.services.crm import CustomerNotFoundError, get_customer_by_name class ImportResponseModel(BaseModel): @@ -70,12 +70,14 @@ class IptrunkImportModel(BaseModel): @classmethod def _get_active_routers(cls) -> set[str]: - return {str(router_id) for router_id in repository.all_active_router_subscriptions(fields=["subscription_id"])} + return { + str(router_id) for router_id in subscriptions.get_active_router_subscriptions(fields=["subscription_id"]) + } @validator("customer") def check_if_customer_exists(cls, value: str) -> str: try: - get_customer_id_by_name(value) + get_customer_by_name(value) except CustomerNotFoundError: raise ValueError(f"Customer {value} not found") diff --git a/gso/services/crm.py b/gso/services/crm.py index 7e4cc9b8aa555c81ae755f6a76058146c42c7428..7568e3116eb2e685d45d1a6c67ff11d9b0bf8533 100644 --- a/gso/services/crm.py +++ b/gso/services/crm.py @@ -22,7 +22,3 @@ def get_customer_by_name(name: str) -> Dict[str, Any]: return customer raise CustomerNotFoundError(f"Customer {name} not found") - - -def get_customer_id_by_name(name: str) -> str: - return get_customer_by_name(name)["id"] diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py new file mode 100644 index 0000000000000000000000000000000000000000..9ee2e4dfa8091ea06ae9e5b1dee947fdc8794156 --- /dev/null +++ b/gso/services/subscriptions.py @@ -0,0 +1,104 @@ +from uuid import UUID + +from asyncio_redis import Subscription +from orchestrator.db import ( + ProductTable, + ResourceTypeTable, + SubscriptionInstanceTable, + SubscriptionInstanceValueTable, + SubscriptionTable, +) + +from gso.schemas.enums import ProductType, SubscriptionStatus + + +def get_active_subscriptions( + product_type: str, + fields: list[str], +) -> list[Subscription]: + """Retrieve active subscriptions for a specific product type. + + Args: + ---- + product_type (str): The type of the product for which to retrieve subscriptions. + fields (list[str]): List of fields to be included in the returned Subscription objects. + + Returns: + ------- + list[Subscription]: A list of Subscription objects that match the query. + """ + dynamic_fields = [getattr(SubscriptionTable, field) for field in fields] + + return ( + SubscriptionTable.query.join(ProductTable) + .filter( + ProductTable.product_type == product_type, + SubscriptionTable.status == SubscriptionStatus.ACTIVE, + ) + .with_entities(*dynamic_fields) + .all() + ) + + +def get_active_site_subscriptions(fields: list[str]) -> list[Subscription]: + """Retrieve active subscriptions specifically for sites. + + Args: + ---- + fields (list[str]): The fields to be included in the returned Subscription objects. + + Returns: + ------- + list[Subscription]: A list of Subscription objects for sites. + """ + return get_active_subscriptions(ProductType.SITE, fields) + + +def get_active_router_subscriptions(fields: list[str]) -> list[Subscription]: + """Retrieve active subscriptions specifically for routers. + + Args: + ---- + fields (list[str]): The fields to be included in the returned Subscription objects. + + Returns: + ------- + list[Subscription]: A list of Subscription objects for routers. + """ + return get_active_subscriptions(product_type=ProductType.ROUTER, fields=fields) + + +def get_product_id_by_name(product_name: ProductType) -> UUID: + """Retrieve the UUID of a product by its name. + + Args: + ---- + product_name (ProductType): The name of the product. + + Returns: + ------- + UUID: The UUID of the product. + """ + return ProductTable.query.filter_by(name=product_name).first().product_id + + +def get_active_site_subscription_by_name(site_name: str) -> Subscription: + """Retrieve an active subscription for a site by the site's name. + + Args: + ---- + site_name (str): The name of the site for which to retrieve the subscription. + + Returns: + ------- + Subscription: The Subscription object for the site. + """ + return ( + SubscriptionTable.query.join( + ProductTable, SubscriptionInstanceTable, SubscriptionInstanceValueTable, ResourceTypeTable + ) + .filter(SubscriptionInstanceValueTable.value == site_name) + .filter(ResourceTypeTable.resource_type == "site_name") + .filter(SubscriptionTable.status == SubscriptionStatus.ACTIVE) + .first() + ) diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index 5e40775a3e22b786fe08e3ffc62b0c724f13528b..db471657e553918a0aee1e55edcd32ca1c98eba7 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -6,12 +6,11 @@ from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form -from gso import repository from gso.products.product_blocks import PhyPortCapacity from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning from gso.products.product_types.router import Router -from gso.services import ipam, provisioning_proxy +from gso.services import ipam, provisioning_proxy, subscriptions from gso.services.provisioning_proxy import pp_interaction from gso.workflows.utils import customer_selector @@ -21,7 +20,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: # * interface names must be validated routers = {} - for router_id, router_description in repository.all_active_router_subscriptions( + for router_id, router_description in subscriptions.get_active_router_subscriptions( fields=["subscription_id", "description"] ): routers[str(router_id)] = router_description diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index 44793629c442f3238e65dbc8132075d7f54d27da..3f4624caee8da28919fe200f4edf1b2a1fa57afc 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -10,19 +10,18 @@ from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form -from gso import repository from gso.products.product_blocks import router as router_pb from gso.products.product_types import router from gso.products.product_types.router import RouterInactive, RouterProvisioning from gso.products.product_types.site import Site -from gso.services import ipam, provisioning_proxy +from gso.services import ipam, provisioning_proxy, subscriptions from gso.services.provisioning_proxy import pp_interaction from gso.workflows.utils import customer_selector def site_selector() -> Choice: site_subscriptions = {} - for site_id, site_description in repository.all_active_site_subscriptions( + for site_id, site_description in subscriptions.get_active_site_subscriptions( fields=["subscription_id", "description"] ): site_subscriptions[str(site_id)] = site_description diff --git a/gso/workflows/tasks/import_iptrunk.py b/gso/workflows/tasks/import_iptrunk.py index ab1642ca45cb95b633cce552fffe009c122666ad..72dddffc209f2c2fde908dfc67ac0624933b18df 100644 --- a/gso/workflows/tasks/import_iptrunk.py +++ b/gso/workflows/tasks/import_iptrunk.py @@ -1,4 +1,5 @@ import ipaddress +from uuid import UUID from orchestrator import workflow from orchestrator.forms import FormPage @@ -8,18 +9,19 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle from orchestrator.workflow import StepList, done, init, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription -from gso import repository from gso.products.product_blocks import PhyPortCapacity from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning -from gso.services.crm import get_customer_id_by_name +from gso.schemas.enums import ProductType +from gso.services import subscriptions +from gso.services.crm import get_customer_by_name from gso.workflows.iptrunk.create_iptrunk import initialize_subscription def _generate_routers() -> dict[str, str]: """Generate a dictionary of router IDs and descriptions.""" routers = {} - for router_id, router_description in repository.all_active_router_subscriptions( + for router_id, router_description in subscriptions.get_active_router_subscriptions( fields=["subscription_id", "description"] ): routers[str(router_id)] = router_description @@ -63,8 +65,8 @@ def initial_input_form_generator() -> FormGenerator: @step("Create a new subscription") def create_subscription(customer: str) -> State: - customer_id = get_customer_id_by_name(customer) - product_id = repository.iptrunk_product_id() + customer_id: UUID = get_customer_by_name(customer)["id"] + product_id = subscriptions.get_product_id_by_name(ProductType.IP_TRUNK) subscription = IptrunkInactive.from_product_id(product_id, customer_id) return { diff --git a/gso/workflows/tasks/import_router.py b/gso/workflows/tasks/import_router.py index 5733716259fb0dc858e65be95b1abd4fa3547695..10a8b75e0674438aa3ad4927f60a505f960d8480 100644 --- a/gso/workflows/tasks/import_router.py +++ b/gso/workflows/tasks/import_router.py @@ -9,13 +9,14 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle from orchestrator.workflow import StepList, done, init, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription -from gso import repository from gso.products.product_blocks import router as router_pb from gso.products.product_blocks.router import RouterRole, RouterVendor from gso.products.product_types import router from gso.products.product_types.router import RouterInactive from gso.products.product_types.site import Site -from gso.services.crm import get_customer_id_by_name +from gso.schemas.enums import ProductType +from gso.services import subscriptions +from gso.services.crm import get_customer_by_name def _get_site_by_name(site_name: str) -> Site: @@ -25,7 +26,7 @@ def _get_site_by_name(site_name: str) -> Site: ---- site_name (str): The name of the site. """ - subscription = repository.active_site_subscription_by_name(site_name) + subscription = subscriptions.get_active_site_subscription_by_name(site_name) if not subscription: raise ValueError(f"Site with name {site_name} not found.") @@ -34,8 +35,8 @@ def _get_site_by_name(site_name: str) -> Site: @step("Create subscription") def create_subscription(customer: str) -> State: - customer_id: str = get_customer_id_by_name(customer) - product_id: UUID = repository.router_product_id() + customer_id: UUID = get_customer_by_name(customer)["id"] + product_id: UUID = subscriptions.get_product_id_by_name(ProductType.ROUTER) subscription = RouterInactive.from_product_id(product_id, customer_id) return { diff --git a/gso/workflows/tasks/import_site.py b/gso/workflows/tasks/import_site.py index 14b69d425abffe9449090b7339909a922fb141f5..0d03ed875fa5f53178ed21382ee6d741774c27d2 100644 --- a/gso/workflows/tasks/import_site.py +++ b/gso/workflows/tasks/import_site.py @@ -6,17 +6,18 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription -from gso import repository from gso.products.product_blocks.site import SiteTier from gso.products.product_types.site import SiteInactive -from gso.services.crm import get_customer_id_by_name +from gso.schemas.enums import ProductType +from gso.services import subscriptions +from gso.services.crm import get_customer_by_name from gso.workflows.site.create_site import initialize_subscription @step("Create subscription") def create_subscription(customer: str) -> State: - customer_id: str = get_customer_id_by_name(customer) - product_id: UUID = repository.site_product_id() + customer_id: UUID = get_customer_by_name(customer)["id"] + product_id: UUID = subscriptions.get_product_id_by_name(ProductType.SITE) subscription = SiteInactive.from_product_id(product_id, customer_id) return { diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/conftest.py b/test/conftest.py index 7914bdc3501551271f769e83bd5cc8c0c94bb8ec..03d00132e5dd950ae963063d4d05c9887c7853ed 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -15,21 +15,12 @@ from faker.providers import BaseProvider from orchestrator import app_settings from orchestrator.db import Database, db from orchestrator.db.database import ENGINE_ARGUMENTS, SESSION_ARGUMENTS, BaseModel -from orchestrator.domain import SubscriptionModel -from orchestrator.types import SubscriptionLifecycle, UUIDstr from sqlalchemy import create_engine from sqlalchemy.engine import make_url from sqlalchemy.orm import scoped_session, sessionmaker from starlette.testclient import TestClient -from gso import repository from gso.main import init_gso_app -from gso.products.product_blocks.router import RouterRole, RouterVendor -from gso.products.product_blocks.site import SiteTier -from gso.products.product_types.router import RouterInactive -from gso.products.product_types.site import Site, SiteInactive - -CUSTOMER_ID: UUIDstr = "2f47f65a-0911-e511-80d0-005056956c1a" class FakerProvider(BaseProvider): @@ -48,13 +39,11 @@ class FakerProvider(BaseProvider): return ipaddress.IPv6Network(str(network) + "/64") -faker = Faker() -faker.add_provider(FakerProvider) - - @pytest.fixture(scope="session") -def fake() -> Faker: - return faker +def faker() -> Faker: + fake = Faker() + fake.add_provider(FakerProvider) + return fake @pytest.fixture(scope="session") @@ -249,112 +238,3 @@ def fastapi_app(database, db_uri): @pytest.fixture(scope="session") def test_client(fastapi_app): return TestClient(fastapi_app) - - -@pytest.fixture -def site_subscription_factory(fake): - def subscription_create( - description=None, - start_date="2023-05-24T00:00:00+00:00", - site_name=None, - site_city=None, - site_country=None, - site_country_code=None, - site_latitude=None, - site_longitude=None, - site_bgp_community_id=None, - site_internal_id=None, - site_tier=SiteTier.TIER1, - site_ts_address=None, - ) -> UUIDstr: - description = description or "Site Subscription" - site_name = site_name or fake.name() - site_city = site_city or fake.city() - site_country = site_country or fake.country() - site_country_code = site_country_code or fake.country_code() - site_latitude = site_latitude or float(fake.latitude()) - site_longitude = site_longitude or float(fake.longitude()) - site_bgp_community_id = site_bgp_community_id or fake.pyint() - site_internal_id = site_internal_id or fake.pyint() - site_ts_address = site_ts_address or fake.ipv4() - - product_id = repository.site_product_id() - site_subscription = SiteInactive.from_product_id(product_id, customer_id=CUSTOMER_ID, insync=True) - site_subscription.site.site_city = site_city - site_subscription.site.site_name = site_name - site_subscription.site.site_country = site_country - site_subscription.site.site_country_code = site_country_code - site_subscription.site.site_latitude = site_latitude - site_subscription.site.site_longitude = site_longitude - site_subscription.site.site_bgp_community_id = site_bgp_community_id - site_subscription.site.site_internal_id = site_internal_id - site_subscription.site.site_tier = site_tier - site_subscription.site.site_ts_address = site_ts_address - - site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE) - site_subscription.description = description - site_subscription.start_date = start_date - site_subscription.save() - db.session.commit() - - return str(site_subscription.subscription_id) - - return subscription_create - - -@pytest.fixture -def router_subscription_factory(site_subscription_factory, fake): - def subscription_create( - description=None, - start_date="2023-05-24T00:00:00+00:00", - router_fqdn=None, - router_ts_port=None, - router_access_via_ts=None, - router_lo_ipv4_address=None, - router_lo_ipv6_address=None, - router_lo_iso_address=None, - 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, - ) -> UUIDstr: - description = description or fake.text(max_nb_chars=30) - router_fqdn = router_fqdn or fake.domain_name() - router_ts_port = router_ts_port or fake.random_int(min=1, max=65535) - router_access_via_ts = router_access_via_ts or fake.boolean() - router_lo_ipv4_address = router_lo_ipv4_address or ipaddress.IPv4Address(fake.ipv4()) - router_lo_ipv6_address = router_lo_ipv6_address or ipaddress.IPv6Address(fake.ipv6()) - router_lo_iso_address = router_lo_iso_address or fake.word() - router_si_ipv4_network = router_si_ipv4_network or fake.ipv4_network() - router_ias_lt_ipv4_network = router_ias_lt_ipv4_network or fake.ipv4_network() - router_ias_lt_ipv6_network = router_ias_lt_ipv6_network or fake.ipv6_network() - router_site = router_site or site_subscription_factory() - - product_id = repository.router_product_id() - 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 - router_subscription.router.router_access_via_ts = router_access_via_ts - router_subscription.router.router_lo_ipv4_address = router_lo_ipv4_address - router_subscription.router.router_lo_ipv6_address = router_lo_ipv6_address - router_subscription.router.router_lo_iso_address = router_lo_iso_address - 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 - - router_subscription = SubscriptionModel.from_other_lifecycle(router_subscription, SubscriptionLifecycle.ACTIVE) - router_subscription.description = description - router_subscription.start_date = start_date - router_subscription.save() - db.session.commit() - - return str(router_subscription.subscription_id) - - return subscription_create diff --git a/test/imports/__init__.py b/test/imports/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/imports/conftest.py b/test/imports/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..b6207eddfc82874550a4fc6150ceeb70744cd3d6 --- /dev/null +++ b/test/imports/conftest.py @@ -0,0 +1,124 @@ +import ipaddress + +import pytest +from orchestrator.db import db +from orchestrator.domain import SubscriptionModel +from orchestrator.types import SubscriptionLifecycle, UUIDstr + +from gso.products.product_blocks.router import RouterRole, RouterVendor +from gso.products.product_blocks.site import SiteTier +from gso.products.product_types.router import RouterInactive +from gso.products.product_types.site import Site, SiteInactive +from gso.schemas.enums import ProductType +from gso.services import subscriptions + +CUSTOMER_ID: UUIDstr = "2f47f65a-0911-e511-80d0-005056956c1a" + + +@pytest.fixture +def site_subscription_factory(faker): + def subscription_create( + description=None, + start_date="2023-05-24T00:00:00+00:00", + site_name=None, + site_city=None, + site_country=None, + site_country_code=None, + site_latitude=None, + site_longitude=None, + site_bgp_community_id=None, + site_internal_id=None, + site_tier=SiteTier.TIER1, + site_ts_address=None, + ) -> UUIDstr: + description = description or "Site Subscription" + site_name = site_name or faker.name() + site_city = site_city or faker.city() + site_country = site_country or faker.country() + site_country_code = site_country_code or faker.country_code() + site_latitude = site_latitude or float(faker.latitude()) + site_longitude = site_longitude or float(faker.longitude()) + site_bgp_community_id = site_bgp_community_id or faker.pyint() + site_internal_id = site_internal_id or faker.pyint() + site_ts_address = site_ts_address or faker.ipv4() + + product_id = subscriptions.get_product_id_by_name(ProductType.SITE) + site_subscription = SiteInactive.from_product_id(product_id, customer_id=CUSTOMER_ID, insync=True) + site_subscription.site.site_city = site_city + site_subscription.site.site_name = site_name + site_subscription.site.site_country = site_country + site_subscription.site.site_country_code = site_country_code + site_subscription.site.site_latitude = site_latitude + site_subscription.site.site_longitude = site_longitude + site_subscription.site.site_bgp_community_id = site_bgp_community_id + site_subscription.site.site_internal_id = site_internal_id + site_subscription.site.site_tier = site_tier + site_subscription.site.site_ts_address = site_ts_address + + site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE) + site_subscription.description = description + site_subscription.start_date = start_date + site_subscription.save() + db.session.commit() + + return str(site_subscription.subscription_id) + + return subscription_create + + +@pytest.fixture +def router_subscription_factory(site_subscription_factory, faker): + def subscription_create( + description=None, + start_date="2023-05-24T00:00:00+00:00", + router_fqdn=None, + router_ts_port=None, + router_access_via_ts=None, + router_lo_ipv4_address=None, + router_lo_ipv6_address=None, + router_lo_iso_address=None, + 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, + ) -> UUIDstr: + description = description or faker.text(max_nb_chars=30) + router_fqdn = router_fqdn or faker.domain_name() + router_ts_port = router_ts_port or faker.random_int(min=1, max=65535) + router_access_via_ts = router_access_via_ts or faker.boolean() + router_lo_ipv4_address = router_lo_ipv4_address or ipaddress.IPv4Address(faker.ipv4()) + router_lo_ipv6_address = router_lo_ipv6_address or ipaddress.IPv6Address(faker.ipv6()) + router_lo_iso_address = router_lo_iso_address or faker.word() + router_si_ipv4_network = router_si_ipv4_network or faker.ipv4_network() + router_ias_lt_ipv4_network = router_ias_lt_ipv4_network or faker.ipv4_network() + 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) + 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 + router_subscription.router.router_access_via_ts = router_access_via_ts + router_subscription.router.router_lo_ipv4_address = router_lo_ipv4_address + router_subscription.router.router_lo_ipv6_address = router_lo_ipv6_address + router_subscription.router.router_lo_iso_address = router_lo_iso_address + 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 + + router_subscription = SubscriptionModel.from_other_lifecycle(router_subscription, SubscriptionLifecycle.ACTIVE) + router_subscription.description = description + router_subscription.start_date = start_date + router_subscription.save() + db.session.commit() + + return str(router_subscription.subscription_id) + + return subscription_create diff --git a/test/test_imports_iptrunk.py b/test/imports/test_imports.py similarity index 58% rename from test/test_imports_iptrunk.py rename to test/imports/test_imports.py index 511117309ca6a335d12363628ccdfa70cfe5753a..c41497c026ad36d7391542542da103b04db49519 100644 --- a/test/test_imports_iptrunk.py +++ b/test/imports/test_imports.py @@ -2,37 +2,42 @@ from unittest.mock import patch from uuid import uuid4 import pytest +from orchestrator.db import SubscriptionTable from orchestrator.services import subscriptions from gso.products.product_blocks import PhyPortCapacity from gso.products.product_blocks.iptrunk import IptrunkType +from gso.products.product_blocks.router import RouterRole, RouterVendor +from gso.products.product_blocks.site import SiteTier +SITE_IMPORT_ENDPOINT = "/api/v1/imports/sites" +ROUTER_IMPORT_ENDPOINT = "/api/v1/imports/routers" IPTRUNK_IMPORT_API_URL = "/api/v1/imports/iptrunks" @pytest.fixture -def iptrunk_data(router_subscription_factory, fake): +def iptrunk_data(router_subscription_factory, faker): router_side_a = router_subscription_factory() router_side_b = router_subscription_factory() return { "customer": "GÉANT", - "geant_s_sid": fake.pystr(), + "geant_s_sid": faker.pystr(), "iptrunk_type": IptrunkType.DARK_FIBER, - "iptrunk_description": fake.sentence(), + "iptrunk_description": faker.sentence(), "iptrunk_speed": PhyPortCapacity.HUNDRED_GIGABIT_PER_SECOND, "iptrunk_minimum_links": 5, "iptrunk_sideA_node_id": router_side_a, - "iptrunk_sideA_ae_iface": fake.pystr(), - "iptrunk_sideA_ae_geant_a_sid": fake.pystr(), - "iptrunk_sideA_ae_members": [fake.pystr() for _ in range(5)], - "iptrunk_sideA_ae_members_descriptions": [fake.sentence() for _ in range(5)], + "iptrunk_sideA_ae_iface": faker.pystr(), + "iptrunk_sideA_ae_geant_a_sid": faker.pystr(), + "iptrunk_sideA_ae_members": [faker.pystr() for _ in range(5)], + "iptrunk_sideA_ae_members_descriptions": [faker.sentence() for _ in range(5)], "iptrunk_sideB_node_id": router_side_b, - "iptrunk_sideB_ae_iface": fake.pystr(), - "iptrunk_sideB_ae_geant_a_sid": fake.pystr(), - "iptrunk_sideB_ae_members": [fake.pystr() for _ in range(5)], - "iptrunk_sideB_ae_members_descriptions": [fake.sentence() for _ in range(5)], - "iptrunk_ipv4_network": str(fake.ipv4_network()), - "iptrunk_ipv6_network": str(fake.ipv6_network()), + "iptrunk_sideB_ae_iface": faker.pystr(), + "iptrunk_sideB_ae_geant_a_sid": faker.pystr(), + "iptrunk_sideB_ae_members": [faker.pystr() for _ in range(5)], + "iptrunk_sideB_ae_members_descriptions": [faker.sentence() for _ in range(5)], + "iptrunk_ipv4_network": str(faker.ipv4_network()), + "iptrunk_ipv6_network": str(faker.ipv6_network()), } @@ -48,9 +53,9 @@ def mock_routers(iptrunk_data): (str(uuid4()), "random description"), ], ] - with patch("gso.repository.all_active_router_subscriptions") as mock_all_active_router_subscriptions: - mock_all_active_router_subscriptions.side_effect = side_effects - yield mock_all_active_router_subscriptions + with patch("gso.services.subscriptions.get_active_router_subscriptions") as mock_get_active_router_subscriptions: + mock_get_active_router_subscriptions.side_effect = side_effects + yield mock_get_active_router_subscriptions @patch("gso.api.v1.imports._start_process") @@ -62,6 +67,106 @@ def test_import_iptrunk_successful_with_mocked_process(mock_start_process, test_ assert response.json()["pid"] == "123e4567-e89b-12d3-a456-426655440000" +@pytest.fixture +def site_data(faker): + return { + "site_name": faker.name(), + "site_city": faker.city(), + "site_country": faker.country(), + "site_country_code": faker.country_code(), + "site_latitude": float(faker.latitude()), + "site_longitude": float(faker.longitude()), + "site_bgp_community_id": faker.pyint(), + "site_internal_id": faker.pyint(), + "site_tier": SiteTier.TIER1, + "site_ts_address": faker.ipv4(), + "customer": "GÉANT", + } + + +@pytest.fixture +def router_data(faker, site_data): + return { + "hostname": "127.0.0.1", + "router_role": RouterRole.PE, + "router_vendor": RouterVendor.JUNIPER, + "router_site": site_data["site_name"], + "ts_port": 1234, + "customer": "GÉANT", + "is_ias_connected": True, + "router_lo_ipv4_address": faker.ipv4(), + "router_lo_ipv6_address": faker.ipv6(), + "router_lo_iso_address": "TestAddress", + } + + +def test_import_site_endpoint(test_client, site_data): + assert SubscriptionTable.query.all() == [] + # Post data to the endpoint + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert response.status_code == 201 + assert "detail" in response.json() + assert "pid" in response.json() + subscription = subscriptions.retrieve_subscription_by_subscription_instance_value( + resource_type="site_name", value=site_data["site_name"] + ) + assert subscription is not None + + +def test_import_site_endpoint_with_existing_site(test_client, site_data): + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert SubscriptionTable.query.count() == 1 + assert response.status_code == 201 + + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert response.status_code == 409 + assert SubscriptionTable.query.count() == 1 + + +def test_import_site_endpoint_with_invalid_data(test_client, site_data): + # invalid data, missing site_latitude and invalid site_longitude + site_data.pop("site_latitude") + site_data["site_longitude"] = "invalid" + assert SubscriptionTable.query.count() == 0 + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert response.status_code == 422 + assert SubscriptionTable.query.count() == 0 + response = response.json() + assert response["detail"][0]["loc"] == ["body", "site_latitude"] + assert response["detail"][0]["msg"] == "field required" + assert response["detail"][1]["loc"] == ["body", "site_longitude"] + assert response["detail"][1]["msg"] == "value is not a valid float" + + +def test_import_router_endpoint(test_client, site_data, router_data): + # Create a site first + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert response.status_code == 201 + assert SubscriptionTable.query.count() == 1 + + response = test_client.post(ROUTER_IMPORT_ENDPOINT, json=router_data) + assert response.status_code == 201 + assert SubscriptionTable.query.count() == 2 + + +def test_import_router_endpoint_with_invalid_data(test_client, site_data, router_data): + response = test_client.post(SITE_IMPORT_ENDPOINT, json=site_data) + assert response.status_code == 201 + assert SubscriptionTable.query.count() == 1 + + # invalid data, missing hostname and invalid router_lo_ipv6_address + router_data.pop("hostname") + router_data["router_lo_ipv6_address"] = "invalid" + response = test_client.post(ROUTER_IMPORT_ENDPOINT, json=router_data) + assert response.status_code == 422 + assert SubscriptionTable.query.count() == 1 + response = response.json() + assert response["detail"][0]["loc"] == ["body", "hostname"] + assert response["detail"][0]["msg"] == "field required" + assert response["detail"][1]["loc"] == ["body", "router_lo_ipv6_address"] + assert response["detail"][1]["msg"] == "value is not a valid IPv6 address" + + def test_import_iptrunk_successful_with_real_process(test_client, iptrunk_data, mock_routers): response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 201 diff --git a/test/imports/test_imports_iptrunk.py b/test/imports/test_imports_iptrunk.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/test_imports.py b/test/test_imports.py deleted file mode 100644 index 9857e4094bd7bc6dde33837bd9b113b60cf28299..0000000000000000000000000000000000000000 --- a/test/test_imports.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest -from faker import Faker -from orchestrator.db import SubscriptionTable -from orchestrator.services import subscriptions - -from gso.products.product_blocks.router import RouterRole, RouterVendor -from gso.products.product_blocks.site import SiteTier - - -class TestImportEndpoints: - @pytest.fixture(autouse=True) - def setup(self, test_client): - self.faker = Faker() - self.client = test_client - self.site_import_endpoint = "/api/v1/imports/sites" - self.router_import_endpoint = "/api/v1/imports/routers" - self.site_data = { - "site_name": self.faker.name(), - "site_city": self.faker.city(), - "site_country": self.faker.country(), - "site_country_code": self.faker.country_code(), - "site_latitude": float(self.faker.latitude()), - "site_longitude": float(self.faker.longitude()), - "site_bgp_community_id": self.faker.pyint(), - "site_internal_id": self.faker.pyint(), - "site_tier": SiteTier.TIER1, - "site_ts_address": self.faker.ipv4(), - "customer": "GÉANT", - } - self.router_data = { - "hostname": "127.0.0.1", - "router_role": RouterRole.PE, - "router_vendor": RouterVendor.JUNIPER, - "router_site": self.site_data["site_name"], - "ts_port": 1234, - "customer": "GÉANT", - "is_ias_connected": True, - "router_lo_ipv4_address": self.faker.ipv4(), - "router_lo_ipv6_address": self.faker.ipv6(), - "router_lo_iso_address": "TestAddress", - } - - def test_import_site_endpoint(self): - assert SubscriptionTable.query.all() == [] - # Post data to the endpoint - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert response.status_code == 201 - assert "detail" in response.json() - assert "pid" in response.json() - subscription = subscriptions.retrieve_subscription_by_subscription_instance_value( - resource_type="site_name", value=self.site_data["site_name"] - ) - assert subscription is not None - self.site_data.pop("customer") - - def test_import_site_endpoint_with_existing_site(self): - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert SubscriptionTable.query.count() == 1 - assert response.status_code == 201 - - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert response.status_code == 409 - assert SubscriptionTable.query.count() == 1 - - def test_import_site_endpoint_with_invalid_data(self): - # invalid data, missing site_latitude and invalid site_longitude - self.site_data.pop("site_latitude") - self.site_data["site_longitude"] = "invalid" - assert SubscriptionTable.query.count() == 0 - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert response.status_code == 422 - assert SubscriptionTable.query.count() == 0 - response = response.json() - assert response["detail"][0]["loc"] == ["body", "site_latitude"] - assert response["detail"][0]["msg"] == "field required" - assert response["detail"][1]["loc"] == ["body", "site_longitude"] - assert response["detail"][1]["msg"] == "value is not a valid float" - - def test_import_router_endpoint(self): - # Create a site first - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert response.status_code == 201 - assert SubscriptionTable.query.count() == 1 - - response = self.client.post(self.router_import_endpoint, json=self.router_data) - assert response.status_code == 201 - assert SubscriptionTable.query.count() == 2 - - def test_import_router_endpoint_with_invalid_data(self): - response = self.client.post(self.site_import_endpoint, json=self.site_data) - assert response.status_code == 201 - assert SubscriptionTable.query.count() == 1 - - # invalid data, missing hostname and invalid router_lo_ipv6_address - self.router_data.pop("hostname") - self.router_data["router_lo_ipv6_address"] = "invalid" - response = self.client.post(self.router_import_endpoint, json=self.router_data) - assert response.status_code == 422 - assert SubscriptionTable.query.count() == 1 - response = response.json() - assert response["detail"][0]["loc"] == ["body", "hostname"] - assert response["detail"][0]["msg"] == "field required" - assert response["detail"][1]["loc"] == ["body", "router_lo_ipv6_address"] - assert response["detail"][1]["msg"] == "value is not a valid IPv6 address"