DistributedVirtualRouter mac address to OVO

This patch introduces and integrates Oslo-Versioned Objects for the
DistributedVirtualRouter mac address model class.

Partially-Implements: blueprint adopt-oslo-versioned-objects-for-db
Co-Authored-By: Victor Morales<victor.morales@intel.com>
Change-Id: I3b8a213a7daf95d2492b48ae59d3ad534911e1bb
This commit is contained in:
tonytan4ever 2016-03-03 12:20:27 +00:00 committed by Ihar Hrachyshka
parent d37f0652b9
commit 071cb905f3
5 changed files with 106 additions and 60 deletions

View File

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

View File

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

View File

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

View File

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

View File

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