From 1a1e02114910becff04d3d862401fb480e2f13de Mon Sep 17 00:00:00 2001 From: asarfaty Date: Sun, 14 Feb 2016 12:12:06 +0200 Subject: [PATCH] NSXv - allow changing the router type exclusive <-> shared. APIImpact Change-Id: I5b91498365ab4bd50b7b1deff8b5397d95eeb1ee --- vmware_nsx/extensions/routertype.py | 2 +- .../nsx_v/drivers/abstract_router_driver.py | 15 +++++ .../nsx_v/drivers/exclusive_router_driver.py | 67 +++++++++++++++++-- .../nsx_v/drivers/shared_router_driver.py | 34 ++++++---- vmware_nsx/plugins/nsx_v/plugin.py | 37 ++++++++++ vmware_nsx/tests/unit/nsx_v/test_plugin.py | 39 +++++++++++ 6 files changed, 176 insertions(+), 18 deletions(-) diff --git a/vmware_nsx/extensions/routertype.py b/vmware_nsx/extensions/routertype.py index f42c6b902b..ed7cebb6cc 100644 --- a/vmware_nsx/extensions/routertype.py +++ b/vmware_nsx/extensions/routertype.py @@ -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}, diff --git a/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py index ec761434ef..0030b95454 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py @@ -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'] \ No newline at end of file diff --git a/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py index c73406b209..810aaa6abe 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py @@ -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. diff --git a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py index 34b190c737..28cec7a010 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py @@ -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 diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index f8e5bc388c..fb5c1f9c82 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -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) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 5905da3bc6..688dbaa899 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -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'])