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:
Vivekanandan Narasimhan 2014-06-23 18:53:32 -07:00 committed by armando-migliaccio
parent 474b3556a5
commit 10579d28d7
15 changed files with 662 additions and 8 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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')

View File

@ -1 +1 @@
3927f7f7c456
2026156eab2f

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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'))

View File

@ -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 ")),
]

View 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)

View File

View 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))