From 7b13bc1888ff1fd1fc56869cf2870830a4f8431b Mon Sep 17 00:00:00 2001
From: Doug Hellmann <doug.hellmann@dreamhost.com>
Date: Tue, 4 Feb 2014 09:18:35 -0800
Subject: [PATCH] Update ExpectedException handling

Catch expected exceptions in a way that supports subclasses also being
seen as "expected."

Log a little more detail about unexpected exceptions in the ExecutorBase
so it is easier to add them to the list of expected exceptions.

Change-Id: I1bdd628eba717308f0afe1a889efd8711bad6296
Closes-bug: #1276163
---
 oslo/messaging/_executors/base.py |  8 ++++++--
 oslo/messaging/rpc/server.py      | 13 ++++++++-----
 tests/test_expected_exceptions.py | 13 +++++++++++++
 3 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/oslo/messaging/_executors/base.py b/oslo/messaging/_executors/base.py
index 79ab6223e..897097d8c 100644
--- a/oslo/messaging/_executors/base.py
+++ b/oslo/messaging/_executors/base.py
@@ -38,12 +38,16 @@ class ExecutorBase(object):
             _LOG.debug('Expected exception during message handling (%s)' %
                        e.exc_info[1])
             incoming.reply(failure=e.exc_info, log_failure=False)
-        except Exception:
+        except Exception as e:
             # sys.exc_info() is deleted by LOG.exception().
             exc_info = sys.exc_info()
-            _LOG.error('Exception during message handling',
+            _LOG.error('Exception during message handling: %s', e,
                        exc_info=exc_info)
             incoming.reply(failure=exc_info)
+            # NOTE(dhellmann): Remove circular object reference
+            # between the current stack frame and the traceback in
+            # exc_info.
+            del exc_info
 
     @abc.abstractmethod
     def start(self):
diff --git a/oslo/messaging/rpc/server.py b/oslo/messaging/rpc/server.py
index aaa071671..2a1f238c7 100644
--- a/oslo/messaging/rpc/server.py
+++ b/oslo/messaging/rpc/server.py
@@ -151,10 +151,13 @@ def expected_exceptions(*exceptions):
         def inner(*args, **kwargs):
             try:
                 return func(*args, **kwargs)
-            except Exception as e:
-                if type(e) in exceptions:
-                    raise ExpectedException()
-                else:
-                    raise
+            # Take advantage of the fact that we can catch
+            # multiple exception types using a tuple of
+            # exception classes, with subclass detection
+            # for free. Any exception that is not in or
+            # derived from the args passed to us will be
+            # ignored and thrown as normal.
+            except exceptions:
+                raise ExpectedException()
         return inner
     return outer
diff --git a/tests/test_expected_exceptions.py b/tests/test_expected_exceptions.py
index 1585e4b0d..8ec94a149 100644
--- a/tests/test_expected_exceptions.py
+++ b/tests/test_expected_exceptions.py
@@ -43,6 +43,19 @@ class TestExpectedExceptions(test_utils.BaseTestCase):
 
         self.assertRaises(messaging.ExpectedException, naughty)
 
+    def test_decorator_expected_subclass(self):
+        class FooException(Exception):
+            pass
+
+        class BarException(FooException):
+            pass
+
+        @messaging.expected_exceptions(FooException)
+        def naughty():
+            raise BarException()
+
+        self.assertRaises(messaging.ExpectedException, naughty)
+
     def test_decorator_unexpected(self):
         class FooException(Exception):
             pass