diff --git a/doc/scripts/configdoc.xsl b/doc/scripts/configdoc.xsl
index 94a9adb0b..2784c433d 100644
--- a/doc/scripts/configdoc.xsl
+++ b/doc/scripts/configdoc.xsl
@@ -39,7 +39,16 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/misc/config_tools/scenario_config/default_populator.py b/misc/config_tools/scenario_config/default_populator.py
index fd4fef0a6..560f1a885 100755
--- a/misc/config_tools/scenario_config/default_populator.py
+++ b/misc/config_tools/scenario_config/default_populator.py
@@ -11,6 +11,7 @@ import argparse
from scenario_transformer import ScenarioTransformer
from pipeline import PipelineObject, PipelineStage, PipelineEngine
+from schema_slicer import SlicingSchemaByVMTypeStage
class DefaultValuePopulator(ScenarioTransformer):
def add_missing_nodes(self, xsd_element_node, xml_parent_node, new_node_index):
@@ -51,6 +52,7 @@ def main(args):
pipeline.add_stages([
LXMLLoadStage("schema"),
XMLLoadStage("scenario"),
+ SlicingSchemaByVMTypeStage(),
DefaultValuePopulatingStage(),
])
diff --git a/misc/config_tools/scenario_config/schema_slicer.py b/misc/config_tools/scenario_config/schema_slicer.py
new file mode 100755
index 000000000..e0a6a84e6
--- /dev/null
+++ b/misc/config_tools/scenario_config/schema_slicer.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 Intel Corporation.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import os
+import argparse
+from copy import deepcopy
+
+from pipeline import PipelineObject, PipelineStage, PipelineEngine
+
+class SchemaTypeSlicer:
+ xpath_ns = {
+ "xs": "http://www.w3.org/2001/XMLSchema",
+ "acrn": "https://projectacrn.org",
+ }
+
+ @classmethod
+ def get_node(cls, element, xpath):
+ return element.find(xpath, namespaces=cls.xpath_ns)
+
+ @classmethod
+ def get_nodes(cls, element, xpath):
+ return element.findall(xpath, namespaces=cls.xpath_ns)
+
+ def __init__(self, etree):
+ self.etree = etree
+
+ def get_type_definition(self, type_name):
+ type_node = self.get_node(self.etree, f"//xs:complexType[@name='{type_name}']")
+ if type_node is None:
+ type_node = self.get_node(self.etree, f"//xs:simpleType[@name='{type_name}']")
+ return type_node
+
+ def slice_element_list(self, element_list_node, new_nodes):
+ sliced = False
+
+ for element_node in self.get_nodes(element_list_node, "xs:element"):
+ if not self.is_element_needed(element_node):
+ element_list_node.remove(element_node)
+ sliced = True
+ continue
+
+ # For embedded complex type definition, also slice in place. If the sliced type contains no sub-element,
+ # remove the element itself, too.
+ element_type_node = self.get_node(element_node, "xs:complexType")
+ if element_type_node is not None:
+ new_sub_nodes = self.slice(element_type_node, in_place=True)
+ if len(self.get_nodes(element_type_node, ".//xs:element")) > 0:
+ new_nodes.extend(new_sub_nodes)
+ else:
+ element_list_node.remove(element_node)
+ continue
+
+ # For external type definition, create a copy to slice. If the sliced type contains no sub-element, remove
+ # the element itself.
+ element_type_name = element_node.get("type")
+ if element_type_name:
+ element_type_node = self.get_type_definition(element_type_name)
+ if element_type_node is not None:
+ sliced_type_name = self.get_name_of_slice(element_type_name)
+
+ # If a sliced type already exists, do not duplicate the effort
+ type_node = self.get_type_definition(sliced_type_name)
+ if type_node is not None:
+ element_node.set("type", sliced_type_name)
+ sliced = True
+ else:
+ new_sub_nodes = self.slice(element_type_node)
+ if len(new_sub_nodes) == 0:
+ continue
+ elif new_sub_nodes[-1].tag.endswith("simpleType") or len(self.get_nodes(new_sub_nodes[-1], ".//xs:element")) > 0:
+ new_nodes.extend(new_sub_nodes)
+ element_node.set("type", sliced_type_name)
+ sliced = True
+ else:
+ element_list_node.remove(element_node)
+
+ return sliced
+
+ def slice_restriction(self, restriction_node):
+ sliced = False
+
+ for restriction in self.get_nodes(restriction_node, "xs:enumeration"):
+ if not self.is_element_needed(restriction):
+ restriction_node.remove(restriction)
+ sliced = True
+
+ return sliced
+
+ def slice(self, type_node, in_place=False, force_copy=False):
+ new_nodes = []
+ sliced = False
+
+ if in_place:
+ new_type_node = type_node
+ else:
+ new_type_node = deepcopy(type_node)
+ type_name = type_node.get("name")
+ if type_name != None:
+ sliced_type_name = self.get_name_of_slice(type_name)
+ new_type_node.set("name", sliced_type_name)
+
+ element_list_node = self.get_node(new_type_node, "xs:all")
+ if element_list_node is not None:
+ sliced = self.slice_element_list(element_list_node, new_nodes)
+
+ restriction_node = self.get_node(new_type_node, "xs:restriction")
+ if restriction_node is not None:
+ sliced = self.slice_restriction(restriction_node)
+
+ if not in_place and (sliced or force_copy):
+ new_nodes.append(new_type_node)
+ return new_nodes
+
+ def is_element_needed(self, element_node):
+ return True
+
+ def get_name_of_slice(self, name):
+ return f"Sliced{name}"
+
+class SlicingSchemaByVMTypeStage(PipelineStage):
+ uses = {"schema_etree"}
+ provides = {"schema_etree"}
+
+ class VMTypeSlicer(SchemaTypeSlicer):
+ def is_element_needed(self, element_node):
+ annot_node = self.get_node(element_node, "xs:annotation")
+ if annot_node is None:
+ return True
+ applicable_vms = annot_node.get("{https://projectacrn.org}applicable-vms")
+ return applicable_vms is None or applicable_vms.find(self.vm_type_indicator) >= 0
+
+ def get_name_of_slice(self, name):
+ return f"{self.type_prefix}{name}"
+
+ class PreLaunchedTypeSlicer(VMTypeSlicer):
+ vm_type_indicator = "pre-launched"
+ type_prefix = "PreLaunched"
+
+ class ServiceVMTypeSlicer(VMTypeSlicer):
+ vm_type_indicator = "service-vm"
+ type_prefix = "Service"
+
+ class PostLaunchedTypeSlicer(VMTypeSlicer):
+ vm_type_indicator = "post-launched"
+ type_prefix = "PostLaunched"
+
+ def run(self, obj):
+ schema_etree = obj.get("schema_etree")
+
+ vm_type_name = "VMConfigType"
+ vm_type_node = SchemaTypeSlicer.get_node(schema_etree, f"//xs:complexType[@name='{vm_type_name}']")
+ slicers = [
+ self.PreLaunchedTypeSlicer(schema_etree),
+ self.ServiceVMTypeSlicer(schema_etree),
+ self.PostLaunchedTypeSlicer(schema_etree)
+ ]
+
+ for slicer in slicers:
+ new_nodes = slicer.slice(vm_type_node, force_copy=True)
+ for n in new_nodes:
+ schema_etree.getroot().append(n)
+
+ for node in SchemaTypeSlicer.get_nodes(schema_etree, "//xs:complexType[@name='ACRNConfigType']//xs:element[@name='vm']//xs:alternative"):
+ test = node.get("test")
+ if test.find("PRE_LAUNCHED_VM") >= 0:
+ node.set("type", slicers[0].get_name_of_slice(vm_type_name))
+ elif test.find("SERVICE_VM") >= 0:
+ node.set("type", slicers[1].get_name_of_slice(vm_type_name))
+ elif test.find("POST_LAUNCHED_VM") >= 0:
+ node.set("type", slicers[2].get_name_of_slice(vm_type_name))
+
+ obj.set("schema_etree", schema_etree)
+
+class SlicingSchemaByViewStage(PipelineStage):
+ uses = {"schema_etree"}
+ provides = {"schema_etree"}
+
+ class ViewSlicer(SchemaTypeSlicer):
+ def is_element_needed(self, element_node):
+ annot_node = self.get_node(element_node, "xs:annotation")
+ if annot_node is None:
+ return True
+ views = annot_node.get("{https://projectacrn.org}views")
+ return views is None or views.find(self.view_indicator) >= 0
+
+ def get_name_of_slice(self, name):
+ if name.find("ConfigType") >= 0:
+ return name.replace("ConfigType", f"{self.type_prefix}ConfigType")
+ else:
+ return f"{self.type_prefix}{name}"
+
+ class BasicViewSlicer(ViewSlicer):
+ view_indicator = "basic"
+ type_prefix = "Basic"
+
+ class AdvancedViewSlicer(ViewSlicer):
+ view_indicator = "advanced"
+ type_prefix = "Advanced"
+
+ def run(self, obj):
+ schema_etree = obj.get("schema_etree")
+
+ type_nodes = list(filter(lambda x: x.get("name") and x.get("name").endswith("VMConfigType"), SchemaTypeSlicer.get_nodes(schema_etree, "//xs:complexType")))
+ type_nodes.append(SchemaTypeSlicer.get_node(schema_etree, "//xs:complexType[@name = 'HVConfigType']"))
+
+ slicers = [
+ self.BasicViewSlicer(schema_etree),
+ self.AdvancedViewSlicer(schema_etree),
+ ]
+
+ for slicer in slicers:
+ for type_node in type_nodes:
+ new_nodes = slicer.slice(type_node, force_copy=True)
+ for n in new_nodes:
+ schema_etree.getroot().append(n)
+
+ obj.set("schema_etree", schema_etree)
+
+def main(args):
+ from lxml_loader import LXMLLoadStage
+
+ pipeline = PipelineEngine(["schema_path"])
+ pipeline.add_stages([
+ LXMLLoadStage("schema"),
+ SlicingSchemaByVMTypeStage(),
+ SlicingSchemaByViewStage(),
+ ])
+
+ obj = PipelineObject(schema_path = args.schema)
+ pipeline.run(obj)
+ obj.get("schema_etree").write(args.out)
+
+ print(f"Sliced schema written to {args.out}")
+
+if __name__ == "__main__":
+ config_tools_dir = os.path.join(os.path.dirname(__file__), "..")
+ schema_dir = os.path.join(config_tools_dir, "schema")
+
+ parser = argparse.ArgumentParser(description="Slice a given scenario schema by VM types and views")
+ parser.add_argument("out", nargs="?", default=os.path.join(schema_dir, "sliced.xsd"), help="Path where the output is placed")
+ parser.add_argument("--schema", default=os.path.join(schema_dir, "config.xsd"), help="the XML schema that defines the syntax of scenario XMLs")
+ args = parser.parse_args()
+
+ main(args)
diff --git a/misc/config_tools/scenario_config/validator.py b/misc/config_tools/scenario_config/validator.py
index a99b740f6..8c3f5d171 100755
--- a/misc/config_tools/scenario_config/validator.py
+++ b/misc/config_tools/scenario_config/validator.py
@@ -20,6 +20,7 @@ except ImportError:
sys.exit(0)
from pipeline import PipelineObject, PipelineStage, PipelineEngine
+from schema_slicer import SlicingSchemaByVMTypeStage
from default_populator import DefaultValuePopulatingStage
def existing_file_type(parser):
@@ -174,6 +175,7 @@ def main(args):
validator_construction_pipeline.add_stages([
LXMLLoadStage("schema"),
LXMLLoadStage("datachecks"),
+ SlicingSchemaByVMTypeStage(),
ValidatorConstructionStage(),
])
diff --git a/misc/config_tools/schema/config.xsd b/misc/config_tools/schema/config.xsd
index 06f4c7f12..0e6852901 100644
--- a/misc/config_tools/schema/config.xsd
+++ b/misc/config_tools/schema/config.xsd
@@ -450,7 +450,7 @@ board by configuring the hypervisor image features and capabilities such as
setting up the log and the serial port.
-
+
VM configuration includes **scenario-based** VM configuration
information that is used to describe the characteristics and attributes for
@@ -458,6 +458,9 @@ all VMs in a user scenario. It also includes **launch script-based** VM
configuration information, where parameters are passed to the device model
to launch post-launched User VMs.
+
+
+