Merge "HA for DVR - Neutron Server side code changes"
This commit is contained in:
commit
5b7fd5f0f1
neutron
db
extensions
services/l3_router
tests
functional/services/l3_router
unit
releasenotes/notes
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…
x
Reference in New Issue
Block a user