L2 Model additions to support DVR
This patch introduces the models, the DB migrations and the config options required by the L2 layer to support DVR east/west traffic. These changes will be used by the control-plane made of ML2, L2pop and L2 agent. Two new configuration options have been introduced: 'dvr_base_mac' is used to set DVR MAC addresses apart from tenant ones (every distributed router will have ports being created on compute hosts) and 'enable_distributed_routing' is used to enable dvr support in the L2 agent. This gives the capability of rolling out the dvr functionality in stages. Partially-implements: blueprint neutron-ovs-dvr DocImpact Change-Id: Iab6505f239d2c4c9bcbf4e32a292d7b4b5320c8e Authored-by: Vivekanandan Narasimhan <vivekanandan.narasimhan@hp.com> Co-Authored-By: Armando Migliaccio <armamig@gmail.com>
This commit is contained in:
parent
503dbab8e9
commit
daafe8b247
@ -87,6 +87,14 @@ lock_path = $state_path/lock
|
||||
# 4 octet
|
||||
# base_mac = fa:16:3e:4f:00:00
|
||||
|
||||
# DVR Base MAC address. The first 3 octets will remain unchanged. If the
|
||||
# 4th octet is not 00, it will also be used. The others will be randomly
|
||||
# generated. The 'dvr_base_mac' *must* be different from 'base_mac' to
|
||||
# avoid mixing them up with MAC's allocated for tenant ports.
|
||||
# A 4 octet example would be dvr_base_mac = fa:16:3f:4f:00:00
|
||||
# The default is 3 octet
|
||||
# dvr_base_mac = fa:16:3f:00:00:00
|
||||
|
||||
# Maximum amount of retries to generate a unique MAC address
|
||||
# mac_generation_retries = 16
|
||||
|
||||
|
@ -144,6 +144,11 @@
|
||||
#
|
||||
# dont_fragment = True
|
||||
|
||||
# (BoolOpt) Set to True on L2 agents to enable support
|
||||
# for distributed virtual routing.
|
||||
#
|
||||
# enable_distributed_routing = False
|
||||
|
||||
[securitygroup]
|
||||
# Firewall driver for realizing neutron security group function.
|
||||
# firewall_driver = neutron.agent.firewall.NoopFirewallDriver
|
||||
|
@ -271,6 +271,15 @@ def is_valid_vlan_tag(vlan):
|
||||
return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG
|
||||
|
||||
|
||||
def get_random_mac(base_mac):
|
||||
mac = [int(base_mac[0], 16), int(base_mac[1], 16),
|
||||
int(base_mac[2], 16), random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
|
||||
if base_mac[3] != '00':
|
||||
mac[3] = int(base_mac[3], 16)
|
||||
return ':'.join(["%02x" % x for x in mac])
|
||||
|
||||
|
||||
def get_random_string(length):
|
||||
"""Get a random hex string of the specified length.
|
||||
|
||||
|
157
neutron/db/dvr_mac_db.py
Normal file
157
neutron/db/dvr_mac_db.py
Normal file
@ -0,0 +1,157 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
# 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 oslo.db import exception as db_exc
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.common import exceptions as q_exc
|
||||
from neutron.common import log
|
||||
from neutron.common import utils
|
||||
from neutron.db import model_base
|
||||
from neutron.extensions import dvr as ext_dvr
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from oslo.config import cfg
|
||||
from sqlalchemy.orm import exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
dvr_mac_address_opts = [
|
||||
cfg.StrOpt('dvr_base_mac',
|
||||
default="fa:16:3f:00:00:00",
|
||||
help=_('The base mac address used for unique '
|
||||
'DVR instances by Neutron')),
|
||||
]
|
||||
cfg.CONF.register_opts(dvr_mac_address_opts)
|
||||
|
||||
|
||||
class DistributedVirtualRouterMacAddress(model_base.BASEV2):
|
||||
"""Represents a v2 neutron distributed virtual router mac address."""
|
||||
|
||||
__tablename__ = 'dvr_host_macs'
|
||||
|
||||
host = sa.Column(sa.String(255), primary_key=True, nullable=False)
|
||||
mac_address = sa.Column(sa.String(32), nullable=False, unique=True)
|
||||
|
||||
|
||||
class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase):
|
||||
"""Mixin class to add dvr mac address to db_plugin_base_v2."""
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
try:
|
||||
if self._plugin is not None:
|
||||
return self._plugin
|
||||
except AttributeError:
|
||||
pass
|
||||
self._plugin = manager.NeutronManager.get_plugin()
|
||||
return self._plugin
|
||||
|
||||
def _get_dvr_mac_address_by_host(self, context, host):
|
||||
try:
|
||||
query = context.session.query(DistributedVirtualRouterMacAddress)
|
||||
dvrma = query.filter(
|
||||
DistributedVirtualRouterMacAddress.host == host).one()
|
||||
except exc.NoResultFound:
|
||||
raise ext_dvr.DVRMacAddressNotFound(host=host)
|
||||
return dvrma
|
||||
|
||||
def _create_dvr_mac_address(self, context, host):
|
||||
"""Create dvr mac address for a given host."""
|
||||
base_mac = cfg.CONF.dvr_base_mac.split(':')
|
||||
max_retries = cfg.CONF.mac_generation_retries
|
||||
for attempt in reversed(range(max_retries)):
|
||||
try:
|
||||
with context.session.begin(subtransactions=True):
|
||||
mac_address = utils.get_random_mac(base_mac)
|
||||
dvr_mac_binding = DistributedVirtualRouterMacAddress(
|
||||
host=host, mac_address=mac_address)
|
||||
context.session.add(dvr_mac_binding)
|
||||
LOG.debug("Generated DVR mac for host %(host)s "
|
||||
"is %(mac_address)s",
|
||||
{'host': host, 'mac_address': mac_address})
|
||||
return self._make_dvr_mac_address_dict(dvr_mac_binding)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
LOG.debug("Generated DVR mac %(mac)s exists."
|
||||
" Remaining attempts %(attempts_left)s.",
|
||||
{'mac': mac_address, 'attempts_left': attempt})
|
||||
LOG.error(_("MAC generation error after %s attempts"), max_retries)
|
||||
raise ext_dvr.MacAddressGenerationFailure(host=host)
|
||||
|
||||
def delete_dvr_mac_address(self, context, host):
|
||||
query = context.session.query(DistributedVirtualRouterMacAddress)
|
||||
(query.
|
||||
filter(DistributedVirtualRouterMacAddress.host == host).
|
||||
delete(synchronize_session=False))
|
||||
|
||||
def get_dvr_mac_address_list(self, context):
|
||||
with context.session.begin(subtransactions=True):
|
||||
return (context.session.
|
||||
query(DistributedVirtualRouterMacAddress).all())
|
||||
|
||||
def get_dvr_mac_address_by_host(self, context, host):
|
||||
"""Determine the MAC for the DVR port associated to host."""
|
||||
if not host:
|
||||
return
|
||||
|
||||
try:
|
||||
return self._get_dvr_mac_address_by_host(context, host)
|
||||
except ext_dvr.DVRMacAddressNotFound:
|
||||
return self._create_dvr_mac_address(context, host)
|
||||
|
||||
def _make_dvr_mac_address_dict(self, dvr_mac_entry, fields=None):
|
||||
return {'host': dvr_mac_entry['host'],
|
||||
'mac_address': dvr_mac_entry['mac_address']}
|
||||
|
||||
@log.log
|
||||
def get_compute_ports_on_host_by_subnet(self, context, host, subnet):
|
||||
# FIXME(vivek, salv-orlando): improve this query by adding the
|
||||
# capability of filtering by binding:host_id
|
||||
vm_ports_by_host = []
|
||||
filter = {'fixed_ips': {'subnet_id': [subnet]}}
|
||||
ports = self.plugin.get_ports(context, filters=filter)
|
||||
LOG.debug("List of Ports on subnet %(subnet)s received as %(ports)s",
|
||||
{'subnet': subnet, 'ports': ports})
|
||||
for port in ports:
|
||||
if 'compute:' in port['device_owner']:
|
||||
if port['binding:host_id'] == host:
|
||||
port_dict = self.plugin._make_port_dict(
|
||||
port, process_extensions=False)
|
||||
vm_ports_by_host.append(port_dict)
|
||||
LOG.debug("Returning list of VM Ports on host %(host)s for subnet "
|
||||
"%(subnet)s ports %(ports)s",
|
||||
{'host': host, 'subnet': subnet, 'ports': vm_ports_by_host})
|
||||
return vm_ports_by_host
|
||||
|
||||
@log.log
|
||||
def get_subnet_for_dvr(self, context, subnet):
|
||||
try:
|
||||
subnet_info = self.plugin.get_subnet(context, subnet)
|
||||
except q_exc.SubnetNotFound:
|
||||
return {}
|
||||
else:
|
||||
# retrieve the gateway port on this subnet
|
||||
filter = {'fixed_ips': {'subnet_id': [subnet],
|
||||
'ip_address': [subnet_info['gateway_ip']]}}
|
||||
internal_gateway_ports = self.plugin.get_ports(
|
||||
context, filters=filter)
|
||||
if not internal_gateway_ports:
|
||||
LOG.error(_("Could not retrieve gateway port "
|
||||
"for subnet %s"), subnet_info)
|
||||
return {}
|
||||
internal_port = internal_gateway_ports[0]
|
||||
subnet_info['gateway_mac'] = internal_port['mac_address']
|
||||
return subnet_info
|
@ -0,0 +1,78 @@
|
||||
# Copyright 2014 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""L2 models to support DVR
|
||||
|
||||
Revision ID: 2026156eab2f
|
||||
Revises: 3927f7f7c456
|
||||
Create Date: 2014-06-23 19:12:43.392912
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2026156eab2f'
|
||||
down_revision = '3927f7f7c456'
|
||||
|
||||
migration_for_plugins = [
|
||||
'*'
|
||||
]
|
||||
|
||||
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(
|
||||
'dvr_host_macs',
|
||||
sa.Column('host', sa.String(length=255), nullable=False),
|
||||
sa.Column('mac_address', sa.String(length=32),
|
||||
nullable=False, unique=True),
|
||||
sa.PrimaryKeyConstraint('host')
|
||||
)
|
||||
op.create_table(
|
||||
'ml2_dvr_port_bindings',
|
||||
sa.Column('port_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('host', sa.String(length=255), nullable=False),
|
||||
sa.Column('router_id', sa.String(length=36), nullable=True),
|
||||
sa.Column('vif_type', sa.String(length=64), nullable=False),
|
||||
sa.Column('vif_details', sa.String(length=4095),
|
||||
nullable=False, server_default=''),
|
||||
sa.Column('vnic_type', sa.String(length=64),
|
||||
nullable=False, server_default='normal'),
|
||||
sa.Column('profile', sa.String(length=4095),
|
||||
nullable=False, server_default=''),
|
||||
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.Column(u'status', sa.String(16), nullable=False),
|
||||
sa.ForeignKeyConstraint(['port_id'], ['ports.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['segment'], ['ml2_network_segments.id'],
|
||||
ondelete='SET NULL'),
|
||||
sa.PrimaryKeyConstraint('port_id', 'host')
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
if not migration.should_run(active_plugins, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.drop_table('ml2_dvr_port_bindings')
|
||||
op.drop_table('dvr_host_macs')
|
@ -1 +1 @@
|
||||
3927f7f7c456
|
||||
2026156eab2f
|
||||
|
@ -24,6 +24,7 @@ Based on this comparison database can be healed with healing migration.
|
||||
from neutron.db import agents_db # noqa
|
||||
from neutron.db import agentschedulers_db # noqa
|
||||
from neutron.db import allowedaddresspairs_db # noqa
|
||||
from neutron.db import dvr_mac_db # noqa
|
||||
from neutron.db import external_net_db # noqa
|
||||
from neutron.db import extradhcpopt_db # noqa
|
||||
from neutron.db import extraroute_db # noqa
|
||||
|
@ -12,8 +12,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import constants
|
||||
from neutron.common import exceptions
|
||||
|
||||
DISTRIBUTED = 'distributed'
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
@ -28,6 +33,15 @@ EXTENDED_ATTRIBUTES_2_0 = {
|
||||
}
|
||||
|
||||
|
||||
class DVRMacAddressNotFound(exceptions.NotFound):
|
||||
message = _("Distributed Virtual Router Mac Address for "
|
||||
"host %(host)s does not exist.")
|
||||
|
||||
|
||||
class MacAddressGenerationFailure(exceptions.ServiceUnavailable):
|
||||
message = _("Unable to generate unique DVR mac for host %(host)s.")
|
||||
|
||||
|
||||
class Dvr(object):
|
||||
"""Extension class supporting distributed virtual router."""
|
||||
|
||||
@ -65,3 +79,19 @@ class Dvr(object):
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class DVRMacAddressPluginBase(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_dvr_mac_address(self, context, host):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_dvr_mac_address_list(self, context):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_dvr_mac_address_by_host(self, context, host):
|
||||
pass
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
from sqlalchemy.orm import exc
|
||||
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db import models_v2
|
||||
from neutron.db import securitygroups_db as sg_db
|
||||
@ -88,6 +89,36 @@ def get_locked_port_and_binding(session, port_id):
|
||||
return None, None
|
||||
|
||||
|
||||
def ensure_dvr_port_binding(session, port_id, host, router_id=None):
|
||||
# FIXME(armando-migliaccio): take care of LP #1335226
|
||||
# DVR ports are slightly different from the others in
|
||||
# that binding happens at a later stage via L3 agent
|
||||
# therefore we need to keep this logic of creation on
|
||||
# missing binding.
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
record = (session.query(models.DVRPortBinding).
|
||||
filter_by(port_id=port_id, host=host).one())
|
||||
except exc.NoResultFound:
|
||||
record = models.DVRPortBinding(
|
||||
port_id=port_id,
|
||||
host=host,
|
||||
router_id=router_id,
|
||||
vif_type=portbindings.VIF_TYPE_UNBOUND,
|
||||
vnic_type=portbindings.VNIC_NORMAL,
|
||||
cap_port_filter=False,
|
||||
status=n_const.PORT_STATUS_DOWN)
|
||||
session.add(record)
|
||||
return record
|
||||
|
||||
|
||||
def delete_dvr_port_binding(session, port_id, host):
|
||||
with session.begin(subtransactions=True):
|
||||
(session.query(models.DVRPortBinding).
|
||||
filter_by(port_id=port_id, host=host).
|
||||
delete(synchronize_session=False))
|
||||
|
||||
|
||||
def get_port(session, port_id):
|
||||
"""Get port record for update within transcation."""
|
||||
|
||||
@ -156,3 +187,37 @@ def get_port_binding_host(port_id):
|
||||
port_id)
|
||||
return
|
||||
return query.host
|
||||
|
||||
|
||||
def generate_dvr_port_status(session, port_id):
|
||||
# an OR'ed value of status assigned to parent port from the
|
||||
# dvrportbinding bucket
|
||||
query = session.query(models.DVRPortBinding)
|
||||
final_status = n_const.PORT_STATUS_BUILD
|
||||
for bind in query.filter(models.DVRPortBinding.port_id == port_id):
|
||||
if bind.status == n_const.PORT_STATUS_ACTIVE:
|
||||
return bind.status
|
||||
elif bind.status == n_const.PORT_STATUS_DOWN:
|
||||
final_status = bind.status
|
||||
return final_status
|
||||
|
||||
|
||||
def get_dvr_port_binding_by_host(session, port_id, host):
|
||||
with session.begin(subtransactions=True):
|
||||
binding = (session.query(models.DVRPortBinding).
|
||||
filter(models.DVRPortBinding.port_id.startswith(port_id),
|
||||
models.DVRPortBinding.host == host).first())
|
||||
if not binding:
|
||||
LOG.debug("No binding for DVR port %(port_id)s with host "
|
||||
"%(host)s", {'port_id': port_id, 'host': host})
|
||||
return binding
|
||||
|
||||
|
||||
def get_dvr_port_bindings(session, port_id):
|
||||
with session.begin(subtransactions=True):
|
||||
bindings = (session.query(models.DVRPortBinding).
|
||||
filter(models.DVRPortBinding.port_id.startswith(port_id)).
|
||||
all())
|
||||
if not bindings:
|
||||
LOG.debug("No bindings for DVR port %s", port_id)
|
||||
return bindings
|
||||
|
@ -70,14 +70,44 @@ class L2populationDbMixin(base_db.CommonDbMixin):
|
||||
l2_const.SUPPORTED_AGENT_TYPES))
|
||||
return query
|
||||
|
||||
def get_nondvr_network_ports(self, session, network_id):
|
||||
query = self.get_network_ports(session, network_id)
|
||||
return query.filter(models_v2.Port.device_owner !=
|
||||
const.DEVICE_OWNER_DVR_INTERFACE)
|
||||
|
||||
def get_dvr_network_ports(self, session, network_id):
|
||||
with session.begin(subtransactions=True):
|
||||
query = session.query(ml2_models.DVRPortBinding,
|
||||
agents_db.Agent)
|
||||
query = query.join(agents_db.Agent,
|
||||
agents_db.Agent.host ==
|
||||
ml2_models.DVRPortBinding.host)
|
||||
query = query.join(models_v2.Port)
|
||||
query = query.filter(models_v2.Port.network_id == network_id,
|
||||
models_v2.Port.admin_state_up == sql.true(),
|
||||
models_v2.Port.device_owner ==
|
||||
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||
agents_db.Agent.agent_type.in_(
|
||||
l2_const.SUPPORTED_AGENT_TYPES))
|
||||
return query
|
||||
|
||||
def get_agent_network_active_port_count(self, session, agent_host,
|
||||
network_id):
|
||||
with session.begin(subtransactions=True):
|
||||
query = session.query(models_v2.Port)
|
||||
|
||||
query = query.join(ml2_models.PortBinding)
|
||||
query = query.filter(models_v2.Port.network_id == network_id,
|
||||
models_v2.Port.status ==
|
||||
const.PORT_STATUS_ACTIVE,
|
||||
ml2_models.PortBinding.host == agent_host)
|
||||
return query.count()
|
||||
query1 = query.join(ml2_models.PortBinding)
|
||||
query1 = query1.filter(models_v2.Port.network_id == network_id,
|
||||
models_v2.Port.status ==
|
||||
const.PORT_STATUS_ACTIVE,
|
||||
models_v2.Port.device_owner !=
|
||||
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||
ml2_models.PortBinding.host == agent_host)
|
||||
query2 = query.join(ml2_models.DVRPortBinding)
|
||||
query2 = query2.filter(models_v2.Port.network_id == network_id,
|
||||
ml2_models.DVRPortBinding.status ==
|
||||
const.PORT_STATUS_ACTIVE,
|
||||
models_v2.Port.device_owner ==
|
||||
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||
ml2_models.DVRPortBinding.host ==
|
||||
agent_host)
|
||||
return (query1.count() + query2.count())
|
||||
|
@ -77,3 +77,40 @@ class PortBinding(model_base.BASEV2):
|
||||
backref=orm.backref("port_binding",
|
||||
lazy='joined', uselist=False,
|
||||
cascade='delete'))
|
||||
|
||||
|
||||
class DVRPortBinding(model_base.BASEV2):
|
||||
"""Represent binding-related state of a DVR port.
|
||||
|
||||
Port binding for all the ports associated to a DVR identified by router_id.
|
||||
"""
|
||||
|
||||
__tablename__ = 'ml2_dvr_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, primary_key=True)
|
||||
router_id = sa.Column(sa.String(36), nullable=True)
|
||||
vif_type = sa.Column(sa.String(64), nullable=False)
|
||||
vif_details = sa.Column(sa.String(4095), nullable=False, default='',
|
||||
server_default='')
|
||||
vnic_type = sa.Column(sa.String(64), nullable=False,
|
||||
default=portbindings.VNIC_NORMAL,
|
||||
server_default=portbindings.VNIC_NORMAL)
|
||||
profile = sa.Column(sa.String(BINDING_PROFILE_LEN), nullable=False,
|
||||
default='', server_default='')
|
||||
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"))
|
||||
status = sa.Column(sa.String(16), nullable=False)
|
||||
|
||||
# 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("dvr_port_binding",
|
||||
lazy='joined', uselist=False,
|
||||
cascade='delete'))
|
||||
|
@ -86,6 +86,8 @@ agent_opts = [
|
||||
cfg.BoolOpt('dont_fragment', default=True,
|
||||
help=_("Set or un-set the don't fragment (DF) bit on "
|
||||
"outgoing IP packet carrying GRE/VXLAN tunnel")),
|
||||
cfg.BoolOpt('enable_distributed_routing', default=False,
|
||||
help=_("Make the l2 agent run in DVR mode ")),
|
||||
]
|
||||
|
||||
|
||||
|
102
neutron/tests/unit/db/test_dvr_mac_db.py
Normal file
102
neutron/tests/unit/db/test_dvr_mac_db.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron import context
|
||||
from neutron.db import api as db
|
||||
from neutron.db import dvr_mac_db
|
||||
from neutron.extensions import dvr
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class DVRDbMixinImpl(dvr_mac_db.DVRDbMixin):
|
||||
|
||||
def __init__(self, notifier):
|
||||
self.notifier = notifier
|
||||
|
||||
|
||||
class DvrDbMixinTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DvrDbMixinTestCase, self).setUp()
|
||||
db.configure_db()
|
||||
self.ctx = context.get_admin_context()
|
||||
self.addCleanup(db.clear_db)
|
||||
self.mixin = DVRDbMixinImpl(mock.Mock())
|
||||
|
||||
def _create_dvr_mac_entry(self, host, mac_address):
|
||||
with self.ctx.session.begin(subtransactions=True):
|
||||
entry = dvr_mac_db.DistributedVirtualRouterMacAddress(
|
||||
host=host, mac_address=mac_address)
|
||||
self.ctx.session.add(entry)
|
||||
|
||||
def test__get_dvr_mac_address_by_host(self):
|
||||
with self.ctx.session.begin(subtransactions=True):
|
||||
entry = dvr_mac_db.DistributedVirtualRouterMacAddress(
|
||||
host='foo_host', mac_address='foo_mac_address')
|
||||
self.ctx.session.add(entry)
|
||||
result = self.mixin._get_dvr_mac_address_by_host(self.ctx, 'foo_host')
|
||||
self.assertEqual(entry, result)
|
||||
|
||||
def test__get_dvr_mac_address_by_host_not_found(self):
|
||||
self.assertRaises(dvr.DVRMacAddressNotFound,
|
||||
self.mixin._get_dvr_mac_address_by_host,
|
||||
self.ctx, 'foo_host')
|
||||
|
||||
def test__create_dvr_mac_address_success(self):
|
||||
entry = {'host': 'foo_host', 'mac_address': '00:11:22:33:44:55:66'}
|
||||
with mock.patch.object(dvr_mac_db.utils, 'get_random_mac') as f:
|
||||
f.return_value = entry['mac_address']
|
||||
expected = self.mixin._create_dvr_mac_address(
|
||||
self.ctx, entry['host'])
|
||||
self.assertEqual(expected, entry)
|
||||
|
||||
def test__create_dvr_mac_address_retries_exceeded_retry_logic(self):
|
||||
new_retries = 8
|
||||
cfg.CONF.set_override('mac_generation_retries', new_retries)
|
||||
self._create_dvr_mac_entry('foo_host_1', 'non_unique_mac')
|
||||
with mock.patch.object(dvr_mac_db.utils, 'get_random_mac') as f:
|
||||
f.return_value = 'non_unique_mac'
|
||||
self.assertRaises(dvr.MacAddressGenerationFailure,
|
||||
self.mixin._create_dvr_mac_address,
|
||||
self.ctx, "foo_host_2")
|
||||
self.assertEqual(new_retries, f.call_count)
|
||||
|
||||
def test_delete_dvr_mac_address(self):
|
||||
self._create_dvr_mac_entry('foo_host', 'foo_mac_address')
|
||||
self.mixin.delete_dvr_mac_address(self.ctx, 'foo_host')
|
||||
count = self.ctx.session.query(
|
||||
dvr_mac_db.DistributedVirtualRouterMacAddress).count()
|
||||
self.assertFalse(count)
|
||||
|
||||
def test_get_dvr_mac_address_list(self):
|
||||
self._create_dvr_mac_entry('host_1', 'mac_1')
|
||||
self._create_dvr_mac_entry('host_2', 'mac_2')
|
||||
mac_list = self.mixin.get_dvr_mac_address_list(self.ctx)
|
||||
self.assertEqual(2, len(mac_list))
|
||||
|
||||
def test_get_dvr_mac_address_by_host_existing_host(self):
|
||||
self._create_dvr_mac_entry('foo_host', 'foo_mac')
|
||||
with mock.patch.object(self.mixin,
|
||||
'_get_dvr_mac_address_by_host') as f:
|
||||
self.mixin.get_dvr_mac_address_by_host(self.ctx, 'foo_host')
|
||||
self.assertEqual(1, f.call_count)
|
||||
|
||||
def test_get_dvr_mac_address_by_host_missing_host(self):
|
||||
with mock.patch.object(self.mixin, '_create_dvr_mac_address') as f:
|
||||
self.mixin.get_dvr_mac_address_by_host(self.ctx, 'foo_host')
|
||||
self.assertEqual(1, f.call_count)
|
0
neutron/tests/unit/ml2/db/__init__.py
Normal file
0
neutron/tests/unit/ml2/db/__init__.py
Normal file
130
neutron/tests/unit/ml2/db/test_ml2_dvr_db.py
Normal file
130
neutron/tests/unit/ml2/db/test_ml2_dvr_db.py
Normal file
@ -0,0 +1,130 @@
|
||||
# Copyright (c) 2014 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 import context
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.plugins.ml2 import db as ml2_db
|
||||
from neutron.plugins.ml2 import models as ml2_models
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class Ml2DBTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(Ml2DBTestCase, self).setUp()
|
||||
db_api.configure_db()
|
||||
self.ctx = context.get_admin_context()
|
||||
self.addCleanup(db_api.clear_db)
|
||||
|
||||
def _setup_neutron_network(self, network_id, port_ids):
|
||||
with self.ctx.session.begin(subtransactions=True):
|
||||
self.ctx.session.add(models_v2.Network(id=network_id))
|
||||
ports = []
|
||||
for port_id in port_ids:
|
||||
port = models_v2.Port(id=port_id,
|
||||
network_id=network_id,
|
||||
mac_address='foo_mac_address',
|
||||
admin_state_up=True,
|
||||
status='ACTIVE',
|
||||
device_id='',
|
||||
device_owner='')
|
||||
self.ctx.session.add(port)
|
||||
ports.append(port)
|
||||
return ports
|
||||
|
||||
def _setup_neutron_router(self):
|
||||
with self.ctx.session.begin(subtransactions=True):
|
||||
router = l3_db.Router()
|
||||
self.ctx.session.add(router)
|
||||
return router
|
||||
|
||||
def _setup_dvr_binding(self, network_id, port_id, router_id, host_id):
|
||||
with self.ctx.session.begin(subtransactions=True):
|
||||
record = ml2_models.DVRPortBinding(
|
||||
port_id=port_id,
|
||||
host=host_id,
|
||||
router_id=router_id,
|
||||
vif_type=portbindings.VIF_TYPE_UNBOUND,
|
||||
vnic_type=portbindings.VNIC_NORMAL,
|
||||
cap_port_filter=False,
|
||||
status='DOWN')
|
||||
self.ctx.session.add(record)
|
||||
return record
|
||||
|
||||
def test_ensure_dvr_port_binding(self):
|
||||
network_id = 'foo_network_id'
|
||||
port_id = 'foo_port_id'
|
||||
self._setup_neutron_network(network_id, [port_id])
|
||||
router = self._setup_neutron_router()
|
||||
ml2_db.ensure_dvr_port_binding(
|
||||
self.ctx.session, port_id, 'foo_host', router.id)
|
||||
expected = (self.ctx.session.query(ml2_models.DVRPortBinding).
|
||||
filter_by(port_id=port_id).one())
|
||||
self.assertEqual(expected.port_id, port_id)
|
||||
|
||||
def test_ensure_dvr_port_binding_multiple_bindings(self):
|
||||
network_id = 'foo_network_id'
|
||||
port_id = 'foo_port_id'
|
||||
self._setup_neutron_network(network_id, [port_id])
|
||||
router = self._setup_neutron_router()
|
||||
ml2_db.ensure_dvr_port_binding(
|
||||
self.ctx.session, port_id, 'foo_host_1', router.id)
|
||||
ml2_db.ensure_dvr_port_binding(
|
||||
self.ctx.session, port_id, 'foo_host_2', router.id)
|
||||
bindings = (self.ctx.session.query(ml2_models.DVRPortBinding).
|
||||
filter_by(port_id=port_id).all())
|
||||
self.assertEqual(2, len(bindings))
|
||||
|
||||
def test_delete_dvr_port_binding(self):
|
||||
network_id = 'foo_network_id'
|
||||
port_id = 'foo_port_id'
|
||||
self._setup_neutron_network(network_id, [port_id])
|
||||
router = self._setup_neutron_router()
|
||||
binding = self._setup_dvr_binding(
|
||||
network_id, port_id, router.id, 'foo_host_id')
|
||||
ml2_db.delete_dvr_port_binding(
|
||||
self.ctx.session, port_id, 'foo_host_id')
|
||||
count = (self.ctx.session.query(ml2_models.DVRPortBinding).
|
||||
filter_by(port_id=binding.port_id).count())
|
||||
self.assertFalse(count)
|
||||
|
||||
def test_delete_dvr_port_binding_not_found(self):
|
||||
ml2_db.delete_dvr_port_binding(
|
||||
self.ctx.session, 'foo_port_id', 'foo_host')
|
||||
|
||||
def test_get_dvr_port_binding_by_host_not_found(self):
|
||||
port = ml2_db.get_dvr_port_binding_by_host(
|
||||
self.ctx.session, 'foo_port_id', 'foo_host_id')
|
||||
self.assertIsNone(port)
|
||||
|
||||
def test_get_dvr_port_bindings_not_found(self):
|
||||
port = ml2_db.get_dvr_port_bindings(self.ctx.session, 'foo_port_id')
|
||||
self.assertFalse(len(port))
|
||||
|
||||
def test_get_dvr_port_bindings(self):
|
||||
network_id = 'foo_network_id'
|
||||
port_id_1 = 'foo_port_id_1'
|
||||
port_id_2 = 'foo_port_id_2'
|
||||
self._setup_neutron_network(network_id, [port_id_1, port_id_2])
|
||||
router = self._setup_neutron_router()
|
||||
self._setup_dvr_binding(
|
||||
network_id, port_id_1, router.id, 'foo_host_id_1')
|
||||
self._setup_dvr_binding(
|
||||
network_id, port_id_1, router.id, 'foo_host_id_2')
|
||||
ports = ml2_db.get_dvr_port_bindings(self.ctx.session, 'foo_port_id')
|
||||
self.assertEqual(2, len(ports))
|
Loading…
Reference in New Issue
Block a user