diff -crB --new-file neutron-9.4.2.dev21-backup/neutron/plugins/ml2/rpc.py neutron-9.4.2.dev21/neutron/plugins/ml2/rpc.py
*** neutron-9.4.2.dev21-backup/neutron/plugins/ml2/rpc.py	2019-03-25 15:14:42.540094384 -0700
--- neutron-9.4.2.dev21/neutron/plugins/ml2/rpc.py	2019-03-25 15:28:14.976352266 -0700
***************
*** 58,63 ****
--- 58,71 ----
          self.setup_tunnel_callback_mixin(notifier, type_manager)
          super(RpcCallbacks, self).__init__()
  
+     def _get_new_status(self, host, port_context):
+         port = port_context.current
+         if not host or host == port_context.host:
+             new_status = (n_const.PORT_STATUS_BUILD if port['admin_state_up']
+                           else n_const.PORT_STATUS_DOWN)
+             if port['status'] != new_status:
+                 return new_status
+ 
      def get_device_details(self, rpc_context, **kwargs):
          """Agent requests device details."""
          agent_id = kwargs.get('agent_id')
***************
*** 88,103 ****
              if port['network_id'] not in cached_networks:
                  cached_networks[port['network_id']] = (
                      port_context.network.current)
!         return self._get_device_details(rpc_context, agent_id=agent_id,
!                                         host=host, device=device,
!                                         port_context=port_context)
  
      def _get_device_details(self, rpc_context, agent_id, host, device,
                              port_context):
          segment = port_context.bottom_bound_segment
          port = port_context.current
-         plugin = manager.NeutronManager.get_plugin()
-         port_id = port_context.current['id'] 
          if not segment:
              LOG.warning(_LW("Device %(device)s requested by agent "
                              "%(agent_id)s on network %(network_id)s not "
--- 96,119 ----
              if port['network_id'] not in cached_networks:
                  cached_networks[port['network_id']] = (
                      port_context.network.current)
!         result = self._get_device_details(rpc_context, agent_id=agent_id,
!                                           host=host, device=device,
!                                           port_context=port_context)
!         if 'network_id' in result:
!             # success so we update status
!             new_status = self._get_new_status(host, port_context)
!             if new_status:
!                 plugin.update_port_status(rpc_context,
!                                           port_id,
!                                           new_status,
!                                           host,
!                                           port_context.network.current)
!         return result
  
      def _get_device_details(self, rpc_context, agent_id, host, device,
                              port_context):
          segment = port_context.bottom_bound_segment
          port = port_context.current
          if not segment:
              LOG.warning(_LW("Device %(device)s requested by agent "
                              "%(agent_id)s on network %(network_id)s not "
***************
*** 108,123 ****
                           'vif_type': port_context.vif_type})
              return {'device': device}
  
-         if (not host or host == port_context.host):
-             new_status = (n_const.PORT_STATUS_BUILD if port['admin_state_up']
-                           else n_const.PORT_STATUS_DOWN)
-             if port['status'] != new_status:
-                 plugin.update_port_status(rpc_context,
-                                           port_id,
-                                           new_status,
-                                           host,
-                                           port_context.network.current)
- 
          network_qos_policy_id = port_context.network._network.get(
              qos_consts.QOS_POLICY_ID)
          entry = {'device': device,
--- 124,129 ----
***************
*** 182,187 ****
--- 188,205 ----
                  LOG.exception(_LE("Failed to get details for device %s"),
                                device)
                  failed_devices.append(device)
+         new_status_map = {ctxt.current['id']: self._get_new_status(host, ctxt)
+                           for ctxt in bound_contexts.values() if ctxt}
+         # filter out any without status changes
+         new_status_map = {p: s for p, s in new_status_map.items() if s}
+         try:
+             for port_id, new_status in new_status_map.items():
+                 plugin.update_port_status(rpc_context, port_id,
+                                           new_status, host)
+         except Exception:
+             LOG.exception("Failure updating statuses, retrying all")
+             failed_devices = devices_to_fetch
+             devices = []
  
          return {'devices': devices,
                  'failed_devices': failed_devices}
diff -crB --new-file neutron-9.4.2.dev21-backup/neutron/tests/unit/plugins/ml2/test_rpc.py neutron-9.4.2.dev21/neutron/tests/unit/plugins/ml2/test_rpc.py
*** neutron-9.4.2.dev21-backup/neutron/tests/unit/plugins/ml2/test_rpc.py	2019-03-25 15:17:53.573061103 -0700
--- neutron-9.4.2.dev21/neutron/tests/unit/plugins/ml2/test_rpc.py	2019-03-25 15:30:35.788059194 -0700
***************
*** 178,187 ****
              f.assert_has_calls(calls)
  
      def test_get_devices_details_list(self):
!         devices = [1, 2, 3, 4, 5]
!         expected = devices
          callback = self.callbacks.get_devices_details_list
!         self._test_get_devices_list(callback, devices, expected)
  
      def test_get_devices_details_list_with_empty_devices(self):
          with mock.patch.object(self.callbacks, 'get_device_details') as f:
--- 178,187 ----
              f.assert_has_calls(calls)
  
      def test_get_devices_details_list(self):
!         results = [{'device': [v]} for v in [1, 2, 3, 4, 5]]
!         expected = results
          callback = self.callbacks.get_devices_details_list
!         self._test_get_devices_list(callback, results, expected)
  
      def test_get_devices_details_list_with_empty_devices(self):
          with mock.patch.object(self.callbacks, 'get_device_details') as f:
