Skip to content

Commit

Permalink
FEATURE: Allow non-frontend parents to be time servers
Browse files Browse the repository at this point in the history
Also allow for non management of time
  • Loading branch information
anooprajendra authored and bsanders committed Sep 19, 2019
1 parent d7e8142 commit cb68613
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 26 deletions.
1 change: 1 addition & 0 deletions common/nodes/time-server.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ https://github.com/Teradata/stacki/blob/master/LICENSE.txt
<stack:script stack:stage="install-post">
/opt/stack/bin/stack set attr attr=time.protocol value=chrony
/opt/stack/bin/stack set appliance attr frontend attr=time.protocol value=chrony
/opt/stack/bin/stack set appliance attr frontend attr=time.orphantype value=parent
/opt/stack/bin/stack report host time &hostname; | /opt/stack/bin/stack report script | sh
</stack:script>
</stack:stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,91 @@
from stack.exception import *

## DESIGN CONSIDERATIONS
# The NTP system set up by default in Stacki has the
# The timekeeping system set up by default in Stacki has the
# following design considerations.
#
# 1. The Frontend syncs its own time from external servers. - stored in time.servers attribute
# 2. The Frontend serves NTP to all backends on all networks that are PXE enabled.
# 3. By default, the Backends sync time from the frontend.
# 1. All servers in the cluster, can sync times from 2 sources. Any external server
# specified in the time.servers attribute, and any servers with time.orphantype value
# set to "parent"
# 2. By default, the frontend is configured as a parent, and can serve time to the rest
# of the cluster.
# 3. time.servers attribute should ideally be set for the frontend, so that it can pick
# up
# 4. If the time.servers attribute is set for a backend host,
# then those servers are appended to the list of servers to
# sync from.
# 5. If certain nodes are set as parent servers, they can form a time island, from which
# all other servers can sync
# 6. If Stacki shouldn't be managing time at all, then the time.protocol attribute can
# be unset, and the admin can manage the time by themselves

class Command(stack.commands.HostArgumentProcessor,
stack.commands.NetworkArgumentProcessor,
stack.commands.report.command):
"""
Create a time configuration report (NTP or chrony).
At a minimum at least one server in the cluster has to be designated as a "parent"
time server. This ensures that there's atleast one server in the cluster that can
serve time. By default, the stacki frontend is a parent timekeeper.
Ideally, a "parent" time server will also have the "time.servers" attribute set
to talk to an external time server, so that the cluster is in sync with the external
time-keeping entity.
Relevant Attributes:
time.servers - Optional - Comma-separated list of servers (IP addresses
or resolvable names) that dictate the servers to use for time-keeping.
This list is in *addition* to the Stacki frontend.
time.orphantype - Optional - Only the value of "parent" affects the behaviour
if the NTP service. If a host is designated as a parent Orphan-type,
that means this host can co-ordinate time with other peers to maintain
time.orphantype - Required for at-least one host in the cluster.
Only the value of "parent" affects the behaviour
of the NTP service. If a host is designated as a
parent Orphan-type, that means this host can
co-ordinate time with other peers to maintain
time on the time island.
time.protocol - Optional - Can take the value of "chrony" or "ntp". Default
Stacki behaviour is to use chrony for the hosts.
Stacki behaviour is to use chrony for all hosts, except SLES 11 hosts.
SLES 11 hosts use NTP by default
<arg optional='0' type='string' name='host'>
Host name of machine
</arg>
<example cmd='report host time backend-0-0'>
Create a time configuration file for backend-0-0.
*Configuration*
Scenario 1: Use Frontend for time keeping
If the desired behavior is to use the frontend as the primary, and only time server,
no configuration changes have to be made in Stacki. This is the default behavior.
Scenario 2: Use frontend for time keeping, in sync with an external time server.
Set time.servers attribute on the frontend to the IP Address, or Hostname of an external time server.
This will set the frontend to sync time against an external time server, and all other hosts to
sync time from the frontend.
Scenario 3: Sync time on all hosts using an external time server only.
Unset the frontend appliance attribute of time.orphantype. This will disable the frontend
from serving time.
Set the time.servers attribute for all hosts to the IP address, or Hostname of external
time server. This will require that all hosts can contact, and connect to the external time
server.
Scenario 4: Sync time on some hosts from external time servers, and create a time island.
Set some of the hosts' time.orphantype attribute to parent. Then set those hosts' time.servers
attribute to the IP address, or Hostname of external time server. The list of parent time servers
can include the frontend or not.
Scenario 5: Don't use Stacki to sync time.
To do this, make sure that the time.protocol attribute is not set for a host or a set of hosts.
</example>
"""

def getNTPPeers(self, host):

peerlist = []
parents = [ h for h in self.attrs if ( self.attrs[h].get('time.orphantype') == 'parent' and h != host and h != self.frontend ) ]
parents = [ h for h in self.attrs if ( self.attrs[h].get('time.orphantype') == 'parent' and h != host) ]
if parents:
op = self.call('list.host.interface', parents + ['expanded=true'])
networks = self.getNTPNetworkNames(host)
Expand All @@ -65,6 +105,7 @@ def getNTPPeers(self, host):
for i in n:
if i not in peerlist:
peerlist.append(i)

return peerlist

def getNTPNetworkNames(self, host):
Expand All @@ -82,7 +123,8 @@ def getNTPNetworkNames(self, host):
if ntp_net_extra:
for net in ntp_net_extra.split(','):
if net not in stacki_networks:
raise CommandError(self, f"Network {net} is unknown to Stacki. Fix 'time.networks' attribute for host {host}")
raise CommandError(self, f"Network {net} is unknown to Stacki\n" + \
"Fix 'time.networks' attribute for host {host}")
networks.append(net)
return networks

Expand All @@ -103,20 +145,11 @@ def getNTPServers(self, host):
if timeservers:
timeservers = timeservers.split(",")

if self.appliance == 'frontend':
if not timeservers:
timeservers = [self.attrs[host].get('Kickstart_PublicNTPHost')]
else:
n = []
output = self.call('list.host.interface', [ host , 'expanded=true'])
for o in output:
if o['network'] in self.frontend_ntp_addrs:
n.append(self.frontend_ntp_addrs[o['network']])
if not timeservers:
timeservers = n
else:
timeservers = n + timeservers
if not timeservers:
timeservers = []

for peer in self.getNTPPeers(host):
timeservers.append(peer)
return timeservers

def set_timezone(self, host):
Expand Down Expand Up @@ -163,12 +196,17 @@ def run(self, params, args):
self.appliance = self.attrs[host].get('appliance')
self.osversion = self.attrs[host].get('os.version')

if protocol == None:
continue

if self.osversion == '11.x':
protocol = 'ntp'

self.timeservers = self.getNTPServers(host)
if len(self.timeservers) == 0:
raise CommandError(self, f'No time servers specified for host {host}. Check network interface assignments')
if not 'time.orphantype' in self.attrs[host] or not self.attrs[host]['time.orphantype'] == 'parent':
raise CommandError(self, f'No time servers specified for host {host}\n' +
'Check time.* attributes, and network interface assignments')

self.runImplementation('time_%s' % protocol, (host))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def run(self, host):
else:
self.client(host)

self.owner.addOutput(host, "/usr/sbin/chronyd -q 'server %s iburst'" % self.owner.timeservers[0])
if self.owner.timeservers:
self.owner.addOutput(host, "/usr/sbin/chronyd -q 'server %s iburst'" % self.owner.timeservers[0])

self.owner.addOutput(host, "systemctl enable chronyd")
self.owner.addOutput(host, 'systemctl start chronyd')
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ def run(self, host):
#
# set the clock right now
#
self.owner.addOutput(host, '/usr/sbin/ntpdate %s' % self.owner.timeservers[0])
if self.owner.timeservers:
self.owner.addOutput(host, '/usr/sbin/ntpdate %s' % self.owner.timeservers[0])
# Restart the NTPD service
if self.owner.osversion == '11.x':
self.owner.addOutput(host, 'service ntp start')
Expand Down

0 comments on commit cb68613

Please sign in to comment.