diff --git a/lso/routes/ip_trunk.py b/lso/routes/ip_trunk.py index e12c7fff2d3ba87b5c95968696146955930b8197..ae3b2218b5e9a1ef255c02b0a070285739d2916e 100644 --- a/lso/routes/ip_trunk.py +++ b/lso/routes/ip_trunk.py @@ -42,6 +42,18 @@ class IPTrunkModifyParams(IPTrunkParams): old_subscription: dict +class IPTrunkMigrationParams(IPTrunkParams): + """Additional parameters for migrating an IPTrunk.""" + + #: Whether this playbook execution should be a dry run, or run for real. Defaults to ``True`` for obvious reasons, + #: also making it an optional parameter. + dry_run: Optional[bool] = True + #: The new Router that this IP Trunk is migrating to. + new_side: dict + #: An Ansible playbook verb that is passed along for indicating the phase of the migration that is performed. + verb: str + + class IPTrunkCheckParams(IPTrunkParams): """Additional parameters for checking an IPtrunk.""" @@ -173,7 +185,38 @@ def check_ip_trunk(params: IPTrunkCheckParams) -> PlaybookLaunchResponse: return run_playbook( playbook_path=path.join(config_params.ansible_playbooks_root_dir, "iptrunks_checks.yaml"), - inventory=params.subscription["iptrunk"]["iptrunk_sideA_node"]["router_fqdn"], + inventory=params.subscription["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["router_fqdn"], + extra_vars=extra_vars, + callback=params.callback, + ) + + +@router.post("/migrate") +def migrate_ip_trunk(params: IPTrunkMigrationParams) -> PlaybookLaunchResponse: + """Launch a playbook to provision a new IP trunk service. + + The response will contain either a job ID, or error information. + + :param params: The parameters that define the new subscription object that is to be migrated. + :type params: :class:`IPTrunkMigrationParams` + :return: Response from the Ansible runner, including a run ID. + :rtype: :class:`lso.playbook.PlaybookLaunchResponse` + """ + extra_vars = { + "wfo_trunk_json": params.subscription, + "new_side": params.new_side, + "dry_run": str(params.dry_run), + "verb": params.verb, + } + + return run_playbook( + playbook_path=path.join(config_params.ansible_playbooks_root_dir, "iptrunks.yaml"), + inventory=str( + params.subscription["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["router_fqdn"] + + "\n" + + params.subscription["iptrunk"]["iptrunk_sides"][1]["iptrunk_side_node"]["router_fqdn"] + + "\n" + ), extra_vars=extra_vars, callback=params.callback, ) diff --git a/test/routes/test_ip_trunk.py b/test/routes/test_ip_trunk.py index 1ca8e32e9af2edcfbb7b1ed88dfb65e08ae253c9..3f999aa9503a09177aad3e28f135c2b25974b56e 100644 --- a/test/routes/test_ip_trunk.py +++ b/test/routes/test_ip_trunk.py @@ -16,80 +16,137 @@ _SUBSCRIPTION_OBJECT = { "iptrunk_description": "A description for this trunk", "iptrunk_isis_metric": 9000, "iptrunk_minimum_links": 1, - "iptrunk_sideA_ae_geant_a_sid": "GA-00000", - "iptrunk_sideA_ae_iface": "ae0", - "iptrunk_sideA_ae_members": ["ge-0/0/0"], - "iptrunk_sideA_ae_members_description": ["this is the first interface on side A"], - "iptrunk_sideA_node": { - "router_fqdn": "rtx.city.country.geant.net", - "router_ias_lt_ipv4_network": "1.0.0.0/31", - "router_ias_lt_ipv6_network": "dead:beef::3/126", - "router_lo_ipv4_address": "1.0.0.0", - "router_lo_ipv6_address": "dead:beef::", - "router_lo_iso_address": "00.0000.0000.0000.0000.0000.00", - "router_role": "p", - "router_si_ipv4_network": "0.0.1.0/31", - "router_site": { - "name": "SiteBlock", + "iptrunk_sides": [ + { + "name": "IptrunkSideBlock", "label": None, - "site_city": "City", - "site_name": "city", - "site_tier": "1", - "site_country": "Country", - "site_latitude": 0.0, - "site_longitude": 0.0, - "site_internal_id": 0, - "site_country_code": "XX", - "owner_subscription_id": "0", - "site_bgp_community_id": 0, - "subscription_instance_id": "0", - "site_ts_address": "127.0.0.2", + "iptrunk_side_node": { + "name": "RouterBlock", + "label": None, + "router_fqdn": "rt1.city.country.geant.net", + "router_role": "p", + "router_site": { + "name": "SiteBlock", + "label": None, + "site_city": "City", + "site_name": "city", + "site_tier": "1", + "site_country": "Country", + "site_latitude": 1, + "site_longitude": 1, + "site_ts_address": "0.0.0.0", + "site_internal_id": 1, + "site_country_code": "xxx", + "owner_subscription_id": "b5146e62-da79-4791-b703-d03b0ebeebf8", + "site_bgp_community_id": 1, + "subscription_instance_id": "039e03e5-5c09-4236-8d28-cd569e04315e", + }, + "router_vendor": "juniper", + "router_ts_port": 22222, + "router_access_via_ts": True, + "owner_subscription_id": "4a0001f1-459d-46f5-9a85-b8177e1bbc1b", + "router_lo_iso_address": "49.51e5.0001.0620.4009.6014.00", + "router_lo_ipv4_address": "0.0.0.0", + "router_lo_ipv6_address": "::", + "router_si_ipv4_network": "0.0.0.0/31", + "router_is_ias_connected": True, + "subscription_instance_id": "2242883e-a581-4ce1-919c-9c986ded57f6", + "router_ias_lt_ipv4_network": "0.0.0.0/31", + "router_ias_lt_ipv6_network": "::/126", + }, + "iptrunk_side_ae_iface": "ae1", + "owner_subscription_id": "c9ddbe14-e107-4749-82ac-e22091cdb132", + "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"], + "subscription_instance_id": "6276fac5-9d31-4c9a-9247-48f02a19f151", + "iptrunk_side_ae_geant_a_sid": "SID-11112", + "iptrunk_side_ae_members_description": ["first one", "second one"], }, - "router_ts_port": 22, - "router_vendor": "vendor", - "owner_subscription_id": "0", - "subscription_instance_id": "0", - }, - "iptrunk_sideB_ae_geant_a_sid": "GA-00002", - "iptrunk_sideB_ae_iface": "ae0", - "iptrunk_sideB_ae_members": ["ge-0/0/0"], - "iptrunk_sideB_ae_members_description": ["this is the first interface side B"], - "iptrunk_sideB_node": { - "router_fqdn": "rtx.town.country.geant.net", - "router_ias_lt_ipv4_network": "0.0.0.0/31", - "router_ias_lt_ipv6_network": "deaf:beef::1/126", - "router_lo_ipv4_address": "0.0.0.0", - "router_lo_ipv6_address": "dead:beef::2", - "router_lo_iso_address": "00.0000.0000.0000.0000.0000.00", - "router_role": "p", - "router_si_ipv4_network": "0.1.0.0/31", - "router_site": { - "name": "SiteBlock", + { + "name": "IptrunkSideBlock", "label": None, - "site_city": "Town", - "site_name": "town", - "site_tier": "1", - "site_country": "Country", - "site_latitude": 0.0, - "site_longitude": 0.0, - "site_internal_id": 1, - "site_country_code": "xx", - "owner_subscription_id": "0", - "site_bgp_community_id": 2, - "subscription_instance_id": "0", - "site_ts_address": "127.0.0.2", + "iptrunk_side_node": { + "name": "RouterBlock", + "label": None, + "router_fqdn": "rt2.city.country.geant.net", + "router_role": "p", + "router_site": { + "name": "SiteBlock", + "label": None, + "site_city": "City", + "site_name": "city", + "site_tier": "1", + "site_country": "Country", + "site_latitude": 1, + "site_longitude": 1, + "site_ts_address": "0.0.0.0", + "site_internal_id": 2, + "site_country_code": "country", + "owner_subscription_id": "93cba8dc-7424-44c0-8872-13159df93042", + "site_bgp_community_id": 2, + "subscription_instance_id": "6bf4f274-6496-438d-9dba-9c3984d0ec07", + }, + "router_vendor": "juniper", + "router_ts_port": 11111, + "router_access_via_ts": True, + "owner_subscription_id": "9cb1fc7d-9608-42ce-aacc-2a97f9620a91", + "router_lo_iso_address": "49.51e5.0001.0620.4009.6066.00", + "router_lo_ipv4_address": "0.0.0.0", + "router_lo_ipv6_address": "::", + "router_si_ipv4_network": "0.0.0.0/31", + "router_is_ias_connected": True, + "subscription_instance_id": "6d09394e-658b-4e55-8b1f-8b812d59f5a1", + "router_ias_lt_ipv4_network": "0.0.0.0/31", + "router_ias_lt_ipv6_network": "::/126", + }, + "iptrunk_side_ae_iface": "ae1", + "owner_subscription_id": "c9ddbe14-e107-4749-82ac-e22091cdb132", + "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"], }, - "router_ts_port": 22, - "router_vendor": "vendor", - "owner_subscription_id": "0", - "subscription_instance_id": "0", - }, - "iptrunk_speed": "1", - "iptrunk_type": "Dark_fiber", + ], }, "status": "provisioning", } +_MIGRATION_OBJECT = { + "new_node": { + "name": "RouterBlock", + "label": None, + "router_fqdn": "rt2.city.country.geant.net", + "router_role": "p", + "router_site": { + "name": "SiteBlock", + "label": None, + "site_city": "City", + "site_name": "city", + "site_tier": "1", + "site_country": "Country", + "site_latitude": 1, + "site_longitude": 1, + "site_ts_address": "0.0.0.0", + "site_internal_id": 2, + "site_country_code": "country", + "owner_subscription_id": "93cba8dc-7424-44c0-8872-13159df93042", + "site_bgp_community_id": 2, + "subscription_instance_id": "6bf4f274-6496-438d-9dba-9c3984d0ec07", + }, + "router_vendor": "juniper", + "router_ts_port": 11111, + "router_access_via_ts": True, + "owner_subscription_id": "9cb1fc7d-9608-42ce-aacc-2a97f9620a91", + "router_lo_iso_address": "49.51e5.0001.0620.4009.6066.00", + "router_lo_ipv4_address": "0.0.0.0", + "router_lo_ipv6_address": "::", + "router_si_ipv4_network": "0.0.0.0/31", + "router_is_ias_connected": True, + "subscription_instance_id": "6d09394e-658b-4e55-8b1f-8b812d59f5a1", + "router_ias_lt_ipv4_network": "0.0.0.0/31", + "router_ias_lt_ipv6_network": "::/126", + }, + "new_lag_interface": "ae1", + "new_lag_member_interfaces": ["ge-0/0/0", "ge-0/0/1"], + "replace_index": 0, +} + @responses.activate def test_ip_trunk_provisioning(client: TestClient) -> None: @@ -158,3 +215,28 @@ def test_ip_trunk_deletion(client: TestClient) -> None: responses.assert_call_count(TEST_CALLBACK_URL, 1) assert response["status"] == "ok" + + +@responses.activate +def test_ip_trunk_migration(client: TestClient) -> None: + responses.put(url=TEST_CALLBACK_URL, status=204) + + params = { + "callback": TEST_CALLBACK_URL, + "dry_run": True, + "verb": "migrate", + "subscription": _SUBSCRIPTION_OBJECT, + "new_side": _MIGRATION_OBJECT, + } + + with patch("lso.playbook.ansible_runner.run", new=test_ansible_runner_run) as _: + rv = client.request(url="/api/ip_trunk/migrate", method=responses.POST, json=params) + assert rv.status_code == 200 + response = rv.json() + # Wait a second for the run to finish + time.sleep(1) + + jsonschema.validate(response, PlaybookLaunchResponse.model_json_schema()) + responses.assert_call_count(TEST_CALLBACK_URL, 1) + + assert response["status"] == "ok"