Implement ML2 port binding

The ml2 plugin uses mechanism drivers to determine which network
segment and what VIF driver to use for a port. Mechanism drivers
supporting the openvswitch, linuxbridge, and hyperv agents are
added. The binding:host attribute is set on ports belonging to the
dhcp and l3 agents so that they can be bound.

To use with devstack until it is updated, set
"Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,linuxbridge" in localrc.

The hyperv L2 agent does not currently implement the agents_db RPC,
and will therefore not work with its ml2 mechanism driver. This issue
will be tracked as a bug to be fixed in a separate merge.

implements blueprint: ml2-portbinding

Change-Id: Icb9c70d8b0d7fcb34b57adc760bb713b740e5dad
This commit is contained in:
Bob Kukura 2013-08-12 21:58:36 -04:00
parent 0160ee2c5c
commit 8bc02a7fbe
26 changed files with 1268 additions and 115 deletions

View File

@ -67,6 +67,7 @@ TYPE_DICT = "dict"
AGENT_TYPE_DHCP = 'DHCP agent'
AGENT_TYPE_OVS = 'Open vSwitch agent'
AGENT_TYPE_LINUXBRIDGE = 'Linux bridge agent'
AGENT_TYPE_HYPERV = 'HyperV agent'
AGENT_TYPE_NEC = 'NEC plugin agent'
AGENT_TYPE_L3 = 'L3 agent'
AGENT_TYPE_LOADBALANCER = 'Loadbalancer agent'
@ -78,6 +79,7 @@ PAGINATION_INFINITE = 'infinite'
SORT_DIRECTION_ASC = 'asc'
SORT_DIRECTION_DESC = 'desc'
PORT_BINDING_EXT_ALIAS = 'binding'
L3_AGENT_SCHEDULER_EXT_ALIAS = 'l3_agent_scheduler'
DHCP_AGENT_SCHEDULER_EXT_ALIAS = 'dhcp_agent_scheduler'
LBAAS_AGENT_SCHEDULER_EXT_ALIAS = 'lbaas_agent_scheduler'

View File

@ -19,6 +19,7 @@ from sqlalchemy.orm import exc
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import utils
from neutron.extensions import portbindings
from neutron import manager
from neutron.openstack.common import log as logging
@ -227,6 +228,7 @@ class DhcpRpcCallbackMixin(object):
'host': host})
port['port']['device_owner'] = constants.DEVICE_OWNER_DHCP
port['port'][portbindings.HOST_ID] = host
if 'mac_address' not in port['port']:
port['port']['mac_address'] = attributes.ATTR_NOT_SPECIFIED
plugin = manager.NeutronManager.get_plugin()

View File

@ -18,6 +18,7 @@ from oslo.config import cfg
from neutron.common import constants
from neutron.common import utils
from neutron import context as neutron_context
from neutron.extensions import portbindings
from neutron import manager
from neutron.openstack.common import jsonutils
from neutron.openstack.common import log as logging
@ -49,10 +50,31 @@ class L3RpcCallbackMixin(object):
context, host, router_ids)
else:
routers = plugin.get_sync_data(context, router_ids)
if utils.is_extension_supported(
plugin, constants.PORT_BINDING_EXT_ALIAS):
self._ensure_host_set_on_ports(context, plugin, host, routers)
LOG.debug(_("Routers returned to l3 agent:\n %s"),
jsonutils.dumps(routers, indent=5))
return routers
def _ensure_host_set_on_ports(self, context, plugin, host, routers):
for router in routers:
LOG.debug("checking router: %s for host: %s" %
(router['id'], host))
self._ensure_host_set_on_port(context, plugin, host,
router.get('gw_port'))
for interface in router.get(constants.INTERFACE_KEY, []):
self._ensure_host_set_on_port(context, plugin, host,
interface)
def _ensure_host_set_on_port(self, context, plugin, host, port):
if (port and
(port.get(portbindings.HOST_ID) != host or
port.get(portbindings.VIF_TYPE) ==
portbindings.VIF_TYPE_BINDING_FAILED)):
plugin.update_port(context, port['id'],
{'port': {portbindings.HOST_ID: host}})
def get_external_network_id(self, context, **kwargs):
"""Get one external network id for l3 agent.

View File

@ -0,0 +1,70 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 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 portbinding
Revision ID: 32a65f71af51
Revises: 14f24494ca31
Create Date: 2013-09-03 08:40:22.706651
"""
# revision identifiers, used by Alembic.
revision = '32a65f71af51'
down_revision = '14f24494ca31'
# 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.create_table(
'ml2_port_bindings',
sa.Column('port_id', sa.String(length=36), nullable=False),
sa.Column('host', sa.String(length=255), nullable=False),
sa.Column('vif_type', sa.String(length=64), nullable=False),
sa.Column('cap_port_filter', sa.Boolean(), nullable=False),
sa.Column('driver', sa.String(length=64), nullable=True),
sa.Column('segment', sa.String(length=36), nullable=True),
sa.ForeignKeyConstraint(['port_id'], ['ports.id'],
ondelete='CASCADE'),
sa.ForeignKeyConstraint(['segment'], ['ml2_network_segments.id'],
ondelete='SET NULL'),
sa.PrimaryKeyConstraint('port_id')
)
# Note that 176a85fc7d79_add_portbindings_db.py was never enabled
# for ml2, so there is no need to drop the portbindingports table
# that is no longer used.
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.drop_table('ml2_port_bindings')

View File

@ -497,7 +497,7 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
'binary': 'neutron-linuxbridge-agent',
'host': cfg.CONF.host,
'topic': constants.L2_AGENT_TOPIC,
'configurations': interface_mappings,
'configurations': {'interface_mappings': interface_mappings},
'agent_type': constants.AGENT_TYPE_LINUXBRIDGE,
'start_flag': True}

View File

@ -18,6 +18,7 @@ from sqlalchemy.orm import exc
from neutron.db import api as db_api
from neutron.db import models_v2
from neutron.db import securitygroups_db as sg_db
from neutron.extensions import portbindings
from neutron import manager
from neutron.openstack.common import log
from neutron.openstack.common import uuidutils
@ -52,12 +53,29 @@ def get_network_segments(session, network_id):
with session.begin(subtransactions=True):
records = (session.query(models.NetworkSegment).
filter_by(network_id=network_id))
return [{api.NETWORK_TYPE: record.network_type,
return [{api.ID: record.id,
api.NETWORK_TYPE: record.network_type,
api.PHYSICAL_NETWORK: record.physical_network,
api.SEGMENTATION_ID: record.segmentation_id}
for record in records]
def ensure_port_binding(session, port_id):
with session.begin(subtransactions=True):
try:
record = (session.query(models.PortBinding).
filter_by(port_id=port_id).
one())
except exc.NoResultFound:
record = models.PortBinding(
port_id=port_id,
host='',
vif_type=portbindings.VIF_TYPE_UNBOUND,
cap_port_filter=False)
session.add(record)
return record
def get_port(session, port_id):
"""Get port record for update within transcation."""

View File

@ -20,6 +20,7 @@ from abc import ABCMeta, abstractmethod, abstractproperty
# neutron.extensions.providernet so that drivers don't need to change
# if/when providernet moves to the core API.
#
ID = 'id'
NETWORK_TYPE = 'network_type'
PHYSICAL_NETWORK = 'physical_network'
SEGMENTATION_ID = 'segmentation_id'
@ -232,6 +233,34 @@ class PortContext(object):
"""Return the NetworkContext associated with this port."""
pass
@abstractproperty
def bound_segment(self):
"""Return the currently bound segment dictionary."""
pass
@abstractmethod
def host_agents(self, agent_type):
"""Get agents of the specified type on port's host.
:param agent_type: Agent type identifier
:returns: List of agents_db.Agent records
"""
pass
@abstractmethod
def set_binding(self, segment_id, vif_type, cap_port_filter):
"""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.
Called by MechanismDriver.bind_port to indicate success and
specify binding details to use for port. The segment_id must
identify an item in network.network_segments.
"""
pass
class MechanismDriver(object):
"""Define stable abstract interface for ML2 mechanism drivers.
@ -530,3 +559,39 @@ class MechanismDriver(object):
deleted.
"""
pass
def bind_port(self, context):
"""Attempt to bind a port.
:param context: PortContext instance describing the port
Called inside transaction context on session, prior to
create_network_precommit or update_network_precommit, to
attempt to establish a port binding. If the driver is able to
bind the port, it calls context.set_binding with the binding
details.
"""
pass
def validate_port_binding(self, context):
"""Check whether existing port binding is still valid.
:param context: PortContext instance describing the port
:returns: True if binding is valid, otherwise False
Called inside transaction context on session to validate that
the MechanismDriver's existing binding for the port is still
valid.
"""
return False
def unbind_port(self, context):
"""Undo existing port binding.
:param context: PortContext instance describing the port
Called inside transaction context on session to notify the
MechanismDriver that its existing binding for the port is no
longer valid.
"""
pass

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_api as api
@ -29,11 +30,12 @@ class MechanismDriverContext(object):
class NetworkContext(MechanismDriverContext, api.NetworkContext):
def __init__(self, plugin, plugin_context, network,
segments=None, original_network=None):
original_network=None):
super(NetworkContext, self).__init__(plugin, plugin_context)
self._network = network
self._original_network = original_network
self._segments = segments
self._segments = db.get_network_segments(plugin_context.session,
network['id'])
@property
def current(self):
@ -45,9 +47,6 @@ class NetworkContext(MechanismDriverContext, api.NetworkContext):
@property
def network_segments(self):
if not self._segments:
self._segments = self._plugin.get_network_segments(
self._plugin_context, self._network['id'])
return self._segments
@ -69,12 +68,15 @@ class SubnetContext(MechanismDriverContext, api.SubnetContext):
class PortContext(MechanismDriverContext, api.PortContext):
def __init__(self, plugin, plugin_context, port,
def __init__(self, plugin, plugin_context, port, network,
original_port=None):
super(PortContext, self).__init__(plugin, plugin_context)
self._port = port
self._original_port = original_port
self._network_context = None
self._network_context = NetworkContext(plugin, plugin_context,
network)
self._binding = db.ensure_port_binding(plugin_context.session,
port['id'])
@property
def current(self):
@ -86,11 +88,27 @@ class PortContext(MechanismDriverContext, api.PortContext):
@property
def network(self):
"""Return the NetworkContext associated with this port."""
if not self._network_context:
network = self._plugin.get_network(self._plugin_context,
self._port["network_id"])
self._network_context = NetworkContext(self._plugin,
self._plugin_context,
network)
return self._network_context
@property
def bound_segment(self):
id = self._binding.segment
if id:
for segment in self._network_context.network_segments:
if segment[api.ID] == id:
return segment
def host_agents(self, agent_type):
return self._plugin.get_agents(self._plugin_context,
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?
# 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

View File

@ -0,0 +1,105 @@
# Copyright (c) 2013 OpenStack Foundation
# 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 abc import ABCMeta, abstractmethod
from neutron.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
LOG = log.getLogger(__name__)
class AgentMechanismDriverBase(api.MechanismDriver):
"""Base class for drivers that attach to networks using an L2 agent.
The AgentMechanismDriverBase provides common code for mechanism
drivers that integrate the ml2 plugin with L2 agents. 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 VIF type constants to __init__(), and must implement
check_segment_for_agent().
"""
__metaclass__ = ABCMeta
def __init__(self, agent_type, vif_type, cap_port_filter):
"""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
"""
self.agent_type = agent_type
self.vif_type = vif_type
self.cap_port_filter = cap_port_filter
def initialize(self):
pass
def bind_port(self, context):
LOG.debug(_("Attempting to bind port %(port)s on "
"network %(network)s"),
{'port': context.current['id'],
'network': context.network.current['id']})
for agent in context.host_agents(self.agent_type):
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)
LOG.debug(_("Bound using segment: %s"), segment)
return
else:
LOG.warning(_("Attempting to bind with dead agent: %s"),
agent)
def validate_port_binding(self, context):
LOG.debug(_("Validating binding for port %(port)s on "
"network %(network)s"),
{'port': context.current['id'],
'network': context.network.current['id']})
for agent in context.host_agents(self.agent_type):
LOG.debug(_("Checking agent: %s"), agent)
if agent['alive'] and self.check_segment_for_agent(
context.bound_segment, agent):
LOG.debug(_("Binding valid"))
return True
LOG.warning(_("Binding invalid for port: %s"), context.current)
return False
def unbind_port(self, context):
LOG.debug(_("Unbinding port %(port)s on "
"network %(network)s"),
{'port': context.current['id'],
'network': context.network.current['id']})
@abstractmethod
def check_segment_for_agent(self, segment, agent):
"""Check if segment can be bound for agent.
:param segment: segment dictionary describing segment to bind
: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.
"""

View File

@ -0,0 +1,51 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class HypervMechanismDriver(mech_agent.AgentMechanismDriverBase):
"""Attach to networks using hyperv L2 agent.
The HypervMechanismDriver integrates the ml2 plugin with the
hyperv L2 agent. Port binding with this driver requires the hyperv
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.
"""
def __init__(self):
super(HypervMechanismDriver, self).__init__(
constants.AGENT_TYPE_HYPERV,
portbindings.VIF_TYPE_HYPERV,
False)
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('vswitch_mappings', {})
LOG.debug(_("Checking segment: %(segment)s "
"for mappings: %(mappings)s"),
{'segment': segment, 'mappings': mappings})
network_type = segment[api.NETWORK_TYPE]
if network_type == 'local':
return True
elif network_type in ['flat', 'vlan']:
return segment[api.PHYSICAL_NETWORK] in mappings
else:
return False

View File

@ -0,0 +1,52 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class LinuxbridgeMechanismDriver(mech_agent.AgentMechanismDriverBase):
"""Attach to networks using linuxbridge L2 agent.
The LinuxbridgeMechanismDriver integrates the ml2 plugin with the
linuxbridge L2 agent. Port binding with this driver requires the
linuxbridge 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.
"""
def __init__(self):
super(LinuxbridgeMechanismDriver, self).__init__(
constants.AGENT_TYPE_LINUXBRIDGE,
portbindings.VIF_TYPE_BRIDGE,
True)
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('interface_mappings', {})
LOG.debug(_("Checking segment: %(segment)s "
"for mappings: %(mappings)s"),
{'segment': segment, 'mappings': mappings})
network_type = segment[api.NETWORK_TYPE]
if network_type == 'local':
return True
elif network_type in ['flat', 'vlan']:
return segment[api.PHYSICAL_NETWORK] in mappings
else:
return False

View File

@ -0,0 +1,57 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers import mech_agent
LOG = log.getLogger(__name__)
class OpenvswitchMechanismDriver(mech_agent.AgentMechanismDriverBase):
"""Attach to networks using openvswitch L2 agent.
The OpenvswitchMechanismDriver integrates the ml2 plugin with the
openvswitch L2 agent. Port binding with this driver requires the
openvswitch 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.
"""
def __init__(self):
super(OpenvswitchMechanismDriver, self).__init__(
constants.AGENT_TYPE_OVS,
portbindings.VIF_TYPE_OVS,
True)
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('bridge_mappings', {})
tunnel_types = agent['configurations'].get('tunnel_types', [])
LOG.debug(_("Checking segment: %(segment)s "
"for mappings: %(mappings)s "
"with tunnel_types: %(tunnel_types)s"),
{'segment': segment, 'mappings': mappings,
'tunnel_types': tunnel_types})
network_type = segment[api.NETWORK_TYPE]
if network_type == 'local':
return True
elif network_type in tunnel_types:
return True
elif network_type in ['flat', 'vlan']:
return segment[api.PHYSICAL_NETWORK] in mappings
else:
return False

View File

@ -19,6 +19,7 @@ from oslo.config import cfg
import stevedore
from neutron.common import exceptions as exc
from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import driver_api as api
@ -31,12 +32,9 @@ class TypeManager(stevedore.named.NamedExtensionManager):
"""Manage network segment types using drivers."""
def __init__(self):
# REVISIT(rkukura): Need way to make stevedore use our logging
# configuration. Currently, nothing is logged if loading a
# driver fails.
# Mapping from type name to DriverManager
self.drivers = {}
LOG.info(_("Configured type driver names: %s"),
cfg.CONF.ml2.type_drivers)
super(TypeManager, self).__init__('neutron.ml2.type_drivers',
@ -106,19 +104,9 @@ class TypeManager(stevedore.named.NamedExtensionManager):
class MechanismManager(stevedore.named.NamedExtensionManager):
"""Manage networking mechanisms using drivers.
Note that this is still a work in progress, and the interface
may change before the final release of Havana.
"""
# TODO(apech): add calls for subnets
"""Manage networking mechanisms using drivers."""
def __init__(self):
# REVISIT(rkukura): Need way to make stevedore use our logging
# configuration. Currently, nothing is logged if loading a
# driver fails.
# Registered mechanism drivers, keyed by name.
self.mech_drivers = {}
# Ordered list of mechanism drivers, defining
@ -435,3 +423,85 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
"""
self._call_on_drivers("delete_port_postcommit", context,
continue_on_failure=True)
def bind_port(self, context):
"""Attempt to bind a port using registered mechanism drivers.
:param context: PortContext instance describing the port
Called inside transaction context on session, prior to
create_network_precommit or update_network_precommit, to
attempt to establish a port binding.
"""
binding = context._binding
LOG.debug(_("Attempting to bind port %(port)s on host %(host)s"),
{'port': context._port['id'],
'host': binding.host})
for driver in self.ordered_mech_drivers:
try:
driver.obj.bind_port(context)
if binding.segment:
binding.driver = driver.name
LOG.debug(_("Bound port: %(port)s, host: %(host)s, "
"driver: %(driver)s, vif_type: %(vif_type)s, "
"cap_port_filter: %(cap_port_filter)s, "
"segment: %(segment)s"),
{'port': context._port['id'],
'host': binding.host,
'driver': binding.driver,
'vif_type': binding.vif_type,
'cap_port_filter': binding.cap_port_filter,
'segment': binding.segment})
return
except Exception:
LOG.exception(_("Mechanism driver %s failed in "
"bind_port"),
driver.name)
binding.vif_type = portbindings.VIF_TYPE_BINDING_FAILED
LOG.warning(_("Failed to bind port %(port)s on host %(host)s"),
{'port': context._port['id'],
'host': binding.host})
def validate_port_binding(self, context):
"""Check whether existing port binding is still valid.
:param context: PortContext instance describing the port
:returns: True if binding is valid, otherwise False
Called inside transaction context on session to validate that
the bound MechanismDriver's existing binding for the port is
still valid.
"""
binding = context._binding
driver = self.mech_drivers.get(binding.driver, None)
if driver:
try:
return driver.obj.validate_port_binding(context)
except Exception:
LOG.exception(_("Mechanism driver %s failed in "
"validate_port_binding"),
driver.name)
return False
def unbind_port(self, context):
"""Undo existing port binding.
:param context: PortContext instance describing the port
Called inside transaction context on session to notify the
bound MechanismDriver that its existing binding for the port
is no longer valid.
"""
binding = context._binding
driver = self.mech_drivers.get(binding.driver, None)
if driver:
try:
driver.obj.unbind_port(context)
except Exception:
LOG.exception(_("Mechanism driver %s failed in "
"unbind_port"),
driver.name)
binding.vif_type = portbindings.VIF_TYPE_UNBOUND
binding.cap_port_filter = False
binding.driver = None
binding.segment = None

View File

@ -14,6 +14,7 @@
# under the License.
import sqlalchemy as sa
from sqlalchemy import orm
from neutron.db import model_base
from neutron.db import models_v2
@ -35,3 +36,34 @@ class NetworkSegment(model_base.BASEV2, models_v2.HasId):
network_type = sa.Column(sa.String(32), nullable=False)
physical_network = sa.Column(sa.String(64))
segmentation_id = sa.Column(sa.Integer)
class PortBinding(model_base.BASEV2):
"""Represent binding-related state of a port.
A port binding stores the port attributes required for the
portbindings extension, as well as internal ml2 state such as
which MechanismDriver and which segment are used by the port
binding.
"""
__tablename__ = 'ml2_port_bindings'
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
host = sa.Column(sa.String(255), nullable=False)
vif_type = sa.Column(sa.String(64), nullable=False)
cap_port_filter = sa.Column(sa.Boolean, nullable=False)
driver = sa.Column(sa.String(64))
segment = sa.Column(sa.String(36),
sa.ForeignKey('ml2_network_segments.id',
ondelete="SET NULL"))
# Add a relationship to the Port model in order to instruct SQLAlchemy to
# eagerly load port bindings
port = orm.relationship(
models_v2.Port,
backref=orm.backref("port_binding",
lazy='joined', uselist=False,
cascade='delete'))

View File

@ -26,7 +26,7 @@ from neutron.db import agentschedulers_db
from neutron.db import db_base_plugin_v2
from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.db import portbindings_db
from neutron.db import models_v2
from neutron.db import quota_db # noqa
from neutron.db import securitygroups_rpc_base as sg_db_rpc
from neutron.extensions import portbindings
@ -41,6 +41,7 @@ from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import driver_context
from neutron.plugins.ml2 import managers
from neutron.plugins.ml2 import models
from neutron.plugins.ml2 import rpc
LOG = log.getLogger(__name__)
@ -55,8 +56,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
l3_gwmode_db.L3_NAT_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.L3AgentSchedulerDbMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
portbindings_db.PortBindingMixin):
agentschedulers_db.DhcpAgentSchedulerDbMixin):
"""Implement the Neutron L2 abstractions using modules.
Ml2Plugin is a Neutron plugin based on separately extensible sets
@ -151,7 +151,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def _extend_network_dict_provider(self, context, network):
id = network['id']
segments = self.get_network_segments(context, id)
segments = db.get_network_segments(context.session, id)
if not segments:
LOG.error(_("Network %s has no segments"), id)
network[provider.NETWORK_TYPE] = None
@ -171,28 +171,88 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# TODO(rkukura): Implement filtering.
return nets
def _extend_port_dict_binding(self, context, port):
# TODO(rkukura): Implement based on host_id, agents, and
# MechanismDrivers. Also set CAPABILITIES. Use
# base_binding_dict if applicable, or maybe a new hook so
# base handles field processing and get_port and get_ports
# don't need to be overridden.
port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_UNBOUND
def _process_port_binding(self, mech_context, attrs):
binding = mech_context._binding
port = mech_context.current
self._update_port_dict_binding(port, binding)
def _notify_port_updated(self, context, port):
session = context.session
with session.begin(subtransactions=True):
network_id = port['network_id']
segments = self.get_network_segments(context, network_id)
if not segments:
LOG.warning(_("In _notify_port_updated() for port %(port_id), "
"network %(network_id) has no segments"),
{'port_id': port['id'],
'network_id': network_id})
return
# TODO(rkukura): Use port binding to select segment.
segment = segments[0]
self.notifier.port_update(context, port,
host = attrs and attrs.get(portbindings.HOST_ID)
host_set = attributes.is_attr_set(host)
if binding.vif_type != portbindings.VIF_TYPE_UNBOUND:
if (not host_set and binding.segment and
self.mechanism_manager.validate_port_binding(mech_context)):
return False
self.mechanism_manager.unbind_port(mech_context)
self._update_port_dict_binding(port, binding)
if host_set:
binding.host = host
port[portbindings.HOST_ID] = host
if binding.host:
self.mechanism_manager.bind_port(mech_context)
self._update_port_dict_binding(port, binding)
return True
def _update_port_dict_binding(self, port, binding):
port[portbindings.HOST_ID] = binding.host
port[portbindings.VIF_TYPE] = binding.vif_type
port[portbindings.CAPABILITIES] = {
portbindings.CAP_PORT_FILTER: binding.cap_port_filter}
def _delete_port_binding(self, mech_context):
binding = mech_context._binding
port = mech_context.current
self._update_port_dict_binding(port, binding)
self.mechanism_manager.unbind_port(mech_context)
self._update_port_dict_binding(port, binding)
def _extend_port_dict_binding(self, port_res, port_db):
# None when called during unit tests for other plugins.
if port_db.port_binding:
self._update_port_dict_binding(port_res, port_db.port_binding)
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.PORTS, [_extend_port_dict_binding])
# Note - The following hook methods have "ml2" in their names so
# that they are not called twice during unit tests due to global
# registration of hooks in portbindings_db.py used by other
# plugins.
def _ml2_port_model_hook(self, context, original_model, query):
query = query.outerjoin(models.PortBinding,
(original_model.id ==
models.PortBinding.port_id))
return query
def _ml2_port_result_filter_hook(self, query, filters):
values = filters and filters.get(portbindings.HOST_ID, [])
if not values:
return query
return query.filter(models.PortBinding.host.in_(values))
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
models_v2.Port,
"ml2_port_bindings",
'_ml2_port_model_hook',
None,
'_ml2_port_result_filter_hook')
def _notify_port_updated(self, mech_context):
port = mech_context._port
segment = mech_context.bound_segment
if not segment:
# REVISIT(rkukura): This should notify agent to unplug port
network = mech_context.network.current
LOG.warning(_("In _notify_port_updated(), no bound segment for "
"port %(port_id)s on network %(network_id)s"),
{'port_id': port['id'],
'network_id': network['id']})
return
self.notifier.port_update(mech_context._plugin_context, port,
segment[api.NETWORK_TYPE],
segment[api.SEGMENTATION_ID],
segment[api.PHYSICAL_NETWORK])
@ -218,10 +278,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# to TypeManager.
db.add_network_segment(session, id, segment)
self._extend_network_dict_provider(context, result)
mech_context = driver_context.NetworkContext(self,
context,
result,
segments=[segment])
mech_context = driver_context.NetworkContext(self, context,
result)
self.mechanism_manager.create_network_precommit(mech_context)
try:
@ -280,24 +338,15 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return [self._fields(net, fields) for net in nets]
def get_network_segments(self, context, id):
session = context.session
with session.begin(subtransactions=True):
segments = db.get_network_segments(session, id)
return segments
def delete_network(self, context, id):
session = context.session
with session.begin(subtransactions=True):
network = self.get_network(context, id)
segments = self.get_network_segments(context, id)
mech_context = driver_context.NetworkContext(self,
context,
network,
segments=segments)
mech_context = driver_context.NetworkContext(self, context,
network)
self.mechanism_manager.delete_network_precommit(mech_context)
super(Ml2Plugin, self).delete_network(context, id)
for segment in segments:
for segment in mech_context.network_segments:
self.type_manager.release_segment(session, segment)
# The segment records are deleted via cascade from the
# network record, so explicit removal is not necessary.
@ -368,11 +417,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port)
result = super(Ml2Plugin, self).create_port(context, port)
self._process_portbindings_create_and_update(context, attrs,
result)
self._process_port_create_security_group(context, result, sgids)
self._extend_port_dict_binding(context, result)
mech_context = driver_context.PortContext(self, context, result)
network = self.get_network(context, result['network_id'])
mech_context = driver_context.PortContext(self, context, result,
network)
self._process_port_binding(mech_context, attrs)
self.mechanism_manager.create_port_precommit(mech_context)
try:
@ -396,13 +445,12 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
port)
need_port_update_notify = self.update_security_group_on_port(
context, id, port, original_port, updated_port)
self._process_portbindings_create_and_update(context,
attrs,
updated_port)
self._extend_port_dict_binding(context, updated_port)
network = self.get_network(context, original_port['network_id'])
mech_context = driver_context.PortContext(
self, context, updated_port,
self, context, updated_port, network,
original_port=original_port)
need_port_update_notify |= self._process_port_binding(
mech_context, attrs)
self.mechanism_manager.update_port_precommit(mech_context)
# TODO(apech) - handle errors raised by update_port, potentially
@ -418,31 +466,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
need_port_update_notify = True
if need_port_update_notify:
self._notify_port_updated(context, updated_port)
self._notify_port_updated(mech_context)
return updated_port
def get_port(self, context, id, fields=None):
session = context.session
with session.begin(subtransactions=True):
port = super(Ml2Plugin, self).get_port(context, id, fields)
self._extend_port_dict_binding(context, port)
return self._fields(port, fields)
def get_ports(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None, page_reverse=False):
session = context.session
with session.begin(subtransactions=True):
ports = super(Ml2Plugin,
self).get_ports(context, filters, fields, sorts,
limit, marker, page_reverse)
# TODO(nati): filter by security group
for port in ports:
self._extend_port_dict_binding(context, port)
return [self._fields(port, fields) for port in ports]
def delete_port(self, context, id, l3_port_check=True):
if l3_port_check:
self.prevent_l3_port_deletion(context, id)
@ -451,7 +478,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
with session.begin(subtransactions=True):
self.disassociate_floatingips(context, id)
port = self.get_port(context, id)
mech_context = driver_context.PortContext(self, context, port)
network = self.get_network(context, port['network_id'])
mech_context = driver_context.PortContext(self, context, port,
network)
self._delete_port_binding(mech_context)
self.mechanism_manager.delete_port_precommit(mech_context)
self._delete_port_security_group_bindings(context, id)
super(Ml2Plugin, self).delete_port(context, id)

View File

@ -97,6 +97,7 @@ class RpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
"%(agent_id)s not found in database"),
{'device': device, 'agent_id': agent_id})
return {'device': device}
segments = db.get_network_segments(session, port.network_id)
if not segments:
LOG.warning(_("Device %(device)s requested by agent "
@ -106,8 +107,29 @@ class RpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
'agent_id': agent_id,
'network_id': port.network_id})
return {'device': device}
#TODO(rkukura): Use/create port binding
segment = segments[0]
binding = db.ensure_port_binding(session, port.id)
if not binding.segment:
LOG.warning(_("Device %(device)s requested by agent "
"%(agent_id)s on network %(network_id)s not "
"bound, vif_type: %(vif_type)s"),
{'device': device,
'agent_id': agent_id,
'network_id': port.network_id,
'vif_type': binding.vif_type})
return {'device': device}
segment = self._find_segment(segments, binding.segment)
if not segment:
LOG.warning(_("Device %(device)s requested by agent "
"%(agent_id)s on network %(network_id)s "
"invalid segment, vif_type: %(vif_type)s"),
{'device': device,
'agent_id': agent_id,
'network_id': port.network_id,
'vif_type': binding.vif_type})
return {'device': device}
new_status = (q_const.PORT_STATUS_ACTIVE if port.admin_state_up
else q_const.PORT_STATUS_DOWN)
if port.status != new_status:
@ -122,6 +144,11 @@ class RpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
LOG.debug(_("Returning: %s"), entry)
return entry
def _find_segment(self, segments, segment_id):
for segment in segments:
if segment[api.ID] == segment_id:
return segment
def update_device_down(self, rpc_context, **kwargs):
"""Device no longer exists on agent."""
# TODO(garyk) - live migration and port status

View File

@ -174,9 +174,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
'binary': 'neutron-openvswitch-agent',
'host': cfg.CONF.host,
'topic': q_const.L2_AGENT_TOPIC,
'configurations': bridge_mappings,
'configurations': {'bridge_mappings': bridge_mappings,
'tunnel_types': self.tunnel_types},
'agent_type': q_const.AGENT_TYPE_OVS,
'tunnel_types': self.tunnel_types,
'start_flag': True}
self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper)

View File

@ -0,0 +1,207 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.plugins.ml2 import driver_api as api
from neutron.tests import base
NETWORK_ID = "fake_network"
PORT_ID = "fake_port"
class FakeNetworkContext(api.NetworkContext):
def __init__(self, segments):
self._network_segments = segments
@property
def current(self):
return {'id': NETWORK_ID}
@property
def original(self):
return None
@property
def network_segments(self):
return self._network_segments
class FakePortContext(api.PortContext):
def __init__(self, agent_type, agents, segments):
self._agent_type = agent_type
self._agents = agents
self._network_context = FakeNetworkContext(segments)
self._bound_segment_id = None
self._bound_vif_type = None
self._bound_cap_port_filter = None
@property
def current(self):
return {'id': PORT_ID}
@property
def original(self):
return None
@property
def network(self):
return self._network_context
@property
def bound_segment(self):
if self._bound_segment_id:
for segment in self._network_context.network_segments:
if segment[api.ID] == self._bound_segment_id:
return segment
def host_agents(self, agent_type):
if agent_type == self._agent_type:
return self._agents
else:
return []
def set_binding(self, segment_id, vif_type, cap_port_filter):
self._bound_segment_id = segment_id
self._bound_vif_type = vif_type
self._bound_cap_port_filter = cap_port_filter
class AgentMechanismBaseTestCase(base.BaseTestCase):
# These following must be overriden for the specific mechanism
# driver being tested:
VIF_TYPE = None
CAP_PORT_FILTER = None
AGENT_TYPE = None
AGENTS = None
AGENTS_DEAD = None
AGENTS_BAD = None
def _check_unbound(self, context):
self.assertIsNone(context._bound_segment_id)
self.assertIsNone(context._bound_vif_type)
self.assertIsNone(context._bound_cap_port_filter)
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)
class AgentMechanismGenericTestCase(AgentMechanismBaseTestCase):
UNKNOWN_TYPE_SEGMENTS = [{api.ID: 'unknown_segment_id',
api.NETWORK_TYPE: 'no_such_type'}]
def test_unknown_type(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS,
self.UNKNOWN_TYPE_SEGMENTS)
self.driver.bind_port(context)
self._check_unbound(context)
class AgentMechanismLocalTestCase(AgentMechanismBaseTestCase):
LOCAL_SEGMENTS = [{api.ID: 'unknown_segment_id',
api.NETWORK_TYPE: 'no_such_type'},
{api.ID: 'local_segment_id',
api.NETWORK_TYPE: 'local'}]
def test_type_local(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS,
self.LOCAL_SEGMENTS)
self.driver.bind_port(context)
self._check_bound(context, self.LOCAL_SEGMENTS[1])
self.assertTrue(self.driver.validate_port_binding(context))
self.driver.unbind_port(context)
def test_type_local_dead(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS_DEAD,
self.LOCAL_SEGMENTS)
self.driver.bind_port(context)
self._check_unbound(context)
class AgentMechanismFlatTestCase(AgentMechanismBaseTestCase):
FLAT_SEGMENTS = [{api.ID: 'unknown_segment_id',
api.NETWORK_TYPE: 'no_such_type'},
{api.ID: 'flat_segment_id',
api.NETWORK_TYPE: 'flat',
api.PHYSICAL_NETWORK: 'fake_physical_network'}]
def test_type_flat(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS,
self.FLAT_SEGMENTS)
self.driver.bind_port(context)
self._check_bound(context, self.FLAT_SEGMENTS[1])
self.assertTrue(self.driver.validate_port_binding(context))
self.driver.unbind_port(context)
def test_type_flat_bad(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS_BAD,
self.FLAT_SEGMENTS)
self.driver.bind_port(context)
self._check_unbound(context)
class AgentMechanismVlanTestCase(AgentMechanismBaseTestCase):
VLAN_SEGMENTS = [{api.ID: 'unknown_segment_id',
api.NETWORK_TYPE: 'no_such_type'},
{api.ID: 'vlan_segment_id',
api.NETWORK_TYPE: 'vlan',
api.PHYSICAL_NETWORK: 'fake_physical_network',
api.SEGMENTATION_ID: 1234}]
def test_type_vlan(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS,
self.VLAN_SEGMENTS)
self.driver.bind_port(context)
self._check_bound(context, self.VLAN_SEGMENTS[1])
self.assertTrue(self.driver.validate_port_binding(context))
self.driver.unbind_port(context)
def test_type_vlan_bad(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS_BAD,
self.VLAN_SEGMENTS)
self.driver.bind_port(context)
self._check_unbound(context)
class AgentMechanismGreTestCase(AgentMechanismBaseTestCase):
GRE_SEGMENTS = [{api.ID: 'unknown_segment_id',
api.NETWORK_TYPE: 'no_such_type'},
{api.ID: 'gre_segment_id',
api.NETWORK_TYPE: 'gre',
api.SEGMENTATION_ID: 1234}]
def test_type_gre(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS,
self.GRE_SEGMENTS)
self.driver.bind_port(context)
self._check_bound(context, self.GRE_SEGMENTS[1])
self.assertTrue(self.driver.validate_port_binding(context))
self.driver.unbind_port(context)
def test_type_gre_bad(self):
context = FakePortContext(self.AGENT_TYPE,
self.AGENTS_BAD,
self.GRE_SEGMENTS)
self.driver.bind_port(context)
self._check_unbound(context)

View File

@ -87,6 +87,7 @@ class LoggerMechanismDriver(api.MechanismDriver):
{'method': method_name,
'current': context.current,
'original': context.original,
'segment': context.bound_segment,
'network': network_context.current})
def create_port_precommit(self, context):
@ -106,3 +107,12 @@ class LoggerMechanismDriver(api.MechanismDriver):
def delete_port_postcommit(self, context):
self._log_port_call("delete_port_postcommit", context)
def bind_port(self, context):
self._log_port_call("bind_port", context)
def validate_port_binding(self, context):
self._log_port_call("validate_port_binding", context)
def unbind_port(self, context):
self._log_port_call("unbind_port", context)

View File

@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.extensions import portbindings
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import driver_context
class TestMechanismDriver(api.MechanismDriver):
@ -24,7 +24,7 @@ class TestMechanismDriver(api.MechanismDriver):
pass
def _check_network_context(self, context, original_expected):
assert(isinstance(context, driver_context.NetworkContext))
assert(isinstance(context, api.NetworkContext))
assert(isinstance(context.current, dict))
assert(context.current['id'] is not None)
if original_expected:
@ -53,7 +53,7 @@ class TestMechanismDriver(api.MechanismDriver):
self._check_network_context(context, False)
def _check_subnet_context(self, context, original_expected):
assert(isinstance(context, driver_context.SubnetContext))
assert(isinstance(context, api.SubnetContext))
assert(isinstance(context.current, dict))
assert(context.current['id'] is not None)
if original_expected:
@ -81,7 +81,7 @@ class TestMechanismDriver(api.MechanismDriver):
self._check_subnet_context(context, False)
def _check_port_context(self, context, original_expected):
assert(isinstance(context, driver_context.PortContext))
assert(isinstance(context, api.PortContext))
assert(isinstance(context.current, dict))
assert(context.current['id'] is not None)
if original_expected:
@ -90,7 +90,7 @@ class TestMechanismDriver(api.MechanismDriver):
else:
assert(not context.original)
network_context = context.network
assert(isinstance(network_context, driver_context.NetworkContext))
assert(isinstance(network_context, api.NetworkContext))
self._check_network_context(network_context, False)
def create_port_precommit(self, context):
@ -110,3 +110,19 @@ class TestMechanismDriver(api.MechanismDriver):
def delete_port_postcommit(self, context):
self._check_port_context(context, False)
def bind_port(self, context):
self._check_port_context(context, False)
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)
elif host == "host-bridge-filter":
context.set_binding(segment, portbindings.VIF_TYPE_BRIDGE, True)
def validate_port_binding(self, context):
self._check_port_context(context, False)
return True
def unbind_port(self, context):
self._check_port_context(context, False)

View File

@ -0,0 +1,65 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.plugins.ml2.drivers import mech_hyperv
from neutron.tests.unit.ml2 import _test_mech_agent as base
class HypervMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
VIF_TYPE = portbindings.VIF_TYPE_HYPERV
CAP_PORT_FILTER = False
AGENT_TYPE = constants.AGENT_TYPE_HYPERV
GOOD_MAPPINGS = {'fake_physical_network': 'fake_vswitch'}
GOOD_CONFIGS = {'vswitch_mappings': GOOD_MAPPINGS}
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_vswitch'}
BAD_CONFIGS = {'vswitch_mappings': BAD_MAPPINGS}
AGENTS = [{'alive': True,
'configurations': GOOD_CONFIGS}]
AGENTS_DEAD = [{'alive': False,
'configurations': GOOD_CONFIGS}]
AGENTS_BAD = [{'alive': False,
'configurations': GOOD_CONFIGS},
{'alive': True,
'configurations': BAD_CONFIGS}]
def setUp(self):
super(HypervMechanismBaseTestCase, self).setUp()
self.driver = mech_hyperv.HypervMechanismDriver()
self.driver.initialize()
class HypervMechanismGenericTestCase(HypervMechanismBaseTestCase,
base.AgentMechanismGenericTestCase):
pass
class HypervMechanismLocalTestCase(HypervMechanismBaseTestCase,
base.AgentMechanismLocalTestCase):
pass
class HypervMechanismFlatTestCase(HypervMechanismBaseTestCase,
base.AgentMechanismFlatTestCase):
pass
class HypervMechanismVlanTestCase(HypervMechanismBaseTestCase,
base.AgentMechanismVlanTestCase):
pass

View File

@ -0,0 +1,65 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.plugins.ml2.drivers import mech_linuxbridge
from neutron.tests.unit.ml2 import _test_mech_agent as base
class LinuxbridgeMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
VIF_TYPE = portbindings.VIF_TYPE_BRIDGE
CAP_PORT_FILTER = True
AGENT_TYPE = constants.AGENT_TYPE_LINUXBRIDGE
GOOD_MAPPINGS = {'fake_physical_network': 'fake_interface'}
GOOD_CONFIGS = {'interface_mappings': GOOD_MAPPINGS}
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_interface'}
BAD_CONFIGS = {'interface_mappings': BAD_MAPPINGS}
AGENTS = [{'alive': True,
'configurations': GOOD_CONFIGS}]
AGENTS_DEAD = [{'alive': False,
'configurations': GOOD_CONFIGS}]
AGENTS_BAD = [{'alive': False,
'configurations': GOOD_CONFIGS},
{'alive': True,
'configurations': BAD_CONFIGS}]
def setUp(self):
super(LinuxbridgeMechanismBaseTestCase, self).setUp()
self.driver = mech_linuxbridge.LinuxbridgeMechanismDriver()
self.driver.initialize()
class LinuxbridgeMechanismGenericTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismGenericTestCase):
pass
class LinuxbridgeMechanismLocalTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismLocalTestCase):
pass
class LinuxbridgeMechanismFlatTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismFlatTestCase):
pass
class LinuxbridgeMechanismVlanTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismVlanTestCase):
pass

View File

@ -0,0 +1,74 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.common import constants
from neutron.extensions import portbindings
from neutron.plugins.ml2.drivers import mech_openvswitch
from neutron.tests.unit.ml2 import _test_mech_agent as base
class OpenvswitchMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
VIF_TYPE = portbindings.VIF_TYPE_OVS
CAP_PORT_FILTER = True
AGENT_TYPE = constants.AGENT_TYPE_OVS
GOOD_MAPPINGS = {'fake_physical_network': 'fake_bridge'}
GOOD_TUNNEL_TYPES = ['gre', 'vxlan']
GOOD_CONFIGS = {'bridge_mappings': GOOD_MAPPINGS,
'tunnel_types': GOOD_TUNNEL_TYPES}
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_bridge'}
BAD_TUNNEL_TYPES = ['bad_tunnel_type']
BAD_CONFIGS = {'bridge_mappings': BAD_MAPPINGS,
'tunnel_types': BAD_TUNNEL_TYPES}
AGENTS = [{'alive': True,
'configurations': GOOD_CONFIGS}]
AGENTS_DEAD = [{'alive': False,
'configurations': GOOD_CONFIGS}]
AGENTS_BAD = [{'alive': False,
'configurations': GOOD_CONFIGS},
{'alive': True,
'configurations': BAD_CONFIGS}]
def setUp(self):
super(OpenvswitchMechanismBaseTestCase, self).setUp()
self.driver = mech_openvswitch.OpenvswitchMechanismDriver()
self.driver.initialize()
class OpenvswitchMechanismGenericTestCase(OpenvswitchMechanismBaseTestCase,
base.AgentMechanismGenericTestCase):
pass
class OpenvswitchMechanismLocalTestCase(OpenvswitchMechanismBaseTestCase,
base.AgentMechanismLocalTestCase):
pass
class OpenvswitchMechanismFlatTestCase(OpenvswitchMechanismBaseTestCase,
base.AgentMechanismFlatTestCase):
pass
class OpenvswitchMechanismVlanTestCase(OpenvswitchMechanismBaseTestCase,
base.AgentMechanismVlanTestCase):
pass
class OpenvswitchMechanismGreTestCase(OpenvswitchMechanismBaseTestCase,
base.AgentMechanismGreTestCase):
pass

View File

@ -13,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.extensions import portbindings
from neutron.plugins.ml2 import config as config
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit import test_db_plugin as test_plugin
from neutron.tests.unit import test_extension_ext_gw_mode
from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc
PLUGIN_NAME = 'neutron.plugins.ml2.plugin.Ml2Plugin'
@ -61,10 +63,22 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
self.assertEqual(self.port_create_status, 'DOWN')
# TODO(rkukura) add TestMl2PortBinding
class TestMl2PortBinding(Ml2PluginV2TestCase,
test_bindings.PortBindingsTestCase):
# Test case does not set binding:host_id, so ml2 does not attempt
# to bind port
VIF_TYPE = portbindings.VIF_TYPE_UNBOUND
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_HYBRID_DRIVER
def setUp(self, firewall_driver=None):
test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER)
super(TestMl2PortBinding, self).setUp()
# TODO(rkukura) add TestMl2PortBindingNoSG
class TestMl2PortBindingNoSG(TestMl2PortBinding):
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
class TestMl2PortBindingHost(Ml2PluginV2TestCase,

View File

@ -0,0 +1,78 @@
# Copyright (c) 2013 OpenStack Foundation
# 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.extensions import portbindings
from neutron import manager
from neutron.plugins.ml2 import config as config
from neutron.tests.unit import test_db_plugin as test_plugin
PLUGIN_NAME = 'neutron.plugins.ml2.plugin.Ml2Plugin'
class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = PLUGIN_NAME
def setUp(self):
# Enable the test mechanism driver to ensure that
# we can successfully call through to all mechanism
# driver apis.
config.cfg.CONF.set_override('mechanism_drivers',
['logger', 'test'],
'ml2')
self.addCleanup(config.cfg.CONF.reset)
super(PortBindingTestCase, self).setUp(PLUGIN_NAME)
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 _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)
port_id = port['port']['id']
details = self.plugin.callbacks.get_device_details(
None, agent_id="theAgentId", device=port_id)
if bound:
self.assertEqual(details['network_type'], 'local')
else:
self.assertNotIn('network_type', details)
def test_unbound(self):
self._test_port_binding("",
portbindings.VIF_TYPE_UNBOUND,
False, False)
def test_binding_failed(self):
self._test_port_binding("host-fail",
portbindings.VIF_TYPE_BINDING_FAILED,
False, False)
def test_binding_no_filter(self):
self._test_port_binding("host-ovs-no_filter",
portbindings.VIF_TYPE_OVS,
False, True)
def test_binding_filter(self):
self._test_port_binding("host-bridge-filter",
portbindings.VIF_TYPE_BRIDGE,
True, True)

View File

@ -117,6 +117,9 @@ neutron.ml2.type_drivers =
neutron.ml2.mechanism_drivers =
logger = neutron.tests.unit.ml2.drivers.mechanism_logger:LoggerMechanismDriver
test = neutron.tests.unit.ml2.drivers.mechanism_test:TestMechanismDriver
linuxbridge = neutron.plugins.ml2.drivers.mech_linuxbridge:LinuxbridgeMechanismDriver
openvswitch = neutron.plugins.ml2.drivers.mech_openvswitch:OpenvswitchMechanismDriver
hyperv = neutron.plugins.ml2.drivers.mech_hyperv:HypervMechanismDriver
ncs = neutron.plugins.ml2.drivers.mechanism_ncs:NCSMechanismDriver
arista = neutron.plugins.ml2.drivers.mech_arista.mechanism_arista:AristaDriver