Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions ocaml/idl/datamodel_host.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2627,6 +2627,25 @@ let get_ntp_servers_status =
)
~allowed_roles:_R_READ_ONLY ()

let set_timezone =
call ~name:"set_timezone" ~lifecycle:[] ~doc:"Set the host's timezone."
~params:
[
(Ref _host, "self", "The host")
; ( String
, "value"
, "The time zone identifier as defined in the IANA Time Zone Database"
)
]
~allowed_roles:_R_POOL_OP ()

let list_timezones =
call ~name:"list_timezones" ~lifecycle:[]
~doc:"List all available timezones on the host."
~params:[(Ref _host, "self", "The host")]
~result:(Set String, "The set of available timezones on the host")
~allowed_roles:_R_READ_ONLY ()

(** Hosts *)
let t =
create_obj ~in_db:true
Expand Down Expand Up @@ -2779,6 +2798,8 @@ let t =
; disable_ntp
; enable_ntp
; get_ntp_servers_status
; set_timezone
; list_timezones
]
~contents:
([
Expand Down Expand Up @@ -3256,6 +3277,9 @@ let t =
; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:Bool
~default_value:(Some (VBool false)) "ntp_enabled"
"Reflects whether NTP is enabled on the host"
; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:String
~default_value:(Some (VString "UTC")) "timezone"
"The time zone identifier as defined in the IANA Time Zone Database"
]
)
()
2 changes: 1 addition & 1 deletion ocaml/idl/schematest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
(* BEWARE: if this changes, check that schema has been bumped accordingly in
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)

let last_known_schema_hash = "34c69ac52c1e6c1d46bc35f610562a58"
let last_known_schema_hash = "c72edb27945bceb074de3fa54381ddd4"

let current_schema_hash : string =
let open Datamodel_types in
Expand Down
2 changes: 1 addition & 1 deletion ocaml/tests/common/test_common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ let make_host2 ~__context ?(ref = Ref.make ()) ?(uuid = make_uuid ())
~last_update_hash:"" ~ssh_enabled:true ~ssh_enabled_timeout:0L
~ssh_expiry:Date.epoch ~console_idle_timeout:0L ~ssh_auto_mode:false
~max_cstate:"" ~secure_boot:false ~ntp_mode:`ntp_mode_dhcp
~ntp_custom_servers:[] ~ntp_enabled:false ;
~ntp_custom_servers:[] ~ntp_enabled:false ~timezone:"UTC" ;
ref

let make_pif ~__context ~network ~host ?(device = "eth0")
Expand Down
6 changes: 6 additions & 0 deletions ocaml/xapi-cli-server/records.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3426,6 +3426,12 @@ let host_record rpc session_id host =
; make_field ~name:"ntp_enabled"
~get:(fun () -> string_of_bool (x ()).API.host_ntp_enabled)
()
; make_field ~name:"timezone"
~get:(fun () -> (x ()).API.host_timezone)
~set:(fun value ->
Client.Host.set_timezone ~rpc ~session_id ~self:host ~value
)
()
]
}

Expand Down
12 changes: 12 additions & 0 deletions ocaml/xapi/dbsync_slave.ml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,18 @@ let update_env __context sync_keys =
switched_sync Xapi_globs.sync_ntp_config (fun () ->
Xapi_host.sync_ntp_config ~__context ~host:localhost
) ;
switched_sync Xapi_globs.sync_timezone (fun () ->
let timezone =
try
let linkpath = Unix.realpath "/etc/localtime" in
Scanf.sscanf linkpath "/usr/share/zoneinfo/%s" (fun tz -> tz)
with e ->
warn "%s error when sync timezone: %s" __FUNCTION__
(Printexc.to_string e) ;
"UTC"
in
Db.Host.set_timezone ~__context ~self:localhost ~value:timezone
) ;

switched_sync Xapi_globs.sync_secure_boot (fun () ->
let result =
Expand Down
14 changes: 14 additions & 0 deletions ocaml/xapi/message_forwarding.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4170,6 +4170,20 @@ functor
let local_fn = Local.Host.get_ntp_servers_status ~self in
let remote_fn = Client.Host.get_ntp_servers_status ~self in
do_op_on ~local_fn ~__context ~host:self ~remote_fn

let set_timezone ~__context ~self ~value =
info "Host.set_timezone: host = '%s'; value = '%s'"
(host_uuid ~__context self)
value ;
let local_fn = Local.Host.set_timezone ~self ~value in
let remote_fn = Client.Host.set_timezone ~self ~value in
do_op_on ~local_fn ~__context ~host:self ~remote_fn

let list_timezones ~__context ~self =
info "Host.list_timezones: host = '%s'" (host_uuid ~__context self) ;
let local_fn = Local.Host.list_timezones ~self in
let remote_fn = Client.Host.list_timezones ~self in
do_op_on ~local_fn ~__context ~host:self ~remote_fn
end

module Host_crashdump = struct
Expand Down
9 changes: 9 additions & 0 deletions ocaml/xapi/xapi_globs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ let sync_max_cstate = "sync_max_cstate"

let sync_ntp_config = "sync_ntp_config"

let sync_timezone = "sync_timezone"

let sync_secure_boot = "sync_secure_boot"

let sync_pci_devices = "sync_pci_devices"
Expand Down Expand Up @@ -812,6 +814,8 @@ let ntp_dhcp_dir = ref "/run/chrony-dhcp"

let ntp_client_path = ref "/usr/bin/chronyc"

let timedatectl = ref "/usr/bin/timedatectl"

let udhcpd_skel = ref (Filename.concat "/etc/xensource" "udhcpd.skel")

let udhcpd_leases_db = ref "/var/lib/xcp/dhcp-leases.db"
Expand Down Expand Up @@ -1907,6 +1911,11 @@ let other_options =
, (fun () -> !ntp_client_path)
, "Path to the ntp client binary"
)
; ( "timedatectl"
, Arg.Set_string timedatectl
, (fun () -> !timedatectl)
, "Path to the timedatectl executable"
)
; gen_list_option "legacy-default-ntp-servers"
"space-separated list of legacy default NTP servers"
(fun s -> s)
Expand Down
22 changes: 21 additions & 1 deletion ocaml/xapi/xapi_host.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ let create ~__context ~uuid ~name_label ~name_description:_ ~hostname ~address
~pending_guidances_recommended:[] ~pending_guidances_full:[] ~ssh_enabled
~ssh_enabled_timeout ~ssh_expiry ~console_idle_timeout ~ssh_auto_mode
~max_cstate:"" ~secure_boot ~ntp_mode:`ntp_mode_dhcp ~ntp_custom_servers:[]
~ntp_enabled:false ;
~ntp_enabled:false ~timezone:"UTC" ;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Not related with this PR)
Just to confirm: the timezone should be reserved when a host joins a pool.

Copy link
Contributor Author

@changlei-li changlei-li Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pool join case, the new-joined host will keep its own timezone. For example, coordinator is 'UTC', host A is 'Asia/Shanghai' before join. Then hostA joins the pool and reboot, the hostA dbsync will set its timezone field to 'Asia/Shanghai' according to the real setting on host.
The test result

# xe host-list params=uuid,timezone
uuid ( RO)        : f910aa46-0e52-4a86-8bdf-634ea894c32a
    timezone ( RW): UTC


uuid ( RO)        : ff2efb83-326a-4842-8b98-8e2efef32d3c
    timezone ( RW): Asia/Shanghai

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pool join, the host will first be added to the pool database using this function, with UTC as timezone. Then xapi will restart and dbsync will update it to the real timezone.

(* If the host we're creating is us, make sure its set to live *)
Db.Host_metrics.set_last_updated ~__context ~self:metrics ~value:(Date.now ()) ;
Db.Host_metrics.set_live ~__context ~self:metrics ~value:host_is_us ;
Expand Down Expand Up @@ -3561,3 +3561,23 @@ let get_ntp_servers_status ~__context ~self:_ =
Xapi_host_ntp.get_servers_status ()
else
[]

let set_timezone ~__context ~self ~value =
try
let _ =
Helpers.call_script !Xapi_globs.timedatectl ["set-timezone"; value]
in
Db.Host.set_timezone ~__context ~self ~value
with
| Forkhelpers.Spawn_internal_error (stderr, _, _)
when String.starts_with ~prefix:"Failed to set time zone: Invalid" stderr ->
raise
(Api_errors.Server_error (Api_errors.invalid_value, ["timezone"; value]))
| e ->
Helpers.internal_error "%s" (ExnHelper.string_of_exn e)

let list_timezones ~__context ~self:_ =
try
Helpers.call_script !Xapi_globs.timedatectl ["list-timezones"]
|> Astring.String.cuts ~empty:false ~sep:"\n"
with e -> Helpers.internal_error "%s" (ExnHelper.string_of_exn e)
5 changes: 5 additions & 0 deletions ocaml/xapi/xapi_host.mli
Original file line number Diff line number Diff line change
Expand Up @@ -627,3 +627,8 @@ val sync_ntp_config : __context:Context.t -> host:API.ref_host -> unit

val get_ntp_servers_status :
__context:Context.t -> self:API.ref_host -> (string * string) list

val set_timezone :
__context:Context.t -> self:API.ref_host -> value:string -> unit

val list_timezones : __context:Context.t -> self:API.ref_host -> string list
Loading