Fix cancel_all_events event name parsing

The cancel_all_events function was incorrectly assuming
that event tags didn't have dashes in them, but they all
do since they are either port or volume IDs, which are UUIDs.

Instead of just storing our event data indexed by the composite
event key, we should just store the name and tag distinctly so
that there is no question about how to dissect the two from the
key later. This is all internal record-keeping inside of the
compute manager, so we can change it to whatever we want.

This includes a slight change to the internal virtapi interface
for wait_for_instance_event(), which removes the ability to
pass composite event keys as strings instead of (name, tag)
tuples. In reality, none of the virt code was using the key string
format, so this just cleans up a few tests that were. It makes
that interface less ambiguous, which is good anyway.

Change-Id: I15f041e820c2dabe82e8e25bf77f9cc86330a76a
Closes-Bug: #1760303
This commit is contained in:
Matt Riedemann 2018-03-31 10:28:29 -04:00 committed by Dan Smith
parent 167023b507
commit d9369d034c
4 changed files with 58 additions and 47 deletions

View File

@ -298,7 +298,7 @@ class InstanceEvents(object):
def _lock_name(instance):
return '%s-%s' % (instance.uuid, 'events')
def prepare_for_instance_event(self, instance, event_name):
def prepare_for_instance_event(self, instance, name, tag):
"""Prepare to receive an event for an instance.
This will register an event for the given instance that we will
@ -307,7 +307,8 @@ class InstanceEvents(object):
object should be wait()'d on to ensure completion.
:param instance: the instance for which the event will be generated
:param event_name: the name of the event we're expecting
:param name: the name of the event we're expecting
:param tag: the tag associated with the event we're expecting
:returns: an event object that should be wait()'d on
"""
if self._events is None:
@ -319,10 +320,10 @@ class InstanceEvents(object):
@utils.synchronized(self._lock_name(instance))
def _create_or_get_event():
instance_events = self._events.setdefault(instance.uuid, {})
return instance_events.setdefault(event_name,
return instance_events.setdefault((name, tag),
eventlet.event.Event())
LOG.debug('Preparing to wait for external event %(event)s',
{'event': event_name}, instance=instance)
LOG.debug('Preparing to wait for external event %(name)s-%(tag)s',
{'name': name, 'tag': tag}, instance=instance)
return _create_or_get_event()
def pop_instance_event(self, instance, event):
@ -349,7 +350,7 @@ class InstanceEvents(object):
events = self._events.get(instance.uuid)
if not events:
return no_events_sentinel
_event = events.pop(event.key, None)
_event = events.pop((event.name, event.tag), None)
if not events:
del self._events[instance.uuid]
if _event is None:
@ -386,7 +387,11 @@ class InstanceEvents(object):
LOG.debug('Unexpected attempt to clear events during shutdown',
instance=instance)
return dict()
return self._events.pop(instance.uuid, {})
# NOTE(danms): We have historically returned the raw internal
# format here, which is {event.key: [events, ...])} so just
# trivially convert it here.
return {'%s-%s' % k: e
for k, e in self._events.pop(instance.uuid, {}).items()}
return _clear_events()
def cancel_all_events(self):
@ -398,12 +403,12 @@ class InstanceEvents(object):
self._events = None
for instance_uuid, events in our_events.items():
for event_name, eventlet_event in events.items():
LOG.debug('Canceling in-flight event %(event)s for '
for (name, tag), eventlet_event in events.items():
LOG.debug('Canceling in-flight event %(name)s-%(tag)s for '
'instance %(instance_uuid)s',
{'event': event_name,
{'name': name,
'tag': tag,
'instance_uuid': instance_uuid})
name, tag = event_name.rsplit('-', 1)
event = objects.InstanceExternalEvent(
instance_uuid=instance_uuid,
name=name, status='failed',
@ -444,9 +449,9 @@ class ComputeVirtAPI(virtapi.VirtAPI):
or raise an exception which will bubble up to the waiter.
:param instance: The instance for which an event is expected
:param event_names: A list of event names. Each element can be a
string event name or tuple of strings to
indicate (name, tag).
:param event_names: A list of event names. Each element is a
tuple of strings to indicate (name, tag),
where name is required, but tag may be None.
:param deadline: Maximum number of seconds we should wait for all
of the specified events to arrive.
:param error_callback: A function to be called if an event arrives
@ -457,14 +462,12 @@ class ComputeVirtAPI(virtapi.VirtAPI):
error_callback = self._default_error_callback
events = {}
for event_name in event_names:
if isinstance(event_name, tuple):
name, tag = event_name
event_name = objects.InstanceExternalEvent.make_key(
name, tag)
name, tag = event_name
event_name = objects.InstanceExternalEvent.make_key(name, tag)
try:
events[event_name] = (
self._compute.instance_events.prepare_for_instance_event(
instance, event_name))
instance, name, tag))
except exception.NovaException:
error_callback(event_name, instance)
# NOTE(danms): Don't wait for any of the events. They

View File

@ -2556,13 +2556,14 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
def test_prepare_for_instance_event(self, lock_name_mock):
inst_obj = objects.Instance(uuid=uuids.instance)
result = self.compute.instance_events.prepare_for_instance_event(
inst_obj, 'test-event')
inst_obj, 'test-event', None)
self.assertIn(uuids.instance, self.compute.instance_events._events)
self.assertIn('test-event',
self.assertIn(('test-event', None),
self.compute.instance_events._events[uuids.instance])
self.assertEqual(
result,
self.compute.instance_events._events[uuids.instance]['test-event'])
self.compute.instance_events._events[uuids.instance]
[('test-event', None)])
self.assertTrue(hasattr(result, 'send'))
lock_name_mock.assert_called_once_with(inst_obj)
@ -2571,7 +2572,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
event = eventlet_event.Event()
self.compute.instance_events._events = {
uuids.instance: {
'network-vif-plugged': event,
('network-vif-plugged', None): event,
}
}
inst_obj = objects.Instance(uuid=uuids.instance)
@ -2587,13 +2588,13 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
event = eventlet_event.Event()
self.compute.instance_events._events = {
uuids.instance: {
'test-event': event,
('test-event', None): event,
}
}
inst_obj = objects.Instance(uuid=uuids.instance)
result = self.compute.instance_events.clear_events_for_instance(
inst_obj)
self.assertEqual(result, {'test-event': event})
self.assertEqual(result, {'test-event-None': event})
lock_name_mock.assert_called_once_with(inst_obj)
def test_instance_events_lock_name(self):
@ -2604,24 +2605,25 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
def test_prepare_for_instance_event_again(self):
inst_obj = objects.Instance(uuid=uuids.instance)
self.compute.instance_events.prepare_for_instance_event(
inst_obj, 'test-event')
inst_obj, 'test-event', None)
# A second attempt will avoid creating a new list; make sure we
# get the current list
result = self.compute.instance_events.prepare_for_instance_event(
inst_obj, 'test-event')
inst_obj, 'test-event', None)
self.assertIn(uuids.instance, self.compute.instance_events._events)
self.assertIn('test-event',
self.assertIn(('test-event', None),
self.compute.instance_events._events[uuids.instance])
self.assertEqual(
result,
self.compute.instance_events._events[uuids.instance]['test-event'])
self.compute.instance_events._events[uuids.instance]
[('test-event', None)])
self.assertTrue(hasattr(result, 'send'))
def test_process_instance_event(self):
event = eventlet_event.Event()
self.compute.instance_events._events = {
uuids.instance: {
'network-vif-plugged': event,
('network-vif-plugged', None): event,
}
}
inst_obj = objects.Instance(uuid=uuids.instance)
@ -2921,7 +2923,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
fake_eventlet_event = mock.MagicMock()
self.compute.instance_events._events = {
inst.uuid: {
'network-vif-plugged-bar': fake_eventlet_event,
('network-vif-plugged', uuids.portid): fake_eventlet_event,
}
}
self.compute.instance_events.cancel_all_events()
@ -2930,7 +2932,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
self.assertTrue(fake_eventlet_event.send.called)
event = fake_eventlet_event.send.call_args_list[0][0][0]
self.assertEqual('network-vif-plugged', event.name)
self.assertEqual('bar', event.tag)
self.assertEqual(uuids.portid, event.tag)
self.assertEqual('failed', event.status)
def test_cleanup_cancels_all_events(self):
@ -2944,7 +2946,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
callback = mock.MagicMock()
body = mock.MagicMock()
with self.compute.virtapi.wait_for_instance_event(
instance, ['network-vif-plugged-bar'],
instance, [('network-vif-plugged', 'bar')],
error_callback=callback):
body()
self.assertTrue(body.called)

View File

@ -93,10 +93,12 @@ class FakeCompute(object):
event.status = 'completed'
return event
def _prepare_for_instance_event(self, instance, event_name):
def _prepare_for_instance_event(self, instance, name, tag):
m = mock.MagicMock()
m.instance = instance
m.event_name = event_name
m.name = name
m.tag = tag
m.event_name = '%s-%s' % (name, tag)
m.wait.side_effect = self._event_waiter
self._events.append(m)
return m
@ -125,7 +127,7 @@ class ComputeVirtAPITest(VirtAPIBaseTest):
event_2_tag = objects.InstanceExternalEvent.make_key(
'event2', 'tag')
events = {
'event1': event_1_tag,
('event1', None): event_1_tag,
('event2', 'tag'): event_2_tag,
}
with self.virtapi.wait_for_instance_event('instance', events.keys()):
@ -135,7 +137,7 @@ class ComputeVirtAPITest(VirtAPIBaseTest):
self.assertEqual(2, len(self.compute._events))
for event in self.compute._events:
self.assertEqual('instance', event.instance)
self.assertIn(event.event_name, events.values())
self.assertIn((event.name, event.tag), events.keys())
event.wait.assert_called_once_with()
def test_wait_for_instance_event_failed(self):
@ -146,7 +148,8 @@ class ComputeVirtAPITest(VirtAPIBaseTest):
@mock.patch.object(self.virtapi._compute, '_event_waiter', _failer)
def do_test():
with self.virtapi.wait_for_instance_event('instance', ['foo']):
with self.virtapi.wait_for_instance_event('instance',
[('foo', 'bar')]):
pass
self.assertRaises(exception.NovaException, do_test)
@ -160,7 +163,8 @@ class ComputeVirtAPITest(VirtAPIBaseTest):
@mock.patch.object(self.virtapi._compute, '_event_waiter', _failer)
def do_test():
callback = mock.MagicMock()
with self.virtapi.wait_for_instance_event('instance', ['foo'],
with self.virtapi.wait_for_instance_event('instance',
[('foo', None)],
error_callback=callback):
pass
callback.assert_called_with('foo', 'instance')
@ -177,7 +181,8 @@ class ComputeVirtAPITest(VirtAPIBaseTest):
@mock.patch.object(self.virtapi._compute, '_event_waiter', _failer)
@mock.patch('eventlet.timeout.Timeout')
def do_test(timeout):
with self.virtapi.wait_for_instance_event('instance', ['foo']):
with self.virtapi.wait_for_instance_event('instance',
[('foo', 'bar')]):
pass
self.assertRaises(TestException, do_test)

View File

@ -10545,8 +10545,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
def fake_recover():
pass
def fake_prepare(instance, event_name):
ev = mock.MagicMock(instance=instance, event_name=event_name)
def fake_prepare(instance, name, tag):
ev = mock.MagicMock(instance=instance,
event_name='%s-%s' % (name, tag))
ev.wait.return_value = mock.MagicMock(status='completed')
generated_events.append(ev)
return ev
@ -10577,7 +10578,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
CONF.libvirt.live_migration_bandwidth)
prepare.assert_has_calls([
mock.call(instance, 'network-vif-plugged-%s' % uuids.vif_1)])
mock.call(instance, 'network-vif-plugged', uuids.vif_1)])
for event in generated_events:
event.wait.assert_called_once_with()
@ -15760,10 +15761,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
event.status = 'completed'
return event
def fake_prepare(instance, event_name):
def fake_prepare(instance, name, tag):
m = mock.MagicMock()
m.instance = instance
m.event_name = event_name
m.event_name = '%s-%s' % (name, tag)
m.wait.side_effect = wait_timeout
generated_events.append(m)
return m
@ -15802,8 +15803,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
if utils.is_neutron() and CONF.vif_plugging_timeout and power_on:
prepare.assert_has_calls([
mock.call(instance, 'network-vif-plugged-%s' % uuids.vif_1),
mock.call(instance, 'network-vif-plugged-%s' % uuids.vif_2)])
mock.call(instance, 'network-vif-plugged', uuids.vif_1),
mock.call(instance, 'network-vif-plugged', uuids.vif_2)])
for event in generated_events:
if neutron_failure and generated_events.index(event) != 0:
self.assertEqual(0, event.call_count)