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
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)
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 []
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
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)
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'])
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'])
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'])
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)
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):
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)
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)