diff --git a/ocaml/idl/datamodel_errors.ml b/ocaml/idl/datamodel_errors.ml index 62cf8d8452..52543b509a 100644 --- a/ocaml/idl/datamodel_errors.ml +++ b/ocaml/idl/datamodel_errors.ml @@ -2062,6 +2062,9 @@ let _ = error Api_errors.sysprep ["vm"; "message"] ~doc:"VM.sysprep error with details in the message" () ; + error Api_errors.invalid_ntp_config ["reason"] + ~doc:"The NTP configuration is invalid." () ; + message (fst Api_messages.ha_pool_overcommitted) ~doc: diff --git a/ocaml/idl/datamodel_host.ml b/ocaml/idl/datamodel_host.ml index 6808190557..541e80a45f 100644 --- a/ocaml/idl/datamodel_host.ml +++ b/ocaml/idl/datamodel_host.ml @@ -2544,6 +2544,58 @@ let set_max_cstate = ] ~allowed_roles:_R_POOL_OP () +let host_ntp_mode = + Enum + ( "host_ntp_mode" + , [ + ("ntp_mode_dhcp", "Using NTP servers assigned by DHCP to sync time") + ; ( "ntp_mode_custom" + , "Using custom NTP servers configured by user to sync time" + ) + ; ("ntp_mode_default", "Using default NTP servers to sync time") + ] + ) + +let set_ntp_mode = + call ~name:"set_ntp_mode" ~lifecycle:[] ~doc:"Set the NTP mode for the host" + ~params: + [ + (Ref _host, "self", "The host") + ; (host_ntp_mode, "value", "The NTP mode to set") + ] + ~allowed_roles:_R_POOL_OP () + +let set_ntp_custom_servers = + call ~name:"set_ntp_custom_servers" ~lifecycle:[] + ~doc:"Set the custom NTP servers for the host" + ~params: + [ + (Ref _host, "self", "The host") + ; (Set String, "value", "The set of custom NTP servers to configure") + ] + ~allowed_roles:_R_POOL_OP () + +let disable_ntp = + call ~name:"disable_ntp" ~lifecycle:[] ~doc:"Disable NTP on the host" + ~params:[(Ref _host, "self", "The host")] + ~allowed_roles:_R_POOL_OP () + +let enable_ntp = + call ~name:"enable_ntp" ~lifecycle:[] ~doc:"Enable NTP on the host" + ~params:[(Ref _host, "self", "The host")] + ~allowed_roles:_R_POOL_OP () + +let get_ntp_servers_status = + call ~name:"get_ntp_servers_status" ~lifecycle:[] + ~doc:"Get the NTP servers status on the host" + ~params:[(Ref _host, "self", "The host")] + ~result: + ( Map (String, String) + , "The map of NTP server to its status, status may be \ + synced/combined/uncombined/error/variable/unreachable/unknown" + ) + ~allowed_roles:_R_READ_ONLY () + (** Hosts *) let t = create_obj ~in_db:true @@ -2689,6 +2741,11 @@ let t = ; set_console_idle_timeout ; set_ssh_auto_mode ; set_max_cstate + ; set_ntp_mode + ; set_ntp_custom_servers + ; disable_ntp + ; enable_ntp + ; get_ntp_servers_status ] ~contents: ([ @@ -3156,6 +3213,16 @@ let t = ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:Bool ~default_value:(Some (VBool false)) "secure_boot" "Whether the host has booted in secure boot mode" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:host_ntp_mode + ~default_value:(Some (VEnum "ntp_mode_dhcp")) "ntp_mode" + "Indicates NTP servers are assigned by DHCP, or configured by \ + user, or the default servers" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set String) + ~default_value:(Some (VSet [])) "ntp_custom_servers" + "The set of NTP servers configured for the host" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:Bool + ~default_value:(Some (VBool false)) "ntp_enabled" + "Reflects whether NTP is enabled on the host" ] ) () diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index 2d32341c03..71a8a2c5cc 100644 --- a/ocaml/idl/schematest.ml +++ b/ocaml/idl/schematest.ml @@ -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 = "6fa9e85157804e2753dee31972b52b61" +let last_known_schema_hash = "34c69ac52c1e6c1d46bc35f610562a58" let current_schema_hash : string = let open Datamodel_types in diff --git a/ocaml/tests/common/test_common.ml b/ocaml/tests/common/test_common.ml index 1dce38d284..ee3bbeacbd 100644 --- a/ocaml/tests/common/test_common.ml +++ b/ocaml/tests/common/test_common.ml @@ -221,7 +221,8 @@ let make_host2 ~__context ?(ref = Ref.make ()) ?(uuid = make_uuid ()) ~pending_guidances_recommended:[] ~pending_guidances_full:[] ~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 ; + ~max_cstate:"" ~secure_boot:false ~ntp_mode:`ntp_mode_dhcp + ~ntp_custom_servers:[] ~ntp_enabled:false ; ref let make_pif ~__context ~network ~host ?(device = "eth0") diff --git a/ocaml/xapi-cli-server/cli_frontend.ml b/ocaml/xapi-cli-server/cli_frontend.ml index 8f8117fde0..41540e7505 100644 --- a/ocaml/xapi-cli-server/cli_frontend.ml +++ b/ocaml/xapi-cli-server/cli_frontend.ml @@ -1098,6 +1098,24 @@ let rec cmdtable_data : (string * cmd_spec) list = ; flags= [Neverforward] } ) + ; ( "host-enable-ntp" + , { + reqd= [] + ; optn= [] + ; help= "Enable ntp service on the host." + ; implementation= No_fd Cli_operations.host_enable_ntp + ; flags= [Host_selectors] + } + ) + ; ( "host-disable-ntp" + , { + reqd= [] + ; optn= [] + ; help= "Disable ntp service on the host." + ; implementation= No_fd Cli_operations.host_disable_ntp + ; flags= [Host_selectors] + } + ) ; ( "patch-upload" , { reqd= ["file-name"] diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index 3296affb65..1a76b1d6ae 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -7817,6 +7817,26 @@ let host_disable_ssh _printer rpc session_id params = params [] ) +let host_enable_ntp _printer rpc session_id params = + ignore + (do_host_op rpc session_id + (fun _ host -> + let host = host.getref () in + Client.Host.enable_ntp ~rpc ~session_id ~self:host + ) + params [] + ) + +let host_disable_ntp _printer rpc session_id params = + ignore + (do_host_op rpc session_id + (fun _ host -> + let host = host.getref () in + Client.Host.disable_ntp ~rpc ~session_id ~self:host + ) + params [] + ) + module SDN_controller = struct let introduce printer rpc session_id params = let port = diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index 6372ca6576..7d02c689c0 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -3402,6 +3402,30 @@ let host_record rpc session_id host = ; make_field ~name:"secure-boot" ~get:(fun () -> string_of_bool (x ()).API.host_secure_boot) () + ; make_field ~name:"ntp-mode" + ~get:(fun () -> + Record_util.host_ntp_mode_to_string (x ()).API.host_ntp_mode + ) + ~set:(fun value -> + Client.Host.set_ntp_mode ~rpc ~session_id ~self:host + ~value:(Record_util.host_ntp_mode_of_string value) + ) + () + ; make_field ~name:"ntp-custom-servers" + ~get:(fun () -> concat_with_comma (x ()).API.host_ntp_custom_servers) + ~get_set:(fun () -> (x ()).API.host_ntp_custom_servers) + ~set:(fun value -> + Client.Host.set_ntp_custom_servers ~rpc ~session_id ~self:host + ~value: + (String.split_on_char ',' value + |> List.map String.trim + |> List.filter (( <> ) "") + ) + ) + () + ; make_field ~name:"ntp_enabled" + ~get:(fun () -> string_of_bool (x ()).API.host_ntp_enabled) + () ] } diff --git a/ocaml/xapi-consts/api_errors.ml b/ocaml/xapi-consts/api_errors.ml index 2a1b9b58b7..14c5806135 100644 --- a/ocaml/xapi-consts/api_errors.ml +++ b/ocaml/xapi-consts/api_errors.ml @@ -1438,3 +1438,5 @@ let tls_verification_not_enabled_in_pool = add_error "TLS_VERIFICATION_NOT_ENABLED_IN_POOL" let sysprep = add_error "SYSPREP" + +let invalid_ntp_config = add_error "INVALID_NTP_CONFIG" diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index b1c194bd29..c0756f638d 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -4122,6 +4122,41 @@ functor let local_fn = Local.Host.set_max_cstate ~self ~value in let remote_fn = Client.Host.set_max_cstate ~self ~value in do_op_on ~local_fn ~__context ~host:self ~remote_fn + + let set_ntp_mode ~__context ~self ~value = + info "Host.set_ntp_mode: host='%s' value='%s'" + (host_uuid ~__context self) + (Record_util.host_ntp_mode_to_string value) ; + let local_fn = Local.Host.set_ntp_mode ~self ~value in + let remote_fn = Client.Host.set_ntp_mode ~self ~value in + do_op_on ~local_fn ~__context ~host:self ~remote_fn + + let set_ntp_custom_servers ~__context ~self ~value = + info "Host.set_ntp_custom_servers: host='%s' value='%s'" + (host_uuid ~__context self) + ("[" ^ String.concat ", " value ^ "]") ; + let local_fn = Local.Host.set_ntp_custom_servers ~self ~value in + let remote_fn = Client.Host.set_ntp_custom_servers ~self ~value in + do_op_on ~local_fn ~__context ~host:self ~remote_fn + + let enable_ntp ~__context ~self = + info "Host.enable_ntp: host='%s'" (host_uuid ~__context self) ; + let local_fn = Local.Host.enable_ntp ~self in + let remote_fn = Client.Host.enable_ntp ~self in + do_op_on ~local_fn ~__context ~host:self ~remote_fn + + let disable_ntp ~__context ~self = + info "Host.disable_ntp: host='%s'" (host_uuid ~__context self) ; + let local_fn = Local.Host.disable_ntp ~self in + let remote_fn = Client.Host.disable_ntp ~self in + do_op_on ~local_fn ~__context ~host:self ~remote_fn + + let get_ntp_servers_status ~__context ~self = + info "Host.get_ntp_servers_status: host = '%s'" + (host_uuid ~__context self) ; + 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 end module Host_crashdump = struct diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml index 45217cfcfb..61f5ce9a79 100644 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@ -795,6 +795,16 @@ let stunnel_conf = ref "/etc/stunnel/xapi.conf" let udhcpd_conf = ref (Filename.concat "/etc/xensource" "udhcpd.conf") +let ntp_service = ref "chronyd" + +let ntp_conf = ref (Filename.concat "/etc" "chrony.conf") + +let ntp_dhcp_script = ref (Filename.concat "/etc/dhcp/dhclient.d" "chrony.sh") + +let ntp_dhcp_dir = ref "/run/chrony-dhcp" + +let ntp_client_path = ref "/usr/bin/chronyc" + let udhcpd_skel = ref (Filename.concat "/etc/xensource" "udhcpd.skel") let udhcpd_leases_db = ref "/var/lib/xcp/dhcp-leases.db" @@ -1387,6 +1397,8 @@ let nvidia_gpumon_detach = ref false let failed_login_alert_freq = ref 3600 +let default_ntp_servers = ref [] + let other_options = [ gen_list_option "sm-plugins" @@ -1816,6 +1828,36 @@ let other_options = , (fun () -> string_of_int !max_span_depth) , "The maximum depth to which spans are recorded in a trace in Tracing" ) + ; ( "ntp-service" + , Arg.Set_string ntp_service + , (fun () -> !ntp_service) + , "Name of the NTP service to manage" + ) + ; ( "ntp-config-path" + , Arg.Set_string ntp_conf + , (fun () -> !ntp_conf) + , "Path to the ntp configuration file" + ) + ; ( "ntp-dhcp-script-path" + , Arg.Set_string ntp_dhcp_script + , (fun () -> !ntp_dhcp_script) + , "Path to the ntp dhcp script file" + ) + ; ( "ntp-dhcp-dir" + , Arg.Set_string ntp_dhcp_dir + , (fun () -> !ntp_dhcp_dir) + , "Path to the ntp dhcp directory" + ) + ; ( "ntp-client-path" + , Arg.Set_string ntp_client_path + , (fun () -> !ntp_client_path) + , "Path to the ntp client binary" + ) + ; gen_list_option "default-ntp-servers" + "space-separated list of default NTP servers" + (fun s -> s) + (fun s -> s) + default_ntp_servers ] (* The options can be set with the variable xapiflags in /etc/sysconfig/xapi. diff --git a/ocaml/xapi/xapi_host.ml b/ocaml/xapi/xapi_host.ml index 2942597840..357867b3b7 100644 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@ -1094,7 +1094,8 @@ let create ~__context ~uuid ~name_label ~name_description:_ ~hostname ~address ~recommended_guidances:[] ~latest_synced_updates_applied:`unknown ~pending_guidances_recommended:[] ~pending_guidances_full:[] ~ssh_enabled ~ssh_enabled_timeout ~ssh_expiry ~console_idle_timeout ~ssh_auto_mode - ~max_cstate:"" ~secure_boot ; + ~max_cstate:"" ~secure_boot ~ntp_mode:`ntp_mode_dhcp ~ntp_custom_servers:[] + ~ntp_enabled:false ; (* 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 ; @@ -3374,3 +3375,75 @@ let sync_max_cstate ~__context ~host = let value = Xapi_host_max_cstate.to_string (max_cstate, max_sub_cstate) in Db.Host.set_max_cstate ~__context ~self:host ~value with e -> error "Failed to sync max_cstate: %s" (Printexc.to_string e) + +let set_ntp_mode ~__context ~self ~value = + let current_mode = Db.Host.get_ntp_mode ~__context ~self in + let ntp_enabled = Db.Host.get_ntp_enabled ~__context ~self in + if current_mode <> value then ( + let open Xapi_host_ntp in + let ensure_custom_servers_exist servers = + if servers = [] then + raise + Api_errors.( + Server_error + ( invalid_ntp_config + , ["Can't set ntp_mode_custom when ntp_custom_servers is empty"] + ) + ) + in + let default_servers = !Xapi_globs.default_ntp_servers in + ( match (current_mode, value) with + | `ntp_mode_dhcp, `ntp_mode_custom -> + let custom_servers = Db.Host.get_ntp_custom_servers ~__context ~self in + ensure_custom_servers_exist custom_servers ; + remove_dhcp_ntp_servers () ; + set_servers_in_conf custom_servers + | `ntp_mode_default, `ntp_mode_custom -> + let custom_servers = Db.Host.get_ntp_custom_servers ~__context ~self in + ensure_custom_servers_exist custom_servers ; + set_servers_in_conf custom_servers + | _, `ntp_mode_dhcp -> + clear_servers_in_conf () ; add_dhcp_ntp_servers () + | `ntp_mode_dhcp, `ntp_mode_default -> + remove_dhcp_ntp_servers () ; + set_servers_in_conf default_servers + | `ntp_mode_custom, `ntp_mode_default -> + set_servers_in_conf default_servers + | _, _ -> + () + ) ; + if ntp_enabled then Xapi_host_ntp.restart_ntp_service () ; + Db.Host.set_ntp_mode ~__context ~self ~value + ) + +let set_ntp_custom_servers ~__context ~self ~value = + let current_mode = Db.Host.get_ntp_mode ~__context ~self in + match (current_mode, value) with + | `ntp_mode_custom, [] -> + raise + Api_errors.( + Server_error + ( invalid_ntp_config + , ["Can't set ntp_custom_servers empty when ntp_mode is custom"] + ) + ) + | `ntp_mode_custom, servers -> + Xapi_host_ntp.set_servers_in_conf servers ; + Xapi_host_ntp.restart_ntp_service () ; + Db.Host.set_ntp_custom_servers ~__context ~self ~value + | _ -> + Db.Host.set_ntp_custom_servers ~__context ~self ~value + +let enable_ntp ~__context ~self = + Xapi_host_ntp.enable_ntp_service () ; + Db.Host.set_ntp_enabled ~__context ~self ~value:true + +let disable_ntp ~__context ~self = + Xapi_host_ntp.disable_ntp_service () ; + Db.Host.set_ntp_enabled ~__context ~self ~value:false + +let get_ntp_servers_status ~__context ~self:_ = + if Xapi_host_ntp.is_ntp_service_active () then + Xapi_host_ntp.get_servers_status () + else + [] diff --git a/ocaml/xapi/xapi_host.mli b/ocaml/xapi/xapi_host.mli index 0e22e90587..7d1d975544 100644 --- a/ocaml/xapi/xapi_host.mli +++ b/ocaml/xapi/xapi_host.mli @@ -595,3 +595,16 @@ val set_max_cstate : __context:Context.t -> self:API.ref_host -> value:string -> unit val sync_max_cstate : __context:Context.t -> host:API.ref_host -> unit + +val set_ntp_mode : + __context:Context.t -> self:API.ref_host -> value:API.host_ntp_mode -> unit + +val set_ntp_custom_servers : + __context:Context.t -> self:API.ref_host -> value:string list -> unit + +val enable_ntp : __context:Context.t -> self:API.ref_host -> unit + +val disable_ntp : __context:Context.t -> self:API.ref_host -> unit + +val get_ntp_servers_status : + __context:Context.t -> self:API.ref_host -> (string * string) list diff --git a/ocaml/xapi/xapi_host_ntp.ml b/ocaml/xapi/xapi_host_ntp.ml new file mode 100644 index 0000000000..7e197774f9 --- /dev/null +++ b/ocaml/xapi/xapi_host_ntp.ml @@ -0,0 +1,142 @@ +(* + * Copyright (c) Cloud Software Group, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *) + +let ( // ) = Filename.concat + +let ntp_dhcp_server_path interface = + Printf.sprintf "%s/%s.sources" !Xapi_globs.ntp_dhcp_dir interface + +let add_exec_permission fname = + let st = Unix.stat fname in + let perm = st.Unix.st_perm lor 0o111 in + Unix.chmod fname perm + +let remove_exec_permission fname = + let st = Unix.stat fname in + let perm = st.Unix.st_perm land lnot 0o111 in + Unix.chmod fname perm + +let get_dhclient_interfaces () = + let extract_interface_name filename = + try Scanf.sscanf filename "dhclient-%[^.].leases" (fun x -> Some x) + with _ -> None + in + Sys.readdir "/var/lib/xcp" + |> Array.to_list + |> List.filter_map extract_interface_name + +let get_dhcp_ntp_server interface = + let fname = Printf.sprintf "/var/lib/xcp/dhclient-%s.leases" interface in + Xapi_stdext_unix.Unixext.read_lines ~path:fname + |> List.rev (* search from the last lease entry *) + |> List.find_map (fun line -> + let line = String.trim line in + try Scanf.sscanf line "option ntp-servers %[^;];" (fun x -> Some x) + with _ -> None + ) + +let add_dhcp_ntp_servers () = + get_dhclient_interfaces () + |> List.iter (fun interface -> + match get_dhcp_ntp_server interface with + | Some server -> + let line = Printf.sprintf "server %s iburst prefer\n" server in + Xapi_stdext_unix.Unixext.write_string_to_file + (ntp_dhcp_server_path interface) + line + | None -> + () + ) ; + add_exec_permission !Xapi_globs.ntp_dhcp_script + +let remove_dhcp_ntp_servers () = + remove_exec_permission !Xapi_globs.ntp_dhcp_script ; + Sys.readdir !Xapi_globs.ntp_dhcp_dir + |> Array.iter (fun fname -> + if String.ends_with ~suffix:".sources" fname then + Sys.remove (!Xapi_globs.ntp_dhcp_dir // fname) + ) + +let restart_ntp_service () = + Xapi_systemctl.restart ~wait_until_success:false !Xapi_globs.ntp_service + +let enable_ntp_service () = + Xapi_systemctl.enable ~wait_until_success:false !Xapi_globs.ntp_service ; + Xapi_systemctl.start ~wait_until_success:false !Xapi_globs.ntp_service + +let disable_ntp_service () = + Xapi_systemctl.stop ~wait_until_success:false !Xapi_globs.ntp_service ; + Xapi_systemctl.disable ~wait_until_success:false !Xapi_globs.ntp_service + +let is_ntp_service_active () = + Fe_systemctl.is_active ~service:!Xapi_globs.ntp_service + +let parse_ntp_conf () = + try + Xapi_stdext_unix.Unixext.read_lines ~path:!Xapi_globs.ntp_conf + |> List.partition (String.starts_with ~prefix:"server ") + with Sys_error _ -> ([], []) + +let write_ntp_conf other servers = + let lines = List.map (fun s -> Printf.sprintf "server %s iburst" s) servers in + let all_lines = other @ lines in + let write_lines fname lines = + Xapi_stdext_unix.Unixext.write_string_to_file fname + (String.concat "\n" lines ^ "\n") + in + write_lines !Xapi_globs.ntp_conf all_lines + +let set_servers_in_conf servers = + let _, other = parse_ntp_conf () in + write_ntp_conf other servers + +let clear_servers_in_conf () = set_servers_in_conf [] + +(* chronyc -c sources output the ntp servers status in csv format. Example: + ^,-,10.62.16.11,5,6,377,54,0.000496471,0.000496471,0.071449950 + ^,?,17.253.14.123,0,8,0,4294967295,0.000000000,0.000000000,0.000000000 + ^,?,104.40.149.189,0,8,0,4294967295,0.000000000,0.000000000,0.000000000 + ^,*,10.71.56.11,5,6,377,57,-0.000006851,-0.000118707,0.082518920 + Source mode: '^' = server, '=' = peer, '#' = local clock. + Source state: '*' = current synced, '+' = combined, '-' = not combined, + '?' = unreachable, 'x' = time may be in error, '~' = time too variable +*) +let get_servers_status () = + let convert = function + | "*" -> + "synced" + | "+" -> + "combined" + | "-" -> + "uncombined" + | "x" -> + "error" + | "~" -> + "variable" + | "?" -> + "unreachable" + | _ -> + "unknown" + in + let r = Helpers.call_script !Xapi_globs.ntp_client_path ["-c"; "sources"] in + let lines = String.split_on_char '\n' r in + List.filter_map + (fun line -> + line |> String.trim |> String.split_on_char ',' |> function + | "^" :: status :: server :: _ -> + Some (server, convert status) + | _ -> + None + ) + lines diff --git a/ocaml/xapi/xapi_host_ntp.mli b/ocaml/xapi/xapi_host_ntp.mli new file mode 100644 index 0000000000..8f9fbf8041 --- /dev/null +++ b/ocaml/xapi/xapi_host_ntp.mli @@ -0,0 +1,31 @@ +(* + * Copyright (c) Cloud Software Group, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *) + +val add_dhcp_ntp_servers : unit -> unit + +val remove_dhcp_ntp_servers : unit -> unit + +val set_servers_in_conf : string list -> unit + +val clear_servers_in_conf : unit -> unit + +val restart_ntp_service : unit -> unit + +val enable_ntp_service : unit -> unit + +val disable_ntp_service : unit -> unit + +val is_ntp_service_active : unit -> bool + +val get_servers_status : unit -> (string * string) list