Merge "Ensure resource classes correctly"
This commit is contained in:
commit
87036b4b27
@ -1057,9 +1057,7 @@ class SchedulerReportClient(object):
|
||||
parent_provider_uuid=parent_provider_uuid)
|
||||
|
||||
# Auto-create custom resource classes coming from a virt driver
|
||||
for rc_name in inv_data:
|
||||
if rc_name not in fields.ResourceClass.STANDARD:
|
||||
self._ensure_resource_class(context, rc_name)
|
||||
self._ensure_resource_classes(context, set(inv_data))
|
||||
|
||||
if 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)
|
||||
|
||||
@safe_connect
|
||||
def _ensure_resource_class(self, context, name):
|
||||
"""Make sure a custom resource class exists.
|
||||
|
||||
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.
|
||||
def _ensure_resource_classes(self, context, names):
|
||||
"""Make sure resource classes exist.
|
||||
|
||||
:param context: The security context
|
||||
:param name: String name of the resource class to check/create.
|
||||
:raises: `exception.InvalidResourceClass` upon error.
|
||||
:param names: Iterable of string names of the resource classes to
|
||||
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
|
||||
response = self.put("/resource_classes/%s" % name, None, version="1.7",
|
||||
global_request_id=context.global_id)
|
||||
if 200 <= response.status_code < 300:
|
||||
return name
|
||||
else:
|
||||
msg = ("Failed to ensure resource class record with placement API "
|
||||
"for resource class %(rc_name)s. Got %(status_code)d: "
|
||||
"%(err_text)s.")
|
||||
args = {
|
||||
'rc_name': name,
|
||||
'status_code': response.status_code,
|
||||
'err_text': response.text,
|
||||
}
|
||||
LOG.error(msg, args)
|
||||
raise exception.InvalidResourceClass(resource_class=name)
|
||||
# Placement API version that supports PUT /resource_classes/CUSTOM_*
|
||||
# to create (or validate the existence of) a consumer-specified
|
||||
# resource class.
|
||||
version = '1.7'
|
||||
to_ensure = set(n for n in names
|
||||
if n.startswith(fields.ResourceClass.CUSTOM_NAMESPACE))
|
||||
|
||||
for name in to_ensure:
|
||||
# no payload on the put request
|
||||
resp = self.put(
|
||||
"/resource_classes/%s" % name, None, version=version,
|
||||
global_request_id=context.global_id)
|
||||
if not resp:
|
||||
msg = ("Failed to ensure resource class record with placement "
|
||||
"API for resource class %(rc_name)s. Got "
|
||||
"%(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):
|
||||
"""Creates or updates stats for the supplied compute node.
|
||||
|
@ -207,7 +207,7 @@ class SchedulerReportClientTests(test.TestCase):
|
||||
# Try setting some invalid inventory and make sure the report
|
||||
# client raises the expected error.
|
||||
inv_data = {
|
||||
'BAD_FOO': {
|
||||
'CUSTOM_BOGU$_CLA$$': {
|
||||
'total': 100,
|
||||
'reserved': 0,
|
||||
'min_unit': 1,
|
||||
@ -279,18 +279,8 @@ class SchedulerReportClientTests(test.TestCase):
|
||||
}
|
||||
with interceptor.RequestsInterceptor(app=self.app, url=self.url):
|
||||
self.client.update_compute_node(self.context, self.compute_node)
|
||||
# Simulate that our locally-running code has an outdated notion of
|
||||
# standard resource classes.
|
||||
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)
|
||||
self.client.set_inventory_for_provider(
|
||||
self.context, self.compute_uuid, self.compute_name, inv)
|
||||
|
||||
@mock.patch('keystoneauth1.session.Session.get_endpoint',
|
||||
return_value='http://localhost:80/placement')
|
||||
|
@ -3119,7 +3119,7 @@ There was a conflict when trying to complete your request.
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_delete_inventory')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_class')
|
||||
'_ensure_resource_classes')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_provider')
|
||||
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,
|
||||
)
|
||||
# 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(
|
||||
self.context,
|
||||
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.'
|
||||
'_delete_inventory')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_class')
|
||||
'_ensure_resource_classes')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_provider')
|
||||
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,
|
||||
parent_provider_uuid=None,
|
||||
)
|
||||
self.assertFalse(mock_erc.called)
|
||||
mock_erc.assert_called_once_with(self.context, set())
|
||||
self.assertFalse(mock_upd.called)
|
||||
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.'
|
||||
'_delete_inventory')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_class')
|
||||
'_ensure_resource_classes')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
'_ensure_resource_provider')
|
||||
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,
|
||||
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(
|
||||
self.context,
|
||||
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.'
|
||||
'_delete_inventory', new=mock.Mock())
|
||||
@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.'
|
||||
'_ensure_resource_provider')
|
||||
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
|
||||
self.assertEqual(0, mock_log.info.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)
|
||||
|
Loading…
Reference in New Issue
Block a user