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.