-
Notifications
You must be signed in to change notification settings - Fork 15
/
ntp-client_proposal.rb
581 lines (517 loc) · 19.2 KB
/
ntp-client_proposal.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
require "yast"
module Yast
# This is used as the general interface between yast2-country
# (time,timezone) and yast2-ntp-client.
class NtpClientProposalClient < Client
include Yast::Logger
def main
Yast.import "UI"
textdomain "ntp-client"
Yast.import "Address"
Yast.import "NetworkService"
Yast.import "NtpClient"
Yast.import "Service"
Yast.import "String"
Yast.import "Stage"
Yast.import "PackageSystem"
Yast.import "Pkg"
Yast.import "Popup"
Yast.import "Progress"
Yast.import "Report"
Yast.import "Timezone"
Yast.import "Wizard"
# API:
#
# Usual *_proposal functions: MakeProposal, AskUser, Write.
# (but not Description; see, it just *looks* like *_proposal)
# Additionally:
# GetNTPEnabled (queries Service::Enabled)
# SetUseNTP [ntp_used]
@ret = nil
@func = ""
@param = {}
if Ops.greater_than(Builtins.size(WFM.Args), 0) &&
Ops.is_string?(WFM.Args(0))
@func = Convert.to_string(WFM.Args(0))
if Ops.greater_than(Builtins.size(WFM.Args), 1) &&
Ops.is_map?(WFM.Args(1))
@param = Convert.to_map(WFM.Args(1))
end
end
Builtins.y2milestone(
"ntp-client_proposal called func %1 param %2",
@func,
@param
)
# FIXME: must go to module to preserve value
@ntp_was_used = false
case @func
when "GetNTPEnabled"
@ret = GetNTPEnabled()
when "SetUseNTP"
NtpClient.ntp_selected = Ops.get_boolean(@param, "ntp_used", false)
@ret = true
when "dhcp_ntp_servers"
@ret = NtpClient.dhcp_ntp_servers.map(&:hostname)
when "MakeProposal"
@ret = MakeProposal()
when "Write"
@ret = Write(@param)
when "ui_help_text"
@ret = ui_help_text
when "ui_init"
@rp = Ops.get_term(@param, "replace_point") { Id(:rp) }
@ft = Ops.get_boolean(@param, "first_time", false)
@ret = ui_init(@rp, @ft)
when "ui_try_save"
@ret = ui_try_save
when "ui_enable_disable_widgets"
@ret = ui_enable_disable_widgets(
Ops.get_boolean(@param, "enabled", false)
)
when "ui_handle"
@ret = ui_handle(Ops.get(@param, "ui"))
else
log.error("Not known called func #{@func}")
end
deep_copy(@ret)
end
def ui_help_text
if Stage.initial
# help text
_(
"<p>Press <b>Synchronize Now</b>, to get your system time set correctly " \
"using the selected NTP server. If you want to make use of NTP permanently, " \
"enable the <b>Save NTP Configuration</b> option</p>"
) + _(
"<p>Enabling <b>Run NTP as daemon</b> option, the NTP service will be " \
"started as daemon. Otherwise the system time will be synchronized periodically. " \
"The default interval is 15 min. You can change it after installation " \
"with the <b>yast2 ntp-client module</b>.</p>"
) + _(
"<p>Synchronization with the NTP server can be done only when " \
"the network is configured.</p>"
)
else
# help text
_(
"<p>Using the <b>Configure</b> button, open the advanced NTP configuration.</p>"
)
end
end
def ui_enable_disable_widgets(enabled)
UI.ChangeWidget(Id(:ntp_address), :Enabled, enabled) if select_ntp_server
if Stage.initial
UI.ChangeWidget(Id(:run_service), :Enabled, enabled)
# FIXME: With chronyd, we cannot synchronize if the service is already
# running, we could force a makestep in this case, but then the button
# should be reworded and maybe the user should confirm it (bsc#1087048)
if !NetworkService.isNetworkRunning || Service.Active(NtpClient.service_name)
UI.ChangeWidget(Id(:ntp_now), :Enabled, false)
else
UI.ChangeWidget(Id(:ntp_now), :Enabled, enabled)
end
UI.ChangeWidget(Id(:ntp_save), :Enabled, enabled)
end
if UI.WidgetExists(Id(:ntp_configure))
# bnc#483787
UI.ChangeWidget(Id(:ntp_configure), :Enabled, enabled)
end
nil
end
def handle_invalid_hostname(server)
# translators: error popup
Popup.Error(Builtins.sformat(_("Invalid NTP server hostname %1"), server))
nil
end
def GetNTPEnabled
if !Stage.initial
progress_orig = Progress.set(false)
NtpClient.Read
Progress.set(progress_orig)
end
Builtins.y2milestone("synchronize_time %1", NtpClient.synchronize_time)
Builtins.y2milestone("run_service %1", NtpClient.run_service)
NtpClient.synchronize_time || Service.Enabled(NtpClient.service_name)
end
def ValidateSingleServer(ntp_server)
if !Address.Check(ntp_server)
UI.SetFocus(Id(:ntp_address))
return false
end
true
end
def MakeProposal
# On the running system, read all the data, otherwise firewall and other
# stuff outside ntp.conf may not be initialized correctly (#375877)
if !Stage.initial
progress_orig = Progress.set(false)
NtpClient.Read
Progress.set(progress_orig)
# ntp_selected is true if NTP was proposed during installation (fate#303520)
elsif !NtpClient.ntp_selected
NtpClient.ProcessNtpConf
end
if select_ntp_server
ntp_items = fallback_ntp_items
# Once read or proposed any config we consider it as read (bnc#427712)
NtpClient.config_has_been_read = true
log.info "ntp_items :#{ntp_items}"
UI.ChangeWidget(Id(:ntp_address), :Items, ntp_items)
if !Stage.initial
UI.ChangeWidget(Id(:ntp_address), :Value,
NtpClient.GetUsedNtpServers.first)
end
end
nil
end
# @param [Yast::Term] replace_point id of replace point which should be used
# @param [Boolean] first_time when asking for first time, we check if service is running
# @return should our radio button be selected
def ui_init(replace_point, first_time)
if select_ntp_server
ntp_server_widget = ComboBox(
Id(:ntp_address),
Opt(:editable, :hstretch),
# TRANSLATORS: combo box label
_("&NTP Server Address")
)
else
# Only show all ntp servers
text = _("Synchronization Servers:\n").dup
counter = (NtpClient.GetUsedNtpServers.size > 3) ? 3 : NtpClient.GetUsedNtpServers.size
counter.times do |i|
text << NtpClient.GetUsedNtpServers[i]
text << "\n"
end
if NtpClient.GetUsedNtpServers.size > 3
# TRANSLATOR %{count} number of additional servers
text << format(_("... (%{count} more servers)"),
count: (NtpClient.GetUsedNtpServers.size - counter))
end
ntp_server_widget = Label(text)
end
if Stage.initial
# TRANSLATORS: push button label
ntp_server_action_widget = Left(PushButton(Id(:ntp_now), _("S&ynchronize now")))
save_run_widget = VBox(
HBox(
HSpacing(0.5),
# TRANSLATORS: check box label
Left(
CheckBox(
Id(:run_service),
_("&Run NTP as daemon"),
NtpClient.run_service
)
)
),
HBox(
HSpacing(0.5),
# TRANSLATORS: check box label
Left(
CheckBox(Id(:ntp_save), _("&Save NTP Configuration"), true)
)
)
)
else
# TRANSLATORS: push button label
# bnc#449615: only simple config for inst-sys
ntp_server_action_widget = Left(PushButton(Id(:ntp_configure), _("&Configure...")))
save_run_widget = VBox()
end
cont = VBox(
VSpacing(0.5),
HBox(
HSpacing(3),
HWeight(
1,
Left(
ntp_server_widget
)
),
HWeight(
1,
VBox(
# In TextMode and empty label is not filling an extra space, so
# an explicit vertical space was added in order to move down the
# push button being aligned with the combo box input.
UI.TextMode ? VSpacing(1) : Label(""),
ntp_server_action_widget
)
)
),
HBox(
HSpacing(3),
HWeight(
1,
save_run_widget
)
)
)
UI.ReplaceWidget(replace_point, cont)
if Stage.initial && !NetworkService.isNetworkRunning
UI.ChangeWidget(Id(:ntp_now), :Enabled, false)
end
# ^ createui0
# FIXME: is it correct? move out?
ntp_used = (first_time && !Stage.initial) ? GetNTPEnabled() : NtpClient.ntp_selected
UI.ChangeWidget(Id(:ntp_save), :Value, ntp_used) if Stage.initial
MakeProposal()
ntp_used
end
def AskUser
ret = nil
if select_ntp_server
# The user can select ONE ntp server.
# So we Initialize the ntp client module with the selected ntp server.
ntp_server = Convert.to_string(UI.QueryWidget(Id(:ntp_address), :Value))
return :invalid_hostname unless ValidateSingleServer(ntp_server)
NtpClient.ntp_conf.clear_pools
NtpClient.ntp_conf.add_pool(ntp_server)
end
# Calling ntp client module.
ret = :next if WFM.CallFunction("ntp-client")
# Initialize the rest
MakeProposal()
ret
end
# Writes configuration for ntp client.
# @param ntp_servers [Array<String>] list of servers to configure as ntp sync sources
# @param ntp_server [String] fallback server that is used if `ntp_servers` param is empty.
# @param run_service [Boolean] define if synchronize with systemd services or via systemd timer
# @return true
def WriteNtpSettings(ntp_servers, ntp_server, run_service)
ntp_servers = deep_copy(ntp_servers)
NtpClient.modified = true
if select_ntp_server
# The user has changed the ntp-server(s). So we are writing them.
NtpClient.ntp_conf.clear_pools
ntp_servers << ntp_server if ntp_servers.empty?
ntp_servers.each do |server|
NtpClient.ntp_conf.add_pool(server)
end
end
if run_service
NtpClient.run_service = true
NtpClient.synchronize_time = false
else
NtpClient.run_service = false
NtpClient.synchronize_time = true
NtpClient.sync_interval = NtpClientClass::DEFAULT_SYNC_INTERVAL
end
# OK, so we stored the server address
# In inst-sys we don't need to care further
# ntp-client_finish will do the job
# In installed system we must write the settings
if !Stage.initial
# FIXME: so that the progress does not disturb the dialog to be returned to
Wizard.OpenAcceptDialog
NtpClient.Write
Wizard.CloseDialog
end
true
end
# Writes the NTP settings
#
# @param [Hash] params
# @option params [String] "server" The NTP server address, taken from the UI if empty
# @option params [Array<String>] "servers" A collection of NTP servers
# @option params [Boolean] "run_service" Whether service should be active and enable
# @option params [Boolean] "write_only" If only is needed to write the settings, (bnc#589296)
# @option params [Boolean] "ntpdate_only" ? TODO: rename to onetime
#
# @return [Symbol] :invalid_hostname, when a not valid ntp_server is given
# :ntpdate_failed, when the ntp sychronization fails
# :success, when settings (and sync if proceed) were performed successfully
def Write(params)
log.info "ntp client proposal Write with #{params.inspect}"
# clean params
params.compact!
ntp_server = params.fetch("server", "")
ntp_servers = params.fetch("servers", NtpClient.GetUsedNtpServers)
run_service = params.fetch("run_service", NtpClient.run_service)
# Get the ntp_server value from UI only if isn't present (probably wasn't given as parameter)
if ntp_server.strip.empty? && select_ntp_server
ntp_server = UI.QueryWidget(Id(:ntp_address), :Value) || ""
end
return :invalid_hostname if !ntp_server.empty? && !ValidateSingleServer(ntp_server)
add_or_install_required_package unless params["write_only"]
WriteNtpSettings(ntp_servers, ntp_server, run_service) unless params["ntpdate_only"]
return :success if params["write_only"]
# Only if network is running try to synchronize
# the ntp server.
if NetworkService.isNetworkRunning && !Service.Active(NtpClient.service_name)
ntp_servers = [ntp_server]
if !select_ntp_server
# Taking also the rest of the ntp servers, configured in the ntp client module.
ntp_servers += NtpClient.GetUsedNtpServers unless NtpClient.GetUsedNtpServers.nil?
end
ntp_servers.delete("")
ntp_servers.uniq
exit_code = 0
ntp_servers.each do |server|
Popup.ShowFeedback("", _("Synchronizing with NTP server...") + server)
exit_code = NtpClient.sync_once(server)
Popup.ClearFeedback
break if exit_code.zero?
end
return :ntpdate_failed unless exit_code.zero?
end
:success
end
# ui = UI::UserInput
def ui_handle(input)
redraw = false
case input
when :ntp_configure
rv = AskUser()
if rv == :invalid_hostname
handle_invalid_hostname(
UI.QueryWidget(Id(:ntp_address), :Value)
)
elsif rv == :next && !Stage.initial
# Updating UI for the changed ntp servers
ui_init(Id(:rp), false)
if Stage.initial
# show the 'save' status after configuration
UI.ChangeWidget(Id(:ntp_save), :Value, GetNTPEnabled())
end
end
when :ntp_now
rv = Write("ntpdate_only" => true)
if rv == :invalid_hostname
handle_invalid_hostname(UI.QueryWidget(Id(:ntp_address), :Value))
elsif rv == :success
redraw = true # update time widgets
else
Report.Error(_("Connection to selected NTP server failed."))
end
when :accept
# checking if chrony is available for installation.
if Stage.initial && UI.QueryWidget(Id(:ntp_save), :Value) == true &&
!Pkg.IsAvailable(NtpClientClass::REQUIRED_PACKAGE)
Report.Error(Builtins.sformat(
# TRANSLATORS: Popup message. %1 is the missing package name.
_("Cannot save NTP configuration because the package %1 is not available."),
NtpClientClass::REQUIRED_PACKAGE
))
UI.ChangeWidget(Id(:ntp_save), :Value, false)
redraw = true
end
end
redraw ? :redraw : nil
end
def ui_try_save
argmap = {}
Ops.set(argmap, "ntpdate_only", false)
Ops.set(argmap, "run_service", NtpClient.run_service)
if Stage.initial
Ops.set(argmap, "ntpdate_only", true) if UI.QueryWidget(Id(:ntp_save), :Value) == false
Ops.set(argmap, "run_service", true) if UI.QueryWidget(Id(:run_service), :Value)
end
rv = Write(argmap)
# The user has not had the possibility to change the ntp server.
# So we are done here.
return true unless select_ntp_server
server = Convert.to_string(UI.QueryWidget(Id(:ntp_address), :Value))
Builtins.y2milestone("ui_try_save argmap %1", argmap)
if rv == :invalid_hostname
handle_invalid_hostname(server)
return false # loop on
elsif rv == :ntpdate_failed
# Translators: yes-no popup,
# ntpdate is a command, %1 is the server address
if Popup.YesNo(
Builtins.sformat(
_(
"Test query to server '%1' failed.\n" \
"If server is not yet accessible or network is not configured\n" \
"click 'No' to ignore. Revisit NTP server configuration?"
),
server
)
)
return false # loop on
elsif !Ops.get_boolean(argmap, "ntpdate_only", false)
WriteNtpSettings(
[],
server,
Ops.get_boolean(argmap, "run_service", false)
) # may be the server is realy not accessable
end
end
# success, exit
true
end
private
def add_or_install_required_package
# In 1st stage, schedule packages for installation
if Stage.initial
Yast.import "Packages"
Packages.addAdditionalPackage(NtpClientClass::REQUIRED_PACKAGE)
# Otherwise, prompt user for confirming pkg installation
elsif !PackageSystem.CheckAndInstallPackages([NtpClientClass::REQUIRED_PACKAGE])
Report.Error(
Builtins.sformat(
_("Synchronization with NTP server is not possible\nwithout package %1 installed."),
NtpClientClass::REQUIRED_PACKAGE
)
)
end
end
# Public list of ntp servers Yast::Term items with the ntp address ID and
# label
#
# @return [Array<Yast::Term>] ntp address Item
def timezone_ntp_items
timezone_country = Timezone.GetCountryForTimezone(Timezone.timezone)
servers = NtpClient.country_ntp_servers(timezone_country)
# Select the first occurrence of pool.ntp.org as the default option (bnc#940881)
selected = servers.find { |s| s.hostname.end_with?("pool.ntp.org") }
servers.map do |server|
Item(Id(server.hostname), server.hostname, server.hostname == selected)
end
end
# List of dhcp ntp servers Yast::Term items with the ntp address ID and
# label
#
# @return [Array<Yast::Term>] ntp address table Item
def dhcp_ntp_items
NtpClient.dhcp_ntp_servers.map { |s| Item(Id(s.hostname), s.hostname) }
end
# List of ntp servers Yast::Term items with the ntp address ID and label
#
# @return [Array<Yast::Term>] ntp address table Item
def fallback_ntp_items
return @cached_fallback_ntp_items if @cached_fallback_ntp_items
@cached_fallback_ntp_items = dhcp_ntp_items
if !@cached_fallback_ntp_items.empty?
log.info("Proposing NTP server list provided by DHCP")
else
log.info("Proposing current timezone-based NTP server list")
@cached_fallback_ntp_items = timezone_ntp_items
end
@cached_fallback_ntp_items
end
# Checking if the user can select one ntp server from the list
# of proposed servers.
# It does not make sense if there are more than one ntp server
# defined.
#
# @return [Boolean] true if the user should select a server
def select_ntp_server
ret = NtpClient.GetUsedNtpServers.nil? || NtpClient.GetUsedNtpServers.empty?
# It could be that the user has defined an own ntp server in the ntp-client
# module which is not defined in the combo box. In that case we do not offer
# a selection. The user should go back to ntp-client to change it.
if NtpClient.GetUsedNtpServers.size == 1
ret = fallback_ntp_items.any? do |item|
item.params[1] == NtpClient.GetUsedNtpServers.first
end
end
ret
end
end
end
Yast::NtpClientProposalClient.new.main