From 1a072eab339bef29a0a07bb3ab431ef682a9d4c0 Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Mon, 5 Mar 2018 19:46:36 +0000 Subject: [PATCH] Move placement exceptions into the placement package This change moves the exceptions raised within the placement service into the nova.api.openstack.placement package. This is part of the process of unifying all placement code into one subdirectory so that a future lift and shift is easier and cleaner. This is mostly a straightforward change of imports and a moving of existing exceptions in the placements handlers and objects with a few caveats: * Dealing with nova/db/sqlalchemy/resource_class_cache.py has not yet been accomplished, so it remains where it is and imports exceptions from the placement hierarchy. A TODO indicating some of the options is left for future work. * Exceptions with the name ResourceProviderInUse and InventoryInUse are used on both sides of the nova/placement interaction. This is noted with comments in both nova/exception.py and placement/exception.py. * test_report_client.py has had a TODO added to make it clear that the exceptions it uses are nova-side, and perhaps the test file should be moved. * The base class of the original exceptions, NoveException, does a bit more than is required on the placement side so a new private class _BaseException is created which removes support for handling 'code'. This is not required because all exceptions in placement are supposed to be manually caught in handlers and explicitly transformed into http exceptions. blueprint placement-extract Change-Id: I2b94945a0963d6a61af931505b69afe2d4733759 --- nova/api/openstack/placement/exception.py | 167 ++++++++++++++++++ nova/api/openstack/placement/handler.py | 2 +- .../placement/handlers/allocation.py | 2 +- .../handlers/allocation_candidate.py | 2 +- .../openstack/placement/handlers/inventory.py | 2 +- .../placement/handlers/resource_class.py | 2 +- .../placement/handlers/resource_provider.py | 2 +- .../api/openstack/placement/handlers/trait.py | 2 +- .../api/openstack/placement/handlers/usage.py | 2 +- .../placement/objects/resource_provider.py | 2 +- nova/db/sqlalchemy/resource_class_cache.py | 5 +- nova/exception.py | 78 +------- .../openstack/placement/test_report_client.py | 4 + .../db/test_allocation_candidates.py | 2 +- .../db/test_resource_class_cache.py | 2 +- .../functional/db/test_resource_provider.py | 2 +- .../objects/test_resource_provider.py | 2 +- 17 files changed, 192 insertions(+), 88 deletions(-) create mode 100644 nova/api/openstack/placement/exception.py diff --git a/nova/api/openstack/placement/exception.py b/nova/api/openstack/placement/exception.py new file mode 100644 index 000000000000..23deba009b43 --- /dev/null +++ b/nova/api/openstack/placement/exception.py @@ -0,0 +1,167 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Exceptions for use in the Placement API.""" + +# NOTE(cdent): The exceptions are copied from nova.exception, where they +# were originally used. To prepare for extracting placement to its own +# repository we wish to no longer do that. Instead, exceptions used by +# placement should be in the placement hierarchy. +# TODO(cdent): Because of the duplication it is likely that some of the +# functionality provided by _BaseException is redundant and can be factored +# out. + +from oslo_log import log as logging + +from nova.i18n import _, _LE + + +LOG = logging.getLogger(__name__) + + +class _BaseException(Exception): + """Base Exception + + To correctly use this class, inherit from it and define + a 'msg_fmt' property. That msg_fmt will get printf'd + with the keyword arguments provided to the constructor. + + """ + msg_fmt = _("An unknown exception occurred.") + + def __init__(self, message=None, **kwargs): + self.kwargs = kwargs + + if not message: + try: + message = self.msg_fmt % kwargs + except Exception: + # NOTE(melwitt): This is done in a separate method so it can be + # monkey-patched during testing to make it a hard failure. + self._log_exception() + message = self.msg_fmt + + self.message = message + super(_BaseException, self).__init__(message) + + def _log_exception(self): + # kwargs doesn't match a variable in the message + # log the issue and the kwargs + LOG.exception(_LE('Exception in string format operation')) + for name, value in self.kwargs.items(): + LOG.error("%s: %s" % (name, value)) # noqa + + def format_message(self): + # Use the first argument to the python Exception object which + # should be our full exception message, (see __init__). + return self.args[0] + + +class NotFound(_BaseException): + msg_fmt = _("Resource could not be found.") + + +class InvalidInventory(_BaseException): + msg_fmt = _("Inventory for '%(resource_class)s' on " + "resource provider '%(resource_provider)s' invalid.") + + +class CannotDeleteParentResourceProvider(_BaseException): + msg_fmt = _("Cannot delete resource provider that is a parent of " + "another. Delete child providers first.") + + +class ConcurrentUpdateDetected(_BaseException): + msg_fmt = _("Another thread concurrently updated the data. " + "Please retry your update") + + +class InvalidAllocationCapacityExceeded(InvalidInventory): + msg_fmt = _("Unable to create allocation for '%(resource_class)s' on " + "resource provider '%(resource_provider)s'. The requested " + "amount would exceed the capacity.") + + +class InvalidAllocationConstraintsViolated(InvalidInventory): + msg_fmt = _("Unable to create allocation for '%(resource_class)s' on " + "resource provider '%(resource_provider)s'. The requested " + "amount would violate inventory constraints.") + + +class InvalidInventoryCapacity(InvalidInventory): + msg_fmt = _("Invalid inventory for '%(resource_class)s' on " + "resource provider '%(resource_provider)s'. " + "The reserved value is greater than or equal to total.") + + +# An exception with this name is used on both sides of the placement/ +# nova interaction. +class InventoryInUse(InvalidInventory): + # NOTE(mriedem): This message cannot change without impacting the + # nova.scheduler.client.report._RE_INV_IN_USE regex. + msg_fmt = _("Inventory for '%(resource_classes)s' on " + "resource provider '%(resource_provider)s' in use.") + + +class InventoryWithResourceClassNotFound(NotFound): + msg_fmt = _("No inventory of class %(resource_class)s found.") + + +class MaxDBRetriesExceeded(_BaseException): + msg_fmt = _("Max retries of DB transaction exceeded attempting to " + "perform %(action)s.") + + +class ObjectActionError(_BaseException): + msg_fmt = _('Object action %(action)s failed because: %(reason)s') + + +class ResourceClassCannotDeleteStandard(_BaseException): + msg_fmt = _("Cannot delete standard resource class %(resource_class)s.") + + +class ResourceClassCannotUpdateStandard(_BaseException): + msg_fmt = _("Cannot update standard resource class %(resource_class)s.") + + +class ResourceClassExists(_BaseException): + msg_fmt = _("Resource class %(resource_class)s already exists.") + + +class ResourceClassInUse(_BaseException): + msg_fmt = _("Cannot delete resource class %(resource_class)s. " + "Class is in use in inventory.") + + +class ResourceClassNotFound(NotFound): + msg_fmt = _("No such resource class %(resource_class)s.") + + +# An exception with this name is used on both sides of the placement/ +# nova interaction. +class ResourceProviderInUse(_BaseException): + msg_fmt = _("Resource provider has allocations.") + + +class TraitCannotDeleteStandard(_BaseException): + msg_fmt = _("Cannot delete standard trait %(name)s.") + + +class TraitExists(_BaseException): + msg_fmt = _("The Trait %(name)s already exists") + + +class TraitInUse(_BaseException): + msg_fmt = _("The trait %(name)s is in use by a resource provider.") + + +class TraitNotFound(NotFound): + msg_fmt = _("No such trait(s): %(names)s.") diff --git a/nova/api/openstack/placement/handler.py b/nova/api/openstack/placement/handler.py index 092125eaa6e9..18536bb133c4 100644 --- a/nova/api/openstack/placement/handler.py +++ b/nova/api/openstack/placement/handler.py @@ -28,6 +28,7 @@ import webob from oslo_log import log as logging +from nova.api.openstack.placement import exception from nova.api.openstack.placement.handlers import aggregate from nova.api.openstack.placement.handlers import allocation from nova.api.openstack.placement.handlers import allocation_candidate @@ -39,7 +40,6 @@ from nova.api.openstack.placement.handlers import trait from nova.api.openstack.placement.handlers import usage from nova.api.openstack.placement import policy from nova.api.openstack.placement import util -from nova import exception from nova.i18n import _ LOG = logging.getLogger(__name__) diff --git a/nova/api/openstack/placement/handlers/allocation.py b/nova/api/openstack/placement/handlers/allocation.py index 61d733adbda6..a8e09dc81dd1 100644 --- a/nova/api/openstack/placement/handlers/allocation.py +++ b/nova/api/openstack/placement/handlers/allocation.py @@ -19,12 +19,12 @@ from oslo_utils import encodeutils from oslo_utils import timeutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import allocation as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/allocation_candidate.py b/nova/api/openstack/placement/handlers/allocation_candidate.py index bc8e73151088..a92bd57fad7f 100644 --- a/nova/api/openstack/placement/handlers/allocation_candidate.py +++ b/nova/api/openstack/placement/handlers/allocation_candidate.py @@ -20,12 +20,12 @@ from oslo_utils import timeutils import six import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import allocation_candidate as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/inventory.py b/nova/api/openstack/placement/handlers/inventory.py index f3f22e45be04..7e83d5e0b9d0 100644 --- a/nova/api/openstack/placement/handlers/inventory.py +++ b/nova/api/openstack/placement/handlers/inventory.py @@ -18,13 +18,13 @@ from oslo_serialization import jsonutils from oslo_utils import encodeutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import inventory as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper from nova.db import constants as db_const -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/resource_class.py b/nova/api/openstack/placement/handlers/resource_class.py index 5268131eae31..97e37ca60f0e 100644 --- a/nova/api/openstack/placement/handlers/resource_class.py +++ b/nova/api/openstack/placement/handlers/resource_class.py @@ -16,12 +16,12 @@ from oslo_utils import encodeutils from oslo_utils import timeutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import resource_class as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/resource_provider.py b/nova/api/openstack/placement/handlers/resource_provider.py index fb0b3c849b71..528676a3e2fe 100644 --- a/nova/api/openstack/placement/handlers/resource_provider.py +++ b/nova/api/openstack/placement/handlers/resource_provider.py @@ -18,12 +18,12 @@ from oslo_utils import timeutils from oslo_utils import uuidutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import resource_provider as rp_schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/trait.py b/nova/api/openstack/placement/handlers/trait.py index c48d9cdda4ed..cd825d3b7999 100644 --- a/nova/api/openstack/placement/handlers/trait.py +++ b/nova/api/openstack/placement/handlers/trait.py @@ -17,12 +17,12 @@ from oslo_utils import encodeutils from oslo_utils import timeutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import trait as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/handlers/usage.py b/nova/api/openstack/placement/handlers/usage.py index b9acb7bb46a8..43689e090ba8 100644 --- a/nova/api/openstack/placement/handlers/usage.py +++ b/nova/api/openstack/placement/handlers/usage.py @@ -16,12 +16,12 @@ from oslo_utils import encodeutils from oslo_utils import timeutils import webob +from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova.api.openstack.placement.schemas import usage as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper -from nova import exception from nova.i18n import _ diff --git a/nova/api/openstack/placement/objects/resource_provider.py b/nova/api/openstack/placement/objects/resource_provider.py index 39eebbd8326e..34da0260b47a 100644 --- a/nova/api/openstack/placement/objects/resource_provider.py +++ b/nova/api/openstack/placement/objects/resource_provider.py @@ -36,10 +36,10 @@ from sqlalchemy import func from sqlalchemy import sql from sqlalchemy.sql import null +from nova.api.openstack.placement import exception from nova.db.sqlalchemy import api as db_api from nova.db.sqlalchemy import api_models as models from nova.db.sqlalchemy import resource_class_cache as rc_cache -from nova import exception from nova.i18n import _ from nova import rc_fields diff --git a/nova/db/sqlalchemy/resource_class_cache.py b/nova/db/sqlalchemy/resource_class_cache.py index a31c666f2f73..413c91c1dc5b 100644 --- a/nova/db/sqlalchemy/resource_class_cache.py +++ b/nova/db/sqlalchemy/resource_class_cache.py @@ -14,9 +14,12 @@ from oslo_concurrency import lockutils import six import sqlalchemy as sa +# TODO(cdent): This file and its location is problematic for placement +# extraction but we probably want to switch to os-resource-classes (like +# os-traits) instead of moving it? +from nova.api.openstack.placement import exception from nova.db.sqlalchemy import api as db_api from nova.db.sqlalchemy import api_models as models -from nova import exception from nova import rc_fields as fields _RC_TBL = models.ResourceClass.__table__ diff --git a/nova/exception.py b/nova/exception.py index 78ecef36584e..22b391029f3a 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2068,11 +2068,6 @@ class LibguestfsCannotReadKernel(Invalid): msg_fmt = _("Libguestfs does not have permission to read host kernel.") -class MaxDBRetriesExceeded(NovaException): - msg_fmt = _("Max retries of DB transaction exceeded attempting to " - "perform %(action)s.") - - class RealtimePolicyNotSupported(Invalid): msg_fmt = _("Realtime policy not supported by hypervisor") @@ -2107,20 +2102,8 @@ class InvalidReservedMemoryPagesOption(Invalid): "config-reference.") -class ConcurrentUpdateDetected(NovaException): - msg_fmt = _("Another thread concurrently updated the data. " - "Please retry your update") - - -class ResourceClassNotFound(NotFound): - msg_fmt = _("No such resource class %(resource_class)s.") - - -class CannotDeleteParentResourceProvider(NovaException): - msg_fmt = _("Cannot delete resource provider that is a parent of " - "another. Delete child providers first.") - - +# An exception with this name is used on both sides of the placement/ +# nova interaction. class ResourceProviderInUse(NovaException): msg_fmt = _("Resource provider has allocations.") @@ -2168,31 +2151,10 @@ class ResourceProviderUpdateConflict(PlacementAPIConflict): "provider %(uuid)s (generation %(generation)d): %(error)s") -class InventoryWithResourceClassNotFound(NotFound): - msg_fmt = _("No inventory of class %(resource_class)s found.") - - class InvalidResourceClass(Invalid): msg_fmt = _("Resource class '%(resource_class)s' invalid.") -class ResourceClassExists(NovaException): - msg_fmt = _("Resource class %(resource_class)s already exists.") - - -class ResourceClassInUse(Invalid): - msg_fmt = _("Cannot delete resource class %(resource_class)s. " - "Class is in use in inventory.") - - -class ResourceClassCannotDeleteStandard(Invalid): - msg_fmt = _("Cannot delete standard resource class %(resource_class)s.") - - -class ResourceClassCannotUpdateStandard(Invalid): - msg_fmt = _("Cannot update standard resource class %(resource_class)s.") - - class InvalidResourceAmount(Invalid): msg_fmt = _("Resource amounts must be integers. Received '%(amount)s'.") @@ -2202,6 +2164,8 @@ class InvalidInventory(Invalid): "resource provider '%(resource_provider)s' invalid.") +# An exception with this name is used on both sides of the placement/ +# nova interaction. class InventoryInUse(InvalidInventory): # NOTE(mriedem): This message cannot change without impacting the # nova.scheduler.client.report._RE_INV_IN_USE regex. @@ -2209,24 +2173,6 @@ class InventoryInUse(InvalidInventory): "resource provider '%(resource_provider)s' in use.") -class InvalidInventoryCapacity(InvalidInventory): - msg_fmt = _("Invalid inventory for '%(resource_class)s' on " - "resource provider '%(resource_provider)s'. " - "The reserved value is greater than or equal to total.") - - -class InvalidAllocationCapacityExceeded(InvalidInventory): - msg_fmt = _("Unable to create allocation for '%(resource_class)s' on " - "resource provider '%(resource_provider)s'. The requested " - "amount would exceed the capacity.") - - -class InvalidAllocationConstraintsViolated(InvalidInventory): - msg_fmt = _("Unable to create allocation for '%(resource_class)s' on " - "resource provider '%(resource_provider)s'. The requested " - "amount would violate inventory constraints.") - - class UnsupportedPointerModelRequested(Invalid): msg_fmt = _("Pointer model '%(model)s' requested is not supported by " "host.") @@ -2268,22 +2214,6 @@ class PowerVMAPIFailed(NovaException): "%(reason)s") -class TraitNotFound(NotFound): - msg_fmt = _("No such trait(s): %(names)s.") - - -class TraitExists(NovaException): - msg_fmt = _("The Trait %(name)s already exists") - - -class TraitCannotDeleteStandard(Invalid): - msg_fmt = _("Cannot delete standard trait %(name)s.") - - -class TraitInUse(Invalid): - msg_fmt = _("The trait %(name)s is in use by a resource provider.") - - class TraitRetrievalFailed(NovaException): msg_fmt = _("Failed to retrieve traits from the placement API: %(error)s") diff --git a/nova/tests/functional/api/openstack/placement/test_report_client.py b/nova/tests/functional/api/openstack/placement/test_report_client.py index 27fe1ee2d4e4..36f4d31184c6 100644 --- a/nova/tests/functional/api/openstack/placement/test_report_client.py +++ b/nova/tests/functional/api/openstack/placement/test_report_client.py @@ -20,6 +20,10 @@ from wsgi_intercept import interceptor from nova.api.openstack.placement import deploy from nova import conf from nova import context +# TODO(cdent): This points to the nova, not placement, exception for +# InvalidResourceClass. This test should probably move out of the +# placement hierarchy since it expects a "standard" placement server +# and is not testing the placement service itself. from nova import exception from nova import objects from nova import rc_fields as fields diff --git a/nova/tests/functional/db/test_allocation_candidates.py b/nova/tests/functional/db/test_allocation_candidates.py index 08942de00c5e..d113604fe3d3 100644 --- a/nova/tests/functional/db/test_allocation_candidates.py +++ b/nova/tests/functional/db/test_allocation_candidates.py @@ -13,10 +13,10 @@ import os_traits from oslo_utils import uuidutils import sqlalchemy as sa +from nova.api.openstack.placement import exception from nova.api.openstack.placement import lib as placement_lib from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova import context -from nova import exception from nova import rc_fields as fields from nova import test from nova.tests import fixtures diff --git a/nova/tests/functional/db/test_resource_class_cache.py b/nova/tests/functional/db/test_resource_class_cache.py index d0a5a835d2d8..8ab48a1dbc70 100644 --- a/nova/tests/functional/db/test_resource_class_cache.py +++ b/nova/tests/functional/db/test_resource_class_cache.py @@ -15,8 +15,8 @@ import mock from oslo_utils import timeutils +from nova.api.openstack.placement import exception from nova.db.sqlalchemy import resource_class_cache as rc_cache -from nova import exception from nova import rc_fields as fields from nova import test from nova.tests import fixtures diff --git a/nova/tests/functional/db/test_resource_provider.py b/nova/tests/functional/db/test_resource_provider.py index 375de7c0ad40..9dab292a030d 100644 --- a/nova/tests/functional/db/test_resource_provider.py +++ b/nova/tests/functional/db/test_resource_provider.py @@ -17,9 +17,9 @@ from oslo_db import exception as db_exc import sqlalchemy as sa import nova +from nova.api.openstack.placement import exception from nova.api.openstack.placement.objects import resource_provider as rp_obj from nova import context -from nova import exception from nova import rc_fields as fields from nova import test from nova.tests import fixtures diff --git a/nova/tests/unit/api/openstack/placement/objects/test_resource_provider.py b/nova/tests/unit/api/openstack/placement/objects/test_resource_provider.py index c721e2d7cd71..0a41a2daac3c 100644 --- a/nova/tests/unit/api/openstack/placement/objects/test_resource_provider.py +++ b/nova/tests/unit/api/openstack/placement/objects/test_resource_provider.py @@ -16,10 +16,10 @@ import six from oslo_utils import timeutils import nova +from nova.api.openstack.placement import exception from nova.api.openstack.placement.objects import resource_provider from nova import context from nova.db.sqlalchemy import api_models as models -from nova import exception from nova import rc_fields as fields from nova import test from nova.tests.unit.objects import test_objects