config_tools: merge data in launch XMLs into scenario XMLs

Splitting the definitions of a post-launched VM into two files, namely the
scenario XML and launch XML, introduces duplicated field in both files and
leads to a high probability of having inconsistencies between them (see
issue #7156 as an example). Further more, this split has also adds much
complexity to the configurator which has to either hide some of the items
from user interfaces or synchronize different fields upon changes.

The advantage of the split, however, is not widely adopted. Having a
separate XML capturing the VM definition tweakable in the service VM at
runtime seems to give users more flexibility to redefine a VM without
recompiling the hypervisor. But that is not a common practice in the
industry segment; instead it is preferred to have a static scenario
definition to make sure that all resources are allocated carefully in a
fixed manner in order for better determinism.

As a result, this patch merges the fields in launch XMLs into the schema of
scenario XMLs. Some fields are post-launched VM specific and thus added,
while the others have similar items in scenario XMLs today.

The launch script generator is also updated accordingly to generate launch
scripts from the new scenario XMLs (which now contain the same amount of
information as previous launch XMLs).

Tracked-On: #6690
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
This commit is contained in:
Junjie Mao
2022-02-28 19:53:43 +08:00
committed by acrnsi-robot
parent 678879fd34
commit 0d84ecc4a1
38 changed files with 3113 additions and 3386 deletions

View File

@@ -15,6 +15,9 @@ import copy
def eval_xpath(element, xpath, default_value=None):
return next(iter(element.xpath(xpath)), default_value)
def eval_xpath_all(element, xpath):
return element.xpath(xpath)
class LaunchScript:
script_template_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "launch_script_template.sh")
@@ -53,9 +56,9 @@ class LaunchScript:
current_option = self._options.setdefault(class_code, [])
self._options[class_code] = current_option.append("enable_ptm")
def __init__(self, vm_launch_etree):
def __init__(self, vm_scenario_etree):
self._options = copy.copy(self.passthru_device_options)
if eval_xpath(vm_launch_etree, ".//enable_ptm/text()") == "y":
if eval_xpath(vm_scenario_etree, ".//PTM/text()") == "y":
self._add_option("0x0200", "enable_ptm")
def get_option(self, device_etree):
@@ -67,9 +70,9 @@ class LaunchScript:
passthru_options.extend(v)
return ",".join(passthru_options)
def __init__(self, board_etree, vm_name, vm_launch_etree):
def __init__(self, board_etree, vm_name, vm_scenario_etree):
self._board_etree = board_etree
self._vm_launch_etree = vm_launch_etree
self._vm_scenario_etree = vm_scenario_etree
self._vm_name = vm_name
self._vm_descriptors = {}
@@ -78,7 +81,7 @@ class LaunchScript:
self._deinit_commands = []
self._vbdf_allocator = self.VirtualBDFAllocator()
self._passthru_options = self.PassThruDeviceOptions(vm_launch_etree)
self._passthru_options = self.PassThruDeviceOptions(vm_scenario_etree)
def add_vm_descriptor(self, name, value):
self._vm_descriptors[name] = value
@@ -146,7 +149,7 @@ class LaunchScript:
logging.info(f"Successfully generated launch script {path} for VM '{self._vm_name}'.")
def add_virtual_device(self, kind, vbdf=None, options=""):
if "virtio" in kind and eval_xpath(self._vm_launch_etree, ".//rtos_type/text()", "no") != "no":
if "virtio" in kind and eval_xpath(self._vm_scenario_etree, ".//vm_type/text()") == "RTVM":
self.add_plain_dm_parameter("--virtio_poll 1000000")
if vbdf is None:
@@ -185,9 +188,9 @@ def cpu_id_to_lapic_id(board_etree, vm_name, cpus):
return ret
def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_launch_etree, vm_id):
vm_name = eval_xpath(vm_launch_etree, ".//vm_name/text()", f"ACRN Post-Launched VM")
script = LaunchScript(board_etree, vm_name, vm_launch_etree)
def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_id):
vm_name = eval_xpath(vm_scenario_etree, "./name/text()", f"ACRN Post-Launched VM")
script = LaunchScript(board_etree, vm_name, vm_scenario_etree)
script.add_init_command("probe_modules")
@@ -195,38 +198,29 @@ def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_la
# VM types and guest OSes
###
if eval_xpath(vm_launch_etree, ".//user_vm_type/text()") == "WINDOWS":
if eval_xpath(vm_scenario_etree, ".//os_type/text()") == "Windows OS":
script.add_plain_dm_parameter("--windows")
script.add_vm_descriptor("rtos_type", f"'{eval_xpath(vm_launch_etree, './/rtos_type/text()', 'no')}'")
script.add_vm_descriptor("vm_type", f"'{eval_xpath(vm_scenario_etree, './/vm_type/text()', 'STANDARD_VM')}'")
script.add_vm_descriptor("scheduler", f"'{eval_xpath(hv_scenario_etree, './/SCHEDULER/text()')}'")
###
# CPU and memory resources
###
cpus_in_launch_xml = set(vm_launch_etree.xpath(".//cpu_affinity/pcpu_id[text() != '']/text()"))
cpus_in_scenario_xml = set(vm_scenario_etree.xpath(".//cpu_affinity/pcpu_id[text() != '']/text()"))
if cpus_in_launch_xml:
cpus = cpus_in_scenario_xml & cpus_in_launch_xml
if not cpus:
logging.error(f"CPUs assigned to VM '{vm_name}' in the launch XML are outside of those allowed by the scenario XML.")
else:
cpus = cpus_in_scenario_xml
if not cpus:
logging.error(f"VM '{vm_name}' has no CPU assigned in either the scenario or the launch XML.")
cpus = set(eval_xpath_all(vm_scenario_etree, ".//cpu_affinity/pcpu_id[text() != '']/text()"))
lapic_ids = cpu_id_to_lapic_id(board_etree, vm_name, cpus)
if lapic_ids:
script.add_dynamic_dm_parameter("add_cpus", f"{' '.join([str(x) for x in sorted(lapic_ids)])}")
script.add_plain_dm_parameter(f"-m {eval_xpath(vm_launch_etree, './/mem_size/text()')}M")
script.add_plain_dm_parameter(f"-m {eval_xpath(vm_scenario_etree, './/memory/whole/text()')}M")
if eval_xpath(vm_scenario_etree, "//SSRAM_ENABLED") == "y" and \
eval_xpath(vm_launch_etree, ".//user_vm_type/text()") == "PREEMPT-RT LINUX":
eval_xpath(vm_scenario_etree, ".//vm_type/text()") == "RTVM":
script.add_plain_dm_parameter("--ssram")
###
# Guest BIOS
###
if eval_xpath(vm_launch_etree, ".//vbootloader/text()") == "ovmf":
if eval_xpath(vm_scenario_etree, ".//vbootloader/text()") == "Enable":
script.add_plain_dm_parameter("--ovmf /usr/share/acrn/bios/OVMF.fd")
###
@@ -234,36 +228,36 @@ def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_la
###
# Emulated platform devices
if eval_xpath(vm_launch_etree, ".//user_vm_type/text()") != "PREEMPT-RT LINUX":
if eval_xpath(vm_scenario_etree, ".//vm_type/text()") != "RTVM":
script.add_virtual_device("lpc", vbdf="1:0")
if eval_xpath(vm_launch_etree, ".//vuart0/text()") == "Enable":
if eval_xpath(vm_scenario_etree, ".//vuart0/text()") == "Enable":
script.add_plain_dm_parameter("-l com1,stdio")
# Emulated PCI devices
script.add_virtual_device("hostbridge", vbdf="0:0")
if eval_xpath(vm_scenario_etree, "//IVSHMEM_ENABLED/text()") == "y":
for ivshmem in vm_launch_etree.xpath("//shm_region[text() != '']/text()"):
script.add_virtual_device("ivshmem", options=ivshmem)
for ivshmem in eval_xpath_all(vm_scenario_etree, "//IVSHMEM_REGION[PROVIDED_BY = 'Device model' and .//VM_NAME = 'vm_name']"):
script.add_virtual_device("ivshmem", options=f"dm:/{ivshmem.find('NAME').text},{ivshmem.find('IVSHMEM_SIZE').text}")
if eval_xpath(vm_launch_etree, ".//console_vuart/text()") == "Enable":
if eval_xpath(vm_scenario_etree, ".//console_vuart/text()") == "PCI":
script.add_virtual_device("uart", options="vuart_idx:0")
for comm_vuart in vm_launch_etree.xpath(".//communication_vuart/@id"):
script.add_virtual_device("uart", options=f"vuart_idx:{comm_vuart}")
for idx, conn in enumerate(eval_xpath_all(hv_scenario_etree, f".//vuart_connection[endpoint/vm_name = '{vm_name}']"), start=1):
if eval_xpath(conn, "./type") == "pci":
script.add_virtual_device("uart", options="vuart_idx:{idx}")
# Mediated PCI devices, including virtio
for usb_xhci in vm_launch_etree.xpath(".//usb_xhci[text() != '']/text()"):
for usb_xhci in eval_xpath_all(vm_scenario_etree, ".//usb_xhci[text() != '']/text()"):
script.add_virtual_device("xhci", options=usb_xhci)
for virtio_input in vm_launch_etree.xpath(".//virtio_devices/input[text() != '']/text()"):
for virtio_input in eval_xpath_all(vm_scenario_etree, ".//virtio_devices/input[text() != '']/text()"):
script.add_virtual_device("virtio-input", options=virtio_input)
for virtio_console in vm_launch_etree.xpath(".//virtio_devices/console[text() != '']/text()"):
for virtio_console in eval_xpath_all(vm_scenario_etree, ".//virtio_devices/console[text() != '']/text()"):
script.add_virtual_device("virtio-console", options=virtio_console)
for virtio_network in vm_launch_etree.xpath(".//virtio_devices/network[text() != '']/text()"):
for virtio_network in eval_xpath_all(vm_scenario_etree, ".//virtio_devices/network[text() != '']/text()"):
params = virtio_network.split(",", maxsplit=1)
tap_conf = f"tap={params[0]}"
params = [tap_conf] + params[1:]
@@ -271,7 +265,7 @@ def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_la
params.append(f"mac_seed=${{mac:0:17}}-{vm_name}")
script.add_virtual_device("virtio-net", options=",".join(params))
for virtio_block in vm_launch_etree.xpath(".//virtio_devices/block[text() != '']/text()"):
for virtio_block in eval_xpath_all(vm_scenario_etree, ".//virtio_devices/block[text() != '']/text()"):
params = virtio_block.split(":", maxsplit=1)
if len(params) == 1:
script.add_virtual_device("virtio-blk", options=virtio_block)
@@ -285,22 +279,28 @@ def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_la
# Passthrough PCI devices
bdf_regex = re.compile("([0-9a-f]{2}):([0-1][0-9a-f]).([0-7])")
for passthru_device in vm_launch_etree.xpath(".//passthrough_devices/*/text()"):
for passthru_device in eval_xpath_all(vm_scenario_etree, ".//pci_devs/*/text()"):
m = bdf_regex.match(passthru_device)
if not m:
continue
script.add_passthru_device(int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16))
for sriov_gpu_device in vm_launch_etree.xpath(".//sriov/gpu/text()"):
m = bdf_regex.match(sriov_gpu_device)
if not m:
continue
script.add_passthru_device(int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16), options="igd-vf")
bus = int(m.group(1), 16)
dev = int(m.group(2), 16)
func = int(m.group(3), 16)
device_node = eval_xpath(board_etree, f"//bus[@type='pci' and @address='{hex(bus)}']/device[@address='hex((dev << 16) | func)']")
if device_node and \
eval_xpath(device_node, "class/text()") == "0x030000" and \
eval_xpath(device_node, "resource[@type='memory'") is None:
script.add_passthru_device(bus, dev, func, options="igd-vf")
else:
script.add_passthru_device(bus, dev, func)
###
# Miscellaneous
###
script.add_dynamic_dm_parameter("add_rtvm_options")
if eval_xpath(vm_scenario_etree, ".//vm_type/text()") == "RTVM":
script.add_plain_dm_parameter("--rtvm")
if eval_xpath(vm_scenario_etree, ".//lapic_passthrough/text()") == "y":
script.add_plain_dm_parameter("--lapic_pt")
script.add_dynamic_dm_parameter("add_logger_settings", "console=4 kmsg=3 disk=5")
###
@@ -310,14 +310,13 @@ def generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_la
return script
def main(board_xml, scenario_xml, launch_xml, user_vm_id, out_dir):
def main(board_xml, scenario_xml, user_vm_id, out_dir):
board_etree = etree.parse(board_xml)
scenario_etree = etree.parse(scenario_xml)
launch_etree = etree.parse(launch_xml)
service_vm_id = eval_xpath(scenario_etree, "//vm[load_order='SERVICE_VM']/@id")
service_vm_id = eval_xpath(scenario_etree, "//vm[load_order = 'SERVICE_VM']/@id")
hv_scenario_etree = eval_xpath(scenario_etree, "//hv")
post_vms = scenario_etree.xpath("//vm[starts-with(load_order, 'POST_')]")
post_vms = eval_xpath_all(scenario_etree, "//vm[load_order = 'POST_LAUNCHED_VM']")
if service_vm_id is None and len(post_vms) > 0:
logging.error("The scenario does not define a service VM so no launch scripts will be generated for the post-launched VMs in the scenario.")
return 1
@@ -334,23 +333,17 @@ def main(board_xml, scenario_xml, launch_xml, user_vm_id, out_dir):
return 1
if user_vm_id == 0:
post_vm_ids = [int(vm_scenario_etree.get("id")) - service_vm_id for vm_scenario_etree in post_vms]
post_vm_ids = [int(vm_scenario_etree.get("id")) for vm_scenario_etree in post_vms]
else:
post_vm_ids = [user_vm_id]
post_vm_ids = [user_vm_id + service_vm_id]
for post_vm_id in post_vm_ids:
vm_scenario_etree = eval_xpath(scenario_etree, f"//vm[@id = {service_vm_id + post_vm_id}]")
vm_launch_etree = eval_xpath(launch_etree, f"//user_vm[@id='{post_vm_id}']")
if vm_scenario_etree is None:
logging.warning(f"Post-launched VM {post_vm_id} is not specified in the scenario XML, so no launch script will be generated.")
for post_vm in post_vms:
post_vm_id = int(post_vm.get("id"))
if post_vm_id not in post_vm_ids:
continue
if vm_launch_etree is None:
logging.warning(f"Post-launched VM {post_vm_id} is not specified in the launch XML, so no launch script will be generated.")
continue
script = generate_for_one_vm(board_etree, hv_scenario_etree, vm_scenario_etree, vm_launch_etree, post_vm_id)
script.write_to_file(os.path.join(out_dir, f"launch_user_vm_id{post_vm_id}.sh"))
script = generate_for_one_vm(board_etree, hv_scenario_etree, post_vm, post_vm_id)
script.write_to_file(os.path.join(out_dir, f"launch_user_vm_id{post_vm_id - service_vm_id}.sh"))
return 0
@@ -358,11 +351,11 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--board", help="the XML file summarizing characteristics of the target board")
parser.add_argument("--scenario", help="the XML file specifying the scenario to be set up")
parser.add_argument("--launch", help="the XML file specifying the parameters of post-launched VMs")
parser.add_argument("--launch", default=None, help="(obsoleted. DO NOT USE)")
parser.add_argument("--user_vmid", type=int, default=0, help="the post-launched VM ID (as is specified in the launch XML) whose launch script is to be generated, or 0 if all post-launched VMs shall be processed")
parser.add_argument("--out", default="output", help="path to the directory where generated scripts are placed")
args = parser.parse_args()
logging.basicConfig(level="INFO")
sys.exit(main(args.board, args.scenario, args.launch, args.user_vmid, args.out))
sys.exit(main(args.board, args.scenario, args.user_vmid, args.out))