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'
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'
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
'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()
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
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.
--- /dev/null
+# 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')
'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}
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
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."""
# 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'
"""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.
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
# 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
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):
@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
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):
@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
--- /dev/null
+# 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.
+ """
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
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
"""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',
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
"""
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
# under the License.
import sqlalchemy as sa
+from sqlalchemy import orm
from neutron.db import model_base
from neutron.db import models_v2
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'))
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
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__)
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
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
# 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 _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,
+ def _process_port_binding(self, mech_context, attrs):
+ binding = mech_context._binding
+ port = mech_context.current
+ self._update_port_dict_binding(port, binding)
+
+ 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])
# 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:
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.
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:
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
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)
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)
"%(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 "
'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:
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
'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)
--- /dev/null
+# 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)
{'method': method_name,
'current': context.current,
'original': context.original,
+ 'segment': context.bound_segment,
'network': network_context.current})
def create_port_precommit(self, context):
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)
# 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):
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:
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:
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:
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):
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)
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
# 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'
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,
--- /dev/null
+# 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)
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