The first part of new l3 networking implement
1. What is the problem In the current l3 networking model, the host route will be only valid after DHCP lease time expired, and renewed. 2. What is the solution for the problem To overcome the drawbacks, a new l3 networking model is introduced in this spec(https://review.openstack.org/#/c/530904/). This part of implement includes bottom external network creation after segment-create and bottom external subnet creation after subnet-create using segment. 3. What features need to be implemented to the Tricircle to realize the solution N/A [Test record]http://note.youdao.com/noteshare?id=e2e5e4bf6e73db1bc55f47963a72b39d&sub=6158D53B085D4A7B8EB046F01FB62748 Change-Id: I7d968a402b23428b4d3a7061ec507ed30a6132ef
This commit is contained in:
parent
dca1fb1954
commit
e57c5f497f
|
@ -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 = '-'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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 = []
|
||||
|
|
Loading…
Reference in New Issue