Adds real time clock support in NTP client

Add 2 new parameters which set up the real time clock and enable the NTP
client service.
- ntp_enable_service: enables the NTP service if it's set on True (default)
- real_time_clock_utc: sets the real time clock to use universal time
  (True) or local time (False- default)

Change-Id: Ic6a613b2e0866784ab60ff78b2bfef6eba31a279
Implements: blueprint update-ntp-client
Co-Authored-By: Paula Madalina Crismaru <pcrismaru@cloudbasesolutions.com>
This commit is contained in:
Alessandro Pilotti 2017-02-01 15:00:21 +02:00 committed by Paula Madalina Crismaru
parent 3b415d1d41
commit 687afe3e43
8 changed files with 218 additions and 39 deletions

View File

@ -99,10 +99,17 @@ class GlobalOptions(conf_base.Options):
'heat_config_dir', default='C:\\cfn',
help='The directory where the Heat configuration files must '
'be saved'),
cfg.BoolOpt(
'ntp_enable_service', default=True,
help='Enables the NTP client service'),
cfg.BoolOpt(
'ntp_use_dhcp_config', default=False,
help='Configures NTP client time synchronization using '
'the NTP servers provided via DHCP'),
cfg.BoolOpt(
'real_time_clock_utc', default=False,
help='Sets the real time clock to use universal time (True) '
'or local time (False)'),
cfg.BoolOpt(
'inject_user_password', default=True,
help='Set the password provided in the configuration. '

View File

@ -133,3 +133,9 @@ class BaseOSUtils(object):
def get_current_user(self):
"""Retrieve the username under which the current thread runs."""
raise NotImplementedError()
def is_real_time_clock_utc(self):
raise NotImplementedError()
def set_real_time_clock_utc(self, utc):
raise NotImplementedError()

View File

@ -1322,3 +1322,23 @@ class WindowsUtils(base.BaseOSUtils):
raise exception.CloudbaseInitException(
"The given timezone name is unrecognised: %r" % timezone_name)
timezone.Timezone(windows_name).set(self)
def is_real_time_clock_utc(self):
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
'SYSTEM\\CurrentControlSet\\Control\\'
'TimeZoneInformation') as key:
try:
utc = winreg.QueryValueEx(key, 'RealTimeIsUniversal')[0]
return utc != 0
except WindowsError as ex:
if ex.winerror == 2:
return False
raise
def set_real_time_clock_utc(self, utc):
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
'SYSTEM\\CurrentControlSet\\Control\\'
'TimeZoneInformation',
0, winreg.KEY_ALL_ACCESS) as key:
winreg.SetValueEx(key, 'RealTimeIsUniversal', 0,
winreg.REG_DWORD, 1 if utc else 0)

View File

@ -42,8 +42,19 @@ class NTPClientPlugin(base.BasePlugin):
return list(map(socket.inet_ntoa, chunks))
def execute(self, service, shared_data):
reboot_required = False
osutils = osutils_factory.get_os_utils()
if osutils.is_real_time_clock_utc() != CONF.real_time_clock_utc:
osutils.set_real_time_clock_utc(CONF.real_time_clock_utc)
LOG.info('RTC set to UTC: %s', CONF.real_time_clock_utc)
reboot_required = True
if CONF.ntp_enable_service:
self.verify_time_service(osutils)
LOG.info('NTP client service enabled')
if CONF.ntp_use_dhcp_config:
osutils = osutils_factory.get_os_utils()
dhcp_hosts = osutils.get_dhcp_hosts_in_use()
ntp_option_data = None
@ -58,13 +69,10 @@ class NTPClientPlugin(base.BasePlugin):
if not ntp_option_data:
LOG.debug("Could not obtain the NTP configuration via DHCP")
return base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False
return base.PLUGIN_EXECUTE_ON_NEXT_BOOT, reboot_required
ntp_hosts = self._unpack_ntp_hosts(ntp_option_data)
self.verify_time_service(osutils)
osutils.set_ntp_client_config(ntp_hosts)
LOG.info('NTP client configured. Server(s): %s' % ntp_hosts)
return base.PLUGIN_EXECUTION_DONE, False
return base.PLUGIN_EXECUTION_DONE, reboot_required

View File

@ -33,9 +33,12 @@ class NTPClientPlugin(ntpclient.NTPClientPlugin):
It also changes the current triggers of the service (domain joined
for instance).
"""
args = ["sc.exe", "triggerinfo", _W32TIME_SERVICE, "delete"]
osutils.execute_system32_process(args)
args = ["sc.exe", "triggerinfo", _W32TIME_SERVICE,
"start/networkon", "stop/networkoff"]
return osutils.execute_system32_process(args)
osutils.execute_system32_process(args)
def verify_time_service(self, osutils):
"""Verify that the time service is up and try to start it."""

View File

@ -2193,3 +2193,78 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
"failed: %r", 100):
self._winutils.execute_process_as_user(token, args, True,
new_console)
def _test_is_realtime_clock_uct(self, utc=1, exception=False,
exception_raised=False):
if exception:
eclass = Exception
ex = eclass()
self.windows_utils.WindowsError = eclass
if not exception_raised:
ex.winerror = 2
else:
ex.winerror = mock.sentinel.winerror
self._winreg_mock.QueryValueEx.side_effect = ex
self._winreg_mock.QueryValueEx.return_value = [utc]
if exception_raised:
with self.assertRaises(eclass):
self._winutils.is_real_time_clock_utc()
response = None
else:
response = self._winutils.is_real_time_clock_utc()
if exception_raised:
expected_result = None
elif exception:
expected_result = False
else:
if utc == 0:
expected_result = utc
else:
expected_result = utc != 0
self.assertEqual(response, expected_result)
self._winreg_mock.OpenKey.assert_called_with(
self._winreg_mock.HKEY_LOCAL_MACHINE,
'SYSTEM\\CurrentControlSet\\Control\\'
'TimeZoneInformation')
self._winreg_mock.QueryValueEx.assert_called_with(
self._winreg_mock.OpenKey.return_value.__enter__.return_value,
'RealTimeIsUniversal')
def test_is_realtime_clock_utc(self):
self._test_is_realtime_clock_uct()
def test_is_not_realtime_clock_utc(self):
self._test_is_realtime_clock_uct(utc=0)
def test_is_realtime_clock_utc_registry_value_missing(self):
self._test_is_realtime_clock_uct(exception=True)
def test_is_realtime_clock_utc_exception_raised(self):
self._test_is_realtime_clock_uct(exception=True,
exception_raised=True)
def _test_set_real_time_clock_utc(self, utc):
self._winutils.set_real_time_clock_utc(utc)
self._winreg_mock.OpenKey.assert_called_with(
self._winreg_mock.HKEY_LOCAL_MACHINE,
'SYSTEM\\CurrentControlSet\\Control\\'
'TimeZoneInformation',
0, self._winreg_mock.KEY_ALL_ACCESS)
key = self._winreg_mock.OpenKey.return_value.__enter__.return_value
self._winreg_mock.SetValueEx.assert_called_with(
key, 'RealTimeIsUniversal', 0, self._winreg_mock.REG_DWORD,
1 if utc else 0)
def test_set_real_time_clock_utc_set_zero(self):
self._test_set_real_time_clock_utc(utc=0)
def test_set_real_time_clock_utc(self):
self._test_set_real_time_clock_utc(utc=1)

View File

@ -24,23 +24,26 @@ from cloudbaseinit.plugins.common import ntpclient
from cloudbaseinit.tests import testutils
from cloudbaseinit.utils import dhcp
MODULE_PATH = "cloudbaseinit.plugins.common.ntpclient"
class NTPClientPluginTests(unittest.TestCase):
def setUp(self):
self._ntpclient = ntpclient.NTPClientPlugin()
self.snatcher = testutils.LogSnatcher(MODULE_PATH)
@testutils.ConfPatcher('ntp_use_dhcp_config', True)
@testutils.ConfPatcher("real_time_clock_utc", "fake time")
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
@mock.patch('cloudbaseinit.utils.dhcp.get_dhcp_options')
@mock.patch('cloudbaseinit.plugins.common.ntpclient.NTPClientPlugin.'
'verify_time_service')
@mock.patch('cloudbaseinit.plugins.common.ntpclient.NTPClientPlugin.'
'_unpack_ntp_hosts')
@mock.patch(MODULE_PATH + '.NTPClientPlugin.verify_time_service')
@mock.patch(MODULE_PATH + '.NTPClientPlugin._unpack_ntp_hosts')
def _test_execute(self, mock_unpack_ntp_hosts,
mock_verify_time_service,
mock_get_dhcp_options, mock_get_os_utils,
original_unpack_hosts, ntp_data, expected_hosts):
original_unpack_hosts, ntp_data, expected_hosts,
is_real_time=True, enable_service=False,
use_dhcp_config=False):
# Set the side effect to the actual function, in order to
# see the expected result.
mock_unpack_ntp_hosts.side_effect = original_unpack_hosts
@ -55,34 +58,85 @@ class NTPClientPluginTests(unittest.TestCase):
mock_get_dhcp_options.return_value = mock_options_data
mock_options_data.get.return_value = ntp_data
response = self._ntpclient.execute(service=mock_service,
shared_data='fake data')
expected_logging = []
reboot_required = False
mock_osutils.get_dhcp_hosts_in_use.assert_called_once_with()
mock_get_dhcp_options.assert_called_once_with(
'fake dhcp host', [dhcp.OPTION_NTP_SERVERS])
mock_options_data.get.assert_called_once_with(dhcp.OPTION_NTP_SERVERS)
if ntp_data:
mock_unpack_ntp_hosts.assert_called_once_with(ntp_data)
self.assertEqual((base.PLUGIN_EXECUTION_DONE, False), response)
mock_verify_time_service.assert_called_once_with(mock_osutils)
mock_osutils.set_ntp_client_config.assert_called_once_with(
expected_hosts)
if is_real_time:
mock_time = "fake time"
else:
self.assertEqual((base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False),
response)
mock_time = "fake value"
reboot_required = True
mock_osutils.is_real_time_clock_utc.return_value = mock_time
with self.snatcher:
with testutils.ConfPatcher('ntp_enable_service', enable_service):
with testutils.ConfPatcher('ntp_use_dhcp_config',
use_dhcp_config):
response = self._ntpclient.execute(
service=mock_service,
shared_data=mock.sentinel.shared_data)
mock_get_os_utils.assert_called_once_with()
mock_osutils.is_real_time_clock_utc.assert_called_once_with()
if not is_real_time:
mock_osutils.set_real_time_clock_utc.assert_called_once_with(
"fake time")
expected_logging.append('RTC set to UTC: %s' % mock_time)
if enable_service:
mock_verify_time_service.assert_called_once_with(mock_osutils)
expected_logging.append('NTP client service enabled')
if use_dhcp_config:
mock_osutils.get_dhcp_hosts_in_use.assert_called_once_with()
mock_get_dhcp_options.assert_called_once_with(
'fake dhcp host', [dhcp.OPTION_NTP_SERVERS])
mock_options_data.get.assert_called_once_with(
dhcp.OPTION_NTP_SERVERS)
if ntp_data:
mock_unpack_ntp_hosts.assert_called_once_with(ntp_data)
self.assertEqual(response,
(base.PLUGIN_EXECUTION_DONE, reboot_required))
mock_osutils.set_ntp_client_config.assert_called_once_with(
expected_hosts)
else:
expected_logging.append(
"Could not obtain the NTP configuration via DHCP")
self.assertEqual(
response,
(base.PLUGIN_EXECUTE_ON_NEXT_BOOT, reboot_required))
else:
self.assertEqual(response,
(base.PLUGIN_EXECUTION_DONE, reboot_required))
def test_execute_is_not_real_time(self):
self._test_execute(
original_unpack_hosts=ntpclient.NTPClientPlugin._unpack_ntp_hosts,
ntp_data=b'\xc0\xa8<\x8c',
expected_hosts=['192.168.60.140'],
is_real_time=False)
def test_execute_enable_ntp_service(self):
self._test_execute(
original_unpack_hosts=ntpclient.NTPClientPlugin._unpack_ntp_hosts,
ntp_data=b'\xc0\xa8<\x8c',
expected_hosts=['192.168.60.140'],
enable_service=True)
def test_execute_use_dhcp_config_has_ntp_option_data(self):
self._test_execute(
original_unpack_hosts=ntpclient.NTPClientPlugin._unpack_ntp_hosts,
ntp_data=b'\xc0\xa8<\x8c',
expected_hosts=['192.168.60.140'],
use_dhcp_config=True)
def test_execute_no_ntp_options_data(self):
self._test_execute(original_unpack_hosts=None,
ntp_data=None,
expected_hosts=None)
expected_hosts=None,
use_dhcp_config=True)
def test_execute(self):
def test_execute_no_dhcp_config(self):
self._test_execute(
original_unpack_hosts=ntpclient.NTPClientPlugin._unpack_ntp_hosts,
ntp_data=b'\xc0\xa8<\x8c',
expected_hosts=['192.168.60.140'])
self._test_execute(
original_unpack_hosts=ntpclient.NTPClientPlugin._unpack_ntp_hosts,
ntp_data=b'\xc0\xa8<\x8c\xc0\xa8<\x8e',
expected_hosts=['192.168.60.140', '192.168.60.142'])
original_unpack_hosts=None,
ntp_data=None, expected_hosts=None,
use_dhcp_config=False)

View File

@ -34,9 +34,15 @@ class NTPClientPluginTests(unittest.TestCase):
def test_set_ntp_trigger_mode(self):
mock_osutils = mock.Mock()
self._ntpclient._set_ntp_trigger_mode(mock_osutils)
mock_osutils.execute_system32_process.assert_called_once_with(
["sc.exe", "triggerinfo", ntpclient._W32TIME_SERVICE,
"start/networkon", "stop/networkoff"])
args = [
mock.call.execute_system32_process(
["sc.exe", "triggerinfo", ntpclient._W32TIME_SERVICE,
"delete"]),
mock.call.execute_system32_process(
["sc.exe", "triggerinfo", ntpclient._W32TIME_SERVICE,
"start/networkon", "stop/networkoff"])
]
mock_osutils.assert_has_calls(args)
@mock.patch('time.sleep')
@mock.patch('cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin.'