cloudstack
Use any binary as Cloudstack transformation hook| modified | Tuesday 12 May 2026 |
|---|
Sometimes you need to customize the Libvirt domain XML that Cloudstack uses for virtual machines it launches (for example, you want to pass-through some PCI devices, or add some metadata, or change some other hardcoded values).
In this case you can use a transformation hook script on the virtualization host. It works like this in the nutshell:
Typically, those scripts has been written in Groovy language - Cloudstack supported it since long time ago.
However, this commit silently added “shell” transformer, which in fact can be any executable. As the result, you don’t have to struggle with the Groovy anymore.
Cloudstack will call your script of choice with two command-line arguments:
transform seems to be the single option here)Edit your agent.properties file, and add these statements:
agent.hooks.basedir=/etc/cloudstack/agent/hooks
agent.hooks.libvirt_vm_xml_transformer.shell_script=<your script filename>
Place your transformer script into the /etc/cloudstack/agent/hooks directory. In my case, it was in Python, a script that adds more Hyper-V enlightenments to Windows VMs, and disables HPET (which eats extra CPU on the host for some reason):
1#!/usr/bin/env python3
2import sys
3from xml.etree import ElementTree
4
5def enable_all_hyperv_enlightments(xmlroot):
6 """
7 Finds a features->hyperv tag in the XML.
8 If found, adds more enlightenments to it,
9 and modifies the <clock> tag to remote HPET, and add hypervclock
10 """
11 hv = xmlroot.find('./features/hyperv')
12 if hv is None:
13 return
14
15 # Remove children elements
16 hv.clear()
17 hv.set('mode', 'custom')
18
19 ElementTree.SubElement(hv, 'relaxed').set('state', 'on')
20 ElementTree.SubElement(hv, 'vapic').set('state', 'on')
21 spinlocks = ElementTree.SubElement(hv, 'spinlocks')
22 spinlocks.set('state', 'on')
23 spinlocks.set('retries', '8191')
24 ElementTree.SubElement(hv, 'synic').set('state', 'on')
25 ElementTree.SubElement(hv, 'stimer').set('state', 'on')
26 ElementTree.SubElement(hv, 'frequencies').set('state', 'on')
27 ElementTree.SubElement(hv, 'vpindex').set('state', 'on')
28 ElementTree.SubElement(hv, 'ipi').set('state', 'on')
29
30 # Only appears in Libvirt 10.7.0+ and qemu 7.1+
31 # ElementTree.SubElement(hv, 'xmm_input').set('state', 'on')
32
33 clock = xmlroot.find('./clock')
34 clock.clear()
35 clock.set('offset', 'localtime')
36
37 hvtimer = ElementTree.SubElement(clock, 'timer')
38 hvtimer.set('name', 'hypervclock')
39 hvtimer.set('present', 'yes')
40
41 rtc = ElementTree.SubElement(clock, 'timer')
42 rtc.set('name', 'rtc')
43 rtc.set('tickpolicy', 'catchup')
44
45 pit = ElementTree.SubElement(clock, 'timer')
46 pit.set('name', 'pit')
47 pit.set('tickpolicy', 'delay')
48
49 hpet = ElementTree.SubElement(clock, 'timer')
50 hpet.set('name', 'hpet')
51 hpet.set('present', 'no')
52
53try:
54 method = sys.argv[1]
55 data = sys.argv[2]
56
57 # Only work with 'transform' method
58 if method != 'transform':
59 raise Exception(f"wrong method supplied: {method}")
60except Exception as e:
61 sys.stderr.write(f"Got exception: {e}\n")
62 sys.exit(1)
63
64root = ElementTree.fromstring(data)
65enable_all_hyperv_enlightments(root)
66
67# return the modified XML
68print(ElementTree.tostring(root).decode())