1212from stack .exception import *
1313
1414## DESIGN CONSIDERATIONS
15- # The NTP system set up by default in Stacki has the
15+ # The timekeeping system set up by default in Stacki has the
1616# following design considerations.
1717#
18- # 1. The Frontend syncs its own time from external servers. - stored in time.servers attribute
19- # 2. The Frontend serves NTP to all backends on all networks that are PXE enabled.
20- # 3. By default, the Backends sync time from the frontend.
18+ # 1. All servers in the cluster, can sync times from 2 sources. Any external server
19+ # specified in the time.servers attribute, and any servers with time.orphantype value
20+ # set to "parent"
21+ # 2. By default, the frontend is configured as a parent, and can serve time to the rest
22+ # of the cluster.
23+ # 3. time.servers attribute should ideally be set for the frontend, so that it can pick
24+ # up
2125# 4. If the time.servers attribute is set for a backend host,
2226# then those servers are appended to the list of servers to
2327# sync from.
2428# 5. If certain nodes are set as parent servers, they can form a time island, from which
2529# all other servers can sync
30+ # 6. If Stacki shouldn't be managing time at all, then the time.protocol attribute can
31+ # be unset, and the admin can manage the time by themselves
2632
2733class Command (stack .commands .HostArgumentProcessor ,
2834 stack .commands .NetworkArgumentProcessor ,
2935 stack .commands .report .command ):
3036 """
3137 Create a time configuration report (NTP or chrony).
38+ At a minimum at least one server in the cluster has to be designated as a "parent"
39+ time server. This ensures that there's atleast one server in the cluster that can
40+ serve time. By default, the stacki frontend is a parent timekeeper.
41+ Ideally, a "parent" time server will also have the "time.servers" attribute set
42+ to talk to an external time server, so that the cluster is in sync with the external
43+ time-keeping entity.
3244
3345 Relevant Attributes:
3446
3547 time.servers - Optional - Comma-separated list of servers (IP addresses
3648 or resolvable names) that dictate the servers to use for time-keeping.
3749 This list is in *addition* to the Stacki frontend.
3850
39- time.orphantype - Optional - Only the value of "parent" affects the behaviour
40- if the NTP service. If a host is designated as a parent Orphan-type,
41- that means this host can co-ordinate time with other peers to maintain
51+ time.orphantype - Required for at-least one host in the cluster.
52+ Only the value of "parent" affects the behaviour
53+ of the NTP service. If a host is designated as a
54+ parent Orphan-type, that means this host can
55+ co-ordinate time with other peers to maintain
4256 time on the time island.
4357
4458 time.protocol - Optional - Can take the value of "chrony" or "ntp". Default
45- Stacki behaviour is to use chrony for the hosts.
59+ Stacki behaviour is to use chrony for all hosts, except SLES 11 hosts.
60+ SLES 11 hosts use NTP by default
4661
4762 <arg optional='0' type='string' name='host'>
4863 Host name of machine
4964 </arg>
5065
5166 <example cmd='report host time backend-0-0'>
5267 Create a time configuration file for backend-0-0.
68+
69+ *Configuration*
70+ Scenario 1: Use Frontend for time keeping
71+ If the desired behavior is to use the frontend as the primary, and only time server,
72+ no configuration changes have to be made in Stacki. This is the default behavior.
73+
74+ Scenario 2: Use frontend for time keeping, in sync with an external time server.
75+ Set time.servers attribute on the frontend to the IP Address, or Hostname of an external time server.
76+ This will set the frontend to sync time against an external time server, and all other hosts to
77+ sync time from the frontend.
78+
79+ Scenario 3: Sync time on all hosts using an external time server only.
80+ Unset the frontend appliance attribute of time.orphantype. This will disable the frontend
81+ from serving time.
82+ Set the time.servers attribute for all hosts to the IP address, or Hostname of external
83+ time server. This will require that all hosts can contact, and connect to the external time
84+ server.
85+
86+ Scenario 4: Sync time on some hosts from external time servers, and create a time island.
87+ Set some of the hosts' time.orphantype attribute to parent. Then set those hosts' time.servers
88+ attribute to the IP address, or Hostname of external time server. The list of parent time servers
89+ can include the frontend or not.
90+
91+ Scenario 5: Don't use Stacki to sync time.
92+ To do this, make sure that the time.protocol attribute is not set for a host or a set of hosts.
5393 </example>
5494 """
5595
5696 def getNTPPeers (self , host ):
5797
5898 peerlist = []
59- parents = [ h for h in self .attrs if ( self .attrs [h ].get ('time.orphantype' ) == 'parent' and h != host and h != self . frontend ) ]
99+ parents = [ h for h in self .attrs if ( self .attrs [h ].get ('time.orphantype' ) == 'parent' and h != host ) ]
60100 if parents :
61101 op = self .call ('list.host.interface' , parents + ['expanded=true' ])
62102 networks = self .getNTPNetworkNames (host )
@@ -65,6 +105,7 @@ def getNTPPeers(self, host):
65105 for i in n :
66106 if i not in peerlist :
67107 peerlist .append (i )
108+
68109 return peerlist
69110
70111 def getNTPNetworkNames (self , host ):
@@ -82,7 +123,8 @@ def getNTPNetworkNames(self, host):
82123 if ntp_net_extra :
83124 for net in ntp_net_extra .split (',' ):
84125 if net not in stacki_networks :
85- raise CommandError (self , f"Network { net } is unknown to Stacki. Fix 'time.networks' attribute for host { host } " )
126+ raise CommandError (self , f"Network { net } is unknown to Stacki\n " + \
127+ "Fix 'time.networks' attribute for host {host}" )
86128 networks .append (net )
87129 return networks
88130
@@ -103,20 +145,11 @@ def getNTPServers(self, host):
103145 if timeservers :
104146 timeservers = timeservers .split ("," )
105147
106- if self .appliance == 'frontend' :
107- if not timeservers :
108- timeservers = [self .attrs [host ].get ('Kickstart_PublicNTPHost' )]
109- else :
110- n = []
111- output = self .call ('list.host.interface' , [ host , 'expanded=true' ])
112- for o in output :
113- if o ['network' ] in self .frontend_ntp_addrs :
114- n .append (self .frontend_ntp_addrs [o ['network' ]])
115- if not timeservers :
116- timeservers = n
117- else :
118- timeservers = n + timeservers
148+ if not timeservers :
149+ timeservers = []
119150
151+ for peer in self .getNTPPeers (host ):
152+ timeservers .append (peer )
120153 return timeservers
121154
122155 def set_timezone (self , host ):
@@ -163,12 +196,17 @@ def run(self, params, args):
163196 self .appliance = self .attrs [host ].get ('appliance' )
164197 self .osversion = self .attrs [host ].get ('os.version' )
165198
199+ if protocol == None :
200+ continue
201+
166202 if self .osversion == '11.x' :
167203 protocol = 'ntp'
168204
169205 self .timeservers = self .getNTPServers (host )
170206 if len (self .timeservers ) == 0 :
171- raise CommandError (self , f'No time servers specified for host { host } . Check network interface assignments' )
207+ if not 'time.orphantype' in self .attrs [host ] or not self .attrs [host ]['time.orphantype' ] == 'parent' :
208+ raise CommandError (self , f'No time servers specified for host { host } \n ' +
209+ 'Check time.* attributes, and network interface assignments' )
172210
173211 self .runImplementation ('time_%s' % protocol , (host ))
174212
0 commit comments