diff -crB --new-file neutron-9.4.2.dev21-backup/neutron/agent/l3/agent.py neutron-9.4.2.dev21/neutron/agent/l3/agent.py
*** neutron-9.4.2.dev21-backup/neutron/agent/l3/agent.py	2019-03-11 10:22:22.038936299 -0700
--- neutron-9.4.2.dev21/neutron/agent/l3/agent.py	2019-03-11 23:23:31.067486725 -0700
***************
*** 326,336 ****
              kwargs['host'] = self.host
  
          if router.get('distributed') and router.get('ha'):
!             # if the router does not contain information about the HA interface
!             # this means that this DVR+HA router needs to host only the edge
!             # side of it, typically because it's landing on a node that needs
!             # to provision a router namespace because of a DVR service port
!             # (e.g. DHCP).
              if (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT
                      and router.get(lib_const.HA_INTERFACE_KEY) is not None):
                  kwargs['state_change_callback'] = self.enqueue_state_change
--- 326,340 ----
              kwargs['host'] = self.host
  
          if router.get('distributed') and router.get('ha'):
!             # Case 1: If the router contains information about the HA interface
!             # and if the requesting agent is a DVR_SNAT agent then go ahead
!             # and create a HA router.
!             # Case 2: If the router does not contain information about the HA
!             # interface this means that this DVR+HA router needs to host only
!             # the edge side of it, typically because it's landing on a node
!             # that needs to provision a router namespace because of a DVR
!             # service port (e.g. DHCP). So go ahead and create a regular DVR
!             # edge router.
              if (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT
                      and router.get(lib_const.HA_INTERFACE_KEY) is not None):
                  kwargs['state_change_callback'] = self.enqueue_state_change
***************
*** 474,492 ****
  
      def _process_updated_router(self, router):
          ri = self.router_info[router['id']]
!         is_dvr_only_agent = (
!             self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR)
!         is_ha_router = getattr(ri, 'ha_state', None) is not None
!         # For HA routers check that DB state matches actual state
!         if router.get('ha') and not is_dvr_only_agent and is_ha_router:
!             self.check_ha_state_for_router(
!                 router['id'], router.get(l3_constants.HA_ROUTER_STATE_KEY))
!         ri.router = router
!         registry.notify(resources.ROUTER, events.BEFORE_UPDATE,
!                         self, router=ri)
!         ri.process(self)
!         registry.notify(resources.ROUTER, events.AFTER_UPDATE, self, router=ri)
!         self.l3_ext_manager.update_router(self.context, router)
  
      def _resync_router(self, router_update,
                         priority=queue.PRIORITY_SYNC_ROUTERS_TASK):
--- 478,515 ----
  
      def _process_updated_router(self, router):
          ri = self.router_info[router['id']]
!         is_dvr_snat_agent = (self.conf.agent_mode ==
!                              lib_const.L3_AGENT_MODE_DVR_SNAT)
!         is_dvr_only_agent = (self.conf.agent_mode ==
!                              lib_const.L3_AGENT_MODE_DVR)
!         old_router_ha_interface = ri.router.get(lib_const.HA_INTERFACE_KEY)
!         current_router_ha_interface = router.get(lib_const.HA_INTERFACE_KEY)
!         ha_interface_change = ((old_router_ha_interface is None and
!                                 current_router_ha_interface is not None) or
!                                (old_router_ha_interface is not None and
!                                 current_router_ha_interface is None))
!         is_dvr_ha_router = router.get('distributed') and router.get('ha')
! 
!         if is_dvr_snat_agent and is_dvr_ha_router and ha_interface_change:
!             LOG.debug("Removing HA router %s, since it is not bound to "
!                       "the current agent, and recreating regular DVR router "
!                       "based on service port requirements.",
!                       router['id'])
!             if self._safe_router_removed(router['id']):
!                 self._process_added_router(router)
!         else:
!             is_ha_router = getattr(ri, 'ha_state', None) is not None
!             # For HA routers check that DB state matches actual state
!             if router.get('ha') and not is_dvr_only_agent and is_ha_router:
!                 self.check_ha_state_for_router(
!                     router['id'], router.get(l3_constants.HA_ROUTER_STATE_KEY))
!             ri.router = router
!             registry.notify(resources.ROUTER, events.BEFORE_UPDATE,
!                             self, router=ri)
!             ri.process(self)
!             registry.notify(
!                 resources.ROUTER, events.AFTER_UPDATE, self, router=ri)
!             self.l3_ext_manager.update_router(self.context, router)
  
      def _resync_router(self, router_update,
                         priority=queue.PRIORITY_SYNC_ROUTERS_TASK):
diff -crB --new-file neutron-9.4.2.dev21-backup/neutron/tests/functional/agent/l3/test_dvr_router.py neutron-9.4.2.dev21/neutron/tests/functional/agent/l3/test_dvr_router.py
*** neutron-9.4.2.dev21-backup/neutron/tests/functional/agent/l3/test_dvr_router.py	2019-03-11 10:22:21.898931467 -0700
--- neutron-9.4.2.dev21/neutron/tests/functional/agent/l3/test_dvr_router.py	2019-03-11 23:30:24.789753390 -0700
***************
*** 1054,1059 ****
--- 1054,1096 ----
          self._assert_no_ip_addresses_on_interface(namespace,
                                                    ex_gw_port_name)
  
+     def test_dvr_ha_router_unbound_from_agents(self):
+         self._setup_dvr_ha_agents()
+         self._setup_dvr_ha_bridges()
+ 
+         router1 = self._create_dvr_ha_router(
+             self.agent, enable_gw=True)
+         router2 = self._create_dvr_ha_router(
+             self.failover_agent, enable_gw=True)
+ 
+         utils.wait_until_true(lambda: router1.ha_state == 'master')
+         utils.wait_until_true(lambda: router2.ha_state == 'backup')
+ 
+         self._assert_ip_addresses_in_dvr_ha_snat_namespace(router1)
+         self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router2)
+         router1_ha_device = router1.get_ha_device_name()
+         router2_ha_device = router2.get_ha_device_name()
+         self.assertTrue(
+             ip_lib.device_exists(router1_ha_device, router1.ha_namespace))
+         self.assertTrue(
+             ip_lib.device_exists(router2_ha_device, router2.ha_namespace))
+ 
+         router1.router['_ha_interface'] = None
+         self.agent._process_updated_router(router1.router)
+         router_updated = self.agent.router_info[router1.router_id]
+ 
+         self.assertTrue(self._namespace_exists(router_updated.ns_name))
+         self._assert_snat_namespace_exists(router_updated)
+         snat_namespace_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
+             router_updated.router_id)
+         self.assertFalse(
+             ip_lib.device_exists(router1_ha_device, snat_namespace_name))
+ 
+         utils.wait_until_true(lambda: router2.ha_state == 'master')
+         self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2)
+         self.assertTrue(
+             ip_lib.device_exists(router2_ha_device, router2.ha_namespace))
+ 
      def _test_dvr_ha_router_failover(self, enable_gw):
          self._setup_dvr_ha_agents()
          self._setup_dvr_ha_bridges()
diff -crB --new-file neutron-9.4.2.dev21-backup/neutron/tests/unit/agent/l3/test_agent.py neutron-9.4.2.dev21/neutron/tests/unit/agent/l3/test_agent.py
*** neutron-9.4.2.dev21-backup/neutron/tests/unit/agent/l3/test_agent.py	2019-03-11 10:22:21.898931467 -0700
--- neutron-9.4.2.dev21/neutron/tests/unit/agent/l3/test_agent.py	2019-03-11 23:28:45.496798233 -0700
***************
*** 2242,2247 ****
--- 2242,2308 ----
      def test_process_routers_update_router_deleted_error(self):
          self._test_process_routers_update_router_deleted(True)
  
+     def test_process_dvr_routers_ha_on_update_when_router_unbound(self):
+         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+         agent.conf.agent_mode = 'dvr_snat'
+         router = mock.Mock()
+         router.id = '1234'
+         router.distributed = True
+         router.ha = True
+         router_info = mock.MagicMock()
+         agent.router_info[router.id] = router_info
+         updated_router = {'id': '1234',
+                           'distributed': True,
+                           'ha': True,
+                           'external_gateway_info': {},
+                           'routes': [],
+                           'admin_state_up': True}
+ 
+         self.plugin_api.get_routers.return_value = [updated_router]
+         update = router_processing_queue.RouterUpdate(
+             updated_router['id'], router_processing_queue.PRIORITY_RPC,
+             router=updated_router)
+         agent._queue.add(update)
+         with mock.patch.object(agent,
+                                "_safe_router_removed"
+                                ) as router_remove,\
+             mock.patch.object(agent,
+                               "_process_added_router"
+                               ) as add_router:
+             agent._process_router_if_compatible(updated_router)
+             router_remove.assert_called_once_with(updated_router['id'])
+             add_router.assert_called_once_with(updated_router)
+ 
+     def test_process_dvr_routers_ha_on_update_without_ha_interface(self):
+         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+         agent.conf.agent_mode = 'dvr_snat'
+         router = mock.Mock()
+         router.id = '1234'
+         router.distributed = True
+         router._ha_interface = True
+         router.ha = True
+         router_info = mock.MagicMock()
+         agent.router_info[router.id] = router_info
+         updated_router = {'id': '1234',
+                           'distributed': True, 'ha': True,
+                           'external_gateway_info': {}, 'routes': [],
+                           'admin_state_up': True}
+ 
+         self.plugin_api.get_routers.return_value = [updated_router]
+         update = router_processing_queue.RouterUpdate(
+             updated_router['id'], router_processing_queue.PRIORITY_RPC,
+             router=updated_router)
+         agent._queue.add(update)
+         with mock.patch.object(agent,
+                                "_safe_router_removed"
+                                ) as router_remove,\
+             mock.patch.object(agent,
+                               "_process_added_router"
+                               ) as add_router:
+             agent._process_router_if_compatible(updated_router)
+             router_remove.assert_called_once_with(updated_router['id'])
+             add_router.assert_called_once_with(updated_router)
+ 
      def test_process_ha_dvr_router_if_compatible_no_ha_interface(self):
          agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
          agent.conf.agent_mode = 'dvr_snat'
