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
This commit is contained in:
parent
a147215196
commit
0376da0627
@ -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
|
||||
|
||||
|
@ -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)])
|
||||
|
@ -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."""
|
||||
|
7
releasenotes/notes/set_guest_time-736939fe725cbdab.yaml
Normal file
7
releasenotes/notes/set_guest_time-736939fe725cbdab.yaml
Normal file
@ -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.
|
Loading…
Reference in New Issue
Block a user