[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