From da3ed5a9f6659f44e2ba961298bf66b4cba4ec21 Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Sat, 25 Jun 2016 17:20:14 +0800 Subject: [PATCH] Add shared vlan type driver 1. What is the problem Network type framework has been merged but only the local network type is supported currently, so cross-pod l2 networking is still not available. 2. What is the solution to the problem At the first step, we support the simplest shared vlan network type. VMs in different pods are hosted in the network of its own pod with the same vlan ID and are connected with physical switches. 3. What the features need to be implemented to the Tricircle to realize the solution (1) A shared vlan type driver is added. (2) During the process of VM creation, if Nova_apigw finds that the required network is shared vlan type, it uses all the segment information of the network to form a network creation request and sends it to the Neutron server in the bottom pod with admin token. (3) The creation of bridge network for cross-pod l3 networking directly uses shared vlan type driver, no longer requires extra codes to allocate segments. To fully complete functional shared vlan network type, it's necessary to add functionality of port creation for gateway in each pod. But this patch set does not cover this functionality because of complexities. Change-Id: I8dd75d51fb74340c03d44e007b217f70d1a12d66 --- devstack/plugin.sh | 7 +- setup.cfg | 1 + tricircle/common/constants.py | 1 + tricircle/network/drivers/type_shared_vlan.py | 62 ++++++++++++ tricircle/network/managers.py | 17 ++++ tricircle/network/plugin.py | 96 ++++++++----------- tricircle/nova_apigw/controllers/server.py | 21 +++- tricircle/tests/unit/network/test_plugin.py | 33 ++++--- 8 files changed, 166 insertions(+), 72 deletions(-) create mode 100644 tricircle/network/drivers/type_shared_vlan.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 1dfba81..9eab6f3 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -274,7 +274,12 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then iniset $NEUTRON_CONF client auto_refresh_endpoint True iniset $NEUTRON_CONF client top_pod_name $REGION_NAME - iniset $NEUTRON_CONF tricircle bridge_physical_network `echo $OVS_BRIDGE_MAPPINGS | awk -F: '{print $1}'` + if [ "$Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS" != "" ]; then + iniset $NEUTRON_CONF tricircle type_drivers local,shared_vlan + iniset $NEUTRON_CONF tricircle tenant_network_types local,shared_vlan + iniset $NEUTRON_CONF tricircle network_vlan_ranges `echo $Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS | awk -F= '{print $2}'` + iniset $NEUTRON_CONF tricircle bridge_network_type shared_vlan + fi fi elif [[ "$1" == "stack" && "$2" == "extra" ]]; then diff --git a/setup.cfg b/setup.cfg index 69d9367..07e5e50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,3 +61,4 @@ tempest.test_plugins = tricircle.network.type_drivers = local = tricircle.network.drivers.type_local:LocalTypeDriver + shared_vlan = tricircle.network.drivers.type_shared_vlan:SharedVLANTypeDriver diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index e7ad95e..d7cad78 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -75,3 +75,4 @@ JT_PORT_DELETE = 'port_delete' # network type NT_LOCAL = 'local' +NT_SHARED_VLAN = 'shared_vlan' diff --git a/tricircle/network/drivers/type_shared_vlan.py b/tricircle/network/drivers/type_shared_vlan.py new file mode 100644 index 0000000..12761f7 --- /dev/null +++ b/tricircle/network/drivers/type_shared_vlan.py @@ -0,0 +1,62 @@ +# 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 sys + +from oslo_config import cfg +from oslo_log import log + +from neutron.plugins.common import utils as plugin_utils +from neutron.plugins.ml2 import driver_api +from neutron.plugins.ml2.drivers import type_vlan + +from tricircle.common import constants +from tricircle.common.i18n import _LE +from tricircle.common.i18n import _LI + +LOG = log.getLogger(__name__) + + +class SharedVLANTypeDriver(type_vlan.VlanTypeDriver): + def __init__(self): + super(SharedVLANTypeDriver, self).__init__() + + def _parse_network_vlan_ranges(self): + try: + self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges( + cfg.CONF.tricircle.network_vlan_ranges) + except Exception: + LOG.exception(_LE('Failed to parse network_vlan_ranges. ' + 'Service terminated!')) + sys.exit(1) + LOG.info(_LI('Network VLAN ranges: %s'), self.network_vlan_ranges) + + def get_type(self): + return constants.NT_SHARED_VLAN + + def reserve_provider_segment(self, session, segment): + res = super(SharedVLANTypeDriver, + self).reserve_provider_segment(session, segment) + res[driver_api.NETWORK_TYPE] = constants.NT_SHARED_VLAN + return res + + def allocate_tenant_segment(self, session): + res = super(SharedVLANTypeDriver, + self).allocate_tenant_segment(session) + res[driver_api.NETWORK_TYPE] = constants.NT_SHARED_VLAN + return res + + def get_mtu(self, physical): + pass diff --git a/tricircle/network/managers.py b/tricircle/network/managers.py index 7e6f978..b5cbf0e 100644 --- a/tricircle/network/managers.py +++ b/tricircle/network/managers.py @@ -20,6 +20,7 @@ from neutron.api.v2 import attributes from neutron.extensions import external_net from neutron.plugins.ml2 import managers +from tricircle.common.i18n import _LE from tricircle.common.i18n import _LI LOG = log.getLogger(__name__) @@ -42,6 +43,22 @@ class TricircleTypeManager(managers.TypeManager): self._register_types() self._check_tenant_network_types( cfg.CONF.tricircle.tenant_network_types) + self._check_bridge_network_type( + cfg.CONF.tricircle.bridge_network_type) + + def _check_bridge_network_type(self, bridge_network_type): + if not bridge_network_type: + return + if bridge_network_type == 'local': + LOG.error(_LE("Local is not a valid bridge network type. " + "Service terminated!"), bridge_network_type) + raise SystemExit(1) + + type_set = set(self.tenant_network_types) + if bridge_network_type not in type_set: + LOG.error(_LE("Bridge network type %s is not registered. " + "Service terminated!"), bridge_network_type) + raise SystemExit(1) def _register_types(self): for ext in self: diff --git a/tricircle/network/plugin.py b/tricircle/network/plugin.py index ea51278..8c0fb06 100644 --- a/tricircle/network/plugin.py +++ b/tricircle/network/plugin.py @@ -16,7 +16,6 @@ from oslo_config import cfg import oslo_log.helpers as log_helpers from oslo_log import log -from oslo_utils import uuidutils from neutron.api.v2 import attributes from neutron.common import constants @@ -31,13 +30,12 @@ from neutron.db import l3_agentschedulers_db # noqa from neutron.db import l3_db from neutron.db import models_v2 from neutron.db import portbindings_db -from neutron.db import segments_db 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 neutron.plugins.common.constants as p_constants import neutronclient.common.exceptions as q_cli_exceptions from sqlalchemy import sql @@ -62,9 +60,6 @@ from tricircle.network import security_groups tricircle_opts = [ - cfg.StrOpt('bridge_physical_network', - default='', - help='name of l3 bridge physical network'), cfg.ListOpt('type_drivers', default=['local'], help=_('List of network type driver entry points to be loaded ' @@ -73,7 +68,18 @@ tricircle_opts = [ default=['local'], help=_('Ordered list of network_types to allocate as tenant ' 'networks. The default value "local" is useful for ' - 'single pod connectivity.')) + 'single pod connectivity.')), + cfg.ListOpt('network_vlan_ranges', + default=[], + help=_('List of :: or ' + ' specifying physical_network names ' + 'usable for VLAN provider and tenant networks, as ' + 'well as ranges of VLAN tags on each available for ' + 'allocation to tenant networks.')), + cfg.StrOpt('bridge_network_type', + default='', + help=_('Type of l3 bridge network, this type should be enabled ' + 'in tenant_network_types and is not local type.')) ] tricircle_opt_group = cfg.OptGroup('tricircle') @@ -83,15 +89,6 @@ cfg.CONF.register_opts(tricircle_opts, group=tricircle_opt_group) LOG = log.getLogger(__name__) -class TricircleVlanTypeDriver(type_vlan.VlanTypeDriver): - def __init__(self): - super(TricircleVlanTypeDriver, self).__init__() - - # dump method - def get_mtu(self, physical_network): - return 0 - - class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, security_groups.TricircleSecurityGroupMixin, external_net_db.External_net_db_mixin, @@ -124,9 +121,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, 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() + self.type_manager.initialize() def _setup_rpc(self): self.endpoints = [] @@ -679,8 +674,13 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, filters={'name': [ele_['id']]}) def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_): - return getattr(super(TricirclePlugin, self), - 'create_%s' % _type_)(q_ctx_, body_) + if _type_ == t_constants.RT_NETWORK: + # for network, we call TricirclePlugin's own create_network to + # handle network segment + return self.create_network(q_ctx_, body_) + else: + return getattr(super(TricirclePlugin, self), + 'create_%s' % _type_)(q_ctx_, body_) return t_lock.get_or_create_element( t_ctx, q_ctx, @@ -746,32 +746,15 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, is_admin = q_ctx.is_admin q_ctx.is_admin = True - net_body = {'network': {'tenant_id': project_id, - 'name': net_name, - 'shared': False, - 'admin_state_up': True}} + net_body = {'network': { + 'tenant_id': project_id, + 'name': net_name, + 'shared': False, + 'admin_state_up': True, + provider.NETWORK_TYPE: cfg.CONF.tricircle.bridge_network_type}} _, net_id = self._prepare_top_element( t_ctx, q_ctx, project_id, pod, net_ele, 'network', net_body) - # allocate a VLAN id for bridge network - phy_net = cfg.CONF.tricircle.bridge_physical_network - with q_ctx.session.begin(): - query = q_ctx.session.query(segments_db.NetworkSegment) - query = query.filter_by(network_id=net_id) - if not query.first(): - segment = self.vlan_driver.reserve_provider_segment( - q_ctx.session, {'physical_network': phy_net}) - record = segments_db.NetworkSegment( - id=uuidutils.generate_uuid(), - network_id=net_id, - network_type='vlan', - physical_network=phy_net, - segmentation_id=segment['segmentation_id'], - segment_index=0, - is_dynamic=False - ) - q_ctx.session.add(record) - subnet_body = { 'subnet': { 'network_id': net_id, @@ -864,22 +847,23 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, t_ctx, q_ctx, project_id, pod, port_ele, 'port', port_body) return super(TricirclePlugin, self).get_port(q_ctx, port_id) + @staticmethod + def _transfer_network_type(network_type): + network_type_map = {t_constants.NT_SHARED_VLAN: p_constants.TYPE_VLAN} + return network_type_map.get(network_type, network_type) + def _get_bottom_bridge_elements(self, q_ctx, project_id, pod, t_net, is_external, t_subnet, t_port): t_ctx = t_context.get_context_from_neutron_context(q_ctx) - phy_net = cfg.CONF.tricircle.bridge_physical_network - with q_ctx.session.begin(): - query = q_ctx.session.query(segments_db.NetworkSegment) - query = query.filter_by(network_id=t_net['id']) - vlan = query.first().segmentation_id - - net_body = {'network': {'tenant_id': project_id, - 'name': t_net['id'], - 'provider:network_type': 'vlan', - 'provider:physical_network': phy_net, - 'provider:segmentation_id': vlan, - 'admin_state_up': True}} + net_body = {'network': { + 'tenant_id': project_id, + 'name': t_net['id'], + 'provider:network_type': self._transfer_network_type( + t_net['provider:network_type']), + 'provider:physical_network': t_net['provider:physical_network'], + 'provider:segmentation_id': t_net['provider:segmentation_id'], + 'admin_state_up': True}} if is_external: net_body['network'][external_net.EXTERNAL] = True _, b_net_id = self._prepare_bottom_element( diff --git a/tricircle/nova_apigw/controllers/server.py b/tricircle/nova_apigw/controllers/server.py index 30a3177..5455c6f 100644 --- a/tricircle/nova_apigw/controllers/server.py +++ b/tricircle/nova_apigw/controllers/server.py @@ -308,6 +308,13 @@ class ServerController(rest.RestController): 'admin_state_up': True } } + network_type = network.get('provider:network_type') + if network_type == constants.NT_SHARED_VLAN: + body['network']['provider:network_type'] = 'vlan' + body['network']['provider:physical_network'] = network[ + 'provider:physical_network'] + body['network']['provider:segmentation_id'] = network[ + 'provider:segmentation_id'] return body def _get_create_subnet_body(self, subnet, bottom_net_id): @@ -382,17 +389,23 @@ class ServerController(rest.RestController): client = self._get_client(pod_['pod_name']) return client.create_resources(_type_, t_ctx, body_) + # we don't need neutron context, so pass None return t_lock.get_or_create_element( - context, None, # we don't need neutron context, so pass None - self.project_id, pod, ele, _type, body, + context, None, self.project_id, pod, ele, _type, body, list_resources, create_resources) def _handle_network(self, context, pod, net, subnets, port=None, top_sg_ids=None, bottom_sg_ids=None): # network net_body = self._get_create_network_body(net) - _, bottom_net_id = self._prepare_neutron_element(context, pod, net, - 'network', net_body) + if net_body['network'].get('provider:network_type'): + # if network type specified, we need to switch to admin account + admin_context = t_context.get_admin_context() + _, bottom_net_id = self._prepare_neutron_element( + admin_context, pod, net, 'network', net_body) + else: + _, bottom_net_id = self._prepare_neutron_element( + context, pod, net, 'network', net_body) # subnet subnet_map = {} diff --git a/tricircle/tests/unit/network/test_plugin.py b/tricircle/tests/unit/network/test_plugin.py index ec8dbd6..ee09b44 100644 --- a/tricircle/tests/unit/network/test_plugin.py +++ b/tricircle/tests/unit/network/test_plugin.py @@ -46,6 +46,7 @@ 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.drivers import type_shared_vlan from tricircle.network import managers from tricircle.network import plugin from tricircle.tests.unit.network import test_security_groups @@ -638,25 +639,18 @@ class FakeExtension(object): class FakeTypeManager(managers.TricircleTypeManager): def _register_types(self): - driver = type_local.LocalTypeDriver() - self.drivers[constants.NT_LOCAL] = FakeExtension(driver) + local_driver = type_local.LocalTypeDriver() + self.drivers[constants.NT_LOCAL] = FakeExtension(local_driver) + vlan_driver = type_shared_vlan.SharedVLANTypeDriver() + self.drivers[constants.NT_SHARED_VLAN] = FakeExtension(vlan_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, - group='tricircle') - for vlan in (2000, 2001): - TOP_VLANALLOCATIONS.append( - DotDict({'physical_network': phynet, - 'vlan_id': vlan, 'allocated': False})) - def _get_client(self, pod_name): return FakeClient(pod_name) @@ -747,6 +741,23 @@ class PluginTest(unittest.TestCase, manager.NeutronManager._get_default_service_plugins = mock.Mock() manager.NeutronManager._get_default_service_plugins.return_value = [] + phynet = 'bridge' + vlan_min = 2000 + vlan_max = 2001 + cfg.CONF.set_override('type_drivers', ['local', 'shared_vlan'], + group='tricircle') + cfg.CONF.set_override('tenant_network_types', ['local', 'shared_vlan'], + group='tricircle') + cfg.CONF.set_override('network_vlan_ranges', + ['%s:%d:%d' % (phynet, vlan_min, vlan_max)], + group='tricircle') + cfg.CONF.set_override('bridge_network_type', 'shared_vlan', + group='tricircle') + for vlan in (vlan_min, vlan_max): + TOP_VLANALLOCATIONS.append( + DotDict({'physical_network': phynet, + 'vlan_id': vlan, 'allocated': False})) + def _basic_pod_route_setup(self): pod1 = {'pod_id': 'pod_id_1', 'pod_name': 'pod_1',