From 0376da0627b022bc6aeb3e423250f9e29181f9ab Mon Sep 17 00:00:00 2001 From: Vladik Romanovsky Date: Thu, 12 May 2016 19:33:33 -0400 Subject: [PATCH] libvirt: update guest time after suspend When an instance is resumed from suspension or a migration, the guest's clock might get a large delay between the current and the suspension time. In some cases, NTP might not be able to resynchronize the guest time and will result in a skewed clock. In order to address the issue, this patch will try to set the guest time after it is resumed. This operation will require the guest agent to be configured and running in order to be able to run. However, this operation will not be disruptive. This code is similar to a change that has been commited to oVirt. https://gerrit.ovirt.org/#/c/50426 Closes-Bug: #1593745 Change-Id: I2732af50aab238502ccdc067f62290fd962c8c64 --- nova/tests/unit/virt/libvirt/fakelibvirt.py | 6 ++- nova/tests/unit/virt/libvirt/test_guest.py | 8 ++++ nova/virt/libvirt/guest.py | 43 +++++++++++++++++++ .../set_guest_time-736939fe725cbdab.yaml | 7 +++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/set_guest_time-736939fe725cbdab.yaml diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index 7fd7c0a1bf61..0261223a422b 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -124,7 +124,8 @@ VIR_ERR_INTERNAL_ERROR = 950 VIR_ERR_CONFIG_UNSUPPORTED = 951 VIR_ERR_NO_NODE_DEVICE = 667 VIR_ERR_NO_SECRET = 66 - +VIR_ERR_AGENT_UNRESPONSIVE = 86 +VIR_ERR_ARGUMENT_UNSUPPORTED = 74 # Readonly VIR_CONNECT_RO = 1 @@ -563,6 +564,9 @@ class Domain(object): def blockStats(self, device): return [2, 10000242400, 234, 2343424234, 34] + def setTime(self, time=None, flags=0): + pass + def suspend(self): self._state = VIR_DOMAIN_PAUSED diff --git a/nova/tests/unit/virt/libvirt/test_guest.py b/nova/tests/unit/virt/libvirt/test_guest.py index 974e98a51ebf..d039b72687e2 100644 --- a/nova/tests/unit/virt/libvirt/test_guest.py +++ b/nova/tests/unit/virt/libvirt/test_guest.py @@ -139,6 +139,14 @@ class GuestTestCase(test.NoDBTestCase): self.guest.resume() self.domain.resume.assert_called_once_with() + @mock.patch('time.time', return_value=1234567890.125) + def test_time_sync_no_errors(self, time_mock): + self.domain.setTime.side_effect = fakelibvirt.libvirtError('error') + self.guest.resume() + self.domain.setTime.assert_called_once_with(time={ + 'nseconds': 125000000, + 'seconds': 1234567890}) + def test_get_vcpus_info(self): self.domain.vcpus.return_value = ([(0, 1, int(10290000000), 2)], [(True, True)]) diff --git a/nova/virt/libvirt/guest.py b/nova/virt/libvirt/guest.py index 796370f613e1..5a70376cf227 100644 --- a/nova/virt/libvirt/guest.py +++ b/nova/virt/libvirt/guest.py @@ -33,11 +33,13 @@ from oslo_service import loopingcall from oslo_utils import encodeutils from oslo_utils import excutils from oslo_utils import importutils +import time from nova.compute import power_state from nova import exception from nova.i18n import _ from nova.i18n import _LE +from nova.i18n import _LW from nova import utils from nova.virt import hardware from nova.virt.libvirt import compat @@ -145,6 +147,46 @@ class Guest(object): """Stops a running guest.""" self._domain.destroy() + def _sync_guest_time(self): + """Try to set VM time to the current value. This is typically useful + when clock wasn't running on the VM for some time (e.g. during + suspension or migration), especially if the time delay exceeds NTP + tolerance. + + It is not guaranteed that the time is actually set (it depends on guest + environment, especially QEMU agent presence) or that the set time is + very precise (NTP in the guest should take care of it if needed). + """ + t = time.time() + seconds = int(t) + nseconds = int((t - seconds) * 10 ** 9) + try: + self._domain.setTime(time={'seconds': seconds, + 'nseconds': nseconds}) + except libvirt.libvirtError as e: + code = e.get_error_code() + if code == libvirt.VIR_ERR_AGENT_UNRESPONSIVE: + LOG.debug('Failed to set time: QEMU agent unresponsive', + instance_uuid=self.uuid) + elif code == libvirt.VIR_ERR_NO_SUPPORT: + LOG.debug('Failed to set time: not supported', + instance_uuid=self.uuid) + elif code == libvirt.VIR_ERR_ARGUMENT_UNSUPPORTED: + LOG.debug('Failed to set time: agent not configured', + instance_uuid=self.uuid) + else: + LOG.warning(_LW('Failed to set time: %(reason)s'), + {'reason': e}, instance_uuid=self.uuid) + except Exception as ex: + # The highest priority is not to let this method crash and thus + # disrupt its caller in any way. So we swallow this error here, + # to be absolutely safe. + LOG.debug('Failed to set time: %(reason)s', + {'reason': ex}, instance_uuid=self.uuid) + else: + LOG.debug('Time updated to: %d.%09d', seconds, nseconds, + instance_uuid=self.uuid) + def inject_nmi(self): """Injects an NMI to a guest.""" self._domain.injectNMI() @@ -152,6 +194,7 @@ class Guest(object): def resume(self): """Resumes a suspended guest.""" self._domain.resume() + self._sync_guest_time() def enable_hairpin(self): """Enables hairpin mode for this guest.""" diff --git a/releasenotes/notes/set_guest_time-736939fe725cbdab.yaml b/releasenotes/notes/set_guest_time-736939fe725cbdab.yaml new file mode 100644 index 000000000000..184829af75df --- /dev/null +++ b/releasenotes/notes/set_guest_time-736939fe725cbdab.yaml @@ -0,0 +1,7 @@ +--- +features: + - Libvirt driver will attempt to update the time of a suspended and/or + a migrated guest in order to keep the guest clock in sync. + This operation will require the guest agent to be + configured and running in order to be able to run. However, this operation + will not be disruptive.