Merge "Force refresh instance info_cache during heal"
This commit is contained in:
commit
33aad0fe41
|
@ -7072,7 +7072,8 @@ class ComputeManager(manager.Manager):
|
|||
try:
|
||||
# Call to network API to get instance info.. this will
|
||||
# force an update to the instance's info_cache
|
||||
self.network_api.get_instance_nw_info(context, instance)
|
||||
self.network_api.get_instance_nw_info(
|
||||
context, instance, force_refresh=True)
|
||||
LOG.debug('Updated the network info_cache for instance',
|
||||
instance=instance)
|
||||
except exception.InstanceNotFound:
|
||||
|
|
|
@ -1692,7 +1692,8 @@ class API(base_api.NetworkAPI):
|
|||
def _get_instance_nw_info(self, context, instance, networks=None,
|
||||
port_ids=None, admin_client=None,
|
||||
preexisting_port_ids=None,
|
||||
refresh_vif_id=None, **kwargs):
|
||||
refresh_vif_id=None, force_refresh=False,
|
||||
**kwargs):
|
||||
# NOTE(danms): This is an inner method intended to be called
|
||||
# by other code that updates instance nwinfo. It *must* be
|
||||
# called with the refresh_cache-%(instance_uuid) lock held!
|
||||
|
@ -1704,12 +1705,17 @@ class API(base_api.NetworkAPI):
|
|||
nw_info = self._build_network_info_model(context, instance, networks,
|
||||
port_ids, admin_client,
|
||||
preexisting_port_ids,
|
||||
refresh_vif_id)
|
||||
refresh_vif_id,
|
||||
force_refresh=force_refresh)
|
||||
return network_model.NetworkInfo.hydrate(nw_info)
|
||||
|
||||
def _gather_port_ids_and_networks(self, context, instance, networks=None,
|
||||
port_ids=None, neutron=None):
|
||||
"""Return an instance's complete list of port_ids and networks."""
|
||||
"""Return an instance's complete list of port_ids and networks.
|
||||
|
||||
The results are based on the instance info_cache in the nova db, not
|
||||
the instance's current list of ports in neutron.
|
||||
"""
|
||||
|
||||
if ((networks is None and port_ids is not None) or
|
||||
(port_ids is None and networks is not None)):
|
||||
|
@ -2415,7 +2421,8 @@ class API(base_api.NetworkAPI):
|
|||
return port['device_id']
|
||||
|
||||
def get_vifs_by_instance(self, context, instance):
|
||||
raise NotImplementedError()
|
||||
return objects.VirtualInterfaceList.get_by_instance_uuid(context,
|
||||
instance.uuid)
|
||||
|
||||
def get_vif_by_mac_address(self, context, mac_address):
|
||||
raise NotImplementedError()
|
||||
|
@ -2808,7 +2815,7 @@ class API(base_api.NetworkAPI):
|
|||
def _build_network_info_model(self, context, instance, networks=None,
|
||||
port_ids=None, admin_client=None,
|
||||
preexisting_port_ids=None,
|
||||
refresh_vif_id=None):
|
||||
refresh_vif_id=None, force_refresh=False):
|
||||
"""Return list of ordered VIFs attached to instance.
|
||||
|
||||
:param context: Request context.
|
||||
|
@ -2830,6 +2837,10 @@ class API(base_api.NetworkAPI):
|
|||
cache rather than the entire cache. This can be
|
||||
triggered via a "network-changed" server external event
|
||||
from Neutron.
|
||||
:param force_refresh: If ``networks`` and ``port_ids`` are both None,
|
||||
by default the instance.info_cache will be used to
|
||||
populate the network info. Pass ``True`` to force
|
||||
collection of ports and networks from neutron directly.
|
||||
"""
|
||||
|
||||
search_opts = {'tenant_id': instance.project_id,
|
||||
|
@ -2901,11 +2912,28 @@ class API(base_api.NetworkAPI):
|
|||
return nw_info
|
||||
# else there is no existing cache and we need to build it
|
||||
|
||||
# Determine if we're doing a full refresh (_heal_instance_info_cache)
|
||||
# or if we are refreshing because we have attached/detached a port.
|
||||
# TODO(mriedem); we should leverage refresh_vif_id in the latter case
|
||||
# since we are unnecessarily rebuilding the entire cache for one port
|
||||
nw_info_refresh = networks is None and port_ids is None
|
||||
networks, port_ids = self._gather_port_ids_and_networks(
|
||||
context, instance, networks, port_ids, client)
|
||||
nw_info = network_model.NetworkInfo()
|
||||
if nw_info_refresh and force_refresh:
|
||||
# Use the current set of ports from neutron rather than the cache.
|
||||
port_ids = self._get_ordered_port_list(context, instance,
|
||||
current_neutron_ports)
|
||||
net_ids = [current_neutron_port_map.get(port_id).get('network_id')
|
||||
for port_id in port_ids]
|
||||
|
||||
# This is copied from _gather_port_ids_and_networks.
|
||||
networks = self._get_available_networks(
|
||||
context, instance.project_id, net_ids, client)
|
||||
else:
|
||||
# We are refreshing the full cache using the existing cache rather
|
||||
# than what is currently in neutron.
|
||||
networks, port_ids = self._gather_port_ids_and_networks(
|
||||
context, instance, networks, port_ids, client)
|
||||
|
||||
nw_info = network_model.NetworkInfo()
|
||||
for port_id in port_ids:
|
||||
current_neutron_port = current_neutron_port_map.get(port_id)
|
||||
if current_neutron_port:
|
||||
|
@ -2921,6 +2949,43 @@ class API(base_api.NetworkAPI):
|
|||
|
||||
return nw_info
|
||||
|
||||
def _get_ordered_port_list(self, context, instance, current_neutron_ports):
|
||||
"""Returns ordered port list using nova virtual_interface data."""
|
||||
|
||||
# a dict, keyed by port UUID, of the port's "index"
|
||||
# so that we can order the returned port UUIDs by the
|
||||
# original insertion order followed by any newly-attached
|
||||
# ports
|
||||
port_uuid_to_index_map = {}
|
||||
port_order_list = []
|
||||
ports_without_order = []
|
||||
|
||||
# Get set of ports from nova vifs
|
||||
vifs = self.get_vifs_by_instance(context, instance)
|
||||
for port in current_neutron_ports:
|
||||
# NOTE(mjozefcz): For each port check if we have its index from
|
||||
# nova virtual_interfaces objects. If not - it seems
|
||||
# to be a new port - add it at the end of list.
|
||||
|
||||
# Find port index if it was attached before.
|
||||
for vif in vifs:
|
||||
if vif.uuid == port['id']:
|
||||
port_uuid_to_index_map[port['id']] = vif.id
|
||||
break
|
||||
|
||||
if port['id'] not in port_uuid_to_index_map:
|
||||
# Assume that it's new port and add it to the end of port list.
|
||||
ports_without_order.append(port['id'])
|
||||
|
||||
# Lets sort created port order_list by given index.
|
||||
port_order_list = sorted(port_uuid_to_index_map,
|
||||
key=lambda k: port_uuid_to_index_map[k])
|
||||
|
||||
# Add ports without order to the end of list
|
||||
port_order_list.extend(ports_without_order)
|
||||
|
||||
return port_order_list
|
||||
|
||||
def _get_subnets_from_port(self, context, port, client=None):
|
||||
"""Return the subnets for a given port."""
|
||||
|
||||
|
|
|
@ -7166,14 +7166,14 @@ class ComputeTestCase(BaseTestCase,
|
|||
return instance_map[instance_uuid]
|
||||
|
||||
# NOTE(comstud): Override the stub in setUp()
|
||||
def fake_get_instance_nw_info(cls, context, instance,
|
||||
use_slave=False):
|
||||
def fake_get_instance_nw_info(cls, context, instance, **kwargs):
|
||||
# Note that this exception gets caught in compute/manager
|
||||
# and is ignored. However, the below increment of
|
||||
# 'get_nw_info' won't happen, and you'll get an assert
|
||||
# failure checking it below.
|
||||
self.assertEqual(call_info['expected_instance']['uuid'],
|
||||
instance['uuid'])
|
||||
self.assertTrue(kwargs['force_refresh'])
|
||||
call_info['get_nw_info'] += 1
|
||||
if _get_instance_nw_info_raise:
|
||||
raise exception.InstanceNotFound(instance_id=instance['uuid'])
|
||||
|
|
|
@ -45,6 +45,7 @@ from nova.network.neutronv2 import api as neutronapi
|
|||
from nova.network.neutronv2 import constants
|
||||
from nova import objects
|
||||
from nova.objects import network_request as net_req_obj
|
||||
from nova.objects import virtual_interface as obj_vif
|
||||
from nova.pci import manager as pci_manager
|
||||
from nova.pci import request as pci_request
|
||||
from nova.pci import utils as pci_utils
|
||||
|
@ -6921,6 +6922,11 @@ class TestGetInstanceNetworkInfo(test.NoDBTestCase):
|
|||
network_id = kwargs.get('network_id', uuids.network_id)
|
||||
return {'id': port_id, 'network_id': network_id}
|
||||
|
||||
@staticmethod
|
||||
def _get_fake_vif(context, **kwargs):
|
||||
"""Returns VirtualInterface based on provided VIF ID"""
|
||||
return obj_vif.VirtualInterface(context=context, **kwargs)
|
||||
|
||||
def test_get_nw_info_refresh_vif_id_add_vif(self):
|
||||
"""Tests that a network-changed event occurred on a single port
|
||||
which is not already in the cache so it's added.
|
||||
|
@ -7011,3 +7017,123 @@ class TestGetInstanceNetworkInfo(test.NoDBTestCase):
|
|||
self.assertIsNotNone(old_vif)
|
||||
removed_vif = self._get_vif_in_cache(nwinfo, uuids.removed_port)
|
||||
self.assertIsNone(removed_vif)
|
||||
|
||||
def test_get_instance_nw_info_force_refresh(self):
|
||||
"""Tests a full refresh of the instance info cache using information
|
||||
from neutron rather than the instance's current info cache data.
|
||||
"""
|
||||
# Fake out an empty cache.
|
||||
self.instance.info_cache = self._get_fake_info_cache([])
|
||||
# The instance has one attached port in neutron.
|
||||
self.client.list_ports.return_value = {
|
||||
'ports': [self._get_fake_port(uuids.port_id)]}
|
||||
ordered_port_list = [uuids.port_id]
|
||||
|
||||
with test.nested(
|
||||
mock.patch.object(self.api, '_get_available_networks',
|
||||
return_value=[{'id': uuids.network_id}]),
|
||||
mock.patch.object(self.api, '_build_vif_model',
|
||||
return_value=model.VIF(uuids.port_id)),
|
||||
# We should not call _gather_port_ids_and_networks since that uses
|
||||
# the existing instance.info_cache when no ports/networks are
|
||||
# passed to _build_network_info_model and what we want is a full
|
||||
# refresh of the ports based on what neutron says is current.
|
||||
mock.patch.object(self.api, '_gather_port_ids_and_networks',
|
||||
new_callable=mock.NonCallableMock),
|
||||
mock.patch.object(self.api, '_get_ordered_port_list',
|
||||
return_value=ordered_port_list)
|
||||
) as (
|
||||
get_nets, build_vif, gather_ports, mock_port_map
|
||||
):
|
||||
nwinfo = self.api._get_instance_nw_info(
|
||||
self.context, self.instance, force_refresh=True)
|
||||
get_nets.assert_called_once_with(
|
||||
self.context, self.instance.project_id,
|
||||
[uuids.network_id], self.client)
|
||||
# Assert that the port is in the cache now.
|
||||
self.assertIsNotNone(self._get_vif_in_cache(nwinfo, uuids.port_id))
|
||||
|
||||
def test__get_ordered_port_list(self):
|
||||
"""This test if port_list is sorted by VirtualInterface id
|
||||
sequence.
|
||||
"""
|
||||
nova_vifs = [
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_1, id=0),
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_2, id=1),
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_3, id=2),
|
||||
]
|
||||
# Random order.
|
||||
current_neutron_ports = [
|
||||
self._get_fake_port(uuids.port_id_2),
|
||||
self._get_fake_port(uuids.port_id_1),
|
||||
self._get_fake_port(uuids.port_id_3),
|
||||
]
|
||||
expected_port_list = [uuids.port_id_1,
|
||||
uuids.port_id_2,
|
||||
uuids.port_id_3]
|
||||
with mock.patch.object(self.api, 'get_vifs_by_instance',
|
||||
return_value=nova_vifs):
|
||||
port_list = self.api._get_ordered_port_list(
|
||||
self.context, self.instance, current_neutron_ports)
|
||||
self.assertEqual(expected_port_list,
|
||||
port_list)
|
||||
|
||||
def test__get_ordered_port_list_new_port(self):
|
||||
"""This test if port_list is sorted by VirtualInterface id
|
||||
sequence while new port appears.
|
||||
"""
|
||||
nova_vifs = [
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_1, id=0),
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_3, id=2),
|
||||
]
|
||||
# New port appears.
|
||||
current_neutron_ports = [
|
||||
self._get_fake_port(uuids.port_id_1),
|
||||
self._get_fake_port(uuids.port_id_4),
|
||||
self._get_fake_port(uuids.port_id_3)
|
||||
]
|
||||
expected_port_list = [uuids.port_id_1,
|
||||
uuids.port_id_3,
|
||||
uuids.port_id_4]
|
||||
with mock.patch.object(self.api, 'get_vifs_by_instance',
|
||||
return_value=nova_vifs):
|
||||
port_list = self.api._get_ordered_port_list(
|
||||
self.context, self.instance, current_neutron_ports)
|
||||
self.assertEqual(expected_port_list,
|
||||
port_list)
|
||||
|
||||
def test__get_ordered_port_list_new_port_and_deleted_vif(self):
|
||||
"""This test if port_list is sorted by VirtualInterface id
|
||||
sequence while new port appears along with deleted old
|
||||
VirtualInterface objects.
|
||||
"""
|
||||
# Display also deleted VirtualInterface.
|
||||
nova_vifs = [
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_1, id=0,
|
||||
deleted=True),
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_2, id=3),
|
||||
self._get_fake_vif(self.context,
|
||||
uuid=uuids.port_id_3, id=5),
|
||||
]
|
||||
# Random order and new port.
|
||||
current_neutron_ports = [
|
||||
self._get_fake_port(uuids.port_id_4),
|
||||
self._get_fake_port(uuids.port_id_3),
|
||||
self._get_fake_port(uuids.port_id_2),
|
||||
]
|
||||
expected_port_list = [uuids.port_id_2,
|
||||
uuids.port_id_3,
|
||||
uuids.port_id_4]
|
||||
with mock.patch.object(self.api, 'get_vifs_by_instance',
|
||||
return_value=nova_vifs):
|
||||
port_list = self.api._get_ordered_port_list(
|
||||
self.context, self.instance, current_neutron_ports)
|
||||
self.assertEqual(expected_port_list,
|
||||
port_list)
|
||||
|
|
Loading…
Reference in New Issue