mrc0mmand / rpms / libguestfs

Forked from rpms/libguestfs 3 years ago
Clone

Blame SOURCES/0012-v2v-add-o-json-output-mode.patch

da373f
From 87df9bcb99bdb60d5cedf52e155361826e700816 Mon Sep 17 00:00:00 2001
3efd08
From: Pino Toscano <ptoscano@redhat.com>
3efd08
Date: Mon, 25 Feb 2019 13:14:43 +0100
3efd08
Subject: [PATCH] v2v: add -o json output mode
3efd08
3efd08
Add a new output mode to virt-v2v: similar to -o local, the written
3efd08
metadata is a JSON file with the majority of the data that virt-v2v
3efd08
knowns about (or collects) during the conversion.
3efd08
3efd08
This is meant to be used only when no existing output mode is usable,
3efd08
and a guest needs to be converted to run on KVM anyway.  The user of
3efd08
this mode is supposed to use all the data in the JSON, as they contain
3efd08
important details on how even run the guest (e.g. w.r.t. firmware,
3efd08
drivers of disks/NICs, etc).
3efd08
3efd08
(cherry picked from commit f190e08d85556dac293ef15bfeee38e54471570f)
3efd08
---
3efd08
 v2v/Makefile.am               |   4 +
3efd08
 v2v/cmdline.ml                |  29 +++
3efd08
 v2v/create_json.ml            | 348 ++++++++++++++++++++++++++++++++++
3efd08
 v2v/create_json.mli           |  29 +++
3efd08
 v2v/output_json.ml            | 116 ++++++++++++
3efd08
 v2v/output_json.mli           |  31 +++
3efd08
 v2v/virt-v2v-output-local.pod |  55 ++++++
3efd08
 v2v/virt-v2v.pod              |  15 +-
3efd08
 8 files changed, 625 insertions(+), 2 deletions(-)
3efd08
 create mode 100644 v2v/create_json.ml
3efd08
 create mode 100644 v2v/create_json.mli
3efd08
 create mode 100644 v2v/output_json.ml
3efd08
 create mode 100644 v2v/output_json.mli
3efd08
3efd08
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
3efd08
index f196be81d..53c137fc6 100644
3efd08
--- a/v2v/Makefile.am
3efd08
+++ b/v2v/Makefile.am
3efd08
@@ -52,6 +52,7 @@ SOURCES_MLI = \
3efd08
 	config.mli \
3efd08
 	convert_linux.mli \
3efd08
 	convert_windows.mli \
3efd08
+	create_json.mli \
3efd08
 	create_libvirt_xml.mli \
3efd08
 	create_ovf.mli \
3efd08
 	DOM.mli \
3efd08
@@ -75,6 +76,7 @@ SOURCES_MLI = \
3efd08
 	networks.mli \
3efd08
 	openstack_image_properties.mli \
3efd08
 	output_glance.mli \
3efd08
+	output_json.mli \
3efd08
 	output_libvirt.mli \
3efd08
 	output_local.mli \
3efd08
 	output_null.mli \
3efd08
@@ -117,6 +119,7 @@ SOURCES_ML = \
3efd08
 	parse_ovf_from_ova.ml \
3efd08
 	parse_ova.ml \
3efd08
 	create_ovf.ml \
3efd08
+	create_json.ml \
3efd08
 	linux.ml \
3efd08
 	windows.ml \
3efd08
 	windows_virtio.ml \
3efd08
@@ -141,6 +144,7 @@ SOURCES_ML = \
3efd08
 	convert_windows.ml \
3efd08
 	output_null.ml \
3efd08
 	output_glance.ml \
3efd08
+	output_json.ml \
3efd08
 	output_libvirt.ml \
3efd08
 	output_local.ml \
3efd08
 	output_qemu.ml \
3efd08
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
3efd08
index 46f6910d0..4d390f249 100644
3efd08
--- a/v2v/cmdline.ml
3efd08
+++ b/v2v/cmdline.ml
3efd08
@@ -138,6 +138,7 @@ let parse_cmdline () =
3efd08
     | "glance" -> output_mode := `Glance
3efd08
     | "libvirt" -> output_mode := `Libvirt
3efd08
     | "disk" | "local" -> output_mode := `Local
3efd08
+    | "json" -> output_mode := `JSON
3efd08
     | "null" -> output_mode := `Null
3efd08
     | "openstack" | "osp" | "rhosp" -> output_mode := `Openstack
3efd08
     | "ovirt" | "rhv" | "rhev" -> output_mode := `RHV
3efd08
@@ -413,6 +414,17 @@ read the man page virt-v2v(1).
3efd08
     | `RHV -> no_options (); `RHV
3efd08
     | `QEmu -> no_options (); `QEmu
3efd08
 
3efd08
+    | `JSON ->
3efd08
+       if is_query then (
3efd08
+         Output_json.print_output_options ();
3efd08
+         exit 0
3efd08
+       )
3efd08
+       else (
3efd08
+         let json_options =
3efd08
+           Output_json.parse_output_options output_options in
3efd08
+         `JSON json_options
3efd08
+       )
3efd08
+
3efd08
     | `Openstack ->
3efd08
        if is_query then (
3efd08
          Output_openstack.print_output_options ();
3efd08
@@ -546,6 +558,23 @@ read the man page virt-v2v(1).
3efd08
       Output_libvirt.output_libvirt output_conn output_storage,
3efd08
       output_format, output_alloc
3efd08
 
3efd08
+    | `JSON json_options ->
3efd08
+      if output_password <> None then
3efd08
+        error_option_cannot_be_used_in_output_mode "json" "-op";
3efd08
+      if output_conn <> None then
3efd08
+        error_option_cannot_be_used_in_output_mode "json" "-oc";
3efd08
+      let os =
3efd08
+        match output_storage with
3efd08
+        | None ->
3efd08
+           error (f_"-o json: output directory was not specified, use '-os /dir'")
3efd08
+        | Some d when not (is_directory d) ->
3efd08
+           error (f_"-os %s: output directory does not exist or is not a directory") d
3efd08
+        | Some d -> d in
3efd08
+      if qemu_boot then
3efd08
+        error_option_cannot_be_used_in_output_mode "json" "--qemu-boot";
3efd08
+      Output_json.output_json os json_options,
3efd08
+      output_format, output_alloc
3efd08
+
3efd08
     | `Local ->
3efd08
       if output_password <> None then
3efd08
         error_option_cannot_be_used_in_output_mode "local" "-op";
3efd08
diff --git a/v2v/create_json.ml b/v2v/create_json.ml
3efd08
new file mode 100644
3efd08
index 000000000..fdf7b12f5
3efd08
--- /dev/null
3efd08
+++ b/v2v/create_json.ml
3efd08
@@ -0,0 +1,348 @@
3efd08
+(* virt-v2v
3efd08
+ * Copyright (C) 2019 Red Hat Inc.
3efd08
+ *
3efd08
+ * This program is free software; you can redistribute it and/or modify
3efd08
+ * it under the terms of the GNU General Public License as published by
3efd08
+ * the Free Software Foundation; either version 2 of the License, or
3efd08
+ * (at your option) any later version.
3efd08
+ *
3efd08
+ * This program is distributed in the hope that it will be useful,
3efd08
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3efd08
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3efd08
+ * GNU General Public License for more details.
3efd08
+ *
3efd08
+ * You should have received a copy of the GNU General Public License along
3efd08
+ * with this program; if not, write to the Free Software Foundation, Inc.,
3efd08
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3efd08
+ *)
3efd08
+
3efd08
+open Std_utils
3efd08
+open C_utils
3efd08
+open Tools_utils
3efd08
+
3efd08
+open Types
3efd08
+open Utils
3efd08
+
3efd08
+module G = Guestfs
3efd08
+
3efd08
+let json_list_of_string_list =
3efd08
+  List.map (fun x -> JSON.String x)
3efd08
+
3efd08
+let json_list_of_string_string_list =
3efd08
+  List.map (fun (x, y) -> x, JSON.String y)
3efd08
+
3efd08
+let push_optional_string lst name = function
3efd08
+  | None -> ()
3efd08
+  | Some v -> List.push_back lst (name, JSON.String v)
3efd08
+
3efd08
+let push_optional_int lst name = function
3efd08
+  | None -> ()
3efd08
+  | Some v -> List.push_back lst (name, JSON.Int (Int64.of_int v))
3efd08
+
3efd08
+let json_unknown_string = function
3efd08
+  | "unknown" -> JSON.Null
3efd08
+  | v -> JSON.String v
3efd08
+
3efd08
+let find_target_disk targets { s_disk_id = id } =
3efd08
+  try List.find (fun t -> t.target_overlay.ov_source.s_disk_id = id) targets
3efd08
+  with Not_found -> assert false
3efd08
+
3efd08
+let create_json_metadata source targets target_buses
3efd08
+                         guestcaps inspect target_firmware =
3efd08
+  let doc = ref [
3efd08
+    "version", JSON.Int 1L;
3efd08
+    "name", JSON.String source.s_name;
3efd08
+    "memory", JSON.Int source.s_memory;
3efd08
+    "vcpu", JSON.Int (Int64.of_int source.s_vcpu);
3efd08
+  ] in
3efd08
+
3efd08
+  (match source.s_genid with
3efd08
+   | None -> ()
3efd08
+   | Some genid -> List.push_back doc ("genid", JSON.String genid)
3efd08
+  );
3efd08
+
3efd08
+  if source.s_cpu_vendor <> None || source.s_cpu_model <> None ||
3efd08
+     source.s_cpu_topology <> None then (
3efd08
+    let cpu = ref [] in
3efd08
+
3efd08
+    push_optional_string cpu "vendor" source.s_cpu_vendor;
3efd08
+    push_optional_string cpu "model" source.s_cpu_model;
3efd08
+    (match source.s_cpu_topology with
3efd08
+     | None -> ()
3efd08
+     | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } ->
3efd08
+        let attrs = [
3efd08
+          "sockets", JSON.Int (Int64.of_int s_cpu_sockets);
3efd08
+          "cores", JSON.Int (Int64.of_int s_cpu_cores);
3efd08
+          "threads", JSON.Int (Int64.of_int s_cpu_threads);
3efd08
+        ] in
3efd08
+        List.push_back cpu ("topology", JSON.Dict attrs)
3efd08
+    );
3efd08
+
3efd08
+    List.push_back doc ("cpu", JSON.Dict !cpu);
3efd08
+  );
3efd08
+
3efd08
+  let firmware =
3efd08
+    let firmware_type =
3efd08
+      match target_firmware with
3efd08
+      | TargetBIOS -> "bios"
3efd08
+      | TargetUEFI -> "uefi" in
3efd08
+
3efd08
+    let fw = ref [
3efd08
+      "type", JSON.String firmware_type;
3efd08
+    ] in
3efd08
+
3efd08
+    (match target_firmware with
3efd08
+     | TargetBIOS -> ()
3efd08
+     | TargetUEFI ->
3efd08
+       let uefi_firmware = find_uefi_firmware guestcaps.gcaps_arch in
3efd08
+       let flags =
3efd08
+         List.map (
3efd08
+           function
3efd08
+           | Uefi.UEFI_FLAG_SECURE_BOOT_REQUIRED -> "secure_boot_required"
3efd08
+         ) uefi_firmware.Uefi.flags in
3efd08
+
3efd08
+       let uefi = ref [
3efd08
+         "code", JSON.String uefi_firmware.Uefi.code;
3efd08
+         "vars", JSON.String uefi_firmware.Uefi.vars;
3efd08
+         "flags", JSON.List (json_list_of_string_list flags);
3efd08
+       ] in
3efd08
+
3efd08
+       push_optional_string uefi "code-debug" uefi_firmware.Uefi.code_debug;
3efd08
+
3efd08
+       List.push_back fw ("uefi", JSON.Dict !uefi)
3efd08
+    );
3efd08
+
3efd08
+    !fw in
3efd08
+  List.push_back doc ("firmware", JSON.Dict firmware);
3efd08
+
3efd08
+  List.push_back doc ("features",
3efd08
+                      JSON.List (json_list_of_string_list source.s_features));
3efd08
+
3efd08
+  let machine =
3efd08
+    match guestcaps.gcaps_machine with
3efd08
+    | I440FX -> "pc"
3efd08
+    | Q35 -> "q35"
3efd08
+    | Virt -> "virt" in
3efd08
+  List.push_back doc ("machine", JSON.String machine);
3efd08
+
3efd08
+  let disks, removables =
3efd08
+    let disks = ref []
3efd08
+    and removables = ref [] in
3efd08
+
3efd08
+    let iter_bus bus_name drive_prefix i = function
3efd08
+    | BusSlotEmpty -> ()
3efd08
+    | BusSlotDisk d ->
3efd08
+       (* Find the corresponding target disk. *)
3efd08
+       let t = find_target_disk targets d in
3efd08
+
3efd08
+       let target_file =
3efd08
+         match t.target_file with
3efd08
+         | TargetFile s -> s
3efd08
+         | TargetURI _ -> assert false in
3efd08
+
3efd08
+       let disk = [
3efd08
+         "dev", JSON.String (drive_prefix ^ drive_name i);
3efd08
+         "bus", JSON.String bus_name;
3efd08
+         "format", JSON.String t.target_format;
3efd08
+         "file", JSON.String (absolute_path target_file);
3efd08
+       ] in
3efd08
+
3efd08
+       List.push_back disks (JSON.Dict disk)
3efd08
+
3efd08
+    | BusSlotRemovable { s_removable_type = CDROM } ->
3efd08
+       let cdrom = [
3efd08
+         "type", JSON.String "cdrom";
3efd08
+         "dev", JSON.String (drive_prefix ^ drive_name i);
3efd08
+         "bus", JSON.String bus_name;
3efd08
+       ] in
3efd08
+
3efd08
+       List.push_back removables (JSON.Dict cdrom)
3efd08
+
3efd08
+    | BusSlotRemovable { s_removable_type = Floppy } ->
3efd08
+       let floppy = [
3efd08
+         "type", JSON.String "floppy";
3efd08
+         "dev", JSON.String (drive_prefix ^ drive_name i);
3efd08
+       ] in
3efd08
+
3efd08
+       List.push_back removables (JSON.Dict floppy)
3efd08
+    in
3efd08
+
3efd08
+    Array.iteri (iter_bus "virtio" "vd") target_buses.target_virtio_blk_bus;
3efd08
+    Array.iteri (iter_bus "ide" "hd") target_buses.target_ide_bus;
3efd08
+    Array.iteri (iter_bus "scsi" "sd") target_buses.target_scsi_bus;
3efd08
+    Array.iteri (iter_bus "floppy" "fd") target_buses.target_floppy_bus;
3efd08
+
3efd08
+    !disks, !removables in
3efd08
+  List.push_back doc ("disks", JSON.List disks);
3efd08
+  List.push_back doc ("removables", JSON.List removables);
3efd08
+
3efd08
+  let nics =
3efd08
+    List.map (
3efd08
+      fun { s_mac = mac; s_vnet_type = vnet_type; s_nic_model = nic_model;
3efd08
+            s_vnet = vnet; } ->
3efd08
+        let vnet_type_str =
3efd08
+          match vnet_type with
3efd08
+          | Bridge -> "bridge"
3efd08
+          | Network -> "network" in
3efd08
+
3efd08
+        let nic = ref [
3efd08
+          "vnet", JSON.String vnet;
3efd08
+          "vnet-type", JSON.String vnet_type_str;
3efd08
+        ] in
3efd08
+
3efd08
+        let nic_model_str = Option.map string_of_nic_model nic_model in
3efd08
+        push_optional_string nic "model" nic_model_str;
3efd08
+
3efd08
+        push_optional_string nic "mac" mac;
3efd08
+
3efd08
+        JSON.Dict !nic
3efd08
+    ) source.s_nics in
3efd08
+  List.push_back doc ("nics", JSON.List nics);
3efd08
+
3efd08
+  let guestcaps_dict =
3efd08
+    let block_bus =
3efd08
+      match guestcaps.gcaps_block_bus with
3efd08
+      | Virtio_blk -> "virtio-blk"
3efd08
+      | Virtio_SCSI -> "virtio-scsi"
3efd08
+      | IDE -> "ide" in
3efd08
+    let net_bus =
3efd08
+      match guestcaps.gcaps_net_bus with
3efd08
+      | Virtio_net -> "virtio-net"
3efd08
+      | E1000 -> "e1000"
3efd08
+      | RTL8139 -> "rtl8139" in
3efd08
+    let video =
3efd08
+      match guestcaps.gcaps_video with
3efd08
+      | QXL -> "qxl"
3efd08
+      | Cirrus -> "cirrus" in
3efd08
+    let machine =
3efd08
+      match guestcaps.gcaps_machine with
3efd08
+      | I440FX -> "i440fx"
3efd08
+      | Q35 -> "q35"
3efd08
+      | Virt -> "virt" in
3efd08
+
3efd08
+    [
3efd08
+      "block-bus", JSON.String block_bus;
3efd08
+      "net-bus", JSON.String net_bus;
3efd08
+      "video", JSON.String video;
3efd08
+      "machine", JSON.String machine;
3efd08
+      "arch", JSON.String guestcaps.gcaps_arch;
3efd08
+      "virtio-rng", JSON.Bool guestcaps.gcaps_virtio_rng;
3efd08
+      "virtio-balloon", JSON.Bool guestcaps.gcaps_virtio_balloon;
3efd08
+      "isa-pvpanic", JSON.Bool guestcaps.gcaps_isa_pvpanic;
3efd08
+      "acpi", JSON.Bool guestcaps.gcaps_acpi;
3efd08
+    ] in
3efd08
+  List.push_back doc ("guestcaps", JSON.Dict guestcaps_dict);
3efd08
+
3efd08
+  (match source.s_sound with
3efd08
+   | None -> ()
3efd08
+   | Some { s_sound_model = model } ->
3efd08
+     let sound = [
3efd08
+       "model", JSON.String (string_of_source_sound_model model);
3efd08
+     ] in
3efd08
+     List.push_back doc ("sound", JSON.Dict sound)
3efd08
+   );
3efd08
+
3efd08
+  (match source.s_display with
3efd08
+   | None -> ()
3efd08
+   | Some d ->
3efd08
+     let display_type =
3efd08
+       match d.s_display_type with
3efd08
+       | Window -> "window"
3efd08
+       | VNC -> "vnc"
3efd08
+       | Spice -> "spice" in
3efd08
+
3efd08
+     let display = ref [
3efd08
+       "type", JSON.String display_type;
3efd08
+     ] in
3efd08
+
3efd08
+     push_optional_string display "keymap" d.s_keymap;
3efd08
+     push_optional_string display "password" d.s_password;
3efd08
+
3efd08
+     let listen =
3efd08
+       match d.s_listen with
3efd08
+       | LNoListen -> None
3efd08
+       | LAddress address ->
3efd08
+         Some [
3efd08
+           "type", JSON.String "address";
3efd08
+           "address", JSON.String address;
3efd08
+         ]
3efd08
+       | LNetwork network ->
3efd08
+         Some [
3efd08
+           "type", JSON.String "network";
3efd08
+           "network", JSON.String network;
3efd08
+         ]
3efd08
+       | LSocket None ->
3efd08
+         Some [
3efd08
+           "type", JSON.String "socket";
3efd08
+           "socket", JSON.Null;
3efd08
+         ]
3efd08
+       | LSocket (Some socket) ->
3efd08
+         Some [
3efd08
+           "type", JSON.String "socket";
3efd08
+           "socket", JSON.String socket;
3efd08
+         ]
3efd08
+       | LNone ->
3efd08
+         Some [
3efd08
+           "type", JSON.String "none";
3efd08
+         ] in
3efd08
+     (match listen with
3efd08
+      | None -> ()
3efd08
+      | Some l -> List.push_back display ("listen", JSON.Dict l)
3efd08
+     );
3efd08
+
3efd08
+     push_optional_int display "port" d.s_port;
3efd08
+
3efd08
+     List.push_back doc ("display", JSON.Dict !display)
3efd08
+  );
3efd08
+
3efd08
+  let inspect_dict =
3efd08
+    let apps =
3efd08
+      List.map (
3efd08
+        fun { G.app2_name = name; app2_display_name = display_name;
3efd08
+              app2_epoch = epoch; app2_version = version;
3efd08
+              app2_release = release; app2_arch = arch; } ->
3efd08
+          JSON.Dict [
3efd08
+            "name", JSON.String name;
3efd08
+            "display-name", JSON.String display_name;
3efd08
+            "epoch", JSON.Int (Int64.of_int32 epoch);
3efd08
+            "version", JSON.String version;
3efd08
+            "release", JSON.String release;
3efd08
+            "arch", JSON.String arch;
3efd08
+          ]
3efd08
+      ) inspect.i_apps in
3efd08
+
3efd08
+    let firmware_dict =
3efd08
+      match inspect.i_firmware with
3efd08
+      | I_BIOS ->
3efd08
+        [
3efd08
+          "type", JSON.String "bios";
3efd08
+        ]
3efd08
+      | I_UEFI devices ->
3efd08
+        [
3efd08
+          "type", JSON.String "uefi";
3efd08
+          "devices", JSON.List (json_list_of_string_list devices);
3efd08
+        ] in
3efd08
+
3efd08
+    [
3efd08
+      "root", JSON.String inspect.i_root;
3efd08
+      "type", JSON.String inspect.i_type;
3efd08
+      "distro", json_unknown_string inspect.i_distro;
3efd08
+      "osinfo", json_unknown_string inspect.i_osinfo;
3efd08
+      "arch", JSON.String inspect.i_arch;
3efd08
+      "major-version", JSON.Int (Int64.of_int inspect.i_major_version);
3efd08
+      "minor-version", JSON.Int (Int64.of_int inspect.i_minor_version);
3efd08
+      "package-format", json_unknown_string inspect.i_package_format;
3efd08
+      "package-management", json_unknown_string inspect.i_package_management;
3efd08
+      "product-name", json_unknown_string inspect.i_product_name;
3efd08
+      "product-variant", json_unknown_string inspect.i_product_variant;
3efd08
+      "mountpoints", JSON.Dict (json_list_of_string_string_list inspect.i_mountpoints);
3efd08
+      "applications", JSON.List apps;
3efd08
+      "windows-systemroot", JSON.String inspect.i_windows_systemroot;
3efd08
+      "windows-software-hive", JSON.String inspect.i_windows_software_hive;
3efd08
+      "windows-system-hive", JSON.String inspect.i_windows_system_hive;
3efd08
+      "windows-current-control-set", JSON.String inspect.i_windows_current_control_set;
3efd08
+      "firmware", JSON.Dict firmware_dict;
3efd08
+    ] in
3efd08
+  List.push_back doc ("inspect", JSON.Dict inspect_dict);
3efd08
+
3efd08
+  !doc
3efd08
diff --git a/v2v/create_json.mli b/v2v/create_json.mli
3efd08
new file mode 100644
3efd08
index 000000000..6dbb6e48b
3efd08
--- /dev/null
3efd08
+++ b/v2v/create_json.mli
3efd08
@@ -0,0 +1,29 @@
3efd08
+(* virt-v2v
3efd08
+ * Copyright (C) 2019 Red Hat Inc.
3efd08
+ *
3efd08
+ * This program is free software; you can redistribute it and/or modify
3efd08
+ * it under the terms of the GNU General Public License as published by
3efd08
+ * the Free Software Foundation; either version 2 of the License, or
3efd08
+ * (at your option) any later version.
3efd08
+ *
3efd08
+ * This program is distributed in the hope that it will be useful,
3efd08
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3efd08
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3efd08
+ * GNU General Public License for more details.
3efd08
+ *
3efd08
+ * You should have received a copy of the GNU General Public License along
3efd08
+ * with this program; if not, write to the Free Software Foundation, Inc.,
3efd08
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3efd08
+ *)
3efd08
+
3efd08
+(** Create JSON metadata for [-o json]. *)
3efd08
+
3efd08
+val create_json_metadata : Types.source -> Types.target list ->
3efd08
+                           Types.target_buses ->
3efd08
+                           Types.guestcaps ->
3efd08
+                           Types.inspect ->
3efd08
+                           Types.target_firmware ->
3efd08
+                           JSON.doc
3efd08
+(** [create_json_metadata source targets target_buses guestcaps
3efd08
+    inspect target_firmware] creates the JSON with the majority
3efd08
+    of the data that virt-v2v used for the conversion. *)
3efd08
diff --git a/v2v/output_json.ml b/v2v/output_json.ml
3efd08
new file mode 100644
3efd08
index 000000000..ca0bda978
3efd08
--- /dev/null
3efd08
+++ b/v2v/output_json.ml
3efd08
@@ -0,0 +1,116 @@
3efd08
+(* virt-v2v
3efd08
+ * Copyright (C) 2019 Red Hat Inc.
3efd08
+ *
3efd08
+ * This program is free software; you can redistribute it and/or modify
3efd08
+ * it under the terms of the GNU General Public License as published by
3efd08
+ * the Free Software Foundation; either version 2 of the License, or
3efd08
+ * (at your option) any later version.
3efd08
+ *
3efd08
+ * This program is distributed in the hope that it will be useful,
3efd08
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3efd08
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3efd08
+ * GNU General Public License for more details.
3efd08
+ *
3efd08
+ * You should have received a copy of the GNU General Public License along
3efd08
+ * with this program; if not, write to the Free Software Foundation, Inc.,
3efd08
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3efd08
+ *)
3efd08
+
3efd08
+open Printf
3efd08
+
3efd08
+open Std_utils
3efd08
+open Tools_utils
3efd08
+open Common_gettext.Gettext
3efd08
+
3efd08
+open Types
3efd08
+open Utils
3efd08
+
3efd08
+type json_options = {
3efd08
+  json_disks_pattern : string;
3efd08
+}
3efd08
+
3efd08
+let print_output_options () =
3efd08
+  printf (f_"Output options (-oo) which can be used with -o json:
3efd08
+
3efd08
+  -oo json-disks-pattern=PATTERN   Pattern for the disks.
3efd08
+")
3efd08
+
3efd08
+let known_pattern_variables = ["DiskNo"; "DiskDeviceName"; "GuestName"]
3efd08
+
3efd08
+let parse_output_options options =
3efd08
+  let json_disks_pattern = ref None in
3efd08
+
3efd08
+  List.iter (
3efd08
+    function
3efd08
+    | "json-disks-pattern", v ->
3efd08
+       if !json_disks_pattern <> None then
3efd08
+         error (f_"-o json: -oo json-disks-pattern set more than once");
3efd08
+       let vars =
3efd08
+         try Var_expander.scan_variables v
3efd08
+         with Var_expander.Invalid_variable var ->
3efd08
+           error (f_"-o json: -oo json-disks-pattern: invalid variable %%{%s}")
3efd08
+             var in
3efd08
+       List.iter (
3efd08
+         fun var ->
3efd08
+           if not (List.mem var known_pattern_variables) then
3efd08
+             error (f_"-o json: -oo json-disks-pattern: unhandled variable %%{%s}")
3efd08
+               var
3efd08
+       ) vars;
3efd08
+       json_disks_pattern := Some v
3efd08
+    | k, _ ->
3efd08
+       error (f_"-o json: unknown output option ‘-oo %s’") k
3efd08
+  ) options;
3efd08
+
3efd08
+  let json_disks_pattern =
3efd08
+    Option.default "%{GuestName}-%{DiskDeviceName}" !json_disks_pattern in
3efd08
+
3efd08
+  { json_disks_pattern }
3efd08
+
3efd08
+class output_json dir json_options = object
3efd08
+  inherit output
3efd08
+
3efd08
+  method as_options = sprintf "-o json -os %s" dir
3efd08
+
3efd08
+  method prepare_targets source overlays _ _ _ _ =
3efd08
+    List.mapi (
3efd08
+      fun i (_, ov) ->
3efd08
+        let outname =
3efd08
+          let vars_fn = function
3efd08
+            | "DiskNo" -> Some (string_of_int (i+1))
3efd08
+            | "DiskDeviceName" -> Some ov.ov_sd
3efd08
+            | "GuestName" -> Some source.s_name
3efd08
+            | _ -> assert false
3efd08
+          in
3efd08
+          Var_expander.replace_fn json_options.json_disks_pattern vars_fn in
3efd08
+        let destname = dir // outname in
3efd08
+        mkdir_p (Filename.dirname destname) 0o755;
3efd08
+        TargetFile destname
3efd08
+    ) overlays
3efd08
+
3efd08
+  method supported_firmware = [ TargetBIOS; TargetUEFI ]
3efd08
+
3efd08
+  method create_metadata source targets
3efd08
+                         target_buses guestcaps inspect target_firmware =
3efd08
+    let doc =
3efd08
+      Create_json.create_json_metadata source targets target_buses
3efd08
+                                       guestcaps inspect target_firmware in
3efd08
+    let doc_string = JSON.string_of_doc ~fmt:JSON.Indented doc in
3efd08
+
3efd08
+    if verbose () then (
3efd08
+      eprintf "resulting JSON:\n";
3efd08
+      output_string stderr doc_string;
3efd08
+      eprintf "\n\n%!";
3efd08
+    );
3efd08
+
3efd08
+    let name = source.s_name in
3efd08
+    let file = dir // name ^ ".json" in
3efd08
+
3efd08
+    with_open_out file (
3efd08
+      fun chan ->
3efd08
+        output_string chan doc_string;
3efd08
+        output_char chan '\n'
3efd08
+    )
3efd08
+end
3efd08
+
3efd08
+let output_json = new output_json
3efd08
+let () = Modules_list.register_output_module "json"
3efd08
diff --git a/v2v/output_json.mli b/v2v/output_json.mli
3efd08
new file mode 100644
3efd08
index 000000000..52f58f2d1
3efd08
--- /dev/null
3efd08
+++ b/v2v/output_json.mli
3efd08
@@ -0,0 +1,31 @@
3efd08
+(* virt-v2v
3efd08
+ * Copyright (C) 2019 Red Hat Inc.
3efd08
+ *
3efd08
+ * This program is free software; you can redistribute it and/or modify
3efd08
+ * it under the terms of the GNU General Public License as published by
3efd08
+ * the Free Software Foundation; either version 2 of the License, or
3efd08
+ * (at your option) any later version.
3efd08
+ *
3efd08
+ * This program is distributed in the hope that it will be useful,
3efd08
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3efd08
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3efd08
+ * GNU General Public License for more details.
3efd08
+ *
3efd08
+ * You should have received a copy of the GNU General Public License along
3efd08
+ * with this program; if not, write to the Free Software Foundation, Inc.,
3efd08
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3efd08
+ *)
3efd08
+
3efd08
+(** [-o json] target. *)
3efd08
+
3efd08
+type json_options
3efd08
+(** Miscellaneous extra command line parameters used by json. *)
3efd08
+
3efd08
+val print_output_options : unit -> unit
3efd08
+val parse_output_options : (string * string) list -> json_options
3efd08
+(** Print and parse json -oo options. *)
3efd08
+
3efd08
+val output_json : string -> json_options -> Types.output
3efd08
+(** [output_json directory json_options] creates and returns a new
3efd08
+    {!Types.output} object specialized for writing output to local
3efd08
+    files with JSON metadata. *)
3efd08
diff --git a/v2v/virt-v2v-output-local.pod b/v2v/virt-v2v-output-local.pod
3efd08
index 7427b1ed7..7c397c0a4 100644
3efd08
--- a/v2v/virt-v2v-output-local.pod
3efd08
+++ b/v2v/virt-v2v-output-local.pod
3efd08
@@ -11,6 +11,9 @@ or libvirt
3efd08
 
3efd08
  virt-v2v [-i* options] -o qemu -os DIRECTORY [--qemu-boot]
3efd08
 
3efd08
+ virt-v2v [-i* options] -o json -os DIRECTORY
3efd08
+                        [-oo json-disks-pattern=PATTERN]
3efd08
+
3efd08
  virt-v2v [-i* options] -o null
3efd08
 
3efd08
 =head1 DESCRIPTION
3efd08
@@ -54,6 +57,13 @@ above, a shell script is created which contains the raw qemu command
3efd08
 you would need to boot the guest.  However the shell script is not
3efd08
 run, I<unless> you also add the I<--qemu-boot> option.
3efd08
 
3efd08
+=item B<-o json -os> C<DIRECTORY>
3efd08
+
3efd08
+This converts the guest to files in C<DIRECTORY>.  The metadata
3efd08
+produced is a JSON file containing the majority of the data virt-v2v
3efd08
+gathers during the conversion.
3efd08
+See L</OUTPUT TO JSON> below.
3efd08
+
3efd08
 =item B<-o null>
3efd08
 
3efd08
 The guest is converted, but the final result is thrown away and no
3efd08
@@ -140,6 +150,51 @@ Define the final guest in libvirt:
3efd08
 
3efd08
 =back
3efd08
 
3efd08
+=head1 OUTPUT TO JSON
3efd08
+
3efd08
+The I<-o json> option produces the following files by default:
3efd08
+
3efd08
+ NAME.json                     JSON metadata.
3efd08
+ NAME-sda, NAME-sdb, etc.      Guest disk(s).
3efd08
+
3efd08
+where C<NAME> is the guest name.
3efd08
+
3efd08
+It is possible to change the pattern of the disks using the
3efd08
+I<-oo json-disks-pattern=...> option: it allows parameters in form of
3efd08
+C<%{...}> variables, for example:
3efd08
+
3efd08
+ -oo json-disks-pattern=disk%{DiskNo}.img
3efd08
+
3efd08
+Recognized variables are:
3efd08
+
3efd08
+=over 4
3efd08
+
3efd08
+=item C<%{DiskNo}>
3efd08
+
3efd08
+The index of the disk, starting from 1.
3efd08
+
3efd08
+=item C<%{DiskDeviceName}>
3efd08
+
3efd08
+The destination device of the disk, e.g. C<sda>, C<sdb>, etc.
3efd08
+
3efd08
+=item C<%{GuestName}>
3efd08
+
3efd08
+The name of the guest.
3efd08
+
3efd08
+=back
3efd08
+
3efd08
+Using a pattern it is possible use subdirectories for the disks,
3efd08
+even with names depending on variables; for example:
3efd08
+
3efd08
+ -oo json-disks-pattern=%{GuestName}-%{DiskNo}/disk.img
3efd08
+
3efd08
+The default pattern is C<%{GuestName}-%{DiskDeviceName}>.
3efd08
+
3efd08
+If the literal C<%{...}> text is needed, it is possible to avoid the
3efd08
+escape it with a leading C<%>; for example,
3efd08
+C<%%{GuestName}-%{DiskNo}.img> will create file names for the
3efd08
+disks like C<%%{GuestName}-1.img>, C<%%{GuestName}-2.img>, etc.
3efd08
+
3efd08
 =head1 SEE ALSO
3efd08
 
3efd08
 L<virt-v2v(1)>.
3efd08
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
3efd08
index cf9464834..9a555c3be 100644
3efd08
--- a/v2v/virt-v2v.pod
3efd08
+++ b/v2v/virt-v2v.pod
3efd08
@@ -425,6 +425,17 @@ instead.
3efd08
 Set the output method to OpenStack Glance.  In this mode the converted
3efd08
 guest is uploaded to Glance.  See L<virt-v2v-output-openstack(1)>.
3efd08
 
3efd08
+=item B<-o> B<json>
3efd08
+
3efd08
+Set the output method to I<json>.
3efd08
+
3efd08
+In this mode, the converted guest is written to a local directory
3efd08
+specified by I<-os /dir> (the directory must exist), with a JSON file
3efd08
+containing the majority of the metadata that virt-v2v gathered during
3efd08
+the conversion.
3efd08
+
3efd08
+See L<virt-v2v-output-local(1)>.
3efd08
+
3efd08
 =item B<-o> B<libvirt>
3efd08
 
3efd08
 Set the output method to I<libvirt>.  This is the default.
3efd08
@@ -696,8 +707,8 @@ The location of the storage for the converted guest.
3efd08
 For I<-o libvirt>, this is a libvirt directory pool
3efd08
 (see S<C<virsh pool-list>>) or pool UUID.
3efd08
 
3efd08
-For I<-o local> and I<-o qemu>, this is a directory name.  The
3efd08
-directory must exist.
3efd08
+For I<-o json>, I<-o local> and I<-o qemu>, this is a directory name.
3efd08
+The directory must exist.
3efd08
 
3efd08
 For I<-o rhv-upload>, this is the name of the destination Storage
3efd08
 Domain.
3efd08
-- 
da373f
2.18.4
3efd08