Skip to content

Add VM.sysprep API call #6547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

lindig
Copy link
Contributor

@lindig lindig commented Jun 20, 2025

A new API call that works in collaboration with a guest agent on a Windows VM to pass a unattend.xml to Windows sysprep.

  • Receives the XML content as a parameter. The XE interface supports defining this from a file but an API client has to pass the content as a parameter.
  • The API call creates a local ISO SR; creates an ISO with unattended.xml and inserts the ISO into the VMs CD-ROM, then notifies the VM via xenstore.
  • Outside of scaffolding for the API, everything in vm_sysprep.ml.
  • Exceptions raised are handled in Xapi_vm.sysprep()

@last-genius
Copy link
Contributor

Did you mean to assign Rob and Edwin as reviewers, not assignees?

@last-genius
Copy link
Contributor

I can guess the big-picture intentions behind this, but as someone not terribly familiar with Windows setup, it could be worth spelling out why this is wanted and what this simplifies and improves

@lindig
Copy link
Contributor Author

lindig commented Jun 20, 2025

It's coming from a customer requirement: supporting a way to customise a Windows VM using Windows tools. The challenge is how to get a file to the VM such that a tool in the VM can read it. My preferred method would have been providing this via the network but this does not work when the VM has no network yet set up. So we end up with a somewhat convoluted approach to create an ISO that we provide via CD ROM to the VM. This is currently disabled via a flag.

@lindig lindig requested review from robhoes and edwintorok June 20, 2025 13:41
@lindig lindig force-pushed the private/christianlin/vm-sysprep branch from 5c8c133 to 7ecd772 Compare June 23, 2025 10:06
, {
reqd= ["filename"]
; optn= []
; help= "Pass and execure sysprep configuration file"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo


let finally = Xapi_stdext_pervasives.Pervasiveext.finally

let genisoimage = "/usr/bin/genisoimage"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the usual way to refer to these is by declaring it in Xapi_globs.Resources, perhaps as an optional runtime dependency (if the file is missing we won't be able to sysprep, but we should still be able to perform other ops).

let genisoimage = "/usr/bin/genisoimage"

(** This will be shown to the user to explain a failure *)
exception Failure of string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be confusing, because failwith also raises a builtin Failure exception, can we use a different name that doesn't clash with stdlib builtins?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that thought; can rename it.

(** create a name with a random infix. We need random names for
temporay directories to avoid collition *)
let temp_name prefix suffix =
let rnd = Random.State.bits (Lazy.force prng) land 0xFFFFFF in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy doesn't play nicely with multithreaded code (in OCaml 4.14.2 it can actually segfault, although that is fixed on the 4.14 branch, and we should also really apply that patch to our internal compiler), also sharing a Random.State between threads is probably worse than using the default random state (which in OCaml 5 has some code to make it domain-local).

To avoid issues with lazy can you initialize this fully on startup like this:
let prng = Random.State.make_self_init ()

Then even if we have race conditions on this state, it'll only affect this prng, and even if we end up with same random number in 2 threads that just means we'll fail to generate the temp name and retry.

We have multiple PRNGs in XAPI already though, see e.g. xapi_session.ml where we use Mirage_crypto_rng.generate, although in this case we don't necessarily need cryptographic quality random numbers.

@lindig lindig force-pushed the private/christianlin/vm-sysprep branch 3 times, most recently from 2d29a74 to ffd8aed Compare June 24, 2025 12:55
@lindig lindig marked this pull request as ready for review June 24, 2025 13:12
@lindig lindig force-pushed the private/christianlin/vm-sysprep branch from ffd8aed to 782a6d6 Compare June 25, 2025 15:36
Christian Lindig added 13 commits June 25, 2025 16:37
Add a new API call for VM sysprep and the corresponding XE
implementation. This is mostly scaffolding.

Signed-off-by: Christian Lindig <[email protected]>
We want to create a temporary directory that will be used to hold files
for creating an ISO. There is no existing function that creates all
necessary directories in a predicatble way.

Signed-off-by: Christian Lindig <[email protected]>
Implement creating an ISO from a temporary directory

Signed-off-by: Christian Lindig <[email protected]>
Signed-off-by: Christian Lindig <[email protected]>
Create a local SR unless it exists.

Signed-off-by: Christian Lindig <[email protected]>
We need to locate the CD drive of the VM.

Signed-off-by: Christian Lindig <[email protected]>
The VM is notified to perform a sysprep by writing to XenStore. The VM
picks this up via its guest agent.

Signed-off-by: Christian Lindig <[email protected]>
Introduce on_startup(), called on xapi startup, to clean up the local SR
to avoid accumulating ISO files.

Signed-off-by: Christian Lindig <[email protected]>
For simplicity, add vm-sysprep-enabled = true/false to xapi.conf rather
than using a full V6D feature flag.

Signed-off-by: Christian Lindig <[email protected]>
Signed-off-by: Christian Lindig <[email protected]>
Add the paths to genisoimage so xapi_globs and list it as non-essential
binary.

Signed-off-by: Christian Lindig <[email protected]>
@lindig lindig force-pushed the private/christianlin/vm-sysprep branch from 782a6d6 to 6e37536 Compare June 26, 2025 09:40
~policy (fun () ->
forward_vm_op ~local_fn ~__context ~vm:self ~remote_fn
) ;
Xapi_vm_lifecycle.update_allowed_operations ~__context ~self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_vm_operation already does this.

@@ -239,6 +239,8 @@ let prototyped_of_message = function
Some "25.2.0"
| "host", "set_numa_affinity_policy" ->
Some "24.0.0"
| "VM", "sysprep" ->
Some "25.22.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a refresh


(* VM.sysprep *)

(* Using a single error during development, might want to expand this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this now that the code is stable

Db.SR.get_VDIs ~__context ~self:sr
|> List.iter @@ fun self ->
match Db.VDI.get_record ~__context ~self with
| API.{vDI_VBDs= []; vDI_location= _location; _} ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is vDI_location= _location doing? I don't see _location defined anywhere?

)

(** create a name with a random infix. We need random names for
temporay directories to avoid collition *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"temporay"... "collition"... random mutations? :)

let device_config = [("location", SR.dir); ("legacy_mode", "true")] in
Xapi_sr.create ~__context ~host ~name_label:label ~device_config
~content_type:"iso" ~_type:"iso" ~name_description:"Sysprep ISOs"
~shared:false ~sm_config:[] ~physical_size:(mib 512)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 512MiB if the XML file is limited to just 32KiB?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SR holds multiple ISOs, each is about 300k; we remove them after each API call. I am not sure the 512MB is enforced by SM.

Comment on lines +224 to +231
match xs.Xs.read (control // "feature-sysprep") with
| "1" ->
debug "%s: VM %s supports sysprep" __FUNCTION__ vm_uuid
| _ ->
fail "VM %s does not support sysprep" vm_uuid
| exception _ ->
fail "VM %s does not support sysprep" vm_uuid
) ;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could eventually be replaced by allowed_operations checking (see check_op_for_feature in xapi_vm_lifecycle.ml).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test is good because it checks against the driver directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, and so does the xapi_vm_lifecycle.ml check, which is based on xapi_guest_agent.ml putting all those control keys (including all feature- keys) into VM_guest_metrics.other (which is a bad name).

E.g. on a VM on my machine:

let vdi = find_vdi ~__context ~label in
debug "%s: inserting Sysprep VDI for VM %s" __FUNCTION__ vm_uuid ;
Xapi_vbd.insert ~__context ~vdi ~vbd ;
Thread.delay 5.0 ;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the guest agent can watch for the device to appear rather than adding an arbitrary delay here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the desire; but we also need to be sure it has seen the ISO as otherwise we can't tell it to read from it. So this would require a back channel via xenstore that we watch

Comment on lines +247 to +248
Xapi_vbd.eject ~__context ~vbd ;
Sys.remove iso ;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two duplicated lines could be factored out.

Comment on lines +1173 to +1176
; ( "Remove local ISO SR"
, [Startup.OnThread]
, fun () -> Vm_sysprep.on_startup ~__context
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved after the block below, where the master connection is established if running on a pool member, and dbsync has run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants