Merge "Enable CRUD for trunk ports"

This commit is contained in:
Jenkins 2016-07-06 02:15:23 +00:00 committed by Gerrit Code Review
commit d4df22ae94
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 =