Merge "placement: raise exc when resource class not found"

This commit is contained in:
Jenkins 2016-11-04 22:00:40 +00:00 committed by Gerrit Code Review
commit cdaef22aea
8 changed files with 135 additions and 23 deletions

View File

@ -340,6 +340,19 @@ def set_inventories(req):
try:
resource_provider.set_inventory(inventories)
except exception.ResourceClassNotFound as exc:
raise webob.exc.HTTPBadRequest(
_('Unknown resource class in inventory for resource provider '
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
'error': exc},
json_formatter=util.json_error_formatter)
except exception.InventoryWithResourceClassNotFound as exc:
raise webob.exc.HTTPConflict(
_('Race condition detected when setting inventory. No inventory '
'record with resource class for resource provider '
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
'error': exc},
json_formatter=util.json_error_formatter)
except (exception.ConcurrentUpdateDetected,
exception.InventoryInUse,
db_exc.DBDuplicateEntry) as exc:
@ -392,6 +405,12 @@ def update_inventory(req):
raise webob.exc.HTTPConflict(
_('update conflict: %(error)s') % {'error': exc},
json_formatter=util.json_error_formatter)
except exception.InventoryWithResourceClassNotFound as exc:
raise webob.exc.HTTPBadRequest(
_('No inventory record with resource class for resource provider '
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
'error': exc},
json_formatter=util.json_error_formatter)
except exception.InvalidInventoryCapacity as exc:
raise webob.exc.HTTPBadRequest(
_('Unable to update inventory for resource provider '

View File

@ -15,6 +15,7 @@ import sqlalchemy as sa
from nova.db.sqlalchemy import api as db_api
from nova.db.sqlalchemy import api_models as models
from nova import exception
from nova.objects import fields
_RC_TBL = models.ResourceClass.__table__
@ -78,6 +79,8 @@ class ResourceClassCache(object):
:returns integer identifier for the resource class, or None, if no such
resource class was found in the list of standard resource
classes or the resource_classes database table.
:raises `exception.ResourceClassNotFound` if rc_str cannot be found in
either the standard classes or the DB.
"""
if rc_str in self.id_cache:
return self.id_cache[rc_str]
@ -88,7 +91,9 @@ class ResourceClassCache(object):
else:
# Otherwise, check the database table
_refresh_from_db(self.ctx, self)
return self.id_cache.get(rc_str)
if rc_str in self.id_cache:
return self.id_cache[rc_str]
raise exception.ResourceClassNotFound(resource_class=rc_str)
def string_from_id(self, rc_id):
"""The reverse of the id_from_string() method. Given a supplied numeric
@ -102,6 +107,8 @@ class ResourceClassCache(object):
:returns string identifier for the resource class, or None, if no such
resource class was found in the list of standard resource
classes or the resource_classes database table.
:raises `exception.ResourceClassNotFound` if rc_id cannot be found in
either the standard classes or the DB.
"""
if rc_id in self.str_cache:
return self.str_cache[rc_id]
@ -112,4 +119,6 @@ class ResourceClassCache(object):
except IndexError:
# Otherwise, check the database table
_refresh_from_db(self.ctx, self)
return self.str_cache.get(rc_id)
if rc_id in self.str_cache:
return self.str_cache[rc_id]
raise exception.ResourceClassNotFound(resource_class=rc_id)

View File

@ -2114,10 +2114,18 @@ class ConcurrentUpdateDetected(NovaException):
"Please retry your update")
class ResourceClassNotFound(NotFound):
msg_fmt = _("No such resource class %(resource_class)s.")
class ResourceProviderInUse(NovaException):
msg_fmt = _("Resource provider has allocations.")
class InventoryWithResourceClassNotFound(NotFound):
msg_fmt = _("No inventory of class %(resource_class)s found.")
class InvalidInventory(Invalid):
msg_fmt = _("Inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s' invalid.")

View File

@ -159,8 +159,8 @@ def _update_inventory_for_provider(conn, rp, inv_list, to_update):
allocation_ratio=inv_record.allocation_ratio)
res = conn.execute(upd_stmt)
if not res.rowcount:
raise exception.NotFound(
'No inventory of class %s found for update' % rc_str)
raise exception.InventoryWithResourceClassNotFound(
resource_class=rc_str)
return exceeded
@ -191,12 +191,13 @@ def _increment_provider_generation(conn, rp):
@db_api.api_context_manager.writer
def _add_inventory(context, rp, inventory):
"""Add one Inventory that wasn't already on the provider."""
"""Add one Inventory that wasn't already on the provider.
:raises `exception.ResourceClassNotFound` if inventory.resource_class
cannot be found in either the standard classes or the DB.
"""
_ensure_rc_cache(context)
rc_id = _RC_CACHE.id_from_string(inventory.resource_class)
if rc_id is None:
raise exception.NotFound("No such resource class '%s'" %
inventory.resource_class)
inv_list = InventoryList(objects=[inventory])
conn = context.session.connection()
with conn.begin():
@ -207,12 +208,13 @@ def _add_inventory(context, rp, inventory):
@db_api.api_context_manager.writer
def _update_inventory(context, rp, inventory):
"""Update an inventory already on the provider."""
"""Update an inventory already on the provider.
:raises `exception.ResourceClassNotFound` if inventory.resource_class
cannot be found in either the standard classes or the DB.
"""
_ensure_rc_cache(context)
rc_id = _RC_CACHE.id_from_string(inventory.resource_class)
if rc_id is None:
raise exception.NotFound("No such resource class '%s'" %
inventory.resource_class)
inv_list = InventoryList(objects=[inventory])
conn = context.session.connection()
with conn.begin():
@ -224,7 +226,11 @@ def _update_inventory(context, rp, inventory):
@db_api.api_context_manager.writer
def _delete_inventory(context, rp, resource_class):
"""Delete up to one Inventory of the given resource_class string."""
"""Delete up to one Inventory of the given resource_class string.
:raises `exception.ResourceClassNotFound` if resource_class
cannot be found in either the standard classes or the DB.
"""
_ensure_rc_cache(context)
conn = context.session.connection()
rc_id = _RC_CACHE.id_from_string(resource_class)
@ -251,6 +257,9 @@ def _set_inventory(context, rp, inv_list):
the same resource provider's view of its inventory or allocations
in between the time when this object was originally read
and the call to set the inventory.
:raises `exception.ResourceClassNotFound` if any resource class in any
inventory in inv_list cannot be found in either the standard
classes or the DB.
"""
_ensure_rc_cache(context)
conn = context.session.connection()
@ -853,16 +862,19 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
We must check that there is capacity for each allocation.
If there is not we roll back the entire set.
:raises `exception.ResourceClassNotFound` if any resource class in any
allocation in allocs cannot be found in either the standard
classes or the DB.
"""
_ensure_rc_cache(context)
conn = context.session.connection()
# Short-circuit out if there are any allocations with string
# resource class names that don't exist.
# resource class names that don't exist this will raise a
# ResourceClassNotFound exception.
for alloc in allocs:
if _RC_CACHE.id_from_string(alloc.resource_class) is None:
raise exception.NotFound("No such resource class '%s'" %
alloc.resource_class)
_RC_CACHE.id_from_string(alloc.resource_class)
# Before writing any allocation records, we check that the submitted
# allocations do not cause any inventory capacity to be exceeded for

View File

@ -144,7 +144,7 @@ tests:
COWS: 12
status: 400
response_strings:
- No such resource class 'COWS'
- No such resource class COWS
- name: delete allocation
DELETE: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958

View File

@ -107,6 +107,17 @@ tests:
response_strings:
- resource provider generation conflict
- name: modify inventory no such resource class in inventory
PUT: /resource_providers/$ENVIRON['RP_UUID']/inventories/MEMORY_MB
request_headers:
content-type: application/json
data:
resource_provider_generation: 2
total: 2048
status: 400
response_strings:
- No inventory record with resource class
- name: modify inventory invalid data
desc: This should 400 because reserved is greater than total
PUT: $LAST_URL
@ -163,7 +174,7 @@ tests:
total: 2048
status: 400
response_strings:
- No such resource class 'NO_CLASS_14'
- No such resource class NO_CLASS_14
- name: post inventory duplicated resource class
desc: DISK_GB was already created above
@ -302,6 +313,19 @@ tests:
response_strings:
- resource provider generation conflict
- name: put all inventory unknown resource class
PUT: /resource_providers/$ENVIRON['RP_UUID']/inventories
request_headers:
content-type: application/json
data:
resource_provider_generation: 6
inventories:
HOUSE:
total: 253
status: 400
response_strings:
- Unknown resource class in inventory
# NOTE(cdent): The generation is 6 now, based on the activity at
# the start of this file.
- name: put all inventory bad capacity

View File

@ -13,6 +13,7 @@
import mock
from nova.db.sqlalchemy import resource_class_cache as rc_cache
from nova import exception
from nova import test
from nova.tests import fixtures
@ -49,9 +50,12 @@ class TestResourceClassCache(test.TestCase):
"""
cache = rc_cache.ResourceClassCache(self.context)
# Haven't added anything to the DB yet, so should return None
self.assertIsNone(cache.string_from_id(1001))
self.assertIsNone(cache.id_from_string("IRON_NFV"))
# Haven't added anything to the DB yet, so should raise
# ResourceClassNotFound
self.assertRaises(exception.ResourceClassNotFound,
cache.string_from_id, 1001)
self.assertRaises(exception.ResourceClassNotFound,
cache.id_from_string, "IRON_NFV")
# Now add to the database and verify appropriate results...
with self.context.session.connection() as conn:
@ -69,3 +73,13 @@ class TestResourceClassCache(test.TestCase):
self.assertEqual('IRON_NFV', cache.string_from_id(1001))
self.assertEqual(1001, cache.id_from_string('IRON_NFV'))
self.assertFalse(sel_mock.called)
def test_rc_cache_miss(self):
"""Test that we raise ResourceClassNotFound if an unknown resource
class ID or string is searched for.
"""
cache = rc_cache.ResourceClassCache(self.context)
self.assertRaises(exception.ResourceClassNotFound,
cache.string_from_id, 99999999)
self.assertRaises(exception.ResourceClassNotFound,
cache.id_from_string, 'UNKNOWN')

View File

@ -200,6 +200,32 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
self.context, resource_provider.uuid))
self.assertEqual(33, reloaded_inventories[0].total)
def test_set_inventory_unknown_resource_class(self):
"""Test attempting to set inventory to an unknown resource class raises
an exception.
"""
rp = objects.ResourceProvider(
context=self.context,
uuid=uuidsentinel.rp_uuid,
name='compute-host',
)
rp.create()
inv = objects.Inventory(
resource_provider=rp,
resource_class='UNKNOWN',
total=1024,
reserved=15,
min_unit=10,
max_unit=100,
step_size=10,
allocation_ratio=1.0,
)
inv_list = objects.InventoryList(objects=[inv])
self.assertRaises(exception.ResourceClassNotFound,
rp.set_inventory, inv_list)
@mock.patch('nova.objects.resource_provider.LOG')
def test_set_inventory_over_capacity(self, mock_log):
rp = objects.ResourceProvider(context=self.context,
@ -419,7 +445,7 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
disk_inv.obj_set_defaults()
error = self.assertRaises(exception.NotFound, rp.update_inventory,
disk_inv)
self.assertIn('No inventory of class DISK_GB found for update',
self.assertIn('No inventory of class DISK_GB found',
str(error))
@mock.patch('nova.objects.resource_provider.LOG')