public inbox for kvm@vger.kernel.org
 help / color / mirror / Atom feed
From: Dor Laor <dlaor@redhat.com>
To: Michael Goldish <mgoldish@redhat.com>
Cc: Lucas Meneghel Rodrigues <lmr@redhat.com>,
	kvm@vger.kernel.org, nsprei@redhat.com, mtosatti@redhat.com,
	autotest@test.kernel.org
Subject: Re: [PATCH] [RFC] KVM test: Control files automatic generation to save memory
Date: Sun, 14 Feb 2010 22:54:24 +0200	[thread overview]
Message-ID: <4B786300.7040408@redhat.com> (raw)
In-Reply-To: <1176411060.1596701266167276088.JavaMail.root@zmail05.collab.prod.int.phx2.redhat.com>

On 02/14/2010 07:07 PM, Michael Goldish wrote:
>
> ----- "Lucas Meneghel Rodrigues"<lmr@redhat.com>  wrote:
>
>> As our configuration system generates a list of dicts
>> with test parameters, and that list might be potentially
>> *very* large, keeping all this information in memory might
>> be a problem for smaller virtualization hosts due to
>> the memory pressure created. Tests made on my 4GB laptop
>> show that most of the memory is being used during a
>> typical kvm autotest session.
>>
>> So, instead of keeping all this information in memory,
>> let's take a different approach and unfold all the
>> tests generated by the config system and generate a
>> control file:
>>
>> job.run_test('kvm', params={param1, param2, ...}, tag='foo', ...)
>> job.run_test('kvm', params={param1, param2, ...}, tag='bar', ...)
>>
>> By dumping all the dicts that were before in the memory to
>> a control file, the memory usage of a typical kvm autotest
>> session is drastically reduced making it easier to run in smaller
>> virt hosts.
>>
>> The advantages of taking this new approach are:
>>   * You can see what tests are going to run and the dependencies
>>     between them by looking at the generated control file
>>   * The control file is all ready to use, you can for example
>>     paste it on the web interface and profit
>>   * As mentioned, a lot less memory consumption, avoiding
>>     memory pressure on virtualization hosts.
>>
>> This is a crude 1st pass at implementing this approach, so please
>> provide comments.
>>
>> Signed-off-by: Lucas Meneghel Rodrigues<lmr@redhat.com>
>> ---
>
> Interesting idea!
>
> - Personally I don't like the renaming of kvm_config.py to
> generate_control.py, and prefer to keep them separate, so that
> generate_control.py has the create_control() function and
> kvm_config.py has everything else.  It's just a matter of naming;
> kvm_config.py deals mostly with config files, not with control files,
> and it can be used for other purposes than generating control files.
>
> - I wonder why so much memory is used by the test list.  Our daily
> test sets aren't very big, so although the parser should use a huge
> amount of memory while parsing, nearly all of that memory should be
> freed by the time the parser is done, because the final 'only'
> statement reduces the number of tests to a small fraction of the total
> number in a full set.  What test set did you try with that 4 GB
> machine, and how much memory was used by the test list?  If a
> ridiculous amount of memory was used, this might indicate a bug in
> kvm_config.py (maybe it keeps references to deleted tests, forcing
> them to stay in memory).

I agree, it's worth getting to the bottom of it - I wonder how many 
objects are created on kvm unstable set. It should be a huge number.
Besides that, one can always call the python garbage collection 
interface in order to free unreferenced memory immediately.

>
> - I don't think this approach will work for control.parallel, because
> the tests have to be assigned dynamically to available queues, and
> AFAIK this can't be done by a simple static control file.
>
> - Whether or not this is a good idea probably depends on the users.
> On one hand, users will be required to run generate_control.py before
> autotest.py, and the generated control files will be very big and
> ugly; on the other hand, maybe they won't care.
>
> I probably haven't given this enough thought so I might have missed a
> few things.
>
>
>>   client/tests/kvm/control             |   64 ----
>>   client/tests/kvm/generate_control.py |  586
>> ++++++++++++++++++++++++++++++++++
>>   client/tests/kvm/kvm_config.py       |  524
>> ------------------------------
>>   3 files changed, 586 insertions(+), 588 deletions(-)
>>   delete mode 100644 client/tests/kvm/control
>>   create mode 100755 client/tests/kvm/generate_control.py
>>   delete mode 100755 client/tests/kvm/kvm_config.py
>>
>> diff --git a/client/tests/kvm/control b/client/tests/kvm/control
>> deleted file mode 100644
>> index 163286e..0000000
>> --- a/client/tests/kvm/control
>> +++ /dev/null
>> @@ -1,64 +0,0 @@
>> -AUTHOR = """
>> -uril@redhat.com (Uri Lublin)
>> -drusso@redhat.com (Dror Russo)
>> -mgoldish@redhat.com (Michael Goldish)
>> -dhuff@redhat.com (David Huff)
>> -aeromenk@redhat.com (Alexey Eromenko)
>> -mburns@redhat.com (Mike Burns)
>> -"""
>> -TIME = 'MEDIUM'
>> -NAME = 'KVM test'
>> -TEST_TYPE = 'client'
>> -TEST_CLASS = 'Virtualization'
>> -TEST_CATEGORY = 'Functional'
>> -
>> -DOC = """
>> -Executes the KVM test framework on a given host. This module is
>> separated in
>> -minor functions, that execute different tests for doing Quality
>> Assurance on
>> -KVM (both kernelspace and userspace) code.
>> -
>> -For online docs, please refer to
>> http://www.linux-kvm.org/page/KVM-Autotest
>> -"""
>> -
>> -import sys, os, logging
>> -# Add the KVM tests dir to the python path
>> -kvm_test_dir = os.path.join(os.environ['AUTODIR'],'tests/kvm')
>> -sys.path.append(kvm_test_dir)
>> -# Now we can import modules inside the KVM tests dir
>> -import kvm_utils, kvm_config
>> -
>> -# set English environment (command output might be localized, need to
>> be safe)
>> -os.environ['LANG'] = 'en_US.UTF-8'
>> -
>> -build_cfg_path = os.path.join(kvm_test_dir, "build.cfg")
>> -build_cfg = kvm_config.config(build_cfg_path)
>> -# Make any desired changes to the build configuration here. For
>> example:
>> -#build_cfg.parse_string("""
>> -#release_tag = 84
>> -#""")
>> -if not kvm_utils.run_tests(build_cfg.get_list(), job):
>> -    logging.error("KVM build step failed, exiting.")
>> -    sys.exit(1)
>> -
>> -tests_cfg_path = os.path.join(kvm_test_dir, "tests.cfg")
>> -tests_cfg = kvm_config.config(tests_cfg_path)
>> -# Make any desired changes to the test configuration here. For
>> example:
>> -#tests_cfg.parse_string("""
>> -#display = sdl
>> -#install|setup: timeout_multiplier = 3
>> -#""")
>> -
>> -pools_cfg_path = os.path.join(kvm_test_dir, "address_pools.cfg")
>> -tests_cfg.parse_file(pools_cfg_path)
>> -hostname = os.uname()[1].split(".")[0]
>> -if tests_cfg.filter("^" + hostname):
>> -    tests_cfg.parse_string("only ^%s" % hostname)
>> -else:
>> -    tests_cfg.parse_string("only ^default_host")
>> -
>> -# Run the tests
>> -kvm_utils.run_tests(tests_cfg.get_list(), job)
>> -
>> -# Generate a nice HTML report inside the job's results dir
>> -kvm_utils.create_report(kvm_test_dir, job.resultdir)
>> -
>> diff --git a/client/tests/kvm/generate_control.py
>> b/client/tests/kvm/generate_control.py
>> new file mode 100755
>> index 0000000..c64dc52
>> --- /dev/null
>> +++ b/client/tests/kvm/generate_control.py
>> @@ -0,0 +1,586 @@
>> +#!/usr/bin/python
>> +"""
>> +KVM configuration file utility functions.
>> +
>> +@copyright: Red Hat 2008-2009
>> +"""
>> +
>> +import logging, re, os, sys, StringIO, optparse
>> +import common
>> +import kvm_utils
>> +from autotest_lib.client.common_lib import error
>> +from autotest_lib.client.common_lib import logging_config,
>> logging_manager
>> +
>> +
>> +class config:
>> +    """
>> +    Parse an input file or string that follows the KVM Test Config
>> File format
>> +    and generate a list of dicts that will be later used as
>> configuration
>> +    parameters by the the KVM tests.
>> +
>> +    @see:
>> http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
>> +    """
>> +
>> +    def __init__(self, filename=None, debug=False):
>> +        """
>> +        Initialize the list and optionally parse filename.
>> +
>> +        @param filename: Path of the file that will be taken.
>> +        @param debug: Whether to turn debugging output.
>> +        """
>> +        self.list = [{"name": "", "shortname": "", "depend": []}]
>> +        self.debug = debug
>> +        self.filename = filename
>> +        if filename:
>> +            self.parse_file(filename)
>> +
>> +
>> +    def parse_file(self, filename):
>> +        """
>> +        Parse filename, return the resulting list and store it in
>> .list. If
>> +        filename does not exist, raise an exception.
>> +
>> +        @param filename: Path of the configuration file.
>> +        """
>> +        if not os.path.exists(filename):
>> +            raise Exception, "File %s not found" % filename
>> +        self.filename = filename
>> +        file = open(filename, "r")
>> +        self.list = self.parse(file, self.list)
>> +        file.close()
>> +        return self.list
>> +
>> +
>> +    def parse_string(self, str):
>> +        """
>> +        Parse a string, return the resulting list and store it in
>> .list.
>> +
>> +        @param str: String that will be parsed.
>> +        """
>> +        file = StringIO.StringIO(str)
>> +        self.list = self.parse(file, self.list)
>> +        file.close()
>> +        return self.list
>> +
>> +
>> +    def get_list(self):
>> +        """
>> +        Return the list of dictionaries. This should probably be
>> called after
>> +        parsing something.
>> +        """
>> +        return self.list
>> +
>> +
>> +    def match(self, filter, dict):
>> +        """
>> +        Return True if dict matches filter.
>> +
>> +        @param filter: A regular expression that defines the filter.
>> +        @param dict: Dictionary that will be inspected.
>> +        """
>> +        filter = re.compile(r"(\.|^)(%s)(\.|$)" % filter)
>> +        return bool(filter.search(dict["name"]))
>> +
>> +
>> +    def filter(self, filter, list=None):
>> +        """
>> +        Filter a list of dicts.
>> +
>> +        @param filter: A regular expression that will be used as a
>> filter.
>> +        @param list: A list of dictionaries that will be filtered.
>> +        """
>> +        if list is None:
>> +            list = self.list
>> +        return [dict for dict in list if self.match(filter, dict)]
>> +
>> +
>> +    def split_and_strip(self, str, sep="="):
>> +        """
>> +        Split str and strip quotes from the resulting parts.
>> +
>> +        @param str: String that will be processed
>> +        @param sep: Separator that will be used to split the string
>> +        """
>> +        temp = str.split(sep, 1)
>> +        for i in range(len(temp)):
>> +            temp[i] = temp[i].strip()
>> +            if re.findall("^\".*\"$", temp[i]):
>> +                temp[i] = temp[i].strip("\"")
>> +            elif re.findall("^\'.*\'$", temp[i]):
>> +                temp[i] = temp[i].strip("\'")
>> +        return temp
>> +
>> +
>> +    def get_next_line(self, file):
>> +        """
>> +        Get the next non-empty, non-comment line in a file like
>> object.
>> +
>> +        @param file: File like object
>> +        @return: If no line is available, return None.
>> +        """
>> +        while True:
>> +            line = file.readline()
>> +            if line == "": return None
>> +            stripped_line = line.strip()
>> +            if len(stripped_line)>  0 \
>> +                    and not stripped_line.startswith('#') \
>> +                    and not stripped_line.startswith('//'):
>> +                return line
>> +
>> +
>> +    def get_next_line_indent(self, file):
>> +        """
>> +        Return the indent level of the next non-empty, non-comment
>> line in file.
>> +
>> +        @param file: File like object.
>> +        @return: If no line is available, return -1.
>> +        """
>> +        pos = file.tell()
>> +        line = self.get_next_line(file)
>> +        if not line:
>> +            file.seek(pos)
>> +            return -1
>> +        line = line.expandtabs()
>> +        indent = 0
>> +        while line[indent] == ' ':
>> +            indent += 1
>> +        file.seek(pos)
>> +        return indent
>> +
>> +
>> +    def add_name(self, str, name, append=False):
>> +        """
>> +        Add name to str with a separator dot and return the result.
>> +
>> +        @param str: String that will be processed
>> +        @param name: name that will be appended to the string.
>> +        @return: If append is True, append name to str.
>> +                Otherwise, pre-pend name to str.
>> +        """
>> +        if str == "":
>> +            return name
>> +        # Append?
>> +        elif append:
>> +            return str + "." + name
>> +        # Prepend?
>> +        else:
>> +            return name + "." + str
>> +
>> +
>> +    def parse_variants(self, file, list, subvariants=False,
>> prev_indent=-1):
>> +        """
>> +        Read and parse lines from file like object until a line with
>> an indent
>> +        level lower than or equal to prev_indent is encountered.
>> +
>> +        @brief: Parse a 'variants' or 'subvariants' block from a
>> file-like
>> +        object.
>> +        @param file: File-like object that will be parsed
>> +        @param list: List of dicts to operate on
>> +        @param subvariants: If True, parse in 'subvariants' mode;
>> +        otherwise parse in 'variants' mode
>> +        @param prev_indent: The indent level of the "parent" block
>> +        @return: The resulting list of dicts.
>> +        """
>> +        new_list = []
>> +
>> +        while True:
>> +            indent = self.get_next_line_indent(file)
>> +            if indent<= prev_indent:
>> +                break
>> +            indented_line = self.get_next_line(file).rstrip()
>> +            line = indented_line.strip()
>> +
>> +            # Get name and dependencies
>> +            temp = line.strip("- ").split(":")
>> +            name = temp[0]
>> +            if len(temp) == 1:
>> +                dep_list = []
>> +            else:
>> +                dep_list = temp[1].split()
>> +
>> +            # See if name should be added to the 'shortname' field
>> +            add_to_shortname = True
>> +            if name.startswith("@"):
>> +                name = name.strip("@")
>> +                add_to_shortname = False
>> +
>> +            # Make a deep copy of list
>> +            temp_list = []
>> +            for dict in list:
>> +                new_dict = dict.copy()
>> +                new_dict["depend"] = dict["depend"][:]
>> +                temp_list.append(new_dict)
>> +
>> +            if subvariants:
>> +                # If we're parsing 'subvariants', first modify the
>> list
>> +                self.__modify_list_subvariants(temp_list, name,
>> dep_list,
>> +                                               add_to_shortname)
>> +                temp_list = self.parse(file, temp_list,
>> +                        restricted=True, prev_indent=indent)
>> +            else:
>> +                # If we're parsing 'variants', parse before modifying
>> the list
>> +                if self.debug:
>> +                    self.__debug_print(indented_line,
>> +                                       "Entering variant '%s' "
>> +                                       "(variant inherits %d dicts)"
>> %
>> +                                       (name, len(list)))
>> +                temp_list = self.parse(file, temp_list,
>> +                        restricted=False, prev_indent=indent)
>> +                self.__modify_list_variants(temp_list, name,
>> dep_list,
>> +                                            add_to_shortname)
>> +
>> +            new_list += temp_list
>> +
>> +        return new_list
>> +
>> +
>> +    def parse(self, file, list, restricted=False, prev_indent=-1):
>> +        """
>> +        Read and parse lines from file until a line with an indent
>> level lower
>> +        than or equal to prev_indent is encountered.
>> +
>> +        @brief: Parse a file-like object.
>> +        @param file: A file-like object
>> +        @param list: A list of dicts to operate on (list is modified
>> in
>> +        place and should not be used after the call)
>> +        @param restricted: if True, operate in restricted mode
>> +        (prohibit 'variants')
>> +        @param prev_indent: the indent level of the "parent" block
>> +        @return: Return the resulting list of dicts.
>> +        @note: List is destroyed and should not be used after the
>> call.
>> +        Only the returned list should be used.
>> +        """
>> +        while True:
>> +            indent = self.get_next_line_indent(file)
>> +            if indent<= prev_indent:
>> +                break
>> +            indented_line = self.get_next_line(file).rstrip()
>> +            line = indented_line.strip()
>> +            words = line.split()
>> +
>> +            len_list = len(list)
>> +
>> +            # Look for a known operator in the line
>> +            operators = ["?+=", "?<=", "?=", "+=", "<=", "="]
>> +            op_found = None
>> +            op_pos = len(line)
>> +            for op in operators:
>> +                pos = line.find(op)
>> +                if pos>= 0 and pos<  op_pos:
>> +                    op_found = op
>> +                    op_pos = pos
>> +
>> +            # Found an operator?
>> +            if op_found:
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Parsing operator (%d dicts in
>> current "
>> +                                       "context)" % len_list)
>> +                (left, value) = self.split_and_strip(line, op_found)
>> +                filters_and_key = self.split_and_strip(left, ":")
>> +                filters = filters_and_key[:-1]
>> +                key = filters_and_key[-1]
>> +                filtered_list = list
>> +                for filter in filters:
>> +                    filtered_list = self.filter(filter,
>> filtered_list)
>> +                # Apply the operation to the filtered list
>> +                if op_found == "=":
>> +                    for dict in filtered_list:
>> +                        dict[key] = value
>> +                elif op_found == "+=":
>> +                    for dict in filtered_list:
>> +                        dict[key] = dict.get(key, "") + value
>> +                elif op_found == "<=":
>> +                    for dict in filtered_list:
>> +                        dict[key] = value + dict.get(key, "")
>> +                elif op_found.startswith("?"):
>> +                    exp = re.compile("^(%s)$" % key)
>> +                    if op_found == "?=":
>> +                        for dict in filtered_list:
>> +                            for key in dict.keys():
>> +                                if exp.match(key):
>> +                                    dict[key] = value
>> +                    elif op_found == "?+=":
>> +                        for dict in filtered_list:
>> +                            for key in dict.keys():
>> +                                if exp.match(key):
>> +                                    dict[key] = dict.get(key, "") +
>> value
>> +                    elif op_found == "?<=":
>> +                        for dict in filtered_list:
>> +                            for key in dict.keys():
>> +                                if exp.match(key):
>> +                                    dict[key] = value + dict.get(key,
>> "")
>> +
>> +            # Parse 'no' and 'only' statements
>> +            elif words[0] == "no" or words[0] == "only":
>> +                if len(words)<= 1:
>> +                    continue
>> +                filters = words[1:]
>> +                filtered_list = []
>> +                if words[0] == "no":
>> +                    for dict in list:
>> +                        for filter in filters:
>> +                            if self.match(filter, dict):
>> +                                break
>> +                        else:
>> +                            filtered_list.append(dict)
>> +                if words[0] == "only":
>> +                    for dict in list:
>> +                        for filter in filters:
>> +                            if self.match(filter, dict):
>> +                                filtered_list.append(dict)
>> +                                break
>> +                list = filtered_list
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Parsing no/only (%d dicts in
>> current "
>> +                                       "context, %d remain)" %
>> +                                       (len_list, len(list)))
>> +
>> +            # Parse 'variants'
>> +            elif line == "variants:":
>> +                # 'variants' not allowed in restricted mode
>> +                # (inside an exception or inside subvariants)
>> +                if restricted:
>> +                    e_msg = "Using variants in this context is not
>> allowed"
>> +                    raise error.AutotestError(e_msg)
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Entering variants block (%d
>> dicts in "
>> +                                       "current context)" %
>> len_list)
>> +                list = self.parse_variants(file, list,
>> subvariants=False,
>> +                                           prev_indent=indent)
>> +
>> +            # Parse 'subvariants' (the block is parsed for each dict
>> +            # separately)
>> +            elif line == "subvariants:":
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Entering subvariants block
>> (%d dicts in "
>> +                                       "current context)" %
>> len_list)
>> +                new_list = []
>> +                # Remember current file position
>> +                pos = file.tell()
>> +                # Read the lines in any case
>> +                self.parse_variants(file, [], subvariants=True,
>> +                                    prev_indent=indent)
>> +                # Iterate over the list...
>> +                for index in range(len(list)):
>> +                    # Revert to initial file position in this
>> 'subvariants'
>> +                    # block
>> +                    file.seek(pos)
>> +                    # Everything inside 'subvariants' should be
>> parsed in
>> +                    # restricted mode
>> +                    new_list += self.parse_variants(file,
>> list[index:index+1],
>> +
>> subvariants=True,
>> +
>> prev_indent=indent)
>> +                list = new_list
>> +
>> +            # Parse 'include' statements
>> +            elif words[0] == "include":
>> +                if len(words)<= 1:
>> +                    continue
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Entering file %s" %
>> words[1])
>> +                if self.filename:
>> +                    filename =
>> os.path.join(os.path.dirname(self.filename),
>> +                                            words[1])
>> +                    if os.path.exists(filename):
>> +                        new_file = open(filename, "r")
>> +                        list = self.parse(new_file, list,
>> restricted)
>> +                        new_file.close()
>> +                        if self.debug and not restricted:
>> +                            self.__debug_print("", "Leaving file %s"
>> % words[1])
>> +                    else:
>> +                        logging.warning("Cannot include %s -- file
>> not found",
>> +                                        filename)
>> +                else:
>> +                    logging.warning("Cannot include %s because no
>> file is "
>> +                                    "currently open", words[1])
>> +
>> +            # Parse multi-line exceptions
>> +            # (the block is parsed for each dict separately)
>> +            elif line.endswith(":"):
>> +                if self.debug and not restricted:
>> +                    self.__debug_print(indented_line,
>> +                                       "Entering multi-line exception
>> block "
>> +                                       "(%d dicts in current context
>> outside "
>> +                                       "exception)" % len_list)
>> +                line = line.strip(":")
>> +                new_list = []
>> +                # Remember current file position
>> +                pos = file.tell()
>> +                # Read the lines in any case
>> +                self.parse(file, [], restricted=True,
>> prev_indent=indent)
>> +                # Iterate over the list...
>> +                for index in range(len(list)):
>> +                    if self.match(line, list[index]):
>> +                        # Revert to initial file position in this
>> +                        # exception block
>> +                        file.seek(pos)
>> +                        # Everything inside an exception should be
>> parsed in
>> +                        # restricted mode
>> +                        new_list += self.parse(file,
>> list[index:index+1],
>> +                                               restricted=True,
>> +                                               prev_indent=indent)
>> +                    else:
>> +                        new_list += list[index:index+1]
>> +                list = new_list
>> +
>> +        return list
>> +
>> +
>> +    def __debug_print(self, str1, str2=""):
>> +        """
>> +        Nicely print two strings and an arrow.
>> +
>> +        @param str1: First string
>> +        @param str2: Second string
>> +        """
>> +        if str2:
>> +            str = "%-50s --->  %s" % (str1, str2)
>> +        else:
>> +            str = str1
>> +        logging.debug(str)
>> +
>> +
>> +    def __modify_list_variants(self, list, name, dep_list,
>> add_to_shortname):
>> +        """
>> +        Make some modifications to list, as part of parsing a
>> 'variants' block.
>> +
>> +        @param list: List to be processed
>> +        @param name: Name to be prepended to the dictionary's 'name'
>> key
>> +        @param dep_list: List of dependencies to be added to the
>> dictionary's
>> +                'depend' key
>> +        @param add_to_shortname: Boolean indicating whether name
>> should be
>> +                prepended to the dictionary's 'shortname' key as
>> well
>> +        """
>> +        for dict in list:
>> +            # Prepend name to the dict's 'name' field
>> +            dict["name"] = self.add_name(dict["name"], name)
>> +            # Prepend name to the dict's 'shortname' field
>> +            if add_to_shortname:
>> +                dict["shortname"] = self.add_name(dict["shortname"],
>> name)
>> +            # Prepend name to each of the dict's dependencies
>> +            for i in range(len(dict["depend"])):
>> +                dict["depend"][i] = self.add_name(dict["depend"][i],
>> name)
>> +            # Add new dependencies
>> +            dict["depend"] += dep_list
>> +
>> +
>> +    def __modify_list_subvariants(self, list, name, dep_list,
>> add_to_shortname):
>> +        """
>> +        Make some modifications to list, as part of parsing a
>> 'subvariants'
>> +        block.
>> +
>> +        @param list: List to be processed
>> +        @param name: Name to be appended to the dictionary's 'name'
>> key
>> +        @param dep_list: List of dependencies to be added to the
>> dictionary's
>> +                'depend' key
>> +        @param add_to_shortname: Boolean indicating whether name
>> should be
>> +                appended to the dictionary's 'shortname' as well
>> +        """
>> +        for dict in list:
>> +            # Add new dependencies
>> +            for dep in dep_list:
>> +                dep_name = self.add_name(dict["name"], dep,
>> append=True)
>> +                dict["depend"].append(dep_name)
>> +            # Append name to the dict's 'name' field
>> +            dict["name"] = self.add_name(dict["name"], name,
>> append=True)
>> +            # Append name to the dict's 'shortname' field
>> +            if add_to_shortname:
>> +                dict["shortname"] = self.add_name(dict["shortname"],
>> name,
>> +                                                  append=True)
>> +
>> +
>> +def create_control(dict_list, control_path):
>> +    """
>> +    Creates a kvm test control file from a given test list
>> dictionary.
>> +
>> +    @param dict_list: A list with dictionaries representing kvm test
>> parameters.
>> +    @param control_path: Path to the kvm control file that will be
>> generated.
>> +    """
>> +    indent = "    "
>> +    indent_level = 0
>> +    control_file = open(control_path, "w")
>> +    control_file.write("# Control file generated by
>> create_control.py\n")
>> +    control_file.write("kvm_test_dir =
>> os.path.join(os.environ['AUTODIR'], "
>> +                       "'tests/kvm')\n")
>> +    control_file.write("sys.path.append(kvm_test_dir)\n")
>> +
>> +    while dict_list:
>> +        current_dict = dict_list[0]
>> +        test_iterations = int(current_dict.get("iterations", 1))
>> +        test_tag = current_dict.get("shortname")
>> +
>> +        if len(current_dict.get("depend")) == 0:
>> +            indent_level = 0
>> +
>> +        try:
>> +            future_dict = dict_list[1]
>> +        except IndexError:
>> +            control_file.write("%sjob.run_test('kvm', params=%s,
>> tag='%s', "
>> +                               "iterations=%s)\n" % (indent *
>> indent_level,
>> +                                                     current_dict,
>> test_tag,
>> +
>> test_iterations))
>> +            break
>> +
>> +        if current_dict.get("name") in future_dict.get("depend"):
>> +            control_file.write("%sif job.run_test('kvm', params=%s,
>> tag='%s', "
>> +                               "iterations=%s):\n" % (indent *
>> indent_level,
>> +                                                      current_dict,
>> test_tag,
>> +
>> test_iterations))
>> +            indent_level += 1
>> +        else:
>> +            control_file.write("%sjob.run_test('kvm', params=%s,
>> tag='%s', "
>> +                               "iterations=%s)\n" % (indent *
>> indent_level,
>> +                                                     current_dict,
>> test_tag,
>> +
>> test_iterations))
>> +        dict_list.pop(0)
>> +        continue
>> +
>> +    control_file.close()
>> +
>> +
>> +if __name__ == "__main__":
>> +    parser = optparse.OptionParser()
>> +    parser.add_option('-f', '--file', dest="filename",
>> action='store',
>> +                      help='path to a config file that will be
>> parsed. '
>> +                           'If not specified, will parse
>> kvm_tests.cfg '
>> +                           'located inside the kvm test dir.')
>> +    parser.add_option('-c', '--control', dest="control_path",
>> action='store',
>> +                      help='path to an output control file. If not
>> specified, '
>> +                           'will generate a file called "control" at
>> the top '
>> +                           'of the kvm test directory.')
>> +    parser.add_option('--verbose', dest="debug",
>> action='store_true',
>> +                      help='include debug messages in console
>> output')
>> +    options, args = parser.parse_args()
>> +    filename = options.filename
>> +    control_path = options.control_path
>> +    debug = options.debug
>> +
>> +    if not filename:
>> +        filename = os.path.join(os.path.dirname(sys.argv[0]),
>> "tests.cfg")
>> +
>> +    # Here we configure the stand alone program to use the autotest
>> +    # logging system.
>> +    logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(),
>> verbose=debug)
>> +    list = config(filename, debug=debug).get_list()
>> +    i = 0
>> +    logging.info("List of dictionaries generated from config file
>> %s:",
>> +                 filename)
>> +    for dict in list:
>> +        logging.info("Dictionary #%d:", i)
>> +        keys = dict.keys()
>> +        keys.sort()
>> +        for key in keys:
>> +            logging.info("    %s = %s", key, dict[key])
>> +        i += 1
>> +
>> +    if not control_path:
>> +        control_path = os.path.join(os.path.dirname(sys.argv[0]),
>> "control")
>> +
>> +    logging.info("Creating control file %s from config file",
>> control_path)
>> +
>> +    create_control(list, control_path)
>> diff --git a/client/tests/kvm/kvm_config.py
>> b/client/tests/kvm/kvm_config.py
>> deleted file mode 100755
>> index 656f6b3..0000000
>> --- a/client/tests/kvm/kvm_config.py
>> +++ /dev/null
>> @@ -1,524 +0,0 @@
>> -#!/usr/bin/python
>> -"""
>> -KVM configuration file utility functions.
>> -
>> -@copyright: Red Hat 2008-2009
>> -"""
>> -
>> -import logging, re, os, sys, StringIO, optparse
>> -import common
>> -import kvm_utils
>> -from autotest_lib.client.common_lib import error
>> -from autotest_lib.client.common_lib import logging_config,
>> logging_manager
>> -
>> -
>> -class config:
>> -    """
>> -    Parse an input file or string that follows the KVM Test Config
>> File format
>> -    and generate a list of dicts that will be later used as
>> configuration
>> -    parameters by the the KVM tests.
>> -
>> -    @see:
>> http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
>> -    """
>> -
>> -    def __init__(self, filename=None, debug=False):
>> -        """
>> -        Initialize the list and optionally parse filename.
>> -
>> -        @param filename: Path of the file that will be taken.
>> -        @param debug: Whether to turn debugging output.
>> -        """
>> -        self.list = [{"name": "", "shortname": "", "depend": []}]
>> -        self.debug = debug
>> -        self.filename = filename
>> -        if filename:
>> -            self.parse_file(filename)
>> -
>> -
>> -    def parse_file(self, filename):
>> -        """
>> -        Parse filename, return the resulting list and store it in
>> .list. If
>> -        filename does not exist, raise an exception.
>> -
>> -        @param filename: Path of the configuration file.
>> -        """
>> -        if not os.path.exists(filename):
>> -            raise Exception, "File %s not found" % filename
>> -        self.filename = filename
>> -        file = open(filename, "r")
>> -        self.list = self.parse(file, self.list)
>> -        file.close()
>> -        return self.list
>> -
>> -
>> -    def parse_string(self, str):
>> -        """
>> -        Parse a string, return the resulting list and store it in
>> .list.
>> -
>> -        @param str: String that will be parsed.
>> -        """
>> -        file = StringIO.StringIO(str)
>> -        self.list = self.parse(file, self.list)
>> -        file.close()
>> -        return self.list
>> -
>> -
>> -    def get_list(self):
>> -        """
>> -        Return the list of dictionaries. This should probably be
>> called after
>> -        parsing something.
>> -        """
>> -        return self.list
>> -
>> -
>> -    def match(self, filter, dict):
>> -        """
>> -        Return True if dict matches filter.
>> -
>> -        @param filter: A regular expression that defines the filter.
>> -        @param dict: Dictionary that will be inspected.
>> -        """
>> -        filter = re.compile(r"(\.|^)(%s)(\.|$)" % filter)
>> -        return bool(filter.search(dict["name"]))
>> -
>> -
>> -    def filter(self, filter, list=None):
>> -        """
>> -        Filter a list of dicts.
>> -
>> -        @param filter: A regular expression that will be used as a
>> filter.
>> -        @param list: A list of dictionaries that will be filtered.
>> -        """
>> -        if list is None:
>> -            list = self.list
>> -        return [dict for dict in list if self.match(filter, dict)]
>> -
>> -
>> -    def split_and_strip(self, str, sep="="):
>> -        """
>> -        Split str and strip quotes from the resulting parts.
>> -
>> -        @param str: String that will be processed
>> -        @param sep: Separator that will be used to split the string
>> -        """
>> -        temp = str.split(sep, 1)
>> -        for i in range(len(temp)):
>> -            temp[i] = temp[i].strip()
>> -            if re.findall("^\".*\"$", temp[i]):
>> -                temp[i] = temp[i].strip("\"")
>> -            elif re.findall("^\'.*\'$", temp[i]):
>> -                temp[i] = temp[i].strip("\'")
>> -        return temp
>> -
>> -
>> -    def get_next_line(self, file):
>> -        """
>> -        Get the next non-empty, non-comment line in a file like
>> object.
>> -
>> -        @param file: File like object
>> -        @return: If no line is available, return None.
>> -        """
>> -        while True:
>> -            line = file.readline()
>> -            if line == "": return None
>> -            stripped_line = line.strip()
>> -            if len(stripped_line)>  0 \
>> -                    and not stripped_line.startswith('#') \
>> -                    and not stripped_line.startswith('//'):
>> -                return line
>> -
>> -
>> -    def get_next_line_indent(self, file):
>> -        """
>> -        Return the indent level of the next non-empty, non-comment
>> line in file.
>> -
>> -        @param file: File like object.
>> -        @return: If no line is available, return -1.
>> -        """
>> -        pos = file.tell()
>> -        line = self.get_next_line(file)
>> -        if not line:
>> -            file.seek(pos)
>> -            return -1
>> -        line = line.expandtabs()
>> -        indent = 0
>> -        while line[indent] == ' ':
>> -            indent += 1
>> -        file.seek(pos)
>> -        return indent
>> -
>> -
>> -    def add_name(self, str, name, append=False):
>> -        """
>> -        Add name to str with a separator dot and return the result.
>> -
>> -        @param str: String that will be processed
>> -        @param name: name that will be appended to the string.
>> -        @return: If append is True, append name to str.
>> -                Otherwise, pre-pend name to str.
>> -        """
>> -        if str == "":
>> -            return name
>> -        # Append?
>> -        elif append:
>> -            return str + "." + name
>> -        # Prepend?
>> -        else:
>> -            return name + "." + str
>> -
>> -
>> -    def parse_variants(self, file, list, subvariants=False,
>> prev_indent=-1):
>> -        """
>> -        Read and parse lines from file like object until a line with
>> an indent
>> -        level lower than or equal to prev_indent is encountered.
>> -
>> -        @brief: Parse a 'variants' or 'subvariants' block from a
>> file-like
>> -        object.
>> -        @param file: File-like object that will be parsed
>> -        @param list: List of dicts to operate on
>> -        @param subvariants: If True, parse in 'subvariants' mode;
>> -        otherwise parse in 'variants' mode
>> -        @param prev_indent: The indent level of the "parent" block
>> -        @return: The resulting list of dicts.
>> -        """
>> -        new_list = []
>> -
>> -        while True:
>> -            indent = self.get_next_line_indent(file)
>> -            if indent<= prev_indent:
>> -                break
>> -            indented_line = self.get_next_line(file).rstrip()
>> -            line = indented_line.strip()
>> -
>> -            # Get name and dependencies
>> -            temp = line.strip("- ").split(":")
>> -            name = temp[0]
>> -            if len(temp) == 1:
>> -                dep_list = []
>> -            else:
>> -                dep_list = temp[1].split()
>> -
>> -            # See if name should be added to the 'shortname' field
>> -            add_to_shortname = True
>> -            if name.startswith("@"):
>> -                name = name.strip("@")
>> -                add_to_shortname = False
>> -
>> -            # Make a deep copy of list
>> -            temp_list = []
>> -            for dict in list:
>> -                new_dict = dict.copy()
>> -                new_dict["depend"] = dict["depend"][:]
>> -                temp_list.append(new_dict)
>> -
>> -            if subvariants:
>> -                # If we're parsing 'subvariants', first modify the
>> list
>> -                self.__modify_list_subvariants(temp_list, name,
>> dep_list,
>> -                                               add_to_shortname)
>> -                temp_list = self.parse(file, temp_list,
>> -                        restricted=True, prev_indent=indent)
>> -            else:
>> -                # If we're parsing 'variants', parse before modifying
>> the list
>> -                if self.debug:
>> -                    self.__debug_print(indented_line,
>> -                                       "Entering variant '%s' "
>> -                                       "(variant inherits %d dicts)"
>> %
>> -                                       (name, len(list)))
>> -                temp_list = self.parse(file, temp_list,
>> -                        restricted=False, prev_indent=indent)
>> -                self.__modify_list_variants(temp_list, name,
>> dep_list,
>> -                                            add_to_shortname)
>> -
>> -            new_list += temp_list
>> -
>> -        return new_list
>> -
>> -
>> -    def parse(self, file, list, restricted=False, prev_indent=-1):
>> -        """
>> -        Read and parse lines from file until a line with an indent
>> level lower
>> -        than or equal to prev_indent is encountered.
>> -
>> -        @brief: Parse a file-like object.
>> -        @param file: A file-like object
>> -        @param list: A list of dicts to operate on (list is modified
>> in
>> -        place and should not be used after the call)
>> -        @param restricted: if True, operate in restricted mode
>> -        (prohibit 'variants')
>> -        @param prev_indent: the indent level of the "parent" block
>> -        @return: Return the resulting list of dicts.
>> -        @note: List is destroyed and should not be used after the
>> call.
>> -        Only the returned list should be used.
>> -        """
>> -        while True:
>> -            indent = self.get_next_line_indent(file)
>> -            if indent<= prev_indent:
>> -                break
>> -            indented_line = self.get_next_line(file).rstrip()
>> -            line = indented_line.strip()
>> -            words = line.split()
>> -
>> -            len_list = len(list)
>> -
>> -            # Look for a known operator in the line
>> -            operators = ["?+=", "?<=", "?=", "+=", "<=", "="]
>> -            op_found = None
>> -            op_pos = len(line)
>> -            for op in operators:
>> -                pos = line.find(op)
>> -                if pos>= 0 and pos<  op_pos:
>> -                    op_found = op
>> -                    op_pos = pos
>> -
>> -            # Found an operator?
>> -            if op_found:
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Parsing operator (%d dicts in
>> current "
>> -                                       "context)" % len_list)
>> -                (left, value) = self.split_and_strip(line, op_found)
>> -                filters_and_key = self.split_and_strip(left, ":")
>> -                filters = filters_and_key[:-1]
>> -                key = filters_and_key[-1]
>> -                filtered_list = list
>> -                for filter in filters:
>> -                    filtered_list = self.filter(filter,
>> filtered_list)
>> -                # Apply the operation to the filtered list
>> -                if op_found == "=":
>> -                    for dict in filtered_list:
>> -                        dict[key] = value
>> -                elif op_found == "+=":
>> -                    for dict in filtered_list:
>> -                        dict[key] = dict.get(key, "") + value
>> -                elif op_found == "<=":
>> -                    for dict in filtered_list:
>> -                        dict[key] = value + dict.get(key, "")
>> -                elif op_found.startswith("?"):
>> -                    exp = re.compile("^(%s)$" % key)
>> -                    if op_found == "?=":
>> -                        for dict in filtered_list:
>> -                            for key in dict.keys():
>> -                                if exp.match(key):
>> -                                    dict[key] = value
>> -                    elif op_found == "?+=":
>> -                        for dict in filtered_list:
>> -                            for key in dict.keys():
>> -                                if exp.match(key):
>> -                                    dict[key] = dict.get(key, "") +
>> value
>> -                    elif op_found == "?<=":
>> -                        for dict in filtered_list:
>> -                            for key in dict.keys():
>> -                                if exp.match(key):
>> -                                    dict[key] = value + dict.get(key,
>> "")
>> -
>> -            # Parse 'no' and 'only' statements
>> -            elif words[0] == "no" or words[0] == "only":
>> -                if len(words)<= 1:
>> -                    continue
>> -                filters = words[1:]
>> -                filtered_list = []
>> -                if words[0] == "no":
>> -                    for dict in list:
>> -                        for filter in filters:
>> -                            if self.match(filter, dict):
>> -                                break
>> -                        else:
>> -                            filtered_list.append(dict)
>> -                if words[0] == "only":
>> -                    for dict in list:
>> -                        for filter in filters:
>> -                            if self.match(filter, dict):
>> -                                filtered_list.append(dict)
>> -                                break
>> -                list = filtered_list
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Parsing no/only (%d dicts in
>> current "
>> -                                       "context, %d remain)" %
>> -                                       (len_list, len(list)))
>> -
>> -            # Parse 'variants'
>> -            elif line == "variants:":
>> -                # 'variants' not allowed in restricted mode
>> -                # (inside an exception or inside subvariants)
>> -                if restricted:
>> -                    e_msg = "Using variants in this context is not
>> allowed"
>> -                    raise error.AutotestError(e_msg)
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Entering variants block (%d
>> dicts in "
>> -                                       "current context)" %
>> len_list)
>> -                list = self.parse_variants(file, list,
>> subvariants=False,
>> -                                           prev_indent=indent)
>> -
>> -            # Parse 'subvariants' (the block is parsed for each dict
>> -            # separately)
>> -            elif line == "subvariants:":
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Entering subvariants block
>> (%d dicts in "
>> -                                       "current context)" %
>> len_list)
>> -                new_list = []
>> -                # Remember current file position
>> -                pos = file.tell()
>> -                # Read the lines in any case
>> -                self.parse_variants(file, [], subvariants=True,
>> -                                    prev_indent=indent)
>> -                # Iterate over the list...
>> -                for index in range(len(list)):
>> -                    # Revert to initial file position in this
>> 'subvariants'
>> -                    # block
>> -                    file.seek(pos)
>> -                    # Everything inside 'subvariants' should be
>> parsed in
>> -                    # restricted mode
>> -                    new_list += self.parse_variants(file,
>> list[index:index+1],
>> -
>> subvariants=True,
>> -
>> prev_indent=indent)
>> -                list = new_list
>> -
>> -            # Parse 'include' statements
>> -            elif words[0] == "include":
>> -                if len(words)<= 1:
>> -                    continue
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Entering file %s" %
>> words[1])
>> -                if self.filename:
>> -                    filename =
>> os.path.join(os.path.dirname(self.filename),
>> -                                            words[1])
>> -                    if os.path.exists(filename):
>> -                        new_file = open(filename, "r")
>> -                        list = self.parse(new_file, list,
>> restricted)
>> -                        new_file.close()
>> -                        if self.debug and not restricted:
>> -                            self.__debug_print("", "Leaving file %s"
>> % words[1])
>> -                    else:
>> -                        logging.warning("Cannot include %s -- file
>> not found",
>> -                                        filename)
>> -                else:
>> -                    logging.warning("Cannot include %s because no
>> file is "
>> -                                    "currently open", words[1])
>> -
>> -            # Parse multi-line exceptions
>> -            # (the block is parsed for each dict separately)
>> -            elif line.endswith(":"):
>> -                if self.debug and not restricted:
>> -                    self.__debug_print(indented_line,
>> -                                       "Entering multi-line exception
>> block "
>> -                                       "(%d dicts in current context
>> outside "
>> -                                       "exception)" % len_list)
>> -                line = line.strip(":")
>> -                new_list = []
>> -                # Remember current file position
>> -                pos = file.tell()
>> -                # Read the lines in any case
>> -                self.parse(file, [], restricted=True,
>> prev_indent=indent)
>> -                # Iterate over the list...
>> -                for index in range(len(list)):
>> -                    if self.match(line, list[index]):
>> -                        # Revert to initial file position in this
>> -                        # exception block
>> -                        file.seek(pos)
>> -                        # Everything inside an exception should be
>> parsed in
>> -                        # restricted mode
>> -                        new_list += self.parse(file,
>> list[index:index+1],
>> -                                               restricted=True,
>> -                                               prev_indent=indent)
>> -                    else:
>> -                        new_list += list[index:index+1]
>> -                list = new_list
>> -
>> -        return list
>> -
>> -
>> -    def __debug_print(self, str1, str2=""):
>> -        """
>> -        Nicely print two strings and an arrow.
>> -
>> -        @param str1: First string
>> -        @param str2: Second string
>> -        """
>> -        if str2:
>> -            str = "%-50s --->  %s" % (str1, str2)
>> -        else:
>> -            str = str1
>> -        logging.debug(str)
>> -
>> -
>> -    def __modify_list_variants(self, list, name, dep_list,
>> add_to_shortname):
>> -        """
>> -        Make some modifications to list, as part of parsing a
>> 'variants' block.
>> -
>> -        @param list: List to be processed
>> -        @param name: Name to be prepended to the dictionary's 'name'
>> key
>> -        @param dep_list: List of dependencies to be added to the
>> dictionary's
>> -                'depend' key
>> -        @param add_to_shortname: Boolean indicating whether name
>> should be
>> -                prepended to the dictionary's 'shortname' key as
>> well
>> -        """
>> -        for dict in list:
>> -            # Prepend name to the dict's 'name' field
>> -            dict["name"] = self.add_name(dict["name"], name)
>> -            # Prepend name to the dict's 'shortname' field
>> -            if add_to_shortname:
>> -                dict["shortname"] = self.add_name(dict["shortname"],
>> name)
>> -            # Prepend name to each of the dict's dependencies
>> -            for i in range(len(dict["depend"])):
>> -                dict["depend"][i] = self.add_name(dict["depend"][i],
>> name)
>> -            # Add new dependencies
>> -            dict["depend"] += dep_list
>> -
>> -
>> -    def __modify_list_subvariants(self, list, name, dep_list,
>> add_to_shortname):
>> -        """
>> -        Make some modifications to list, as part of parsing a
>> 'subvariants'
>> -        block.
>> -
>> -        @param list: List to be processed
>> -        @param name: Name to be appended to the dictionary's 'name'
>> key
>> -        @param dep_list: List of dependencies to be added to the
>> dictionary's
>> -                'depend' key
>> -        @param add_to_shortname: Boolean indicating whether name
>> should be
>> -                appended to the dictionary's 'shortname' as well
>> -        """
>> -        for dict in list:
>> -            # Add new dependencies
>> -            for dep in dep_list:
>> -                dep_name = self.add_name(dict["name"], dep,
>> append=True)
>> -                dict["depend"].append(dep_name)
>> -            # Append name to the dict's 'name' field
>> -            dict["name"] = self.add_name(dict["name"], name,
>> append=True)
>> -            # Append name to the dict's 'shortname' field
>> -            if add_to_shortname:
>> -                dict["shortname"] = self.add_name(dict["shortname"],
>> name,
>> -                                                  append=True)
>> -
>> -
>> -if __name__ == "__main__":
>> -    parser = optparse.OptionParser()
>> -    parser.add_option('-f', '--file', dest="filename",
>> action='store',
>> -                      help='path to a config file that will be
>> parsed. '
>> -                           'If not specified, will parse
>> kvm_tests.cfg '
>> -                           'located inside the kvm test dir.')
>> -    parser.add_option('--verbose', dest="debug",
>> action='store_true',
>> -                      help='include debug messages in console
>> output')
>> -
>> -    options, args = parser.parse_args()
>> -    filename = options.filename
>> -    debug = options.debug
>> -
>> -    if not filename:
>> -        filename = os.path.join(os.path.dirname(sys.argv[0]),
>> "tests.cfg")
>> -
>> -    # Here we configure the stand alone program to use the autotest
>> -    # logging system.
>> -    logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(),
>> verbose=debug)
>> -    list = config(filename, debug=debug).get_list()
>> -    i = 0
>> -    for dict in list:
>> -        logging.info("Dictionary #%d:", i)
>> -        keys = dict.keys()
>> -        keys.sort()
>> -        for key in keys:
>> -            logging.info("    %s = %s", key, dict[key])
>> -        i += 1
>> --
>> 1.6.6.1
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe kvm" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


  reply	other threads:[~2010-02-14 20:53 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <1872956130.1596671266167151002.JavaMail.root@zmail05.collab.prod.int.phx2.redhat.com>
2010-02-14 17:07 ` [PATCH] [RFC] KVM test: Control files automatic generation to save memory Michael Goldish
2010-02-14 20:54   ` Dor Laor [this message]
2010-02-18 10:28   ` Lucas Meneghel Rodrigues
     [not found] <106007940.1864191266490999633.JavaMail.root@zmail05.collab.prod.int.phx2.redhat.com>
2010-02-18 11:15 ` Michael Goldish
2010-02-11  6:42 Lucas Meneghel Rodrigues

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4B786300.7040408@redhat.com \
    --to=dlaor@redhat.com \
    --cc=autotest@test.kernel.org \
    --cc=kvm@vger.kernel.org \
    --cc=lmr@redhat.com \
    --cc=mgoldish@redhat.com \
    --cc=mtosatti@redhat.com \
    --cc=nsprei@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox