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'] } }