diff --git a/ocaml/idl/datamodel_vm.ml b/ocaml/idl/datamodel_vm.ml index 5e4134afd0b..34ffda62d6b 100644 --- a/ocaml/idl/datamodel_vm.ml +++ b/ocaml/idl/datamodel_vm.ml @@ -2376,8 +2376,12 @@ let sysprep = [ (Ref _vm, "self", "The VM") ; (String, "unattend", "XML content passed to sysprep") + ; (Float, "timeout", "timeout in seconds for expected reboot") ] - ~doc:"Pass unattend.xml to Windows sysprep" ~allowed_roles:_R_VM_ADMIN () + ~doc: + "Pass unattend.xml to Windows sysprep and wait for the VM to shut down \ + as part of a reboot." + ~allowed_roles:_R_VM_ADMIN () let vm_uefi_mode = Enum diff --git a/ocaml/xapi-cli-server/cli_frontend.ml b/ocaml/xapi-cli-server/cli_frontend.ml index 255f2be789e..d6b553567e4 100644 --- a/ocaml/xapi-cli-server/cli_frontend.ml +++ b/ocaml/xapi-cli-server/cli_frontend.ml @@ -2767,7 +2767,7 @@ let rec cmdtable_data : (string * cmd_spec) list = ; ( "vm-sysprep" , { reqd= ["filename"] - ; optn= [] + ; optn= ["timeout"] ; help= "Pass and execute sysprep configuration file" ; implementation= With_fd Cli_operations.vm_sysprep ; flags= [Vm_selectors] diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index f51c50851d4..40c5b4a9de3 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -3590,6 +3590,15 @@ let vm_data_source_forget printer rpc session_id params = let vm_sysprep fd printer rpc session_id params = let filename = List.assoc "filename" params in + let timeout = + match List.assoc "timeout" params |> float_of_string with + | exception _ -> + 3.0 *. 60.0 (* default in the CLI, no default in the API *) + | s when s < 0.0 -> + 0.0 + | s -> + s + in let unattend = match get_client_file fd filename with | Some xml -> @@ -3602,8 +3611,9 @@ let vm_sysprep fd printer rpc session_id params = (do_vm_op printer rpc session_id (fun vm -> Client.VM.sysprep ~rpc ~session_id ~self:(vm.getref ()) ~unattend + ~timeout ) - params ["filename"] + params ["filename"; "timeout"] ) (* APIs to collect SR level RRDs *) diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 4c79f91cf5f..ca168ad4d08 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -3116,10 +3116,10 @@ functor Local.VM.remove_from_blocked_operations ~__context ~self ~key ; Xapi_vm_lifecycle.update_allowed_operations ~__context ~self - let sysprep ~__context ~self ~unattend = + let sysprep ~__context ~self ~unattend ~timeout = info "VM.sysprep: self = '%s'" (vm_uuid ~__context self) ; - let local_fn = Local.VM.sysprep ~self ~unattend in - let remote_fn = Client.VM.sysprep ~self ~unattend in + let local_fn = Local.VM.sysprep ~self ~unattend ~timeout in + let remote_fn = Client.VM.sysprep ~self ~unattend ~timeout in let policy = Helpers.Policy.fail_immediately in with_vm_operation ~__context ~self ~doc:"VM.sysprep" ~op:`sysprep ~policy (fun () -> diff --git a/ocaml/xapi/vm_sysprep.ml b/ocaml/xapi/vm_sysprep.ml index effdecabd83..bebffe47edc 100644 --- a/ocaml/xapi/vm_sysprep.ml +++ b/ocaml/xapi/vm_sysprep.ml @@ -207,27 +207,36 @@ let find_vdi ~__context ~label = (** notify the VM with [domid] to run sysprep and where to find the file. *) -let trigger ~domid ~uuid = +let trigger ~domid ~uuid ~timeout = let open Ezxenstore_core.Xenstore in let module Watch = Ezxenstore_core.Watch in let control = Printf.sprintf "/local/domain/%Ld/control/sysprep" domid in + let domain = Printf.sprintf "/local/domain/%Ld" domid in with_xs (fun xs -> xs.Xs.write (control // "filename") "D://unattend.xml" ; xs.Xs.write (control // "vdi-uuid") uuid ; xs.Xs.write (control // "action") "sysprep" ; debug "%s: notified domain %Ld" __FUNCTION__ domid ; try + (* wait for sysprep to start, then domain to dissapear *) Watch.( wait_for ~xs ~timeout:5.0 (value_to_become (control // "action") "running") ) ; - "running" - with Watch.Timeout _ -> xs.Xs.read (control // "action") + debug "%s: sysprep is runnung; waiting for sysprep to finish" + __FUNCTION__ ; + Watch.(wait_for ~xs ~timeout (key_to_disappear (control // "action"))) ; + debug "%s sysprep is finished" __FUNCTION__ ; + Watch.(wait_for ~xs ~timeout (key_to_disappear domain)) ; + true + with Watch.Timeout _ -> + debug "%s: sysprep timeout" __FUNCTION__ ; + false ) (* This function is executed on the host where [vm] is running *) -let sysprep ~__context ~vm ~unattend = - debug "%s" __FUNCTION__ ; +let sysprep ~__context ~vm ~unattend ~timeout = + debug "%s (timeout %f)" __FUNCTION__ timeout ; if not !Xapi_globs.vm_sysprep_enabled then fail API_not_enabled ; let vm_uuid = Db.VM.get_uuid ~__context ~self:vm in @@ -259,14 +268,13 @@ let sysprep ~__context ~vm ~unattend = call ~__context @@ fun rpc session_id -> Client.VBD.insert ~rpc ~session_id ~vdi ~vbd ; Thread.delay !Xapi_globs.vm_sysprep_wait ; - match trigger ~domid ~uuid with - | "running" -> + match trigger ~domid ~uuid ~timeout with + | true -> debug "%s: sysprep running, ejecting CD" __FUNCTION__ ; - Thread.delay 1.0 ; Client.VBD.eject ~rpc ~session_id ~vbd ; Sys.remove iso - | status -> - debug "%s: sysprep %S, ejecting CD" __FUNCTION__ status ; + | false -> + debug "%s: sysprep timeout, ejecting CD" __FUNCTION__ ; Client.VBD.eject ~rpc ~session_id ~vbd ; Sys.remove iso ; fail VM_sysprep_timeout diff --git a/ocaml/xapi/vm_sysprep.mli b/ocaml/xapi/vm_sysprep.mli index 80f1874d7e9..746c260badc 100644 --- a/ocaml/xapi/vm_sysprep.mli +++ b/ocaml/xapi/vm_sysprep.mli @@ -27,7 +27,12 @@ exception Sysprep of error val on_startup : __context:Context.t -> unit (** clean up on toolstart start up *) -val sysprep : __context:Context.t -> vm:API.ref_VM -> unattend:string -> unit +val sysprep : + __context:Context.t + -> vm:API.ref_VM + -> unattend:string + -> timeout:float + -> unit (** Execute sysprep on [vm] using script [unattend]. This requires driver support from the VM and is checked. [unattend:string] must not exceed 32kb. Raised [Failure] that must be handled, *) diff --git a/ocaml/xapi/xapi_vm.ml b/ocaml/xapi/xapi_vm.ml index f53f506e522..eeaa9b99c91 100644 --- a/ocaml/xapi/xapi_vm.ml +++ b/ocaml/xapi/xapi_vm.ml @@ -1702,10 +1702,15 @@ let get_secureboot_readiness ~__context ~self = ) ) -let sysprep ~__context ~self ~unattend = +let sysprep ~__context ~self ~unattend ~timeout = let uuid = Db.VM.get_uuid ~__context ~self in - debug "%s %S" __FUNCTION__ uuid ; - match Vm_sysprep.sysprep ~__context ~vm:self ~unattend with + debug "%s %S (timeout %f)" __FUNCTION__ uuid timeout ; + if timeout < 0.0 then + raise + Api_errors.( + Server_error (invalid_value, ["timeout"; string_of_float timeout]) + ) ; + match Vm_sysprep.sysprep ~__context ~vm:self ~unattend ~timeout with | () -> debug "%s %S success" __FUNCTION__ uuid ; () diff --git a/ocaml/xapi/xapi_vm.mli b/ocaml/xapi/xapi_vm.mli index 005b4cae4ae..2e861e8601b 100644 --- a/ocaml/xapi/xapi_vm.mli +++ b/ocaml/xapi/xapi_vm.mli @@ -451,4 +451,9 @@ val add_to_blocked_operations : val remove_from_blocked_operations : __context:Context.t -> self:API.ref_VM -> key:API.vm_operations -> unit -val sysprep : __context:Context.t -> self:API.ref_VM -> unattend:string -> unit +val sysprep : + __context:Context.t + -> self:API.ref_VM + -> unattend:string + -> timeout:float + -> unit