diff --git a/neutron/db/dvr_mac_db.py b/neutron/db/dvr_mac_db.py index 66c55ac9942..17885ede207 100644 --- a/neutron/db/dvr_mac_db.py +++ b/neutron/db/dvr_mac_db.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr + from neutron_lib.api.definitions import portbindings from neutron_lib.callbacks import events from neutron_lib.callbacks import registry @@ -20,20 +22,18 @@ from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory -from neutron_lib.utils import net from oslo_config import cfg -from oslo_db import exception as db_exc from oslo_log import helpers as log_helpers from oslo_log import log as logging from sqlalchemy import or_ -from sqlalchemy.orm import exc from neutron._i18n import _, _LE from neutron.common import utils from neutron.db import api as db_api -from neutron.db.models import dvr as dvr_models from neutron.db import models_v2 from neutron.extensions import dvr as ext_dvr +from neutron.objects import exceptions +from neutron.objects import router LOG = logging.getLogger(__name__) @@ -81,42 +81,33 @@ class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase): # there are still agents on this host, don't mess with the mac # entry until they are all deleted. return - try: - with db_api.context_manager.writer.using(context): - entry = (context.session. - query(dvr_models.DistributedVirtualRouterMacAddress). - filter_by(host=host). - one()) - context.session.delete(entry) - except exc.NoResultFound: + + if not router.DVRMacAddress.delete_objects(context, host=host): return + # notify remaining agents so they cleanup flows dvr_macs = plugin.get_dvr_mac_address_list(context) plugin.notifier.dvr_mac_address_update(context, dvr_macs) @db_api.context_manager.reader def _get_dvr_mac_address_by_host(self, context, host): - try: - query = context.session.query( - dvr_models.DistributedVirtualRouterMacAddress) - dvrma = query.filter( - dvr_models.DistributedVirtualRouterMacAddress.host == host - ).one() - except exc.NoResultFound: + dvr_obj = router.DVRMacAddress.get_object(context, host=host) + if not dvr_obj: raise ext_dvr.DVRMacAddressNotFound(host=host) - return dvrma + return self._make_dvr_mac_address_dict(dvr_obj) @utils.transaction_guard @db_api.retry_if_session_inactive() def _create_dvr_mac_address_retry(self, context, host, base_mac): with db_api.context_manager.writer.using(context): - mac_address = net.get_random_mac(base_mac) - dvr_mac_binding = dvr_models.DistributedVirtualRouterMacAddress( - host=host, mac_address=mac_address) - context.session.add(dvr_mac_binding) + mac_address = utils.get_random_mac(base_mac) + dvr_mac_binding = router.DVRMacAddress( + context, host=host, mac_address=netaddr.EUI(mac_address)) + dvr_mac_binding.create() LOG.debug("Generated DVR mac for host %(host)s " "is %(mac_address)s", {'host': host, 'mac_address': mac_address}) + dvr_macs = self.get_dvr_mac_address_list(context) # TODO(vivek): improve scalability of this fanout by # sending a single mac address rather than the entire set @@ -128,15 +119,17 @@ class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase): base_mac = cfg.CONF.dvr_base_mac.split(':') try: return self._create_dvr_mac_address_retry(context, host, base_mac) - except db_exc.DBDuplicateEntry: + except exceptions.NeutronDbObjectDuplicateEntry: LOG.error(_LE("MAC generation error after %s attempts"), db_api.MAX_RETRIES) raise ext_dvr.MacAddressGenerationFailure(host=host) @db_api.context_manager.reader def get_dvr_mac_address_list(self, context): - return (context.session. - query(dvr_models.DistributedVirtualRouterMacAddress).all()) + return [ + dvr_mac.to_dict() + for dvr_mac in router.DVRMacAddress.get_objects(context) + ] def get_dvr_mac_address_by_host(self, context, host): """Determine the MAC for the DVR port associated to host.""" @@ -150,7 +143,7 @@ class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase): 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']} + 'mac_address': str(dvr_mac_entry['mac_address'])} @log_helpers.log_method_call @db_api.retry_if_session_inactive() diff --git a/neutron/objects/router.py b/neutron/objects/router.py index 87d51363173..6fca1a6d6d2 100644 --- a/neutron/objects/router.py +++ b/neutron/objects/router.py @@ -17,6 +17,7 @@ from oslo_versionedobjects import fields as obj_fields from sqlalchemy import func from neutron.common import utils +from neutron.db.models import dvr as dvr_models from neutron.db.models import l3 from neutron.db.models import l3_attrs from neutron.db.models import l3agent as rb_model @@ -134,3 +135,34 @@ class RouterPort(base.NeutronDbObject): 'port_id': common_types.UUIDField(), 'port_type': obj_fields.StringField(nullable=True), } + + +@obj_base.VersionedObjectRegistry.register +class DVRMacAddress(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = dvr_models.DistributedVirtualRouterMacAddress + + primary_keys = ['host'] + + fields = { + 'host': obj_fields.StringField(), + 'mac_address': common_types.MACAddressField() + } + + @classmethod + def modify_fields_from_db(cls, db_obj): + fields = super(DVRMacAddress, cls).modify_fields_from_db(db_obj) + if 'mac_address' in fields: + # NOTE(tonytan4ever): Here uses AuthenticEUI to retain the format + # passed from API. + fields['mac_address'] = utils.AuthenticEUI(fields['mac_address']) + return fields + + @classmethod + def modify_fields_to_db(cls, fields): + result = super(DVRMacAddress, cls).modify_fields_to_db(fields) + if 'mac_address' in fields: + result['mac_address'] = cls.filter_to_str(result['mac_address']) + return result diff --git a/neutron/tests/unit/db/test_dvr_mac_db.py b/neutron/tests/unit/db/test_dvr_mac_db.py index 32b4c9dbde2..cd9aed5b499 100644 --- a/neutron/tests/unit/db/test_dvr_mac_db.py +++ b/neutron/tests/unit/db/test_dvr_mac_db.py @@ -22,10 +22,10 @@ from neutron_lib import constants from neutron_lib import context from neutron_lib.plugins import directory -from neutron.db import api as db_api from neutron.db import dvr_mac_db -from neutron.db.models import dvr as dvr_models from neutron.extensions import dvr +from neutron.objects import router +from neutron.tests import tools from neutron.tests.unit.plugins.ml2 import test_plugin @@ -43,19 +43,16 @@ class DvrDbMixinTestCase(test_plugin.Ml2PluginV2TestCase): self.mixin = DVRDbMixinImpl(mock.Mock()) def _create_dvr_mac_entry(self, host, mac_address): - with db_api.context_manager.writer.using(self.ctx): - entry = dvr_models.DistributedVirtualRouterMacAddress( - host=host, mac_address=mac_address) - self.ctx.session.add(entry) + router.DVRMacAddress( + self.ctx, host=host, mac_address=mac_address).create() def test__get_dvr_mac_address_by_host(self): - with db_api.context_manager.writer.using(self.ctx): - entry = dvr_models.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) + entry = router.DVRMacAddress( + self.ctx, host='foo_host', + mac_address=tools.get_random_EUI()) + entry.create() + result = self.mixin._get_dvr_mac_address_by_host(self.ctx, 'foo_host') + self.assertEqual(entry.to_dict(), result) def test__get_dvr_mac_address_by_host_not_found(self): self.assertRaises(dvr.DVRMacAddressNotFound, @@ -63,8 +60,8 @@ class DvrDbMixinTestCase(test_plugin.Ml2PluginV2TestCase): 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.net, 'get_random_mac') as f: + entry = {'host': 'foo_host', 'mac_address': tools.get_random_EUI()} + 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']) @@ -74,17 +71,21 @@ class DvrDbMixinTestCase(test_plugin.Ml2PluginV2TestCase): # limit retries so test doesn't take 40 seconds mock.patch('neutron.db.api._retry_db_errors.max_retries', new=2).start() - self._create_dvr_mac_entry('foo_host_1', 'non_unique_mac') - with mock.patch.object(dvr_mac_db.net, 'get_random_mac') as f: - f.return_value = 'non_unique_mac' + + non_unique_mac = tools.get_random_EUI() + 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") def test_mac_not_cleared_on_agent_delete_event_with_remaining_agents(self): plugin = directory.get_plugin() - self._create_dvr_mac_entry('host_1', 'mac_1') - self._create_dvr_mac_entry('host_2', 'mac_2') + mac_1 = tools.get_random_EUI() + mac_2 = tools.get_random_EUI() + self._create_dvr_mac_entry('host_1', mac_1) + self._create_dvr_mac_entry('host_2', mac_2) agent1 = {'host': 'host_1', 'id': 'a1'} agent2 = {'host': 'host_1', 'id': 'a2'} with mock.patch.object(plugin, 'get_agents', return_value=[agent2]): @@ -92,33 +93,41 @@ class DvrDbMixinTestCase(test_plugin.Ml2PluginV2TestCase): registry.notify(resources.AGENT, events.BEFORE_DELETE, self, context=self.ctx, agent=agent1) mac_list = self.mixin.get_dvr_mac_address_list(self.ctx) + for mac in mac_list: + self.assertIsInstance(mac, dict) self.assertEqual(2, len(mac_list)) self.assertFalse(notifier.dvr_mac_address_update.called) def test_mac_cleared_on_agent_delete_event(self): plugin = directory.get_plugin() - - with db_api.context_manager.writer.using(self.ctx): - self._create_dvr_mac_entry('host_1', 'mac_1') - self._create_dvr_mac_entry('host_2', 'mac_2') - agent = {'host': 'host_1', 'id': 'a1'} - with mock.patch.object(plugin, 'notifier') as notifier: - registry.notify(resources.AGENT, events.BEFORE_DELETE, self, - context=self.ctx, agent=agent) - mac_list = self.mixin.get_dvr_mac_address_list(self.ctx) + mac_1 = tools.get_random_EUI() + mac_2 = tools.get_random_EUI() + self._create_dvr_mac_entry('host_1', mac_1) + self._create_dvr_mac_entry('host_2', mac_2) + agent = {'host': 'host_1', 'id': 'a1'} + with mock.patch.object(plugin, 'notifier') as notifier: + registry.notify(resources.AGENT, events.BEFORE_DELETE, self, + context=self.ctx, agent=agent) + mac_list = self.mixin.get_dvr_mac_address_list(self.ctx) self.assertEqual(1, len(mac_list)) + for mac in mac_list: + self.assertIsInstance(mac, dict) self.assertEqual('host_2', mac_list[0]['host']) notifier.dvr_mac_address_update.assert_called_once_with( self.ctx, mac_list) 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_1 = tools.get_random_EUI() + mac_2 = tools.get_random_EUI() + 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)) + for mac in mac_list: + self.assertIsInstance(mac, dict) def test_get_dvr_mac_address_by_host_existing_host(self): - self._create_dvr_mac_entry('foo_host', 'foo_mac') + self._create_dvr_mac_entry('foo_host', tools.get_random_EUI()) 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') diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index d358ec27e35..f80f9765e37 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -34,6 +34,7 @@ object_data = { 'DistributedPortBinding': '1.0-39c0d17b281991dcb66716fee5a8bef2', 'DNSNameServer': '1.0-bf87a85327e2d812d1666ede99d9918b', 'ExternalNetwork': '1.0-53d885e033cb931f9bb3bdd6bbe3f0ce', + 'DVRMacAddress': '1.0-d3c61a8338d20da74db2364d4d6554f2', 'ExtraDhcpOpt': '1.0-632f689cbeb36328995a7aed1d0a78d3', 'FlatAllocation': '1.0-bf666f24f4642b047eeca62311fbcb41', 'Flavor': '1.0-82194de5c9aafce08e8527bb7977f5c6', diff --git a/neutron/tests/unit/objects/test_router.py b/neutron/tests/unit/objects/test_router.py index c8a31c07eab..a7d2771fb41 100644 --- a/neutron/tests/unit/objects/test_router.py +++ b/neutron/tests/unit/objects/test_router.py @@ -65,3 +65,14 @@ class RouterPortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, self.update_obj_fields( {'router_id': lambda: self._create_test_router_id(), 'port_id': lambda: self._create_test_port_id()}) + + +class DVRMacAddressIfaceObjectTestCase(obj_test_base.BaseObjectIfaceTestCase): + + _test_class = router.DVRMacAddress + + +class DVRMacAddressDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = router.DVRMacAddress