Client plugin exception handling methods

This change adds common methods which allow resources
to handle client exceptions without needing to directly
import the exception types.

The most common client resource exception handling is:
* Detecting if an exception is a 404
* Raising any exception which is not a 404
* Detecting if an exception is a 413 (over limit)
* Detecting if an exception was raised by a particular client
  library

Subsequent changes will move to using these methods.

Change-Id: Ib2bd55c31e66b562cfa8388beb450be6c06cc4fb
This commit is contained in:
Steve Baker 2014-06-17 11:46:27 +12:00 committed by Randall Burt
parent 78b68ba0da
commit 9b6d38cd02
11 changed files with 402 additions and 0 deletions

View File

@ -19,6 +19,10 @@ import six
@six.add_metaclass(abc.ABCMeta)
class ClientPlugin():
# Module which contains all exceptions classes which the client
# may emit
exceptions_module = None
def __init__(self, context):
self.context = context
self.clients = context.clients
@ -54,3 +58,22 @@ class ClientPlugin():
except (cfg.NoSuchGroupError, cfg.NoSuchOptError):
cfg.CONF.import_opt(option, 'heat.common.config', group='clients')
return getattr(cfg.CONF.clients, option)
def is_client_exception(self, ex):
'''Returns True if the current exception comes from the client.'''
if self.exceptions_module:
return type(ex) in self.exceptions_module.__dict__.values()
return False
def is_not_found(self, ex):
'''Returns True if the exception is a not-found.'''
return False
def is_over_limit(self, ex):
'''Returns True if the exception is an over-limit.'''
return False
def ignore_not_found(self, ex):
'''Raises the exception unless it is a not-found.'''
if not self.is_not_found(ex):
raise ex

View File

@ -12,12 +12,15 @@
# under the License.
from ceilometerclient import client as cc
from ceilometerclient import exc
from heat.engine.clients import client_plugin
class CeilometerClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exc
def _create(self):
con = self.context
@ -37,3 +40,9 @@ class CeilometerClientPlugin(client_plugin.ClientPlugin):
}
return cc.Client('2', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, exc.HTTPNotFound)
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)

View File

@ -12,12 +12,15 @@
# under the License.
from cinderclient import client as cc
from cinderclient import exceptions
from heat.engine.clients import client_plugin
class CinderClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
@ -40,3 +43,9 @@ class CinderClientPlugin(client_plugin.ClientPlugin):
client.client.management_url = management_url
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.OverLimit)

View File

@ -26,6 +26,8 @@ LOG = logging.getLogger(__name__)
class GlanceClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exc
def _create(self):
con = self.context
@ -46,6 +48,12 @@ class GlanceClientPlugin(client_plugin.ClientPlugin):
return gc.Client('1', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, exc.HTTPNotFound)
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)
def get_image_id(self, image_identifier):
'''
Return an id for the specified image name or identifier.

View File

@ -12,12 +12,15 @@
# under the License.
from heatclient import client as hc
from heatclient import exc
from heat.engine.clients import client_plugin
class HeatClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exc
def _create(self):
args = {
'auth_url': self.context.auth_url,
@ -33,6 +36,12 @@ class HeatClientPlugin(client_plugin.ClientPlugin):
endpoint = self.get_heat_url()
return hc.Client('1', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, exc.HTTPNotFound)
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)
def get_heat_url(self):
heat_url = self._get_client_option('heat', 'url')
if heat_url:

View File

@ -11,11 +11,21 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneclient import exceptions
from heat.common import heat_keystoneclient as hkc
from heat.engine.clients import client_plugin
class KeystoneClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
return hkc.KeystoneClient(self.context)
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.RequestEntityTooLarge)

View File

@ -21,6 +21,8 @@ from heat.engine import constraints
class NeutronClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
@ -39,6 +41,24 @@ class NeutronClientPlugin(client_plugin.ClientPlugin):
return nc.Client(**args)
def is_not_found(self, ex):
if isinstance(ex, (exceptions.NotFound,
exceptions.NetworkNotFoundClient,
exceptions.PortNotFoundClient)):
return True
return (isinstance(ex, exceptions.NeutronClientException) and
ex.status_code == 404)
def is_conflict(self, ex):
if not isinstance(ex, exceptions.NeutronClientException):
return False
return ex.status_code == 409
def is_over_limit(self, ex):
if not isinstance(ex, exceptions.NeutronClientException):
return False
return ex.status_code == 413
class NetworkConstraint(constraints.BaseCustomConstraint):

View File

@ -12,6 +12,7 @@
# under the License.
from novaclient import client as nc
from novaclient import exceptions
from novaclient import shell as novashell
from heat.engine.clients import client_plugin
@ -19,6 +20,8 @@ from heat.engine.clients import client_plugin
class NovaClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
computeshell = novashell.OpenStackComputeShell()
extensions = computeshell._discover_extensions("1.1")
@ -46,3 +49,12 @@ class NovaClientPlugin(client_plugin.ClientPlugin):
client.client.management_url = management_url
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.OverLimit)
def is_bad_request(self, ex):
return isinstance(ex, exceptions.BadRequest)

View File

@ -12,12 +12,15 @@
# under the License.
from swiftclient import client as sc
from swiftclient import exceptions
from heat.engine.clients import client_plugin
class SwiftClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
@ -36,3 +39,14 @@ class SwiftClientPlugin(client_plugin.ClientPlugin):
'insecure': self._get_client_option('swift', 'insecure')
}
return sc.Connection(**args)
def is_client_exception(self, ex):
return isinstance(ex, exceptions.ClientException)
def is_not_found(self, ex):
return (isinstance(ex, exceptions.ClientException) and
ex.http_status == 404)
def is_over_limit(self, ex):
return (isinstance(ex, exceptions.ClientException) and
ex.http_status == 413)

View File

@ -12,12 +12,15 @@
# under the License.
from troveclient import client as tc
from troveclient.client import exceptions
from heat.engine.clients import client_plugin
class TroveClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
@ -40,3 +43,9 @@ class TroveClientPlugin(client_plugin.ClientPlugin):
client.client.management_url = management_url
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.RequestEntityTooLarge)

View File

@ -11,6 +11,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from ceilometerclient import exc as ceil_exc
from cinderclient import exceptions as cinder_exc
from glanceclient import exc as glance_exc
from heatclient import exc as heat_exc
from keystoneclient import exceptions as keystone_exc
from neutronclient.common import exceptions as neutron_exc
from swiftclient import exceptions as swift_exc
from troveclient.client import exceptions as trove_exc
from heatclient import client as heatclient
import mock
from oslo.config import cfg
@ -19,6 +28,7 @@ from testtools.testcase import skip
from heat.engine import clients
from heat.engine.clients import client_plugin
from heat.tests.common import HeatTestCase
from heat.tests.v1_1 import fakes
class ClientsTest(HeatTestCase):
@ -214,3 +224,272 @@ class TestClientPluginsInitialise(HeatTestCase):
self.assertEqual(c, plugin.clients)
self.assertEqual(con, plugin.context)
self.assertIsNone(plugin._client)
class TestIsNotFound(HeatTestCase):
scenarios = [
('ceilometer_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='ceilometer',
exception=lambda: ceil_exc.HTTPNotFound(details='gone'),
)),
('ceilometer_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='ceilometer',
exception=lambda: Exception()
)),
('ceilometer_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='ceilometer',
exception=lambda: ceil_exc.HTTPOverLimit(details='over'),
)),
('cinder_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='cinder',
exception=lambda: cinder_exc.NotFound(code=404),
)),
('cinder_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='cinder',
exception=lambda: Exception()
)),
('cinder_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='cinder',
exception=lambda: cinder_exc.OverLimit(code=413),
)),
('glance_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='glance',
exception=lambda: glance_exc.HTTPNotFound(details='gone'),
)),
('glance_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='glance',
exception=lambda: Exception()
)),
('glance_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='glance',
exception=lambda: glance_exc.HTTPOverLimit(details='over'),
)),
('heat_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='heat',
exception=lambda: heat_exc.HTTPNotFound(message='gone'),
)),
('heat_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='heat',
exception=lambda: Exception()
)),
('heat_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='heat',
exception=lambda: heat_exc.HTTPOverLimit(message='over'),
)),
('keystone_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='keystone',
exception=lambda: keystone_exc.NotFound(details='gone'),
)),
('keystone_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='keystone',
exception=lambda: Exception()
)),
('keystone_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='keystone',
exception=lambda: keystone_exc.RequestEntityTooLarge(
details='over'),
)),
('neutron_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='neutron',
exception=lambda: neutron_exc.NotFound,
)),
('neutron_network_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='neutron',
exception=lambda: neutron_exc.NetworkNotFoundClient(),
)),
('neutron_port_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='neutron',
exception=lambda: neutron_exc.PortNotFoundClient(),
)),
('neutron_status_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='neutron',
exception=lambda: neutron_exc.NeutronClientException(
status_code=404),
)),
('neutron_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='neutron',
exception=lambda: Exception()
)),
('neutron_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='neutron',
exception=lambda: neutron_exc.NeutronClientException(
status_code=413),
)),
('nova_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='nova',
exception=lambda: fakes.fake_exception(),
)),
('nova_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='nova',
exception=lambda: Exception()
)),
('nova_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='nova',
exception=lambda: fakes.fake_exception(413),
)),
('swift_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='swift',
exception=lambda: swift_exc.ClientException(
msg='gone', http_status=404),
)),
('swift_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='swift',
exception=lambda: Exception()
)),
('swift_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='swift',
exception=lambda: swift_exc.ClientException(
msg='ouch', http_status=413),
)),
('trove_not_found', dict(
is_not_found=True,
is_over_limit=False,
is_client_exception=True,
plugin='trove',
exception=lambda: trove_exc.NotFound(message='gone'),
)),
('trove_exception', dict(
is_not_found=False,
is_over_limit=False,
is_client_exception=False,
plugin='trove',
exception=lambda: Exception()
)),
('trove_overlimit', dict(
is_not_found=False,
is_over_limit=True,
is_client_exception=True,
plugin='trove',
exception=lambda: trove_exc.RequestEntityTooLarge(
message='over'),
)),
]
def test_is_not_found(self):
con = mock.Mock()
c = clients.Clients(con)
client_plugin = c.client_plugin(self.plugin)
try:
raise self.exception()
except Exception as e:
if self.is_not_found != client_plugin.is_not_found(e):
raise
def test_ignore_not_found(self):
con = mock.Mock()
c = clients.Clients(con)
client_plugin = c.client_plugin(self.plugin)
try:
exp = self.exception()
exp_class = exp.__class__
raise exp
except Exception as e:
if self.is_not_found:
client_plugin.ignore_not_found(e)
else:
self.assertRaises(exp_class,
client_plugin.ignore_not_found,
e)
def test_is_over_limit(self):
con = mock.Mock()
c = clients.Clients(con)
client_plugin = c.client_plugin(self.plugin)
try:
raise self.exception()
except Exception as e:
if self.is_over_limit != client_plugin.is_over_limit(e):
raise
def test_is_client_exception(self):
con = mock.Mock()
c = clients.Clients(con)
client_plugin = c.client_plugin(self.plugin)
try:
raise self.exception()
except Exception as e:
ice = self.is_client_exception
if ice != client_plugin.is_client_exception(e):
raise