(* helper-v2v-input
 * Copyright (C) 2009-2021 Red Hat Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *)

open Printf
open Unix

open Std_utils
open Tools_utils
open Common_gettext.Gettext
open Xpath_helpers

open Types
open Utils

open Parse_libvirt_xml
open Input

module VDDK = struct
  let to_string options args =
    let xs = "-it vddk" :: args in
    let xs =
      match options.input_conn with
      | Some ic -> ("-ic " ^ ic) :: xs
      | None -> xs in
    let xs = "-i libvirt" :: xs in
    String.concat " " xs

  let query_input_options () =
    printf (f_"Input options (-io) which can be used with -it vddk:

  -io vddk-thumbprint=xx:xx:xx:...
                               VDDK server thumbprint (required)

All other settings are optional:

  -io vddk-config=FILE         VDDK configuration file
  -io vddk-cookie=COOKIE       VDDK cookie
  -io vddk-libdir=LIBDIR       VDDK library parent directory
  -io vddk-nfchostport=PORT    VDDK nfchostport
  -io vddk-port=PORT           VDDK port
  -io vddk-snapshot=SNAPSHOT-MOREF
                               VDDK snapshot moref
  -io vddk-transports=MODE:MODE:..
                               VDDK transports

Refer to nbdkit-vddk-plugin(1) and the VDDK documentation for further
information on these settings.
")

  let setup dir options args =
    (* Check there are no input options we don't understand.
     * Also removes the "vddk-" prefix from the internal list.
     *)
    let vddk_option_keys =
      [ "config";
        "cookie";
        "libdir";
        "nfchostport";
        "port";
        "snapshot";
        "thumbprint";
        "transports" ] in

    let io_options =
      List.map (
        fun (key, value) ->
          let error_invalid_key () =
            error (f_"-it vddk: ‘-io %s’ is not a valid input option") key
          in
          if not (String.is_prefix key "vddk-") then error_invalid_key ();
          let key = String.sub key 5 (String.length key-5) in
          if not (List.mem key vddk_option_keys) then error_invalid_key ();
          (key, value)
      ) options.input_options in

    (* Check no option appears more than once. *)
    let keys = List.map fst io_options in
    if List.length keys <> List.length (List.sort_uniq compare keys) then
      error (f_"-it vddk: duplicate -io options on the command line");

    (* thumbprint is mandatory. *)
    if not (List.mem_assoc "thumbprint" io_options) then
      error (f_"You must pass the ‘-io vddk-thumbprint’ option with the SSL thumbprint of the VMware server.  To find the thumbprint, see the nbdkit-vddk-plugin(1) manual.  See also the virt-v2v-input-vmware(1) manual.");

    (* Get the guest name. *)
    let guest =
      match args with
      | [arg] -> arg
      | _ ->
         error (f_"-i libvirt: expecting a libvirt guest name on the command line") in

    (* -ic must be set and it must contain a server.  This is
     * enforced by virt-v2v.
     *)
    let input_conn =
      match options.input_conn with
      | Some ic -> ic
      | None ->
         error (f_"-i libvirt: expecting -ic parameter for vcenter connection") in

    let uri =
      try Xml.parse_uri input_conn
      with Invalid_argument msg ->
        error (f_"could not parse '-ic %s'.  Original error message was: %s")
          input_conn msg in

    (* Connect to the hypervisor. *)
    let conn =
      let auth = Libvirt_utils.auth_for_password_file
                   ?password_file:options.input_password () in
      Libvirt.Connect.connect_auth ~name:input_conn auth in

    (* Parse the libvirt XML. *)
    let source, disks, xml = parse_libvirt_domain conn guest in

    (* Find the <vmware:moref> element from the XML.  This was added
     * in libvirt >= 3.7 and is required.
     *)
    let moref =
      let doc = Xml.parse_memory xml in
      let xpathctx = Xml.xpath_new_context doc in
      Xml.xpath_register_ns xpathctx
        "vmware" "http://libvirt.org/schemas/domain/vmware/1.0";
      let xpath_string = xpath_string xpathctx in
      match xpath_string "/domain/vmware:moref" with
      | Some moref -> moref
      | None ->
         error (f_"<vmware:moref> was not found in the output of ‘virsh dumpxml \"%s\"’.  The most likely reason is that libvirt is too old, try upgrading libvirt to ≥ 3.7.") guest in

    (* It probably never happens that the server name can be missing
     * from the libvirt URI, but we need a server name to pass to
     * nbdkit, so ...
     *)
    let server =
      match uri.Xml.uri_server with
      | Some server -> server
      | None ->
         error (f_"‘-ic %s’ URL does not contain a host name field") input_conn in

    let user = uri.Xml.uri_user in

    let config =
      try Some (List.assoc "config" io_options) with Not_found -> None in
    let cookie =
      try Some (List.assoc "cookie" io_options) with Not_found -> None in
    let libdir =
      try Some (List.assoc "libdir" io_options) with Not_found -> None in
    let nfchostport =
      try Some (List.assoc "nfchostport" io_options) with Not_found -> None in
    let port =
      try Some (List.assoc "port" io_options) with Not_found -> None in
    let snapshot =
      try Some (List.assoc "snapshot" io_options) with Not_found -> None in
    let thumbprint =
      try List.assoc "thumbprint" io_options
      with Not_found -> assert false (* checked above *) in
    let transports =
      try Some (List.assoc "transports" io_options) with Not_found -> None in

    (* Create an nbdkit instance for each disk. *)
    List.iteri (
      fun i { d_format = format; d_type } ->
        let socket = sprintf "%s/in%d" dir i in
        On_exit.unlink socket;

        match d_type with
        | BlockDev _ | NBD _ | HTTP _ -> (* These should never happen? *)
           assert false

        | LocalFile path ->
           (* The <source file=...> attribute returned by the libvirt
            * VMX driver looks like "[datastore] path".  We can use it
            * directly as the nbdkit file= parameter, and it is passed
            * directly in this form to VDDK.
            *)
           let nbdkit =
             let cor = dir // "convert" in
             Nbdkit_vddk.create_vddk ?bandwidth:options.bandwidth
               ?config ?cookie ~cor
               ?libdir ~moref
               ?nfchostport ?password_file:options.input_password ?port
               ~server ?snapshot ~thumbprint ?transports ?user
               path in
           let _, pid = Nbdkit.run_unix ~socket nbdkit in
           On_exit.kill pid
    ) disks;

    source
end
