Merge "placement: adds ResourceClass.destroy()"

This commit is contained in:
Jenkins 2016-12-02 16:26:12 +00:00 committed by Gerrit Code Review
commit 7dc2a74f4c
4 changed files with 129 additions and 7 deletions

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_concurrency import lockutils
import six
import sqlalchemy as sa
@ -19,6 +20,7 @@ from nova import exception
from nova.objects import fields
_RC_TBL = models.ResourceClass.__table__
_LOCKNAME = 'rc_cache'
def raise_if_custom_resource_class_pre_v1_1(rc):
@ -65,6 +67,11 @@ class ResourceClassCache(object):
self.id_cache = {}
self.str_cache = {}
def clear(self):
with lockutils.lock(_LOCKNAME):
self.id_cache = {}
self.str_cache = {}
def get_standards(self):
"""Return a list of {'id': <ID>, 'name': <NAME> for all standard
resource classes.
@ -89,13 +96,13 @@ class ResourceClassCache(object):
: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]
# First check the standard resource classes
if rc_str in fields.ResourceClass.STANDARD:
return fields.ResourceClass.STANDARD.index(rc_str)
else:
with lockutils.lock(_LOCKNAME):
if rc_str in self.id_cache:
return self.id_cache[rc_str]
# Otherwise, check the database table
_refresh_from_db(self.ctx, self)
if rc_str in self.id_cache:
@ -117,13 +124,16 @@ class ResourceClassCache(object):
: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]
# First check the fields.ResourceClass.STANDARD values
try:
return fields.ResourceClass.STANDARD[rc_id]
except IndexError:
pass
with lockutils.lock(_LOCKNAME):
if rc_id in self.str_cache:
return self.str_cache[rc_id]
# Otherwise, check the database table
_refresh_from_db(self.ctx, self)
if rc_id in self.str_cache:

View File

@ -2130,6 +2130,14 @@ class ResourceClassExists(NovaException):
msg_fmt = _("Resource class %(resource_class)s already exists.")
class ResourceClassInUse(Invalid):
msg_fmt = _("Cannot delete resource class. Class is in use in inventory.")
class ResourceClassCannotDeleteStandard(Invalid):
msg_fmt = _("Cannot delete standard resource class %(resource_class)s.")
class InvalidInventory(Invalid):
msg_fmt = _("Inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s' invalid.")

View File

@ -1215,6 +1215,36 @@ class ResourceClass(base.NovaObject):
context.session.add(rc)
return rc
def destroy(self):
if 'id' not in self:
raise exception.ObjectActionError(action='destroy',
reason='ID attribute not found')
# Never delete any standard resource class, since the standard resource
# classes don't even exist in the database table anyway.
_ensure_rc_cache(self._context)
standards = _RC_CACHE.get_standards()
if self.id in (rc['id'] for rc in standards):
raise exception.ResourceClassCannotDeleteStandard(
resource_class=self.name)
self._destroy(self._context, self.id)
_RC_CACHE.clear()
@staticmethod
@db_api.api_context_manager.writer
def _destroy(context, _id):
# Don't delete the resource class if it is referred to in the
# inventories table.
num_inv = context.session.query(models.Inventory).filter(
models.Inventory.resource_class_id == _id).count()
if num_inv:
raise exception.ResourceClassInUse()
res = context.session.query(models.ResourceClass).filter(
models.ResourceClass.id == _id).delete()
if not res:
raise exception.NotFound()
@base.NovaObjectRegistry.register
class ResourceClassList(base.ObjectListBase, base.NovaObject):

View File

@ -1148,3 +1148,77 @@ class ResourceClassTestCase(ResourceProviderBaseCase):
name='CUSTOM_IRON_NFV',
)
self.assertRaises(exception.ResourceClassExists, rc.create)
def test_destroy_fail_no_id(self):
rc = objects.ResourceClass(
self.context,
name='CUSTOM_IRON_NFV',
)
self.assertRaises(exception.ObjectActionError, rc.destroy)
def test_destroy_fail_standard(self):
rc = objects.ResourceClass.get_by_name(
self.context,
'VCPU',
)
self.assertRaises(exception.ResourceClassCannotDeleteStandard,
rc.destroy)
def test_destroy(self):
rc = objects.ResourceClass(
self.context,
name='CUSTOM_IRON_NFV',
)
rc.create()
rc_list = objects.ResourceClassList.get_all(self.context)
rc_ids = (r.id for r in rc_list)
self.assertIn(rc.id, rc_ids)
rc = objects.ResourceClass.get_by_name(
self.context,
'CUSTOM_IRON_NFV',
)
rc.destroy()
rc_list = objects.ResourceClassList.get_all(self.context)
rc_ids = (r.id for r in rc_list)
self.assertNotIn(rc.id, rc_ids)
# Verify rc cache was purged of the old entry
self.assertRaises(exception.ResourceClassNotFound,
objects.ResourceClass.get_by_name,
self.context,
'CUSTOM_IRON_NFV')
def test_destroy_fail_with_inventory(self):
"""Test that we raise an exception when attempting to delete a resource
class that is referenced in an inventory record.
"""
rc = objects.ResourceClass(
self.context,
name='CUSTOM_IRON_NFV',
)
rc.create()
rp = objects.ResourceProvider(
self.context,
name='my rp',
uuid=uuidsentinel.rp,
)
rp.create()
inv = objects.Inventory(
resource_provider=rp,
resource_class='CUSTOM_IRON_NFV',
total=1,
)
inv.obj_set_defaults()
inv_list = objects.InventoryList(objects=[inv])
rp.set_inventory(inv_list)
self.assertRaises(exception.ResourceClassInUse,
rc.destroy)
rp.set_inventory(objects.InventoryList(objects=[]))
rc.destroy()
rc_list = objects.ResourceClassList.get_all(self.context)
rc_ids = (r.id for r in rc_list)
self.assertNotIn(rc.id, rc_ids)