Merge "Refactor instance state change event handler"

This commit is contained in:
Zuul 2018-11-01 14:03:54 +00:00 committed by Gerrit Code Review
commit 0be182f1bc
6 changed files with 100 additions and 93 deletions

View File

@ -126,6 +126,7 @@ class HyperVDriver(driver.ComputeDriver):
self._imagecache = imagecache.ImageCache()
self._image_api = image.API()
self._pathutils = pathutils.PathUtils()
self._event_handler = eventhandler.InstanceEventHandler()
def _check_minimum_windows_version(self):
hostutils = utilsfactory.get_hostutils()
@ -151,13 +152,19 @@ class HyperVDriver(driver.ComputeDriver):
def init_host(self, host):
self._serialconsoleops.start_console_handlers()
event_handler = eventhandler.InstanceEventHandler(
state_change_callback=self.emit_event)
event_handler.start_listener()
self._set_event_handler_callbacks()
self._event_handler.start_listener()
instance_path = self._pathutils.get_instances_dir()
self._pathutils.check_create_dir(instance_path)
def _set_event_handler_callbacks(self):
# Subclasses may override this.
self._event_handler.add_callback(self.emit_event)
self._event_handler.add_callback(
self._vmops.instance_state_change_callback)
def list_instance_uuids(self):
return self._vmops.list_instance_uuids()

View File

@ -20,7 +20,6 @@ from os_win import utilsfactory
from oslo_log import log as logging
import compute_hyperv.nova.conf
from compute_hyperv.nova import serialconsoleops
from compute_hyperv.nova import vmops
LOG = logging.getLogger(__name__)
@ -28,6 +27,13 @@ LOG = logging.getLogger(__name__)
CONF = compute_hyperv.nova.conf.CONF
class HyperVLifecycleEvent(virtevent.LifecycleEvent):
def __init__(self, uuid, name, transition, timestamp=None):
super(HyperVLifecycleEvent, self).__init__(uuid, transition, timestamp)
self.name = name
class InstanceEventHandler(object):
_TRANSITION_MAP = {
constants.HYPERV_VM_STATE_ENABLED: virtevent.EVENT_LIFECYCLE_STARTED,
@ -37,7 +43,7 @@ class InstanceEventHandler(object):
virtevent.EVENT_LIFECYCLE_SUSPENDED
}
def __init__(self, state_change_callback=None):
def __init__(self):
self._vmutils = utilsfactory.get_vmutils()
self._listener = self._vmutils.get_vm_power_state_change_listener(
timeframe=CONF.hyperv.power_state_check_timeframe,
@ -46,13 +52,16 @@ class InstanceEventHandler(object):
get_handler=True)
self._vmops = vmops.VMOps()
self._serial_console_ops = serialconsoleops.SerialConsoleOps()
self._state_change_callback = state_change_callback
self._callbacks = []
def add_callback(self, callback):
self._callbacks.append(callback)
def start_listener(self):
utils.spawn_n(self._listener, self._event_callback)
utils.spawn_n(self._listener, self._handle_event)
def _event_callback(self, instance_name, instance_power_state):
def _handle_event(self, instance_name, instance_power_state):
# Instance uuid set by Nova. If this is missing, we assume that
# the instance was not created by Nova and ignore the event.
instance_uuid = self._vmops.get_instance_uuid(instance_name)
@ -69,27 +78,15 @@ class InstanceEventHandler(object):
def _emit_event(self, instance_name, instance_uuid, instance_state):
virt_event = self._get_virt_event(instance_uuid,
instance_name,
instance_state)
utils.spawn_n(self._state_change_callback, virt_event)
should_enable_metrics = (
CONF.hyperv.enable_instance_metrics_collection and
instance_state == constants.HYPERV_VM_STATE_ENABLED)
if should_enable_metrics:
utils.spawn_n(self._vmops.configure_instance_metrics,
instance_name,
enable_network_metrics=True)
for callback in self._callbacks:
utils.spawn_n(callback, virt_event)
utils.spawn_n(self._handle_serial_console_workers,
instance_name, instance_state)
def _handle_serial_console_workers(self, instance_name, instance_state):
if instance_state == constants.HYPERV_VM_STATE_ENABLED:
self._serial_console_ops.start_console_handler(instance_name)
else:
self._serial_console_ops.stop_console_handler(instance_name)
def _get_virt_event(self, instance_uuid, instance_state):
def _get_virt_event(self, instance_uuid, instance_name, instance_state):
transition = self._TRANSITION_MAP[instance_state]
return virtevent.LifecycleEvent(uuid=instance_uuid,
transition=transition)
return HyperVLifecycleEvent(
uuid=instance_uuid,
name=instance_name,
transition=transition)

View File

@ -30,6 +30,7 @@ from nova import objects
from nova.objects import fields
from nova import utils
from nova.virt import configdrive
from nova.virt import event as virtevent
from nova.virt import hardware
from os_win import constants as os_win_const
from os_win import exceptions as os_win_exc
@ -1466,3 +1467,15 @@ class VMOps(object):
"its uuid. It may have been deleted meanwhile.",
instance_name)
ctxt.reraise = expect_existing
def instance_state_change_callback(self, event):
if event.transition in (virtevent.EVENT_LIFECYCLE_STARTED,
virtevent.EVENT_LIFECYCLE_RESUMED):
# We can handle the following operations concurrently.
utils.spawn_n(self._serial_console_ops.start_console_handler,
event.name)
utils.spawn_n(self.configure_instance_metrics,
event.name,
enable_network_metrics=True)
else:
self._serial_console_ops.stop_console_handler(event.name)

View File

@ -35,6 +35,7 @@ from compute_hyperv.tests.unit import test_base
class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
_autospec_classes = [
driver.eventhandler.InstanceEventHandler,
driver.hostops.HostOps,
driver.volumeops.VolumeOps,
driver.vmops.VMOps,
@ -124,9 +125,7 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
# original frame will contain the 'foo' variable.
self.assertEqual('foofoo', trace.tb_frame.f_locals['foo'])
@mock.patch.object(driver.eventhandler, 'InstanceEventHandler')
def test_init_host(self, mock_InstanceEventHandler):
def test_init_host(self):
mock_get_inst_dir = self.driver._pathutils.get_instances_dir
mock_get_inst_dir.return_value = mock.sentinel.FAKE_DIR
@ -135,10 +134,10 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
mock_start_console_handlers = (
self.driver._serialconsoleops.start_console_handlers)
mock_start_console_handlers.assert_called_once_with()
mock_InstanceEventHandler.assert_called_once_with(
state_change_callback=self.driver.emit_event)
fake_event_handler = mock_InstanceEventHandler.return_value
fake_event_handler.start_listener.assert_called_once_with()
self.driver._event_handler.add_callback.assert_has_calls(
[mock.call(self.driver.emit_event),
mock.call(self.driver._vmops.instance_state_change_callback)])
self.driver._event_handler.start_listener.assert_called_once_with()
mock_get_inst_dir.assert_called_once_with()
self.driver._pathutils.check_create_dir.assert_called_once_with(

View File

@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from nova import utils
from nova.virt import driver
from os_win import constants
from compute_hyperv.nova import eventhandler
@ -23,20 +23,14 @@ from compute_hyperv.nova import vmops
from compute_hyperv.tests.unit import test_base
@ddt.ddt
class EventHandlerTestCase(test_base.HyperVBaseTestCase):
_autospec_classes = [
eventhandler.serialconsoleops.SerialConsoleOps,
]
_FAKE_POLLING_INTERVAL = 3
_FAKE_EVENT_CHECK_TIMEFRAME = 15
def setUp(self):
super(EventHandlerTestCase, self).setUp()
self._state_change_callback = mock.Mock(
autospec=driver.ComputeDriver.emit_event)
self.flags(
power_state_check_timeframe=self._FAKE_EVENT_CHECK_TIMEFRAME,
group='hyperv')
@ -44,20 +38,19 @@ class EventHandlerTestCase(test_base.HyperVBaseTestCase):
power_state_event_polling_interval=self._FAKE_POLLING_INTERVAL,
group='hyperv')
self._event_handler = eventhandler.InstanceEventHandler(
self._state_change_callback)
self._event_handler = eventhandler.InstanceEventHandler()
@ddt.data(True, False)
@mock.patch.object(vmops.VMOps, 'get_instance_uuid')
@mock.patch.object(eventhandler.InstanceEventHandler, '_emit_event')
def _test_event_callback(self, mock_emit_event, mock_get_uuid,
missing_uuid=False):
def test_handle_event(self, missing_uuid, mock_emit_event, mock_get_uuid):
mock_get_uuid.return_value = (
mock.sentinel.instance_uuid if not missing_uuid else None)
self._event_handler._vmutils.get_vm_power_state.return_value = (
mock.sentinel.power_state)
self._event_handler._event_callback(mock.sentinel.instance_name,
mock.sentinel.power_state)
self._event_handler._handle_event(mock.sentinel.instance_name,
mock.sentinel.power_state)
if not missing_uuid:
mock_emit_event.assert_called_once_with(
@ -67,60 +60,33 @@ class EventHandlerTestCase(test_base.HyperVBaseTestCase):
else:
self.assertFalse(mock_emit_event.called)
def test_event_callback_uuid_present(self):
self._test_event_callback()
def test_event_callback_missing_uuid(self):
self._test_event_callback(missing_uuid=True)
@mock.patch.object(eventhandler.InstanceEventHandler, '_get_virt_event')
@mock.patch.object(utils, 'spawn_n')
def test_emit_event(self, mock_spawn, mock_get_event):
@mock.patch.object(utils, 'spawn_n',
lambda f, *args, **kwargs: f(*args, **kwargs))
def test_emit_event(self, mock_get_event):
state = constants.HYPERV_VM_STATE_ENABLED
self.flags(enable_instance_metrics_collection=True,
group="hyperv")
callbacks = [mock.Mock(), mock.Mock()]
for cbk in callbacks:
self._event_handler.add_callback(cbk)
self._event_handler._emit_event(mock.sentinel.instance_name,
mock.sentinel.instance_uuid,
state)
virt_event = mock_get_event.return_value
mock_spawn.assert_has_calls(
[mock.call(self._state_change_callback, virt_event),
mock.call(self._event_handler._vmops.configure_instance_metrics,
mock.sentinel.instance_name,
enable_network_metrics=True),
mock.call(self._event_handler._handle_serial_console_workers,
mock.sentinel.instance_name,
state)])
for cbk in callbacks:
cbk.assert_called_once_with(mock_get_event.return_value)
def test_handle_serial_console_instance_running(self):
self._event_handler._handle_serial_console_workers(
mock.sentinel.instance_name,
constants.HYPERV_VM_STATE_ENABLED)
serialops = self._event_handler._serial_console_ops
serialops.start_console_handler.assert_called_once_with(
mock.sentinel.instance_name)
def test_handle_serial_console_instance_stopped(self):
self._event_handler._handle_serial_console_workers(
mock.sentinel.instance_name,
constants.HYPERV_VM_STATE_DISABLED)
serialops = self._event_handler._serial_console_ops
serialops.stop_console_handler.assert_called_once_with(
mock.sentinel.instance_name)
@mock.patch('nova.virt.event.LifecycleEvent')
def test_get_virt_event(self, mock_lifecycle_event):
def test_get_virt_event(self):
instance_state = constants.HYPERV_VM_STATE_ENABLED
expected_transition = self._event_handler._TRANSITION_MAP[
instance_state]
virt_event = self._event_handler._get_virt_event(
mock.sentinel.instance_uuid, instance_state)
mock.sentinel.instance_uuid,
mock.sentinel.instance_name,
instance_state)
self.assertEqual(mock_lifecycle_event.return_value,
virt_event)
mock_lifecycle_event.assert_called_once_with(
uuid=mock.sentinel.instance_uuid,
transition=expected_transition)
self.assertEqual(mock.sentinel.instance_name, virt_event.name)
self.assertEqual(mock.sentinel.instance_uuid, virt_event.uuid)
self.assertEqual(expected_transition, virt_event.transition)

View File

@ -22,6 +22,8 @@ from nova import exception
from nova import objects
from nova.objects import fields
from nova.tests.unit.objects import test_virtual_interface
from nova import utils
from nova.virt import event as virtevent
from nova.virt import hardware
from os_win import constants as os_win_const
from os_win import exceptions as os_win_exc
@ -31,6 +33,7 @@ from oslo_utils import units
import compute_hyperv.nova.conf
from compute_hyperv.nova import constants
from compute_hyperv.nova import eventhandler
from compute_hyperv.nova import vmops
from compute_hyperv.tests import fake_instance
from compute_hyperv.tests.unit import test_base
@ -2420,3 +2423,25 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
self._vmops.get_instance_uuid,
mock.sentinel.instance_name,
expect_existing=True)
@ddt.data(virtevent.EVENT_LIFECYCLE_STARTED,
virtevent.EVENT_LIFECYCLE_STOPPED)
@mock.patch.object(vmops.VMOps, 'configure_instance_metrics')
@mock.patch.object(utils, 'spawn_n',
lambda f, *args, **kwargs: f(*args, **kwargs))
def test_instance_state_change_callback(self, transition,
mock_configure_metrics):
event = eventhandler.HyperVLifecycleEvent(
mock.sentinel.uuid,
mock.sentinel.name,
transition)
self._vmops.instance_state_change_callback(event)
serialops = self._vmops._serial_console_ops
if transition == virtevent.EVENT_LIFECYCLE_STARTED:
serialops.start_console_handler.assert_called_once_with(event.name)
mock_configure_metrics.assert_called_once_with(
event.name, enable_network_metrics=True)
else:
serialops.stop_console_handler.assert_called_once_with(event.name)