From 404b3fc87eff4abcdaecf813d9aa5b0c0d23a23d Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Mon, 20 Jun 2016 19:40:38 +0800 Subject: [PATCH] Add network type support to the Tricircle plugin 1. What is the problem In the current implementation of the Tricircle plugin for neutron, network type is not supported so users cannot create networks with network type specified. In the specification of cross-pod l2 networking feature[1], we decide to support several network types like local, shared VLAN, shared VxLAN, etc, the first step is to make the Tricircle plugin be aware of network type. 2. What is the solution to the problem Handle network type in the Tricircle plugin for neutron. 3. What the features need to be implemented to the Tricircle to realize the solution In this patch, we add a framework to load type driver which processes different network type. The framework is based on neutron ML2 implemenation, we inherit the ML2 type manager and create a new Tricircle type manager. Also the Tricircle plugin is modified to extract network type parameter from request and insert network type information to response. [1] https://github.com/openstack/tricircle/blob/master/specs/cross-pod-l2-networking.rst Change-Id: Ida9b88df6113db46e637a7841ce5c1adaf651eba --- etc/tricircle_plugin-cfg-gen.conf | 4 + setup.cfg | 4 + tox.ini | 1 + tricircle/common/constants.py | 3 + tricircle/network/drivers/__init__.py | 0 tricircle/network/drivers/type_local.py | 44 ++++++++++ tricircle/network/managers.py | 92 +++++++++++++++++++++ tricircle/network/opts.py | 22 +++++ tricircle/network/plugin.py | 49 ++++++++++- tricircle/tests/unit/network/test_plugin.py | 20 ++++- 10 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 etc/tricircle_plugin-cfg-gen.conf create mode 100644 tricircle/network/drivers/__init__.py create mode 100644 tricircle/network/drivers/type_local.py create mode 100644 tricircle/network/managers.py create mode 100644 tricircle/network/opts.py diff --git a/etc/tricircle_plugin-cfg-gen.conf b/etc/tricircle_plugin-cfg-gen.conf new file mode 100644 index 0000000..fa22422 --- /dev/null +++ b/etc/tricircle_plugin-cfg-gen.conf @@ -0,0 +1,4 @@ +[DEFAULT] +output_file = etc/tricircle_plugin.conf.sample +wrap_width = 79 +namespace = tricircle.network diff --git a/setup.cfg b/setup.cfg index b4c7d49..827a028 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ oslo.config.opts = tricircle.api = tricircle.api.opts:list_opts tricircle.common = tricircle.common.opts:list_opts tricircle.db = tricircle.db.opts:list_opts + tricircle.network = tricircle.network.opts:list_opts tricircle.nova_apigw = tricircle.nova_apigw.opts:list_opts tricircle.cinder_apigw = tricircle.cinder_apigw.opts:list_opts @@ -58,3 +59,6 @@ oslo.config.opts = tempest.test_plugins = tricircle_tests = tricircle.tempestplugin.plugin:TricircleTempestPlugin + +tricircle.network.type_drivers = + local = tricircle.network.drivers.type_local:LocalTypeDriver diff --git a/tox.ini b/tox.ini index cb01053..ffb3b0e 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,7 @@ commands = oslo-config-generator --config-file=etc/api-cfg-gen.conf oslo-config-generator --config-file=etc/nova_apigw-cfg-gen.conf oslo-config-generator --config-file=etc/cinder_apigw-cfg-gen.conf oslo-config-generator --config-file=etc/xjob-cfg-gen.conf + oslo-config-generator --config-file=etc/tricircle_plugin-cfg-gen.conf [testenv:docs] commands = python setup.py build_sphinx diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index 5147aef..38d5341 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -71,3 +71,6 @@ TOP = 'top' # job type JT_ROUTER = 'router' JT_PORT_DELETE = 'port_delete' + +# network type +NT_LOCAL = 'local' diff --git a/tricircle/network/drivers/__init__.py b/tricircle/network/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tricircle/network/drivers/type_local.py b/tricircle/network/drivers/type_local.py new file mode 100644 index 0000000..6d07ddb --- /dev/null +++ b/tricircle/network/drivers/type_local.py @@ -0,0 +1,44 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.plugins.ml2 import driver_api + +from tricircle.common import constants + + +class LocalTypeDriver(driver_api.TypeDriver): + def get_type(self): + return constants.NT_LOCAL + + def initialize(self): + pass + + def is_partial_segment(self, segment): + return False + + def validate_provider_segment(self, segment): + pass + + def reserve_provider_segment(self, session, segment): + return segment + + def allocate_tenant_segment(self, session): + return {driver_api.NETWORK_TYPE: constants.NT_LOCAL} + + def release_segment(self, session, segment): + pass + + def get_mtu(self, physical): + pass diff --git a/tricircle/network/managers.py b/tricircle/network/managers.py new file mode 100644 index 0000000..c9f8a8a --- /dev/null +++ b/tricircle/network/managers.py @@ -0,0 +1,92 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg +from oslo_log import log + +from neutron.api.v2 import attributes +from neutron.extensions import external_net +from neutron.plugins.ml2 import managers + +from tricircle.common.i18n import _LI + +LOG = log.getLogger(__name__) + + +class TricircleTypeManager(managers.TypeManager): + + def __init__(self): + self.drivers = {} + + # NOTE(zhiyuan) here we call __init__ of super class's super class, + # which is NamedExtensionManager's __init__ to bypass initialization + # process of ml2 type manager + super(managers.TypeManager, self).__init__( + 'tricircle.network.type_drivers', + cfg.CONF.tricircle.type_drivers, + invoke_on_load=True) + LOG.info(_LI('Loaded type driver names: %s'), self.names()) + + self._register_types() + self._check_tenant_network_types( + cfg.CONF.tricircle.tenant_network_types) + + def _register_types(self): + for ext in self: + network_type = ext.obj.get_type() + if network_type not in self.drivers: + self.drivers[network_type] = ext + + @staticmethod + def _is_external_network(network): + external = network.get(external_net.EXTERNAL) + external_set = attributes.is_attr_set(external) + if not external_set or not external: + return False + else: + return True + + def create_network_segments(self, context, network, tenant_id): + # NOTE(zhiyuan) before we figure out how to deal with external network + # segment allocation, skip segment creation for external network + if self._is_external_network(network): + return + segments = self._process_provider_create(network) + session = context.session + mtu = [] + with session.begin(subtransactions=True): + network_id = network['id'] + if segments: + for segment_index, segment in enumerate(segments): + segment = self.reserve_provider_segment( + session, segment) + self._add_network_segment(session, network_id, segment, + mtu, segment_index) + else: + segment = self._allocate_tenant_net_segment(session) + self._add_network_segment(session, network_id, segment, mtu) + + def extend_networks_dict_provider(self, context, networks): + internal_networks = [] + for network in networks: + # NOTE(zhiyuan) before we figure out how to deal with external + # network segment allocation, skip external network since it does + # not have segment information + if not self._is_external_network(network): + internal_networks.append(network) + if internal_networks: + super(TricircleTypeManager, + self).extend_networks_dict_provider(context, + internal_networks) diff --git a/tricircle/network/opts.py b/tricircle/network/opts.py new file mode 100644 index 0000000..0a4265e --- /dev/null +++ b/tricircle/network/opts.py @@ -0,0 +1,22 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import tricircle.network.plugin + + +def list_opts(): + return [ + ('DEFAULT', tricircle.network.plugin.tricircle_opts), + ] diff --git a/tricircle/network/plugin.py b/tricircle/network/plugin.py index 5240ef3..ea51278 100644 --- a/tricircle/network/plugin.py +++ b/tricircle/network/plugin.py @@ -36,6 +36,7 @@ from neutron.db import sqlalchemyutils from neutron.extensions import availability_zone as az_ext from neutron.extensions import external_net from neutron.extensions import l3 +from neutron.extensions import providernet as provider from neutron.plugins.ml2.drivers import type_vlan import neutronclient.common.exceptions as q_cli_exceptions @@ -56,14 +57,25 @@ import tricircle.db.api as db_api from tricircle.db import core from tricircle.db import models import tricircle.network.exceptions as t_network_exc +from tricircle.network import managers from tricircle.network import security_groups tricircle_opts = [ cfg.StrOpt('bridge_physical_network', default='', - help='name of l3 bridge physical network') + help='name of l3 bridge physical network'), + cfg.ListOpt('type_drivers', + default=['local'], + help=_('List of network type driver entry points to be loaded ' + 'from the tricircle.network.type_drivers namespace.')), + cfg.ListOpt('tenant_network_types', + default=['local'], + help=_('Ordered list of network_types to allocate as tenant ' + 'networks. The default value "local" is useful for ' + 'single pod connectivity.')) ] + tricircle_opt_group = cfg.OptGroup('tricircle') cfg.CONF.register_group(tricircle_opt_group) cfg.CONF.register_opts(tricircle_opts, group=tricircle_opt_group) @@ -111,6 +123,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self.clients = {} self.xjob_handler = xrpcapi.XJobAPI() self._setup_rpc() + self.type_manager = managers.TricircleTypeManager() # use VlanTypeDriver to allocate VLAN for bridge network self.vlan_driver = TricircleVlanTypeDriver() self.vlan_driver.initialize() @@ -220,7 +233,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, return self def create_network(self, context, network): - net_data = network['network'] + net_data = network[attributes.NETWORK] + tenant_id = net_data['tenant_id'] is_external = self._ensure_az_set_for_external_network(net_data) if az_ext.AZ_HINTS in net_data: self._validate_availability_zones(context, @@ -228,6 +242,10 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, is_external) with context.session.begin(subtransactions=True): res = super(TricirclePlugin, self).create_network(context, network) + net_data['id'] = res['id'] + self.type_manager.create_network_segments(context, net_data, + tenant_id) + self.type_manager.extend_network_dict_provider(context, res) if az_ext.AZ_HINTS in net_data: az_hints = az_ext.convert_az_list_to_string( net_data[az_ext.AZ_HINTS]) @@ -267,11 +285,34 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, filters=[{'key': 'top_id', 'comparator': 'eq', 'value': network_id}]) - super(TricirclePlugin, self).delete_network(context, network_id) + + session = context.session + with session.begin(subtransactions=True): + self.type_manager.release_network_segments(session, network_id) + super(TricirclePlugin, self).delete_network(context, network_id) def update_network(self, context, network_id, network): - return super(TricirclePlugin, self).update_network( + net_data = network[attributes.NETWORK] + provider._raise_if_updates_provider_attributes(net_data) + + net = super(TricirclePlugin, self).update_network( context, network_id, network) + self.type_manager.extend_network_dict_provider(context, net) + return net + + def get_network(self, context, network_id, fields=None): + net = super(TricirclePlugin, self).get_network(context, network_id, + fields) + self.type_manager.extend_network_dict_provider(context, net) + return net + + def get_networks(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, page_reverse=False): + nets = super(TricirclePlugin, + self).get_networks(context, filters, None, sorts, + limit, marker, page_reverse) + self.type_manager.extend_networks_dict_provider(context, nets) + return nets def create_subnet(self, context, subnet): subnet_data = subnet['subnet'] diff --git a/tricircle/tests/unit/network/test_plugin.py b/tricircle/tests/unit/network/test_plugin.py index 54e8433..a565bef 100644 --- a/tricircle/tests/unit/network/test_plugin.py +++ b/tricircle/tests/unit/network/test_plugin.py @@ -45,6 +45,8 @@ from tricircle.common import exceptions import tricircle.db.api as db_api from tricircle.db import core from tricircle.db import models +from tricircle.network.drivers import type_local +from tricircle.network import managers from tricircle.network import plugin from tricircle.tests.unit.network import test_security_groups @@ -629,11 +631,23 @@ class FakeRPCAPI(object): pass +class FakeExtension(object): + def __init__(self, ext_obj): + self.obj = ext_obj + + +class FakeTypeManager(managers.TricircleTypeManager): + def _register_types(self): + driver = type_local.LocalTypeDriver() + self.drivers[constants.NT_LOCAL] = FakeExtension(driver) + + class FakePlugin(plugin.TricirclePlugin): def __init__(self): self.set_ipam_backend() self.xjob_handler = FakeRPCAPI() self.vlan_driver = plugin.TricircleVlanTypeDriver() + self.type_manager = FakeTypeManager() phynet = 'bridge' cfg.CONF.set_override('bridge_physical_network', phynet, @@ -912,7 +926,7 @@ class PluginTest(unittest.TestCase, mock_context.return_value = tricircle_context network = {'network': { - 'id': 'net_id', 'name': 'net_az', + 'id': 'net_id', 'name': 'net_az', 'tenant_id': 'test_tenant_id', 'availability_zone_hints': ['az_name_1', 'az_name_2']}} mock_create.return_value = {'id': 'net_id', 'name': 'net_az'} mock_update.return_value = network['network'] @@ -923,7 +937,7 @@ class PluginTest(unittest.TestCase, 'availability_zone_hints': '["az_name_1", "az_name_2"]'}}) err_network = {'network': { - 'id': 'net_id', 'name': 'net_az', + 'id': 'net_id', 'name': 'net_az', 'tenant_id': 'test_tenant_id', 'availability_zone_hints': ['az_name_1', 'az_name_3']}} mock_create.return_value = {'id': 'net_id', 'name': 'net_az'} self.assertRaises(az_ext.AvailabilityZoneNotFound, @@ -1490,6 +1504,7 @@ class PluginTest(unittest.TestCase, body = { 'network': { 'router:external': True, + 'tenant_id': 'test_tenant_id', } } self.assertRaises(exceptions.ExternalNetPodNotSpecify, @@ -1498,6 +1513,7 @@ class PluginTest(unittest.TestCase, body = { 'network': { 'router:external': True, + 'tenant_id': 'test_tenant_id', 'availability_zone_hints': ['az_name_1'] } }