Enable CRUD for trunk ports

This patch enables basic CRUD operations on trunk ports and defines
related API extensions. Trunk ports and sub-ports can be persisted
in the Neutron model and are made visible through the API, but the
L2 agent is not notified and no trunk ports or subports are actually
instantiated on compute hosts.

This one of the main patches in the series that implement the end
to end functionality.

Partially-implements: blueprint vlan-aware-vms

Co-Authored-By: Armando Migliaccio <armamig@gmail.com>
Change-Id: I26453eb9a1b25e116193417271400994ac57e4c1
This commit is contained in:
Ryan Tidwell 2016-02-18 17:34:43 +08:00 committed by Armando Migliaccio
parent 080bf68895
commit 9cda319687
26 changed files with 1238 additions and 88 deletions

7
devstack/lib/trunk Normal file
View File

@ -0,0 +1,7 @@
function configure_trunk_service_plugin {
_neutron_service_plugin_class_add "trunk"
}
function configure_trunk_extension {
configure_trunk_service_plugin
}

View File

@ -6,6 +6,7 @@ source $LIBDIR/l2_agent_sriovnicswitch
source $LIBDIR/ml2
source $LIBDIR/qos
source $LIBDIR/ovs
source $LIBDIR/trunk
Q_BUILD_OVS_FROM_GIT=$(trueorfalse False Q_BUILD_OVS_FROM_GIT)
@ -22,6 +23,9 @@ if [[ "$1" == "stack" ]]; then
if is_service_enabled q-qos; then
configure_qos
fi
if is_service_enabled q-trunk; then
configure_trunk_extension
fi
if [[ "$Q_AGENT" == "openvswitch" ]] && \
[[ "$Q_BUILD_OVS_FROM_GIT" == "True" ]]; then
remove_ovs_packages

View File

@ -218,5 +218,12 @@
"create_flavor_service_profile": "rule:admin_only",
"delete_flavor_service_profile": "rule:admin_only",
"get_flavor_service_profile": "rule:regular_user",
"get_auto_allocated_topology": "rule:admin_or_owner"
"get_auto_allocated_topology": "rule:admin_or_owner",
"create_trunk": "rule:regular_user",
"get_trunk": "rule:admin_or_owner",
"delete_trunk": "rule:admin_or_owner",
"get_subports": "",
"add_subports": "rule:admin_or_owner",
"remove_subports": "rule:admin_or_owner"
}

152
neutron/extensions/trunk.py Executable file
View File

@ -0,0 +1,152 @@
# Copyright (c) 2016 ZTE Inc.
# 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_log import log as logging
from neutron_lib.api import converters
from neutron_lib.api import validators
from neutron._i18n import _
from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import resource_helper
LOG = logging.getLogger(__name__)
# TODO(armax): this validator was introduced in neutron-lib in
# https://review.openstack.org/#/c/319386/; remove it as soon
# as there is a new release.
def validate_subports(data, valid_values=None):
if not isinstance(data, list):
msg = _("Invalid data format for subports: '%s'") % data
LOG.debug(msg)
return msg
subport_ids = set()
segmentation_ids = set()
for subport in data:
if not isinstance(subport, dict):
msg = _("Invalid data format for subport: '%s'") % subport
LOG.debug(msg)
return msg
# Expect a non duplicated and valid port_id for the subport
if 'port_id' not in subport:
msg = _("A valid port UUID must be specified")
LOG.debug(msg)
return msg
elif validators.validate_uuid(subport["port_id"]):
msg = _("Invalid UUID for subport: '%s'") % subport["port_id"]
return msg
elif subport["port_id"] in subport_ids:
msg = _("Non unique UUID for subport: '%s'") % subport["port_id"]
return msg
subport_ids.add(subport["port_id"])
# Validate that both segmentation id and segmentation type are
# specified, and that the client does not duplicate segmentation
# ids
segmentation_id = subport.get("segmentation_id")
segmentation_type = subport.get("segmentation_type")
if (not segmentation_id or not segmentation_type) and len(subport) > 1:
msg = _("Invalid subport details '%s': missing segmentation "
"information. Must specify both segmentation_id and "
"segmentation_type") % subport
LOG.debug(msg)
return msg
if segmentation_id in segmentation_ids:
msg = _("Segmentation ID '%(seg_id)s' for '%(subport)s' is not "
"unique") % {"seg_id": segmentation_id,
"subport": subport["port_id"]}
LOG.debug(msg)
return msg
if segmentation_id:
segmentation_ids.add(segmentation_id)
validators.validators['type:subports'] = validate_subports
RESOURCE_ATTRIBUTE_MAP = {
'trunks': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True, 'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True,
'validate':
{'type:string': attr.TENANT_ID_MAX_LEN},
'is_visible': True},
'port_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True,
'validate': {'type:uuid': None},
'is_visible': True},
'sub_ports': {'allow_post': True, 'allow_put': False,
'default': [],
'convert_list_to': converters.convert_kvp_list_to_dict,
'validate': {'type:subports': None},
'enforce_policy': True,
'is_visible': True}
},
}
class Trunk(extensions.ExtensionDescriptor):
"""Trunk API extension."""
@classmethod
def get_name(cls):
return "Trunk Extension"
@classmethod
def get_alias(cls):
return "trunk"
@classmethod
def get_description(cls):
return "Provides support for trunk ports"
@classmethod
def get_updated(cls):
return "2016-01-01T10:00:00-00:00"
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
action_map = {'trunk': {'add_subports': 'PUT',
'remove_subports': 'PUT',
'get_subports': 'GET'}}
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
'trunk',
action_map=action_map,
register_quota=True)
def update_attributes_map(self, attributes, extension_attrs_map=None):
super(Trunk, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_required_extensions(self):
return ["binding"]
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -0,0 +1,56 @@
# 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_lib import constants
from neutron.api import extensions
# NOTE(armax): because of the API machinery, this extension must be on
# its own. This aims at providing subport information for ports that
# are parent in a trunk so that consumers of the Neutron API, like Nova
# can efficiently access trunk information for things like metadata or
# config-drive configuration.
EXTENDED_ATTRIBUTES_2_0 = {
'ports': {'trunk_details': {'allow_post': False, 'allow_put': False,
'default': constants.ATTR_NOT_SPECIFIED,
'is_visible': True,
'enforce_policy': True,
'required_by_policy': True}},
}
class Trunk_details(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Trunk port details"
@classmethod
def get_alias(cls):
return "trunk-details"
@classmethod
def get_description(cls):
return "Expose trunk port details"
@classmethod
def get_updated(cls):
return "2016-01-01T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -43,6 +43,12 @@ class SubPort(base.NeutronDbObject):
fields_no_update = ['segmentation_type', 'segmentation_id']
def to_dict(self):
_dict = super(SubPort, self).to_dict()
# trunk_id is redundant in the subport dict.
_dict.pop('trunk_id')
return _dict
def create(self):
with db_api.autonested_transaction(self.obj_context.session):
try:
@ -66,6 +72,11 @@ class SubPort(base.NeutronDbObject):
raise t_exc.TrunkNotFound(trunk_id=self.trunk_id)
raise n_exc.PortNotFound(port_id=self.port_id)
except base.NeutronDbObjectDuplicateEntry:
raise t_exc.DuplicateSubPort(
segmentation_type=self.segmentation_type,
segmentation_id=self.segmentation_id,
trunk_id=self.trunk_id)
@obj_base.VersionedObjectRegistry.register

View File

@ -0,0 +1,15 @@
# 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 neutron.services.trunk.validators # noqa

View File

@ -0,0 +1,20 @@
# 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.
PARENT_PORT = 'parent_port'
SUBPORT = 'subport'
TRUNK = 'trunk'
TRUNK_PLUGIN = 'trunk_plugin'
VLAN = 'vlan'

View File

@ -1,35 +0,0 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company, LP
#
# 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_db import exception as db_exc
from oslo_utils import uuidutils
from neutron.services.trunk import exceptions
from neutron.services.trunk import models
def create_trunk(context, port_id, description=None):
"""Create a trunk (with description) given the parent port uuid."""
try:
with context.session.begin(subtransactions=True):
context.session.add(
models.Trunk(
id=uuidutils.generate_uuid(),
tenant_id=context.tenant_id,
port_id=port_id,
description=description))
except db_exc.DBDuplicateEntry:
raise exceptions.TrunkPortInUse(port_id=port_id)

View File

@ -24,3 +24,22 @@ class TrunkPortInUse(n_exc.InUse):
class TrunkNotFound(n_exc.NotFound):
message = _("Trunk %(trunk_id)s could not be found.")
class SubPortNotFound(n_exc.NotFound):
message = _("SubPort on trunk %(trunk_id)s with parent port %(port_id)s "
"could not be found.")
class DuplicateSubPort(n_exc.InUse):
message = _("segmentation_type %(segmentation_type)s and segmentation_id "
"%(segmentation_id)s already in use on trunk %(trunk_id)s.")
class ParentPortInUse(n_exc.InUse):
message = _("Port %(port_id)s is currently in use and is not "
"eligible for use as a parent port.")
class TrunkInUse(n_exc.InUse):
message = _("Trunk %(trunk_id)s is currently in use.")

View File

@ -0,0 +1,178 @@
# 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_log import log as logging
from oslo_utils import uuidutils
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.db import api as db_api
from neutron.db import common_db_mixin
from neutron.db import db_base_plugin_common
from neutron.objects import base as objects_base
from neutron.objects import trunk as trunk_objects
from neutron.services import service_base
from neutron.services.trunk import constants
from neutron.services.trunk import exceptions as trunk_exc
from neutron.services.trunk import rules
LOG = logging.getLogger(__name__)
class TrunkPlugin(service_base.ServicePluginBase,
common_db_mixin.CommonDbMixin):
supported_extension_aliases = ["trunk", "trunk-details"]
def __init__(self):
self._segmentation_types = {}
#TODO(tidwellr) notify using events.AFTER_INIT once available
registry.notify(constants.TRUNK_PLUGIN, events.AFTER_CREATE, self)
LOG.debug('Trunk plugin loaded')
def add_segmentation_type(self, segmentation_type, id_validator):
self._segmentation_types[segmentation_type] = id_validator
LOG.debug('Added support for segmentation type %s', segmentation_type)
def validate(self, context, trunk):
"""Return a valid trunk or raises an error if unable to do so."""
trunk_details = trunk
trunk_validator = rules.TrunkPortValidator(trunk['port_id'])
trunk_details['port_id'] = trunk_validator.validate(context)
subports_validator = rules.SubPortsValidator(
self._segmentation_types, trunk['sub_ports'], trunk['port_id'])
trunk_details['sub_ports'] = subports_validator.validate(context)
return trunk_details
def get_plugin_description(self):
return "Trunk port service plugin"
@classmethod
def get_plugin_type(cls):
return "trunk"
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_trunk(self, context, trunk_id, fields=None):
"""Return information for the specified trunk."""
obj = trunk_objects.Trunk.get_object(context, id=trunk_id)
if obj is None:
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
return obj
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_trunks(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None, page_reverse=False):
"""Return information for available trunks."""
filters = filters or {}
pager = objects_base.Pager(sorts=sorts, limit=limit,
page_reverse=page_reverse, marker=marker)
return trunk_objects.Trunk.get_objects(context, _pager=pager,
**filters)
@db_base_plugin_common.convert_result_to_dict
def create_trunk(self, context, trunk):
"""Create a trunk."""
trunk = self.validate(context, trunk['trunk'])
sub_ports = [trunk_objects.SubPort(
context=context,
port_id=p['port_id'],
segmentation_id=p['segmentation_id'],
segmentation_type=p['segmentation_type'])
for p in trunk['sub_ports']]
trunk_obj = trunk_objects.Trunk(context=context,
id=uuidutils.generate_uuid(),
tenant_id=trunk['tenant_id'],
port_id=trunk['port_id'],
sub_ports=sub_ports)
trunk_obj.create()
return trunk_obj
def delete_trunk(self, context, trunk_id):
"""Delete the specified trunk."""
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
if trunk:
trunk_validator = rules.TrunkPortValidator(trunk.port_id)
if not trunk_validator.is_bound(context):
trunk.delete()
return
raise trunk_exc.TrunkInUse(trunk_id=trunk_id)
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
@db_base_plugin_common.convert_result_to_dict
def add_subports(self, context, trunk_id, subports):
"""Add one or more subports to trunk."""
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
if trunk is None:
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
# Check for basic validation since the request body here is not
# automatically validated by the API layer.
subports_validator = rules.SubPortsValidator(
self._segmentation_types, subports)
subports = subports_validator.validate(context, basic_validation=True)
with db_api.autonested_transaction(context.session):
for subport in subports:
obj = trunk_objects.SubPort(
context=context,
trunk_id=trunk_id,
port_id=subport['port_id'],
segmentation_type=subport['segmentation_type'],
segmentation_id=subport['segmentation_id'])
obj.create()
trunk['sub_ports'].append(obj)
return trunk
@db_base_plugin_common.convert_result_to_dict
def remove_subports(self, context, trunk_id, subports):
"""Remove one or more subports from trunk."""
with db_api.autonested_transaction(context.session):
trunk = trunk_objects.Trunk.get_object(context, id=trunk_id)
if trunk is None:
raise trunk_exc.TrunkNotFound(trunk_id=trunk_id)
subports_validator = rules.SubPortsValidator(
self._segmentation_types, subports)
# the subports are being removed, therefore we do not need to
# enforce any specific trunk rules, other than basic validation
# of the request body.
subports = subports_validator.validate(
context, basic_validation=True,
trunk_validation=False)
current_subports = {p.port_id: p for p in trunk.sub_ports}
for subport in subports:
subport_obj = current_subports.pop(subport['port_id'], None)
if not subport_obj:
raise trunk_exc.SubPortNotFound(trunk_id=trunk_id,
port_id=subport['port_id'])
subport_obj.delete()
trunk.sub_ports = list(current_subports.values())
return trunk
@db_base_plugin_common.filter_fields
def get_subports(self, context, trunk_id, fields=None):
"""Return subports for the specified trunk."""
trunk = self.get_trunk(context, trunk_id)
return {'sub_ports': trunk['sub_ports']}

View File

@ -0,0 +1,116 @@
# 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_lib import constants as n_const
from neutron_lib import exceptions as n_exc
from neutron._i18n import _
from neutron.extensions import portbindings
from neutron.extensions import trunk
from neutron import manager
from neutron.objects import trunk as trunk_objects
from neutron.services.trunk import exceptions as trunk_exc
# This layer is introduced for keeping busines logic and
# data persistence decoupled.
class TrunkPortValidator(object):
def __init__(self, port_id):
self.port_id = port_id
def validate(self, context):
"""Validate that the port can be used in a trunk."""
# TODO(tidwellr): there is a chance of a race between the
# time these checks are performed and the time the trunk
# creation is executed. To be revisited, if it bites.
# Validate that the given port_id is not used by a subport.
subports = trunk_objects.SubPort.get_objects(
context, port_id=self.port_id)
if subports:
raise trunk_exc.TrunkPortInUse(port_id=self.port_id)
# Validate that the given port_id is not used by a trunk.
trunks = trunk_objects.Trunk.get_objects(context, port_id=self.port_id)
if trunks:
raise trunk_exc.ParentPortInUse(port_id=self.port_id)
if self.is_bound(context):
raise trunk_exc.ParentPortInUse(port_id=self.port_id)
return self.port_id
def is_bound(self, context):
"""Return true if the port is bound, false otherwise."""
# Validate that the given port_id does not have a port binding.
core_plugin = manager.NeutronManager.get_plugin()
port = core_plugin.get_port(context, self.port_id)
device_owner = port.get('device_owner', '')
return port.get(portbindings.HOST_ID) or \
device_owner.startswith(n_const.DEVICE_OWNER_COMPUTE_PREFIX)
class SubPortsValidator(object):
def __init__(self, segmentation_types, subports, trunk_port_id=None):
self._segmentation_types = segmentation_types
self.subports = subports
self.trunk_port_id = trunk_port_id
def validate(self, context,
basic_validation=False, trunk_validation=True):
"""Validate that subports can be used in a trunk."""
# Perform basic validation on subports, in case subports
# are not automatically screened by the API layer.
if basic_validation:
msg = trunk.validate_subports(self.subports)
if msg:
raise n_exc.InvalidInput(error_message=msg)
if trunk_validation:
return [self._validate(context, s) for s in self.subports]
else:
return self.subports
def _validate(self, context, subport):
# Check that the subport doesn't reference the same port_id as a
# trunk we may be in the middle of trying to create, in other words
# make the validation idiot proof.
if subport['port_id'] == self.trunk_port_id:
raise trunk_exc.ParentPortInUse(port_id=subport['port_id'])
# If the segmentation details are missing, we will need to
# figure out defaults when the time comes to support Ironic.
# We can reasonably expect segmentation details to be provided
# in all other cases for now.
segmentation_id = subport.get("segmentation_id")
segmentation_type = subport.get("segmentation_type")
if not segmentation_id or not segmentation_type:
msg = _("Invalid subport details '%s': missing segmentation "
"information. Must specify both segmentation_id and "
"segmentation_type") % subport
raise n_exc.InvalidInput(error_message=msg)
if segmentation_type not in self._segmentation_types:
msg = _("Invalid segmentation_type '%s'") % segmentation_type
raise n_exc.InvalidInput(error_message=msg)
if not self._segmentation_types[segmentation_type](segmentation_id):
msg = _("Invalid segmentation id '%s'") % segmentation_id
raise n_exc.InvalidInput(error_message=msg)
trunk_validator = TrunkPortValidator(subport['port_id'])
trunk_validator.validate(context)
return subport

View File

@ -0,0 +1,18 @@
# 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.services.trunk.validators import vlan
# Register segmentation_type validation drivers
vlan.register()

View File

@ -0,0 +1,39 @@
# 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_log import log as logging
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.plugins.common import constants as common_consts
from neutron.services.trunk import constants as trunk_consts
LOG = logging.getLogger(__name__)
def register():
#TODO(tidwellr) register for AFTER_INIT once available
registry.subscribe(handler, trunk_consts.TRUNK_PLUGIN,
events.AFTER_CREATE)
LOG.debug('Registering for trunk support')
def handler(resource, event, trigger, **kwargs):
trigger.add_segmentation_type(trunk_consts.VLAN, vlan_range)
LOG.debug('Registration complete')
def vlan_range(segmentation_id):
min_vid, max_vid = common_consts.MIN_VLAN_TAG, common_consts.MAX_VLAN_TAG
return min_vid <= segmentation_id <= max_vid

View File

@ -66,6 +66,7 @@ case $VENV in
load_conf_hook sorting
load_conf_hook pagination
load_rc_hook qos
load_rc_hook trunk
load_conf_hook osprofiler
if [[ "$VENV" =~ "pecan" ]]; then
load_conf_hook pecan

View File

@ -33,5 +33,7 @@ NETWORK_API_EXTENSIONS="
standard-attr-description, \
subnet_allocation, \
tag, \
timestamp_core"
timestamp_core, \
trunk, \
trunk-details"
NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')"

View File

@ -0,0 +1 @@
enable_service q-trunk

View File

@ -218,5 +218,12 @@
"create_flavor_service_profile": "rule:admin_only",
"delete_flavor_service_profile": "rule:admin_only",
"get_flavor_service_profile": "rule:regular_user",
"get_auto_allocated_topology": "rule:admin_or_owner"
"get_auto_allocated_topology": "rule:admin_or_owner",
"create_trunk": "rule:regular_user",
"get_trunk": "rule:admin_or_owner",
"delete_trunk": "rule:admin_or_owner",
"get_subports": "",
"add_subports": "rule:admin_or_owner",
"remove_subports": "rule:admin_or_owner"
}

View File

@ -0,0 +1,133 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 tempest.lib import exceptions as lib_exc
from tempest import test
from neutron.tests.tempest.api import base
class TrunkTestJSONBase(base.BaseAdminNetworkTest):
def _create_trunk_with_network_and_parent(self, subports):
network = self.create_network()
parent_port = self.create_port(network)
return self.client.create_trunk(parent_port['id'], subports)
class TrunkTestJSON(TrunkTestJSONBase):
@classmethod
@test.requires_ext(extension="trunk", service="network")
def resource_setup(cls):
super(TrunkTestJSON, cls).resource_setup()
def tearDown(self):
# NOTE(tidwellr) These tests create networks and ports, clean them up
# after each test to avoid hitting quota limits
self.resource_cleanup()
super(TrunkTestJSON, self).tearDown()
@test.idempotent_id('e1a6355c-4768-41f3-9bf8-0f1d192bd501')
def test_create_trunk_empty_subports_list(self):
trunk = self._create_trunk_with_network_and_parent([])
observed_trunk = self.client.show_trunk(trunk['trunk']['id'])
self.assertEqual(trunk, observed_trunk)
@test.idempotent_id('382dfa39-ca03-4bd3-9a1c-91e36d2e3796')
def test_create_trunk_subports_not_specified(self):
trunk = self._create_trunk_with_network_and_parent(None)
observed_trunk = self.client.show_trunk(trunk['trunk']['id'])
self.assertEqual(trunk, observed_trunk)
@test.idempotent_id('7de46c22-e2b6-4959-ac5a-0e624632ab32')
def test_create_show_delete_trunk(self):
trunk = self._create_trunk_with_network_and_parent(None)
trunk_id = trunk['trunk']['id']
parent_port_id = trunk['trunk']['port_id']
res = self.client.show_trunk(trunk_id)
self.assertEqual(trunk_id, res['trunk']['id'])
self.assertEqual(parent_port_id, res['trunk']['port_id'])
self.client.delete_trunk(trunk_id)
self.assertRaises(lib_exc.NotFound, self.client.show_trunk, trunk_id)
@test.idempotent_id('73365f73-bed6-42cd-960b-ec04e0c99d85')
def test_list_trunks(self):
trunk1 = self._create_trunk_with_network_and_parent(None)
trunk2 = self._create_trunk_with_network_and_parent(None)
expected_trunks = {trunk1['trunk']['id']: trunk1['trunk'],
trunk2['trunk']['id']: trunk2['trunk']}
trunk_list = self.client.list_trunks()['trunks']
matched_trunks = [x for x in trunk_list if x['id'] in expected_trunks]
self.assertEqual(2, len(matched_trunks))
for trunk in matched_trunks:
self.assertEqual(expected_trunks[trunk['id']], trunk)
@test.idempotent_id('bb5fcead-09b5-484a-bbe6-46d1e06d6cc0')
def test_add_subport(self):
trunk = self._create_trunk_with_network_and_parent([])
network = self.create_network()
port = self.create_port(network)
subports = [{'port_id': port['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}]
self.client.add_subports(trunk['trunk']['id'], subports)
trunk = self.client.show_trunk(trunk['trunk']['id'])
observed_subports = trunk['trunk']['sub_ports']
self.assertEqual(1, len(observed_subports))
created_subport = observed_subports[0]
self.assertEqual(subports[0], created_subport)
@test.idempotent_id('96eea398-a03c-4c3e-a99e-864392c2ca53')
def test_remove_subport(self):
subport_parent1 = self.create_port(self.create_network())
subport_parent2 = self.create_port(self.create_network())
subports = [{'port_id': subport_parent1['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2},
{'port_id': subport_parent2['id'],
'segmentation_type': 'vlan',
'segmentation_id': 4}]
trunk = self._create_trunk_with_network_and_parent(subports)
removed_subport = trunk['trunk']['sub_ports'][0]
expected_subport = None
for subport in subports:
if subport['port_id'] != removed_subport['port_id']:
expected_subport = subport
break
# Remove the subport and validate PUT response
res = self.client.remove_subports(trunk['trunk']['id'],
[removed_subport])
self.assertEqual(1, len(res['sub_ports']))
self.assertEqual(expected_subport, res['sub_ports'][0])
# Validate the results of a subport list
trunk = self.client.show_trunk(trunk['trunk']['id'])
observed_subports = trunk['trunk']['sub_ports']
self.assertEqual(1, len(observed_subports))
self.assertEqual(expected_subport, observed_subports[0])
@test.idempotent_id('bb5fcaad-09b5-484a-dde6-4cd1ea6d6ff0')
def test_get_subports(self):
network = self.create_network()
port = self.create_port(network)
subports = [{'port_id': port['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}]
trunk = self._create_trunk_with_network_and_parent(subports)
trunk = self.client.get_subports(trunk['trunk']['id'])
observed_subports = trunk['sub_ports']
self.assertEqual(1, len(observed_subports))

View File

@ -0,0 +1,190 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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_utils import uuidutils
from tempest.lib import exceptions as lib_exc
from tempest import test
from neutron.tests.tempest.api import test_trunk
class TrunkTestJSON(test_trunk.TrunkTestJSONBase):
def tearDown(self):
# NOTE(tidwellr) These tests create networks and ports, clean them up
# after each test to avoid hitting quota limits
self.resource_cleanup()
super(TrunkTestJSON, self).tearDown()
@classmethod
@test.requires_ext(extension="trunk", service="network")
def resource_setup(cls):
super(test_trunk.TrunkTestJSONBase, cls).resource_setup()
@test.attr(type='negative')
@test.idempotent_id('1b5cf87a-1d3a-4a94-ba64-647153d54f32')
def test_create_trunk_nonexistent_port_id(self):
self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
uuidutils.generate_uuid(), [])
@test.attr(type='negative')
@test.idempotent_id('980bca3b-b0be-45ac-8067-b401e445b796')
def test_create_trunk_nonexistent_subport_port_id(self):
network = self.create_network()
parent_port = self.create_port(network)
self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
parent_port['id'],
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'vlan',
'segmentation_id': 2}])
@test.attr(type='negative')
@test.idempotent_id('a5c5200a-72a0-43c5-a11a-52f808490344')
def test_create_subport_nonexistent_port_id(self):
trunk = self._create_trunk_with_network_and_parent([])
self.assertRaises(lib_exc.NotFound, self.client.add_subports,
trunk['trunk']['id'],
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'vlan',
'segmentation_id': 2}])
@test.attr(type='negative')
@test.idempotent_id('80deb6a9-da2a-48db-b7fd-bcef5b14edc1')
def test_create_subport_nonexistent_trunk(self):
network = self.create_network()
parent_port = self.create_port(network)
self.assertRaises(lib_exc.NotFound, self.client.add_subports,
uuidutils.generate_uuid(),
[{'port_id': parent_port['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}])
@test.attr(type='negative')
@test.idempotent_id('7e0f99ab-fe37-408b-a889-9e44ef300084')
def test_create_subport_missing_segmentation_id(self):
trunk = self._create_trunk_with_network_and_parent([])
subport_network = self.create_network()
parent_port = self.create_port(subport_network)
self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
trunk['trunk']['id'],
[{'port_id': parent_port['id'],
'segmentation_type': 'vlan'}])
@test.attr(type='negative')
@test.idempotent_id('a315d78b-2f43-4efa-89ae-166044c568aa')
def test_create_trunk_with_subport_missing_segmentation_id(self):
subport_network = self.create_network()
parent_port = self.create_port(subport_network)
self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
parent_port['id'],
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'vlan'}])
@test.attr(type='negative')
@test.idempotent_id('33498618-f75a-4796-8ae6-93d4fd203fa4')
def test_create_trunk_with_subport_missing_segmentation_type(self):
subport_network = self.create_network()
parent_port = self.create_port(subport_network)
self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
parent_port['id'],
[{'port_id': uuidutils.generate_uuid(),
'segmentation_id': 3}])
@test.attr(type='negative')
@test.idempotent_id('a717691c-4e07-4d81-a98d-6f1c18c5d183')
def test_create_trunk_with_subport_missing_port_id(self):
subport_network = self.create_network()
parent_port = self.create_port(subport_network)
self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
parent_port['id'],
[{'segmentation_type': 'vlan',
'segmentation_id': 3}])
@test.attr(type='negative')
@test.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57')
def test_create_trunk_duplicate_subport_segmentation_ids(self):
trunk = self._create_trunk_with_network_and_parent([])
subport_network1 = self.create_network()
subport_network2 = self.create_network()
parent_port1 = self.create_port(subport_network1)
parent_port2 = self.create_port(subport_network2)
self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
trunk['trunk']['id'],
[{'port_id': parent_port1['id'],
'segmentation_id': 2,
'segmentation_type': 'vlan'},
{'port_id': parent_port2['id'],
'segmentation_id': 2,
'segmentation_type': 'vlan'}])
@test.attr(type='negative')
@test.idempotent_id('6f132ccc-1380-42d8-9c44-50411612bd01')
def test_add_subport_port_id_uses_trunk_port_id(self):
trunk = self._create_trunk_with_network_and_parent(None)
self.assertRaises(lib_exc.Conflict, self.client.add_subports,
trunk['trunk']['id'],
[{'port_id': trunk['trunk']['port_id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}])
@test.attr(type='negative')
@test.idempotent_id('00cb40bb-1593-44c8-808c-72b47e64252f')
def test_add_subport_duplicate_segmentation_details(self):
trunk = self._create_trunk_with_network_and_parent(None)
network = self.create_network()
parent_port1 = self.create_port(network)
parent_port2 = self.create_port(network)
self.client.add_subports(trunk['trunk']['id'],
[{'port_id': parent_port1['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}])
self.assertRaises(lib_exc.Conflict, self.client.add_subports,
trunk['trunk']['id'],
[{'port_id': parent_port2['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}])
@test.attr(type='negative')
@test.idempotent_id('4eac8c25-83ee-4051-9620-34774f565730')
def test_add_subport_passing_dict(self):
trunk = self._create_trunk_with_network_and_parent(None)
self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
trunk['trunk']['id'],
{'port_id': trunk['trunk']['port_id'],
'segmentation_type': 'vlan',
'segmentation_id': 2})
@test.attr(type='negative')
@test.idempotent_id('17ca7dd7-96a8-445a-941e-53c0c86c2fe2')
def test_remove_subport_passing_dict(self):
network = self.create_network()
parent_port = self.create_port(network)
subport_data = {'port_id': parent_port['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}
trunk = self._create_trunk_with_network_and_parent([subport_data])
self.assertRaises(lib_exc.BadRequest, self.client.remove_subports,
trunk['trunk']['id'], subport_data)
@test.attr(type='negative')
@test.idempotent_id('aaca7dd7-96b8-445a-931e-63f0d86d2fe2')
def test_remove_subport_not_found(self):
network = self.create_network()
parent_port = self.create_port(network)
subport_data = {'port_id': parent_port['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}
trunk = self._create_trunk_with_network_and_parent([])
self.assertRaises(lib_exc.NotFound, self.client.remove_subports,
trunk['trunk']['id'], [subport_data])

View File

@ -659,6 +659,64 @@ class NetworkClientJSON(service_client.RestClient):
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def create_trunk(self, parent_port_id, subports, tenant_id=None):
uri = '%s/trunks' % self.uri_prefix
post_data = {
'trunk': {
'port_id': parent_port_id,
}
}
if subports is not None:
post_data['trunk']['sub_ports'] = subports
if tenant_id is not None:
post_data['trunk']['tenant_id'] = tenant_id
resp, body = self.post(uri, self.serialize(post_data))
body = self.deserialize_single(body)
self.expected_success(201, resp.status)
return service_client.ResponseBody(resp, body)
def show_trunk(self, trunk_id):
uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
resp, body = self.get(uri)
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def list_trunks(self, **kwargs):
uri = '%s/trunks' % self.uri_prefix
if kwargs:
uri += '?' + urlparse.urlencode(kwargs, doseq=1)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize_single(body)
return service_client.ResponseBody(resp, body)
def delete_trunk(self, trunk_id):
uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
resp, body = self.delete(uri)
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
def _subports_action(self, action, trunk_id, subports):
uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, action)
resp, body = self.put(uri, jsonutils.dumps(subports))
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def add_subports(self, trunk_id, subports):
return self._subports_action('add_subports', trunk_id, subports)
def remove_subports(self, trunk_id, subports):
return self._subports_action('remove_subports', trunk_id, subports)
def get_subports(self, trunk_id):
uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, 'get_subports')
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def get_auto_allocated_topology(self, tenant_id=None):
uri = '%s/auto-allocated-topology/%s' % (self.uri_prefix, tenant_id)
resp, body = self.get(uri)

View File

@ -13,9 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron_lib import exceptions as n_exc
from oslo_db import exception as obj_exc
from oslo_utils import uuidutils
from neutron.objects.db import api as obj_db_api
from neutron.objects import trunk as t_obj
from neutron.services.trunk import exceptions as t_exc
from neutron.tests.unit.objects import test_base
@ -26,6 +30,12 @@ class SubPortObjectTestCase(test_base.BaseObjectIfaceTestCase):
_test_class = t_obj.SubPort
def test_create_duplicates(self):
with mock.patch.object(obj_db_api, 'create_object',
side_effect=obj_exc.DBDuplicateEntry):
obj = self._test_class(self.context, **self.obj_fields[0])
self.assertRaises(t_exc.DuplicateSubPort, obj.create)
class SubPortDbObjectTestCase(test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase):

View File

@ -1,50 +0,0 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company, LP
#
# 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 import context
from neutron.db import models_v2
from neutron.services.trunk import db
from neutron.services.trunk import exceptions
from neutron.tests.unit import testlib_api
class TrunkDBTestCase(testlib_api.SqlTestCase):
def setUp(self):
super(TrunkDBTestCase, self).setUp()
self.ctx = context.get_admin_context()
def _add_network(self, net_id):
with self.ctx.session.begin(subtransactions=True):
self.ctx.session.add(models_v2.Network(id=net_id))
def _add_port(self, net_id, port_id):
with self.ctx.session.begin(subtransactions=True):
port = models_v2.Port(id=port_id,
network_id=net_id,
mac_address='foo_mac_%s' % port_id,
admin_state_up=True,
status='DOWN',
device_id='',
device_owner='')
self.ctx.session.add(port)
def test_create_trunk_raise_port_in_use(self):
self._add_network('foo_net')
self._add_port('foo_net', 'foo_port')
db.create_trunk(self.ctx, 'foo_port')
self.assertRaises(exceptions.TrunkPortInUse,
db.create_trunk,
self.ctx, 'foo_port')

View File

@ -0,0 +1,40 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company, LP
#
# 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 import manager
from neutron.services.trunk import exceptions as trunk_exc
from neutron.services.trunk import plugin as trunk_plugin
from neutron.tests.unit.plugins.ml2 import test_plugin
class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
def setUp(self):
super(TrunkPluginTestCase, self).setUp()
self.trunk_plugin = trunk_plugin.TrunkPlugin()
def test_delete_trunk_raise_in_use(self):
with self.port() as port:
trunk = {'port_id': port['port']['id'],
'tenant_id': 'test_tenant',
'sub_ports': []}
response = (
self.trunk_plugin.create_trunk(self.context, {'trunk': trunk}))
core_plugin = manager.NeutronManager.get_plugin()
port['port']['binding:host_id'] = 'host'
core_plugin.update_port(self.context, port['port']['id'], port)
self.assertRaises(trunk_exc.TrunkInUse,
self.trunk_plugin.delete_trunk,
self.context, response['id'])

View File

@ -0,0 +1,150 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company, LP
#
# 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 mock
from neutron_lib import constants as n_const
from neutron_lib import exceptions as n_exc
from oslo_utils import uuidutils
from neutron import manager
from neutron.services.trunk import constants
from neutron.services.trunk import exceptions as trunk_exc
from neutron.services.trunk import plugin as trunk_plugin
from neutron.services.trunk import rules
from neutron.services.trunk.validators import vlan as vlan_driver
from neutron.tests import base
from neutron.tests.unit.plugins.ml2 import test_plugin
class SubPortsValidatorTestCase(base.BaseTestCase):
def setUp(self):
super(SubPortsValidatorTestCase, self).setUp()
self.segmentation_types = {constants.VLAN: vlan_driver.vlan_range}
self.context = mock.ANY
def test_validate_subport_subport_and_trunk_shared_port_id(self):
shared_id = uuidutils.generate_uuid()
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': shared_id,
'segmentation_type': 'vlan',
'segmentation_id': 2}],
shared_id)
self.assertRaises(trunk_exc.ParentPortInUse,
validator.validate, self.context)
def test_validate_subport_invalid_vlan_id(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'vlan',
'segmentation_id': 5000}])
self.assertRaises(n_exc.InvalidInput,
validator.validate,
self.context)
def test_validate_subport_subport_invalid_segmenation_type(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'fake',
'segmentation_id': 100}])
self.assertRaises(n_exc.InvalidInput,
validator.validate,
self.context)
def test_validate_subport_missing_segmenation_type(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': uuidutils.generate_uuid(),
'segmentation_id': 100}])
self.assertRaises(n_exc.InvalidInput,
validator.validate,
self.context)
def test_validate_subport_missing_segmenation_id(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'fake'}])
self.assertRaises(n_exc.InvalidInput,
validator.validate,
self.context)
def test_validate_subport_missing_port_id(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'segmentation_type': 'fake',
'segmentation_id': 100}])
self.assertRaises(n_exc.InvalidInput,
validator.validate,
self.context, basic_validation=True)
class TrunkPortValidatorTestCase(test_plugin.Ml2PluginV2TestCase):
def setUp(self):
super(TrunkPortValidatorTestCase, self).setUp()
self.trunk_plugin = trunk_plugin.TrunkPlugin()
self.trunk_plugin.add_segmentation_type(constants.VLAN,
vlan_driver.vlan_range)
def test_validate_port_parent_in_use_by_trunk(self):
with self.port() as trunk_parent:
trunk = {'port_id': trunk_parent['port']['id'],
'tenant_id': 'test_tenant',
'sub_ports': []}
self.trunk_plugin.create_trunk(self.context, {'trunk': trunk})
validator = rules.TrunkPortValidator(trunk_parent['port']['id'])
self.assertRaises(trunk_exc.ParentPortInUse,
validator.validate,
self.context)
def test_validate_port_id_in_use_by_unrelated_trunk(self):
with self.port() as trunk_parent,\
self.port() as subport:
trunk = {'port_id': trunk_parent['port']['id'],
'tenant_id': 'test_tenant',
'sub_ports': [{'port_id': subport['port']['id'],
'segmentation_type': 'vlan',
'segmentation_id': 2}]}
self.trunk_plugin.create_trunk(self.context, {'trunk': trunk})
validator = rules.TrunkPortValidator(subport['port']['id'])
self.assertRaises(trunk_exc.TrunkPortInUse,
validator.validate,
self.context)
def test_validate_port_has_binding_host(self):
with self.port() as port:
core_plugin = manager.NeutronManager.get_plugin()
port['port']['binding:host_id'] = 'host'
core_plugin.update_port(self.context, port['port']['id'], port)
validator = rules.TrunkPortValidator(port['port']['id'])
self.assertRaises(trunk_exc.ParentPortInUse,
validator.validate,
self.context)
def test_validate_port_has_device_owner_compute(self):
with self.port() as port:
core_plugin = manager.NeutronManager.get_plugin()
device_owner = n_const.DEVICE_OWNER_COMPUTE_PREFIX + 'test'
port['port']['device_owner'] = device_owner
core_plugin.update_port(self.context, port['port']['id'], port)
validator = rules.TrunkPortValidator(port['port']['id'])
self.assertRaises(trunk_exc.ParentPortInUse,
validator.validate,
self.context)

View File

@ -82,6 +82,7 @@ neutron.service_plugins =
segments = neutron.services.segments.plugin:Plugin
network_ip_availability = neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
timestamp_core = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
trunk = neutron.services.trunk.plugin:TrunkPlugin
neutron.qos.notification_drivers =
message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver
neutron.ml2.type_drivers =