NSXv - allow changing the router type exclusive <-> shared. APIImpact

Change-Id: I5b91498365ab4bd50b7b1deff8b5397d95eeb1ee
This commit is contained in:
asarfaty 2016-02-14 12:12:06 +02:00
parent 7b216ebf78
commit 1a1e021149
6 changed files with 176 additions and 18 deletions

View File

@ -19,7 +19,7 @@ from neutron.api.v2 import attributes
ROUTER_TYPE = 'router_type'
EXTENDED_ATTRIBUTES_2_0 = {
'routers': {
ROUTER_TYPE: {'allow_post': True, 'allow_put': False,
ROUTER_TYPE: {'allow_post': True, 'allow_put': True,
'validate': {'type:values': ['shared', 'exclusive']},
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True},

View File

@ -16,6 +16,9 @@ import abc
import six
from neutron.db import l3_db
from neutron.db import models_v2
@six.add_metaclass(abc.ABCMeta)
class RouterAbstractDriver(object):
@ -65,3 +68,15 @@ class RouterBaseDriver(RouterAbstractDriver):
self.plugin = plugin
self.nsx_v = plugin.nsx_v
self.edge_manager = plugin.edge_manager
def _get_external_network_id_by_router(self, context, router_id):
"""Get router's external network id if it has."""
router = self.plugin.get_router(context, router_id)
ports_qry = context.session.query(models_v2.Port)
gw_ports = ports_qry.filter_by(
device_id=router_id,
device_owner=l3_db.DEVICE_OWNER_ROUTER_GW,
id=router['gw_port_id']).all()
if gw_ports:
return gw_ports[0]['network_id']

View File

@ -64,6 +64,61 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
context, router_id, self.get_type(), r['admin_state_up'])
return self.plugin.get_router(context, router_id)
def detach_router(self, context, router_id, router):
LOG.debug("Detach exclusive router id %s", router_id)
self.edge_manager.unbind_router_on_edge(context, router_id)
metadata_proxy_handler = self.plugin.metadata_proxy_handler
if metadata_proxy_handler:
metadata_proxy_handler.cleanup_router_edge(router_id)
def _build_router_data_from_db(self, router_db, router):
if 'status' not in router['router']:
router['router']['status'] = router_db.status
if 'name' not in router['router']:
router['router']['name'] = router_db.name
if 'admin_state_up' not in router['router']:
router['router']['admin_state_up'] = router_db.admin_state_up
if 'tenant_id' not in router['router']:
router['router']['tenant_id'] = router_db.tenant_id
if 'id' not in router['router']:
router['router']['id'] = router_db.id
def attach_router(self, context, router_id, router, appliance_size=None):
router_db = self.plugin._get_router(context, router_id)
# Add DB attributes to the router data structure
# before creating it as an exclusive router
self._build_router_data_from_db(router_db, router)
self.create_router(context,
router['router'],
allow_metadata=False,
appliance_size=appliance_size)
edge_id = edge_utils.get_router_edge_id(context, router_id)
LOG.debug("Exclusive router %s attached to edge %s",
router_id, edge_id)
# add all internal interfaces of the router on edge
intf_net_ids = (
self.plugin._get_internal_network_ids_by_router(context,
router_id))
for network_id in intf_net_ids:
address_groups = self.plugin._get_address_groups(
context, router_id, network_id)
edge_utils.update_internal_interface(
self.nsx_v, context, router_id, network_id,
address_groups, router_db.admin_state_up)
# Update external interface (which also update nat rules, routes, etc)
external_net_id = self._get_external_network_id_by_router(context,
router_id)
gw_info = None
if (external_net_id):
gw_info = {'network_id': external_net_id}
self._update_router_gw_info(
context, router_id, gw_info, force_update=True)
def delete_router(self, context, router_id):
self.edge_manager.delete_lrouter(context, router_id, dist=False)
if self.plugin.metadata_proxy_handler:
@ -76,7 +131,7 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
self.plugin._update_routes(context, router_id, nexthop)
def _update_router_gw_info(self, context, router_id, info,
is_routes_update=False):
is_routes_update=False, force_update=False):
router = self.plugin._get_router(context, router_id)
org_ext_net_id = router.gw_port_id and router.gw_port.network_id
org_enable_snat = router.enable_snat
@ -95,14 +150,15 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
edge_id = self._get_router_edge_id(context, router_id)
with locking.LockManager.get_lock(edge_id):
if new_ext_net_id != org_ext_net_id and orgnexthop:
if ((new_ext_net_id != org_ext_net_id or force_update)
and orgnexthop):
# network changed, so need to remove default gateway before
# vnic can be configured
LOG.debug("Delete default gateway %s", orgnexthop)
edge_utils.clear_gateway(self.nsx_v, context, router_id)
# Update external vnic if addr or mask is changed
if orgaddr != newaddr or orgmask != newmask:
if orgaddr != newaddr or orgmask != newmask or force_update:
edge_utils.update_external_interface(
self.nsx_v, context, router_id,
new_ext_net_id, newaddr, newmask)
@ -111,12 +167,13 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
# or ext net not changed but snat is changed.
if (new_ext_net_id != org_ext_net_id or
(new_ext_net_id == org_ext_net_id and
new_enable_snat != org_enable_snat)):
new_enable_snat != org_enable_snat) or
force_update):
self.plugin._update_nat_rules(context, router)
if (new_ext_net_id != org_ext_net_id or
new_enable_snat != org_enable_snat or
is_routes_update):
is_routes_update or force_update):
self.plugin._update_subnets_and_dnat_firewall(context, router)
# Update static routes in all.

View File

@ -77,6 +77,28 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
r['admin_state_up'])
return self.plugin.get_router(context, router_id)
def detach_router(self, context, router_id, router):
LOG.debug("Detach shared router id %s", router_id)
# if it is the last shared router on this adge - add it to the pool
edge_id = edge_utils.get_router_edge_id(context, router_id)
if not edge_id:
return
self._remove_router_services_on_edge(context, router_id)
self._unbind_router_on_edge(context, router_id)
def attach_router(self, context, router_id, router, appliance_size=None):
# find the right place to add, and create a new one if necessary
router_db = self.plugin._get_router(context, router_id)
self._bind_router_on_available_edge(
context, router_id, router_db.admin_state_up)
edge_id = edge_utils.get_router_edge_id(context, router_id)
LOG.debug("Shared router %s attached to edge %s", router_id, edge_id)
with locking.LockManager.get_lock(
str(edge_id),
lock_file_prefix=NSXV_ROUTER_RECONFIG):
self._add_router_services_on_available_edge(context, router_id)
def delete_router(self, context, router_id):
pass
@ -369,18 +391,6 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
intf_num = len(router_net_ids)
return (conflict_network_ids, conflict_router_ids, intf_num)
def _get_external_network_id_by_router(self, context, router_id):
"""Get router's external network id if it has."""
router = self.plugin.get_router(context, router_id)
ports_qry = context.session.query(models_v2.Port)
gw_ports = ports_qry.filter_by(
device_id=router_id,
device_owner=l3_db.DEVICE_OWNER_ROUTER_GW,
id=router['gw_port_id']).all()
if gw_ports:
return gw_ports[0]['network_id']
def _get_conflict_network_ids_by_ext_net(self, context, router_id):
"""Collect conflicting networks based on external network.
Collect conflicting networks which has overlapping subnet with the

View File

@ -1493,6 +1493,43 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
if r['distributed'] != router['router']['distributed']:
err_msg = _('Unable to update distributed mode')
raise n_exc.InvalidInput(error_message=err_msg)
# Toggling router type is supported only for non-distributed router
elif 'router_type' in router['router']:
r = self.get_router(context, router_id)
if r['router_type'] != router['router']['router_type']:
if r["distributed"]:
err_msg = _('Unable to update distributed mode')
raise n_exc.InvalidInput(error_message=err_msg)
else:
# should migrate the router because its type changed
new_router_type = router['router']['router_type']
self._validate_router_size(router)
# remove the router from the old pool, and free resources
old_router_driver = \
self._router_managers.get_tenant_router_driver(
context, r['router_type'])
old_router_driver.detach_router(context, router_id, router)
# update the router-type
with context.session.begin(subtransactions=True):
router_db = self._get_router(context, router_id)
self._process_nsx_router_create(
context, router_db, router['router'])
# add the router to the new pool
appliance_size = router['router'].get(ROUTER_SIZE)
new_router_driver = \
self._router_managers.get_tenant_router_driver(
context, new_router_type)
new_router_driver.attach_router(
context,
router_id,
router,
appliance_size=appliance_size)
# continue to update the router with the new driver
router_driver = self._find_router_driver(context, router_id)
return router_driver.update_router(context, router_id, router)

View File

@ -2293,6 +2293,28 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
def test_create_router_gateway_fails(self):
self.skipTest('not supported')
def test_migrate_exclusive_router_to_shared(self):
with self._create_l3_ext_network() as net:
with self.subnet(network=net, enable_dhcp=False) as s:
data = {'router': {'tenant_id': 'whatever'}}
data['router']['name'] = 'router1'
data['router']['external_gateway_info'] = {
'network_id': s['subnet']['network_id']}
data['router']['router_type'] = 'exclusive'
router_req = self.new_create_request('routers', data,
self.fmt)
res = router_req.get_response(self.ext_api)
router = self.deserialize(self.fmt, res)
# update the router type:
router_id = router['router']['id']
self._update('routers', router_id,
{'router': {'router_type': 'shared'}})
# get the updated router and check it's type
body = self._show('routers', router_id)
self.assertEqual('shared', body['router']['router_type'])
class ExtGwModeTestCase(NsxVPluginV2TestCase,
test_ext_gw_mode.ExtGwModeIntTestCase):
@ -3316,3 +3338,20 @@ class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase,
context.get_admin_context(), r1['router']['id']))
self.assertIn(r2['router']['id'], conflict_router_ids)
self.assertEqual(0, len(available_router_ids))
def test_migrate_shared_router_to_exclusive(self):
with self.router(name='r7') as r1, \
self.subnet(cidr='11.0.0.0/24') as s1:
self._router_interface_action('add',
r1['router']['id'],
s1['subnet']['id'],
None)
# update the router type:
router_id = r1['router']['id']
self._update('routers', router_id,
{'router': {'router_type': 'exclusive'}})
# get the updated router and check it's type
body = self._show('routers', router_id)
self.assertEqual('exclusive', body['router']['router_type'])