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
This commit is contained in:
zhiyuan_cai 2016-06-25 17:20:14 +08:00
parent 7d2e179cc3
commit da3ed5a9f6
8 changed files with 166 additions and 72 deletions

View File

@ -274,7 +274,12 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then
iniset $NEUTRON_CONF client auto_refresh_endpoint True iniset $NEUTRON_CONF client auto_refresh_endpoint True
iniset $NEUTRON_CONF client top_pod_name $REGION_NAME 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 fi
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then elif [[ "$1" == "stack" && "$2" == "extra" ]]; then

View File

@ -61,3 +61,4 @@ tempest.test_plugins =
tricircle.network.type_drivers = tricircle.network.type_drivers =
local = tricircle.network.drivers.type_local:LocalTypeDriver local = tricircle.network.drivers.type_local:LocalTypeDriver
shared_vlan = tricircle.network.drivers.type_shared_vlan:SharedVLANTypeDriver

View File

@ -75,3 +75,4 @@ JT_PORT_DELETE = 'port_delete'
# network type # network type
NT_LOCAL = 'local' NT_LOCAL = 'local'
NT_SHARED_VLAN = 'shared_vlan'

View File

@ -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

View File

@ -20,6 +20,7 @@ from neutron.api.v2 import attributes
from neutron.extensions import external_net from neutron.extensions import external_net
from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import managers
from tricircle.common.i18n import _LE
from tricircle.common.i18n import _LI from tricircle.common.i18n import _LI
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -42,6 +43,22 @@ class TricircleTypeManager(managers.TypeManager):
self._register_types() self._register_types()
self._check_tenant_network_types( self._check_tenant_network_types(
cfg.CONF.tricircle.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): def _register_types(self):
for ext in self: for ext in self:

View File

@ -16,7 +16,6 @@
from oslo_config import cfg from oslo_config import cfg
import oslo_log.helpers as log_helpers import oslo_log.helpers as log_helpers
from oslo_log import log from oslo_log import log
from oslo_utils import uuidutils
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.common import constants 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 l3_db
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import portbindings_db from neutron.db import portbindings_db
from neutron.db import segments_db
from neutron.db import sqlalchemyutils from neutron.db import sqlalchemyutils
from neutron.extensions import availability_zone as az_ext from neutron.extensions import availability_zone as az_ext
from neutron.extensions import external_net from neutron.extensions import external_net
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.extensions import providernet as provider 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 import neutronclient.common.exceptions as q_cli_exceptions
from sqlalchemy import sql from sqlalchemy import sql
@ -62,9 +60,6 @@ from tricircle.network import security_groups
tricircle_opts = [ tricircle_opts = [
cfg.StrOpt('bridge_physical_network',
default='',
help='name of l3 bridge physical network'),
cfg.ListOpt('type_drivers', cfg.ListOpt('type_drivers',
default=['local'], default=['local'],
help=_('List of network type driver entry points to be loaded ' help=_('List of network type driver entry points to be loaded '
@ -73,7 +68,18 @@ tricircle_opts = [
default=['local'], default=['local'],
help=_('Ordered list of network_types to allocate as tenant ' help=_('Ordered list of network_types to allocate as tenant '
'networks. The default value "local" is useful for ' 'networks. The default value "local" is useful for '
'single pod connectivity.')) 'single pod connectivity.')),
cfg.ListOpt('network_vlan_ranges',
default=[],
help=_('List of <physical_network>:<vlan_min>:<vlan_max> or '
'<physical_network> 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') tricircle_opt_group = cfg.OptGroup('tricircle')
@ -83,15 +89,6 @@ cfg.CONF.register_opts(tricircle_opts, group=tricircle_opt_group)
LOG = log.getLogger(__name__) 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, class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
security_groups.TricircleSecurityGroupMixin, security_groups.TricircleSecurityGroupMixin,
external_net_db.External_net_db_mixin, external_net_db.External_net_db_mixin,
@ -124,9 +121,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
self.xjob_handler = xrpcapi.XJobAPI() self.xjob_handler = xrpcapi.XJobAPI()
self._setup_rpc() self._setup_rpc()
self.type_manager = managers.TricircleTypeManager() self.type_manager = managers.TricircleTypeManager()
# use VlanTypeDriver to allocate VLAN for bridge network self.type_manager.initialize()
self.vlan_driver = TricircleVlanTypeDriver()
self.vlan_driver.initialize()
def _setup_rpc(self): def _setup_rpc(self):
self.endpoints = [] self.endpoints = []
@ -679,8 +674,13 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
filters={'name': [ele_['id']]}) filters={'name': [ele_['id']]})
def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_): def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_):
return getattr(super(TricirclePlugin, self), if _type_ == t_constants.RT_NETWORK:
'create_%s' % _type_)(q_ctx_, body_) # 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( return t_lock.get_or_create_element(
t_ctx, q_ctx, t_ctx, q_ctx,
@ -746,32 +746,15 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
is_admin = q_ctx.is_admin is_admin = q_ctx.is_admin
q_ctx.is_admin = True q_ctx.is_admin = True
net_body = {'network': {'tenant_id': project_id, net_body = {'network': {
'name': net_name, 'tenant_id': project_id,
'shared': False, 'name': net_name,
'admin_state_up': True}} 'shared': False,
'admin_state_up': True,
provider.NETWORK_TYPE: cfg.CONF.tricircle.bridge_network_type}}
_, net_id = self._prepare_top_element( _, net_id = self._prepare_top_element(
t_ctx, q_ctx, project_id, pod, net_ele, 'network', net_body) 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_body = {
'subnet': { 'subnet': {
'network_id': net_id, '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) t_ctx, q_ctx, project_id, pod, port_ele, 'port', port_body)
return super(TricirclePlugin, self).get_port(q_ctx, port_id) 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, def _get_bottom_bridge_elements(self, q_ctx, project_id,
pod, t_net, is_external, t_subnet, t_port): pod, t_net, is_external, t_subnet, t_port):
t_ctx = t_context.get_context_from_neutron_context(q_ctx) t_ctx = t_context.get_context_from_neutron_context(q_ctx)
phy_net = cfg.CONF.tricircle.bridge_physical_network net_body = {'network': {
with q_ctx.session.begin(): 'tenant_id': project_id,
query = q_ctx.session.query(segments_db.NetworkSegment) 'name': t_net['id'],
query = query.filter_by(network_id=t_net['id']) 'provider:network_type': self._transfer_network_type(
vlan = query.first().segmentation_id t_net['provider:network_type']),
'provider:physical_network': t_net['provider:physical_network'],
net_body = {'network': {'tenant_id': project_id, 'provider:segmentation_id': t_net['provider:segmentation_id'],
'name': t_net['id'], 'admin_state_up': True}}
'provider:network_type': 'vlan',
'provider:physical_network': phy_net,
'provider:segmentation_id': vlan,
'admin_state_up': True}}
if is_external: if is_external:
net_body['network'][external_net.EXTERNAL] = True net_body['network'][external_net.EXTERNAL] = True
_, b_net_id = self._prepare_bottom_element( _, b_net_id = self._prepare_bottom_element(

View File

@ -308,6 +308,13 @@ class ServerController(rest.RestController):
'admin_state_up': True '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 return body
def _get_create_subnet_body(self, subnet, bottom_net_id): 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']) client = self._get_client(pod_['pod_name'])
return client.create_resources(_type_, t_ctx, body_) return client.create_resources(_type_, t_ctx, body_)
# we don't need neutron context, so pass None
return t_lock.get_or_create_element( return t_lock.get_or_create_element(
context, None, # we don't need neutron context, so pass None context, None, self.project_id, pod, ele, _type, body,
self.project_id, pod, ele, _type, body,
list_resources, create_resources) list_resources, create_resources)
def _handle_network(self, context, pod, net, subnets, port=None, def _handle_network(self, context, pod, net, subnets, port=None,
top_sg_ids=None, bottom_sg_ids=None): top_sg_ids=None, bottom_sg_ids=None):
# network # network
net_body = self._get_create_network_body(net) net_body = self._get_create_network_body(net)
_, bottom_net_id = self._prepare_neutron_element(context, pod, net, if net_body['network'].get('provider:network_type'):
'network', net_body) # 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
subnet_map = {} subnet_map = {}

View File

@ -46,6 +46,7 @@ import tricircle.db.api as db_api
from tricircle.db import core from tricircle.db import core
from tricircle.db import models from tricircle.db import models
from tricircle.network.drivers import type_local from tricircle.network.drivers import type_local
from tricircle.network.drivers import type_shared_vlan
from tricircle.network import managers from tricircle.network import managers
from tricircle.network import plugin from tricircle.network import plugin
from tricircle.tests.unit.network import test_security_groups from tricircle.tests.unit.network import test_security_groups
@ -638,25 +639,18 @@ class FakeExtension(object):
class FakeTypeManager(managers.TricircleTypeManager): class FakeTypeManager(managers.TricircleTypeManager):
def _register_types(self): def _register_types(self):
driver = type_local.LocalTypeDriver() local_driver = type_local.LocalTypeDriver()
self.drivers[constants.NT_LOCAL] = FakeExtension(driver) 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): class FakePlugin(plugin.TricirclePlugin):
def __init__(self): def __init__(self):
self.set_ipam_backend() self.set_ipam_backend()
self.xjob_handler = FakeRPCAPI() self.xjob_handler = FakeRPCAPI()
self.vlan_driver = plugin.TricircleVlanTypeDriver()
self.type_manager = FakeTypeManager() 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): def _get_client(self, pod_name):
return FakeClient(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 = mock.Mock()
manager.NeutronManager._get_default_service_plugins.return_value = [] 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): def _basic_pod_route_setup(self):
pod1 = {'pod_id': 'pod_id_1', pod1 = {'pod_id': 'pod_id_1',
'pod_name': 'pod_1', 'pod_name': 'pod_1',