hyper-v: Converts all os-win exceptions

When os-win was first introduced, a decorator which converts all os-win
exceptions to virt inspector exceptions has been added to the
HyperVInspector.

However, the decorator works as intended for methods that return values,
and not for the ones that yield (they return a generator).

This patch makes sure that exceptions are converted properly for yielding
methods as well.

Closes-Bug #1751088

Change-Id: I7d09e1860c6940758f0d0965fedfe4dd285e0cae
This commit is contained in:
Claudiu Belu 2018-02-22 18:06:10 +02:00
parent 01670dfcb4
commit e2a3542fe8
2 changed files with 61 additions and 30 deletions

View File

@ -26,39 +26,45 @@ from ceilometer.compute.pollsters import util
from ceilometer.compute.virt import inspector as virt_inspector
def convert_exceptions(function, exception_map):
def convert_exceptions(exception_map, yields=True):
expected_exceptions = tuple(exception_map.keys())
@functools.wraps(function)
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except expected_exceptions as ex:
# exception might be a subclass of an expected exception.
for expected in expected_exceptions:
if isinstance(ex, expected):
raised_exception = exception_map[expected]
break
def _reraise_exception(exc):
# exception might be a subclass of an expected exception.
for expected in expected_exceptions:
if isinstance(exc, expected):
raised_exception = exception_map[expected]
break
exc_info = sys.exc_info()
# NOTE(claudiub): Python 3 raises the exception object given as
# the second argument in six.reraise.
# The original message will be maintained by passing the original
# exception.
exc = raised_exception(six.text_type(exc_info[1]))
six.reraise(raised_exception, exc, exc_info[2])
return wrapper
exc_info = sys.exc_info()
# NOTE(claudiub): Python 3 raises the exception object given as
# the second argument in six.reraise.
# The original message will be maintained by passing the
# original exception.
exc = raised_exception(six.text_type(exc_info[1]))
six.reraise(raised_exception, exc, exc_info[2])
def decorator(function):
if yields:
@functools.wraps(function)
def wrapper(*args, **kwargs):
try:
# NOTE(claudiub): We're consuming the function's yield in
# order to avoid yielding a generator.
for item in function(*args, **kwargs):
yield item
except expected_exceptions as ex:
_reraise_exception(ex)
else:
@functools.wraps(function)
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except expected_exceptions as ex:
_reraise_exception(ex)
def decorate_all_methods(decorator, *args, **kwargs):
def decorate(cls):
for attr in cls.__dict__:
class_member = getattr(cls, attr)
if callable(class_member):
setattr(cls, attr, decorator(class_member, *args, **kwargs))
return cls
return decorate
return wrapper
return decorator
exception_conversion_map = collections.OrderedDict([
@ -69,12 +75,11 @@ exception_conversion_map = collections.OrderedDict([
(os_win_exc.OSWinException, virt_inspector.InspectorException),
])
# NOTE(claudiub): the purpose of the decorator below is to prevent any
# NOTE(claudiub): the purpose of the decorators below is to prevent any
# os_win exceptions (subclasses of OSWinException) to leak outside of the
# HyperVInspector.
@decorate_all_methods(convert_exceptions, exception_conversion_map)
class HyperVInspector(virt_inspector.Inspector):
def __init__(self, conf):
@ -91,6 +96,7 @@ class HyperVInspector(virt_inspector.Inspector):
return float(host_cpu_clock * host_cpu_count)
@convert_exceptions(exception_conversion_map, yields=False)
def inspect_instance(self, instance, duration):
instance_name = util.instance_name(instance)
(cpu_clock_used,
@ -105,6 +111,7 @@ class HyperVInspector(virt_inspector.Inspector):
cpu_time=cpu_time,
memory_usage=memory_usage)
@convert_exceptions(exception_conversion_map)
def inspect_vnics(self, instance, duration):
instance_name = util.instance_name(instance)
for vnic_metrics in self._utils.get_vnic_metrics(instance_name):
@ -122,6 +129,7 @@ class HyperVInspector(virt_inspector.Inspector):
tx_drop=0,
tx_errors=0)
@convert_exceptions(exception_conversion_map)
def inspect_disks(self, instance, duration):
instance_name = util.instance_name(instance)
for disk_metrics in self._utils.get_disk_metrics(instance_name):
@ -134,6 +142,7 @@ class HyperVInspector(virt_inspector.Inspector):
write_bytes=disk_metrics['write_mb'] * units.Mi,
errors=0, wr_total_times=0, rd_total_times=0)
@convert_exceptions(exception_conversion_map)
def inspect_disk_latency(self, instance, duration):
instance_name = util.instance_name(instance)
for disk_metrics in self._utils.get_disk_latency_metrics(
@ -142,6 +151,7 @@ class HyperVInspector(virt_inspector.Inspector):
device=disk_metrics['instance_id'],
disk_latency=disk_metrics['disk_latency'] / 1000)
@convert_exceptions(exception_conversion_map)
def inspect_disk_iops(self, instance, duration):
instance_name = util.instance_name(instance)
for disk_metrics in self._utils.get_disk_iops_count(instance_name):

View File

@ -58,6 +58,27 @@ class TestHyperVInspection(base.BaseTestCase):
self._inspector.inspect_instance,
mock.sentinel.instance, None)
def _yield_consumer(generator_method, *args, **kwargs):
list(generator_method(*args, **kwargs))
self._inspector._utils.get_vnic_metrics.side_effect = (
os_win_exc.OSWinException)
self.assertRaises(virt_inspector.InspectorException,
_yield_consumer, self._inspector.inspect_vnics,
mock.sentinel.instance, None)
self._inspector._utils.get_vnic_metrics.side_effect = (
os_win_exc.HyperVException)
self.assertRaises(virt_inspector.InspectorException,
_yield_consumer, self._inspector.inspect_vnics,
mock.sentinel.instance, None)
self._inspector._utils.get_vnic_metrics.side_effect = (
os_win_exc.NotFound(resource='foofoo'))
self.assertRaises(virt_inspector.InstanceNotFoundException,
_yield_consumer, self._inspector.inspect_vnics,
mock.sentinel.instance, None)
def test_assert_original_traceback_maintained(self):
def bar(self):
foo = "foofoo"