From 333c99ad20ff2ff647cf2b9a0affaea4bb2eb712 Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Fri, 29 Apr 2016 17:35:39 +0800 Subject: [PATCH] Implement router gateway removing When creating external network in top pod, az hint is passed to specify which pod bottom external network is located. So plugin can get bottom router ID with top router ID and bottom pod ID from resource routing table. Plugin fist updates router in top pod to remove gateway, then sends "remove_gateway" request to target bottom pod to update bottom router. Change-Id: I69e411188e758016ea789a91298ccd243bdc31cd --- tricircle/common/client.py | 1 + tricircle/network/plugin.py | 149 +++++++++++++------- tricircle/tests/unit/network/test_plugin.py | 83 +++++++++++ 3 files changed, 179 insertions(+), 54 deletions(-) diff --git a/tricircle/common/client.py b/tricircle/common/client.py index 4747b41..d32f827 100644 --- a/tricircle/common/client.py +++ b/tricircle/common/client.py @@ -514,6 +514,7 @@ class Client(object): volume -> set_bootable -> volume, flag -> none router -> add_interface -> router, body -> none router -> add_gateway -> router, body -> none + router -> remove_gateway -> router -> none server_volume -> create_server_volume -> server_id, volume_id, device=None -> none diff --git a/tricircle/network/plugin.py b/tricircle/network/plugin.py index 8a17943..f396029 100644 --- a/tricircle/network/plugin.py +++ b/tricircle/network/plugin.py @@ -898,52 +898,68 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, query = context.session.query(l3_db.RouterPort) query.filter_by(port_id=port_id, router_id=router_id).delete() - def _update_bottom_router_gateway(self, context, router_id, router_data): + def _add_router_gateway(self, context, router_id, router_data): + # get top external network information ext_net_id = router_data[l3.EXTERNAL_GW_INFO].get('network_id') - if ext_net_id: - # add router gateway - t_ctx = t_context.get_context_from_neutron_context(context) - network = self.get_network(context, ext_net_id) - pod_name = network[az_ext.AZ_HINTS][0] - pod = db_api.get_pod_by_name(t_ctx, pod_name) - b_net_id = db_api.get_bottom_id_by_top_id_pod_name( - t_ctx, ext_net_id, pod_name, t_constants.RT_NETWORK) - t_router = self._get_router(context, router_id) - body = {'router': {'name': router_id, - 'distributed': False}} - _, b_router_id = self._prepare_bottom_element( - t_ctx, t_router['tenant_id'], pod, t_router, - t_constants.RT_ROUTER, body) - b_client = self._get_client(pod_name) - t_info = router_data[l3.EXTERNAL_GW_INFO] - b_info = {'network_id': b_net_id} - if 'enable_snat' in t_info: - b_info['enable_snat'] = t_info['enable_snat'] - if 'external_fixed_ips' in t_info: - fixed_ips = [] - for ip in t_info['external_fixed_ips']: - t_subnet_id = ip['subnet_id'] - b_subnet_id = db_api.get_bottom_id_by_top_id_pod_name( - t_ctx, t_subnet_id, pod_name, - t_constants.RT_SUBNET) - fixed_ips.append({'subnet_id': b_subnet_id, - 'ip_address': ip['ip_address']}) - b_info['external_fixed_ips'] = fixed_ips - b_client.action_routers(t_ctx, 'add_gateway', b_router_id, b_info) + t_ctx = t_context.get_context_from_neutron_context(context) + network = self.get_network(context, ext_net_id) - # create bridge network and attach to router - t_pod = db_api.get_top_pod(t_ctx) - project_id = t_router['tenant_id'] - admin_project_id = 'admin_project_id' - pool_id = self._get_bridge_subnet_pool_id( - t_ctx, context, admin_project_id, t_pod, False) - t_bridge_net, t_bridge_subnet = self._get_bridge_network_subnet( - t_ctx, context, project_id, t_pod, pool_id, False) - (_, _, b_bridge_subnet_id, - b_bridge_net_id) = self._get_bottom_bridge_elements( - context, project_id, pod, t_bridge_net, False, t_bridge_subnet, - None) - is_attach = False + # when creating external network in top pod, pod name is passed via + # az hint parameter, so tricircle plugin knows where to create the + # corresponding bottom external network. here we get bottom external + # network ID from resource routing table. + pod_name = network[az_ext.AZ_HINTS][0] + pod = db_api.get_pod_by_name(t_ctx, pod_name) + b_net_id = db_api.get_bottom_id_by_top_id_pod_name( + t_ctx, ext_net_id, pod_name, t_constants.RT_NETWORK) + + # create corresponding bottom router in the pod where external network + # is located. + t_router = self._get_router(context, router_id) + body = {'router': {'name': router_id, + 'distributed': False}} + _, b_router_id = self._prepare_bottom_element( + t_ctx, t_router['tenant_id'], pod, t_router, + t_constants.RT_ROUTER, body) + + # both router and external network in bottom pod are ready, attach + # external network to router in bottom pod. + b_client = self._get_client(pod_name) + t_info = router_data[l3.EXTERNAL_GW_INFO] + b_info = {'network_id': b_net_id} + if 'enable_snat' in t_info: + b_info['enable_snat'] = t_info['enable_snat'] + if 'external_fixed_ips' in t_info: + fixed_ips = [] + for ip in t_info['external_fixed_ips']: + t_subnet_id = ip['subnet_id'] + b_subnet_id = db_api.get_bottom_id_by_top_id_pod_name( + t_ctx, t_subnet_id, pod_name, + t_constants.RT_SUBNET) + fixed_ips.append({'subnet_id': b_subnet_id, + 'ip_address': ip['ip_address']}) + b_info['external_fixed_ips'] = fixed_ips + b_client.action_routers(t_ctx, 'add_gateway', b_router_id, b_info) + + # when internal network(providing fixed ip) and external network + # (providing floating ip) are in different bottom pods, we utilize a + # bridge network to connect these two networks. here we create the + # bridge network. + t_pod = db_api.get_top_pod(t_ctx) + project_id = t_router['tenant_id'] + pool_id = self._get_bridge_subnet_pool_id( + t_ctx, context, None, t_pod, False) + t_bridge_net, t_bridge_subnet = self._get_bridge_network_subnet( + t_ctx, context, project_id, t_pod, pool_id, False) + (_, _, b_bridge_subnet_id, + b_bridge_net_id) = self._get_bottom_bridge_elements( + context, project_id, pod, t_bridge_net, False, t_bridge_subnet, + None) + + # here we attach the bridge network to the router in bottom pod. to + # make this method reentrant, we check if the interface is already + # attached before attaching the interface. + def _is_bridge_network_attached(): interfaces = b_client.list_ports(t_ctx, filters=[{'key': 'device_id', 'comparator': 'eq', @@ -951,13 +967,39 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, for interface in interfaces: for fixed_ip in interface['fixed_ips']: if fixed_ip['subnet_id'] == b_bridge_subnet_id: - is_attach = True - break - if is_attach: - break - if not is_attach: - b_client.action_routers(t_ctx, 'add_interface', b_router_id, - {'subnet_id': b_bridge_subnet_id}) + return True + return False + + is_attach = _is_bridge_network_attached() + if not is_attach: + b_client.action_routers(t_ctx, 'add_interface', b_router_id, + {'subnet_id': b_bridge_subnet_id}) + + def _remove_router_gateway(self, context, router_id): + t_ctx = t_context.get_context_from_neutron_context(context) + t_router = self._get_router(context, router_id) + gw_port = t_router.gw_port + if not gw_port: + return + ext_net_id = gw_port['network_id'] + t_network = self.get_network(context, ext_net_id) + if az_ext.AZ_HINTS not in t_network: + raise t_exceptions.ExternalNetPodNotSpecify() + if not t_network[az_ext.AZ_HINTS]: + raise t_exceptions.ExternalNetPodNotSpecify() + + pod_name = t_network[az_ext.AZ_HINTS][0] + b_router_id = db_api.get_bottom_id_by_top_id_pod_name( + t_ctx, router_id, pod_name, t_constants.RT_ROUTER) + b_client = self._get_client(pod_name) + b_client.action_routers(t_ctx, 'remove_gateway', b_router_id) + + def _update_bottom_router_gateway(self, context, router_id, router_data): + ext_net_id = router_data[l3.EXTERNAL_GW_INFO].get('network_id') + if ext_net_id: + self._add_router_gateway(context, router_id, router_data) + else: + self._remove_router_gateway(context, router_id) def update_router(self, context, router_id, router): router_data = router['router'] @@ -982,7 +1024,6 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, router = self._get_router(context, router_id) project_id = router['tenant_id'] - admin_project_id = 'admin_project_id' add_by_port, _ = self._validate_interface_info(interface_info) # make sure network not crosses pods # TODO(zhiyuan) support cross-pod tenant network @@ -999,7 +1040,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, # bridge network for E-W networking pool_id = self._get_bridge_subnet_pool_id( - t_ctx, context, admin_project_id, t_pod, True) + t_ctx, context, None, t_pod, True) t_bridge_net, t_bridge_subnet = self._get_bridge_network_subnet( t_ctx, context, project_id, t_pod, pool_id, True) t_bridge_port = self._get_bridge_interface( @@ -1022,7 +1063,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, need_ns_bridge = True if need_ns_bridge: pool_id = self._get_bridge_subnet_pool_id( - t_ctx, context, admin_project_id, t_pod, False) + t_ctx, context, None, t_pod, False) t_bridge_net, t_bridge_subnet = self._get_bridge_network_subnet( t_ctx, context, project_id, t_pod, pool_id, False) (_, _, b_bridge_subnet_id, diff --git a/tricircle/tests/unit/network/test_plugin.py b/tricircle/tests/unit/network/test_plugin.py index 43e9c50..fe0f238 100644 --- a/tricircle/tests/unit/network/test_plugin.py +++ b/tricircle/tests/unit/network/test_plugin.py @@ -470,6 +470,9 @@ class FakeQuery(object): def all(self): return self.records + def count(self): + return len(self.records) + def __iter__(self): return self @@ -562,6 +565,9 @@ class FakeSession(object): def flush(self): pass + def expire(self, obj): + pass + class FakeRPCAPI(object): def configure_extra_routes(self, context, router_id): @@ -1549,6 +1555,83 @@ class PluginTest(unittest.TestCase, {'subnet_id': b_ns_bridge_subnet_id})] mock_action.assert_has_calls(calls) + @patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend, + '_allocate_specific_ip', new=_allocate_specific_ip) + @patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend, + '_generate_ip', new=fake_generate_ip) + @patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict', + new=fake_make_router_dict) + @patch.object(db_base_plugin_common.DbBasePluginCommon, + '_make_subnet_dict', new=fake_make_subnet_dict) + @patch.object(subnet_alloc.SubnetAllocator, '_lock_subnetpool', + new=mock.Mock) + @patch.object(FakeClient, 'action_routers') + @patch.object(context, 'get_context_from_neutron_context') + def test_unset_gateway(self, mock_context, mock_action): + plugin_path = 'tricircle.tests.unit.network.test_plugin.FakePlugin' + cfg.CONF.set_override('core_plugin', plugin_path) + + self._basic_pod_route_setup() + + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + tenant_id = 'test_tenant_id' + t_net_body = { + 'name': 'ext_net', + 'availability_zone_hints': ['pod_1'], + 'tenant_id': tenant_id, + 'router:external': True, + 'admin_state_up': True, + 'shared': False, + } + fake_plugin.create_network(q_ctx, {'network': t_net_body}) + t_net_id = TOP_NETS[0]['id'] + + t_subnet_body = { + 'network_id': t_net_id, # only one network created + 'name': 'ext_subnet', + 'ip_version': 4, + 'cidr': '100.64.0.0/24', + 'allocation_pools': [], + 'enable_dhcp': False, + 'gateway_ip': '100.64.0.1', + 'dns_nameservers': '', + 'host_routes': '', + 'tenant_id': tenant_id + } + fake_plugin.create_subnet(q_ctx, {'subnet': t_subnet_body}) + t_subnet_id = TOP_SUBNETS[0]['id'] + + t_router_id = uuidutils.generate_uuid() + t_router = { + 'id': t_router_id, + 'name': 'router', + 'distributed': False, + 'tenant_id': tenant_id, + 'attached_ports': [] + } + + TOP_ROUTERS.append(DotDict(t_router)) + # first add router gateway + fake_plugin.update_router( + q_ctx, t_router_id, + {'router': {'external_gateway_info': { + 'network_id': t_net_id, + 'enable_snat': False, + 'external_fixed_ips': [{'subnet_id': t_subnet_id, + 'ip_address': '100.64.0.5'}]}}}) + _, b_router_id = db_api.get_bottom_mappings_by_top_id( + t_ctx, t_router_id, constants.RT_ROUTER)[0] + + # then remove router gateway + fake_plugin.update_router( + q_ctx, t_router_id, + {'router': {'external_gateway_info': {}}}) + mock_action.assert_called_with(t_ctx, 'remove_gateway', b_router_id) + def _prepare_associate_floatingip_test(self, t_ctx, q_ctx, fake_plugin): tenant_id = 'test_tenant_id' self._basic_pod_route_setup()