From e85d91362b45d383180020c39dd39b570a4d6d09 Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Thu, 20 Apr 2017 11:49:28 +0800 Subject: [PATCH] Multiple pods share the same physical network name 1. What is the problem? Multiple pods may be configured with physical networks that have the same name, so theoretically external networks in different pods can have the same physical network name. But currently we can not create such external networks, because central Neutron will save the used physical network in the database, when creating the second external networks, FlatNetworkInUse exception will be raised. 2. What is the solution to the problem? Catch FlatNetworkInUse exception and leave the validation of physical network to local Neutron. 3. What features need to be implemented to the Tricircle to realize the solution? Now users can create flat external networks with the same physical network name in different pods. Change-Id: Icf7877bc6cef8757b82552f9c3871336442a07a6 --- tricircle/network/central_plugin.py | 15 +++++- tricircle/network/drivers/type_flat.py | 12 ++++- .../tests/unit/network/test_central_plugin.py | 51 +++++++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/tricircle/network/central_plugin.py b/tricircle/network/central_plugin.py index 0de3cba8..c9a5d318 100644 --- a/tricircle/network/central_plugin.py +++ b/tricircle/network/central_plugin.py @@ -15,6 +15,7 @@ import collections import copy +import re import six from oslo_config import cfg @@ -26,6 +27,7 @@ from neutron.api.v2 import attributes from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources +import neutron.common.exceptions as ml2_exceptions from neutron.db import api as q_db_api from neutron.db.availability_zone import router as router_az from neutron.db import common_db_mixin @@ -314,8 +316,17 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, # rollback if is_external: self._fill_provider_info(res, net_data) - self._create_bottom_external_network( - context, net_data, res['id']) + try: + self._create_bottom_external_network( + context, net_data, res['id']) + except q_cli_exceptions.Conflict as e: + pattern = re.compile('Physical network (.*) is in use') + match = pattern.search(e.message) + if not match: + raise + else: + raise ml2_exceptions.FlatNetworkInUse( + physical_network=match.groups()[0]) return res def delete_network(self, context, network_id): diff --git a/tricircle/network/drivers/type_flat.py b/tricircle/network/drivers/type_flat.py index ddae9b2b..2f6d3009 100644 --- a/tricircle/network/drivers/type_flat.py +++ b/tricircle/network/drivers/type_flat.py @@ -16,6 +16,7 @@ from oslo_config import cfg from oslo_log import log +from neutron.common import exceptions from neutron.plugins.ml2 import driver_api from neutron.plugins.ml2.drivers import type_flat @@ -36,8 +37,15 @@ class FlatTypeDriver(type_flat.FlatTypeDriver): LOG.info("FlatTypeDriver initialization complete") def reserve_provider_segment(self, context, segment): - res = super(FlatTypeDriver, - self).reserve_provider_segment(context, segment) + try: + res = super(FlatTypeDriver, + self).reserve_provider_segment(context, segment) + except exceptions.FlatNetworkInUse: + # to support multiple regions sharing the same physical network + # for external network, we ignore this exception and let local + # Neutron judge whether the physical network is valid + res = segment + res[driver_api.MTU] = None res[driver_api.NETWORK_TYPE] = self.get_type() return res diff --git a/tricircle/tests/unit/network/test_central_plugin.py b/tricircle/tests/unit/network/test_central_plugin.py index aeafbdb8..aa3bb214 100644 --- a/tricircle/tests/unit/network/test_central_plugin.py +++ b/tricircle/tests/unit/network/test_central_plugin.py @@ -28,13 +28,13 @@ from sqlalchemy.sql import elements from sqlalchemy.sql import selectable from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import provider_net import neutron_lib.constants as q_constants import neutron_lib.context as q_context import neutron_lib.exceptions as q_lib_exc from neutron_lib.plugins import directory import neutron.conf.common as q_config - from neutron.db import _utils from neutron.db import db_base_plugin_common from neutron.db import db_base_plugin_v2 @@ -42,6 +42,7 @@ from neutron.db import ipam_pluggable_backend from neutron.db import l3_db from neutron.db import models_v2 from neutron.db import rbac_db_models as rbac_db +import neutron.objects.exceptions as q_obj_exceptions from neutron.extensions import availability_zone as az_ext @@ -66,6 +67,7 @@ import tricircle.db.api as db_api from tricircle.db import core from tricircle.db import models import tricircle.network.central_plugin as plugin +from tricircle.network.drivers import type_flat from tricircle.network.drivers import type_local from tricircle.network.drivers import type_vlan from tricircle.network.drivers import type_vxlan @@ -84,6 +86,7 @@ TOP_SUBNETPOOLPREFIXES = [] TOP_IPALLOCATIONS = [] TOP_VLANALLOCATIONS = [] TOP_VXLANALLOCATIONS = [] +TOP_FLATALLOCATIONS = [] TOP_SEGMENTS = [] TOP_EXTNETS = [] TOP_FLOATINGIPS = [] @@ -106,8 +109,8 @@ BOTTOM2_SGS = [] BOTTOM2_FIPS = [] RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_ROUTERS, TOP_ROUTERPORT, TOP_SUBNETPOOLS, TOP_SUBNETPOOLPREFIXES, TOP_IPALLOCATIONS, - TOP_VLANALLOCATIONS, TOP_VXLANALLOCATIONS, TOP_SEGMENTS, - TOP_EXTNETS, TOP_FLOATINGIPS, TOP_SGS, TOP_SG_RULES, + TOP_VLANALLOCATIONS, TOP_VXLANALLOCATIONS, TOP_FLOATINGIPS, + TOP_SEGMENTS, TOP_EXTNETS, TOP_FLOATINGIPS, TOP_SGS, TOP_SG_RULES, TOP_NETWORK_RBAC, TOP_SUBNETROUTES, TOP_DNSNAMESERVERS, BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_ROUTERS, BOTTOM1_SGS, BOTTOM1_FIPS, @@ -123,6 +126,7 @@ RES_MAP = {'networks': TOP_NETS, 'subnetpoolprefixes': TOP_SUBNETPOOLPREFIXES, 'ml2_vlan_allocations': TOP_VLANALLOCATIONS, 'ml2_vxlan_allocations': TOP_VXLANALLOCATIONS, + 'ml2_flat_allocations': TOP_FLATALLOCATIONS, 'networksegments': TOP_SEGMENTS, 'externalnetworks': TOP_EXTNETS, 'floatingips': TOP_FLOATINGIPS, @@ -996,6 +1000,13 @@ class FakeSession(object): subnet['dns_nameservers'].append(dnsnameservers) break + if model_obj.__tablename__ == 'ml2_flat_allocations': + for alloc in TOP_FLATALLOCATIONS: + if alloc['physical_network'] == model_dict['physical_network']: + raise q_obj_exceptions.NeutronDbObjectDuplicateEntry( + model_obj.__class__, + DotDict({'columns': '', 'value': ''})) + self._extend_standard_attr(model_dict) RES_MAP[model_obj.__tablename__].append(model_dict) @@ -1114,6 +1125,8 @@ class FakeTypeManager(managers.TricircleTypeManager): self.drivers[constants.NT_VLAN] = FakeExtension(vlan_driver) vxlan_driver = type_vxlan.VxLANTypeDriver() self.drivers[constants.NT_VxLAN] = FakeExtension(vxlan_driver) + local_driver = type_flat.FlatTypeDriver() + self.drivers[constants.NT_FLAT] = FakeExtension(local_driver) def extend_network_dict_provider(self, cxt, net): target_net = None @@ -2899,6 +2912,38 @@ class PluginTest(unittest.TestCase, t_ctx, top_net['id'], constants.RT_NETWORK) self.assertEqual(mappings[0][1], bottom_net['id']) + @patch.object(directory, 'get_plugin', new=fake_get_plugin) + @patch.object(context, 'get_context_from_neutron_context') + def test_create_flat_external_network(self, mock_context): + self._basic_pod_route_setup() + + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + body = { + 'network': { + 'name': 'ext-net1', + 'admin_state_up': True, + 'shared': False, + 'tenant_id': TEST_TENANT_ID, + 'router:external': True, + 'availability_zone_hints': ['pod_1'], + provider_net.PHYSICAL_NETWORK: 'extern', + provider_net.NETWORK_TYPE: 'flat' + } + } + fake_plugin.create_network(q_ctx, body) + body['network']['name'] = ['ext-net2'] + body['network']['availability_zone_hints'] = ['pod_2'] + fake_plugin.create_network(q_ctx, body) + # we have ignore the FlatNetworkInUse exception, so only one allocation + # record is created, and both pods have one external network + self.assertEqual(1, len(TOP_FLATALLOCATIONS)) + self.assertEqual(1, len(BOTTOM1_NETS)) + self.assertEqual(1, len(BOTTOM2_NETS)) + def _prepare_external_net_router_test(self, q_ctx, fake_plugin, router_az_hints=None):