Merge "Ensure resource classes correctly"

This commit is contained in:
Zuul 2018-02-13 23:25:48 +00:00 committed by Gerrit Code Review
commit 87036b4b27
3 changed files with 79 additions and 48 deletions

View File

@ -1057,9 +1057,7 @@ class SchedulerReportClient(object):
parent_provider_uuid=parent_provider_uuid) parent_provider_uuid=parent_provider_uuid)
# Auto-create custom resource classes coming from a virt driver # Auto-create custom resource classes coming from a virt driver
for rc_name in inv_data: self._ensure_resource_classes(context, set(inv_data))
if rc_name not in fields.ResourceClass.STANDARD:
self._ensure_resource_class(context, rc_name)
if inv_data: if inv_data:
self._update_inventory(context, rp_uuid, inv_data) self._update_inventory(context, rp_uuid, inv_data)
@ -1221,34 +1219,38 @@ class SchedulerReportClient(object):
raise exception.ResourceProviderUpdateFailed(url=url, error=resp.text) raise exception.ResourceProviderUpdateFailed(url=url, error=resp.text)
@safe_connect @safe_connect
def _ensure_resource_class(self, context, name): def _ensure_resource_classes(self, context, names):
"""Make sure a custom resource class exists. """Make sure resource classes exist.
PUT the resource class using microversion 1.7.
Returns the name of the resource class if it was successfully
created or already exists. Otherwise None.
:param context: The security context :param context: The security context
:param name: String name of the resource class to check/create. :param names: Iterable of string names of the resource classes to
:raises: `exception.InvalidResourceClass` upon error. check/create. Must not be None.
:raises: exception.InvalidResourceClass if an attempt is made to create
an invalid resource class.
""" """
# no payload on the put request # Placement API version that supports PUT /resource_classes/CUSTOM_*
response = self.put("/resource_classes/%s" % name, None, version="1.7", # to create (or validate the existence of) a consumer-specified
global_request_id=context.global_id) # resource class.
if 200 <= response.status_code < 300: version = '1.7'
return name to_ensure = set(n for n in names
else: if n.startswith(fields.ResourceClass.CUSTOM_NAMESPACE))
msg = ("Failed to ensure resource class record with placement API "
"for resource class %(rc_name)s. Got %(status_code)d: " for name in to_ensure:
"%(err_text)s.") # no payload on the put request
args = { resp = self.put(
'rc_name': name, "/resource_classes/%s" % name, None, version=version,
'status_code': response.status_code, global_request_id=context.global_id)
'err_text': response.text, if not resp:
} msg = ("Failed to ensure resource class record with placement "
LOG.error(msg, args) "API for resource class %(rc_name)s. Got "
raise exception.InvalidResourceClass(resource_class=name) "%(status_code)d: %(err_text)s.")
args = {
'rc_name': name,
'status_code': resp.status_code,
'err_text': resp.text,
}
LOG.error(msg, args)
raise exception.InvalidResourceClass(resource_class=name)
def update_compute_node(self, context, compute_node): def update_compute_node(self, context, compute_node):
"""Creates or updates stats for the supplied compute node. """Creates or updates stats for the supplied compute node.

View File

@ -207,7 +207,7 @@ class SchedulerReportClientTests(test.TestCase):
# Try setting some invalid inventory and make sure the report # Try setting some invalid inventory and make sure the report
# client raises the expected error. # client raises the expected error.
inv_data = { inv_data = {
'BAD_FOO': { 'CUSTOM_BOGU$_CLA$$': {
'total': 100, 'total': 100,
'reserved': 0, 'reserved': 0,
'min_unit': 1, 'min_unit': 1,
@ -279,18 +279,8 @@ class SchedulerReportClientTests(test.TestCase):
} }
with interceptor.RequestsInterceptor(app=self.app, url=self.url): with interceptor.RequestsInterceptor(app=self.app, url=self.url):
self.client.update_compute_node(self.context, self.compute_node) self.client.update_compute_node(self.context, self.compute_node)
# Simulate that our locally-running code has an outdated notion of self.client.set_inventory_for_provider(
# standard resource classes. self.context, self.compute_uuid, self.compute_name, inv)
with mock.patch.object(fields.ResourceClass, 'STANDARD',
('VCPU', 'MEMORY_MB', 'DISK_GB')):
# TODO(efried): Once bug #1746615 is fixed, this will no longer
# raise, and can be replaced with:
# self.client.set_inventory_for_provider(
# self.context, self.compute_uuid, self.compute_name, inv)
self.assertRaises(
exception.InvalidResourceClass,
self.client.set_inventory_for_provider,
self.context, self.compute_uuid, self.compute_name, inv)
@mock.patch('keystoneauth1.session.Session.get_endpoint', @mock.patch('keystoneauth1.session.Session.get_endpoint',
return_value='http://localhost:80/placement') return_value='http://localhost:80/placement')

View File

@ -3119,7 +3119,7 @@ There was a conflict when trying to complete your request.
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_delete_inventory') '_delete_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_class') '_ensure_resource_classes')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider') '_ensure_resource_provider')
def test_set_inventory_for_provider_no_custom(self, mock_erp, mock_erc, def test_set_inventory_for_provider_no_custom(self, mock_erp, mock_erc,
@ -3166,7 +3166,8 @@ There was a conflict when trying to complete your request.
parent_provider_uuid=None, parent_provider_uuid=None,
) )
# No custom resource classes to ensure... # No custom resource classes to ensure...
self.assertFalse(mock_erc.called) mock_erc.assert_called_once_with(self.context,
set(['VCPU', 'MEMORY_MB', 'DISK_GB']))
mock_upd.assert_called_once_with( mock_upd.assert_called_once_with(
self.context, self.context,
mock.sentinel.rp_uuid, mock.sentinel.rp_uuid,
@ -3179,7 +3180,7 @@ There was a conflict when trying to complete your request.
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_delete_inventory') '_delete_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_class') '_ensure_resource_classes')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider') '_ensure_resource_provider')
def test_set_inventory_for_provider_no_inv(self, mock_erp, mock_erc, def test_set_inventory_for_provider_no_inv(self, mock_erp, mock_erc,
@ -3200,7 +3201,7 @@ There was a conflict when trying to complete your request.
mock.sentinel.rp_name, mock.sentinel.rp_name,
parent_provider_uuid=None, parent_provider_uuid=None,
) )
self.assertFalse(mock_erc.called) mock_erc.assert_called_once_with(self.context, set())
self.assertFalse(mock_upd.called) self.assertFalse(mock_upd.called)
mock_del.assert_called_once_with(self.context, mock.sentinel.rp_uuid) mock_del.assert_called_once_with(self.context, mock.sentinel.rp_uuid)
@ -3209,7 +3210,7 @@ There was a conflict when trying to complete your request.
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_delete_inventory') '_delete_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_class') '_ensure_resource_classes')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider') '_ensure_resource_provider')
def test_set_inventory_for_provider_with_custom(self, mock_erp, def test_set_inventory_for_provider_with_custom(self, mock_erp,
@ -3265,7 +3266,9 @@ There was a conflict when trying to complete your request.
mock.sentinel.rp_name, mock.sentinel.rp_name,
parent_provider_uuid=None, parent_provider_uuid=None,
) )
mock_erc.assert_called_once_with(self.context, 'CUSTOM_IRON_SILVER') mock_erc.assert_called_once_with(
self.context,
set(['VCPU', 'MEMORY_MB', 'DISK_GB', 'CUSTOM_IRON_SILVER']))
mock_upd.assert_called_once_with( mock_upd.assert_called_once_with(
self.context, self.context,
mock.sentinel.rp_uuid, mock.sentinel.rp_uuid,
@ -3276,7 +3279,7 @@ There was a conflict when trying to complete your request.
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_delete_inventory', new=mock.Mock()) '_delete_inventory', new=mock.Mock())
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_class', new=mock.Mock()) '_ensure_resource_classes', new=mock.Mock())
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.' @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider') '_ensure_resource_provider')
def test_set_inventory_for_provider_with_parent(self, mock_erp): def test_set_inventory_for_provider_with_parent(self, mock_erp):
@ -3588,3 +3591,39 @@ class TestAllocations(SchedulerReportClientTestCase):
# With a 409, only the error should be called # With a 409, only the error should be called
self.assertEqual(0, mock_log.info.call_count) self.assertEqual(0, mock_log.info.call_count)
self.assertEqual(1, mock_log.error.call_count) self.assertEqual(1, mock_log.error.call_count)
class TestResourceClass(SchedulerReportClientTestCase):
def setUp(self):
super(TestResourceClass, self).setUp()
_put_patch = mock.patch(
"nova.scheduler.client.report.SchedulerReportClient.put")
self.addCleanup(_put_patch.stop)
self.mock_put = _put_patch.start()
def test_ensure_resource_classes(self):
rcs = ['VCPU', 'CUSTOM_FOO', 'MEMORY_MB', 'CUSTOM_BAR']
self.client._ensure_resource_classes(self.context, rcs)
self.mock_put.assert_has_calls([
mock.call('/resource_classes/%s' % rc, None, version='1.7',
global_request_id=self.context.global_id)
for rc in ('CUSTOM_FOO', 'CUSTOM_BAR')
], any_order=True)
def test_ensure_resource_classes_none(self):
for empty in ([], (), set(), {}):
self.client._ensure_resource_classes(self.context, empty)
self.mock_put.assert_not_called()
def test_ensure_resource_classes_put_fail(self):
resp = requests.Response()
resp.status_code = 503
self.mock_put.return_value = resp
rcs = ['VCPU', 'MEMORY_MB', 'CUSTOM_BAD']
self.assertRaises(
exception.InvalidResourceClass,
self.client._ensure_resource_classes, self.context, rcs)
# Only called with the "bad" one
self.mock_put.assert_called_once_with(
'/resource_classes/CUSTOM_BAD', None, version='1.7',
global_request_id=self.context.global_id)