[libvirt] [PATCH v3 07/12] hyperv: update wmi code generator.
Matthias Bolte
matthias.bolte at googlemail.com
Tue Apr 4 21:18:55 UTC 2017
2017-04-04 1:52 GMT+02:00 Dawid Zamirski <dzamirski at datto.com>:
> This patch updates the code generator that outputs C headers and code
> for WMI classes. It has been updated to handle multiple versions (or
> namespaces) of the same class which were introduced with Hyperv 2012+
> ---
> src/hyperv/hyperv_wmi_generator.py | 385 +++++++++++++++++++++++++++----------
> 1 file changed, 288 insertions(+), 97 deletions(-)
>
> diff --git a/src/hyperv/hyperv_wmi_generator.py b/src/hyperv/hyperv_wmi_generator.py
> index 8c62882..f2c9cde 100755
> --- a/src/hyperv/hyperv_wmi_generator.py
> +++ b/src/hyperv/hyperv_wmi_generator.py
> @@ -24,130 +24,310 @@ import sys
> import os
> import os.path
>
> +separator = "/*" + ("*" * 50) + "*\n"
> +wmi_version_separator = "/"
> +wmi_classes_by_name = {}
> +
> +class WmiClass:
> + """Represents WMI class and provides methods to generate C code.
> +
> + This class holds one or more instances of WmiClassVersion because with the
> + Windows 2012 release, Microsoft introduced "v2" version of Msvm_* family of
> + classes that need different URI for making wsman requests and also have
> + some additional/changed properties (though many of the properies are the
> + same as in "v1". Therefore, this class makes sure that C code is generated
> + for each of them while avoiding name conflics, identifies common members,
> + and defined *_WmiInfo structs holding info about each version so the driver
> + code can make the right choices based on which Hyper-v host it's connected
s/Hyper-v/Hyper-V/
> + to.
> + """
> +
> + def __init__(self, name, versions = []):
> + self.name = name
> + self.versions = versions
> + self.common = None
>
>
> -separator = "/* " + ("* " * 37) + "*\n"
> + def prepare(self):
> + """Prepares the class for code generation
>
> + Makes sure that "versioned" classes are sorted by version, identfies
s/identfies/identifies/
> + common properies and ensures that they are aligned by name and
> + type in each version
> + """
> + # sort vesioned classes by version in case input file did not have them
> + # in order
> + self.versions = sorted(self.versions, key=lambda cls: cls.version)
>
> + # if there's more than one verion make sure first one has name suffixed
> + # because we'll generate "common" memeber and will be the "base" name
> + if len(self.versions) > 1:
> + first = self.versions[0]
> + if first.version == None:
> + first.version = "v1"
> + first.name = "%s_%s" % (first.name, first.version)
>
> -class Class:
> - def __init__(self, name, properties):
> - self.name = name
> - self.properties = properties
> + # finally, identify common members in all versions and make sure they
> + # are in the same order - to ensure C struc member alignment
s/struc/struct/
> + self._align_property_members()
>
>
> - def generate_header(self):
> + def generate_classes_header(self):
> + """Generate C header code and return it as string
> +
> + Declares:
> + <class_name>_Data - used as one of hypervObject->data members
> + <class_name>_TypeInfo - used as wsman XmlSerializerInfo
> + <class_name> - "inherits" hypervObject struct
> + """
> +
> name_upper = self.name.upper()
>
> header = separator
> header += " * %s\n" % self.name
> header += " */\n"
> header += "\n"
> - header += "int hypervGet%sList(hypervPrivate *priv, virBufferPtr query, %s **list);\n" \
> - % (self.name.replace("_", ""), self.name)
> - header += "\n"
> + header += "#define %s_CLASSNAME \\\n" % name_upper
> + header += " \"%s\"\n" % self.name
> header += "\n"
> + header += "#define %s_WQL_SELECT \\\n" % name_upper
> + header += " \"SELECT * FROM %s \"\n" % self.name
> header += "\n"
> + header += "extern hypervWmiClassInfoListPtr %s_WmiInfo;\n\n" % self.name
> +
> + header += self._declare_data_structs()
> + header += self._declare_hypervObject_struct()
>
> return header
>
>
> + def generate_classes_source(self):
> + """Returns a C code string defining wsman data structs
> +
> + Defines:
> + <class_name>_Data structs
> + <class_name>_WmiInfo - list holding metadata (e.g. request URIs) for
> + each known version of WMI class.
> + """
> +
> + source = separator
> + source += " * %s\n" % self.name
> + source += " */\n"
> +
> + for cls in self.versions:
> + source += "SER_START_ITEMS(%s_Data)\n" % cls.name
> +
> + for property in cls.properties:
> + source += property.generate_classes_source(cls.name)
> +
> + source += "SER_END_ITEMS(%s_Data);\n\n" % cls.name
> +
> +
> + source += self._define_WmiInfo_struct()
> + source += "\n\n"
> +
> + return source
> +
> +
> def generate_classes_typedef(self):
> - typedef = "typedef struct _%s_Data %s_Data;\n" % (self.name, self.name)
> - typedef += "typedef struct _%s %s;\n" % (self.name, self.name)
> + """Returns C string for typdefs"""
> +
> + typedef = "typedef struct _%s %s;\n" % (self.name, self.name)
> +
> + if self.common is not None:
> + typedef += "typedef struct _%s_Data %s_Data;\n" % (self.name, self.name)
> +
> + for cls in self.versions:
> + typedef += "typedef struct _%s_Data %s_Data;\n" % (cls.name, cls.name)
>
> return typedef
>
>
> - def generate_classes_header(self):
> - name_upper = self.name.upper()
>
> - header = separator
> - header += " * %s\n" % self.name
> - header += " */\n"
> - header += "\n"
> - header += "#define %s_RESOURCE_URI \\\n" % name_upper
> + def _declare_data_structs(self):
> + """Returns string C code declaring data structs.
>
> - if self.name.startswith("Win32_") or self.name.startswith("CIM_"):
> - header += " \"http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/%s\"\n" % self.name
> - else:
> - header += " \"http://schemas.microsoft.com/wbem/wsman/1/wmi/root/virtualization/%s\"\n" % self.name
> + The *_Data structs are members of hypervObject data union. Each one has
> + corresponding *_TypeInfo that is used for wsman unserialization of
> + response XML into the *_Data structs. If there's a "common" member, it
> + won't have corresponding *_TypeInfo becuase this is a special case only
> + used to provide a common "view" of v1, v2 etc members
> + """
>
> - header += "\n"
> - header += "#define %s_CLASSNAME \\\n" % name_upper
> - header += " \"%s\"\n" % self.name
> - header += "\n"
> - header += "#define %s_WQL_SELECT \\\n" % name_upper
> - header += " \"select * from %s \"\n" % self.name
> - header += "\n"
> - header += "struct _%s_Data {\n" % self.name
> + header = ""
> + if self.common is not None:
> + header += "struct _%s_Data {\n" % self.name
> + for property in self.common:
> + header += property.generate_classes_header()
> + header += "};\n\n"
>
> - for property in self.properties:
> - header += property.generate_classes_header()
> + # Declare actual data struct for each versions
> + for cls in self.versions:
> + header += "#define %s_RESOURCE_URI \\\n" % cls.name.upper()
> + header += " \"%s\"\n" % cls.uri_info.resourceUri
> + header += "\n"
> + header += "struct _%s_Data {\n" % cls.name
> + for property in cls.properties:
> + header += property.generate_classes_header()
> + header += "};\n\n"
> + header += "SER_DECLARE_TYPE(%s_Data);\n" % cls.name
>
> - header += "};\n"
> - header += "\n"
> - header += "SER_DECLARE_TYPE(%s_Data);\n" % self.name
> - header += "\n"
> + return header
> +
> +
> + def _declare_hypervObject_struct(self):
> + """Return string for C code declaring hypervObject instance"""
> +
> + header = "\n/* must match hypervObject */\n"
> header += "struct _%s {\n" % self.name
> - header += " XmlSerializerInfo *serializerInfo;\n"
> - header += " %s_Data *data;\n" % self.name
> + header += " union {\n"
> +
> + # if there's common use it as "common" else first and only version is
> + # the "common" member
> + if self.common is not None:
> + header += " %s_Data *common;\n" % self.name
> + else:
> + header += " %s_Data *common;\n" % self.versions[0].name
> +
> + for cls in self.versions:
> + header += " %s_Data *%s;\n" % (cls.name, cls.version)
> +
> + header += " } data;\n"
> + header += " hypervWmiClassInfoPtr info;\n"
> header += " %s *next;\n" % self.name
> header += "};\n"
> - header += "\n"
> - header += "\n"
> - header += "\n"
> +
> + header += "\n\n\n"
>
> return header
>
>
> - def generate_source(self):
> - name_upper = self.name.upper()
> + def _define_WmiInfo_struct(self):
> + """Return string for C code defining *_WmiInfo struct
>
> - source = separator
> - source += " * %s\n" % self.name
> - source += " */\n"
> - source += "\n"
> - source += "int\n"
> - source += "hypervGet%sList(hypervPrivate *priv, virBufferPtr query, %s **list)\n" \
> - % (self.name.replace("_", ""), self.name)
> - source += "{\n"
> -
> - if self.name.startswith("Win32_") or self.name.startswith("CIM_"):
> - source += " return hypervEnumAndPull(priv, query, ROOT_CIMV2,\n"
> - else:
> - source += " return hypervEnumAndPull(priv, query, ROOT_VIRTUALIZATION,\n"
> + Those structs hold info with meta-data needed to make wsman requests for
> + each version of WMI class
> + """
> +
> + source = "hypervWmiClassInfoListPtr %s_WmiInfo = &(hypervWmiClassInfoList) {\n" % self.name
> + source += " .count = %d,\n" % len(self.versions)
> + source += " .objs = (hypervWmiClassInfoPtr []) {\n"
>
> - source += " %s_Data_TypeInfo,\n" % self.name
> - source += " %s_RESOURCE_URI,\n" % name_upper
> - source += " %s_CLASSNAME,\n" % name_upper
> - source += " (hypervObject **)list);\n"
> - source += "}\n"
> - source += "\n"
> - source += "\n"
> - source += "\n"
> + for cls in self.versions:
> + source += " &(hypervWmiClassInfo) {\n"
> + source += " .name = %s_CLASSNAME,\n" % self.name.upper()
> + if cls.version is not None:
> + source += " .version = \"%s\",\n" % cls.version
> + else:
> + source += " .version = NULL,\n"
> + source += " .rootUri = %s,\n" % cls.uri_info.rootUri
> + source += " .resourceUri = %s_RESOURCE_URI,\n" % cls.name.upper()
> + source += " .serializerInfo = %s_Data_TypeInfo\n" % cls.name
> + source += " },\n"
> +
> + source += " }\n"
> + source += "};\n"
>
> return source
>
>
> - def generate_classes_source(self):
> - name_upper = self.name.upper()
> + def _align_property_members(self):
> + """Identifies common properties in all class versions.
>
> - source = separator
> - source += " * %s\n" % self.name
> - source += " */\n"
> - source += "\n"
> - source += "SER_START_ITEMS(%s_Data)\n" % self.name
> + Makes sure that properties in all versions are ordered with common
> + members first and that they are in the same order. This makes the
> + generated C structs memory aligned and safe to access via the "common"
> + struct that "shares" members with v1, v2 etc.
> + """
>
> - for property in self.properties:
> - source += property.generate_classes_source(self.name)
> + num_classes = len(self.versions)
> + common = {}
> + property_info = {}
>
> - source += "SER_END_ITEMS(%s_Data);\n" % self.name
> - source += "\n"
> - source += "\n"
> - source += "\n"
> + if num_classes < 2:
> + return
> +
> + # count property occurences in all class versions
> + for cls in self.versions:
> + for prop in cls.properties:
> + # consdered same if matches by name AND type
> + key = "%s_%s" % (prop.name, prop.type)
> +
> + if key in property_info:
> + property_info[key][1] += 1
> + else:
> + property_info[key] = [prop, 1]
> +
> + # isolate those that are common for all and keep track of their postions
> + pos = 0
> + for key in property_info:
> + info = property_info[key]
> + # exists in all class versions
> + if info[1] == num_classes:
> + common[info[0].name] = [info[0], pos]
> + pos += 1
> +
> + # alter each versions's property list so that common members are first
> + # and in the same order as in the common dictionary
> + total = len(common)
> + for cls in self.versions:
> + index = 0
> + count = len(cls.properties)
> +
> + while index < count:
> + prop = cls.properties[index]
> +
> + # it's a "common" proptery
s/proptery/property/
> + if prop.name in common:
> + pos = common[prop.name][1]
> +
> + # move to the same position as in "common" dictionary
> + if index != pos:
> + tmp = cls.properties[pos]
> + cls.properties[pos] = prop
> + cls.properties[index] = tmp
> + else:
> + index += 1
> + else:
> + index += 1
> +
> + # finally, get common properties as list sorted by position in dictionary
> + tmp = sorted(common.values(), key=lambda x: x[1])
> + self.common = []
> + for x in tmp:
> + self.common.append(x[0])
> +
> +
> +
> +class ClassUriInfo:
> + """Prepares URI information needed for wsman requests."""
> +
> + def __init__(self, wmi_name, version):
> + self.rootUri = "ROOT_CIMV2"
> + self.resourceUri = None
> + baseUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2"
> +
> + if wmi_name.startswith("Msvm_"):
> + baseUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/virtualization"
> + self.rootUri = "ROOT_VIRTUALIZATION"
> +
> + if version == "v2":
> + baseUri += "/v2"
> + self.rootUri = "ROOT_VIRTUALIZATION_V2"
> +
> + self.resourceUri = "%s/%s" % (baseUri, wmi_name)
> +
> +
> +
> +class WmiClassVersion:
> + """Represents specific version of WMI class."""
> +
> + def __init__(self, name, version, properties, uri_info):
> + self.name = name
> + self.version = version
> + self.properties = properties
> + self.uri_info = uri_info
>
> - return source
>
>
> class Property:
> @@ -155,9 +335,13 @@ class Property:
> "string" : "STR",
> "datetime" : "STR",
> "int8" : "INT8",
> + "sint8" : "INT8",
> "int16" : "INT16",
> + "sint16" : "INT16",
> "int32" : "INT32",
> + "sint32" : "INT32",
> "int64" : "INT64",
> + "sint64" : "INT64",
> "uint8" : "UINT8",
> "uint16" : "UINT16",
> "uint32" : "UINT32",
> @@ -189,8 +373,6 @@ class Property:
> return " SER_NS_%s(%s_RESOURCE_URI, \"%s\", 1),\n" \
> % (Property.typemap[self.type], class_name.upper(), self.name)
>
> -
> -
> def open_and_print(filename):
> if filename.startswith("./"):
> print " GEN " + filename[2:]
> @@ -217,8 +399,15 @@ def parse_class(block):
> assert header_items[0] == "class"
>
> name = header_items[1]
> -
> properties = []
> + version = None
> + wmi_name = name
> + ns_separator = name.find(wmi_version_separator)
> +
> + if ns_separator != -1:
> + version = name[:ns_separator]
> + wmi_name = name[ns_separator + 1:]
> + name = "%s_%s" % (wmi_name, version)
>
> for line in block[1:]:
> # expected format: <type> <name>
> @@ -236,7 +425,13 @@ def parse_class(block):
> properties.append(Property(type=items[0], name=items[1],
> is_array=is_array))
>
> - return Class(name=name, properties=properties)
> + cls = WmiClassVersion(name=name, version=version, properties=properties,
> + uri_info=ClassUriInfo(wmi_name, version))
> +
> + if wmi_name in wmi_classes_by_name:
> + wmi_classes_by_name[wmi_name].versions.append(cls)
> + else:
> + wmi_classes_by_name[wmi_name] = WmiClass(wmi_name, [cls])
>
>
>
> @@ -248,15 +443,13 @@ def main():
> input_filename = os.path.join(os.getcwd(), "hyperv_wmi_generator.input")
> output_dirname = os.getcwd()
>
> - header = open_and_print(os.path.join(output_dirname, "hyperv_wmi.generated.h"))
> - source = open_and_print(os.path.join(output_dirname, "hyperv_wmi.generated.c"))
> +
Unnecessary whitespace addition.
> classes_typedef = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.typedef"))
> classes_header = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.h"))
> classes_source = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.c"))
>
> - # parse input file
> +
Why did you drop this comment?
> number = 0
> - classes_by_name = {}
> block = None
>
> for line in file(input_filename, "rb").readlines():
> @@ -268,7 +461,7 @@ def main():
> line = line.lstrip().rstrip()
>
> if len(line) < 1:
> - continue
> + continue
Unnecessary whitespace change.
--
Matthias Bolte
http://photron.blogspot.com
More information about the libvir-list
mailing list