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:
parent
5fec791ace
commit
0f0499b89e
@ -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'
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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')
|
@ -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}
|
||||
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
105
neutron/plugins/ml2/drivers/mech_agent.py
Normal file
105
neutron/plugins/ml2/drivers/mech_agent.py
Normal 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.
|
||||
"""
|
51
neutron/plugins/ml2/drivers/mech_hyperv.py
Normal file
51
neutron/plugins/ml2/drivers/mech_hyperv.py
Normal 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
|
52
neutron/plugins/ml2/drivers/mech_linuxbridge.py
Normal file
52
neutron/plugins/ml2/drivers/mech_linuxbridge.py
Normal 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
|
57
neutron/plugins/ml2/drivers/mech_openvswitch.py
Normal file
57
neutron/plugins/ml2/drivers/mech_openvswitch.py
Normal 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
|
@ -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
|
||||
|
@ -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'))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
207
neutron/tests/unit/ml2/_test_mech_agent.py
Normal file
207
neutron/tests/unit/ml2/_test_mech_agent.py
Normal 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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
65
neutron/tests/unit/ml2/test_mech_hyperv.py
Normal file
65
neutron/tests/unit/ml2/test_mech_hyperv.py
Normal 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
|
65
neutron/tests/unit/ml2/test_mech_linuxbridge.py
Normal file
65
neutron/tests/unit/ml2/test_mech_linuxbridge.py
Normal 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
|
74
neutron/tests/unit/ml2/test_mech_openvswitch.py
Normal file
74
neutron/tests/unit/ml2/test_mech_openvswitch.py
Normal 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
|
@ -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,
|
||||
|
78
neutron/tests/unit/ml2/test_port_binding.py
Normal file
78
neutron/tests/unit/ml2/test_port_binding.py
Normal 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)
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user