NSXv - allow changing the router type exclusive <-> shared. APIImpact
Change-Id: I5b91498365ab4bd50b7b1deff8b5397d95eeb1ee
This commit is contained in:
parent
7b216ebf78
commit
1a1e021149
@ -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},
|
||||
|
@ -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']
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'])
|
||||
|
Loading…
Reference in New Issue
Block a user