Merge lp:~allenap/maas/ntp-service-iburst-orphan-et-al into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5421
Proposed branch: lp:~allenap/maas/ntp-service-iburst-orphan-et-al
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 357 lines (+115/-34)
6 files modified
src/maasserver/regiondservices/ntp.py (+2/-2)
src/maasserver/regiondservices/tests/test_ntp.py (+4/-4)
src/provisioningserver/ntp/config.py (+32/-8)
src/provisioningserver/ntp/tests/test_config.py (+70/-13)
src/provisioningserver/rackdservices/ntp.py (+2/-2)
src/provisioningserver/rackdservices/tests/test_ntp.py (+5/-5)
To merge this branch: bzr merge lp:~allenap/maas/ntp-service-iburst-orphan-et-al
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Blake Rouse (community) Approve
Review via email: mp+307183@code.launchpad.net

Commit message

For NTP, use iburst for all servers, configure orphan mode, and add hostnames as pools.

Description of the change

These options have been gleaned from much experimentation and discussion with Paul Gear.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/regiondservices/ntp.py'
2--- src/maasserver/regiondservices/ntp.py 2016-09-23 15:20:23 +0000
3+++ src/maasserver/regiondservices/ntp.py 2016-09-30 16:34:44 +0000
4@@ -17,7 +17,7 @@
5 from maasserver.service_monitor import service_monitor
6 from maasserver.utils.orm import transactional
7 from maasserver.utils.threads import deferToDatabase
8-from provisioningserver.ntp.config import configure
9+from provisioningserver.ntp.config import configure_region
10 from provisioningserver.utils.text import split_string_list
11 from provisioningserver.utils.twisted import (
12 callOut,
13@@ -106,7 +106,7 @@
14 `_getConfiguration`.
15 """
16 d = deferToThread(
17- configure, configuration.references, configuration.peers)
18+ configure_region, configuration.references, configuration.peers)
19 d.addCallback(
20 callOut, service_monitor.restartService, "ntp")
21 return d
22
23=== modified file 'src/maasserver/regiondservices/tests/test_ntp.py'
24--- src/maasserver/regiondservices/tests/test_ntp.py 2016-09-23 15:20:23 +0000
25+++ src/maasserver/regiondservices/tests/test_ntp.py 2016-09-30 16:34:44 +0000
26@@ -90,16 +90,16 @@
27 def test__tryUpdate_updates_ntp_server(self):
28 service = ntp.RegionNetworkTimeProtocolService(reactor)
29 configuration = yield deferToDatabase(self.make_example_configuration)
30- configure = self.patch_autospec(ntp, "configure")
31+ configure_region = self.patch_autospec(ntp, "configure_region")
32 restartService = self.patch_autospec(service_monitor, "restartService")
33 yield service._tryUpdate()
34- self.assertThat(configure, MockCalledOnceWith(
35+ self.assertThat(configure_region, MockCalledOnceWith(
36 configuration.references, configuration.peers))
37 self.assertThat(restartService, MockCalledOnceWith("ntp"))
38 # If the configuration has not changed then a second call to
39- # `_tryUpdate` does not result in another call to `configure`.
40+ # `_tryUpdate` does not result in another call to `configure_region`.
41 yield service._tryUpdate()
42- self.assertThat(configure, MockCalledOnceWith(
43+ self.assertThat(configure_region, MockCalledOnceWith(
44 configuration.references, configuration.peers))
45 self.assertThat(restartService, MockCalledOnceWith("ntp"))
46
47
48=== modified file 'src/provisioningserver/ntp/config.py'
49--- src/provisioningserver/ntp/config.py 2016-09-07 17:12:32 +0000
50+++ src/provisioningserver/ntp/config.py 2016-09-30 16:34:44 +0000
51@@ -4,9 +4,11 @@
52 """NTP service configuration."""
53
54 __all__ = [
55- "configure",
56+ "configure_rack",
57+ "configure_region",
58 ]
59
60+from functools import partial
61 from itertools import (
62 dropwhile,
63 groupby,
64@@ -25,7 +27,7 @@
65 _ntp_maas_conf_name = "ntp/maas.conf"
66
67
68-def configure(servers, peers):
69+def configure(servers, peers, offset):
70 """Configure the local NTP server with the given time references.
71
72 This writes new ``ntp.conf`` and ``ntp.maas.conf`` files, using ``sudo``
73@@ -33,13 +35,27 @@
74
75 :param servers: An iterable of server addresses -- IPv4, IPv6, hostnames
76 -- to use as time references.
77+ :param peers: An iterable of peer addresses -- IPv4, IPv6, hostnames -- to
78+ use as time references.
79+ :param offset: A relative stratum within MAAS's world. A region controller
80+ would be 0 and a rack controller would be 1.
81 """
82- ntp_maas_conf = _render_ntp_maas_conf(servers, peers).encode("ascii")
83+ ntp_maas_conf = _render_ntp_maas_conf(servers, peers, offset)
84 ntp_maas_conf_path = get_tentative_path("etc", _ntp_maas_conf_name)
85- sudo_write_file(ntp_maas_conf_path, ntp_maas_conf, mode=0o644)
86- ntp_conf = _render_ntp_conf(ntp_maas_conf_path).encode("ascii")
87+ sudo_write_file(
88+ ntp_maas_conf_path,
89+ ntp_maas_conf.encode("ascii"),
90+ mode=0o644)
91+ ntp_conf = _render_ntp_conf(ntp_maas_conf_path)
92 ntp_conf_path = get_tentative_path("etc", _ntp_conf_name)
93- sudo_write_file(ntp_conf_path, ntp_conf, mode=0o644)
94+ sudo_write_file(
95+ ntp_conf_path,
96+ ntp_conf.encode("ascii"),
97+ mode=0o644)
98+
99+
100+configure_region = partial(configure, offset=0)
101+configure_rack = partial(configure, offset=1)
102
103
104 def _render_ntp_conf(includefile):
105@@ -66,17 +82,25 @@
106 yield "includefile %s\n" % includefile
107
108
109-def _render_ntp_maas_conf(servers, peers):
110+def _render_ntp_maas_conf(servers, peers, offset):
111 """Render ``ntp.maas.conf`` for the given time references.
112
113 :param servers: An iterable of server addresses -- IPv4, IPv6, hostnames
114 -- to use as time references.
115+ :param peers: An iterable of peer addresses -- IPv4, IPv6, hostnames -- to
116+ use as time references.
117+ :param offset: A relative stratum used when calculating the stratum for
118+ orphan mode (see http://support.ntp.org/bin/view/Support/OrphanMode).
119 """
120 lines = ["# MAAS NTP configuration."]
121 servers = map(_normalise_address, servers)
122- lines.extend("server %s" % server for server in servers)
123+ lines.extend(
124+ "%s %s iburst" % (
125+ ("server" if isinstance(server, IPAddress) else "pool"), server)
126+ for server in servers)
127 peers = map(_normalise_address, peers)
128 lines.extend("peer %s" % peer for peer in peers)
129+ lines.append("tos orphan {:d}".format(offset + 8))
130 lines.append("") # Add newline at end.
131 return "\n".join(lines)
132
133
134=== modified file 'src/provisioningserver/ntp/tests/test_config.py'
135--- src/provisioningserver/ntp/tests/test_config.py 2016-09-07 14:57:19 +0000
136+++ src/provisioningserver/ntp/tests/test_config.py 2016-09-30 16:34:44 +0000
137@@ -5,6 +5,9 @@
138
139 __all__ = []
140
141+from functools import partial
142+from itertools import chain
143+from random import randrange
144 import re
145
146 from maastesting.factory import factory
147@@ -15,6 +18,9 @@
148 from provisioningserver.path import get_path
149 from testtools.matchers import (
150 Equals,
151+ Is,
152+ IsInstance,
153+ MatchesStructure,
154 StartsWith,
155 )
156
157@@ -25,20 +31,34 @@
158
159
160 def extract_servers_and_pools(configuration):
161+ return [
162+ address for _, address, _ in
163+ extract_servers_and_pools_full(configuration)
164+ ]
165+
166+
167+def extract_servers_and_pools_full(configuration):
168 return re.findall(
169- r" ^ \s* (?: server | pool ) \s+ (.+) $ ", configuration,
170- re.VERBOSE | re.MULTILINE)
171+ r"^ *(server|pool) +(\S+)(?: +([^\r\n]+))?$",
172+ configuration, re.MULTILINE)
173
174
175 def extract_peers(configuration):
176+ return [
177+ address for _, address, _ in
178+ extract_peers_full(configuration)
179+ ]
180+
181+
182+def extract_peers_full(configuration):
183 return re.findall(
184- r" ^ \s* peer \s+ (.+) $ ", configuration,
185- re.VERBOSE | re.MULTILINE)
186+ r"^ *(peer) +(\S+)(?: +([^\r\n]+))?$",
187+ configuration, re.MULTILINE)
188
189
190 def extract_included_files(configuration):
191 return re.findall(
192- r" ^ \s* includefile \s+ (.*) $ ", configuration,
193+ r" ^ \s* includefile \s+ (\S*) $ ", configuration,
194 re.VERBOSE | re.MULTILINE)
195
196
197@@ -60,7 +80,8 @@
198 factory.make_ipv6_address(),
199 factory.make_hostname(),
200 ]
201- config.configure(servers, peers)
202+ offset = randrange(0, 5)
203+ config.configure(servers, peers, offset)
204 ntp_conf_path = get_path("etc", config._ntp_conf_name)
205 ntp_maas_conf_path = get_path("etc", config._ntp_maas_conf_name)
206 ntp_conf = read_configuration(ntp_conf_path)
207@@ -74,6 +95,28 @@
208 extract_servers_and_pools(ntp_maas_conf), Equals(servers))
209 self.assertThat(
210 extract_peers(ntp_maas_conf), Equals(peers))
211+ self.assertThat(
212+ extract_tos_options(ntp_maas_conf),
213+ Equals(["orphan", str(offset + 8)]))
214+
215+ def test_configure_region_is_alias(self):
216+ self.assertThat(config.configure_region, IsInstance(partial))
217+ self.assertThat(
218+ config.configure_region, MatchesStructure(
219+ func=Is(config.configure), args=Equals(()),
220+ keywords=Equals({"offset": 0})))
221+
222+ def test_configure_rack_is_alias(self):
223+ self.assertThat(config.configure_rack, IsInstance(partial))
224+ self.assertThat(
225+ config.configure_rack, MatchesStructure(
226+ func=Is(config.configure), args=Equals(()),
227+ keywords=Equals({"offset": 1})))
228+
229+
230+def extract_tos_options(configuration):
231+ commands = re.findall(r"^ *tos +([^\r\n]+)$", configuration, re.MULTILINE)
232+ return list(chain.from_iterable(map(str.split, commands)))
233
234
235 class TestRenderNTPConfFromSource(MAASTestCase):
236@@ -152,11 +195,17 @@
237 factory.make_ipv6_address(),
238 factory.make_hostname(),
239 ]
240- ntp_maas_conf = config._render_ntp_maas_conf(servers, [])
241+ ntp_maas_conf = config._render_ntp_maas_conf(servers, [], 0)
242 self.assertThat(ntp_maas_conf, StartsWith(
243 '# MAAS NTP configuration.\n'))
244- servers_or_pools = extract_servers_and_pools(ntp_maas_conf)
245- self.assertThat(servers_or_pools, Equals(servers))
246+ servers_or_pools = extract_servers_and_pools_full(ntp_maas_conf)
247+ # Hostnames are rendered as `pool` commands so that all IP addresses
248+ # resolved via DNS are included in clock selection.
249+ self.assertThat(servers_or_pools, Equals([
250+ ("server", servers[0], "iburst"),
251+ ("server", servers[1], "iburst"),
252+ ("pool", servers[2], "iburst"),
253+ ]))
254
255 def test_renders_the_given_peers(self):
256 peers = [
257@@ -164,11 +213,12 @@
258 factory.make_ipv6_address(),
259 factory.make_hostname(),
260 ]
261- ntp_maas_conf = config._render_ntp_maas_conf([], peers)
262+ ntp_maas_conf = config._render_ntp_maas_conf([], peers, 0)
263 self.assertThat(ntp_maas_conf, StartsWith(
264 '# MAAS NTP configuration.\n'))
265- observed_peers = extract_peers(ntp_maas_conf)
266- self.assertThat(observed_peers, Equals(peers))
267+ observed_peers = extract_peers_full(ntp_maas_conf)
268+ self.assertThat(observed_peers, Equals(
269+ [("peer", peer, "") for peer in peers]))
270
271 def test_renders_ipv6_mapped_ipv4_addresses_as_plain_ipv4(self):
272 server_as_ipv4 = factory.make_ipv4_address()
273@@ -176,12 +226,19 @@
274 peer_as_ipv4 = factory.make_ipv4_address()
275 peer_as_ipv6 = str(IPAddress(peer_as_ipv4).ipv6())
276 ntp_maas_conf = config._render_ntp_maas_conf(
277- [server_as_ipv6], [peer_as_ipv6])
278+ [server_as_ipv6], [peer_as_ipv6], 0)
279 observed_servers = extract_servers_and_pools(ntp_maas_conf)
280 self.assertThat(observed_servers, Equals([server_as_ipv4]))
281 observed_peers = extract_peers(ntp_maas_conf)
282 self.assertThat(observed_peers, Equals([peer_as_ipv4]))
283
284+ def test_configures_orphan_mode(self):
285+ offset = randrange(0, 5)
286+ ntp_maas_conf = config._render_ntp_maas_conf([], [], offset)
287+ self.assertThat(
288+ extract_tos_options(ntp_maas_conf),
289+ Equals(["orphan", str(offset + 8)]))
290+
291
292 example_ntp_conf = """\
293 # /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
294
295=== modified file 'src/provisioningserver/rackdservices/ntp.py'
296--- src/provisioningserver/rackdservices/ntp.py 2016-08-19 09:38:08 +0000
297+++ src/provisioningserver/rackdservices/ntp.py 2016-09-30 16:34:44 +0000
298@@ -10,7 +10,7 @@
299 from datetime import timedelta
300
301 import attr
302-from provisioningserver.ntp.config import configure
303+from provisioningserver.ntp.config import configure_rack
304 from provisioningserver.rpc import exceptions
305 from provisioningserver.rpc.region import GetControllerType
306 from provisioningserver.service_monitor import service_monitor
307@@ -98,7 +98,7 @@
308 `_getConfiguration`.
309 """
310 if configuration.is_rack and not configuration.is_region:
311- d = deferToThread(configure, configuration.references, ())
312+ d = deferToThread(configure_rack, configuration.references, ())
313 d.addCallback(callOut, service_monitor.restartService, "ntp")
314 return d
315
316
317=== modified file 'src/provisioningserver/rackdservices/tests/test_ntp.py'
318--- src/provisioningserver/rackdservices/tests/test_ntp.py 2016-09-07 15:40:52 +0000
319+++ src/provisioningserver/rackdservices/tests/test_ntp.py 2016-09-30 16:34:44 +0000
320@@ -116,16 +116,16 @@
321 rpc_service, _ = yield prepareRegionForGetControllerType(self)
322 servers = {c.address[0] for c in rpc_service.getAllClients()}
323 service = ntp.RackNetworkTimeProtocolService(rpc_service, reactor)
324- configure = self.patch_autospec(ntp, "configure")
325+ configure_rack = self.patch_autospec(ntp, "configure_rack")
326 restartService = self.patch_autospec(service_monitor, "restartService")
327
328 yield service._tryUpdate()
329- self.assertThat(configure, MockCalledOnceWith(servers, ()))
330+ self.assertThat(configure_rack, MockCalledOnceWith(servers, ()))
331 self.assertThat(restartService, MockCalledOnceWith("ntp"))
332 # If the configuration has not changed then a second call to
333 # `_tryUpdate` does not result in another call to `configure`.
334 yield service._tryUpdate()
335- self.assertThat(configure, MockCalledOnceWith(servers, ()))
336+ self.assertThat(configure_rack, MockCalledOnceWith(servers, ()))
337 self.assertThat(restartService, MockCalledOnceWith("ntp"))
338
339 @inlineCallbacks
340@@ -160,7 +160,7 @@
341 self.useFixture(MAASRootFixture())
342 rpc_service, _ = yield prepareRegionForGetControllerType(self, True)
343 service = ntp.RackNetworkTimeProtocolService(rpc_service, reactor)
344- self.patch_autospec(ntp, "configure") # No-op configuration.
345+ self.patch_autospec(ntp, "configure_rack") # No-op configuration.
346
347 # There is no most recently applied configuration.
348 self.assertThat(service._configuration, Is(None))
349@@ -192,7 +192,7 @@
350 @inlineCallbacks
351 def test__tryUpdate_logs_errors_from_broken_method(self):
352 rpc_service, _ = yield prepareRegionForGetControllerType(self)
353- self.patch_autospec(ntp, "configure") # No-op configuration.
354+ self.patch_autospec(ntp, "configure_rack") # No-op configuration.
355
356 service = ntp.RackNetworkTimeProtocolService(rpc_service, reactor)
357 broken_method = self.patch_autospec(service, self.method)