Merge "placement: adds ResourceClass.destroy()"
This commit is contained in:
commit
7dc2a74f4c
@ -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:
|
||||
|
@ -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.")
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user