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
"--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";
]
(* 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.
*)
(* 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
+
+ (* UEFI firmware and NVRAM on remote, if required. *)
+ let nvram =
+ match remote_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
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 remote_arch with
+ | "aarch64" ->
+ xml ^ sprintf "\
+ <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 ^ "\
</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'/>
<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"
| 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'/>
</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 =
+ if remote_arch = "x86_64" then (
+ xml ^ "\
<input type='tablet' bus='usb'/>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
+"
+ ) else
+ xml in
+ let xml = xml ^ "\
</devices>
</domain>" in
+ xml
+
+ 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
- (* Copy the template to remote and build the guest. *)
+ 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 build"
- (quote hostname)
- (quote template_info.Template.base_image) (* base_image *)
- format (* format *)
- name (* name *)
- (quote remote_image) (* output *)
- 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 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";