diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py index 091a8745cb..55ee8d75ca 100644 --- a/vmware_nsx/db/db.py +++ b/vmware_nsx/db/db.py @@ -191,6 +191,11 @@ def delete_neutron_nsx_dhcp_binding(session, port_id, binding_id): port_id=port_id, nsx_binding_id=binding_id).delete() +def delete_neutron_nsx_dhcp_bindings_by_service_id(session, service_id): + return session.query(nsx_models.NeutronNsxDhcpBinding).filter_by( + nsx_service_id=service_id).delete() + + def get_nsx_switch_ids(session, neutron_id): # This function returns a list of NSX switch identifiers because of # the possibility of chained logical switches diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 58eb060021..b5a26c7396 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -931,6 +931,10 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # entries are still there. self._disable_native_dhcp(context, network['id']) + # Get existing ports on subnet. + existing_ports = super(NsxV3Plugin, self).get_ports( + context, filters={'network_id': [network['id']], + 'fixed_ips': {'subnet_id': [subnet['id']]}}) port_data = { "name": "", "admin_state_up": True, @@ -992,6 +996,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._dhcp_server.delete(dhcp_server['id']) self._cleanup_port(context, neutron_port['id'], nsx_port['id']) + # Configure existing ports to work with the new DHCP server + try: + for port_data in existing_ports: + self._add_dhcp_binding(context, port_data) + except Exception: + LOG.error(_LE('Unable to create DHCP bindings for existing ports ' + 'on subnet %s'), subnet['id']) + def _disable_native_dhcp(self, context, network_id): # Disable native DHCP service on the backend for this network. # First delete the DHCP port in this network. Then delete the @@ -1030,6 +1042,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Delete neutron_id -> dhcp_service_id mapping from the DB. nsx_db.delete_neutron_nsx_service_binding( context.session, network_id, nsxlib_consts.SERVICE_DHCP) + # Delete all DHCP bindings under this DHCP server from the DB. + nsx_db.delete_neutron_nsx_dhcp_bindings_by_service_id( + context.session, dhcp_service['nsx_service_id']) except db_exc.DBError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to delete DHCP server mapping for " diff --git a/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py b/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py index 999cae7823..815561f19d 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py @@ -380,6 +380,40 @@ class NsxNativeDhcpTestCase(test_plugin.NsxV3PluginTestCaseMixin): port['port']['mac_address'], ip, hostname, cfg.CONF.nsx_v3.dhcp_lease_time, options) + def test_dhcp_binding_with_disable_enable_dhcp(self): + # Test if DHCP binding is preserved after DHCP is disabled and + # re-enabled on a subnet. + with self.subnet(enable_dhcp=True) as subnet: + device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'None' + device_id = uuidutils.generate_uuid() + with self.port(subnet=subnet, device_owner=device_owner, + device_id=device_id) as port: + ip = port['port']['fixed_ips'][0]['ip_address'] + dhcp_bindings = nsx_db.get_nsx_dhcp_bindings( + context.get_admin_context().session, port['port']['id']) + dhcp_service = dhcp_bindings[0]['nsx_service_id'] + self.assertEqual(1, len(dhcp_bindings)) + self.assertEqual(ip, dhcp_bindings[0]['ip_address']) + # Disable DHCP on subnet. + data = {'subnet': {'enable_dhcp': False}} + self.plugin.update_subnet(context.get_admin_context(), + subnet['subnet']['id'], data) + dhcp_bindings = nsx_db.get_nsx_dhcp_bindings( + context.get_admin_context().session, port['port']['id']) + self.assertEqual([], dhcp_bindings) + # Re-enable DHCP on subnet. + data = {'subnet': {'enable_dhcp': True}} + self.plugin.update_subnet(context.get_admin_context(), + subnet['subnet']['id'], data) + dhcp_bindings = nsx_db.get_nsx_dhcp_bindings( + context.get_admin_context().session, port['port']['id']) + self.assertEqual(1, len(dhcp_bindings)) + self.assertEqual(ip, dhcp_bindings[0]['ip_address']) + # The DHCP service ID should be different because a new + # logical DHCP server is created for re-enabling DHCP. + self.assertNotEqual(dhcp_service, + dhcp_bindings[0]['nsx_service_id']) + def test_dhcp_binding_with_delete_port(self): # Test if DHCP binding is removed when the associated compute port # is deleted.