Add 'cmdline' template option.
[mclu.git] / mclu_boot.ml
index a253981..625aea2 100644 (file)
@@ -71,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.
    *)
@@ -149,8 +161,31 @@ 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
+  let remote_external_kernel_dir = sprintf "/var/tmp/%s.boot" name in
+  let remote_external_kernel = sprintf "/var/tmp/%s.boot/kernel" name in
+  let remote_external_initrd = sprintf "/var/tmp/%s.boot/initrd" name in
+  let remote_arch = node.MS.node_status.node_info.model in
+
+  (* Guest arch defaults to the node host arch, but can be overridden
+   * in the template.
+   *)
+  let guest_arch =
+    match template_info.Template.guest_arch with
+    | Some arch -> arch
+    | None -> remote_arch in
+
+  (* UEFI firmware and NVRAM on remote, if required. *)
+  let nvram =
+    match guest_arch with
+    | "aarch64" ->
+       Some ("/usr/share/edk2.git/aarch64/QEMU_EFI-pflash.raw",
+             "/usr/share/edk2.git/aarch64/vars-template-pflash.raw",
+             remote_image ^ ".nvram")
+    | _ -> None in
 
   (* Get ready to generate the guest XML. *)
   let vcpus = !vcpus in
@@ -161,23 +196,66 @@ 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>
   <currentMemory unit='KiB'>%Ld</currentMemory>
   <vcpu>%d</vcpu>
+" name (memory /^ 1024L) (memory /^ 1024L) vcpus in
+
+    let xml = xml ^ "\
   <os>
-    <type>hvm</type>
     <boot dev='hd'/>
+" in
+    let xml =
+      match guest_arch with
+      | "arm" | "armv7" | "armv7l" | "armv7hl" ->
+         xml ^ "\
+    <type arch='armv7l' machine='virt'>hvm</type>
+"
+      | "aarch64" ->
+         xml ^ "\
+    <type machine='virt'>hvm</type>
+"
+      | _ ->
+         xml ^ "\
+    <type>hvm</type>
+" in
+
+    let xml =
+      match nvram with
+      | Some (loader, nvram_template, nvram) ->
+         xml ^ sprintf "\
+    <loader readonly='yes' type='pflash'>%s</loader>
+    <nvram template='%s'>%s</nvram>
+" loader nvram_template nvram
+      | None -> xml in
+
+    let xml = xml ^
+      if template_info.Template.needs_external_kernel then
+        sprintf "\
+    <kernel>%s</kernel>
+    <initrd>%s</initrd>
+" remote_external_kernel remote_external_initrd
+      else "" in
+
+    let xml = xml ^
+      match template_info.Template.cmdline with
+      | Some cmdline -> sprintf "    <cmdline>%s</cmdline>\n" cmdline
+      | None -> "" in
+
+    let xml = xml ^ "\
   </os>
   <features>
     <acpi/>
     <apic/>
     <pae/>
   </features>
-  <cpu mode='host-model' fallback='allow' />
+  <cpu mode='host-passthrough'/> <!-- -cpu host, also allows nested -->
   <clock offset='utc'>
     <timer name='rtc' tickpolicy='catchup'/>
     <timer name='pit' tickpolicy='delay'/>
@@ -187,14 +265,14 @@ Try: `mclu on %s'\n" hostname hostname;
   <on_reboot>restart</on_reboot>
   <on_crash>restart</on_crash>
   <devices>
-" name (memory /^ 1024L) (memory /^ 1024L) vcpus in
+" 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"
@@ -203,22 +281,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'/>
@@ -226,13 +305,19 @@ 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='serial' port='0'/>
     </console>
+" in
+    let xml =
+      match guest_arch with
+      | "i386" | "i486" | "i586" | "i686"
+      | "x86_64" ->
+         xml ^ "\
     <input type='tablet' bus='usb'/>
     <input type='mouse' bus='ps2'/>
     <input type='keyboard' bus='ps2'/>
@@ -240,41 +325,127 @@ Try: `mclu on %s'\n" hostname hostname;
     <video>
       <model type='cirrus' vram='9216' heads='1'/>
     </video>
+"
+      | _ -> xml in
+    let xml = xml ^ "\
   </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 initrd=%s\n" (quote remote_external_initrd);
+      fpf "export kernel=%s\n" (quote remote_external_kernel);
+      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/bash\n";
+    fpf "set -e\n";
     (* XXX Don't hard-code network_bridge here. *)
-    sprintf "ssh root@%s \
-LIBGUESTFS_BACKEND_SETTINGS=network_bridge=br0 \
-base_image=%s \
-format=%s \
-name=%s \
-output=%s \
-%s \
-%s \
-%s build"
-      (quote hostname)
-      (quote template_info.Template.base_image) (* base_image *)
-      format (* format *)
-      name (* name *)
-      (quote remote_image) (* output *)
-      (match !size with
-      | 0L -> ""
-      | size -> sprintf "size=%Ldb" size) (* size *)
-      (match !timezone with
-      | "" -> ""
-      | tz -> quote "timezone=" ^ tz)   (* timezone *)
-      remote_filename 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 guest_arch=%s\n" (quote guest_arch);
+    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))
+    );
+    (match nvram with
+     | Some (_, nvram_template, nvram) ->
+        fpf "cp %s %s\n" (quote nvram_template) (quote nvram)
+     | None -> ()
+    );
+    fpf "%s build\n" remote_template;
+    if template_info.Template.needs_external_kernel then (
+      fpf "rm -rf %s\n" (quote remote_external_kernel_dir);
+      fpf "mkdir %s\n" (quote remote_external_kernel_dir);
+      fpf "pushd %s\n" (quote remote_external_kernel_dir);
+      fpf "virt-builder --get-kernel %s\n" (quote remote_image);
+      fpf "ln vmlinuz-* kernel\n";
+      fpf "ln init* initrd\n";
+      fpf "popd\n";
+    );
+    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";