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
|
# 4 octet
|
||||||
# base_mac = fa:16:3e:4f:00:00
|
# 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
|
# Maximum amount of retries to generate a unique MAC address
|
||||||
# mac_generation_retries = 16
|
# mac_generation_retries = 16
|
||||||
|
|
||||||
|
@ -144,6 +144,11 @@
|
|||||||
#
|
#
|
||||||
# dont_fragment = True
|
# dont_fragment = True
|
||||||
|
|
||||||
|
# (BoolOpt) Set to True on L2 agents to enable support
|
||||||
|
# for distributed virtual routing.
|
||||||
|
#
|
||||||
|
# enable_distributed_routing = False
|
||||||
|
|
||||||
[securitygroup]
|
[securitygroup]
|
||||||
# Firewall driver for realizing neutron security group function.
|
# Firewall driver for realizing neutron security group function.
|
||||||
# firewall_driver = neutron.agent.firewall.NoopFirewallDriver
|
# 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
|
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):
|
def get_random_string(length):
|
||||||
"""Get a random hex string of the specified 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 agents_db # noqa
|
||||||
from neutron.db import agentschedulers_db # noqa
|
from neutron.db import agentschedulers_db # noqa
|
||||||
from neutron.db import allowedaddresspairs_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 external_net_db # noqa
|
||||||
from neutron.db import extradhcpopt_db # noqa
|
from neutron.db import extradhcpopt_db # noqa
|
||||||
from neutron.db import extraroute_db # noqa
|
from neutron.db import extraroute_db # noqa
|
||||||
|
@ -12,8 +12,13 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from neutron.api.v2 import attributes
|
from neutron.api.v2 import attributes
|
||||||
from neutron.common import constants
|
from neutron.common import constants
|
||||||
|
from neutron.common import exceptions
|
||||||
|
|
||||||
DISTRIBUTED = 'distributed'
|
DISTRIBUTED = 'distributed'
|
||||||
EXTENDED_ATTRIBUTES_2_0 = {
|
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):
|
class Dvr(object):
|
||||||
"""Extension class supporting distributed virtual router."""
|
"""Extension class supporting distributed virtual router."""
|
||||||
|
|
||||||
@ -65,3 +79,19 @@ class Dvr(object):
|
|||||||
return EXTENDED_ATTRIBUTES_2_0
|
return EXTENDED_ATTRIBUTES_2_0
|
||||||
else:
|
else:
|
||||||
return {}
|
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 sqlalchemy.orm import exc
|
||||||
|
|
||||||
|
from neutron.common import constants as n_const
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.db import securitygroups_db as sg_db
|
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
|
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):
|
def get_port(session, port_id):
|
||||||
"""Get port record for update within transcation."""
|
"""Get port record for update within transcation."""
|
||||||
|
|
||||||
@ -156,3 +187,37 @@ def get_port_binding_host(port_id):
|
|||||||
port_id)
|
port_id)
|
||||||
return
|
return
|
||||||
return query.host
|
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))
|
l2_const.SUPPORTED_AGENT_TYPES))
|
||||||
return query
|
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,
|
def get_agent_network_active_port_count(self, session, agent_host,
|
||||||
network_id):
|
network_id):
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
query = session.query(models_v2.Port)
|
query = session.query(models_v2.Port)
|
||||||
|
query1 = query.join(ml2_models.PortBinding)
|
||||||
query = query.join(ml2_models.PortBinding)
|
query1 = query1.filter(models_v2.Port.network_id == network_id,
|
||||||
query = query.filter(models_v2.Port.network_id == network_id,
|
models_v2.Port.status ==
|
||||||
models_v2.Port.status ==
|
const.PORT_STATUS_ACTIVE,
|
||||||
const.PORT_STATUS_ACTIVE,
|
models_v2.Port.device_owner !=
|
||||||
ml2_models.PortBinding.host == agent_host)
|
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||||
return query.count()
|
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",
|
backref=orm.backref("port_binding",
|
||||||
lazy='joined', uselist=False,
|
lazy='joined', uselist=False,
|
||||||
cascade='delete'))
|
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,
|
cfg.BoolOpt('dont_fragment', default=True,
|
||||||
help=_("Set or un-set the don't fragment (DF) bit on "
|
help=_("Set or un-set the don't fragment (DF) bit on "
|
||||||
"outgoing IP packet carrying GRE/VXLAN tunnel")),
|
"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