diff --git a/blazar/plugins/networks/network_plugin.py b/blazar/plugins/networks/network_plugin.py index 4f5a5747..7864dbc1 100644 --- a/blazar/plugins/networks/network_plugin.py +++ b/blazar/plugins/networks/network_plugin.py @@ -17,6 +17,7 @@ import datetime import concurrent.futures from random import shuffle +import ipaddress from keystoneauth1 import exceptions as keystone_excptions from neutronclient.common import exceptions as neutron_ex @@ -795,6 +796,47 @@ def _filter_networks_by_properties(self, network_properties, return db_api.network_list() +def remove_subnet_route_from_router(router_id, subnet_id, dry_run): + """Removes the route in the router pointing to the subnet + + Args: + router_id (str): The router in which routes need to be checked and removed + subnet_id (str): the subnet to which routes should be removed + """ + neutron_client = neutron.BlazarNeutronClient() + def is_route_pointing_to_subnet(route): + if 'gateway' in route: + next_hop_ip = ipaddress.ip_address(route['gateway']) + elif 'nexthop' in route: + next_hop_ip = ipaddress.ip_address(route['nexthop']) + subnet_cidr = ipaddress.ip_network(subnet['cidr']) + return next_hop_ip in subnet_cidr + try: + subnet = neutron_client.show_subnet(subnet_id).get('subnet', {}) + except neutron_ex.NotFound as e: + LOG.exception(f"Could not get subnet {subnet_id} - {e}") + raise e + try: + router = neutron_client.show_router(router_id).get('router', {}) + except neutron_ex.NotFound as e: + LOG.exception(f"Could not get router {router_id} - {e}") + raise e + # Check if any of the routes in router are pointing to subnet + routes = router.get('routes', []) + subnet_routes = [route for route in routes if is_route_pointing_to_subnet(route)] + for route in subnet_routes: + LOG.warning( + f"Removing the route {route} from router {router['id']} " + f"as it is pointing to {subnet['id']}" + ) + # remove this subnet route + if not dry_run: + neutron_client.remove_extra_routes_from_router( + router_id, + {'router': {'routes': [route]}} + ) + + class NetworkMonitorPlugin(monitor.GeneralMonitorPlugin, neutron.NeutronClientWrapper): """ Monitor plugin for network resources @@ -875,23 +917,28 @@ def process_network(network): port for port in ports_from_network if port["device_owner"] == "network:router_interface" ] - if len(router_ports) == 0: - LOG.debug(f"No router ports for neutron network {network_id_from_neutron} - VLAN {segment_id}") for port in router_ports: for fixed_ip in port["fixed_ips"]: router_id = port["device_id"] - LOG.warning(f"Detaching router {router_id} from Network subnet {fixed_ip['subnet_id']} - VLAN {segment_id}") + subnet_id = fixed_ip["subnet_id"] + try: + remove_subnet_route_from_router(router_id, subnet_id, dry_run) + except Exception as e: + LOG.exception(f"Cannot remove subnet {subnet_id} route in router {router_id} - {e}") + LOG.warning(f"Detaching router {router_id} from Network subnet {subnet_id} - VLAN {segment_id}") if not dry_run: - neutron_client.remove_interface_router( - router_id, { - 'subnet_id': fixed_ip["subnet_id"] - } - ) - for subnet in neutron_client.list_subnets(network_id=network_id_from_neutron)['subnets']: + neutron_client.remove_interface_router(router_id, {'subnet_id': subnet_id}) + for port in ports_from_network: + if not dry_run: + try: + neutron_client.delete_port(port['id']) + except neutron_ex.PortNotFoundClient: + LOG.debug(f"Port {port['id']} not found in neutron") + subnets = neutron_client.list_subnets(network_id=network_id_from_neutron)['subnets'] + for subnet in subnets: LOG.warning(f"Deleting subnet {subnet['id']} - VLAN {segment_id}") if not dry_run: neutron_client.delete_subnet(subnet['id']) - LOG.warning(f"Deleting network {network_id_from_neutron} - VLAN {segment_id}") if not dry_run: neutron_client.delete_network(network_id_from_neutron) diff --git a/blazar/tests/plugins/networks/test_network_plugin.py b/blazar/tests/plugins/networks/test_network_plugin.py index 8e864bbb..cb877335 100644 --- a/blazar/tests/plugins/networks/test_network_plugin.py +++ b/blazar/tests/plugins/networks/test_network_plugin.py @@ -1117,7 +1117,7 @@ def test_network_stuck_in_errored_lease(self): } ] fake_ports = { - 'ports': [{"fixed_ips": [{"subnet_id": "subnet1"}], + 'ports': [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], "device_id": "router1", "device_owner": "network:router_interface"}] } @@ -1137,6 +1137,12 @@ def test_network_stuck_in_errored_lease(self): neutron_list_ports_patch.return_value = fake_ports neutron_list_subnets_patch = self.patch(neutron.neutron_client.Client, 'list_subnets') neutron_list_subnets_patch.return_value = fake_subnets + neutron_show_subnet_patch = self.patch(neutron.neutron_client.Client, 'show_subnet') + neutron_show_subnet_patch.return_value = {'subnet': {'id': 'subnet1', 'cidr': '192.168.1.0/24'}} + neutron_show_router_patch = self.patch(neutron.neutron_client.Client, 'show_router') + neutron_show_router_patch.return_value = {'router': {'id': 'router1', 'routes': [{'gateway': '192.168.1.1'}]}} + neutron_remove_route_patch = self.patch(neutron.neutron_client.Client, 'remove_extra_routes_from_router') + neutron_delete_port_patch = self.patch(neutron.neutron_client.Client, 'delete_port') neutron_remove_interface_patch = self.patch(neutron.neutron_client.Client, 'remove_interface_router') neutron_delete_subnet_patch = self.patch(neutron.neutron_client.Client, 'delete_subnet') neutron_delete_network_patch = self.patch(neutron.neutron_client.Client, 'delete_network') @@ -1148,6 +1154,10 @@ def test_network_stuck_in_errored_lease(self): ) neutron_delete_subnet_patch.assert_called_once_with("subnet1") neutron_delete_network_patch.assert_called_once_with("neutron1") + neutron_remove_route_patch.assert_called_once_with( + 'router1', + {'router': {'routes': [{'gateway': '192.168.1.1'}]}} + ) self.assertEqual(result, ([], [])) def test_network_stuck_in_active_lease(self): @@ -1158,7 +1168,7 @@ def test_network_stuck_in_active_lease(self): } ] fake_ports = { - 'ports': [{"fixed_ips": [{"subnet_id": "subnet1"}], + 'ports': [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], "device_id": "router1", "device_owner": "network:router_interface"}] } @@ -1186,7 +1196,7 @@ def test_network_stuck_in_errored_lease_dry_run(self): } ] fake_ports = { - 'ports': [{"fixed_ips": [{"subnet_id": "subnet1"}], + 'ports': [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], "device_id": "router1", "device_owner": "network:router_interface"}] } @@ -1213,7 +1223,7 @@ def test_network_stuck_in_errored_lease_network_not_found(self): } ] fake_ports = { - 'ports': [{"fixed_ips": [{"subnet_id": "subnet1"}], + 'ports': [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], "device_id": "router1", "device_owner": "network:router_interface"}] } @@ -1242,7 +1252,7 @@ def test_network_stuck_in_errored_lease_no_router_ports(self): } ] fake_ports = { - 'ports': [{"fixed_ips": [{"subnet_id": "subnet1"}], + 'ports': [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], "device_id": "dhcp1", "device_owner": "network:dhcp"}] } @@ -1265,8 +1275,101 @@ def test_network_stuck_in_errored_lease_no_router_ports(self): neutron_remove_interface_patch = self.patch(neutron.neutron_client.Client, 'remove_interface_router') neutron_delete_subnet_patch = self.patch(neutron.neutron_client.Client, 'delete_subnet') neutron_delete_network_patch = self.patch(neutron.neutron_client.Client, 'delete_network') + neutron_delete_port_patch = self.patch(neutron.neutron_client.Client, 'delete_port') result = self.fake_network_monitor_plugin.poll_resource_failures() self.assertFalse(neutron_remove_interface_patch.called) neutron_delete_subnet_patch.assert_called_once_with("subnet1") neutron_delete_network_patch.assert_called_once_with("neutron1") - self.assertEqual(result, ([], [])) \ No newline at end of file + neutron_delete_port_patch.assert_called() + self.assertEqual(result, ([], [])) + + def test_network_stuck_in_errored_lease_subnet_routes(self): + networks_from_blazar = [ + { + 'id': 'network1', + 'segment_id': 'segment1' + } + ] + fake_ports = { + "ports": [{"id":"port1", "fixed_ips": [{"subnet_id": "subnet1"}], + "device_id": "router1", + "device_owner": "network:router_interface"}], + } + fake_neutron_networks = { + 'networks': [{"id": "neutron1", "provider:segmentation_id": "segment1"}] + } + fake_subnets = {'subnets': [{"id": "subnet1"}]} + network_list = self.patch(db_api, 'network_list') + network_list.return_value = networks_from_blazar + get_reservations = self.patch(db_utils, 'get_most_recent_reservation_info_by_network_id') + get_reservations.side_effect = [ + {'id': "1", 'status': status.reservation.ERROR}, + ] + neutron_list_networks_patch = self.patch(neutron.neutron_client.Client, 'list_networks') + neutron_list_networks_patch.return_value = fake_neutron_networks + neutron_list_ports_patch = self.patch(neutron.neutron_client.Client, 'list_ports') + neutron_list_ports_patch.return_value = fake_ports + neutron_list_subnets_patch = self.patch(neutron.neutron_client.Client, 'list_subnets') + neutron_list_subnets_patch.return_value = fake_subnets + neutron_show_subnet_patch = self.patch(neutron.neutron_client.Client, 'show_subnet') + neutron_show_subnet_patch.return_value = {'subnet': {'id': 'subnet1', 'cidr': '192.168.1.0/24'}} + neutron_show_router_patch = self.patch(neutron.neutron_client.Client, 'show_router') + neutron_show_router_patch.return_value = {'router': {'id': 'router1', 'routes': [{'gateway': '192.168.1.1'}]}} + neutron_remove_route_patch = self.patch(neutron.neutron_client.Client, 'remove_extra_routes_from_router') + neutron_remove_interface_patch = self.patch(neutron.neutron_client.Client, 'remove_interface_router') + neutron_delete_subnet_patch = self.patch(neutron.neutron_client.Client, 'delete_subnet') + neutron_delete_network_patch = self.patch(neutron.neutron_client.Client, 'delete_network') + neutron_delete_port_patch = self.patch(neutron.neutron_client.Client, 'delete_port') + result = self.fake_network_monitor_plugin.poll_resource_failures() + neutron_remove_interface_patch.assert_called_once_with( + "router1", { + 'subnet_id': "subnet1" + } + ) + neutron_delete_subnet_patch.assert_called_once_with("subnet1") + neutron_delete_network_patch.assert_called_once_with("neutron1") + neutron_delete_port_patch.assert_called_once_with("port1") + neutron_remove_route_patch.assert_called_once_with( + 'router1', + {'router': {'routes': [{'gateway': '192.168.1.1'}]}} + ) + self.assertEqual(result, ([], [])) + + +class TestRemoveSubnetRouteFromRouter(tests.TestCase): + def test_remove_subnet_route_from_router_same_cidr(self): + neutron_show_subnet_patch = self.patch(neutron.neutron_client.Client, 'show_subnet') + neutron_show_subnet_patch.return_value = {'subnet': {'id': 'subnet1', 'cidr': '192.168.1.0/24'}} + neutron_show_router_patch = self.patch(neutron.neutron_client.Client, 'show_router') + neutron_show_router_patch.return_value = {'router': {'id': 'router1', 'routes': [{'gateway': '192.168.1.1'}]}} + neutron_remove_route_patch = self.patch(neutron.neutron_client.Client, 'remove_extra_routes_from_router') + + router_id = 'router123' + subnet_id = 'subnet456' + dry_run = False + + network_plugin.remove_subnet_route_from_router(router_id, subnet_id, dry_run) + + neutron_show_subnet_patch.assert_called_once_with(subnet_id) + neutron_show_router_patch.assert_called_once_with(router_id) + neutron_remove_route_patch.assert_called_once_with( + router_id, + {'router': {'routes': [{'gateway': '192.168.1.1'}]}} + ) + + def test_remove_subnet_route_from_router_diff_cidr(self): + neutron_show_subnet_patch = self.patch(neutron.neutron_client.Client, 'show_subnet') + neutron_show_subnet_patch.return_value = {'subnet': {'id': 'subnet1', 'cidr': '192.168.1.0/24'}} + neutron_show_router_patch = self.patch(neutron.neutron_client.Client, 'show_router') + neutron_show_router_patch.return_value = {'router': {'id': 'router1', 'routes': [{'gateway': '192.168.2.1'}]}} + neutron_remove_route_patch = self.patch(neutron.neutron_client.Client, 'remove_extra_routes_from_router') + + router_id = 'router123' + subnet_id = 'subnet456' + dry_run = False + + network_plugin.remove_subnet_route_from_router(router_id, subnet_id, dry_run) + + neutron_show_subnet_patch.assert_called_once_with(subnet_id) + neutron_show_router_patch.assert_called_once_with(router_id) + neutron_remove_route_patch.assert_not_called()