From 96efdcf54c887ae88d54332df12a5f5dd962fd0a Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 15 Jul 2022 11:25:45 +0100 Subject: [PATCH] output: Permit output modes to wait on the local NBD server Output.output_to_local_file is used by several output modes that write to local files or devices. It launches an instance of qemu-nbd or nbdkit connected to the local file. Previously we unconditionally added an On_exit handler to kill the NBD server. This is usually safe because nbdcopy --flush has guaranteed that the data was written through to permanent storage, and so killing the NBD server is just there to prevent orphaned processes. However for output to RHV (-o rhv) we actually need the NBD server to be cleaned up before we exit. See the analysis here: https://bugzilla.redhat.com/show_bug.cgi?id=1953286#c26 Allow an alternate strategy of waiting for the NBD server to exit during virt-v2v shutdown. We only need this in virt-v2v so implement it here instead of pushing it all the way into the On_exit module. Reviewed-by: Laszlo Ersek (cherry picked from commit e2a1a7b4dfb6a9e44260da10a7e7029c09753b5c) --- output/output.ml | 91 ++++++++++++++++++++++++++++------------------- output/output.mli | 17 +++++++-- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/output/output.ml b/output/output.ml index 496c32b6..8f83a324 100644 --- a/output/output.ml +++ b/output/output.ml @@ -69,7 +69,10 @@ let error_if_disk_count_gt dir n = if Sys.file_exists socket then error (f_"this output module doesn't support copying more than %d disks") n +type on_exit_kill = Kill | KillAndWait + let output_to_local_file ?(changeuid = fun f -> f ()) ?(compressed = false) + ?(on_exit_kill = Kill) output_alloc output_format filename size socket = (* Check nbdkit is installed and has the required plugin. *) if not (Nbdkit.is_installed ()) then @@ -94,46 +97,60 @@ let output_to_local_file ?(changeuid = fun f -> f ()) ?(compressed = false) fun () -> g#disk_create ?preallocation filename output_format size ); - match output_format with - | "raw" -> - let cmd = Nbdkit.create "file" in - Nbdkit.add_arg cmd "file" filename; - if Nbdkit.version nbdkit_config >= (1, 22, 0) then ( - let cmd = Nbdkit.add_arg cmd "cache" "none" in - cmd - ); - let _, pid = Nbdkit.run_unix socket cmd in + let pid = + match output_format with + | "raw" -> + let cmd = Nbdkit.create "file" in + Nbdkit.add_arg cmd "file" filename; + if Nbdkit.version nbdkit_config >= (1, 22, 0) then ( + let cmd = Nbdkit.add_arg cmd "cache" "none" in + cmd + ); + let _, pid = Nbdkit.run_unix socket cmd in + pid - (* --exit-with-parent should ensure nbdkit is cleaned - * up when we exit, but it's not supported everywhere. - *) - On_exit.kill pid + | "qcow2" -> + let cmd = + if compressed then ( + let qemu_quote str = String.replace str "," ",," in + let image_opts = [ "driver=compress"; + "file.driver=qcow2"; + "file.file.driver=file"; + "file.file.filename=" ^ qemu_quote filename ] in + let image_opts = String.concat "," image_opts in + let cmd = QemuNBD.create image_opts in + QemuNBD.set_image_opts cmd true; + cmd + ) + else (* not compressed *) ( + let cmd = QemuNBD.create filename in + QemuNBD.set_format cmd (Some "qcow2"); + cmd + ) in + QemuNBD.set_snapshot cmd false; + let _, pid = QemuNBD.run_unix socket cmd in + pid - | "qcow2" -> - let cmd = - if compressed then ( - let qemu_quote str = String.replace str "," ",," in - let image_opts = [ "driver=compress"; - "file.driver=qcow2"; - "file.file.driver=file"; - "file.file.filename=" ^ qemu_quote filename ] in - let image_opts = String.concat "," image_opts in - let cmd = QemuNBD.create image_opts in - QemuNBD.set_image_opts cmd true; - cmd - ) - else (* not compressed *) ( - let cmd = QemuNBD.create filename in - QemuNBD.set_format cmd (Some "qcow2"); - cmd - ) in - QemuNBD.set_snapshot cmd false; - let _, pid = QemuNBD.run_unix socket cmd in - On_exit.kill pid + | _ -> + error (f_"output mode only supports raw or qcow2 format (format: %s)") + output_format in + + match on_exit_kill with + | Kill -> + (* Kill the NBD server on exit. (For nbdkit we use --exit-with-parent + * but it's not supported everywhere). + *) + On_exit.kill pid - | _ -> - error (f_"output mode only supports raw or qcow2 format (format: %s)") - output_format + | KillAndWait -> + On_exit.f ( + fun () -> + kill pid Sys.sigterm; + (* Errors from the NBD server don't matter. On successful + * completion we've already committed the data to disk. + *) + ignore (waitpid [] pid) + ) let disk_path os name i = let outdisk = sprintf "%s/%s-sd%s" os name (drive_name i) in diff --git a/output/output.mli b/output/output.mli index c1f0f53d..c4486311 100644 --- a/output/output.mli +++ b/output/output.mli @@ -83,14 +83,27 @@ val error_if_disk_count_gt : string -> int -> unit "in[n]" in the v2v directory [dir]. If the socket exists, [error] is called. *) +type on_exit_kill = Kill | KillAndWait + val output_to_local_file : ?changeuid:((unit -> unit) -> unit) -> - ?compressed:bool -> + ?compressed:bool -> ?on_exit_kill:on_exit_kill -> Types.output_allocation -> string -> string -> int64 -> string -> unit (** When an output mode wants to create a local file with a particular format (only "raw" or "qcow2" allowed) then - this common function can be used. *) + this common function can be used. + + Optional parameter [?on_exit_kill] controls how the NBD server + is cleaned up. The default is {!Kill} which registers an + {!On_exit.kill} handler that kills (but does not wait for) + the server when virt-v2v exits. Most callers should use this. + + Setting [~on_exit_kill:KillAndWait] should be used if the NBD + server must fully exit before we continue with the rest of + virt-v2v shut down. This is only necessary if some other action + (such as unmounting a host filesystem or removing a host device) + depends on the NBD server releasing resources. *) val disk_path : string -> string -> int -> string (** For [-o disk|qemu], return the output disk name of the i'th disk,