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:
parent
5d7ffc03ed
commit
3f0c618cfd
46
neutron/db/l3_dvr_ha_scheduler_db.py
Normal file
46
neutron/db/l3_dvr_ha_scheduler_db.py
Normal 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
|
@ -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)
|
||||
|
@ -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.")
|
||||
|
@ -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.
|
||||
|
@ -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')
|
@ -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
|
||||
"""
|
||||
|
@ -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'])
|
||||
|
@ -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()
|
||||
|
14
releasenotes/notes/dvr-ha-support-cc67e84d9380cd0b.yaml
Normal file
14
releasenotes/notes/dvr-ha-support-cc67e84d9380cd0b.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user