"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",
--- /dev/null
+# 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')
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'
# 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'
'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,
'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},
}
}
@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":
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
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
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()
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}}
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
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
# 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
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)
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):
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:
{'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.
: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)
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
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', {})
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
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', {})
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
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', {})
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:
"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
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',
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
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
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()
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
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}}
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()
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
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()
'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 = {
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,
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):
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):
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):
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)
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)
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: