HA for DVR - Neutron Server side code changes

This patch adds HA support for DVR centralized default SNAT
functionality to Neutron Server. For the agent side changes
another patch has been merged.

Salient changes here are:

 - Schedule/de-schedule SNAT on multiple agents
 - Enables
    'router-create <router name> --ha True --distributed True'

Closes-bug: #1365473

Co-Authored-By: Adolfo Duarte <adolfo.duarte@hpe.com>
Co-Authored-By: Hardik Italia <hardik.italia@hpe.com>
Co-Authored-By: John Schwarz <jschwarz@redhat.com>
Co-Authored-By: Oleg Bondarev <obondarev@mirantis.com>
Change-Id: I6a19481d0e19b8a55f32199a27057bf777548b33
This commit is contained in:
rajeev 2014-12-09 11:51:22 -05:00 committed by Carl Baldwin
parent 5d7ffc03ed
commit 3f0c618cfd
9 changed files with 526 additions and 64 deletions

View File

@ -0,0 +1,46 @@
# Copyright (c) 2016 Hewlett Packard Enterprise 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.
import neutron.db.l3_dvrscheduler_db as l3agent_dvr_sch_db
import neutron.db.l3_hascheduler_db as l3_ha_sch_db
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class L3_DVR_HA_scheduler_db_mixin(l3agent_dvr_sch_db.L3_DVRsch_db_mixin,
l3_ha_sch_db.L3_HA_scheduler_db_mixin):
def get_dvr_routers_to_remove(self, context, port_id):
"""Returns info about which routers should be removed
In case dvr serviceable port was deleted we need to check
if any dvr routers should be removed from l3 agent on port's host
"""
remove_router_info = super(L3_DVR_HA_scheduler_db_mixin,
self).get_dvr_routers_to_remove(context,
port_id)
# Process the router information which was returned to make
# sure we don't delete routers which have dvrhs snat bindings.
processed_remove_router_info = []
for router_info in remove_router_info:
router_id = router_info['router_id']
agent_id = router_info['agent_id']
if not self._check_router_agent_ha_binding(
context, router_id, agent_id):
processed_remove_router_info.append(router_info)
return processed_remove_router_info

View File

@ -402,10 +402,6 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
def create_router(self, context, router):
is_ha = self._is_ha(router['router'])
if is_ha and l3_dvr_db.is_distributed_router(router['router']):
raise l3_ha.DistributedHARouterNotSupported()
router['router']['ha'] = is_ha
router_dict = super(L3_HA_NAT_db_mixin,
self).create_router(context, router)
@ -436,11 +432,29 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
requested_ha_state = data.pop('ha', None)
requested_distributed_state = data.get('distributed', None)
# cvr to dvrha
if not original_distributed_state and not original_ha_state:
if (requested_ha_state is True and
requested_distributed_state is True):
raise l3_ha.UpdateToDvrHamodeNotSupported()
if ((original_ha_state and requested_distributed_state) or
(requested_ha_state and original_distributed_state) or
(requested_ha_state and requested_distributed_state)):
raise l3_ha.DistributedHARouterNotSupported()
# cvrha to any dvr...
elif not original_distributed_state and original_ha_state:
if requested_distributed_state is True:
raise l3_ha.DVRmodeUpdateOfHaNotSupported()
# dvr to any ha...
elif original_distributed_state and not original_ha_state:
if requested_ha_state is True:
raise l3_ha.HAmodeUpdateOfDvrNotSupported()
#dvrha to any cvr...
elif original_distributed_state and original_ha_state:
if requested_distributed_state is False:
raise l3_ha.DVRmodeUpdateOfDvrHaNotSupported()
#elif dvrha to dvr
if requested_ha_state is False:
raise l3_ha.HAmodeUpdateOfDvrHaNotSupported()
with context.session.begin(subtransactions=True):
router_db = super(L3_HA_NAT_db_mixin, self)._update_router_db(
@ -554,6 +568,14 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
return query.all()
@staticmethod
def _check_router_agent_ha_binding(context, router_id, agent_id):
query = context.session.query(L3HARouterAgentPortBinding)
query = query.filter(
L3HARouterAgentPortBinding.router_id == router_id,
L3HARouterAgentPortBinding.l3_agent_id == agent_id)
return query.first() is not None
def _get_bindings_and_update_router_state_for_dead_agents(self, context,
router_id):
"""Return bindings. In case if dead agents were detected update router
@ -654,7 +676,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
admin_ctx = context.elevated()
device_filter = {'device_id': states.keys(),
'device_owner':
[constants.DEVICE_OWNER_ROUTER_INTF]}
[constants.DEVICE_OWNER_ROUTER_INTF,
constants.DEVICE_OWNER_ROUTER_SNAT]}
ports = self._core_plugin.get_ports(admin_ctx, filters=device_filter)
active_ports = (port for port in ports
if states[port['device_id']] == constants.HA_ROUTER_STATE_ACTIVE)

View File

@ -30,11 +30,35 @@ EXTENDED_ATTRIBUTES_2_0 = {
}
class DistributedHARouterNotSupported(exceptions.BadRequest):
message = _("Currently distributed HA routers are "
class HAmodeUpdateOfDvrNotSupported(NotImplementedError):
message = _("Currently update of HA mode for a distributed router is "
"not supported.")
class DVRmodeUpdateOfHaNotSupported(NotImplementedError):
message = _("Currently update of distributed mode for an HA router is "
"not supported.")
class HAmodeUpdateOfDvrHaNotSupported(NotImplementedError):
message = _("Currently update of HA mode for a DVR/HA router is "
"not supported.")
class DVRmodeUpdateOfDvrHaNotSupported(NotImplementedError):
message = _("Currently update of distributed mode for a DVR/HA router "
"is not supported")
class UpdateToDvrHamodeNotSupported(NotImplementedError):
message = _("Currently updating a router to DVR/HA is not supported.")
class UpdateToNonDvrHamodeNotSupported(NotImplementedError):
message = _("Currently updating a router from DVR/HA to non-DVR "
" non-HA is not supported.")
class MaxVRIDAllocationTriesReached(exceptions.NeutronException):
message = _("Failed to allocate a VRID in the network %(network_id)s "
"for the router %(router_id)s after %(max_tries)s tries.")

View File

@ -26,10 +26,10 @@ from neutron.db import common_db_mixin
from neutron.db import dns_db
from neutron.db import extraroute_db
from neutron.db import l3_db
from neutron.db import l3_dvr_ha_scheduler_db
from neutron.db import l3_dvrscheduler_db
from neutron.db import l3_gwmode_db
from neutron.db import l3_hamode_db
from neutron.db import l3_hascheduler_db
from neutron.plugins.common import constants
from neutron.quota import resource_registry
from neutron.services import service_base
@ -40,8 +40,7 @@ class L3RouterPlugin(service_base.ServicePluginBase,
extraroute_db.ExtraRoute_db_mixin,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
l3_dvrscheduler_db.L3_DVRsch_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin,
l3_dvr_ha_scheduler_db.L3_DVR_HA_scheduler_db_mixin,
dns_db.DNSDbMixin):
"""Implementation of the Neutron L3 Router Service Plugin.

View File

@ -0,0 +1,301 @@
# Copyright (c) 2016 Red Hat, Inc.
#
# 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 neutron.common import constants
from neutron.common import topics
from neutron.extensions import external_net
from neutron.extensions import l3_ext_ha_mode
from neutron.extensions import portbindings
from neutron.tests.common import helpers
from neutron.tests.functional.services.l3_router import \
test_l3_dvr_router_plugin
DEVICE_OWNER_COMPUTE = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake'
class L3DvrHATestCase(test_l3_dvr_router_plugin.L3DvrTestCase):
def setUp(self):
super(L3DvrHATestCase, self).setUp()
self.l3_agent_2 = helpers.register_l3_agent(
host="standby",
agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
def _create_router(self, distributed=True, ha=True):
return (super(L3DvrHATestCase, self).
_create_router(distributed=distributed, ha=ha))
def test_update_router_db_cvr_to_dvrha(self):
router = self._create_router(distributed=False, ha=False)
self.assertRaises(
l3_ext_ha_mode.UpdateToDvrHamodeNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': True, 'ha': True}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertFalse(router['distributed'])
self.assertFalse(router['ha'])
def test_update_router_db_dvrha_to_cvr(self):
router = self._create_router(distributed=True, ha=True)
self.assertRaises(
l3_ext_ha_mode.DVRmodeUpdateOfDvrHaNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': False, 'ha': False}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertTrue(router['distributed'])
self.assertTrue(router['ha'])
def test_update_router_db_dvrha_to_dvr(self):
router = self._create_router(distributed=True, ha=True)
self.l3_plugin.update_router(
self.context, router['id'], {'router': {'admin_state_up': False}})
self.assertRaises(
l3_ext_ha_mode.HAmodeUpdateOfDvrHaNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': True, 'ha': False}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertTrue(router['distributed'])
self.assertTrue(router['ha'])
def test_update_router_db_dvrha_to_cvrha(self):
router = self._create_router(distributed=True, ha=True)
self.assertRaises(
l3_ext_ha_mode.DVRmodeUpdateOfDvrHaNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': False, 'ha': True}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertTrue(router['distributed'])
self.assertTrue(router['ha'])
def test_update_router_db_dvr_to_dvrha(self):
router = self._create_router(distributed=True, ha=False)
self.assertRaises(
l3_ext_ha_mode.HAmodeUpdateOfDvrNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': True, 'ha': True}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertTrue(router['distributed'])
self.assertFalse(router['ha'])
def test_update_router_db_cvrha_to_dvrha(self):
router = self._create_router(distributed=False, ha=True)
self.assertRaises(
l3_ext_ha_mode.DVRmodeUpdateOfHaNotSupported,
self.l3_plugin.update_router,
self.context,
router['id'],
{'router': {'distributed': True, 'ha': True}}
)
router = self.l3_plugin.get_router(self.context, router['id'])
self.assertFalse(router['distributed'])
self.assertTrue(router['ha'])
def _assert_router_is_hosted_on_both_dvr_snat_agents(self, router):
agents = self.l3_plugin.list_l3_agents_hosting_router(
self.context, router['id'])
self.assertEqual(2, len(agents['agents']))
dvr_snat_agents = self.l3_plugin.get_ha_router_port_bindings(
self.context, [router['id']])
dvr_snat_agent_ids = [a.l3_agent_id for a in dvr_snat_agents]
self.assertIn(self.l3_agent['id'], dvr_snat_agent_ids)
self.assertIn(self.l3_agent_2['id'], dvr_snat_agent_ids)
def test_router_notifications(self):
"""Check that notifications go to the right hosts in different
conditions
"""
# register l3 agents in dvr mode in addition to existing dvr_snat agent
HOST1, HOST2, HOST3 = 'host1', 'host2', 'host3'
for host in [HOST1, HOST2, HOST3]:
helpers.register_l3_agent(
host=host, agent_mode=constants.L3_AGENT_MODE_DVR)
router = self._create_router(distributed=True, ha=True)
arg_list = (portbindings.HOST_ID,)
with self.subnet() as ext_subnet, \
self.subnet(cidr='20.0.0.0/24') as subnet1, \
self.subnet(cidr='30.0.0.0/24') as subnet2, \
self.subnet(cidr='40.0.0.0/24') as subnet3, \
self.port(subnet=subnet1,
device_owner=DEVICE_OWNER_COMPUTE,
arg_list=arg_list,
**{portbindings.HOST_ID: HOST1}), \
self.port(subnet=subnet2,
device_owner=constants.DEVICE_OWNER_DHCP,
arg_list=arg_list,
**{portbindings.HOST_ID: HOST2}), \
self.port(subnet=subnet3,
device_owner=constants.DEVICE_OWNER_NEUTRON_PREFIX,
arg_list=arg_list,
**{portbindings.HOST_ID: HOST3}):
# make net external
ext_net_id = ext_subnet['subnet']['network_id']
self._update('networks', ext_net_id,
{'network': {external_net.EXTERNAL: True}})
with mock.patch.object(self.l3_plugin.l3_rpc_notifier.client,
'prepare') as mock_prepare:
# add external gateway to router
self.l3_plugin.update_router(
self.context, router['id'],
{'router': {
'external_gateway_info': {'network_id': ext_net_id}}})
# router has no interfaces so notification goes
# to only dvr_snat agents (self.l3_agent and self.l3_agent_2)
self.assertEqual(2, mock_prepare.call_count)
expected = [mock.call(server=self.l3_agent['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=self.l3_agent_2['host'],
topic=topics.L3_AGENT,
version='1.1')]
mock_prepare.assert_has_calls(expected, any_order=True)
mock_prepare.reset_mock()
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': subnet1['subnet']['id']})
self.assertEqual(3, mock_prepare.call_count)
expected = [mock.call(server=self.l3_agent['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=self.l3_agent_2['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=HOST1,
topic=topics.L3_AGENT,
version='1.1')]
mock_prepare.assert_has_calls(expected, any_order=True)
mock_prepare.reset_mock()
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': subnet2['subnet']['id']})
self.assertEqual(4, mock_prepare.call_count)
expected = [mock.call(server=self.l3_agent['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=self.l3_agent_2['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=HOST1,
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=HOST2,
topic=topics.L3_AGENT,
version='1.1')]
mock_prepare.assert_has_calls(expected, any_order=True)
mock_prepare.reset_mock()
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': subnet3['subnet']['id']})
# there are no dvr serviceable ports on HOST3, so notification
# goes to the same hosts
self.assertEqual(4, mock_prepare.call_count)
expected = [mock.call(server=self.l3_agent['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=self.l3_agent_2['host'],
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=HOST1,
topic=topics.L3_AGENT,
version='1.1'),
mock.call(server=HOST2,
topic=topics.L3_AGENT,
version='1.1')]
mock_prepare.assert_has_calls(expected, any_order=True)
def test_router_is_not_removed_from_snat_agent_on_interface_removal(self):
"""Check that dvr router is not removed from dvr_snat l3 agents
on router interface removal
"""
router = self._create_router(distributed=True, ha=True)
kwargs = {'arg_list': (external_net.EXTERNAL,),
external_net.EXTERNAL: True}
with self.subnet() as subnet, \
self.network(**kwargs) as ext_net, \
self.subnet(network=ext_net, cidr='20.0.0.0/24'):
self.l3_plugin._update_router_gw_info(
self.context, router['id'],
{'network_id': ext_net['network']['id']})
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
self._assert_router_is_hosted_on_both_dvr_snat_agents(router)
with mock.patch.object(self.l3_plugin,
'_l3_rpc_notifier') as l3_notifier:
self.l3_plugin.remove_router_interface(
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
self._assert_router_is_hosted_on_both_dvr_snat_agents(router)
self.assertFalse(l3_notifier.router_removed_from_agent.called)
def test_router_is_not_removed_from_snat_agent_on_dhcp_port_deletion(self):
"""Check that dvr router is not removed from l3 agent hosting
SNAT for it on DHCP port removal
"""
router = self._create_router(distributed=True, ha=True)
kwargs = {'arg_list': (external_net.EXTERNAL,),
external_net.EXTERNAL: True}
with self.network(**kwargs) as ext_net, \
self.subnet(network=ext_net), \
self.subnet(cidr='20.0.0.0/24') as subnet, \
self.port(subnet=subnet,
device_owner=constants.DEVICE_OWNER_DHCP) as port:
self.core_plugin.update_port(
self.context, port['port']['id'],
{'port': {'binding:host_id': self.l3_agent['host']}})
self.l3_plugin._update_router_gw_info(
self.context, router['id'],
{'network_id': ext_net['network']['id']})
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': subnet['subnet']['id']})
# router should be scheduled to both dvr_snat l3 agents
self._assert_router_is_hosted_on_both_dvr_snat_agents(router)
notifier = self.l3_plugin.agent_notifiers[
constants.AGENT_TYPE_L3]
with mock.patch.object(
notifier, 'router_removed_from_agent',
side_effect=Exception("BOOOOOOM!")) as remove_mock:
self._delete('ports', port['port']['id'])
# now when port is deleted the router still has external
# gateway and should still be scheduled to the snat agent
remove_mock.assert_not_called()
self._assert_router_is_hosted_on_both_dvr_snat_agents(router)
def test_update_router_db_centralized_to_distributed(self):
self.skipTest('Valid for DVR-only routers')
def test__get_router_ids_for_agent(self):
self.skipTest('Valid for DVR-only routers')

View File

@ -33,9 +33,9 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self.l3_agent = helpers.register_l3_agent(
agent_mode=constants.L3_AGENT_MODE_DVR_SNAT)
def _create_router(self, distributed=True):
def _create_router(self, distributed=True, ha=False):
return (super(L3DvrTestCase, self).
_create_router(distributed=distributed))
_create_router(distributed=distributed, ha=ha))
def test_update_router_db_centralized_to_distributed(self):
router = self._create_router(distributed=False)
@ -527,7 +527,7 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
self._test_router_remove_from_agent_on_vm_port_deletion(
non_admin_port=True)
def test_dvr_router_notifications(self):
def test_router_notifications(self):
"""Check that notifications go to the right hosts in different
conditions
"""

View File

@ -65,12 +65,12 @@ class L3HATestFramework(testlib_api.SqlTestCase):
'host_2', constants.L3_AGENT_MODE_DVR_SNAT)
def _create_router(self, ha=True, tenant_id='tenant1', distributed=None,
ctx=None):
ctx=None, admin_state_up=True):
if ctx is None:
ctx = self.admin_ctx
ctx.tenant_id = tenant_id
router = {'name': 'router1',
'admin_state_up': True,
'admin_state_up': admin_state_up,
'tenant_id': tenant_id}
if ha is not None:
router['ha'] = ha
@ -209,9 +209,12 @@ class L3HATestCase(L3HATestFramework):
self.assertTrue(router['ha'])
def test_ha_router_create_with_distributed(self):
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self._create_router,
distributed=True)
router = self._create_router(ha=True, distributed=True)
self.assertTrue(router['ha'])
self.assertTrue(router['distributed'])
ha_network = self.plugin.get_ha_network(self.admin_ctx,
router['tenant_id'])
self.assertIsNotNone(ha_network)
def test_no_ha_router_create(self):
router = self._create_router(ha=False)
@ -233,6 +236,12 @@ class L3HATestCase(L3HATestFramework):
router = self._create_router(ha=None)
self.assertTrue(router['ha'])
def test_ha_router_delete_with_distributed(self):
router = self._create_router(ha=True, distributed=True)
self.plugin.delete_router(self.admin_ctx, router['id'])
self.assertRaises(l3.RouterNotFound, self.plugin._get_router,
self.admin_ctx, router['id'])
def test_migration_from_ha(self):
router = self._create_router()
self.assertTrue(router['ha'])
@ -256,13 +265,44 @@ class L3HATestCase(L3HATestFramework):
router['id'],
ha=True)
def test_migrate_ha_router_to_distributed(self):
router = self._create_router()
def test_migrate_ha_router_to_distributed_and_ha(self):
router = self._create_router(ha=True, admin_state_up=False,
distributed=False)
self.assertTrue(router['ha'])
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self.assertRaises(l3_ext_ha_mode.DVRmodeUpdateOfHaNotSupported,
self._update_router,
router['id'],
ha=True,
distributed=True)
def test_migrate_ha_router_to_distributed_and_not_ha(self):
router = self._create_router(ha=True, admin_state_up=False,
distributed=False)
self.assertTrue(router['ha'])
self.assertRaises(l3_ext_ha_mode.DVRmodeUpdateOfHaNotSupported,
self._update_router,
router['id'],
ha=False,
distributed=True)
def test_migrate_dvr_router_to_ha_and_not_dvr(self):
router = self._create_router(ha=False, admin_state_up=False,
distributed=True)
self.assertTrue(router['distributed'])
self.assertRaises(l3_ext_ha_mode.HAmodeUpdateOfDvrNotSupported,
self._update_router,
router['id'],
ha=True,
distributed=True)
def test_migrate_dvr_router_to_ha_and_dvr(self):
router = self._create_router(ha=False, admin_state_up=False,
distributed=True)
self.assertTrue(router['distributed'])
self.assertRaises(l3_ext_ha_mode.HAmodeUpdateOfDvrNotSupported,
self._update_router,
router['id'],
ha=True,
distributed=True)
def test_migrate_distributed_router_to_ha(self):
@ -270,7 +310,7 @@ class L3HATestCase(L3HATestFramework):
self.assertFalse(router['ha'])
self.assertTrue(router['distributed'])
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self.assertRaises(l3_ext_ha_mode.HAmodeUpdateOfDvrNotSupported,
self._update_router,
router['id'],
ha=True)
@ -280,7 +320,7 @@ class L3HATestCase(L3HATestFramework):
self.assertFalse(router['ha'])
self.assertFalse(router['distributed'])
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self.assertRaises(l3_ext_ha_mode.UpdateToDvrHamodeNotSupported,
self._update_router,
router['id'],
ha=True,
@ -603,10 +643,10 @@ class L3HATestCase(L3HATestFramework):
self.assertEqual('active', routers[0][constants.HA_ROUTER_STATE_KEY])
def test_exclude_dvr_agents_for_ha_candidates(self):
"""Test dvr agents are not counted in the ha candidates.
"""Test dvr agents configured with "dvr" only, as opposed to "dvr_snat",
are excluded.
This test case tests that when get_number_of_agents_for_scheduling
is called, it doesn't count dvr agents.
is called, it does not count dvr only agents.
"""
# Test setup registers two l3 agents.
# Register another l3 agent with dvr mode and assert that
@ -616,6 +656,19 @@ class L3HATestCase(L3HATestFramework):
self.admin_ctx)
self.assertEqual(2, num_ha_candidates)
def test_include_dvr_snat_agents_for_ha_candidates(self):
"""Test dvr agents configured with "dvr_snat" are excluded.
This test case tests that when get_number_of_agents_for_scheduling
is called, it ounts dvr_snat agents.
"""
# Test setup registers two l3 agents.
# Register another l3 agent with dvr mode and assert that
# get_number_of_ha_agent_candidates return 2.
helpers.register_l3_agent('host_3', constants.L3_AGENT_MODE_DVR_SNAT)
num_ha_candidates = self.plugin.get_number_of_agents_for_scheduling(
self.admin_ctx)
self.assertEqual(3, num_ha_candidates)
def test_get_number_of_agents_for_scheduling_not_enough_agents(self):
cfg.CONF.set_override('min_l3_agents_per_router', 3)
helpers.kill_agent(helpers.register_l3_agent(host='l3host_3')['id'])

View File

@ -20,7 +20,6 @@ import uuid
import mock
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_utils import importutils
from oslo_utils import timeutils
import testscenarios
@ -28,11 +27,11 @@ import testscenarios
from neutron.common import constants
from neutron import context as n_context
from neutron.db import agents_db
from neutron.db import api as db_api
from neutron.db import common_db_mixin
from neutron.db import db_base_plugin_v2 as db_v2
from neutron.db import l3_agentschedulers_db
from neutron.db import l3_db
from neutron.db import l3_dvr_ha_scheduler_db
from neutron.db import l3_dvrscheduler_db
from neutron.db import l3_hamode_db
from neutron.db import l3_hascheduler_db
@ -272,37 +271,6 @@ class L3SchedulerBaseTestCase(base.BaseTestCase):
def test__bind_routers_ha_no_binding(self):
self._test__bind_routers_ha(has_binding=False)
def test_create_ha_port_and_bind_duplicate(self):
agent = agents_db.Agent(id='foo_agent')
context = n_context.get_admin_context()
with mock.patch.object(self.plugin, 'get_ha_network',
return_value=mock.Mock()),\
mock.patch.object(self.scheduler, 'bind_router') as bind,\
mock.patch.object(l3_agent_scheduler.LOG, 'debug') as flog,\
mock.patch.object(db_api, 'autonested_transaction',
side_effect=db_exc.DBDuplicateEntry):
self.scheduler.create_ha_port_and_bind(self.plugin, context,
'foo_router', 'test',
agent)
self.assertEqual(1, flog.call_count)
args, kwargs = flog.call_args
self.assertIn('already scheduled for agent', args[0])
bind.assert_called_once_with(context, 'foo_router', agent)
def test__bind_ha_router_to_agents(self):
agent = agents_db.Agent(id='foo_agent')
context = n_context.get_admin_context()
with mock.patch.object(self.plugin, 'get_ha_router_port_bindings',
return_value=[mock.Mock()]),\
mock.patch.object(db_api, 'autonested_transaction',
side_effect=db_exc.DBDuplicateEntry),\
mock.patch.object(l3_agent_scheduler.LOG, 'debug') as flog:
self.scheduler._bind_ha_router_to_agents(self.plugin, context,
'foo_router', [agent])
self.assertEqual(1, flog.call_count)
args, kwargs = flog.call_args
self.assertIn('already scheduled for agent', args[0])
def test__get_candidates_iterable_on_early_returns(self):
plugin = mock.MagicMock()
# non-distributed router already hosted
@ -709,6 +677,24 @@ class L3SchedulerTestBaseMixin(object):
self._check_get_l3_agent_candidates(
router, agent_list, HOST_DVR_SNAT, count=1)
def test_get_l3_agent_candidates_dvr_ha_snat_no_vms(self):
self._register_l3_dvr_agents()
router = self._make_router(self.fmt,
tenant_id=str(uuid.uuid4()),
name='r2')
router['external_gateway_info'] = None
router['id'] = str(uuid.uuid4())
router['distributed'] = True
router['ha'] = True
agent_list = [self.l3_dvr_snat_agent]
self.check_ports_exist_on_l3agent = mock.Mock(return_value=False)
# Test no VMs present case
self.check_ports_exist_on_l3agent.return_value = False
self.get_subnet_ids_on_router = mock.Mock(return_value=set())
self._check_get_l3_agent_candidates(
router, agent_list, HOST_DVR_SNAT, count=1)
def test_get_l3_agent_candidates_centralized(self):
self._register_l3_dvr_agents()
router = self._make_router(self.fmt,
@ -1784,3 +1770,19 @@ class L3AgentAZLeastRoutersSchedulerTestCase(L3HATestCaseMixin):
expected_hosts = set(['az1-host1', 'az3-host1', 'az3-host2'])
hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts)
class L3DVRHAPlugin(db_v2.NeutronDbPluginV2,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_dvr_ha_scheduler_db.L3_DVR_HA_scheduler_db_mixin):
pass
class L3DVRHATestCaseMixin(testlib_api.SqlTestCase,
L3SchedulerBaseMixin):
def setUp(self):
super(L3DVRHATestCaseMixin, self).setUp()
self.adminContext = n_context.get_admin_context()
self.plugin = L3DVRHAPlugin()

View File

@ -0,0 +1,14 @@
---
prelude: >
High Availability (HA) of SNAT service is supported
for Distributed Virtual Routers (DVRs).
features:
- High Availability support for SNAT services on Distributed
Virtual Routers. Routers can now be created with the flags
distributed=True and ha=True. The created routers will provide
Distributed Virtual Routing as well as SNAT high availability on the
l3 agents configured for dvr_snat mode.
issues:
- Only creation of dvr/ha routers is currently supported.
Upgrade from other types of routers to dvr/ha router is not
supported on this release.