diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index 95806396..f25dc2ab 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -228,3 +228,9 @@ CENTRAL = 'central-neutronclient' LOCAL = 'local-neutronclient' REQUEST_SOURCE_TYPE = set([CENTRAL, LOCAL]) + +# for new L3 network model using routed network +# prefix for the name of segment +SEGMENT_NAME_PATTERN = 'newL3-(.*?)-(.*)' +PREFIX_OF_SEGMENT_NAME = 'newL3-' +PREFIX_OF_SEGMENT_NAME_DIVISION = '-' diff --git a/tricircle/network/central_plugin.py b/tricircle/network/central_plugin.py index f62bff90..73203935 100644 --- a/tricircle/network/central_plugin.py +++ b/tricircle/network/central_plugin.py @@ -128,7 +128,13 @@ tricircle_opts = [ ' to.')), cfg.BoolOpt('enable_api_gateway', default=True, - help=_('Whether the Nova API gateway is enabled')) + help=_('Whether the Nova API gateway is enabled')), + cfg.BoolOpt('enable_l3_route_network', + default=False, + help=_('Whether using the new L3 networking model. When it is' + 'set to true, Tricircle will automatically create a' + 'bottom external network if the name of segment' + 'matches newL3-..')) ] tricircle_opt_group = cfg.OptGroup('tricircle') @@ -267,24 +273,6 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, if validators.is_attr_set(from_net.get(provider_attr)): to_net[provider_attr] = from_net[provider_attr] - def _create_bottom_external_network(self, context, net, top_id): - t_ctx = t_context.get_context_from_neutron_context(context) - # use the first pod - az_name = net[az_def.AZ_HINTS][0] - pod = db_api.find_pod_by_az_or_region(t_ctx, az_name) - body = { - 'network': { - 'name': top_id, - 'tenant_id': net['tenant_id'], - 'admin_state_up': True, - external_net.EXTERNAL: True - } - } - self._fill_provider_info(net, body['network']) - self._prepare_bottom_element( - t_ctx, net['tenant_id'], pod, {'id': top_id}, - t_constants.RT_NETWORK, body) - def _create_bottom_external_subnet(self, context, subnet, net, top_id): t_ctx = t_context.get_context_from_neutron_context(context) region_name = net[az_def.AZ_HINTS][0] @@ -303,7 +291,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, for attr in attrs: if validators.is_attr_set(subnet.get(attr)): body['subnet'][attr] = subnet[attr] - self._prepare_bottom_element( + self.helper.prepare_bottom_element( t_ctx, subnet['tenant_id'], pod, {'id': top_id}, t_constants.RT_SUBNET, body) @@ -340,7 +328,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, if is_external: self._fill_provider_info(res, net_data) try: - self._create_bottom_external_network( + self.helper.prepare_bottom_external_network( context, net_data, res['id']) except q_cli_exceptions.Conflict as e: pattern = re.compile('Physical network (.*) is in use') @@ -544,6 +532,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def create_subnet(self, context, subnet): subnet_data = subnet['subnet'] network = self.get_network(context, subnet_data['network_id']) + is_external = network.get(external_net.EXTERNAL) with context.session.begin(subtransactions=True): res = super(TricirclePlugin, self).create_subnet(context, subnet) @@ -589,7 +578,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self.helper.get_real_shadow_resource_iterator( t_ctx, t_constants.RT_SUBNET, subnet_id)): region_name = pod['region_name'] - self._get_client(region_name).delete_subnets( + b_client = self._get_client(region_name) + b_client.delete_subnets( t_ctx, bottom_subnet_id) interface_name = t_constants.interface_port_name % ( region_name, subnet_id) @@ -607,6 +597,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, 'value': pod['pod_id']}]) except Exception: raise + dhcp_port_name = t_constants.dhcp_port_name % subnet_id self._delete_pre_created_port(t_ctx, context, dhcp_port_name) snat_port_name = t_constants.snat_port_name % subnet_id @@ -1546,11 +1537,6 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, return self.helper.prepare_top_element( t_ctx, q_ctx, project_id, pod, ele, _type, body) - def _prepare_bottom_element(self, t_ctx, - project_id, pod, ele, _type, body): - return self.helper.prepare_bottom_element( - t_ctx, project_id, pod, ele, _type, body) - def _get_bridge_subnet_pool_id(self, t_ctx, q_ctx, project_id, pod): pool_name = t_constants.bridge_subnet_pool_name pool_cidr = cfg.CONF.client.bridge_cidr @@ -1706,7 +1692,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, body = {'router': {'name': t_constants.ns_router_name % router_id, 'distributed': False}} - _, b_router_id = self._prepare_bottom_element( + _, b_router_id = self.helper.prepare_bottom_element( t_ctx, t_router['tenant_id'], pod, t_router, router_type, body) # both router and external network in bottom pod are ready, attach diff --git a/tricircle/network/helper.py b/tricircle/network/helper.py index 340148dc..0b1ca2bf 100644 --- a/tricircle/network/helper.py +++ b/tricircle/network/helper.py @@ -19,8 +19,11 @@ import re import six from six.moves import xrange +from neutron_lib.api.definitions import availability_zone as az_def +from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net +from neutron_lib.api import validators from neutron_lib import constants import neutronclient.common.exceptions as q_cli_exceptions from oslo_serialization import jsonutils @@ -28,6 +31,7 @@ from oslo_serialization import jsonutils from tricircle.common import client import tricircle.common.constants as t_constants import tricircle.common.context as t_context +import tricircle.common.exceptions as t_exceptions import tricircle.common.lock_handle as t_lock from tricircle.common import utils import tricircle.db.api as db_api @@ -969,6 +973,154 @@ class NetworkHelper(object): t_constants.RT_SD_PORT, create_body) return sw_port_id + def prepare_bottom_router(self, n_context, net, b_router_name): + t_ctx = t_context.get_context_from_neutron_context(n_context) + # use the first pod + az_name = net[az_def.AZ_HINTS][0] + pod = db_api.find_pod_by_az_or_region(t_ctx, az_name) + body = { + 'router': { + 'name': b_router_name, + 'tenant_id': net['tenant_id'], + 'admin_state_up': True, + 'distributed': False + } + } + return self.prepare_bottom_element( + t_ctx, net['tenant_id'], pod, {'id': b_router_name}, + t_constants.RT_ROUTER, body) + + def remove_bottom_router_by_name(self, n_context, region_name, + router_name): + t_ctx = t_context.get_context_from_neutron_context(n_context) + b_client = self._get_client(region_name) + bottom_router = b_client.list_routers( + t_ctx, [{'key': 'name', + 'comparator': 'eq', + 'value': router_name}]) + if bottom_router: + b_client.delete_routers(t_ctx, bottom_router[0]['id']) + + def _fill_provider_info(self, from_net, to_net): + provider_attrs = provider_net.ATTRIBUTES + for provider_attr in provider_attrs: + if validators.is_attr_set(from_net.get(provider_attr)): + to_net[provider_attr] = from_net[provider_attr] + if validators.is_attr_set(from_net.get(az_def.AZ_HINTS)): + to_net[az_def.AZ_HINTS] = from_net[az_def.AZ_HINTS] + + def prepare_bottom_external_network(self, n_context, net, top_id): + t_ctx = t_context.get_context_from_neutron_context(n_context) + # use the first pod + az_name = net[az_def.AZ_HINTS][0] + pod = db_api.find_pod_by_az_or_region(t_ctx, az_name) + body = { + 'network': { + 'name': net['name'], + 'tenant_id': net['tenant_id'], + 'admin_state_up': True, + external_net.EXTERNAL: True, + } + } + self._fill_provider_info(net, body['network']) + return self.prepare_bottom_element( + t_ctx, net['tenant_id'], pod, {'id': top_id}, + t_constants.RT_NETWORK, body) + + def remove_bottom_external_network_by_name( + self, n_context, region_name, name): + t_ctx = t_context.get_context_from_neutron_context(n_context) + b_client = self._get_client(region_name) + b_net = b_client.list_networks( + t_ctx, [{'key': 'name', + 'comparator': 'eq', + 'value': name}]) + if b_net: + b_client.delete_networks(t_ctx, b_net[0]['id']) + + def prepare_bottom_external_subnet_by_bottom_name( + self, context, subnet, region_name, b_net_name, top_subnet_id): + t_ctx = t_context.get_context_from_neutron_context(context) + pod = db_api.get_pod_by_name(t_ctx, region_name) + b_client = self._get_client(region_name) + bottom_network = b_client.list_networks( + t_ctx, [{'key': 'name', + 'comparator': 'eq', + 'value': b_net_name}] + ) + if not bottom_network: + raise t_exceptions.InvalidInput( + reason='bottom network not found for %(b_net_name)s' + % {'b_net_name': b_net_name}) + body = { + 'subnet': { + 'name': top_subnet_id, + 'network_id': bottom_network[0]['id'], + 'tenant_id': subnet['tenant_id'] + } + } + attrs = ('ip_version', 'cidr', 'gateway_ip', 'allocation_pools', + 'enable_dhcp') + for attr in attrs: + if validators.is_attr_set(subnet.get(attr)): + body['subnet'][attr] = subnet[attr] + self.prepare_bottom_element( + t_ctx, subnet['tenant_id'], pod, {'id': top_subnet_id}, + t_constants.RT_SUBNET, body) + + def remove_bottom_external_subnet_by_name( + self, context, region_name, b_subnet_name): + t_ctx = t_context.get_context_from_neutron_context(context) + b_client = self._get_client(region_name) + bottom_subnet = b_client.list_subnets( + t_ctx, [{'key': 'name', + 'comparator': 'eq', + 'value': b_subnet_name}] + ) + if bottom_subnet: + b_client.delete_subnets(t_ctx, bottom_subnet[0]['id']) + + def prepare_bottom_router_gateway( + self, n_context, region_name, segment_name): + t_ctx = t_context.get_context_from_neutron_context(n_context) + pod = db_api.get_pod_by_name(t_ctx, region_name) + b_client = self._get_client(pod['region_name']) + # when using new l3 network model, a local router will + # be created automatically for an external net, and the + # router's name is same to the net's id + b_router = b_client.list_routers( + t_ctx, filters=[{'key': 'name', 'comparator': 'eq', + 'value': segment_name}]) + if not b_router: + raise t_exceptions.NotFound() + b_nets = b_client.list_networks( + t_ctx, filters=[{'key': 'name', + 'comparator': 'eq', + 'value': segment_name}] + ) + if not b_nets: + raise t_exceptions.NotFound() + b_info = {'network_id': b_nets[0]['id']} + return b_client.action_routers( + t_ctx, 'add_gateway', b_router[0]['id'], b_info) + + def remove_bottom_router_gateway( + self, n_context, region_name, b_net_name): + t_ctx = t_context.get_context_from_neutron_context(n_context) + pod = db_api.get_pod_by_name(t_ctx, region_name) + b_client = self._get_client(pod['region_name']) + # when using new l3 network model, a local router will + # be created automatically for an external net, and the + # router's name is same to the net's id + b_router = b_client.list_routers( + t_ctx, filters=[{'key': 'name', 'comparator': 'eq', + 'value': b_net_name}]) + if not b_router: + raise t_exceptions.NotFound() + + return b_client.action_routers( + t_ctx, 'remove_gateway', b_router[0]['id'], b_router[0]['id']) + @staticmethod def get_real_shadow_resource_iterator(t_ctx, res_type, res_id): shadow_res_type = None diff --git a/tricircle/network/local_plugin.py b/tricircle/network/local_plugin.py index a162bac3..1bcbff29 100644 --- a/tricircle/network/local_plugin.py +++ b/tricircle/network/local_plugin.py @@ -23,9 +23,12 @@ from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net from neutron_lib.api import extensions from neutron_lib.api import validators +from neutron_lib.callbacks import events import neutron_lib.constants as q_constants import neutron_lib.exceptions as q_exceptions from neutron_lib.plugins import directory +from neutron_lib.plugins.ml2 import api +from neutron_lib.utils import net from neutron_lib.utils import runtime import neutronclient.client as neutronclient @@ -892,6 +895,8 @@ class TricirclePlugin(plugin.Ml2Plugin): def _handle_security_group(self, t_ctx, q_ctx, port): if 'security_groups' not in port: return + if port.get('device_owner') and net.is_port_trusted(port): + return if not port['security_groups']: raw_client = self.neutron_handle._get_client(t_ctx) params = {'name': 'default'} @@ -955,3 +960,23 @@ class TricirclePlugin(plugin.Ml2Plugin): b_sgs.append(self.core_plugin.get_security_group( context, b_sg['id'], fields)) return b_sgs + + def _handle_segment_change(self, rtype, event, trigger, context, segment): + + network_id = segment.get('network_id') + + if event == events.PRECOMMIT_CREATE: + updated_segment = self.type_manager.reserve_network_segment( + context, segment) + # The segmentation id might be from ML2 type driver, update it + # in the original segment. + segment[api.SEGMENTATION_ID] = updated_segment[api.SEGMENTATION_ID] + elif event == events.PRECOMMIT_DELETE: + self.type_manager.release_network_segment(context, segment) + + # change in segments could affect resulting network mtu, so let's + # recalculate it + network_db = self._get_network(context, network_id) + network_db.mtu = self._get_network_mtu( + network_db, validate=(event != events.PRECOMMIT_DELETE)) + network_db.save(session=context.session) diff --git a/tricircle/network/segment_plugin.py b/tricircle/network/segment_plugin.py new file mode 100644 index 00000000..343d785d --- /dev/null +++ b/tricircle/network/segment_plugin.py @@ -0,0 +1,144 @@ +# Copyright 2018 Huazhong University of Science and Technology. +# 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 +import re + +from neutron.services.segments.plugin import Plugin +from neutron_lib.api.definitions import availability_zone as az_def +from neutron_lib.api.definitions import provider_net +from neutron_lib.exceptions import availability_zone as az_exc + +import tricircle.common.client as t_client +from tricircle.common import constants +import tricircle.common.context as t_context +from tricircle.common import xrpcapi +from tricircle.db import core +from tricircle.db import models +from tricircle.network.central_plugin import TricirclePlugin +from tricircle.network import helper + + +LOG = log.getLogger(__name__) + + +class TricircleSegmentPlugin(Plugin): + def __init__(self): + super(TricircleSegmentPlugin, self).__init__() + self.xjob_handler = xrpcapi.XJobAPI() + self.clients = {} + self.central_plugin = TricirclePlugin() + self.helper = helper.NetworkHelper(self) + + def _get_client(self, region_name): + if region_name not in self.clients: + self.clients[region_name] = t_client.Client(region_name) + return self.clients[region_name] + + def get_segment(self, context, sgmt_id, fields=None, tenant_id=None): + return super(TricircleSegmentPlugin, self).get_segment( + context, sgmt_id) + + def get_segments(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + return super(TricircleSegmentPlugin, self).get_segments( + context, filters, fields, sorts, limit, marker, page_reverse) + + @staticmethod + def _validate_availability_zones(context, az_list): + if not az_list: + return + t_ctx = t_context.get_context_from_neutron_context(context) + with context.session.begin(subtransactions=True): + pods = core.query_resource(t_ctx, models.Pod, [], []) + az_set = set(az_list) + + known_az_set = set([pod['az_name'] for pod in pods]) + known_az_set = known_az_set | set( + [pod['region_name'] for pod in pods]) + + diff = az_set - known_az_set + if diff: + raise az_exc.AvailabilityZoneNotFound( + availability_zone=diff.pop()) + + def create_segment(self, context, segment): + """Create a segment.""" + segment_data = segment['segment'] + segment_name = segment_data.get('name') + + # if configed enable_l3_route_network, + # will create real external network for each segment + if cfg.CONF.tricircle.enable_l3_route_network: + match_obj = re.match(constants.SEGMENT_NAME_PATTERN, + segment_name) + if match_obj: + match_list = match_obj.groups() + region_name = match_list[0] + self._validate_availability_zones(context, + [region_name]) + # create segment for maintaining the relationship + # between routed net and real external net + segment_db = super(TricircleSegmentPlugin, self).\ + create_segment(context, segment) + + # prepare real external network in central and bottom + net_data = { + 'tenant_id': segment_data.get('tenant_id'), + 'name': segment_name, + 'shared': False, + 'admin_state_up': True, + az_def.AZ_HINTS: [region_name], + provider_net.PHYSICAL_NETWORK: + segment_data.get('physical_network'), + provider_net.NETWORK_TYPE: + segment_data.get('network_type'), + 'router:external': True + } + self.central_plugin.create_network( + context, {'network': net_data}) + + return segment_db + else: + return super(TricircleSegmentPlugin, self).create_segment( + context, segment) + else: + return super(TricircleSegmentPlugin, self).create_segment( + context, segment) + + def delete_segment(self, context, uuid, for_net_delete=False): + segment_dict = self.get_segment(context, uuid) + segment_name = segment_dict['name'] + + # if enable l3 routed network and segment name starts + # with 'newl3-' need to delete bottom router + # and bottom external network + if cfg.CONF.tricircle.enable_l3_route_network and \ + segment_name and \ + segment_name.startswith(constants.PREFIX_OF_SEGMENT_NAME): + + # delete real external network + net_filter = {'name': [segment_name]} + nets = self.central_plugin.get_networks(context, net_filter) + if len(nets): + self.central_plugin.delete_network(context, nets[0]['id']) + + return super(TricircleSegmentPlugin, self).delete_segment( + context, uuid) + else: + return super(TricircleSegmentPlugin, self).delete_segment( + context, uuid) diff --git a/tricircle/tests/unit/network/test_central_plugin.py b/tricircle/tests/unit/network/test_central_plugin.py index 9081b26c..f3ccb972 100644 --- a/tricircle/tests/unit/network/test_central_plugin.py +++ b/tricircle/tests/unit/network/test_central_plugin.py @@ -367,6 +367,13 @@ class FakeClient(test_utils.FakeClient): if index != -1: del TOP_IPALLOCATIONS[index] + def dhcp_allocate_ip(self, subnets): + fixed_ips = [] + for subnet in subnets: + fixed_ips.append({'subnet_id': subnet['id'], + 'ip_address': '10.0.0.1'}) + return fixed_ips + def add_gateway_routers(self, ctx, *args, **kwargs): router_id, body = args try: @@ -376,6 +383,14 @@ class FakeClient(test_utils.FakeClient): ctx, [{'key': 'name', 'comparator': 'eq', 'value': t_name}]) b_id = t_ports[0]['id'] if t_ports else uuidutils.generate_uuid() host_id = 'host1' if self.region_name == 'pod_1' else 'host_2' + if not body.get('external_fixed_ips'): + net_id = body['network_id'] + subnets = self.list_subnets(ctx, + [{'key': 'network_id', + 'comparator': 'eq', + 'value': net_id}]) + body['external_fixed_ips'] = self.dhcp_allocate_ip(subnets) + self.create_ports(ctx, {'port': { 'admin_state_up': True, 'id': b_id, @@ -427,6 +442,9 @@ class FakeClient(test_utils.FakeClient): router = self.get_resource(constants.RT_ROUTER, ctx, router_id) return _fill_external_gateway_info(router) + def list_routers(self, ctx, filters=None): + return self.list_resources('router', ctx, filters) + def delete_routers(self, ctx, router_id): self.delete_resources('router', ctx, router_id) @@ -985,6 +1003,7 @@ class PluginTest(unittest.TestCase, xmanager.IN_TEST = True phynet = 'bridge' + phynet2 = 'bridge2' vlan_min, vlan_max = 2000, 2001 vxlan_min, vxlan_max = 20001, 20002 cfg.CONF.set_override('type_drivers', ['local', 'vlan'], @@ -992,7 +1011,8 @@ class PluginTest(unittest.TestCase, cfg.CONF.set_override('tenant_network_types', ['local', 'vlan'], group='tricircle') cfg.CONF.set_override('network_vlan_ranges', - ['%s:%d:%d' % (phynet, vlan_min, vlan_max)], + ['%s:%d:%d' % (phynet, vlan_min, vlan_max), + '%s:%d:%d' % (phynet2, vlan_min, vlan_max)], group='tricircle') cfg.CONF.set_override('bridge_network_type', 'vlan', group='tricircle') @@ -2959,8 +2979,8 @@ class PluginTest(unittest.TestCase, 'external_fixed_ips': [{'subnet_id': b_subnet_id, 'ip_address': '100.64.0.5'}]} - mock_action.assert_called_once_with(t_ctx, 'add_gateway', - b_router_id, body) + mock_action.assert_called_with(t_ctx, 'add_gateway', + b_router_id, body) self.assertFalse(mock_get_bridge_network.called) @patch.object(directory, 'get_plugin', new=fake_get_plugin) diff --git a/tricircle/tests/unit/network/test_local_plugin.py b/tricircle/tests/unit/network/test_local_plugin.py index e6daeabc..359a40fc 100644 --- a/tricircle/tests/unit/network/test_local_plugin.py +++ b/tricircle/tests/unit/network/test_local_plugin.py @@ -260,6 +260,7 @@ class FakePlugin(plugin.TricirclePlugin): self.neutron_handle = FakeNeutronHandle() self.on_trunk_create = {} self.on_subnet_delete = {} + self.type_manager = test_utils.FakeTypeManager() class PluginTest(unittest.TestCase): @@ -267,6 +268,11 @@ class PluginTest(unittest.TestCase): self.tenant_id = uuidutils.generate_uuid() self.plugin = FakePlugin() self.context = FakeContext() + phynet2 = 'provider2' + vlan_min, vlan_max = 2000, 3000 + cfg.CONF.set_override('network_vlan_ranges', + ['%s:%d:%d' % (phynet2, vlan_min, vlan_max)], + group='tricircle') def _prepare_resource(self, az_hints=None, enable_dhcp=True): network_id = uuidutils.generate_uuid() diff --git a/tricircle/tests/unit/network/test_segment_plugin.py b/tricircle/tests/unit/network/test_segment_plugin.py new file mode 100644 index 00000000..157abcb4 --- /dev/null +++ b/tricircle/tests/unit/network/test_segment_plugin.py @@ -0,0 +1,375 @@ +# Copyright 2018 Huazhong University of Science and Technology. +# 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 mock import patch +import unittest + +from neutron_lib.api.definitions import provider_net +from neutron_lib.plugins import constants as plugin_constants +from neutron_lib.plugins import directory + +import neutron.conf.common as q_config +from neutron.extensions import segment as extension +from neutron.plugins.ml2 import managers as n_managers +from neutron.services.segments import exceptions as sg_excp +from oslo_config import cfg +from oslo_serialization import jsonutils +from oslo_utils import uuidutils + +from tricircle.common import constants as t_constant +from tricircle.common import context +import tricircle.db.api as db_api +from tricircle.db import core +from tricircle.db import models +import tricircle.network.central_plugin as plugin +from tricircle.network import helper +from tricircle.network.segment_plugin import TricircleSegmentPlugin +from tricircle.tests.unit.network.test_central_plugin \ + import FakeClient as CentralFakeClient +from tricircle.tests.unit.network.test_central_plugin \ + import FakePlugin as CentralFakePlugin + +import tricircle.tests.unit.utils as test_utils + +_resource_store = test_utils.get_resource_store() +TOP_NETS = _resource_store.TOP_NETWORKS +TOP_SUBNETS = _resource_store.TOP_SUBNETS +TOP_PORTS = _resource_store.TOP_PORTS +TOP_ROUTERS = _resource_store.TOP_ROUTERS +TOP_SEGMENTS = _resource_store.TOP_NETWORKSEGMENTS +BOTTOM1_NETS = _resource_store.BOTTOM1_NETWORKS +BOTTOM1_SUBNETS = _resource_store.BOTTOM1_SUBNETS +BOTTOM1_PORTS = _resource_store.BOTTOM1_PORTS +TEST_TENANT_ID = test_utils.TEST_TENANT_ID +FakeNeutronContext = test_utils.FakeNeutronContext +TEST_TENANT_ID = test_utils.TEST_TENANT_ID + + +class FakeClient(CentralFakeClient): + def __init__(self, region_name=None): + super(FakeClient, self).__init__(region_name) + + def delete_segments(self, ctx, segment_id): + self.delete_resources('segment', ctx, segment_id) + + +class FakeExtensionManager(n_managers.ExtensionManager): + def __init__(self): + super(FakeExtensionManager, self).__init__() + + +class FakeHelper(helper.NetworkHelper): + def _get_client(self, region_name=None): + return FakeClient(region_name) + + +class FakeTrunkPlugin(object): + + def get_trunk_subports(self, context, filters): + return None + + +class FakePlugin(TricircleSegmentPlugin): + def start_rpc_state_reports_listener(self): + pass + + def __init__(self): + + self.type_manager = test_utils.FakeTypeManager() + self.extension_manager = FakeExtensionManager() + self.extension_manager.initialize() + self.helper = FakeHelper(self) + self.central_plugin = CentralFakePlugin() + + def _get_client(self, region_name): + return FakeClient(region_name) + + @staticmethod + def get_network_availability_zones(network): + zones = network.get('availability_zone_hints') \ + if network.get('availability_zone_hints') else [] + return list(zones) + + def _make_network_dict(self, network, fields=None, + process_extensions=True, context=None): + network = _transform_az(network) + if 'project_id' in network: + network['tenant_id'] = network['project_id'] + return network + + +def fake_get_plugin(alias=plugin_constants.CORE): + return CentralFakePlugin() + + +def fake_get_client(region_name): + return FakeClient(region_name) + + +def fake_get_context_from_neutron_context(q_context): + return context.get_db_context() + + +def _transform_az(network): + az_hints_key = 'availability_zone_hints' + if az_hints_key in network: + ret = test_utils.DotDict(network) + az_str = network[az_hints_key] + ret[az_hints_key] = jsonutils.loads(az_str) if az_str else [] + return ret + return network + + +def fake_delete_network(self, context, network_id): + fake_client = FakeClient() + fake_client.delete_networks(context, network_id) + + +class PluginTest(unittest.TestCase): + def setUp(self): + core.initialize() + core.ModelBase.metadata.create_all(core.get_engine()) + + cfg.CONF.register_opts(q_config.core_opts) + cfg.CONF.register_opts(plugin.tricircle_opts) + cfg.CONF.set_override('enable_l3_route_network', True, + group='tricircle') + plugin_path = \ + 'tricircle.tests.unit.network.test_central_plugin.FakePlugin' + cfg.CONF.set_override('core_plugin', plugin_path) + cfg.CONF.set_override('enable_api_gateway', True) + self.context = context.Context() + + phynet = 'bridge' + phynet2 = 'bridge2' + vlan_min, vlan_max = 2000, 3000 + cfg.CONF.set_override('type_drivers', ['local', 'vlan'], + group='tricircle') + cfg.CONF.set_override('tenant_network_types', ['local', 'vlan'], + group='tricircle') + cfg.CONF.set_override('network_vlan_ranges', + ['%s:%d:%d' % (phynet, vlan_min, vlan_max), + '%s:%d:%d' % (phynet2, vlan_min, vlan_max)], + group='tricircle') + cfg.CONF.set_override('bridge_network_type', 'vlan', + group='tricircle') + + def fake_get_plugin(alias=plugin_constants.CORE): + if alias == 'trunk': + return FakeTrunkPlugin() + return CentralFakePlugin() + from neutron_lib.plugins import directory + directory.get_plugin = fake_get_plugin + + global segments_plugin + segments_plugin = FakePlugin() + + def _basic_pod_route_setup(self): + pod1 = {'pod_id': 'pod_id_1', + 'region_name': 'pod_1', + 'az_name': 'az_name_1'} + pod2 = {'pod_id': 'pod_id_2', + 'region_name': 'pod_2', + 'az_name': 'az_name_2'} + pod3 = {'pod_id': 'pod_id_0', + 'region_name': 'top_pod', + 'az_name': ''} + for pod in (pod1, pod2, pod3): + db_api.create_pod(self.context, pod) + route1 = { + 'top_id': 'top_id_1', + 'pod_id': 'pod_id_1', + 'bottom_id': 'bottom_id_1', + 'resource_type': 'port'} + route2 = { + 'top_id': 'top_id_2', + 'pod_id': 'pod_id_2', + 'bottom_id': 'bottom_id_2', + 'resource_type': 'port'} + with self.context.session.begin(): + core.create_resource(self.context, models.ResourceRouting, route1) + core.create_resource(self.context, models.ResourceRouting, route2) + + @patch.object(directory, 'get_plugin', new=fake_get_plugin) + @patch.object(context, 'get_context_from_neutron_context') + @patch.object(TricircleSegmentPlugin, '_get_client', + new=fake_get_client) + @patch.object(plugin.TricirclePlugin, '_get_client', + new=fake_get_client) + def test_create_segment(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + neutron_context = FakeNeutronContext() + tricircle_context = context.get_db_context() + mock_context.return_value = tricircle_context + + # create a routed network + top_net_id = uuidutils.generate_uuid() + network = {'network': { + 'id': top_net_id, 'name': 'multisegment1', + 'tenant_id': TEST_TENANT_ID, + 'admin_state_up': True, 'shared': False, + 'availability_zone_hints': [], + provider_net.PHYSICAL_NETWORK: 'bridge', + provider_net.NETWORK_TYPE: 'vlan', + provider_net.SEGMENTATION_ID: '2016'}} + fake_plugin.central_plugin.create_network(neutron_context, network) + net_filter = {'name': ['multisegment1']} + top_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertEqual(top_net[0]['id'], top_net_id) + + res = fake_plugin.get_segments(neutron_context) + self.assertEqual(len(res), 1) + + # success + # segment's name matches 'newl3-regionname-detailname' + segment2_id = uuidutils.generate_uuid() + segment2_name = t_constant.PREFIX_OF_SEGMENT_NAME + 'pod_1' \ + + t_constant.PREFIX_OF_SEGMENT_NAME_DIVISION \ + + 'segment2' + segment2 = {'segment': { + 'id': segment2_id, + 'name': segment2_name, + 'network_id': top_net_id, + extension.PHYSICAL_NETWORK: 'bridge2', + extension.NETWORK_TYPE: 'flat', + extension.SEGMENTATION_ID: '2016', + 'tenant_id': TEST_TENANT_ID, + 'description': None + }} + fake_plugin.create_segment(neutron_context, segment2) + res = fake_plugin.get_segment(neutron_context, segment2_id) + self.assertEqual(res['name'], segment2_name) + net_filter = {'name': [segment2_name]} + b_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertEqual(b_net[0]['name'], segment2_name) + + # create segments normally + # segment's name doesn't match 'newl3-regionname-detailname' + segment3_id = uuidutils.generate_uuid() + segment3_name = 'test-segment3' + segment3 = {'segment': { + 'id': segment3_id, + 'name': segment3_name, + 'network_id': top_net_id, + extension.PHYSICAL_NETWORK: 'bridge2', + extension.NETWORK_TYPE: 'flat', + extension.SEGMENTATION_ID: '2016', + 'tenant_id': TEST_TENANT_ID, + 'description': None + }} + fake_plugin.create_segment(neutron_context, segment3) + res = fake_plugin.get_segment(neutron_context, segment3_id) + self.assertEqual(res['name'], segment3_name) + net_filter = {'name': [segment3_name]} + b_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertFalse(b_net) + + @patch.object(directory, 'get_plugin', new=fake_get_plugin) + @patch.object(context, 'get_context_from_neutron_context') + @patch.object(TricircleSegmentPlugin, '_get_client', + new=fake_get_client) + @patch.object(plugin.TricirclePlugin, '_get_client', + new=fake_get_client) + @patch.object(plugin.TricirclePlugin, 'delete_network', + new=fake_delete_network) + def test_delete_segment(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + neutron_context = FakeNeutronContext() + tricircle_context = context.get_db_context() + mock_context.return_value = tricircle_context + + # create a routed network + top_net_id = uuidutils.generate_uuid() + network = {'network': { + 'id': top_net_id, 'name': 'multisegment1', + 'tenant_id': TEST_TENANT_ID, + 'admin_state_up': True, 'shared': False, + 'availability_zone_hints': [], + provider_net.PHYSICAL_NETWORK: 'bridge', + provider_net.NETWORK_TYPE: 'vlan', + provider_net.SEGMENTATION_ID: '2016'}} + fake_plugin.central_plugin.create_network(neutron_context, network) + + # create a normal segment + segment2_id = uuidutils.generate_uuid() + segment2_name = 'test-segment3' + segment2 = {'segment': { + 'id': segment2_id, + 'name': segment2_name, + 'network_id': top_net_id, + extension.PHYSICAL_NETWORK: 'bridge2', + extension.NETWORK_TYPE: 'flat', + extension.SEGMENTATION_ID: '2016', + 'tenant_id': TEST_TENANT_ID, + 'description': None + }} + fake_plugin.create_segment(neutron_context, segment2) + + # create a segment + # with it's name matches 'newl3-regionname-detailname' + segment3_id = uuidutils.generate_uuid() + segment3_name = t_constant.PREFIX_OF_SEGMENT_NAME + 'pod_1'\ + + t_constant.PREFIX_OF_SEGMENT_NAME_DIVISION + 'segment2' + segment3 = {'segment': { + 'id': segment3_id, + 'name': segment3_name, + 'network_id': top_net_id, + extension.PHYSICAL_NETWORK: 'bridge2', + extension.NETWORK_TYPE: 'flat', + extension.SEGMENTATION_ID: '2016', + 'tenant_id': TEST_TENANT_ID, + 'description': None + }} + fake_plugin.create_segment(neutron_context, segment3) + + res = fake_plugin.get_segment(neutron_context, segment2_id) + self.assertEqual(res['name'], segment2_name) + res = fake_plugin.get_segment(neutron_context, segment3_id) + self.assertEqual(res['name'], segment3_name) + net_filter = {'name': [segment2_name]} + b_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertFalse(b_net) + net_filter = {'name': [segment3_name]} + b_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertEqual(b_net[0]['name'], segment3_name) + + # delete a segment + # it's name matches 'newl3-regionname-detailname' + fake_plugin.delete_segment(neutron_context, segment3_id) + self.assertRaises(sg_excp.SegmentNotFound, + fake_plugin.get_segment, + neutron_context, segment3_id) + net_filter = {'name': [segment3_name]} + b_net = fake_plugin.central_plugin.get_networks( + neutron_context, net_filter) + self.assertFalse(b_net) + + # delete a normal segment + fake_plugin.delete_segment(neutron_context, segment2_id) + self.assertRaises(sg_excp.SegmentNotFound, + fake_plugin.get_segment, + neutron_context, segment2_id) + + def tearDown(self): + core.ModelBase.metadata.drop_all(core.get_engine()) + test_utils.get_resource_store().clean() + cfg.CONF.unregister_opts(q_config.core_opts) + cfg.CONF.unregister_opts(plugin.tricircle_opts) diff --git a/tricircle/tests/unit/utils.py b/tricircle/tests/unit/utils.py index 1b7f0d7d..4987e7b5 100644 --- a/tricircle/tests/unit/utils.py +++ b/tricircle/tests/unit/utils.py @@ -65,7 +65,8 @@ class ResourceStore(object): ('sfc_chain_classifier_associations', None), ('qos_policies', constants.RT_QOS), ('qos_bandwidth_limit_rules', - 'qos_bandwidth_limit_rules')] + 'qos_bandwidth_limit_rules'), + ('segments', None)] def __init__(self): self.store_list = []