diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 1dfba810..9eab6f33 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 69d93670..07e5e505 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 e7ad95ea..d7cad784 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 00000000..12761f78 --- /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 7e6f9786..b5cbf0e6 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 ea512789..8c0fb066 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 30a3177f..5455c6f7 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 ec8dbd6d..ee09b44f 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',