Support migrating of legacy routers to HA and back

This patch adds support for migration of legacy routers to HA and
vice-versa. This patch also:

1. Reverts I4171ab481e3943e0110bd9a300d965bbebe44871, which was used to
   disable such migrations until support was inserted to the codebase.
2. Adds an exception to indicate that such migrations are only available
   on routers that have their admin_state_up set to False.

Closes-Bug: #1365426
DocImpact (Handled in patch 233695)
Change-Id: Ie92f8033f47e1bf9ba6310373b3bfc9833317580
This commit is contained in:
John Schwarz 2015-10-12 16:54:17 +03:00 committed by Assaf Muller
parent 9da4dfae56
commit 416c76bc6e
3 changed files with 92 additions and 14 deletions

View File

@ -23,6 +23,7 @@ from sqlalchemy import orm
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron.common import utils as n_utils
from neutron.db import agents_db
from neutron.db import l3_dvr_db
@ -405,28 +406,47 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin):
def _update_router_db(self, context, router_id, data, gw_info):
router_db = self._get_router(context, router_id)
original_distributed_state = router_db.extra_attributes.distributed
original_ha_state = router_db.extra_attributes.ha
if original_ha_state and data.get('distributed'):
requested_ha_state = data.pop('ha', None)
requested_distributed_state = data.get('distributed', None)
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()
with context.session.begin(subtransactions=True):
router_db = super(L3_HA_NAT_db_mixin, self)._update_router_db(
context, router_id, data, gw_info)
ha = data.pop('ha', None)
ha_not_changed = ha is None or ha == original_ha_state
ha_not_changed = (requested_ha_state is None or
requested_ha_state == original_ha_state)
if ha_not_changed:
return router_db
if router_db.admin_state_up:
msg = _('Cannot change HA attribute of active routers. Please '
'set router admin_state_up to False prior to upgrade.')
raise n_exc.BadRequest(resource='router', msg=msg)
ha_network = self.get_ha_network(context,
router_db.tenant_id)
router_db.extra_attributes.ha = ha
if not ha:
router_db.extra_attributes.ha = requested_ha_state
if not requested_ha_state:
self._delete_vr_id_allocation(
context, ha_network, router_db.extra_attributes.ha_vr_id)
router_db.extra_attributes.ha_vr_id = None
if ha:
# The HA attribute has changed. First unbind the router from agents
# to force a proper re-scheduling to agents.
# TODO(jschwarz): This will have to be more selective to get HA + DVR
# working (Only unbind from dvr_snat nodes).
self._unbind_ha_router(context, router_id)
if requested_ha_state:
if not ha_network:
ha_network = self._create_ha_network(context,
router_db.tenant_id)
@ -452,6 +472,10 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin):
context, ha_network, router_db.extra_attributes.ha_vr_id)
self._delete_ha_interfaces(context, router_db.id)
def _unbind_ha_router(self, context, router_id):
for agent in self.get_l3_agents_hosting_routers(context, [router_id]):
self.remove_router_from_l3_agent(context, agent['id'], router_id)
def get_ha_router_port_bindings(self, context, router_ids, host=None):
if not router_ids:
return []

View File

@ -21,7 +21,7 @@ from neutron.common import exceptions
HA_INFO = 'ha'
EXTENDED_ATTRIBUTES_2_0 = {
'routers': {
HA_INFO: {'allow_post': True, 'allow_put': False,
HA_INFO: {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED, 'is_visible': True,
'enforce_policy': True,
'convert_to': attributes.convert_to_boolean_if_not_none}

View File

@ -19,6 +19,7 @@ from oslo_utils import uuidutils
from neutron.api.rpc.handlers import l3_rpc
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron import context
from neutron.db import agents_db
from neutron.db import common_db_mixin
@ -72,12 +73,20 @@ class L3HATestFramework(testlib_api.SqlTestCase):
router['distributed'] = distributed
return self.plugin.create_router(ctx, {'router': router})
def _update_router(self, router_id, ha=None, distributed=None, ctx=None):
def _migrate_router(self, router_id, ha):
self._update_router(router_id, admin_state=False)
self._update_router(router_id, ha=ha)
return self._update_router(router_id, admin_state=True)
def _update_router(self, router_id, ha=None, distributed=None, ctx=None,
admin_state=None):
if ctx is None:
ctx = self.admin_ctx
data = {'ha': ha} if ha is not None else {}
if distributed is not None:
data['distributed'] = distributed
if admin_state is not None:
data['admin_state_up'] = admin_state
return self.plugin._update_router_db(ctx, router_id,
data, None)
@ -190,7 +199,7 @@ class L3HATestCase(L3HATestFramework):
router = self._create_router()
self.assertTrue(router['ha'])
router = self._update_router(router['id'], ha=False)
router = self._migrate_router(router['id'], False)
self.assertFalse(router.extra_attributes['ha'])
self.assertIsNone(router.extra_attributes['ha_vr_id'])
@ -198,10 +207,17 @@ class L3HATestCase(L3HATestFramework):
router = self._create_router(ha=False)
self.assertFalse(router['ha'])
router = self._update_router(router['id'], ha=True)
router = self._migrate_router(router['id'], True)
self.assertTrue(router.extra_attributes['ha'])
self.assertIsNotNone(router.extra_attributes['ha_vr_id'])
def test_migration_requires_admin_state_down(self):
router = self._create_router(ha=False)
self.assertRaises(n_exc.BadRequest,
self._update_router,
router['id'],
ha=True)
def test_migrate_ha_router_to_distributed(self):
router = self._create_router()
self.assertTrue(router['ha'])
@ -211,6 +227,44 @@ class L3HATestCase(L3HATestFramework):
router['id'],
distributed=True)
def test_migrate_distributed_router_to_ha(self):
router = self._create_router(ha=False, distributed=True)
self.assertFalse(router['ha'])
self.assertTrue(router['distributed'])
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self._update_router,
router['id'],
ha=True)
def test_migrate_legacy_router_to_distributed_and_ha(self):
router = self._create_router(ha=False, distributed=False)
self.assertFalse(router['ha'])
self.assertFalse(router['distributed'])
self.assertRaises(l3_ext_ha_mode.DistributedHARouterNotSupported,
self._update_router,
router['id'],
ha=True,
distributed=True)
def test_unbind_ha_router(self):
router = self._create_router()
self._bind_router(router['id'])
bound_agents = self.plugin.get_l3_agents_hosting_routers(
self.admin_ctx, [router['id']])
self.assertEqual(2, len(bound_agents))
with mock.patch.object(manager.NeutronManager,
'get_service_plugins') as mock_manager:
self.plugin._unbind_ha_router(self.admin_ctx, router['id'])
bound_agents = self.plugin.get_l3_agents_hosting_routers(
self.admin_ctx, [router['id']])
self.assertEqual(0, len(bound_agents))
self.assertEqual(2, mock_manager.call_count)
def test_l3_agent_routers_query_interface(self):
router = self._create_router()
self._bind_router(router['id'])
@ -251,7 +305,7 @@ class L3HATestCase(L3HATestFramework):
else:
self.assertIsNotNone(interface)
self._update_router(router['id'], to_ha)
self._migrate_router(router['id'], to_ha)
routers = self.plugin.get_ha_sync_data_for_host(self.admin_ctx)
router = routers[0]
interface = router.get(constants.HA_INTERFACE_KEY)
@ -273,7 +327,7 @@ class L3HATestCase(L3HATestFramework):
def test_update_router_to_ha_notifies_agent(self):
router = self._create_router(ha=False)
self.notif_m.reset_mock()
self._update_router(router['id'], ha=True)
self._migrate_router(router['id'], True)
self.assertTrue(self.notif_m.called)
def test_unique_vr_id_between_routers(self):
@ -333,7 +387,7 @@ class L3HATestCase(L3HATestFramework):
allocs_before = self.plugin._get_allocated_vr_id(self.admin_ctx,
network.network_id)
router = self._create_router()
self._update_router(router['id'], ha=False)
self._migrate_router(router['id'], False)
allocs_after = self.plugin._get_allocated_vr_id(self.admin_ctx,
network.network_id)
self.assertEqual(allocs_before, allocs_after)
@ -617,7 +671,7 @@ class L3HAUserTestCase(L3HATestFramework):
def test_update_router(self):
router = self._create_router(ctx=self.user_ctx)
self._update_router(router['id'], ha=False, ctx=self.user_ctx)
self._update_router(router['id'], ctx=self.user_ctx)
def test_delete_router(self):
router = self._create_router(ctx=self.user_ctx)