boot: Allow template to specify custom libvirt XML.
[mclu.git] / mclu_boot.ml
index 6927363..16b8ac9 100644 (file)
@@ -33,13 +33,28 @@ let set_memory s =
     eprintf "mclu: don't understand --memory parameter '%s'
 Try something like --memory 1G\n" s;
     exit 1
+let size = ref 0L                       (* 0 = default *)
+let set_size s =
+  try size := bytes_of_human_size s
+  with Not_found ->
+    eprintf "mclu: don't understand --size parameter '%s'
+Try something like --size 20G\n" s;
+    exit 1
+let timezone = ref ""                   (* "" = no timezone set *)
 let vcpus = ref 0                       (* 0 = choose for me *)
 
+let open_console = ref false
+let open_viewer = ref false
+
 let get_arg_speclist () = Arg.align [
+  "--console",  Arg.Set open_console, " Open the serial console";
   "--cpus",     Arg.Set_int vcpus, "n Number of virtual CPUs";
   "--memory",   Arg.String set_memory, "nnG Amount of RAM to give guest";
   "--ram",      Arg.String set_memory, "nnG Amount of RAM to give guest";
+  "--size",     Arg.String set_size, "nnG Size of disk to give guest";
+  "--timezone", Arg.Set_string timezone, "TZ Set timezone of guest";
   "--vcpus",    Arg.Set_int vcpus, "n Number of virtual CPUs";
+  "--viewer",   Arg.Set open_viewer, " Open the graphical console";
 ]
 
 let boot ~verbose template name =
@@ -56,6 +71,18 @@ Try `mclu list --templates' to list all known templates.\n" template;
   (* Probe the template for various features. *)
   let template_info = Template.probe ~verbose template_filename in
 
+  (* Check --size is not too small. *)
+  let size =
+    match !size, template_info.Template.minimum_size with
+    | 0L, None -> 0L               (* virt-builder default *)
+    | 0L, Some min_size ->         (* go with template minimum size *)
+      min_size
+    | size, Some min_size when size < min_size ->
+      eprintf "mclu: --size parameter is smaller than the minimum specified by the template (%s).\n"
+        (human_size min_size);
+      exit 1
+    | size, _ -> size in           (* go with user-specified size *)
+
   (* Decide how much RAM we will give the guest.  This affects our
    * choice of node, so do it early.
    *)
@@ -134,7 +161,9 @@ Try: `mclu on %s'\n" hostname hostname;
 
   (* Where we upload the template and image on remote. *)
   let format, extension = "qcow2", "qcow2" in
-  let remote_filename = sprintf "/tmp/mclu%s.sh" (string_random8 ()) in
+  let remote_template = sprintf "/tmp/mclu%s.sh" (string_random8 ()) in
+  let remote_template_wrapper = sprintf "/tmp/mclu%s.sh" (string_random8 ()) in
+  let xml_template_wrapper = sprintf "/tmp/mclu%s.sh" (string_random8 ()) in
   let remote_image = sprintf "/var/tmp/%s.%s" name extension in
 
   (* Get ready to generate the guest XML. *)
@@ -146,8 +175,10 @@ Try: `mclu on %s'\n" hostname hostname;
     sprintf "52:54:00:%02x:%02x:%02x"
       (Random.int 256) (Random.int 256) (Random.int 256) in
 
-  (* Generate the guest XML.  XXX Quoting. *)
-  let xml = sprintf "\
+  (* Generate the guest XML. *)
+  let generate_standard_xml () =
+    (* XXX Better quoting. *)
+    let xml = sprintf "\
 <domain type='kvm'>
   <name>%s</name>
   <memory unit='KiB'>%Ld</memory>
@@ -174,12 +205,12 @@ Try: `mclu on %s'\n" hostname hostname;
   <devices>
 " name (memory /^ 1024L) (memory /^ 1024L) vcpus in
 
-  let xml = xml ^ sprintf "\
+    let xml = xml ^ sprintf "\
   <disk type='file' device='disk'>
     <driver name='qemu' type='%s' cache='none' io='native'/>
     <source file='%s'/>
 " format remote_image in
-  let xml = xml ^
+    let xml = xml ^
     match template_info.Template.disk_bus with
     | Some "ide" ->
       "      <target dev='sda' bus='ide'/>\n"
@@ -188,22 +219,23 @@ Try: `mclu on %s'\n" hostname hostname;
     | Some bus ->
       eprintf "mclu: unknown disk-bus: %s\n" bus;
       exit 1 in
-  let xml = xml ^ "\
+    let xml = xml ^ "\
     </disk>
 " in
 
-  let xml = xml ^
-    if template_info.Template.disk_bus = Some "virtio-scsi" then
-      "  <controller type='scsi' index='0' model='virtio-scsi'/>\n"
-    else
-      "" in
-
-  (* XXX Don't hard-code bridge name here. *)
-  let network_model =
-    match template_info with
-    | { Template.network_model = None } -> "virtio"
-    | { Template.network_model = Some d } -> d in
-  let xml = xml ^ sprintf "\
+    let xml =
+      xml ^
+        if template_info.Template.disk_bus = Some "virtio-scsi" then
+          "  <controller type='scsi' index='0' model='virtio-scsi'/>\n"
+        else
+          "" in
+
+    (* XXX Don't hard-code bridge name here. *)
+    let network_model =
+      match template_info with
+      | { Template.network_model = None } -> "virtio"
+      | { Template.network_model = Some d } -> d in
+    let xml = xml ^ sprintf "\
     <interface type='bridge'>
       <mac address='%s'/>
       <source bridge='br0'/>
@@ -211,9 +243,12 @@ Try: `mclu on %s'\n" hostname hostname;
     </interface>
 " mac_addr network_model in
 
-  let xml = xml ^ "\
+    let xml = xml ^ "\
+    <serial type='pty'>
+      <target port='0'/>
+    </serial>
     <console type='pty'>
-      <target type='virtio' port='0'/>
+      <target type='serial' port='0'/>
     </console>
     <input type='tablet' bus='usb'/>
     <input type='mouse' bus='ps2'/>
@@ -224,22 +259,104 @@ Try: `mclu on %s'\n" hostname hostname;
     </video>
   </devices>
 </domain>" in
+    xml
 
-  (* Copy the template to remote and build the guest. *)
+  and generate_custom_xml () =
+    (* Generate a wrapper script to make passing the variables
+     * to the template easier.
+     *)
+    let () =
+      let chan = open_out xml_template_wrapper in
+      let fpf fs = fprintf chan fs in
+      fpf "#!/bin/sh\n";
+      fpf "export format=%s\n" (quote format);
+      fpf "export mac_addr=%s\n" (quote mac_addr);
+      fpf "export memory_kb=%Ld\n" (memory /^ 1024L);
+      fpf "export name=%s\n" (quote name);
+      fpf "export output=%s\n" (quote remote_image);
+      fpf "export vcpus=%d\n" vcpus;
+      fpf "%s xml\n" template_filename;
+      close_out chan;
+      Unix.chmod xml_template_wrapper 0o755 in
+
+    if verbose then printf "%s\n%!" xml_template_wrapper;
+    let chan = Unix.open_process_in xml_template_wrapper in
+    let lines = ref [] in
+    (try while true do lines := input_line chan :: !lines done
+     with End_of_file -> ());
+    let stat = Unix.close_process_in chan in
+    (match stat with
+     | Unix.WEXITED 0 -> ()
+     | Unix.WEXITED i ->
+        eprintf "mclu: template '%s' subcmd xml exited with error %d\n"
+                template_filename i;
+        exit 1
+     | Unix.WSIGNALED i ->
+        eprintf "mclu: template '%s' subcmd xml killed by signal %d\n"
+                template_filename i;
+        exit 1
+     | Unix.WSTOPPED i ->
+        eprintf "mclu: template '%s' subcmd xml stopped by signal %d\n"
+                template_filename i;
+        exit 1
+    );
+    let xml = String.concat "\n" (List.rev !lines) in
+    xml
+  in
+
+  let xml =
+    if not template_info.Template.has_xml_target then
+      generate_standard_xml ()
+    else
+      generate_custom_xml () in
+
+  (* Copy the template to remote. *)
   let cmd =
     sprintf "scp %s root@%s:%s"
-      (quote template_filename) (quote hostname) remote_filename in
+      (quote template_filename) (quote hostname) remote_template in
   if verbose then printf "%s\n%!" cmd;
   if Sys.command cmd <> 0 then (
     eprintf "mclu: scp template to remote failed\n";
     exit 1
   );
-  let cmd =
+
+  (* Create a wrapper script that sets the variables and runs the
+   * template.  This just avoids complex quoting.
+   *)
+  let () =
+    let chan = open_out remote_template_wrapper in
+    let fpf fs = fprintf chan fs in
+    fpf "#!/bin/sh\n";
     (* XXX Don't hard-code network_bridge here. *)
-    sprintf "ssh root@%s LIBGUESTFS_BACKEND_SETTINGS=network_bridge=br0 %s build %s %s %s"
-      (quote hostname) remote_filename
-      (quote template_info.Template.base_image) (quote remote_image)
-      format in
+    fpf "export LIBGUESTFS_BACKEND_SETTINGS=network_bridge=br0\n";
+    fpf "export base_image=%s\n" (quote template_info.Template.base_image);
+    fpf "export format=%s\n" (quote format);
+    fpf "export name=%s\n" (quote name);
+    fpf "export output=%s\n" (quote remote_image);
+    (match size with
+    | 0L -> ()
+    | size -> fpf "export size=%s\n" (quote (sprintf "--size %Ldb" size))
+    );
+    (match !timezone with
+    | "" -> ()
+    | tz -> fpf "export timezone=%s\n" (quote (sprintf "--timezone %s" tz))
+    );
+    fpf "%s build\n" remote_template;
+    close_out chan;
+    Unix.chmod remote_template_wrapper 0o755 in
+
+  let cmd =
+    sprintf "scp %s root@%s:%s"
+      (quote remote_template_wrapper) (quote hostname)
+      (quote remote_template_wrapper) in
+  if verbose then printf "%s\n%!" cmd;
+  if Sys.command cmd <> 0 then (
+    eprintf "mclu: scp template wrapper to remote failed\n";
+    exit 1
+  );
+
+  let cmd =
+    sprintf "ssh root@%s %s" (quote hostname) (quote remote_template_wrapper) in
   if verbose then printf "%s\n%!" cmd;
   if Sys.command cmd <> 0 then (
     eprintf "mclu: remote build failed\n";
@@ -247,15 +364,25 @@ Try: `mclu on %s'\n" hostname hostname;
   );
 
   (* Start the guest. *)
-  try
-    let conn =
-      let name = node.MS.node_status.MS.node.Mclu_conf.libvirt_uri in
-      C.connect ~name () in
-    let dom = D.create_xml conn xml [] in
-    printf "mclu: %s:%s started\n" hostname (D.get_name dom)
-  with Libvirt.Virterror msg ->
-    eprintf "mclu: %s: %s\n" hostname (Libvirt.Virterror.to_string msg);
-    exit 1
+  let dom =
+    try
+      let conn =
+        let name = node.MS.node_status.MS.node.Mclu_conf.libvirt_uri in
+        C.connect ~name () in
+      let dom = D.create_xml conn xml [] in
+      printf "mclu: %s:%s started\n" hostname (D.get_name dom);
+      dom
+    with Libvirt.Virterror msg ->
+      eprintf "mclu: %s: %s\n" hostname (Libvirt.Virterror.to_string msg);
+      exit 1 in
+
+  (* Graphical console? *)
+  if !open_viewer then
+    Mclu_viewer.viewer ~verbose ~host:hostname (D.get_name dom);
+
+  (* Serial console?  (Interactive, so run it last) *)
+  if !open_console then
+    Mclu_console.console ~verbose ~host:hostname (D.get_name dom)
 
 let run ~verbose = function
   | [ template; name ] ->