Merge "Track provider traits in report client"
This commit is contained in:
@@ -42,8 +42,9 @@ _RE_INV_IN_USE = re.compile("Inventory for (.+) on resource provider "
|
||||
"(.+) in use")
|
||||
WARN_EVERY = 10
|
||||
PLACEMENT_CLIENT_SEMAPHORE = 'placement_client'
|
||||
# Number of seconds between attempts to update a provider's aggregates
|
||||
AGGREGATE_REFRESH = 300
|
||||
# Number of seconds between attempts to update a provider's aggregates and
|
||||
# traits
|
||||
ASSOCIATION_REFRESH = 300
|
||||
NESTED_PROVIDER_API_VERSION = '1.14'
|
||||
POST_ALLOCATIONS_API_VERSION = '1.13'
|
||||
|
||||
@@ -261,8 +262,8 @@ class SchedulerReportClient(object):
|
||||
# An object that contains a nova-compute-side cache of resource
|
||||
# provider and inventory information
|
||||
self._provider_tree = provider_tree.ProviderTree()
|
||||
# Track the last time we updated the aggregate map.
|
||||
self.aggregate_refresh_time = {}
|
||||
# Track the last time we updated providers' aggregates and traits
|
||||
self.association_refresh_time = {}
|
||||
self._client = self._create_client()
|
||||
# NOTE(danms): Keep track of how naggy we've been
|
||||
self._warn_count = 0
|
||||
@@ -270,9 +271,9 @@ class SchedulerReportClient(object):
|
||||
@utils.synchronized(PLACEMENT_CLIENT_SEMAPHORE)
|
||||
def _create_client(self):
|
||||
"""Create the HTTP session accessing the placement service."""
|
||||
# Flush provider tree and aggregates so we start from a clean slate.
|
||||
# Flush provider tree and associations so we start from a clean slate.
|
||||
self._provider_tree = provider_tree.ProviderTree()
|
||||
self.aggregate_refresh_time = {}
|
||||
self.association_refresh_time = {}
|
||||
# TODO(mriedem): Perform some version discovery at some point.
|
||||
client = utils.get_ksa_adapter('placement')
|
||||
# Set accept header on every request to ensure we notify placement
|
||||
@@ -396,6 +397,35 @@ class SchedulerReportClient(object):
|
||||
}
|
||||
LOG.error(msg, args)
|
||||
|
||||
@safe_connect
|
||||
def _get_provider_traits(self, rp_uuid):
|
||||
"""Queries the placement API for a resource provider's traits. Returns
|
||||
a set() of string trait names, or None if no such resource provider was
|
||||
found or there was an error communicating with the placement API.
|
||||
|
||||
:param rp_uuid: UUID of the resource provider to grab traits for.
|
||||
"""
|
||||
resp = self.get("/resource_providers/%s/traits" % rp_uuid,
|
||||
version='1.6')
|
||||
|
||||
if resp.status_code == 200:
|
||||
return set(resp.json()['traits'])
|
||||
|
||||
placement_req_id = get_placement_request_id(resp)
|
||||
if resp.status_code == 404:
|
||||
LOG.warning(
|
||||
"[%(placement_req_id)s] Tried to get a provider's traits, but "
|
||||
"the provider %(uuid)s does not exist.",
|
||||
{'uuid': rp_uuid, 'placement_req_id': placement_req_id})
|
||||
else:
|
||||
LOG.error(
|
||||
"[%(placement_req_id)s] Failed to retrieve traits from "
|
||||
"placement API for resource provider with UUID %(uuid)s. Got "
|
||||
"%(status_code)d: %(err_text)s.",
|
||||
{'placement_req_id': placement_req_id, 'uuid': rp_uuid,
|
||||
'status_code': resp.status_code, 'err_text': resp.text})
|
||||
return None
|
||||
|
||||
@safe_connect
|
||||
def _get_resource_provider(self, uuid):
|
||||
"""Queries the placement API for a resource provider record with the
|
||||
@@ -585,7 +615,7 @@ class SchedulerReportClient(object):
|
||||
# parent_provider_uuid on a previously-parent-less provider - so we do
|
||||
# NOT handle that scenario here.
|
||||
if self._provider_tree.exists(uuid):
|
||||
self._refresh_aggregates(uuid)
|
||||
self._refresh_associations(uuid)
|
||||
return uuid
|
||||
|
||||
# No local information about the resource provider in our tree. Check
|
||||
@@ -609,8 +639,8 @@ class SchedulerReportClient(object):
|
||||
generation=rp['generation'])
|
||||
|
||||
# If there had been no local resource provider record, force refreshing
|
||||
# the aggregate map.
|
||||
self._refresh_aggregates(uuid, rp['generation'], force=True)
|
||||
# the aggregate & trait caches.
|
||||
self._refresh_associations(uuid, rp['generation'], force=True)
|
||||
|
||||
return ret
|
||||
|
||||
@@ -640,20 +670,22 @@ class SchedulerReportClient(object):
|
||||
self._provider_tree.update_inventory(rp_uuid, curr_inv, cur_gen)
|
||||
return curr
|
||||
|
||||
def _refresh_aggregates(self, rp_uuid, generation=None, force=False):
|
||||
"""Refresh the aggregate map for the provided resource provider uuid.
|
||||
def _refresh_associations(self, rp_uuid, generation=None, force=False):
|
||||
"""Refresh the aggregates and traits for the provided resource provider
|
||||
uuid.
|
||||
|
||||
Only refresh if there has been no refresh during the lifetime of
|
||||
this process, AGGREGATE_REFRESH seconds have passed, or the force arg
|
||||
this process, ASSOCIATION_REFRESH seconds have passed, or the force arg
|
||||
has been set to True.
|
||||
|
||||
:param rp_uuid: UUID of the resource provider to check for fresh
|
||||
aggregates
|
||||
aggregates and traits
|
||||
:param generation: The resource provider generation to set. If None,
|
||||
the provider's generation is not updated.
|
||||
:param force: If True, force the refresh
|
||||
"""
|
||||
if force or self._aggregate_map_stale(rp_uuid):
|
||||
if force or self._associations_stale(rp_uuid):
|
||||
# Refresh aggregates
|
||||
aggs = self._get_provider_aggregates(rp_uuid)
|
||||
if aggs is not None:
|
||||
msg = ("Refreshing aggregate associations for resource "
|
||||
@@ -664,16 +696,29 @@ class SchedulerReportClient(object):
|
||||
# doesn't exist in our _provider_tree.
|
||||
self._provider_tree.update_aggregates(
|
||||
rp_uuid, aggs, generation=generation)
|
||||
self.aggregate_refresh_time[rp_uuid] = time.time()
|
||||
|
||||
def _aggregate_map_stale(self, uuid):
|
||||
"""Respond True if aggregates have not been refreshed "recently".
|
||||
# Refresh traits
|
||||
traits = self._get_provider_traits(rp_uuid)
|
||||
if traits is not None:
|
||||
msg = ("Refreshing trait associations for resource "
|
||||
"provider %s, traits: %s")
|
||||
LOG.debug(msg, rp_uuid, ','.join(traits or ['None']))
|
||||
# NOTE(efried): This will blow up if called for a RP that
|
||||
# doesn't exist in our _provider_tree.
|
||||
self._provider_tree.update_traits(
|
||||
rp_uuid, traits, generation=generation)
|
||||
|
||||
It is old if aggregate_refresh_time for this uuid is not set
|
||||
or more than AGGREGATE_REFRESH seconds ago.
|
||||
self.association_refresh_time[rp_uuid] = time.time()
|
||||
|
||||
def _associations_stale(self, uuid):
|
||||
"""Respond True if aggregates and traits have not been refreshed
|
||||
"recently".
|
||||
|
||||
It is old if association_refresh_time for this uuid is not set
|
||||
or more than ASSOCIATION_REFRESH seconds ago.
|
||||
"""
|
||||
refresh_time = self.aggregate_refresh_time.get(uuid, 0)
|
||||
return (time.time() - refresh_time) > AGGREGATE_REFRESH
|
||||
refresh_time = self.association_refresh_time.get(uuid, 0)
|
||||
return (time.time() - refresh_time) > ASSOCIATION_REFRESH
|
||||
|
||||
def _update_inventory_attempt(self, rp_uuid, inv_data):
|
||||
"""Update the inventory for this resource provider if needed.
|
||||
@@ -874,7 +919,7 @@ class SchedulerReportClient(object):
|
||||
"inventory. Ignoring.",
|
||||
msg_args)
|
||||
self._provider_tree.remove(rp_uuid)
|
||||
self.aggregate_refresh_time.pop(rp_uuid, None)
|
||||
self.association_refresh_time.pop(rp_uuid, None)
|
||||
return
|
||||
elif r.status_code == 409:
|
||||
rc_str = _extract_inventory_in_use(r.text)
|
||||
@@ -1435,7 +1480,7 @@ class SchedulerReportClient(object):
|
||||
self._provider_tree.remove(rp_uuid)
|
||||
except ValueError:
|
||||
pass
|
||||
self.aggregate_refresh_time.pop(rp_uuid, None)
|
||||
self.association_refresh_time.pop(rp_uuid, None)
|
||||
else:
|
||||
# Check for 404 since we don't need to log a warning if we tried to
|
||||
# delete something which doesn"t actually exist.
|
||||
|
||||
@@ -116,13 +116,16 @@ class SchedulerReportClientTests(test.TestCase):
|
||||
rps = self.client._get_providers_in_tree(self.compute_uuid)
|
||||
self.assertEqual(1, len(rps))
|
||||
|
||||
# We should also have an empty list set of aggregate UUID
|
||||
# We should also have empty sets of aggregate and trait
|
||||
# associations
|
||||
self.assertEqual(
|
||||
[], self.client._get_providers_in_aggregates([uuids.agg]))
|
||||
self.assertFalse(
|
||||
self.client._provider_tree.have_aggregates_changed(
|
||||
self.compute_uuid, []))
|
||||
self.assertFalse(
|
||||
self.client._provider_tree.have_traits_changed(
|
||||
self.compute_uuid, []))
|
||||
|
||||
# TODO(cdent): change this to use the methods built in
|
||||
# to the report client to retrieve inventory?
|
||||
|
||||
@@ -1195,10 +1195,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
'_create_resource_provider')
|
||||
@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_resource_provider')
|
||||
def test_ensure_resource_provider_exists_in_cache(self, get_rp_mock,
|
||||
get_agg_mock, create_rp_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
|
||||
@@ -1211,12 +1213,18 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
)
|
||||
|
||||
get_agg_mock.return_value = set([uuids.agg1, uuids.agg2])
|
||||
get_trait_mock.return_value = set(['CUSTOM_GOLD', 'CUSTOM_SILVER'])
|
||||
self.client._ensure_resource_provider(cn.uuid)
|
||||
get_agg_mock.assert_called_once_with(cn.uuid)
|
||||
self.assertTrue(self.client._provider_tree.in_aggregates(
|
||||
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(
|
||||
uuids.compute_node, ['CUSTOM_GOLD', 'CUSTOM_SILVER']))
|
||||
self.assertFalse(self.client._provider_tree.has_traits(
|
||||
uuids.compute_node, ['CUSTOM_GOLD', 'CUSTOM_BRONZE']))
|
||||
self.assertFalse(get_rp_mock.called)
|
||||
self.assertFalse(create_rp_mock.called)
|
||||
|
||||
@@ -1224,10 +1232,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
'_create_resource_provider')
|
||||
@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_resource_provider')
|
||||
def test_ensure_resource_provider_get(self, get_rp_mock, get_agg_mock,
|
||||
create_rp_mock):
|
||||
def test_ensure_resource_provider_get(self, get_rp_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.
|
||||
@@ -1238,6 +1248,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
}
|
||||
|
||||
get_agg_mock.return_value = set([uuids.agg1])
|
||||
get_trait_mock.return_value = set(['CUSTOM_GOLD'])
|
||||
|
||||
self.client._ensure_resource_provider(uuids.compute_node)
|
||||
|
||||
@@ -1250,6 +1261,13 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
self.assertFalse(
|
||||
self.client._provider_tree.in_aggregates(uuids.compute_node,
|
||||
[uuids.agg2]))
|
||||
get_trait_mock.assert_called_once_with(uuids.compute_node)
|
||||
self.assertTrue(
|
||||
self.client._provider_tree.has_traits(uuids.compute_node,
|
||||
['CUSTOM_GOLD']))
|
||||
self.assertFalse(
|
||||
self.client._provider_tree.has_traits(uuids.compute_node,
|
||||
['CUSTOM_SILVER']))
|
||||
self.assertTrue(self.client._provider_tree.exists(uuids.compute_node))
|
||||
self.assertFalse(create_rp_mock.called)
|
||||
|
||||
@@ -1257,10 +1275,12 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
'_create_resource_provider')
|
||||
@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_resource_provider')
|
||||
def test_ensure_resource_provider_create_fail(self, get_rp_mock,
|
||||
get_agg_mock, create_rp_mock):
|
||||
get_trait_mock, get_agg_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
|
||||
@@ -1277,18 +1297,24 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
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.assertRaises(
|
||||
ValueError,
|
||||
self.client._provider_tree.in_aggregates, uuids.compute_node, [])
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.client._provider_tree.has_traits, uuids.compute_node, [])
|
||||
|
||||
@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')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_get_resource_provider')
|
||||
def test_ensure_resource_provider_create(self, get_rp_mock, get_agg_mock,
|
||||
create_rp_mock):
|
||||
def test_ensure_resource_provider_create(self, get_rp_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.
|
||||
@@ -1299,14 +1325,17 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
'generation': 1,
|
||||
}
|
||||
get_agg_mock.return_value = set([uuids.agg1, uuids.agg2])
|
||||
get_trait_mock.return_value = set(['CUSTOM_FOO'])
|
||||
self.assertEqual(
|
||||
uuids.compute_node,
|
||||
self.client._ensure_resource_provider(uuids.compute_node))
|
||||
self._validate_provider(uuids.compute_node, name='compute-name',
|
||||
generation=1, parent_uuid=None,
|
||||
aggregates=set([uuids.agg1, uuids.agg2]))
|
||||
aggregates=set([uuids.agg1, uuids.agg2]),
|
||||
traits=set(['CUSTOM_FOO']))
|
||||
|
||||
get_agg_mock.assert_called_once_with(uuids.compute_node)
|
||||
get_trait_mock.assert_called_once_with(uuids.compute_node)
|
||||
get_rp_mock.assert_called_once_with(uuids.compute_node)
|
||||
create_rp_mock.assert_called_once_with(
|
||||
uuids.compute_node,
|
||||
@@ -1332,7 +1361,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
uuids.cn2, 'a-name', parent_provider_uuid=None)
|
||||
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_refresh_aggregates', new=mock.Mock())
|
||||
'_refresh_associations', new=mock.Mock())
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_create_resource_provider')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
@@ -1739,9 +1768,6 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
||||
|
||||
class TestAggregates(SchedulerReportClientTestCase):
|
||||
def test_get_provider_aggregates_found(self):
|
||||
"""Test that when the placement API returns a list of aggregate UUIDs,
|
||||
that we cache that aggregate information in the appropriate map.
|
||||
"""
|
||||
uuid = uuids.compute_node
|
||||
resp_mock = mock.Mock(status_code=200)
|
||||
json_data = {
|
||||
@@ -1808,66 +1834,152 @@ class TestAggregates(SchedulerReportClientTestCase):
|
||||
log_mock.call_args[0][1]['placement_req_id'])
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestTraits(SchedulerReportClientTestCase):
|
||||
def test_get_provider_traits_found(self):
|
||||
uuid = uuids.compute_node
|
||||
resp_mock = mock.Mock(status_code=200)
|
||||
traits = [
|
||||
'CUSTOM_GOLD',
|
||||
'CUSTOM_SILVER',
|
||||
]
|
||||
resp_mock.json.return_value = {'traits': traits}
|
||||
self.ks_adap_mock.get.return_value = resp_mock
|
||||
|
||||
result = self.client._get_provider_traits(uuid)
|
||||
|
||||
expected_url = '/resource_providers/' + uuid + '/traits'
|
||||
self.ks_adap_mock.get.assert_called_once_with(
|
||||
expected_url, raise_exc=False, microversion='1.6')
|
||||
self.assertEqual(set(traits), result)
|
||||
|
||||
@mock.patch.object(report.LOG, 'warning')
|
||||
def test_get_provider_traits_not_found(self, log_mock):
|
||||
"""Test that when the placement API returns a 404 when looking up a
|
||||
provider's traits, we simply return None and log a warning.
|
||||
"""
|
||||
uuid = uuids.compute_node
|
||||
self.ks_adap_mock.get.return_value = mock.Mock(
|
||||
status_code=404, headers={
|
||||
'x-openstack-request-id': uuids.request_id})
|
||||
|
||||
result = self.client._get_provider_traits(uuid)
|
||||
|
||||
expected_url = '/resource_providers/' + uuid + '/traits'
|
||||
self.ks_adap_mock.get.assert_called_once_with(
|
||||
expected_url, raise_exc=False, microversion='1.6')
|
||||
self.assertTrue(log_mock.called)
|
||||
self.assertEqual(uuids.request_id,
|
||||
log_mock.call_args[0][1]['placement_req_id'])
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch.object(report.LOG, 'error')
|
||||
def test_get_provider_traits_bad_request(self, log_mock):
|
||||
"""Test that when the placement API returns a 400 when looking up a
|
||||
provider's traits, that we simply return None and log an error.
|
||||
"""
|
||||
uuid = uuids.compute_node
|
||||
self.ks_adap_mock.get.return_value = mock.Mock(
|
||||
status_code=400, headers={
|
||||
'x-openstack-request-id': uuids.request_id})
|
||||
|
||||
result = self.client._get_provider_traits(uuid)
|
||||
|
||||
expected_url = '/resource_providers/' + uuid + '/traits'
|
||||
self.ks_adap_mock.get.assert_called_once_with(
|
||||
expected_url, raise_exc=False, microversion='1.6')
|
||||
self.assertTrue(log_mock.called)
|
||||
self.assertEqual(uuids.request_id,
|
||||
log_mock.call_args[0][1]['placement_req_id'])
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestAssociations(SchedulerReportClientTestCase):
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_get_provider_aggregates')
|
||||
def test_refresh_aggregates_no_last(self, mock_get):
|
||||
"""Test that aggregates are updated when a new provider is added."""
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_get_provider_traits')
|
||||
def test_refresh_associations_no_last(self, mock_trait_get, mock_agg_get):
|
||||
"""Test that associations are refreshed when stale."""
|
||||
uuid = uuids.compute_node
|
||||
# Seed the provider tree so _refresh_aggregates finds the provider
|
||||
# Seed the provider tree so _refresh_associations finds the provider
|
||||
self.client._provider_tree.new_root('compute', uuid, 1)
|
||||
mock_get.return_value = set([uuids.agg1])
|
||||
self.client._refresh_aggregates(uuid)
|
||||
mock_get.assert_called_once_with(uuid)
|
||||
self.assertIn(uuid, self.client.aggregate_refresh_time)
|
||||
mock_agg_get.return_value = set([uuids.agg1])
|
||||
mock_trait_get.return_value = set(['CUSTOM_GOLD'])
|
||||
self.client._refresh_associations(uuid)
|
||||
mock_agg_get.assert_called_once_with(uuid)
|
||||
mock_trait_get.assert_called_once_with(uuid)
|
||||
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.'
|
||||
'_aggregate_map_stale')
|
||||
def test_refresh_aggregates_not_stale(self, mock_stale, mock_get):
|
||||
"""Test that aggregates are not updated when there are no changes."""
|
||||
'_get_provider_traits')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_associations_stale')
|
||||
def test_refresh_associations_not_stale(self, mock_stale, mock_trait_get,
|
||||
mock_agg_get):
|
||||
"""Test that refresh associations is not called when the map is
|
||||
not stale.
|
||||
"""
|
||||
mock_stale.return_value = False
|
||||
uuid = uuids.compute_node
|
||||
self.client._refresh_aggregates(uuid)
|
||||
mock_get.assert_not_called()
|
||||
self.assertFalse(self.client.aggregate_refresh_time)
|
||||
self.client._refresh_associations(uuid)
|
||||
mock_agg_get.assert_not_called()
|
||||
mock_trait_get.assert_not_called()
|
||||
self.assertFalse(self.client.association_refresh_time)
|
||||
|
||||
@mock.patch.object(report.LOG, 'debug')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_get_provider_aggregates')
|
||||
def test_refresh_aggregates_time(self, mock_get, log_mock):
|
||||
"""Test that aggregates are updated at a given interval."""
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_get_provider_traits')
|
||||
def test_refresh_associations_time(self, 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_aggregates finds the provider
|
||||
# Seed the provider tree so _refresh_associations finds the provider
|
||||
self.client._provider_tree.new_root('compute', uuid, 1)
|
||||
mock_get.return_value = set([])
|
||||
mock_agg_get.return_value = set([])
|
||||
mock_trait_get.return_value = set([])
|
||||
|
||||
# Called a first time because aggregate_refresh_time is empty.
|
||||
# Called a first time because association_refresh_time is empty.
|
||||
now = time.time()
|
||||
self.client._refresh_aggregates(uuid)
|
||||
mock_get.assert_called_once_with(uuid)
|
||||
log_mock.assert_called_once_with(
|
||||
'Refreshing aggregate associations for resource '
|
||||
'provider %s, aggregates: %s', uuid, 'None')
|
||||
self.assertIn(uuid, self.client.aggregate_refresh_time)
|
||||
self.client._refresh_associations(uuid)
|
||||
mock_agg_get.assert_called_once_with(uuid)
|
||||
mock_trait_get.assert_called_once_with(uuid)
|
||||
log_mock.assert_has_calls([
|
||||
mock.call('Refreshing aggregate associations for resource '
|
||||
'provider %s, aggregates: %s', uuid, 'None'),
|
||||
mock.call('Refreshing trait associations for resource '
|
||||
'provider %s, traits: %s', uuid, 'None')
|
||||
])
|
||||
self.assertIn(uuid, self.client.association_refresh_time)
|
||||
|
||||
# Clear call count.
|
||||
mock_get.reset_mock()
|
||||
mock_agg_get.reset_mock()
|
||||
mock_trait_get.reset_mock()
|
||||
|
||||
with mock.patch('time.time') as mock_future:
|
||||
# Not called a second time because not enough time has passed.
|
||||
mock_future.return_value = now + report.AGGREGATE_REFRESH / 2
|
||||
self.client._refresh_aggregates(uuid)
|
||||
mock_get.assert_not_called()
|
||||
mock_future.return_value = now + report.ASSOCIATION_REFRESH / 2
|
||||
self.client._refresh_associations(uuid)
|
||||
mock_agg_get.assert_not_called()
|
||||
mock_trait_get.assert_not_called()
|
||||
|
||||
# Called because time has passed.
|
||||
mock_future.return_value = now + report.AGGREGATE_REFRESH + 1
|
||||
self.client._refresh_aggregates(uuid)
|
||||
mock_get.assert_called_once_with(uuid)
|
||||
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)
|
||||
|
||||
|
||||
class TestComputeNodeToInventoryDict(test.NoDBTestCase):
|
||||
@@ -2221,7 +2333,7 @@ There was a conflict when trying to complete your request.
|
||||
cn = self.compute_node
|
||||
# Make sure the resource provider exists for preventing to call the API
|
||||
self._init_provider_tree()
|
||||
self.client.aggregate_refresh_time[uuids.cn] = mock.Mock()
|
||||
self.client.association_refresh_time[uuids.cn] = mock.Mock()
|
||||
|
||||
mock_get.return_value.json.return_value = {
|
||||
'resource_provider_generation': 1,
|
||||
@@ -2238,7 +2350,7 @@ There was a conflict when trying to complete your request.
|
||||
self.assertIsNone(result)
|
||||
self.assertFalse(self.client._provider_tree.exists(cn.uuid))
|
||||
self.assertTrue(mock_debug.called)
|
||||
self.assertNotIn(cn.uuid, self.client.aggregate_refresh_time)
|
||||
self.assertNotIn(cn.uuid, self.client.association_refresh_time)
|
||||
self.assertIn('deleted by another thread', mock_debug.call_args[0][0])
|
||||
self.assertEqual(uuids.request_id,
|
||||
mock_debug.call_args[0][1]['placement_req_id'])
|
||||
@@ -3130,7 +3242,7 @@ class TestAllocations(SchedulerReportClientTestCase):
|
||||
def test_delete_resource_provider_no_cascade(self, mock_by_host,
|
||||
mock_del_alloc, mock_delete):
|
||||
self.client._provider_tree.new_root(uuids.cn, uuids.cn, 1)
|
||||
self.client.aggregate_refresh_time[uuids.cn] = mock.Mock()
|
||||
self.client.association_refresh_time[uuids.cn] = mock.Mock()
|
||||
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
|
||||
hypervisor_hostname="fake_hostname", )
|
||||
inst1 = objects.Instance(uuid=uuids.inst1)
|
||||
@@ -3144,7 +3256,7 @@ class TestAllocations(SchedulerReportClientTestCase):
|
||||
exp_url = "/resource_providers/%s" % uuids.cn
|
||||
mock_delete.assert_called_once_with(
|
||||
exp_url, global_request_id=self.context.global_id)
|
||||
self.assertNotIn(uuids.cn, self.client.aggregate_refresh_time)
|
||||
self.assertNotIn(uuids.cn, self.client.association_refresh_time)
|
||||
|
||||
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
||||
"delete")
|
||||
|
||||
Reference in New Issue
Block a user