Replace binding:capabilities with binding:vif_details

In addition to binding:vif_type, the neutron core plugin needs to
supply various information to nova's VIF driver, such as VIF security
details and PCI details when SR-IOV is being used. This information is
read-only, requires admin privileges, and is not intended for normal
users. Rather than add separate mechanisms throughout the stack for
each such requirement, the binding:capabilities port attibute, which
is a dictionary and is not currently not used by nova, is renamed to
binding:vif_details to serve as a general-purpose mechanism for
supplying binding-specific details to the VIF driver.

This patch does not remove or replace the CAP_PORT_FILTER boolean
previously used in binding:capabilities. A separate patch should
implement the specific key/value pairs carried by binding:vif_details
to implement VIF security. Another patch will implement the key/value
pairs needed for SR-IOV.

The ML2 plugin now allows the bound mechanism driver to supply the
binding:vif_details dictionary content, instead of just the
CAP_PORT_FILTER boolean previously carried by the binding:capabilities
attribute.

DocImpact: Need to update portbinding extension API, but no impact on
user or administrator documentation.

Implements: blueprint vif-details
Related-Bug: 1112912
Change-Id: I34be746fcfa73c70f72b4f9add8eff3ac88c723f
This commit is contained in:
Bob Kukura 2014-02-03 23:18:44 -05:00
parent 8dd2070fd0
commit be8a068943
28 changed files with 231 additions and 79 deletions

View File

@ -52,7 +52,7 @@
"get_port": "rule:admin_or_owner",
"get_port:queue_id": "rule:admin_only",
"get_port:binding:vif_type": "rule:admin_only",
"get_port:binding:capabilities": "rule:admin_only",
"get_port:binding:vif_details": "rule:admin_only",
"get_port:binding:host_id": "rule:admin_only",
"get_port:binding:profile": "rule:admin_only",
"get_port:binding:vnic_type": "rule:admin_or_owner",

View File

@ -0,0 +1,59 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2014 OpenStack Foundation
#
# 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.
#
"""ml2 binding:vif_details
Revision ID: 50d5ba354c23
Revises: 27cc183af192
Create Date: 2014-02-11 23:21:59.577972
"""
# revision identifiers, used by Alembic.
revision = '50d5ba354c23'
down_revision = '27cc183af192'
# Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [
'neutron.plugins.ml2.plugin.Ml2Plugin'
]
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
def upgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.add_column('ml2_port_bindings',
sa.Column('vif_details', sa.String(length=4095),
nullable=False, server_default=''))
op.drop_column('ml2_port_bindings', 'cap_port_filter')
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.add_column('ml2_port_bindings',
sa.Column('cap_port_filter', sa.Boolean(),
nullable=False, server_default=False))
op.drop_column('ml2_port_bindings', 'vif_details')

View File

@ -22,6 +22,10 @@ from neutron.api.v2 import attributes
VNIC_TYPE = 'binding:vnic_type'
# The service will return the vif type for the specific port.
VIF_TYPE = 'binding:vif_type'
# The service may return a dictionary containing additional
# information needed by the interface driver. The set of items
# returned may depend on the value of VIF_TYPE.
VIF_DETAILS = 'binding:vif_details'
# In some cases different implementations may be run on different hosts.
# The host on which the port will be allocated.
HOST_ID = 'binding:host_id'
@ -29,11 +33,16 @@ HOST_ID = 'binding:host_id'
# on the specific host to pass and receive vif port specific information to
# the plugin.
PROFILE = 'binding:profile'
# The capabilities will be a dictionary that enables pass information about
# functionalies neutron provides. The following value should be provided.
# The keys below are used in the VIF_DETAILS attribute to convey
# information to the VIF driver.
# TODO(rkukura): Replace CAP_PORT_FILTER, which nova no longer
# understands, with the new set of VIF security details to be used in
# the VIF_DETAILS attribute.
#
# - port_filter : Boolean value indicating Neutron provides port filtering
# features such as security group and anti MAC/IP spoofing
CAPABILITIES = 'binding:capabilities'
CAP_PORT_FILTER = 'port_filter'
VIF_TYPE_UNBOUND = 'unbound'
@ -63,6 +72,10 @@ EXTENDED_ATTRIBUTES_2_0 = {
'default': attributes.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True},
VIF_DETAILS: {'allow_post': False, 'allow_put': False,
'default': attributes.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True},
VNIC_TYPE: {'allow_post': True, 'allow_put': True,
'default': VNIC_NORMAL,
'is_visible': True,
@ -77,10 +90,6 @@ EXTENDED_ATTRIBUTES_2_0 = {
'enforce_policy': True,
'validate': {'type:dict_or_none': None},
'is_visible': True},
CAPABILITIES: {'allow_post': False, 'allow_put': False,
'default': attributes.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True},
}
}
@ -112,7 +121,7 @@ class Portbindings(extensions.ExtensionDescriptor):
@classmethod
def get_updated(cls):
return "2012-11-14T10:00:00-00:00"
return "2014-02-03T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":

View File

@ -300,7 +300,8 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
cfg_vif_type = override
port[portbindings.VIF_TYPE] = cfg_vif_type
port[portbindings.CAPABILITIES] = {
port[portbindings.VIF_DETAILS] = {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}
return port

View File

@ -478,7 +478,8 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
def _get_base_binding_dict(self):
binding = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
return binding

View File

@ -255,7 +255,8 @@ class LinuxBridgePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
super(LinuxBridgePluginV2, self).__init__()
self.base_binding_dict = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
self._parse_network_vlan_ranges()

View File

@ -234,7 +234,8 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self.base_binding_dict = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_MIDONET,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}

View File

@ -66,8 +66,7 @@ def ensure_port_binding(session, port_id):
record = models.PortBinding(
port_id=port_id,
host='',
vif_type=portbindings.VIF_TYPE_UNBOUND,
cap_port_filter=False)
vif_type=portbindings.VIF_TYPE_UNBOUND)
session.add(record)
return record

View File

@ -246,12 +246,12 @@ class PortContext(object):
pass
@abstractmethod
def set_binding(self, segment_id, vif_type, cap_port_filter):
def set_binding(self, segment_id, vif_type, vif_details):
"""Set the binding for the port.
:param segment_id: Network segment bound for the port.
:param vif_type: The VIF type for the bound port.
:param cap_port_filter: True if the bound port filters.
:param vif_details: Dictionary with details for VIF driver.
Called by MechanismDriver.bind_port to indicate success and
specify binding details to use for port. The segment_id must

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.openstack.common import jsonutils
from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_api as api
@ -103,12 +104,8 @@ class PortContext(MechanismDriverContext, api.PortContext):
filters={'agent_type': [agent_type],
'host': [self._binding.host]})
def set_binding(self, segment_id, vif_type, cap_port_filter):
# REVISIT(rkukura): Pass extensible list of capabilities? Move
# vif_type and capabilities to methods on the bound mechanism
# driver?
def set_binding(self, segment_id, vif_type, vif_details):
# TODO(rkukura) Verify binding allowed, segment in network
self._binding.segment = segment_id
self._binding.vif_type = vif_type
self._binding.cap_port_filter = cap_port_filter
self._binding.vif_details = jsonutils.dumps(vif_details)

View File

@ -34,21 +34,19 @@ class AgentMechanismDriverBase(api.MechanismDriver):
running on the port's host, and that agent to have connectivity to
at least one segment of the port's network.
MechanismDrivers using this base class must pass the agent type
and VIF type constants to __init__(), and must implement
MechanismDrivers using this base class must pass the agent type to
__init__(), and must implement try_to_bind_segment_for_agent() and
check_segment_for_agent().
"""
def __init__(self, agent_type, vif_type, cap_port_filter,
def __init__(self, agent_type,
supported_vnic_types=[portbindings.VNIC_NORMAL]):
"""Initialize base class for specific L2 agent type.
:param agent_type: Constant identifying agent type in agents_db
:param vif_type: Value for binding:vif_type to when bound
:param supported_vnic_types: The binding:vnic_type values we can bind
"""
self.agent_type = agent_type
self.vif_type = vif_type
self.cap_port_filter = cap_port_filter
self.supported_vnic_types = supported_vnic_types
def initialize(self):
@ -69,10 +67,8 @@ class AgentMechanismDriverBase(api.MechanismDriver):
LOG.debug(_("Checking agent: %s"), agent)
if agent['alive']:
for segment in context.network.network_segments:
if self.check_segment_for_agent(segment, agent):
context.set_binding(segment[api.ID],
self.vif_type,
self.cap_port_filter)
if self.try_to_bind_segment_for_agent(context, segment,
agent):
LOG.debug(_("Bound using segment: %s"), segment)
return
else:
@ -99,6 +95,25 @@ class AgentMechanismDriverBase(api.MechanismDriver):
{'port': context.current['id'],
'network': context.network.current['id']})
@abstractmethod
def try_to_bind_segment_for_agent(self, context, segment, agent):
"""Try to bind with segment for agent.
:param context: PortContext instance describing the port
:param segment: segment dictionary describing segment to bind
:param agent: agents_db entry describing agent to bind
:returns: True iff segment has been bound for agent
Called inside transaction during bind_port() so that derived
MechanismDrivers can use agent_db data along with built-in
knowledge of the corresponding agent's capabilities to attempt
to bind to the specified network segment for the agent.
If the segment can be bound for the agent, this function must
call context.set_binding() with appropriate values and then
return True. Otherwise, it must return False.
"""
@abstractmethod
def check_segment_for_agent(self, segment, agent):
"""Check if segment can be bound for agent.
@ -107,9 +122,49 @@ class AgentMechanismDriverBase(api.MechanismDriver):
:param agent: agents_db entry describing agent to bind
:returns: True iff segment can be bound for agent
Called inside transaction during bind_port() and
validate_port_binding() so that derived MechanismDrivers can
use agent_db data along with built-in knowledge of the
corresponding agent's capabilities to determine whether or not
the specified network segment can be bound for the agent.
Called inside transaction during validate_port_binding() so
that derived MechanismDrivers can use agent_db data along with
built-in knowledge of the corresponding agent's capabilities
to determine whether or not the specified network segment can
be bound for the agent.
"""
@six.add_metaclass(ABCMeta)
class SimpleAgentMechanismDriverBase(AgentMechanismDriverBase):
"""Base class for simple drivers using an L2 agent.
The SimpleAgentMechanismDriverBase provides common code for
mechanism drivers that integrate the ml2 plugin with L2 agents,
where the binding:vif_type and binding:vif_details values are the
same for all bindings. Port binding with this driver requires the
driver's associated agent to be running on the port's host, and
that agent to have connectivity to at least one segment of the
port's network.
MechanismDrivers using this base class must pass the agent type
and the values for binding:vif_type and binding:vif_details to
__init__(). They must implement check_segment_for_agent() as
defined in AgentMechanismDriverBase, which will be called during
both binding establishment and validation.
"""
def __init__(self, agent_type, vif_type, vif_details,
supported_vnic_types=[portbindings.VNIC_NORMAL]):
"""Initialize base class for specific L2 agent type.
:param agent_type: Constant identifying agent type in agents_db
:param vif_type: Value for binding:vif_type when bound
:param vif_details: Dictionary with details for VIF driver when bound
:param supported_vnic_types: The binding:vnic_type values we can bind
"""
super(SimpleAgentMechanismDriverBase, self).__init__(
agent_type, supported_vnic_types)
self.vif_type = vif_type
self.vif_details = vif_details
def try_to_bind_segment_for_agent(self, context, segment, agent):
if self.check_segment_for_agent(segment, agent):
context.set_binding(segment[api.ID],
self.vif_type,
self.vif_details)

View File

@ -24,7 +24,7 @@ from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class HypervMechanismDriver(mech_agent.AgentMechanismDriverBase):
class HypervMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
"""Attach to networks using hyperv L2 agent.
The HypervMechanismDriver integrates the ml2 plugin with the
@ -37,7 +37,7 @@ class HypervMechanismDriver(mech_agent.AgentMechanismDriverBase):
super(HypervMechanismDriver, self).__init__(
constants.AGENT_TYPE_HYPERV,
portbindings.VIF_TYPE_HYPERV,
False)
{portbindings.CAP_PORT_FILTER: False})
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('vswitch_mappings', {})

View File

@ -22,7 +22,7 @@ from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class LinuxbridgeMechanismDriver(mech_agent.AgentMechanismDriverBase):
class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
"""Attach to networks using linuxbridge L2 agent.
The LinuxbridgeMechanismDriver integrates the ml2 plugin with the
@ -36,7 +36,7 @@ class LinuxbridgeMechanismDriver(mech_agent.AgentMechanismDriverBase):
super(LinuxbridgeMechanismDriver, self).__init__(
constants.AGENT_TYPE_LINUXBRIDGE,
portbindings.VIF_TYPE_BRIDGE,
True)
{portbindings.CAP_PORT_FILTER: True})
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('interface_mappings', {})

View File

@ -22,7 +22,7 @@ from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class OpenvswitchMechanismDriver(mech_agent.AgentMechanismDriverBase):
class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
"""Attach to networks using openvswitch L2 agent.
The OpenvswitchMechanismDriver integrates the ml2 plugin with the
@ -36,7 +36,7 @@ class OpenvswitchMechanismDriver(mech_agent.AgentMechanismDriverBase):
super(OpenvswitchMechanismDriver, self).__init__(
constants.AGENT_TYPE_OVS,
portbindings.VIF_TYPE_OVS,
True)
{portbindings.CAP_PORT_FILTER: True})
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('bridge_mappings', {})

View File

@ -450,14 +450,14 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
LOG.debug(_("Bound port: %(port)s, host: %(host)s, "
"vnic_type: %(vnic_type)s, "
"driver: %(driver)s, vif_type: %(vif_type)s, "
"cap_port_filter: %(cap_port_filter)s, "
"vif_details: %(vif_details)s, "
"segment: %(segment)s"),
{'port': context._port['id'],
'host': binding.host,
'vnic_type': binding.vnic_type,
'driver': binding.driver,
'vif_type': binding.vif_type,
'vnic_type': binding.vnic_type,
'cap_port_filter': binding.cap_port_filter,
'vif_details': binding.vif_details,
'segment': binding.segment})
return
except Exception:
@ -509,6 +509,6 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
"unbind_port"),
driver.name)
binding.vif_type = portbindings.VIF_TYPE_UNBOUND
binding.cap_port_filter = False
binding.vif_details = ''
binding.driver = None
binding.segment = None

View File

@ -57,7 +57,7 @@ class PortBinding(model_base.BASEV2):
vnic_type = sa.Column(sa.String(64), nullable=False,
default=portbindings.VNIC_NORMAL)
vif_type = sa.Column(sa.String(64), nullable=False)
cap_port_filter = sa.Column(sa.Boolean, nullable=False)
vif_details = sa.Column(sa.String(4095), nullable=False, default='')
driver = sa.Column(sa.String(64))
segment = sa.Column(sa.String(36),
sa.ForeignKey('ml2_network_segments.id',

View File

@ -40,6 +40,7 @@ from neutron import manager
from neutron.openstack.common import db as os_db
from neutron.openstack.common import excutils
from neutron.openstack.common import importutils
from neutron.openstack.common import jsonutils
from neutron.openstack.common import log
from neutron.openstack.common import rpc as c_rpc
from neutron.plugins.common import constants as service_constants
@ -242,8 +243,18 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
port[portbindings.HOST_ID] = binding.host
port[portbindings.VNIC_TYPE] = binding.vnic_type
port[portbindings.VIF_TYPE] = binding.vif_type
port[portbindings.CAPABILITIES] = {
portbindings.CAP_PORT_FILTER: binding.cap_port_filter}
port[portbindings.VIF_DETAILS] = self._get_vif_details(binding)
def _get_vif_details(self, binding):
if binding.vif_details:
try:
return jsonutils.loads(binding.vif_details)
except Exception:
LOG.error(_("Serialized vif_details DB value '%(value)s' "
"for port %(port)s is invalid"),
{'value': binding.vif_details,
'port': binding.port_id})
return {}
def _delete_port_binding(self, mech_context):
binding = mech_context._binding

View File

@ -102,7 +102,8 @@ class MellanoxEswitchPlugin(db_base_plugin_v2.NeutronDbPluginV2,
self.vnic_type = cfg.CONF.ESWITCH.vnic_type
self.base_binding_dict = {
portbindings.VIF_TYPE: self.vnic_type,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
self._setup_rpc()

View File

@ -381,7 +381,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
def _get_base_binding_dict(self):
binding = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
return binding

View File

@ -183,7 +183,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self.base_binding_dict = {
pbin.VIF_TYPE: pbin.VIF_TYPE_OVS,
pbin.CAPABILITIES: {
pbin.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
pbin.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}

View File

@ -297,7 +297,8 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
super(OVSNeutronPluginV2, self).__init__()
self.base_binding_dict = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
self._parse_network_vlan_ranges()

View File

@ -545,7 +545,8 @@ class NeutronPluginPLUMgridV2(db_base_plugin_v2.NeutronDbPluginV2,
def _port_viftype_binding(self, context, port):
port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_IOVISOR
port[portbindings.CAPABILITIES] = {
port[portbindings.VIF_DETAILS] = {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}
return port

View File

@ -112,7 +112,8 @@ class RyuNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
super(RyuNeutronPluginV2, self).__init__()
self.base_binding_dict = {
portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
portbindings.CAPABILITIES: {
portbindings.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
portbindings_base.register_port_dict_function()

View File

@ -46,7 +46,7 @@ DEPRECATED_POLICY_MAP = {
'extension:router':
['network:router:external'],
'extension:port_binding':
['port:binding:vif_type', 'port:binding:capabilities',
['port:binding:vif_type', 'port:binding:vif_details',
'port:binding:profile', 'port:binding:host_id']
}
DEPRECATED_ACTION_MAP = {

View File

@ -39,15 +39,19 @@ class PortBindingsTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
HAS_PORT_FILTER = False
def _check_response_portbindings(self, port):
self.assertEqual(port['binding:vif_type'], self.VIF_TYPE)
port_cap = port[portbindings.CAPABILITIES]
self.assertEqual(port_cap[portbindings.CAP_PORT_FILTER],
self.HAS_PORT_FILTER)
self.assertEqual(port[portbindings.VIF_TYPE], self.VIF_TYPE)
vif_details = port[portbindings.VIF_DETAILS]
# REVISIT(rkukura): Consider reworking tests to enable ML2 to bind
if self.VIF_TYPE not in [portbindings.VIF_TYPE_UNBOUND,
portbindings.VIF_TYPE_BINDING_FAILED]:
# TODO(rkukura): Replace with new VIF security details
self.assertEqual(vif_details[portbindings.CAP_PORT_FILTER],
self.HAS_PORT_FILTER)
def _check_response_no_portbindings(self, port):
self.assertIn('status', port)
self.assertNotIn(portbindings.VIF_TYPE, port)
self.assertNotIn(portbindings.CAPABILITIES, port)
self.assertNotIn(portbindings.VIF_DETAILS, port)
def _get_non_admin_context(self):
return context.Context(user_id=None,

View File

@ -46,8 +46,7 @@ class FakePortContext(api.PortContext):
self._network_context = FakeNetworkContext(segments)
self._bound_segment_id = None
self._bound_vif_type = None
self._bound_vnic_type = portbindings.VNIC_NORMAL
self._bound_cap_port_filter = None
self._bound_vif_details = None
@property
def current(self):
@ -74,10 +73,10 @@ class FakePortContext(api.PortContext):
else:
return []
def set_binding(self, segment_id, vif_type, cap_port_filter):
def set_binding(self, segment_id, vif_type, vif_details):
self._bound_segment_id = segment_id
self._bound_vif_type = vif_type
self._bound_cap_port_filter = cap_port_filter
self._bound_vif_details = vif_details
class AgentMechanismBaseTestCase(base.BaseTestCase):
@ -93,12 +92,15 @@ class AgentMechanismBaseTestCase(base.BaseTestCase):
def _check_unbound(self, context):
self.assertIsNone(context._bound_segment_id)
self.assertIsNone(context._bound_vif_type)
self.assertIsNone(context._bound_cap_port_filter)
self.assertIsNone(context._bound_vif_details)
def _check_bound(self, context, segment):
self.assertEqual(context._bound_segment_id, segment[api.ID])
self.assertEqual(context._bound_vif_type, self.VIF_TYPE)
self.assertEqual(context._bound_cap_port_filter, self.CAP_PORT_FILTER)
vif_details = context._bound_vif_details
self.assertIsNotNone(vif_details)
self.assertEqual(vif_details[portbindings.CAP_PORT_FILTER],
self.CAP_PORT_FILTER)
class AgentMechanismGenericTestCase(AgentMechanismBaseTestCase):

View File

@ -116,9 +116,11 @@ class TestMechanismDriver(api.MechanismDriver):
host = context.current.get(portbindings.HOST_ID, None)
segment = context.network.network_segments[0][api.ID]
if host == "host-ovs-no_filter":
context.set_binding(segment, portbindings.VIF_TYPE_OVS, False)
context.set_binding(segment, portbindings.VIF_TYPE_OVS,
{portbindings.CAP_PORT_FILTER: False})
elif host == "host-bridge-filter":
context.set_binding(segment, portbindings.VIF_TYPE_BRIDGE, True)
context.set_binding(segment, portbindings.VIF_TYPE_BRIDGE,
{portbindings.CAP_PORT_FILTER: True})
def validate_port_binding(self, context):
self._check_port_context(context, False)

View File

@ -41,17 +41,20 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
self.port_create_status = 'DOWN'
self.plugin = manager.NeutronManager.get_plugin()
def _check_response(self, port, vif_type, has_port_filter):
self.assertEqual(port['binding:vif_type'], vif_type)
port_cap = port[portbindings.CAPABILITIES]
self.assertEqual(port_cap[portbindings.CAP_PORT_FILTER],
has_port_filter)
def _check_response(self, port, vif_type, has_port_filter, bound):
self.assertEqual(port[portbindings.VIF_TYPE], vif_type)
vif_details = port[portbindings.VIF_DETAILS]
if bound:
# TODO(rkukura): Replace with new VIF security details
self.assertEqual(vif_details[portbindings.CAP_PORT_FILTER],
has_port_filter)
def _test_port_binding(self, host, vif_type, has_port_filter, bound):
host_arg = {portbindings.HOST_ID: host}
with self.port(name='name', arg_list=(portbindings.HOST_ID,),
**host_arg) as port:
self._check_response(port['port'], vif_type, has_port_filter)
self._check_response(port['port'], vif_type, has_port_filter,
bound)
port_id = port['port']['id']
details = self.plugin.callbacks.get_device_details(
None, agent_id="theAgentId", device=port_id)
@ -95,9 +98,10 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
neutron_context=neutron_context)
port_data = updated_port['port']
if new_host is not None:
self.assertEqual(port_data['binding:host_id'], new_host)
self.assertEqual(port_data[portbindings.HOST_ID],
new_host)
else:
self.assertEqual(port_data['binding:host_id'], host)
self.assertEqual(port_data[portbindings.HOST_ID], host)
if new_host is not None and new_host != host:
notify_mock.assert_called_once_with(mock.ANY)
else: