Merge "Track associated sharing RPs in report client"

This commit is contained in:
Zuul 2018-01-20 22:27:36 +00:00 committed by Gerrit Code Review
commit d377fe700a
2 changed files with 140 additions and 30 deletions

View File

@ -639,7 +639,7 @@ class SchedulerReportClient(object):
generation=rp['generation'])
# If there had been no local resource provider record, force refreshing
# the aggregate & trait caches.
# the associated aggregates, traits, and sharing providers.
self._refresh_associations(uuid, rp['generation'], force=True)
return ret
@ -670,9 +670,10 @@ class SchedulerReportClient(object):
self._provider_tree.update_inventory(rp_uuid, curr_inv, cur_gen)
return curr
def _refresh_associations(self, rp_uuid, generation=None, force=False):
"""Refresh the aggregates and traits for the provided resource provider
uuid.
def _refresh_associations(self, rp_uuid, generation=None, force=False,
refresh_sharing=True):
"""Refresh aggregates, traits, and (optionally) aggregate-associated
sharing providers for the specified resource provider uuid.
Only refresh if there has been no refresh during the lifetime of
this process, ASSOCIATION_REFRESH seconds have passed, or the force arg
@ -683,6 +684,10 @@ class SchedulerReportClient(object):
:param generation: The resource provider generation to set. If None,
the provider's generation is not updated.
:param force: If True, force the refresh
:param refresh_sharing: If True, fetch all the providers associated
by aggregate with the specified provider,
including their traits and aggregates (but not
*their* sharing providers).
"""
if force or self._associations_stale(rp_uuid):
# Refresh aggregates
@ -708,6 +713,23 @@ class SchedulerReportClient(object):
self._provider_tree.update_traits(
rp_uuid, traits, generation=generation)
if refresh_sharing:
# Refresh providers associated by aggregate
for rp in self._get_providers_in_aggregates(aggs):
if not self._provider_tree.exists(rp['uuid']):
# NOTE(efried): Right now sharing providers are always
# treated as roots. This is deliberate. From the
# context of this compute's RP, it doesn't matter if a
# sharing RP is part of a tree.
self._provider_tree.new_root(
rp['name'], rp['uuid'], rp['generation'])
# Now we have to (populate or) refresh that guy's traits
# and aggregates (but not *his* aggregate-associated
# providers). No need to override force=True for newly-
# added providers - the missing timestamp will always
# trigger them to refresh.
self._refresh_associations(rp['uuid'], force=force,
refresh_sharing=False)
self.association_refresh_time[rp_uuid] = time.time()
def _associations_stale(self, uuid):

View File

@ -1188,10 +1188,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_resource_provider')
def test_ensure_resource_provider_exists_in_cache(self, get_rp_mock,
get_trait_mock, get_agg_mock, create_rp_mock):
get_pia_mock, get_trait_mock, get_agg_mock, create_rp_mock):
# Override the client object's cache to contain a resource provider
# object for the compute host and check that
# _ensure_resource_provider() doesn't call _get_resource_provider() or
@ -1203,19 +1205,57 @@ class TestProviderOperations(SchedulerReportClientTestCase):
1,
)
get_agg_mock.return_value = set([uuids.agg1, uuids.agg2])
get_trait_mock.return_value = set(['CUSTOM_GOLD', 'CUSTOM_SILVER'])
get_agg_mock.side_effect = [
set([uuids.agg1, uuids.agg2]),
set([uuids.agg1, uuids.agg3]),
set([uuids.agg2]),
]
get_trait_mock.side_effect = [
set(['CUSTOM_GOLD', 'CUSTOM_SILVER']),
set(),
set(['CUSTOM_BRONZE'])
]
get_pia_mock.return_value = [
{
'uuid': uuids.shr1,
'name': 'sharing1',
'generation': 1,
},
{
'uuid': uuids.shr2,
'name': 'sharing2',
'generation': 2,
},
]
self.client._ensure_resource_provider(cn.uuid)
get_agg_mock.assert_called_once_with(cn.uuid)
self.assertTrue(self.client._provider_tree.in_aggregates(
get_pia_mock.assert_called_once_with(set([uuids.agg1, uuids.agg2]))
self.assertTrue(self.client._provider_tree.exists(uuids.shr1))
self.assertTrue(self.client._provider_tree.exists(uuids.shr2))
# _get_provider_aggregates and _traits were called thrice: one for the
# compute RP and once for each of the sharing RPs.
expected_calls = [mock.call(uuid)
for uuid in (cn.uuid, uuids.shr1, uuids.shr2)]
get_agg_mock.assert_has_calls(expected_calls)
get_trait_mock.assert_has_calls(expected_calls)
# The compute RP is associated with aggs 1 and 2
self.assertFalse(self.client._provider_tree.have_aggregates_changed(
uuids.compute_node, [uuids.agg1, uuids.agg2]))
self.assertFalse(self.client._provider_tree.in_aggregates(
uuids.compute_node, [uuids.agg1, uuids.agg3]))
get_trait_mock.assert_called_once_with(cn.uuid)
self.assertTrue(self.client._provider_tree.has_traits(
# The first sharing RP is associated with agg1 and agg3
self.assertFalse(self.client._provider_tree.have_aggregates_changed(
uuids.shr1, [uuids.agg1, uuids.agg3]))
# And the second with just agg2
self.assertFalse(self.client._provider_tree.have_aggregates_changed(
uuids.shr2, [uuids.agg2]))
# The compute RP has gold and silver traits
self.assertFalse(self.client._provider_tree.have_traits_changed(
uuids.compute_node, ['CUSTOM_GOLD', 'CUSTOM_SILVER']))
self.assertFalse(self.client._provider_tree.has_traits(
uuids.compute_node, ['CUSTOM_GOLD', 'CUSTOM_BRONZE']))
# The first sharing RP has none
self.assertFalse(self.client._provider_tree.have_traits_changed(
uuids.shr1, []))
# The second has bronze
self.assertFalse(self.client._provider_tree.have_traits_changed(
uuids.shr2, ['CUSTOM_BRONZE']))
# These were not called because we had the root provider in the cache.
self.assertFalse(get_rp_mock.called)
self.assertFalse(create_rp_mock.called)
@ -1225,10 +1265,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_resource_provider')
def test_ensure_resource_provider_get(self, get_rp_mock, get_trait_mock,
get_agg_mock, create_rp_mock):
def test_ensure_resource_provider_get(self, get_rp_mock, get_pia_mock,
get_trait_mock, get_agg_mock, create_rp_mock):
# No resource provider exists in the client's cache, so validate that
# if we get the resource provider from the placement API that we don't
# try to create the resource provider.
@ -1240,6 +1282,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
get_agg_mock.return_value = set([uuids.agg1])
get_trait_mock.return_value = set(['CUSTOM_GOLD'])
get_pia_mock.return_value = []
self.client._ensure_resource_provider(uuids.compute_node)
@ -1259,19 +1302,18 @@ class TestProviderOperations(SchedulerReportClientTestCase):
self.assertFalse(
self.client._provider_tree.has_traits(uuids.compute_node,
['CUSTOM_SILVER']))
get_pia_mock.assert_called_once_with(set([uuids.agg1]))
self.assertTrue(self.client._provider_tree.exists(uuids.compute_node))
self.assertFalse(create_rp_mock.called)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_create_resource_provider')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
'_refresh_associations')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_resource_provider')
def test_ensure_resource_provider_create_fail(self, get_rp_mock,
get_trait_mock, get_agg_mock, create_rp_mock):
refresh_mock, create_rp_mock):
# No resource provider exists in the client's cache, and
# _create_provider raises, indicating there was an error with the
# create call. Ensure we don't populate the resource provider cache
@ -1287,8 +1329,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
create_rp_mock.assert_called_once_with(
uuids.compute_node, uuids.compute_node, parent_provider_uuid=None)
self.assertFalse(self.client._provider_tree.exists(uuids.compute_node))
self.assertFalse(get_agg_mock.called)
self.assertFalse(get_trait_mock.called)
self.assertFalse(refresh_mock.called)
self.assertRaises(
ValueError,
self.client._provider_tree.in_aggregates, uuids.compute_node, [])
@ -1302,10 +1343,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_resource_provider')
def test_ensure_resource_provider_create(self, get_rp_mock, get_trait_mock,
get_agg_mock, create_rp_mock):
def test_ensure_resource_provider_create(self, get_rp_mock, get_pia_mock,
get_trait_mock, get_agg_mock, create_rp_mock):
# No resource provider exists in the client's cache and no resource
# provider was returned from the placement API, so verify that in this
# case we try to create the resource provider via the placement API.
@ -1317,6 +1360,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
}
get_agg_mock.return_value = set([uuids.agg1, uuids.agg2])
get_trait_mock.return_value = set(['CUSTOM_FOO'])
get_pia_mock.return_value = []
self.assertEqual(
uuids.compute_node,
self.client._ensure_resource_provider(uuids.compute_node))
@ -1327,6 +1371,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
get_agg_mock.assert_called_once_with(uuids.compute_node)
get_trait_mock.assert_called_once_with(uuids.compute_node)
get_pia_mock.assert_called_once_with(set([uuids.agg1, uuids.agg2]))
get_rp_mock.assert_called_once_with(uuids.compute_node)
create_rp_mock.assert_called_once_with(
uuids.compute_node,
@ -2021,7 +2066,10 @@ class TestAssociations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
def test_refresh_associations_no_last(self, mock_trait_get, mock_agg_get):
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
def test_refresh_associations_no_last(self, mock_pia_get, mock_trait_get,
mock_agg_get):
"""Test that associations are refreshed when stale."""
uuid = uuids.compute_node
# Seed the provider tree so _refresh_associations finds the provider
@ -2031,6 +2079,7 @@ class TestAssociations(SchedulerReportClientTestCase):
self.client._refresh_associations(uuid)
mock_agg_get.assert_called_once_with(uuid)
mock_trait_get.assert_called_once_with(uuid)
mock_pia_get.assert_called_once_with(mock_agg_get.return_value)
self.assertIn(uuid, self.client.association_refresh_time)
self.assertTrue(
self.client._provider_tree.in_aggregates(uuid, [uuids.agg1]))
@ -2045,10 +2094,41 @@ class TestAssociations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
def test_refresh_associations_no_refresh_sharing(self, mock_pia_get,
mock_trait_get,
mock_agg_get):
"""Test refresh_sharing=False."""
uuid = uuids.compute_node
# Seed the provider tree so _refresh_associations finds the provider
self.client._provider_tree.new_root('compute', uuid, 1)
mock_agg_get.return_value = set([uuids.agg1])
mock_trait_get.return_value = set(['CUSTOM_GOLD'])
self.client._refresh_associations(uuid, refresh_sharing=False)
mock_agg_get.assert_called_once_with(uuid)
mock_trait_get.assert_called_once_with(uuid)
mock_pia_get.assert_not_called()
self.assertIn(uuid, self.client.association_refresh_time)
self.assertTrue(
self.client._provider_tree.in_aggregates(uuid, [uuids.agg1]))
self.assertFalse(
self.client._provider_tree.in_aggregates(uuid, [uuids.agg2]))
self.assertTrue(
self.client._provider_tree.has_traits(uuid, ['CUSTOM_GOLD']))
self.assertFalse(
self.client._provider_tree.has_traits(uuid, ['CUSTOM_SILVER']))
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_associations_stale')
def test_refresh_associations_not_stale(self, mock_stale, mock_trait_get,
mock_agg_get):
def test_refresh_associations_not_stale(self, mock_stale, mock_pia_get,
mock_trait_get, mock_agg_get):
"""Test that refresh associations is not called when the map is
not stale.
"""
@ -2057,6 +2137,7 @@ class TestAssociations(SchedulerReportClientTestCase):
self.client._refresh_associations(uuid)
mock_agg_get.assert_not_called()
mock_trait_get.assert_not_called()
mock_pia_get.assert_not_called()
self.assertFalse(self.client.association_refresh_time)
@mock.patch.object(report.LOG, 'debug')
@ -2064,20 +2145,24 @@ class TestAssociations(SchedulerReportClientTestCase):
'_get_provider_aggregates')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_provider_traits')
def test_refresh_associations_time(self, mock_trait_get, mock_agg_get,
log_mock):
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_providers_in_aggregates')
def test_refresh_associations_time(self, mock_pia_get, mock_trait_get,
mock_agg_get, log_mock):
"""Test that refresh associations is called when the map is stale."""
uuid = uuids.compute_node
# Seed the provider tree so _refresh_associations finds the provider
self.client._provider_tree.new_root('compute', uuid, 1)
mock_agg_get.return_value = set([])
mock_trait_get.return_value = set([])
mock_pia_get.return_value = []
# Called a first time because association_refresh_time is empty.
now = time.time()
self.client._refresh_associations(uuid)
mock_agg_get.assert_called_once_with(uuid)
mock_trait_get.assert_called_once_with(uuid)
mock_pia_get.assert_called_once_with(set())
log_mock.assert_has_calls([
mock.call('Refreshing aggregate associations for resource '
'provider %s, aggregates: %s', uuid, 'None'),
@ -2089,6 +2174,7 @@ class TestAssociations(SchedulerReportClientTestCase):
# Clear call count.
mock_agg_get.reset_mock()
mock_trait_get.reset_mock()
mock_pia_get.reset_mock()
with mock.patch('time.time') as mock_future:
# Not called a second time because not enough time has passed.
@ -2096,12 +2182,14 @@ class TestAssociations(SchedulerReportClientTestCase):
self.client._refresh_associations(uuid)
mock_agg_get.assert_not_called()
mock_trait_get.assert_not_called()
mock_pia_get.assert_not_called()
# Called because time has passed.
mock_future.return_value = now + report.ASSOCIATION_REFRESH + 1
self.client._refresh_associations(uuid)
mock_agg_get.assert_called_once_with(uuid)
mock_trait_get.assert_called_once_with(uuid)
mock_pia_get.assert_called_once_with(set())
class TestComputeNodeToInventoryDict(test.NoDBTestCase):