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
This commit is contained in:
zhiyuan_cai 2016-06-20 19:40:38 +08:00
parent f1fa18dcb8
commit 404b3fc87e
10 changed files with 233 additions and 6 deletions

View File

@ -0,0 +1,4 @@
[DEFAULT]
output_file = etc/tricircle_plugin.conf.sample
wrap_width = 79
namespace = tricircle.network

View File

@ -51,6 +51,7 @@ oslo.config.opts =
tricircle.api = tricircle.api.opts:list_opts tricircle.api = tricircle.api.opts:list_opts
tricircle.common = tricircle.common.opts:list_opts tricircle.common = tricircle.common.opts:list_opts
tricircle.db = tricircle.db.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.nova_apigw = tricircle.nova_apigw.opts:list_opts
tricircle.cinder_apigw = tricircle.cinder_apigw.opts:list_opts tricircle.cinder_apigw = tricircle.cinder_apigw.opts:list_opts
@ -58,3 +59,6 @@ oslo.config.opts =
tempest.test_plugins = tempest.test_plugins =
tricircle_tests = tricircle.tempestplugin.plugin:TricircleTempestPlugin tricircle_tests = tricircle.tempestplugin.plugin:TricircleTempestPlugin
tricircle.network.type_drivers =
local = tricircle.network.drivers.type_local:LocalTypeDriver

View File

@ -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/nova_apigw-cfg-gen.conf
oslo-config-generator --config-file=etc/cinder_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/xjob-cfg-gen.conf
oslo-config-generator --config-file=etc/tricircle_plugin-cfg-gen.conf
[testenv:docs] [testenv:docs]
commands = python setup.py build_sphinx commands = python setup.py build_sphinx

View File

@ -71,3 +71,6 @@ TOP = 'top'
# job type # job type
JT_ROUTER = 'router' JT_ROUTER = 'router'
JT_PORT_DELETE = 'port_delete' JT_PORT_DELETE = 'port_delete'
# network type
NT_LOCAL = 'local'

View File

View File

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

View File

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

22
tricircle/network/opts.py Normal file
View File

@ -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),
]

View File

@ -36,6 +36,7 @@ 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.plugins.ml2.drivers import type_vlan from neutron.plugins.ml2.drivers import type_vlan
import neutronclient.common.exceptions as q_cli_exceptions 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 core
from tricircle.db import models from tricircle.db import models
import tricircle.network.exceptions as t_network_exc import tricircle.network.exceptions as t_network_exc
from tricircle.network import managers
from tricircle.network import security_groups from tricircle.network import security_groups
tricircle_opts = [ tricircle_opts = [
cfg.StrOpt('bridge_physical_network', cfg.StrOpt('bridge_physical_network',
default='', 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') tricircle_opt_group = cfg.OptGroup('tricircle')
cfg.CONF.register_group(tricircle_opt_group) cfg.CONF.register_group(tricircle_opt_group)
cfg.CONF.register_opts(tricircle_opts, 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.clients = {}
self.xjob_handler = xrpcapi.XJobAPI() self.xjob_handler = xrpcapi.XJobAPI()
self._setup_rpc() self._setup_rpc()
self.type_manager = managers.TricircleTypeManager()
# use VlanTypeDriver to allocate VLAN for bridge network # use VlanTypeDriver to allocate VLAN for bridge network
self.vlan_driver = TricircleVlanTypeDriver() self.vlan_driver = TricircleVlanTypeDriver()
self.vlan_driver.initialize() self.vlan_driver.initialize()
@ -220,7 +233,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return self return self
def create_network(self, context, network): 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) is_external = self._ensure_az_set_for_external_network(net_data)
if az_ext.AZ_HINTS in net_data: if az_ext.AZ_HINTS in net_data:
self._validate_availability_zones(context, self._validate_availability_zones(context,
@ -228,6 +242,10 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
is_external) is_external)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
res = super(TricirclePlugin, self).create_network(context, network) 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: if az_ext.AZ_HINTS in net_data:
az_hints = az_ext.convert_az_list_to_string( az_hints = az_ext.convert_az_list_to_string(
net_data[az_ext.AZ_HINTS]) net_data[az_ext.AZ_HINTS])
@ -267,11 +285,34 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
filters=[{'key': 'top_id', filters=[{'key': 'top_id',
'comparator': 'eq', 'comparator': 'eq',
'value': network_id}]) '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): 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) 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): def create_subnet(self, context, subnet):
subnet_data = subnet['subnet'] subnet_data = subnet['subnet']

View File

@ -45,6 +45,8 @@ from tricircle.common import exceptions
import tricircle.db.api as db_api 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 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
@ -629,11 +631,23 @@ class FakeRPCAPI(object):
pass 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): 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.vlan_driver = plugin.TricircleVlanTypeDriver()
self.type_manager = FakeTypeManager()
phynet = 'bridge' phynet = 'bridge'
cfg.CONF.set_override('bridge_physical_network', phynet, cfg.CONF.set_override('bridge_physical_network', phynet,
@ -912,7 +926,7 @@ class PluginTest(unittest.TestCase,
mock_context.return_value = tricircle_context mock_context.return_value = tricircle_context
network = {'network': { 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']}} 'availability_zone_hints': ['az_name_1', 'az_name_2']}}
mock_create.return_value = {'id': 'net_id', 'name': 'net_az'} mock_create.return_value = {'id': 'net_id', 'name': 'net_az'}
mock_update.return_value = network['network'] mock_update.return_value = network['network']
@ -923,7 +937,7 @@ class PluginTest(unittest.TestCase,
'availability_zone_hints': '["az_name_1", "az_name_2"]'}}) 'availability_zone_hints': '["az_name_1", "az_name_2"]'}})
err_network = {'network': { 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']}} 'availability_zone_hints': ['az_name_1', 'az_name_3']}}
mock_create.return_value = {'id': 'net_id', 'name': 'net_az'} mock_create.return_value = {'id': 'net_id', 'name': 'net_az'}
self.assertRaises(az_ext.AvailabilityZoneNotFound, self.assertRaises(az_ext.AvailabilityZoneNotFound,
@ -1490,6 +1504,7 @@ class PluginTest(unittest.TestCase,
body = { body = {
'network': { 'network': {
'router:external': True, 'router:external': True,
'tenant_id': 'test_tenant_id',
} }
} }
self.assertRaises(exceptions.ExternalNetPodNotSpecify, self.assertRaises(exceptions.ExternalNetPodNotSpecify,
@ -1498,6 +1513,7 @@ class PluginTest(unittest.TestCase,
body = { body = {
'network': { 'network': {
'router:external': True, 'router:external': True,
'tenant_id': 'test_tenant_id',
'availability_zone_hints': ['az_name_1'] 'availability_zone_hints': ['az_name_1']
} }
} }