1414import functools
1515import threading
1616
17+ import eventlet
1718import fixtures
1819from oslo_log import log as logging
1920import oslo_messaging
@@ -76,6 +77,7 @@ class FakeNotifier(object):
7677
7778 def __init__ (
7879 self , transport , publisher_id , serializer = None , parent = None ,
80+ test_case_id = None
7981 ):
8082 self .transport = transport
8183 self .publisher_id = publisher_id
@@ -92,13 +94,16 @@ def __init__(
9294 functools .partial (self ._notify , priority .upper ()),
9395 )
9496
97+ self .test_case_id = test_case_id
98+
9599 def prepare (self , publisher_id = None ):
96100 if publisher_id is None :
97101 publisher_id = self .publisher_id
98102
99103 return self .__class__ (
100104 self .transport , publisher_id ,
101105 serializer = self ._serializer , parent = self ,
106+ test_case_id = self .test_case_id
102107 )
103108
104109 def _notify (self , priority , ctxt , event_type , payload ):
@@ -130,8 +135,10 @@ def reset(self):
130135class FakeVersionedNotifier (FakeNotifier ):
131136 def __init__ (
132137 self , transport , publisher_id , serializer = None , parent = None ,
138+ test_case_id = None
133139 ):
134- super ().__init__ (transport , publisher_id , serializer )
140+ super ().__init__ (
141+ transport , publisher_id , serializer , test_case_id = test_case_id )
135142 if parent :
136143 self .versioned_notifications = parent .versioned_notifications
137144 else :
@@ -142,7 +149,31 @@ def __init__(
142149 else :
143150 self .subscriptions = collections .defaultdict (_Sub )
144151
152+ @staticmethod
153+ def _get_sender_test_case_id ():
154+ current = eventlet .getcurrent ()
155+ # NOTE(gibi) not all eventlet spawn is under our control, so there can
156+ # be senders without test_case_id set, find the first ancestor that
157+ # was spawned from nova.utils.spawn[_n] and therefore has the id set.
158+ while not getattr (current , 'test_case_id' , None ):
159+ current = current .parent
160+ return current .test_case_id
161+
145162 def _notify (self , priority , ctxt , event_type , payload ):
163+ sender_test_case_id = self ._get_sender_test_case_id ()
164+ # NOTE(gibi): this is here to prevent late notifications from already
165+ # finished test cases to break the currently running test case. See
166+ # more in https://bugs.launchpad.net/nova/+bug/1946339
167+ if sender_test_case_id != self .test_case_id :
168+ raise RuntimeError (
169+ 'FakeVersionedNotifier received %s notification emitted by %s '
170+ 'test case which is different from the currently running test '
171+ 'case %s. This notification is ignored. The sender test case '
172+ 'probably leaked a running eventlet that emitted '
173+ 'notifications after the test case finished. Now this eventlet'
174+ 'is terminated by raising this exception.' %
175+ (event_type , sender_test_case_id , self .test_case_id ))
176+
146177 payload = self ._serializer .serialize_entity (ctxt , payload )
147178 notification = {
148179 'publisher_id' : self .publisher_id ,
@@ -180,7 +211,9 @@ def setUp(self):
180211 self .fake_versioned_notifier = FakeVersionedNotifier (
181212 rpc .NOTIFIER .transport ,
182213 rpc .NOTIFIER .publisher_id ,
183- serializer = getattr (rpc .NOTIFIER , '_serializer' , None ))
214+ serializer = getattr (rpc .NOTIFIER , '_serializer' , None ),
215+ test_case_id = self .test .id ()
216+ )
184217 if rpc .LEGACY_NOTIFIER and rpc .NOTIFIER :
185218 self .test .stub_out ('nova.rpc.LEGACY_NOTIFIER' , self .fake_notifier )
186219 self .test .stub_out (
0 commit comments