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' ROUTER_TYPE = 'router_type'
EXTENDED_ATTRIBUTES_2_0 = { EXTENDED_ATTRIBUTES_2_0 = {
'routers': { 'routers': {
ROUTER_TYPE: {'allow_post': True, 'allow_put': False, ROUTER_TYPE: {'allow_post': True, 'allow_put': True,
'validate': {'type:values': ['shared', 'exclusive']}, 'validate': {'type:values': ['shared', 'exclusive']},
'default': attributes.ATTR_NOT_SPECIFIED, 'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True}, 'is_visible': True},

View File

@ -16,6 +16,9 @@ import abc
import six import six
from neutron.db import l3_db
from neutron.db import models_v2
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class RouterAbstractDriver(object): class RouterAbstractDriver(object):
@ -65,3 +68,15 @@ class RouterBaseDriver(RouterAbstractDriver):
self.plugin = plugin self.plugin = plugin
self.nsx_v = plugin.nsx_v self.nsx_v = plugin.nsx_v
self.edge_manager = plugin.edge_manager 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']) context, router_id, self.get_type(), r['admin_state_up'])
return self.plugin.get_router(context, router_id) 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): def delete_router(self, context, router_id):
self.edge_manager.delete_lrouter(context, router_id, dist=False) self.edge_manager.delete_lrouter(context, router_id, dist=False)
if self.plugin.metadata_proxy_handler: if self.plugin.metadata_proxy_handler:
@ -76,7 +131,7 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
self.plugin._update_routes(context, router_id, nexthop) self.plugin._update_routes(context, router_id, nexthop)
def _update_router_gw_info(self, context, router_id, info, 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) router = self.plugin._get_router(context, router_id)
org_ext_net_id = router.gw_port_id and router.gw_port.network_id org_ext_net_id = router.gw_port_id and router.gw_port.network_id
org_enable_snat = router.enable_snat 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) edge_id = self._get_router_edge_id(context, router_id)
with locking.LockManager.get_lock(edge_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 # network changed, so need to remove default gateway before
# vnic can be configured # vnic can be configured
LOG.debug("Delete default gateway %s", orgnexthop) LOG.debug("Delete default gateway %s", orgnexthop)
edge_utils.clear_gateway(self.nsx_v, context, router_id) edge_utils.clear_gateway(self.nsx_v, context, router_id)
# Update external vnic if addr or mask is changed # 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( edge_utils.update_external_interface(
self.nsx_v, context, router_id, self.nsx_v, context, router_id,
new_ext_net_id, newaddr, newmask) new_ext_net_id, newaddr, newmask)
@ -111,12 +167,13 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver):
# or ext net not changed but snat is changed. # or ext net not changed but snat is changed.
if (new_ext_net_id != org_ext_net_id or if (new_ext_net_id != org_ext_net_id or
(new_ext_net_id == org_ext_net_id and (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) self.plugin._update_nat_rules(context, router)
if (new_ext_net_id != org_ext_net_id or if (new_ext_net_id != org_ext_net_id or
new_enable_snat != org_enable_snat 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) self.plugin._update_subnets_and_dnat_firewall(context, router)
# Update static routes in all. # Update static routes in all.

View File

@ -77,6 +77,28 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
r['admin_state_up']) r['admin_state_up'])
return self.plugin.get_router(context, router_id) 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): def delete_router(self, context, router_id):
pass pass
@ -369,18 +391,6 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
intf_num = len(router_net_ids) intf_num = len(router_net_ids)
return (conflict_network_ids, conflict_router_ids, intf_num) 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): def _get_conflict_network_ids_by_ext_net(self, context, router_id):
"""Collect conflicting networks based on external network. """Collect conflicting networks based on external network.
Collect conflicting networks which has overlapping subnet with the 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']: if r['distributed'] != router['router']['distributed']:
err_msg = _('Unable to update distributed mode') err_msg = _('Unable to update distributed mode')
raise n_exc.InvalidInput(error_message=err_msg) 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) router_driver = self._find_router_driver(context, router_id)
return router_driver.update_router(context, router_id, router) 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): def test_create_router_gateway_fails(self):
self.skipTest('not supported') 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, class ExtGwModeTestCase(NsxVPluginV2TestCase,
test_ext_gw_mode.ExtGwModeIntTestCase): test_ext_gw_mode.ExtGwModeIntTestCase):
@ -3316,3 +3338,20 @@ class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase,
context.get_admin_context(), r1['router']['id'])) context.get_admin_context(), r1['router']['id']))
self.assertIn(r2['router']['id'], conflict_router_ids) self.assertIn(r2['router']['id'], conflict_router_ids)
self.assertEqual(0, len(available_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'])