From: Ondrej Lichtner olichtne@redhat.com
This commit renames the internally used 'machine' keyword to 'host', and 'command_sequence' to 'task' also updating relevant output messages. The reson for this is the automatic generation of result xmls, that use this keyword and were inconsistent with the recipe xml format.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/NetTestCommand.py | 8 +++--- lnst/Controller/NetTestController.py | 44 +++++++++++++++--------------- lnst/Controller/NetTestResultSerializer.py | 14 +++++----- lnst/Controller/RecipeParser.py | 6 ++-- lnst/Controller/Task.py | 10 +++---- 5 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py index 47a3e4a..7b45b33 100644 --- a/lnst/Common/NetTestCommand.py +++ b/lnst/Common/NetTestCommand.py @@ -25,7 +25,7 @@ def str_command(command): attrs = ["type(%s)" % command["type"]] if command["type"] == "test": attrs.append("module(%s)" % command["module"]) - attrs.append("host(%s)" % command["machine"]) + attrs.append("host(%s)" % command["host"])
if "bg_id" in command: attrs.append("bg_id(%s)" % command["bg_id"]) @@ -33,7 +33,7 @@ def str_command(command): attrs.append("timeout(%s)" % command["timeout"]) elif command["type"] == "exec": attrs.append("command(%s)" % command["command"]) - attrs.append("machine(%s)" % command["machine"]) + attrs.append("host(%s)" % command["host"])
if "from" in command: attrs.append("from(%s)" % command["from"]) @@ -42,10 +42,10 @@ def str_command(command): if "timeout" in command: attrs.append("timeout(%s)" % command["timeout"]) elif command["type"] in ["wait", "intr", "kill"]: - attrs.append("machine(%s)" % command["machine"]) + attrs.append("host(%s)" % command["host"]) attrs.append("bg_id(%s)" % command["proc_id"]) elif command["type"] == "config": - attrs.append("machine(%s)" % command["machine"]) + attrs.append("host(%s)" % command["host"])
if "option" in command: attrs.append("option(%s)" % command["option"]) diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index 7324a7c..400cd19 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -105,8 +105,8 @@ class NetTestController: # There must be some machines specified in the recipe if "machines" not in recipe or \ ("machines" in recipe and len(recipe["machines"]) == 0): - msg = "No machines specified in the recipe. At least two " \ - "machines are required to perform a network test." + msg = "No hosts specified in the recipe. At least two " \ + "hosts are required to perform a network test." raise RecipeError(msg, recipe)
# machine requirements @@ -122,7 +122,7 @@ class NetTestController: if "params" in machine: for p in machine["params"]: if p["name"] in params: - msg = "Parameter '%s' of machine %s was specified " \ + msg = "Parameter '%s' of host %s was specified " \ "multiple times. Overriding the previous value." \ % (p["name"], m_id) logging.warn(RecipeError(msg, p)) @@ -133,14 +133,14 @@ class NetTestController: # Each machine must have at least one interface if "interfaces" not in machine or \ ("interfaces" in machine and len(machine["interfaces"]) == 0): - msg = "Machine '%s' has no interfaces specified." % m_id + msg = "Hot '%s' has no interfaces specified." % m_id raise RecipeError(msg, machine)
ifaces = {} for iface in machine["interfaces"]: if_id = iface["id"] if if_id in ifaces: - msg = "Interface with id='%s' already exists on machine " \ + msg = "Interface with id='%s' already exists on host " \ "'%s'." % (if_id, m_id)
iface_type = iface["type"] @@ -152,7 +152,7 @@ class NetTestController: for i in iface["params"]: if i["name"] in iface_params: msg = "Parameter '%s' of interface %s of " \ - "machine %s was defined multiple times. " \ + "host %s was defined multiple times. " \ "Overriding the previous value." \ % (i["name"], if_id, m_id) logging.warn(RecipeError(msg, p)) @@ -197,14 +197,14 @@ class NetTestController:
if sp.is_setup_virtual() and os.geteuid() != 0: msg = "Provisioning this setup requires additional configuration "\ - "of the virtual machines in the pool. LNST needs root "\ + "of the virtual hosts in the pool. LNST needs root "\ "priviledges so it can connect to qemu." raise NetTestError(msg)
logging.info("Provisioning initialized") for m_id in machines.keys(): provisioner = sp.get_provisioner_id(m_id) - logging.info(" machine %s uses %s" % (m_id, provisioner)) + logging.info(" host %s uses %s" % (m_id, provisioner))
def _prepare_machine(self, m_id, resource_sync=True): machine = self._machines[m_id] @@ -236,7 +236,7 @@ class NetTestController: else: continue for cmd in task['commands']: - if 'machine' not in cmd or cmd['machine'] != m_id: + if 'host' not in cmd or cmd['host'] != m_id: continue if cmd['type'] == 'test': mod = cmd['module'] @@ -318,10 +318,10 @@ class NetTestController: for cmd_data in task["commands"]: cmd = {"type": cmd_data["type"]}
- if "machine" in cmd_data: - cmd["machine"] = cmd_data["machine"] - if cmd["machine"] not in self._machines: - msg = "Invalid machine id '%s'." % cmd["machine"] + if "host" in cmd_data: + cmd["host"] = cmd_data["host"] + if cmd["host"] not in self._machines: + msg = "Invalid host id '%s'." % cmd["host"] raise RecipeError(msg, cmd_data)
if cmd["type"] in ["test", "exec"]: @@ -339,10 +339,10 @@ class NetTestController:
def _prepare_command(self, cmd_data): cmd = {"type": cmd_data["type"]} - if "machine" in cmd_data: - cmd["machine"] = cmd_data["machine"] - if cmd["machine"] not in self._machines: - msg = "Invalid machine id '%s'." % cmd["machine"] + if "host" in cmd_data: + cmd["host"] = cmd_data["host"] + if cmd["host"] not in self._machines: + msg = "Invalid host id '%s'." % cmd["host"] raise RecipeError(msg, cmd_data)
if "expect" in cmd_data: @@ -412,7 +412,7 @@ class NetTestController: if command["type"] == "ctl_wait": continue
- machine_id = command["machine"] + machine_id = command["host"] if not machine_id in bg_ids: bg_ids[machine_id] = set()
@@ -423,7 +423,7 @@ class NetTestController: bg_ids[machine_id].remove(bg_id) else: logging.error("Found command "%s" for bg_id "%s" on " - "machine "%s" which was not previously " + "host "%s" which was not previously " "defined", cmd_type, bg_id, machine_id) err = True
@@ -432,14 +432,14 @@ class NetTestController: if not bg_id in bg_ids[machine_id]: bg_ids[machine_id].add(bg_id) else: - logging.error("Command "%d" uses bg_id "%s" on machine " + logging.error("Command "%d" uses bg_id "%s" on host" ""%s" which is already used", i, bg_id, machine_id) err = True
for machine_id in bg_ids: for bg_id in bg_ids[machine_id]: - logging.error("bg_id "%s" on machine "%s" has no kill/wait " + logging.error("bg_id "%s" on host "%s" has no kill/wait " "command to it", bg_id, machine_id) err = True
@@ -664,7 +664,7 @@ class NetTestController: self._res_serializer.add_cmd_result(command, cmd_res) return cmd_res
- machine_id = command["machine"] + machine_id = command["host"] machine = self._machines[machine_id]
try: diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py index 7f2e6d1..a7702fc 100644 --- a/lnst/Controller/NetTestResultSerializer.py +++ b/lnst/Controller/NetTestResultSerializer.py @@ -86,9 +86,9 @@ class NetTestResultSerializer:
m_id_max = 0 for cmd, cmd_res in task: - if "machine" in cmd and\ - len(cmd["machine"]) > m_id_max: - m_id_max = len(cmd["machine"]) + if "host" in cmd and\ + len(cmd["host"]) > m_id_max: + m_id_max = len(cmd["host"]) for cmd, cmd_res in task: self._format_command(cmd, cmd_res, output_pairs, m_id_max)
@@ -100,9 +100,9 @@ class NetTestResultSerializer: else: res = "FAIL"
- if "machine" in command: - m_id = "host %s: " % command["machine"] - m_id += " " * (m_id_max - len(command["machine"])) + if "host" in command: + m_id = "host %s: " % command["host"] + m_id += " " * (m_id_max - len(command["host"])) else: #len("ctl") == 3; len("host ") == 5; 5-3 = 2 m_id = "ctl: " + " " * (m_id_max + 2) @@ -174,7 +174,7 @@ class NetTestResultSerializer: recipe_el.appendChild(err_el)
for task in recipe["tasks"]: - cmd_seq_el = doc.createElement("command_sequence") + cmd_seq_el = doc.createElement("task") recipe_el.appendChild(cmd_seq_el)
for cmd, cmd_res in task: diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index 2b554d7..046d6d9 100644 --- a/lnst/Controller/RecipeParser.py +++ b/lnst/Controller/RecipeParser.py @@ -260,7 +260,7 @@ class RecipeParser(XmlParser):
def _process_run_cmd(self, cmd_tag): cmd = XmlData(cmd_tag) - cmd["machine"] = self._get_attribute(cmd_tag, "host") + cmd["host"] = self._get_attribute(cmd_tag, "host")
has_module = self._has_attribute(cmd_tag, "module") has_command = self._has_attribute(cmd_tag, "command") @@ -300,7 +300,7 @@ class RecipeParser(XmlParser): def _process_config_cmd(self, cmd_tag): cmd = XmlData(cmd_tag) cmd["type"] = "config" - cmd["machine"] = self._get_attribute(cmd_tag, "host") + cmd["host"] = self._get_attribute(cmd_tag, "host")
if self._has_attribute(cmd_tag, "persistent"): cmd["persistent"] = self._get_attribute(cmd_tag, "persistent") @@ -335,6 +335,6 @@ class RecipeParser(XmlParser): def _process_signal_cmd(self, cmd_tag): cmd = XmlData(cmd_tag) cmd["type"] = cmd_tag.tag - cmd["machine"] = self._get_attribute(cmd_tag, "host") + cmd["host"] = self._get_attribute(cmd_tag, "host") cmd["bg_id"] = self._get_attribute(cmd_tag, "bg_id") return cmd diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index 10c9f02..59f17c5 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -114,7 +114,7 @@ class HostAPI(object): :return: Command result. :rtype: dict """ - cmd = {"machine": str(self._id), "type": "config"} + cmd = {"host": str(self._id), "type": "config"} cmd["options"] = [{"name": option, "value": value}] cmd["persistent"] = persistent
@@ -139,7 +139,7 @@ class HostAPI(object): :return: A handle for process. :rtype: ProcessAPI """ - cmd = {"machine": str(self._id)} + cmd = {"host": str(self._id)} bg_id = None
for arg, argval in kwargs.iteritems(): @@ -307,7 +307,7 @@ class ProcessAPI(object): def wait(self): """ Blocking wait until the command returns. """ if self._bg_id: - cmd = {"machine": self._host, + cmd = {"host": self._host, "type": "wait", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd) @@ -315,7 +315,7 @@ class ProcessAPI(object): def intr(self): """ Interrupt the command. """ if self._bg_id: - cmd = {"machine": self._host, + cmd = {"host": self._host, "type": "intr", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd) @@ -329,7 +329,7 @@ class ProcessAPI(object): to keep the results, use 'intr' instead. """ if self._bg_id: - cmd = {"machine": self._host, + cmd = {"host": self._host, "type": "kill", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd)
From: Ondrej Lichtner olichtne@redhat.com
This folder contains 3 files: xml_to_html.xsl - a XSLT stylesheet that transforms result xml files into a HTML file viewable in a web browser xml_to_html.js - a javascript file that is referenced by the created HTML file, it defines a single function that shows/hides the result data returned by a test xml_to_html.css - a CSS file that is referenced by the created HTML file, it contains some basic formatting
These files should be uploaded to a 'well known' location so that they can be properly linked which will result in a very convenient use of the result xml files - a user opens them in a web browser, and everything just works.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- result_xslt/xml_to_html.css | 23 +++ result_xslt/xml_to_html.js | 21 +++ result_xslt/xml_to_html.xsl | 339 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+) create mode 100644 result_xslt/xml_to_html.css create mode 100644 result_xslt/xml_to_html.js create mode 100644 result_xslt/xml_to_html.xsl
diff --git a/result_xslt/xml_to_html.css b/result_xslt/xml_to_html.css new file mode 100644 index 0000000..2f85bd7 --- /dev/null +++ b/result_xslt/xml_to_html.css @@ -0,0 +1,23 @@ +table.lnst_results td { + padding: 0px 10px 0px 10px; +} + +.result_data{ + background-color: lightgrey; +} + +table.result_data{ + border: 1px solid black; +} + +table.result_data td{ + vertical-align: top; +} + +td.result_pass{ + background-color: lime; +} + +td.result_fail{ + background-color: red; +} diff --git a/result_xslt/xml_to_html.js b/result_xslt/xml_to_html.js new file mode 100644 index 0000000..4c42b66 --- /dev/null +++ b/result_xslt/xml_to_html.js @@ -0,0 +1,21 @@ +window.toggleResultData = function (event, task_id, bg_id) { + switch_display = function(elem){ + if (elem.style.display == 'none') + elem.style.display = ''; + else + elem.style.display = 'none'; + } + + row = event.target.parentNode; + result_row_i = row.rowIndex + 1; + result_row = row.parentNode.rows[result_row_i]; + switch_display(result_row); + + if (task_id !== undefined && bg_id !== undefined){ + rows = row.parentNode.childNodes; + for (i = 0; i < rows.length; ++i){ + if (rows[i].id == ("task_id="+task_id+"bg_id="+bg_id)) + rows[i].style.display = result_row.style.display; + } + } +} diff --git a/result_xslt/xml_to_html.xsl b/result_xslt/xml_to_html.xsl new file mode 100644 index 0000000..7774bbb --- /dev/null +++ b/result_xslt/xml_to_html.xsl @@ -0,0 +1,339 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform%22%3E + <xsl:output method="html" indent="yes"/> + <xsl:template match="/"> + <html> + <body> + <link rel="stylesheet" type="text/css" href="./xml_to_html.xsl"/> + <script type="text/javascript" src="./xml_to_html.js"/> + <h2>LNST results</h2> + <xsl:apply-templates select="results/recipe"/> + </body> + </html> + </xsl:template> + + <xsl:template match="recipe"> + <h3><xsl:value-of select="@name"/></h3> + <table class="lnst_results"> + <tr><th colspan="4">Task</th></tr> + <tr><th>Host</th><th>Command</th><th>Result</th><th>Result message</th></tr> + <xsl:apply-templates select="task"/> + </table> + </xsl:template> + + <xsl:template match="task"> + <tr><th colspan="4">Task <xsl:value-of select="position()"/></th></tr> + <xsl:apply-templates select="command"> + <xsl:with-param name="task_id" select="position()"/> + </xsl:apply-templates> + </xsl:template> + + <xsl:template match="command[@type='exec']"> + <xsl:param name="task_id"/> + <tr> + xsl:choose + <xsl:when test="@bg_id"> + <xsl:attribute name="onclick"> + toggleResultData(event, <xsl:value-of select="$task_id"/>, <xsl:value-of select="@bg_id"/>); + </xsl:attribute> + </xsl:when> + xsl:otherwise + <xsl:attribute name="onclick"> + toggleResultData(event); + </xsl:attribute> + </xsl:otherwise> + </xsl:choose> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + <xsl:value-of select="@command"/> + <xsl:if test="@bg_id"> + bg_id=<xsl:value-of select="@bg_id"/> + </xsl:if> + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='ctl_wait']"> + <xsl:param name="task_id"/> + <tr onclick="toggleResultData(event);"> + <td> + Controller + </td> + <td> + wait <xsl:value-of select="@seconds"/>s + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='test']"> + <xsl:param name="task_id"/> + <tr> + xsl:choose + <xsl:when test="@bg_id"> + <xsl:attribute name="onclick"> + toggleResultData(event, <xsl:value-of select="$task_id"/>, <xsl:value-of select="@bg_id"/>); + </xsl:attribute> + </xsl:when> + xsl:otherwise + <xsl:attribute name="onclick"> + toggleResultData(event); + </xsl:attribute> + </xsl:otherwise> + </xsl:choose> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + <xsl:value-of select="@module"/> + <xsl:if test="@bg_id"> + bg_id=<xsl:value-of select="@bg_id"/> + </xsl:if> + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='config']"> + <xsl:param name="task_id"/> + <tr onclick="toggleResultData(event);"> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + config + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='intr']"> + <xsl:param name="task_id"/> + <tr onclick="toggleResultData(event);"> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + interrupt bg_id=<xsl:value-of select="@proc_id"/> + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <xsl:attribute name="id">task_id=<xsl:value-of select="$task_id"/>bg_id=<xsl:value-of select="@proc_id"/></xsl:attribute> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='kill']"> + <xsl:param name="task_id"/> + <tr onclick="toggleResultData(event);"> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + kill bg_id=<xsl:value-of select="@proc_id"/> + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <xsl:attribute name="id">task_id=<xsl:value-of select="$task_id"/>bg_id=<xsl:value-of select="@proc_id"/></xsl:attribute> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="command[@type='wait']"> + <xsl:param name="task_id"/> + <tr onclick="toggleResultData(event);"> + <td> + <xsl:value-of select="@host"/> + </td> + <td> + wait for bg_id=<xsl:value-of select="@proc_id"/> + </td> + <xsl:apply-templates select="result"/> + </tr> + <tr style="display:none;"> + <xsl:attribute name="id">task_id=<xsl:value-of select="$task_id"/>bg_id=<xsl:value-of select="@proc_id"/></xsl:attribute> + <td></td> + <td colspan="3"> + xsl:choose + <xsl:when test="result_data"> + <xsl:apply-templates select="result_data"/> + </xsl:when> + xsl:otherwise + <div class="result_data"> + This command didn't provide any additional result data. + </div> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template match="result"> + xsl:choose + <xsl:when test="@result='PASS'"> + <td class="result_pass">PASSED</td> + </xsl:when> + xsl:otherwise + <td class="result_fail">FAILED</td> + </xsl:otherwise> + </xsl:choose> + <td> + <xsl:if test="message"> + <xsl:value-of select="message"/> + </xsl:if> + </td> + </xsl:template> + + <xsl:template match="result_data"> + <table class="result_data"> + <th>Result Data:</th> + <xsl:for-each select="*"> + <xsl:call-template name="res_data_dict_item"/> + </xsl:for-each> + </table> + </xsl:template> + + <xsl:template name="res_data_dict_item"> + <tr> + <td> + <xsl:value-of select="local-name()"/> + </td> + <td> + xsl:choose + <xsl:when test="@type = 'list'"> + <ul> + <xsl:for-each select="*"> + <li> + <xsl:call-template name="res_data_list_item"/> + </li> + </xsl:for-each> + </ul> + </xsl:when> + <xsl:when test="@type = 'dict'"> + <table class="result_data"> + <xsl:for-each select="*"> + <xsl:call-template name="res_data_dict_item"/> + </xsl:for-each> + </table> + </xsl:when> + xsl:otherwise + <xsl:value-of select="text()"/> + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + </xsl:template> + + <xsl:template name="res_data_list_item"> + xsl:choose + <xsl:when test="@type = 'list'"> + <ul> + <xsl:for-each select="*"> + <li> + <xsl:call-template name="res_data_list_item"/> + </li> + </xsl:for-each> + </ul> + </xsl:when> + <xsl:when test="@type = 'dict'"> + <table class="result_data"> + <xsl:for-each select="*"> + <xsl:call-template name="res_data_dict_item"/> + </xsl:for-each> + </table> + </xsl:when> + xsl:otherwise + <xsl:value-of select="text()"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> +</xsl:stylesheet>
From: Ondrej Lichtner olichtne@redhat.com
This is a Controller specific option, the value is a path or a URL to the XSLT document that should be referenced in a result xml file if the option -x is supplied.
The default value points to the xml_to_html.xsl file in this repository but this should change shortly when we have the files uploaded somewhere.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst-ctl | 12 ++++++++++-- lnst-ctl.conf | 1 + lnst/Common/Config.py | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/lnst-ctl b/lnst-ctl index 2dad6aa..ad7ecf7 100755 --- a/lnst-ctl +++ b/lnst-ctl @@ -48,6 +48,8 @@ def usage(retval=0): print " -p, --packet-capture capture and log all ongoing " \ "network communication during the test" print " -x, --result=FILE file to write xml_result" + print " -s, --xslt-url=URL URL to a XSLT document that the "\ + "result xml will reference, only usefull when -x is used as well" print " -r, --reduce-sync reduces resource synchronization "\ "for python tasks, see documentation" sys.exit(retval) @@ -123,10 +125,10 @@ def main(): try: opts, args = getopt.getopt( sys.argv[1:], - "dhrc:x:poma:A:", + "dhrc:x:s:poma:A:", ["debug", "help", "config", "result=", "packet-capture", "disable-pool-checks", "no-colours", - "define_alias", "override_alias", "reduce-sync"] + "define_alias", "override_alias", "reduce-sync", "xslt-url"] ) except getopt.GetoptError as err: print str(err) @@ -154,6 +156,7 @@ def main(): debug = 0 recipe_path = None result_path = None + xslt_url = None config_path = None packet_capture = False pool_checks = True @@ -182,6 +185,8 @@ def main(): store_alias(arg, overriden_aliases) elif opt in ("-r", "--reduce-sync"): reduce_sync = True + elif opt in ("-s", "--xslt-url"): + xslt_url = arg
if config_path is not None: if not os.path.isfile(config_path): @@ -190,6 +195,9 @@ def main(): else: lnst_config.load_config(config_path)
+ if xslt_url != None: + lnst_config.set_option("environment", "xslt_url", xslt_url) + if coloured_output: coloured_output = not lnst_config.get_option("colours", "disable_colours") diff --git a/lnst-ctl.conf b/lnst-ctl.conf index 584d340..3b32697 100644 --- a/lnst-ctl.conf +++ b/lnst-ctl.conf @@ -10,3 +10,4 @@ machine_pool_dirs = test_tool_dirs = ./test_tools test_module_dirs = ./test_modules log_dir = ./Logs +xslt_url = ./result_xslt/xml_to_html.xsl diff --git a/lnst/Common/Config.py b/lnst/Common/Config.py index ffecfb6..975df5a 100644 --- a/lnst/Common/Config.py +++ b/lnst/Common/Config.py @@ -69,6 +69,12 @@ class Config(): "additive" : False, "action" : self.optionPath, "name" : "resource_dir"} + self._options['environment']['xslt_url'] = { + "value" : "./result_xslt/xml_to_html.xsl", + "additive" : False, + "action" : self.optionPlain, + "name" : "xslt_url" + }
self.colours_scheme()
@@ -263,6 +269,9 @@ class Config():
def optionBool(self, option, cfg_path): return bool_it(option) + + def optionPlain(self, option, cfg_path): + return option
def dump_config(self): string = ""
From: Ondrej Lichtner olichtne@redhat.com
This patch makes it so that the XSLT reference is automatically added when creating a result xml file.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/NetTestResultSerializer.py | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py index a7702fc..61f6c79 100644 --- a/lnst/Controller/NetTestResultSerializer.py +++ b/lnst/Controller/NetTestResultSerializer.py @@ -15,6 +15,7 @@ import logging from xml.dom.minidom import getDOMImplementation from lnst.Common.NetTestCommand import str_command from lnst.Common.Colours import decorate_string, decorate_with_preset +from lnst.Common.Config import lnst_config
def serialize_obj(obj, dom, el, upper_name="unnamed"): if isinstance(obj, dict): @@ -157,7 +158,12 @@ class NetTestResultSerializer: def get_result_xml(self): impl = getDOMImplementation() doc = impl.createDocument(None, "results", None) + + xslt_url = lnst_config.get_option("environment", "xslt_url") + proc_inst = doc.createProcessingInstruction('xml-stylesheet', + 'type="text/xsl" href="'+xslt_url+'"') top_el = doc.documentElement + doc.insertBefore(proc_inst, top_el)
for recipe in self._results: recipe_el = doc.createElement("recipe")
From: Ondrej Lichtner olichtne@redhat.com
We serialize the result_data structure automatically into an arbitrary xml file. This is not well suited for processing by an XSLT stylesheet. This patch adds a 'type' attribute to created elements that the XSLT stylesheet can use to determine the proper transformation.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/NetTestResultSerializer.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py index 61f6c79..3689835 100644 --- a/lnst/Controller/NetTestResultSerializer.py +++ b/lnst/Controller/NetTestResultSerializer.py @@ -20,16 +20,20 @@ from lnst.Common.Config import lnst_config def serialize_obj(obj, dom, el, upper_name="unnamed"): if isinstance(obj, dict): for key in obj: - if upper_name == "options": - new_el = dom.createElement("option") - new_el.setAttribute("name", key) - else: - new_el = dom.createElement(key) + new_el = dom.createElement(key) + if isinstance(obj[key], dict): + new_el.setAttribute("type", "dict") + elif isinstance(obj[key], list): + new_el.setAttribute("type", "list") el.appendChild(new_el) serialize_obj(obj[key], dom, new_el, upper_name=key) elif isinstance(obj, list): for one in obj: new_el = dom.createElement("%s_item" % upper_name) + if isinstance(one, dict): + new_el.setAttribute("type", "dict") + elif isinstance(one, list): + new_el.setAttribute("type", "list") el.appendChild(new_el) serialize_obj(one, dom, new_el) else:
patchset applied. Thanks!
Tue, May 06, 2014 at 10:01:53AM CEST, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
This commit renames the internally used 'machine' keyword to 'host', and 'command_sequence' to 'task' also updating relevant output messages. The reson for this is the automatic generation of result xmls, that use this keyword and were inconsistent with the recipe xml format.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
lnst/Common/NetTestCommand.py | 8 +++--- lnst/Controller/NetTestController.py | 44 +++++++++++++++--------------- lnst/Controller/NetTestResultSerializer.py | 14 +++++----- lnst/Controller/RecipeParser.py | 6 ++-- lnst/Controller/Task.py | 10 +++---- 5 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py index 47a3e4a..7b45b33 100644 --- a/lnst/Common/NetTestCommand.py +++ b/lnst/Common/NetTestCommand.py @@ -25,7 +25,7 @@ def str_command(command): attrs = ["type(%s)" % command["type"]] if command["type"] == "test": attrs.append("module(%s)" % command["module"])
attrs.append("host(%s)" % command["machine"])
attrs.append("host(%s)" % command["host"]) if "bg_id" in command: attrs.append("bg_id(%s)" % command["bg_id"])@@ -33,7 +33,7 @@ def str_command(command): attrs.append("timeout(%s)" % command["timeout"]) elif command["type"] == "exec": attrs.append("command(%s)" % command["command"])
attrs.append("machine(%s)" % command["machine"])
attrs.append("host(%s)" % command["host"]) if "from" in command: attrs.append("from(%s)" % command["from"])@@ -42,10 +42,10 @@ def str_command(command): if "timeout" in command: attrs.append("timeout(%s)" % command["timeout"]) elif command["type"] in ["wait", "intr", "kill"]:
attrs.append("machine(%s)" % command["machine"])
elif command["type"] == "config":attrs.append("host(%s)" % command["host"]) attrs.append("bg_id(%s)" % command["proc_id"])
attrs.append("machine(%s)" % command["machine"])
attrs.append("host(%s)" % command["host"]) if "option" in command: attrs.append("option(%s)" % command["option"])diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index 7324a7c..400cd19 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -105,8 +105,8 @@ class NetTestController: # There must be some machines specified in the recipe if "machines" not in recipe or \ ("machines" in recipe and len(recipe["machines"]) == 0):
msg = "No machines specified in the recipe. At least two " \"machines are required to perform a network test."
msg = "No hosts specified in the recipe. At least two " \"hosts are required to perform a network test." raise RecipeError(msg, recipe) # machine requirements@@ -122,7 +122,7 @@ class NetTestController: if "params" in machine: for p in machine["params"]: if p["name"] in params:
msg = "Parameter '%s' of machine %s was specified " \
msg = "Parameter '%s' of host %s was specified " \ "multiple times. Overriding the previous value." \ % (p["name"], m_id) logging.warn(RecipeError(msg, p))@@ -133,14 +133,14 @@ class NetTestController: # Each machine must have at least one interface if "interfaces" not in machine or \ ("interfaces" in machine and len(machine["interfaces"]) == 0):
msg = "Machine '%s' has no interfaces specified." % m_id
msg = "Hot '%s' has no interfaces specified." % m_id raise RecipeError(msg, machine) ifaces = {} for iface in machine["interfaces"]: if_id = iface["id"] if if_id in ifaces:
msg = "Interface with id='%s' already exists on machine " \
msg = "Interface with id='%s' already exists on host " \ "'%s'." % (if_id, m_id) iface_type = iface["type"]@@ -152,7 +152,7 @@ class NetTestController: for i in iface["params"]: if i["name"] in iface_params: msg = "Parameter '%s' of interface %s of " \
"machine %s was defined multiple times. " \
"host %s was defined multiple times. " \ "Overriding the previous value." \ % (i["name"], if_id, m_id) logging.warn(RecipeError(msg, p))@@ -197,14 +197,14 @@ class NetTestController:
if sp.is_setup_virtual() and os.geteuid() != 0: msg = "Provisioning this setup requires additional configuration "\
"of the virtual machines in the pool. LNST needs root "\
"of the virtual hosts in the pool. LNST needs root "\ "priviledges so it can connect to qemu." raise NetTestError(msg) logging.info("Provisioning initialized") for m_id in machines.keys(): provisioner = sp.get_provisioner_id(m_id)
logging.info(" machine %s uses %s" % (m_id, provisioner))
logging.info(" host %s uses %s" % (m_id, provisioner))def _prepare_machine(self, m_id, resource_sync=True): machine = self._machines[m_id]
@@ -236,7 +236,7 @@ class NetTestController: else: continue for cmd in task['commands']:
if 'machine' not in cmd or cmd['machine'] != m_id:
if 'host' not in cmd or cmd['host'] != m_id: continue if cmd['type'] == 'test': mod = cmd['module']@@ -318,10 +318,10 @@ class NetTestController: for cmd_data in task["commands"]: cmd = {"type": cmd_data["type"]}
if "machine" in cmd_data:cmd["machine"] = cmd_data["machine"]if cmd["machine"] not in self._machines:msg = "Invalid machine id '%s'." % cmd["machine"]
if "host" in cmd_data:cmd["host"] = cmd_data["host"]if cmd["host"] not in self._machines:msg = "Invalid host id '%s'." % cmd["host"] raise RecipeError(msg, cmd_data) if cmd["type"] in ["test", "exec"]:@@ -339,10 +339,10 @@ class NetTestController:
def _prepare_command(self, cmd_data): cmd = {"type": cmd_data["type"]}
if "machine" in cmd_data:cmd["machine"] = cmd_data["machine"]if cmd["machine"] not in self._machines:msg = "Invalid machine id '%s'." % cmd["machine"]
if "host" in cmd_data:cmd["host"] = cmd_data["host"]if cmd["host"] not in self._machines:msg = "Invalid host id '%s'." % cmd["host"] raise RecipeError(msg, cmd_data) if "expect" in cmd_data:@@ -412,7 +412,7 @@ class NetTestController: if command["type"] == "ctl_wait": continue
machine_id = command["machine"]
machine_id = command["host"] if not machine_id in bg_ids: bg_ids[machine_id] = set()@@ -423,7 +423,7 @@ class NetTestController: bg_ids[machine_id].remove(bg_id) else: logging.error("Found command "%s" for bg_id "%s" on "
"machine \"%s\" which was not previously "
"host \"%s\" which was not previously " "defined", cmd_type, bg_id, machine_id) err = True@@ -432,14 +432,14 @@ class NetTestController: if not bg_id in bg_ids[machine_id]: bg_ids[machine_id].add(bg_id) else:
logging.error("Command \"%d\" uses bg_id \"%s\" on machine "
logging.error("Command \"%d\" uses bg_id \"%s\" on host" "\"%s\" which is already used", i, bg_id, machine_id) err = True for machine_id in bg_ids: for bg_id in bg_ids[machine_id]:
logging.error("bg_id \"%s\" on machine \"%s\" has no kill/wait "
logging.error("bg_id \"%s\" on host \"%s\" has no kill/wait " "command to it", bg_id, machine_id) err = True@@ -664,7 +664,7 @@ class NetTestController: self._res_serializer.add_cmd_result(command, cmd_res) return cmd_res
machine_id = command["machine"]
machine_id = command["host"] machine = self._machines[machine_id] try:diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py index 7f2e6d1..a7702fc 100644 --- a/lnst/Controller/NetTestResultSerializer.py +++ b/lnst/Controller/NetTestResultSerializer.py @@ -86,9 +86,9 @@ class NetTestResultSerializer:
m_id_max = 0 for cmd, cmd_res in task:
if "machine" in cmd and\len(cmd["machine"]) > m_id_max:m_id_max = len(cmd["machine"])
if "host" in cmd and\len(cmd["host"]) > m_id_max:m_id_max = len(cmd["host"]) for cmd, cmd_res in task: self._format_command(cmd, cmd_res, output_pairs, m_id_max)@@ -100,9 +100,9 @@ class NetTestResultSerializer: else: res = "FAIL"
if "machine" in command:m_id = "host %s: " % command["machine"]m_id += " " * (m_id_max - len(command["machine"]))
if "host" in command:m_id = "host %s: " % command["host"]m_id += " " * (m_id_max - len(command["host"])) else: #len("ctl") == 3; len("host ") == 5; 5-3 = 2 m_id = "ctl: " + " " * (m_id_max + 2)@@ -174,7 +174,7 @@ class NetTestResultSerializer: recipe_el.appendChild(err_el)
for task in recipe["tasks"]:
cmd_seq_el = doc.createElement("command_sequence")
cmd_seq_el = doc.createElement("task") recipe_el.appendChild(cmd_seq_el) for cmd, cmd_res in task:diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index 2b554d7..046d6d9 100644 --- a/lnst/Controller/RecipeParser.py +++ b/lnst/Controller/RecipeParser.py @@ -260,7 +260,7 @@ class RecipeParser(XmlParser):
def _process_run_cmd(self, cmd_tag): cmd = XmlData(cmd_tag)
cmd["machine"] = self._get_attribute(cmd_tag, "host")
cmd["host"] = self._get_attribute(cmd_tag, "host") has_module = self._has_attribute(cmd_tag, "module") has_command = self._has_attribute(cmd_tag, "command")@@ -300,7 +300,7 @@ class RecipeParser(XmlParser): def _process_config_cmd(self, cmd_tag): cmd = XmlData(cmd_tag) cmd["type"] = "config"
cmd["machine"] = self._get_attribute(cmd_tag, "host")
cmd["host"] = self._get_attribute(cmd_tag, "host") if self._has_attribute(cmd_tag, "persistent"): cmd["persistent"] = self._get_attribute(cmd_tag, "persistent")@@ -335,6 +335,6 @@ class RecipeParser(XmlParser): def _process_signal_cmd(self, cmd_tag): cmd = XmlData(cmd_tag) cmd["type"] = cmd_tag.tag
cmd["machine"] = self._get_attribute(cmd_tag, "host")
cmd["host"] = self._get_attribute(cmd_tag, "host") cmd["bg_id"] = self._get_attribute(cmd_tag, "bg_id") return cmddiff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index 10c9f02..59f17c5 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -114,7 +114,7 @@ class HostAPI(object): :return: Command result. :rtype: dict """
cmd = {"machine": str(self._id), "type": "config"}
cmd = {"host": str(self._id), "type": "config"} cmd["options"] = [{"name": option, "value": value}] cmd["persistent"] = persistent@@ -139,7 +139,7 @@ class HostAPI(object): :return: A handle for process. :rtype: ProcessAPI """
cmd = {"machine": str(self._id)}
cmd = {"host": str(self._id)} bg_id = None for arg, argval in kwargs.iteritems():@@ -307,7 +307,7 @@ class ProcessAPI(object): def wait(self): """ Blocking wait until the command returns. """ if self._bg_id:
cmd = {"machine": self._host,
cmd = {"host": self._host, "type": "wait", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd)@@ -315,7 +315,7 @@ class ProcessAPI(object): def intr(self): """ Interrupt the command. """ if self._bg_id:
cmd = {"machine": self._host,
cmd = {"host": self._host, "type": "intr", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd)@@ -329,7 +329,7 @@ class ProcessAPI(object): to keep the results, use 'intr' instead. """ if self._bg_id:
cmd = {"machine": self._host,
cmd = {"host": self._host, "type": "kill", "proc_id": self._bg_id} self._res = self._ctl._run_command(cmd)-- 1.9.0
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
lnst-developers@lists.fedorahosted.org