# 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
#
# 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
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.
--- /dev/null
+# 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
--- /dev/null
+# 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')
-3927f7f7c456
+2026156eab2f
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
# 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 = {
}
+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."""
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
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
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."""
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
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())
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'))
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 ")),
]
--- /dev/null
+# 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)
--- /dev/null
+# 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))