diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index d5b831302d169f0ce8adbc8835d2909eee7fd51b..0000000000000000000000000000000000000000 --- a/.pylintrc +++ /dev/null @@ -1,9 +0,0 @@ -[MAIN] -extension-pkg-whitelist=pydantic - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# Note that it does not contain TODO, only the default FIXME and XXX -notes=FIXME, - XXX diff --git a/Changelog.md b/Changelog.md index b0b8daefa2b5c71193b8c366899efee496dc60a2..070611980481aab0f9a7eb27788e3b4e0390cee5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,12 +23,6 @@ ## [2.18] - 2024-10-01 - Use solo pool for Celery workers -## [2.17] - 2024-09-30 -- NOTHING IS HERE (JENKINS ISSUE) - -## [2.16] - 2024-09-30 -- NOTHING IS HERE (JENKINS ISSUE) - ## [2.15] - 2024-09-30 - Show current license usage when updating Kentik license of a router - Fix the bug of clearing all the AE members and creating new objects instead of updating it. diff --git a/gso/cli/imports.py b/gso/cli/imports.py index 5415e60c9d3cdba2e3478b257b356a9b6ae94df7..f1553d222d34c974584c45ef15cff56dfa55c371 100644 --- a/gso/cli/imports.py +++ b/gso/cli/imports.py @@ -31,6 +31,7 @@ from gso.services.partners import ( get_partner_by_name, ) from gso.services.subscriptions import ( + get_active_edge_port_subscriptions, get_active_router_subscriptions, get_active_subscriptions_by_field_and_value, get_subscriptions, @@ -38,7 +39,7 @@ from gso.services.subscriptions import ( from gso.utils.shared_enums import SBPType, Vendor from gso.utils.types.base_site import BaseSiteValidatorModel from gso.utils.types.interfaces import LAGMember, LAGMemberList, PhysicalPortCapacity -from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPv6AddressType, PortNumber +from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask, PortNumber app: typer.Typer = typer.Typer() @@ -218,9 +219,6 @@ class EdgePortImportModel(BaseModel): class NRENL3CoreServiceImportModel(BaseModel): """Import :term:`NREN` L3 Core Service model.""" - partner: str - service_binding_ports: list["NRENL3CoreServiceImportModel.ServiceBindingPort"] - class BaseBGPPeer(BaseModel): """Base BGP Peer model.""" @@ -248,11 +246,37 @@ class NRENL3CoreServiceImportModel(BaseModel): vlan_id: VLAN_ID custom_firewall_filters: bool = False ipv4_address: IPv4AddressType + ipv4_mask: IPV4Netmask ipv6_address: IPv6AddressType - rtbh_enabled: bool = True + ipv6_mask: IPV6Netmask is_multi_hop: bool = True bgp_peers: list["NRENL3CoreServiceImportModel.BaseBGPPeer"] + partner: str + service_binding_ports: list[ServiceBindingPort] + + @field_validator("partner") + def check_if_partner_exists(cls, value: str) -> str: + """Validate that the partner exists.""" + try: + get_partner_by_name(value) + except PartnerNotFoundError as e: + msg = f"Partner {value} not found" + raise ValueError(msg) from e + + return value + + @field_validator("service_binding_ports") + def validate_node(cls, value: list[ServiceBindingPort]) -> list[ServiceBindingPort]: + """Check if the Service Binding Ports are valid.""" + edge_ports = [str(subscription["subscription_id"]) for subscription in get_active_edge_port_subscriptions()] + for sbp in value: + if sbp.edge_port not in edge_ports: + msg = f"Edge Port {sbp.edge_port} not found" + raise ValueError(msg) + + return value + T = TypeVar( "T", diff --git a/gso/workflows/edge_port/create_edge_port.py b/gso/workflows/edge_port/create_edge_port.py index 97b9557768a954c4d7251444677d44afafb5aad9..40ebd4b0918926b84199fda07b736a61935fb8b5 100644 --- a/gso/workflows/edge_port/create_edge_port.py +++ b/gso/workflows/edge_port/create_edge_port.py @@ -29,7 +29,6 @@ from gso.utils.helpers import ( partner_choice, validate_edge_port_number_of_members_based_on_lacp, ) -from gso.services.partners import get_partner_by_id from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity from gso.utils.types.tt_number import TTNumber @@ -185,7 +184,9 @@ def allocate_interfaces_in_netbox(subscription: EdgePortProvisioning) -> None: @step("[DRY RUN] Create edge port") -def create_edge_port_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str) -> LSOState: +def create_edge_port_dry( + subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str +) -> LSOState: """Create a new edge port in the network as a dry run.""" extra_vars = { "dry_run": True, @@ -203,7 +204,9 @@ def create_edge_port_dry(subscription: dict[str, Any], tt_number: str, process_i @step("[FOR REAL] Create edge port") -def create_edge_port_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str) -> LSOState: +def create_edge_port_real( + subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str +) -> LSOState: """Create a new edge port in the network for real.""" extra_vars = { "dry_run": False, diff --git a/gso/workflows/edge_port/create_imported_edge_port.py b/gso/workflows/edge_port/create_imported_edge_port.py index 5f9fe262baa2cdc6f87a24e8938227947ae29ccd..b932175f072c4b03d414777ca9896797e9fdf81f 100644 --- a/gso/workflows/edge_port/create_imported_edge_port.py +++ b/gso/workflows/edge_port/create_imported_edge_port.py @@ -106,7 +106,7 @@ def initialize_subscription( target=Target.CREATE, ) def create_imported_edge_port() -> StepList: - """Import a Edge Port without provisioning it.""" + """Import an Edge Port without provisioning it.""" return ( begin >> create_subscription diff --git a/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py index 0bad4c86facfd7f26b9978121507d4831d333cc9..f5a2e2ea00588c4ea9893f71bbb5772c26c7f0cf 100644 --- a/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py +++ b/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py @@ -59,7 +59,7 @@ def initial_input_form_generator() -> FormGenerator: class ImportNRENL3CoreServiceForm(FormPage): partner: str service_binding_ports: list[ServiceBindingPort] - nren_l3_core_service_type: NRENL3CoreServiceType + service_type: NRENL3CoreServiceType user_input = yield ImportNRENL3CoreServiceForm @@ -67,12 +67,12 @@ def initial_input_form_generator() -> FormGenerator: @step("Create subscription") -def create_subscription(partner: str, nren_l3_core_service_type: NRENL3CoreServiceType) -> dict: +def create_subscription(partner: str, service_type: NRENL3CoreServiceType) -> dict: """Create a new subscription object in the database.""" partner_id = get_partner_by_name(partner)["partner_id"] - if nren_l3_core_service_type == NRENL3CoreServiceType.GEANT_IP: + if service_type == NRENL3CoreServiceType.GEANT_IP: product_id = get_product_id_by_name(ProductName.IMPORTED_GEANT_IP) - elif nren_l3_core_service_type == NRENL3CoreServiceType.IAS: + elif service_type == NRENL3CoreServiceType.IAS: product_id = get_product_id_by_name(ProductName.IMPORTED_IAS) subscription = ImportedNRENL3CoreServiceInactive.from_product_id(product_id, partner_id) return {"subscription": subscription, "subscription_id": subscription.subscription_id} diff --git a/pyproject.toml b/pyproject.toml index e9979242b773ca0eb5812755a4a240733cca96fa..2ae64df63bb2609ee3196b6d31605957a5b78a0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,3 +116,6 @@ filterwarnings = [ "ignore", "default:::gso", ] + +[tool.coverage.run] +omit = ["gso/migrations/*"] diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py index 55f7db15ebd2a573e2bc7915a224ea33339e12c7..b0ec0adb8c1209492f049f729ad27b08028a0d87 100644 --- a/test/cli/test_imports.py +++ b/test/cli/test_imports.py @@ -7,12 +7,14 @@ import pytest from gso.cli.imports import ( import_edge_port, import_iptrunks, + import_nren_l3_core_service, import_office_routers, import_opengear, import_routers, import_sites, import_super_pop_switches, ) +from gso.products.product_blocks.bgp_session import IPFamily from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_blocks.router import RouterRole @@ -233,6 +235,103 @@ def edge_port_data(temp_file, faker, nokia_router_subscription_factory, partner_ return _edge_port_data +@pytest.fixture() +def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscription_factory): + def _nren_l3_core_service_data(**kwargs): + nren_l3_core_service_data = { + "partner": partner_factory()["name"], + "service_type": "IMPORTED IAS", + "service_binding_ports": [ + { + "edge_port": edge_port_subscription_factory(), + "ap_type": "PRIMARY", + "geant_sid": faker.geant_sid(), + "vlan_id": faker.vlan_id(), + "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), + "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), + "bgp_peers": [ + { + "bfd_enabled": True, + "bfd_interval": faker.pyint(), + "bfd_multiplier": faker.pyint(), + "has_custom_policies": True, + "authentication_key": faker.password(), + "multipath_enabled": False, + "send_default_route": True, + "is_passive": True, + "peer_address": faker.ipv4(), + "families": [IPFamily.V4UNICAST, IPFamily.V4MULTICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + { + "bfd_enabled": True, + "bfd_interval": faker.pyint(), + "bfd_multiplier": faker.pyint(), + "has_custom_policies": True, + "authentication_key": faker.password(), + "multipath_enabled": False, + "send_default_route": True, + "is_passive": True, + "peer_address": faker.ipv6(), + "families": [IPFamily.V6UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + ], + }, + { + "edge_port": edge_port_subscription_factory(), + "ap_type": "BACKUP", + "geant_sid": faker.geant_sid(), + "vlan_id": faker.vlan_id(), + "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), + "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), + "bgp_peers": [ + { + "bfd_enabled": True, + "bfd_interval": faker.pyint(), + "bfd_multiplier": faker.pyint(), + "has_custom_policies": True, + "authentication_key": faker.password(), + "multipath_enabled": False, + "send_default_route": True, + "is_passive": True, + "peer_address": faker.ipv4(), + "families": [IPFamily.V4UNICAST, IPFamily.V4MULTICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + { + "bfd_enabled": True, + "bfd_interval": faker.pyint(), + "bfd_multiplier": faker.pyint(), + "has_custom_policies": True, + "authentication_key": faker.password(), + "multipath_enabled": False, + "send_default_route": True, + "is_passive": True, + "peer_address": faker.ipv6(), + "families": [IPFamily.V6UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + ], + }, + ], + } + nren_l3_core_service_data.update(**kwargs) + + temp_file.write_text(json.dumps([nren_l3_core_service_data])) + return {"path": str(temp_file), "data": nren_l3_core_service_data} + + return _nren_l3_core_service_data + + ########### # TESTS # ########### @@ -447,3 +546,96 @@ def test_import_edge_port_with_invalid_partner(mock_start_process, mock_sleep, e captured_output, _ = capfd.readouterr() assert "Partner INVALID not found" in captured_output assert mock_start_process.call_count == 0 + + +@patch("gso.cli.imports.time.sleep") +@patch("gso.cli.imports.start_process") +def test_import_nren_l3_core_service_success(mock_start_process, mock_sleep, nren_l3_core_service_data, capfd): + import_nren_l3_core_service(nren_l3_core_service_data()["path"]) + assert mock_start_process.call_count == 1 + + +@patch("gso.cli.imports.time.sleep") +@patch("gso.cli.imports.start_process") +def test_import_nren_l3_core_service_with_invalid_partner( + mock_start_process, mock_sleep, nren_l3_core_service_data, capfd +): + broken_data = nren_l3_core_service_data(partner="INVALID") + import_nren_l3_core_service(broken_data["path"]) + + captured_output, _ = capfd.readouterr() + assert "Partner INVALID not found" in captured_output + assert mock_start_process.call_count == 0 + + +@patch("gso.cli.imports.time.sleep") +@patch("gso.cli.imports.start_process") +def test_import_nren_l3_core_service_with_invalid_edge_port( + mock_start_process, mock_sleep, faker, nren_l3_core_service_data, edge_port_subscription_factory, capfd +): + fake_uuid = faker.uuid4() + broken_data = nren_l3_core_service_data( + service_binding_ports=[ + { + "edge_port": fake_uuid, + "ap_type": "PRIMARY", + "geant_sid": faker.geant_sid(), + "vlan_id": faker.vlan_id(), + "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), + "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), + "bgp_peers": [ + { + "bfd_enabled": False, + "authentication_key": faker.password(), + "peer_address": faker.ipv4(), + "families": [IPFamily.V4UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + { + "bfd_enabled": False, + "authentication_key": faker.password(), + "peer_address": faker.ipv6(), + "families": [IPFamily.V6UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + ], + }, + { + "edge_port": edge_port_subscription_factory(), + "ap_type": "BACKUP", + "geant_sid": faker.geant_sid(), + "vlan_id": faker.vlan_id(), + "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), + "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), + "bgp_peers": [ + { + "bfd_enabled": False, + "authentication_key": faker.password(), + "peer_address": faker.ipv4(), + "families": [IPFamily.V4UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + { + "bfd_enabled": False, + "authentication_key": faker.password(), + "peer_address": faker.ipv6(), + "families": [IPFamily.V6UNICAST], + "is_multi_hop": False, + "rtbh_enabled": True, + }, + ], + }, + ] + ) + import_nren_l3_core_service(broken_data["path"]) + + captured_output, _ = capfd.readouterr() + assert f"Edge Port {fake_uuid} not found" in captured_output + assert mock_start_process.call_count == 0 diff --git a/test/fixtures/nren_l3_core_service_fixtures.py b/test/fixtures/nren_l3_core_service_fixtures.py index 059f2d37d758d7a714160d208abf2a95c7176623..d0fd069a03b4b969e2fd286aaa7f57f5716f10c7 100644 --- a/test/fixtures/nren_l3_core_service_fixtures.py +++ b/test/fixtures/nren_l3_core_service_fixtures.py @@ -76,7 +76,7 @@ def service_binding_port_factory(faker, bgp_session_subscription_factory, edge_p return ServiceBindingPort.new( subscription_id=uuid4(), is_tagged=is_tagged, - vlan_id=vlan_id or faker.pyint(min_value=1, max_value=4096), + vlan_id=vlan_id or faker.vlan_id(), sbp_type=sbp_type, ipv4_address=ipv4_address or faker.ipv4(), ipv4_mask=ipv4_mask or faker.ipv4_netmask(), diff --git a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py index 05982ef1f20d4d5b5b1dff8c1d55ba9683947e91..63aab373915f348d3a8e8e7f7bf161c2e1e5c190 100644 --- a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py +++ b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py @@ -13,7 +13,7 @@ def test_create_imported_nren_l3_core_service_success( ): creation_form_input_data = { "partner": partner_factory()["name"], - "nren_l3_core_service_type": l3_core_service_type, + "service_type": l3_core_service_type, "service_binding_ports": [ { "edge_port": edge_port_subscription_factory(),