Merge ~paulgear/ntp-charm/+git/ntp-charm:chrony-support into ntp-charm:master

Proposed by Paul Gear
Status: Merged
Approved by: Paul Gear
Approved revision: 9e7a3c32a64a7697834bfaf7fc07871fe7dc9a06
Merged at revision: ff753b4081d20cbf0ddc2fabf3a6b933a3006d23
Proposed branch: ~paulgear/ntp-charm/+git/ntp-charm:chrony-support
Merge into: ntp-charm:master
Diff against target: 488 lines (+288/-58)
7 files modified
config.yaml (+16/-14)
hooks/ntp_hooks.py (+38/-33)
hooks/ntp_implementation.py (+132/-0)
templates/chrony.conf (+58/-0)
templates/chrony.default (+13/-0)
templates/ntp.conf (+29/-11)
templates/ntp.default (+2/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+340780@code.launchpad.net

Commit message

Add basic chrony support to ntp charm in preparation for bionic release. The Nagios check does not currently work with chrony.

Description of the change

First cut at adding chrony support to ntp charm in preparation for bionic release.

To post a comment you must log in.
Revision history for this message
Seth Arnold (seth-arnold) wrote :

Nice changes, thanks

Revision history for this message
Stuart Bishop (stub) wrote :

Looks good.

I do strongly believe that the ntp_implementation config option should be dropped, as its only use case is to create broken or unsupported deployments. The function in ntp_implementation.py can keep its auto mode, since it is really external/shared code rather than part of the charm, but the charm should always call it with 'auto'. Not only does a foot-gun option not encode best practice like the charm should, but it will also cause problems in a few years when we drop Xenial support and the NTP code along with it.

review: Approve
Revision history for this message
Paul Gear (paulgear) wrote :

That all makes good sense to me; will push a new rev shortly.

Revision history for this message
Stuart Bishop (stub) wrote :

+1

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Unable to determine commit message from repository - please click "Set commit message" and enter the commit message manually.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision ff753b4081d20cbf0ddc2fabf3a6b933a3006d23

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/config.yaml b/config.yaml
index 1bbc1ed..993673d 100644
--- a/config.yaml
+++ b/config.yaml
@@ -3,13 +3,13 @@ options:
3 default: ""3 default: ""
4 type: string4 type: string
5 description: >5 description: >
6 Space-separated list of NTP servers to use as sources for time.6 Space-separated list of NTP servers to use as time sources.
7 peers:7 peers:
8 default: ""8 default: ""
9 type: string9 type: string
10 description: >10 description: >
11 Space-separated list of NTP servers to use as peers for time.11 Space-separated list of NTP servers to use as peers. Under ntpd,
12 Peers are allowed to query the local NTP server via ntpq.12 peers are allowed to query the local NTP server via ntpq.
13 pools:13 pools:
14 default: >14 default: >
15 0.ubuntu.pool.ntp.org15 0.ubuntu.pool.ntp.org
@@ -19,25 +19,27 @@ options:
19 ntp.ubuntu.com19 ntp.ubuntu.com
20 type: string20 type: string
21 description: >21 description: >
22 Space-separated list of NTP servers to use as pool sources for22 Space-separated list of NTP servers to use as pool sources. These
23 time. Pool sources are recommended over normal sources for their23 are recommended over normal sources for their self-healing
24 self-healing capabilities. Leave empty to disable pool sources.24 capabilities. Leave empty to disable pool sources.
25 orphan_stratum:25 orphan_stratum:
26 default: 626 default: 0
27 type: int27 type: int
28 description: >28 description: >
29 The NTP stratum at which ntp most lose connectivity to before it 29 The stratum at which NTP must lose connectivity to before it considers
30 considers itself orphaned, and starts communicating with local peers.30 itself orphaned, and starts determining the reference time with local
31 The default value of 6 will enable orphaned operation when there are31 peers. A typical value is 6, which will enable orphaned operation
32 no stratum 6 servers or servers of a higher stratum available, which32 when there are no stratum 6 servers or servers of a higher stratum
33 is two strata below most Internet NTP hosts.33 available, which is two strata below most Internet NTP hosts. Set to
34 Set to 0 to disable orphan mode entirely.34 0 to disable orphan mode entirely. You must enable at least one peer
35 in order to use orphan mode, but four or more is recommended for best
36 results.
35 nagios_context:37 nagios_context:
36 default: "juju"38 default: "juju"
37 type: string39 type: string
38 description: |40 description: |
39 Used by the nrpe-external-master subordinate charm.41 Used by the nrpe-external-master subordinate charm.
40 A string that will be prepended to instance name to set the host name42 A string which will be prepended to instance name to set the host name
41 in nagios. So for instance the hostname would be something like:43 in nagios. So for instance the hostname would be something like:
42 juju-myservice-044 juju-myservice-0
43 If you're running multiple environments with the same services in them45 If you're running multiple environments with the same services in them
diff --git a/hooks/ntp_hooks.py b/hooks/ntp_hooks.py
index efde7ad..e9f537d 100755
--- a/hooks/ntp_hooks.py
+++ b/hooks/ntp_hooks.py
@@ -1,21 +1,18 @@
1#!/usr/bin/python31#!/usr/bin/python3
22
3from charmhelpers.contrib.charmsupport import nrpe3from charmhelpers.contrib.charmsupport import nrpe
4from charmhelpers.contrib.templating.jinja import render
5from charmhelpers.core import hookenv, host, unitdata4from charmhelpers.core import hookenv, host, unitdata
6import charmhelpers.fetch as fetch5import charmhelpers.fetch as fetch
7import os6import os
8import shutil
9import sys7import sys
108
9import ntp_implementation
11import ntp_scoring10import ntp_scoring
1211
13NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'12NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
1413
15NTP_CONF = '/etc/ntp.conf'
16NTP_CONF_ORIG = '{}.orig'.format(NTP_CONF)
17
18hooks = hookenv.Hooks()14hooks = hookenv.Hooks()
15implementation = ntp_implementation.get_implementation()
1916
2017
21def get_peer_sources(topN=6):18def get_peer_sources(topN=6):
@@ -65,9 +62,8 @@ def get_peer_sources(topN=6):
65def install():62def install():
66 fetch.apt_update(fatal=True)63 fetch.apt_update(fatal=True)
67 ntp_scoring.install_packages()64 ntp_scoring.install_packages()
68 if ntp_scoring.get_virt_type() != 'container':65 fetch.apt_install(implementation.packages_to_install(), fatal=True)
69 fetch.apt_install(["ntp"], fatal=True)66 implementation.save_config()
70 shutil.copy(NTP_CONF, NTP_CONF_ORIG)
7167
7268
73def get_sources(sources, iburst=True, source_list=None):69def get_sources(sources, iburst=True, source_list=None):
@@ -92,16 +88,20 @@ def get_sources(sources, iburst=True, source_list=None):
92@hooks.hook('master-relation-departed')88@hooks.hook('master-relation-departed')
93@hooks.hook('ntp-peers-relation-joined',89@hooks.hook('ntp-peers-relation-joined',
94 'ntp-peers-relation-changed')90 'ntp-peers-relation-changed')
95@host.restart_on_change({NTP_CONF: ['ntp']})91@host.restart_on_change({implementation.config_file(): [implementation.service_name()]})
96def write_config():92def write_config():
97 ntp_scoring.install_packages()93 ntp_scoring.install_packages()
94
98 if ntp_scoring.get_virt_type() == 'container':95 if ntp_scoring.get_virt_type() == 'container':
99 host.service_stop('ntp')96 is_container = 1
100 host.service_pause('ntp')97 else:
101 hookenv.close_port(123, protocol="UDP")98 is_container = 0
102 return
10399
104 host.service_resume('ntp')100 implementation.set_startup_options({
101 'is_container': is_container,
102 })
103
104 host.service_resume(implementation.service_name())
105 hookenv.open_port(123, protocol="UDP")105 hookenv.open_port(123, protocol="UDP")
106106
107 use_iburst = hookenv.config('use_iburst')107 use_iburst = hookenv.config('use_iburst')
@@ -142,17 +142,17 @@ def write_config():
142 kv.unset('auto_peer')142 kv.unset('auto_peer')
143143
144 if len(remote_sources) == 0 and len(remote_peers) == 0 and len(remote_pools) == 0:144 if len(remote_sources) == 0 and len(remote_peers) == 0 and len(remote_pools) == 0:
145 # we have no peers/pools/servers; restore default ntp.conf provided by OS145 # we have no peers/pools/servers; restore default config
146 shutil.copy(NTP_CONF_ORIG, NTP_CONF)146 implementation.restore_config()
147 else:147 else:
148 # otherwise, write our own configuration148 # otherwise, write our own configuration
149 with open(NTP_CONF, "w") as ntpconf:149 implementation.set_config({
150 ntpconf.write(render(os.path.basename(NTP_CONF), {150 'is_container': is_container,
151 'orphan_stratum': orphan_stratum,151 'orphan_stratum': orphan_stratum,
152 'peers': remote_peers,152 'peers': remote_peers,
153 'pools': remote_pools,153 'pools': remote_pools,
154 'servers': remote_sources,154 'servers': remote_sources,
155 }))155 })
156156
157 if hookenv.relation_ids('nrpe-external-master'):157 if hookenv.relation_ids('nrpe-external-master'):
158 update_nrpe_config()158 update_nrpe_config()
@@ -263,27 +263,32 @@ def hyperv_sync_status():
263263
264@hooks.hook('update-status')264@hooks.hook('update-status')
265def assess_status():265def assess_status():
266 version = fetch.get_upstream_version('ntp')266 package = implementation.package_name()
267 version = fetch.get_upstream_version(package)
267 if version is not None:268 if version is not None:
268 hookenv.application_version_set(version)269 hookenv.application_version_set(version)
269270
270 # create base status271 # base status
271 if ntp_scoring.get_virt_type() == 'container':272 status = package + ': '
272 state = 'blocked'273
273 status = 'NTP not supported in containers: please configure on host'274 # append service status
274 elif host.service_running('ntp'):275 if host.service_running(implementation.service_name()):
275 state = 'active'276 state = 'active'
276 status = 'Ready'277 status += 'Ready'
277 else:278 else:
278 state = 'blocked'279 state = 'blocked'
279 status = 'Not running'280 status += 'Not running'
281
282 # append container status
283 if ntp_scoring.get_virt_type() == 'container':
284 status += ', time sync disabled in container'
280285
281 # append Hyper-V status, if any286 # append Hyper-V status
282 hyperv_status = hyperv_sync_status()287 hyperv_status = hyperv_sync_status()
283 if hyperv_status is not None:288 if hyperv_status is not None:
284 status += ', ' + hyperv_status289 status += ', ' + hyperv_status
285290
286 # append scoring status, if any291 # append scoring status
287 # (don't force update of the score from update-status more than once a month)292 # (don't force update of the score from update-status more than once a month)
288 max_age = 31 * 86400293 max_age = 31 * 86400
289 scorestr = ntp_scoring.get_score_string(max_seconds=max_age)294 scorestr = ntp_scoring.get_score_string(max_seconds=max_age)
diff --git a/hooks/ntp_implementation.py b/hooks/ntp_implementation.py
290new file mode 100644295new file mode 100644
index 0000000..e565b0e
--- /dev/null
+++ b/hooks/ntp_implementation.py
@@ -0,0 +1,132 @@
1
2# Copyright (c) 2018 Canonical Ltd
3# License: GPLv3
4# Author: Paul Gear
5
6# NTP implementation details
7
8import charmhelpers.contrib.templating.jinja as templating
9import charmhelpers.core.host
10import charmhelpers.osplatform
11import os.path
12import shutil
13
14
15class NTPImplementation:
16 """Base class for NTP implementations."""
17
18 def config_file(self):
19 raise NotImplementedError
20
21 def _config_file_backup(self):
22 return self.config_file() + ".bak"
23
24 def _config_file_orig(self):
25 return self.config_file() + ".orig"
26
27 def _config_file_template(self):
28 raise NotImplementedError
29
30 def package_name(self):
31 raise NotImplementedError
32
33 def packages_to_install(self):
34 return [self.package_name()]
35
36 def restore_config(self):
37 for path in [self._config_file_orig(), self._config_file_backup()]:
38 if os.path.exists(path):
39 shutil.copy(path, self.config_file())
40
41 def save_config(self):
42 backup = self._config_file_backup()
43 if not os.path.exists(backup):
44 shutil.copy(self.config_file(), backup)
45
46 def service_name(self):
47 raise NotImplementedError
48
49 def set_config(self, config):
50 with open(self.config_file(), "w") as conffile:
51 conffile.write(templating.render(self._config_file_template(), config))
52
53 def set_startup_options(self, config):
54 with open(self._startup_config_file(), "w") as startup:
55 startup.write(templating.render(self._startup_template_file(), config))
56
57 def _startup_config_file(self):
58 raise NotImplementedError
59
60 def _startup_template_file(self):
61 raise NotImplementedError
62
63
64class Chronyd(NTPImplementation):
65
66 def config_file(self):
67 return "/etc/chrony/chrony.conf"
68
69 def _config_file_orig(self):
70 return "/usr/share/chrony/chrony.conf"
71
72 def _config_file_template(self):
73 return "chrony.conf"
74
75 def package_name(self):
76 return "chrony"
77
78 def service_name(self):
79 return "chrony"
80
81 def _startup_config_file(self):
82 return "/etc/default/chrony"
83
84 def _startup_template_file(self):
85 return "chrony.default"
86
87
88class NTPd(NTPImplementation):
89
90 def config_file(self):
91 return "/etc/ntp.conf"
92
93 def _config_file_template(self):
94 return "ntp.conf"
95
96 def package_name(self):
97 return "ntp"
98
99 def service_name(self):
100 return "ntp"
101
102 def _startup_config_file(self):
103 return "/etc/default/ntp"
104
105 def _startup_template_file(self):
106 return "ntp.default"
107
108
109def get_implementation(implementation_name=None):
110 """Select the appropriate NTP implementation for this platform."""
111 if implementation_name is not None and implementation_name.lower() == 'chrony':
112 return Chronyd()
113 if implementation_name is not None and implementation_name.lower() == 'ntpd':
114 return NTPd()
115
116 # anything else: auto mode
117 platform = charmhelpers.osplatform.get_platform()
118 version = float(charmhelpers.core.host.lsb_release()['DISTRIB_RELEASE'])
119
120 if platform == 'ubuntu':
121 if version > 18:
122 # Ubuntu 18.04 or later: use chronyd
123 return Chronyd()
124 else:
125 # Ubuntu 17.10 or earlier: use ntpd
126 return NTPd()
127 elif platform == 'centos':
128 # CentOS: use chronyd
129 return Chronyd()
130 else:
131 # something else: use ntpd
132 return NTPd()
diff --git a/templates/chrony.conf b/templates/chrony.conf
0new file mode 100644133new file mode 100644
index 0000000..6e559cc
--- /dev/null
+++ b/templates/chrony.conf
@@ -0,0 +1,58 @@
1# juju-generated chrony configuration
2
3# This directive specify the location of the file containing ID/key pairs for
4# NTP authentication.
5keyfile /etc/chrony/chrony.keys
6
7# This directive specify the file into which chronyd will store the rate
8# information.
9driftfile /var/lib/chrony/chrony.drift
10
11# Enable logging
12log tracking measurements statistics rtc refclocks
13
14# Log files location.
15logdir /var/log/chrony
16
17# Step the system clock instead of slewing it if the adjustment is larger than
18# one second, but only in the first three clock updates.
19makestep 1 3
20
21# Stop bad estimates upsetting machine clock.
22maxupdateskew 100.0
23
24{%- if is_container == 0 %}
25
26# This directive enables kernel synchronisation (every 11 minutes) of the
27# real-time clock. Note that it can't be used along with the 'rtcfile' directive.
28rtcsync
29{%- endif %}
30
31# Save state about peers & servers
32dumpdir /var/lib/chrony
33
34{%- if orphan_stratum > 0 %}
35
36# Orphan mode enabled
37local stratum {{ orphan_stratum }} orphan
38{%- endif %}
39
40# allow any host to use us as an NTP server
41# (use juju unexpose to prevent access)
42allow
43
44# PEERS
45{%- for peer in peers %}
46peer {{ peer.name }} {{ peer.iburst }}
47{%- endfor %}
48
49# POOLS
50{%- for pool in pools %}
51pool {{ pool.name }} {{ pool.iburst }}
52{%- endfor %}
53
54# SERVERS
55{%- for server in servers %}
56server {{ server.name }} {{ server.iburst }}
57{%- endfor %}
58
diff --git a/templates/chrony.default b/templates/chrony.default
0new file mode 10064459new file mode 100644
index 0000000..aaaf90e
--- /dev/null
+++ b/templates/chrony.default
@@ -0,0 +1,13 @@
1# juju-generated chrony startup config
2
3# This is a configuration file for /etc/init.d/chrony and
4# /lib/systemd/system/chrony.service; it allows you to pass various options to
5# the chrony daemon without editing the init script or service file.
6
7# Options to pass to chrony.
8{%- if is_container == 1 %}
9DAEMON_OPTS="-x"
10{%- else %}
11DAEMON_OPTS=""
12{%- endif %}
13
diff --git a/templates/ntp.conf b/templates/ntp.conf
index d5674c7..1a38ecf 100644
--- a/templates/ntp.conf
+++ b/templates/ntp.conf
@@ -1,28 +1,46 @@
1# juju-generated ntp configuration1# juju-generated ntp configuration
2tinker panic 02tinker panic 0
3driftfile /var/lib/ntp/ntp.drift3driftfile /var/lib/ntp/ntp.drift
4
4statsdir /var/log/ntpstats5statsdir /var/log/ntpstats
5statistics loopstats peerstats clockstats sysstats6statistics loopstats peerstats clockstats sysstats
6filegen loopstats file loopstats type day enable7filegen loopstats file loopstats type day enable
7filegen peerstats file peerstats type day enable8filegen peerstats file peerstats type day enable
8filegen clockstats file clockstats type day enable9filegen clockstats file clockstats type day enable
9filegen sysstats file sysstats type day enable10filegen sysstats file sysstats type day enable
11
10restrict -4 default kod notrap nomodify nopeer noquery limited12restrict -4 default kod notrap nomodify nopeer noquery limited
11restrict -6 default kod notrap nomodify nopeer noquery limited13restrict -6 default kod notrap nomodify nopeer noquery limited
12restrict source notrap nomodify noquery14restrict source notrap nomodify noquery
13restrict 127.0.0.115restrict 127.0.0.1
14restrict ::116restrict ::1
15{% if orphan_stratum > 0 %}17
18{%- if is_container == 1 %}
19# running in a container - time adjustments disabled
20disable kernel
21{%- endif %}
22
23{%- if orphan_stratum > 0 %}
24
25# orphan mode enabled
16tos orphan {{ orphan_stratum }}26tos orphan {{ orphan_stratum }}
17{% endif %}27{%- endif %}
18{% for peer in peers %}restrict {{ peer.name }} kod notrap nomodify28
19{% endfor %}
20# SERVERS
21{% for server in servers %}server {{ server.name }} {{ server.iburst }}
22{% endfor %}
23# PEERS29# PEERS
24{% for peer in peers %}peer {{ peer.name }} {{ peer.iburst }}30{%- for peer in peers %}
25{% endfor %}31restrict {{ peer.name }} kod notrap nomodify
32{%- endfor %}
33{%- for peer in peers %}
34peer {{ peer.name }} {{ peer.iburst }}
35{%- endfor %}
36
26# POOLS37# POOLS
27{% for pool in pools %}pool {{ pool.name }} {{ pool.iburst }}38{%- for pool in pools %}
28{% endfor %}39pool {{ pool.name }} {{ pool.iburst }}
40{%- endfor %}
41
42# SERVERS
43{%- for server in servers %}
44server {{ server.name }} {{ server.iburst }}
45{%- endfor %}
46
diff --git a/templates/ntp.default b/templates/ntp.default
29new file mode 10064447new file mode 100644
index 0000000..f84c389
--- /dev/null
+++ b/templates/ntp.default
@@ -0,0 +1,2 @@
1# juju-generated ntp startup configuration
2NTPD_OPTS='-g'

Subscribers

People subscribed via source and target branches