[PATCH] XmlParser: Improved XInclude Error Reporting
by Radek Pazdera
Due to the way XInclude is implemented in lxml, we are unable to get the
origin of each tag in the document. This patch provides our own
simplified implementation of XInclude resolving that fixes this problem.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
lnst/Common/XmlParser.py | 115 ++++++++++++++++++++++++++++++++++------
lnst/Common/XmlProcessing.py | 36 +++++++++----
lnst/Controller/RecipeParser.py | 7 ---
3 files changed, 126 insertions(+), 32 deletions(-)
diff --git a/lnst/Common/XmlParser.py b/lnst/Common/XmlParser.py
index 68d3c76..c404c0b 100644
--- a/lnst/Common/XmlParser.py
+++ b/lnst/Common/XmlParser.py
@@ -11,13 +11,17 @@ rpazdera(a)redhat.com (Radek Pazdera)
"""
import os
+import re
import sys
import logging
+import copy
from lxml import etree
from lnst.Common.XmlTemplates import XmlTemplates, XmlTemplateError
from lnst.Common.XmlProcessing import XmlProcessingError, XmlData
class XmlParser(object):
+ XINCLUDE_RE = r"\{http\:\/\/www\.w3\.org\/[0-9]{4}\/XInclude\}include"
+
def __init__(self, schema_file, xml_path):
# locate the schema file
# try git path
@@ -39,9 +43,43 @@ class XmlParser(object):
self._schema = etree.RelaxNG(relaxng_doc)
def parse(self):
+ doc = self._parse(self._path)
+ self._remove_comments(doc)
+
+ # Due to a weird implementation of XInclude in lxml, the
+ # XmlParser resolves included documents on it's own.
+ #
+ # To be able to tell later on where each tag was located
+ # in the XML document, we add a '__file' attribute to
+ # each element of the tree during the parsing.
+ #
+ # However, these special attributes are of course not
+ # valid according to our schemas. To solve this, a copy of
+ # the tree is made and the '__file' attributes are removed
+ # before validation.
+ #
+ # XXX This is a *EXTREMELY* dirty hack. Ideas/proposals
+ # for cleaner solutions are more than welcome!
+ root_tag = self._init_loc(doc.getroot(), self._path)
+ self._expand_xinclude(root_tag, os.path.dirname(self._path))
+
+ self._template_proc.process_aliases(root_tag)
+
try:
- doc = etree.parse(self._path)
- doc.xinclude()
+ self._validate(doc)
+ except:
+ err = self._schema.error_log[0]
+ loc = {"file": os.path.basename(err.filename),
+ "line": err.line, "col": err.column}
+ exc = XmlProcessingError(err.message)
+ exc.set_loc(loc)
+ raise exc
+
+ return self._process(root_tag)
+
+ def _parse(self, path):
+ try:
+ doc = etree.parse(path)
except Exception as err:
# A workaround for cases when lxml (quite strangely)
# sets the filename to <string>.
@@ -56,20 +94,7 @@ class XmlParser(object):
exc.set_loc(loc)
raise exc
- root_tag = doc.getroot()
- self._template_proc.process_aliases(root_tag)
-
- try:
- self._schema.assertValid(doc)
- except:
- err = self._schema.error_log[0]
- loc = {"file": os.path.basename(err.filename),
- "line": err.line, "col": err.column}
- exc = XmlProcessingError(err.message)
- exc.set_loc(loc)
- raise exc
-
- return self._process(root_tag)
+ return doc
def _process(self, root_tag):
pass
@@ -86,3 +111,61 @@ class XmlParser(object):
def _get_content(self, element):
text = etree.tostring(element, method="text")
return self._template_proc.expand_functions(text)
+
+ def _expand_xinclude(self, elem, base_url=""):
+ for e in elem:
+ if re.match(self.XINCLUDE_RE, str(e.tag)):
+ href = os.path.join(base_url, e.get("href"))
+ filename = os.path.basename(href)
+
+ doc = self._parse(href)
+ self._remove_comments(doc)
+ node = doc.getroot()
+
+ node = self._init_loc(node, href)
+
+ if e.tail:
+ node.tail = (node.tail or "") + e.tail
+ self._expand_xinclude(node, os.path.dirname(href))
+
+ parent = e.getparent()
+ if parent is None:
+ return node
+
+ parent.replace(e, node)
+ return elem
+
+ def _remove_comments(self, doc):
+ comments = doc.xpath('//comment()')
+ for c in comments:
+ p = c.getparent()
+ if p is not None:
+ p.remove(c)
+
+ def _init_loc(self, elem, filename):
+ """ Remove all coment tags from the tree """
+
+ elem.attrib["__file"] = filename
+ for e in elem:
+ self._init_loc(e, os.path.basename(filename))
+
+ return elem
+
+ def _validate(self, original):
+ """
+ Make a copy of the tree, remove the '__file' attributes
+ and validate against the appropriate schema.
+
+ Very unfortunate solution.
+ """
+ doc = copy.deepcopy(original)
+ root = doc.getroot()
+
+ self._prepare_tree_for_validation(root)
+ self._schema.assertValid(doc)
+
+ def _prepare_tree_for_validation(self, elem):
+ if "__file" in elem.attrib:
+ del elem.attrib["__file"]
+ for e in elem:
+ self._prepare_tree_for_validation(e)
diff --git a/lnst/Common/XmlProcessing.py b/lnst/Common/XmlProcessing.py
index 5ea297d..771ead5 100644
--- a/lnst/Common/XmlProcessing.py
+++ b/lnst/Common/XmlProcessing.py
@@ -24,15 +24,21 @@ class XmlProcessingError(Exception):
super(XmlProcessingError, self).__init__()
self._msg = msg
- if obj is not None and hasattr(obj, "loc"):
- self.set_loc(obj.loc)
-
- loc = {}
- if obj is not None and hasattr(obj, "base") and obj.base != None:
- loc["file"] = os.path.basename(obj.base)
- if hasattr(obj, "sourceline"):
- loc["line"] = obj.sourceline
- self.set_loc(loc)
+ if obj is not None:
+ if hasattr(obj, "loc"):
+ self.set_loc(obj.loc)
+ elif hasattr(obj, "attrib") and "__file" in obj.attrib:
+ loc = {}
+ loc["file"] = obj.attrib["__file"]
+ if hasattr(obj, "sourceline"):
+ loc["line"] = obj.sourceline
+ self.set_loc(loc)
+ elif hasattr(obj, "base") and obj.base != None:
+ loc = {}
+ loc["file"] = os.path.basename(obj.base)
+ if hasattr(obj, "sourceline"):
+ loc["line"] = obj.sourceline
+ self.set_loc(loc)
def set_loc(self, loc):
@@ -89,6 +95,12 @@ class XmlCollection(list):
if node is not None:
if hasattr(node, "loc"):
self.loc = node.loc
+ elif "__file" in node.attrib:
+ loc = {}
+ loc["file"] = node.attrib["__file"]
+ if hasattr(node, "sourceline"):
+ loc["line"] = node.sourceline
+ self.loc = loc
elif hasattr(node, "base") and node.base != None:
loc = {}
loc["file"] = os.path.basename(node.base)
@@ -113,6 +125,12 @@ class XmlData(dict):
if node is not None:
if hasattr(node, "loc"):
self.loc = node.loc
+ elif "__file" in node.attrib:
+ loc = {}
+ loc["file"] = node.attrib["__file"]
+ if hasattr(node, "sourceline"):
+ loc["line"] = node.sourceline
+ self.loc = loc
elif hasattr(node, "base") and node.base != None:
loc = {}
loc["file"] = os.path.basename(node.base)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py
index 2bd6c38..2720359 100644
--- a/lnst/Controller/RecipeParser.py
+++ b/lnst/Controller/RecipeParser.py
@@ -34,13 +34,6 @@ class RecipeParser(XmlParser):
def _process(self, lnst_recipe):
recipe = XmlData(lnst_recipe)
- # remove all comments from the document
- comments = lnst_recipe.xpath('//comment()')
- for c in comments:
- p = c.getparent()
- if p is not None:
- p.remove(c)
-
# machines
machines_tag = lnst_recipe.find("machines")
if machines_tag is not None:
--
1.8.3.1
10 years, 7 months
[lnst] SlavePool: fix map_setup_virt method
by Jiří Pírko
commit e2e2721c569cfe157a7b5deaf47d940a5ab8103d
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 11 15:10:25 2013 +0200
SlavePool: fix map_setup_virt method
This commit reimplements the method _map_setup_virt because it turned
out that it didn't work properly for some cases. The problem was there
was no backtracking for machine matches, and therefore lnst wouldn't
find a match even if there was one.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/SlavePool.py | 51 +++++++++++++++++++++++++++--------------
1 files changed, 33 insertions(+), 18 deletions(-)
---
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py
index 686f20f..24724be 100644
--- a/lnst/Controller/SlavePool.py
+++ b/lnst/Controller/SlavePool.py
@@ -692,24 +692,39 @@ class SetupMapper:
return True
+ @staticmethod
+ def _get_machine_with_most_params(machines):
+ max_machine = None
+ max_len = 0
+ for m_id, m in machines.iteritems():
+ if len(m["params"]) >= max_len:
+ max_len = len(m["params"])
+ max_machine = m_id
+
+ return max_machine
+
def _map_setup_virt(self, template_machines, pool_machines):
- available = set()
- matches = set()
- for m_id in pool_machines.iterkeys():
- available.add(m_id)
-
- for tm_id, tm in template_machines.iteritems():
- match = None
- for am_id in available:
- if self._machine_matches(tm, pool_machines[am_id]):
- match = (tm_id, am_id)
- available.remove(am_id)
- break
+ if len(template_machines) <= 0:
+ return True
- if match:
- matches.add(match)
- else:
- return False
+ machine_id = self._get_machine_with_most_params(template_machines)
+ machine = template_machines[machine_id]
- self._machine_map = list(matches)
- return True
+ for pm_id, pm in pool_machines.iteritems():
+ if not self._machine_matches(machine, pm):
+ continue
+
+ self._machine_map.add((machine_id, pm_id))
+
+ new_pool = copy.deepcopy(pool_machines)
+ del new_pool[pm_id]
+
+ new_template = copy.deepcopy(template_machines)
+ del new_template[machine_id]
+
+ if self._map_setup_virt(new_template, new_pool):
+ return True
+
+ self._machine_map.discard((machine, possible_match))
+
+ return False
10 years, 7 months
[lnst] SlavePool: adjust how virtual matching is called
by Jiří Pírko
commit 816185e1c8b6efc226c012dda551b764cd0cdd2c
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 11 15:10:24 2013 +0200
SlavePool: adjust how virtual matching is called
This commit modifies how we fallback to matching virtual machines when
normal matching fails.
First of all there is now a logging message informing the user about
this happening.
Next we need to check the interfaces of all the machines that are
requested by the recipe for parameters. Parameters are used for matching
and restricting possible interfaces, however this doesn't make sense
when matching virtual devices since these have no parameters to set. The
only exception is the parameter 'hwaddr' which can be used to specify
what mac address should the created interface have.
And finally I added a filter of the pool machines so that only virtual
machines with the parameter 'libvirt_domain' defined are considered when
matching virtual machines - this was a bug that went unnoticed since we
all use virtual machines all the time.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/SlavePool.py | 34 ++++++++++++++++++++++++++++------
1 files changed, 28 insertions(+), 6 deletions(-)
---
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py
index c50cbe6..686f20f 100644
--- a/lnst/Controller/SlavePool.py
+++ b/lnst/Controller/SlavePool.py
@@ -608,13 +608,35 @@ class SetupMapper:
mmap = self._format_map_dict(machine_map, network_map)
mmap["virtual"] = False
return mmap
- elif self._map_setup_virt(template_machines, pool_machines):
- machine_map = [(tm, pm, []) for tm, pm in self._machine_map]
- mmap = self._format_map_dict(machine_map, [])
- mmap["virtual"] = True
- return mmap
else:
- return None
+ logging.info("Match failed for normal machines, falling back "\
+ "to matching virtual machines.")
+
+ for m_id, m in template_machines.iteritems():
+ for if_id, interface in m["interfaces"].iteritems():
+ if "params" in interface:
+ for name, val in interface["params"].iteritems():
+ if name != "hwaddr":
+ msg = "Dynamically created interfaces "\
+ "only support the 'hwaddr' option. "\
+ "'%s=%s' found on machine '%s' "\
+ "interface '%s'" % (name, val,
+ m_id, if_id)
+ raise MapperError(msg)
+
+ #filter machine pool to only contain virtual machines
+ virt_pool_machines = {}
+ for m_id, m in pool_machines.iteritems():
+ if "params" in m and "libvirt_domain" in m["params"]:
+ virt_pool_machines[m_id] = m
+
+ if self._map_setup_virt(template_machines, virt_pool_machines):
+ machine_map = [(tm, pm, []) for tm, pm in self._machine_map]
+ mmap = self._format_map_dict(machine_map, [])
+ mmap["virtual"] = True
+ return mmap
+ else:
+ return None
def _map_setup(self, template_topology, pool_topology):
10 years, 7 months
[lnst] SlavePool: add MapperError exception
by Jiří Pírko
commit 88de193c434f6fa60f88d3c83f1c49cef218da23
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 11 15:10:23 2013 +0200
SlavePool: add MapperError exception
This commit adds a new exception class- MapperError that can be used to
report errors that arise during machine matching. In a previous patch
where I fixed corner cases of machine matching I used SlaveMachineError
which wasn't ideal since that is used for reporting errors in
SlaveMachine xml files. So I replaced it with the MapperError.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/SlavePool.py | 7 +++++--
1 files changed, 5 insertions(+), 2 deletions(-)
---
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py
index 170c9ac..c50cbe6 100644
--- a/lnst/Controller/SlavePool.py
+++ b/lnst/Controller/SlavePool.py
@@ -254,6 +254,9 @@ class SlavePool:
return machine
+class MapperError(Exception):
+ pass
+
class SetupMapper:
"""
This class can be used for matching machine setups against
@@ -338,7 +341,7 @@ class SetupMapper:
if not net_in_use:
msg = "Network '%s' contains only one machine!" % net_name
- raise SlaveMachineError(msg)
+ raise MapperError(msg)
return topology
@@ -596,7 +599,7 @@ class SetupMapper:
for m, cons in template_topology.iteritems():
if len(cons) == 0:
msg = "Isolated machine in template topology: '%s'" % m
- raise SlaveMachineError(msg)
+ raise MapperError(msg)
if self._map_setup(template_topology, pool_topology):
machine_map = [(tm, pm, self._iface_map[tm]) \
10 years, 7 months
[lnst] SlavePool: fix exception when param not present
by Jiří Pírko
commit d8889358713ed1234be0dae93f3b23f6a0ce7116
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 11 15:10:22 2013 +0200
SlavePool: fix exception when param not present
If a machine in the recipe contained a parameter that was not defined
on a pool machine that was being compared lnst would fail with a
KeyError exception. This commit fixes that.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/SlavePool.py | 6 ++++--
1 files changed, 4 insertions(+), 2 deletions(-)
---
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py
index cd9e508..170c9ac 100644
--- a/lnst/Controller/SlavePool.py
+++ b/lnst/Controller/SlavePool.py
@@ -371,7 +371,8 @@ class SetupMapper:
# check machine properties
for prop_name, prop_value in template_machine["params"].iteritems():
- if pool_machine["params"][prop_name] != prop_value:
+ if prop_name not in pool_machine["params"] or\
+ pool_machine["params"][prop_name] != prop_value:
return False
# check number of devices
@@ -660,7 +661,8 @@ class SetupMapper:
def _machine_matches(self, tm, pm):
for prop_name, prop_value in tm["params"].iteritems():
- if pm["params"][prop_name] != prop_value:
+ if prop_name not in pm["params"] or\
+ pm["params"][prop_name] != prop_value:
return False
return True
10 years, 7 months
[lnst] NetTestCommand: set_fail bug fix
by Jiří Pírko
commit e31716d461a3b933225c9803287ba5409eb0d4df
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 11 15:08:01 2013 +0200
NetTestCommand: set_fail bug fix
This commit fixes the use of function set_fail in several places in the
NetTestCommand module. These changes were overlooked when the change in
result reporting happened, sorry for that...
For ExecCmd the message that was previously there was removed as we
agreed on the meeting. The stderr report log was moved into debug logs
as well.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Common/ExecCmd.py | 2 +-
lnst/Common/NetTestCommand.py | 9 ++++++---
2 files changed, 7 insertions(+), 4 deletions(-)
---
diff --git a/lnst/Common/ExecCmd.py b/lnst/Common/ExecCmd.py
index 174ea39..31f3759 100644
--- a/lnst/Common/ExecCmd.py
+++ b/lnst/Common/ExecCmd.py
@@ -61,7 +61,7 @@ def exec_cmd(cmd, die_on_err=True, log_outputs=True, report_stderr=False):
if data_stdout:
log_output(logging.debug, "Stdout", data_stdout)
if data_stderr:
- log_output(logging.error, "Stderr", data_stderr)
+ log_output(logging.debug, "Stderr", data_stderr)
if subp.returncode and die_on_err:
err = ExecCmdFail(cmd, subp.returncode, data_stderr,report_stderr)
logging.error(err)
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py
index d6f6f0f..965f675 100644
--- a/lnst/Common/NetTestCommand.py
+++ b/lnst/Common/NetTestCommand.py
@@ -378,7 +378,7 @@ class NetTestCommandExec(NetTestCommandGeneric):
logging.info("Command probably intentionally killed. Passing.")
self.set_pass()
else:
- self.set_fail("Command failed to execute")
+ self.set_fail()
def _format_cmd_res_header(self):
cmd_type = self._command["type"]
@@ -413,14 +413,17 @@ class NetTestCommandConfig(NetTestCommandGeneric):
option_abspath[0:6] != "/proc/":
err = "Wrong config option %s. Only /proc or /sys paths are " \
"allowed." % option
- self.set_fail(err)
+ res_data = {"msg": err}
+ self.set_fail(res_data)
return
try:
prev_val = self._retrive_option(option)
self._set_option(option, value)
except ExecCmdFail:
- self.set_fail("Unable to set %s config option!" % option)
+ err = "Unable to set %s config option!" % option
+ res_data = {"msg": err}
+ self.set_fail(res_data)
return
if "persistent" in self._command:
10 years, 7 months
[lnst] Adding prefix method to TaskAPI
by Jiří Pírko
commit 10bffc6f5d5106d36c4bd7d3e5e4df22ad5830fc
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Wed Sep 11 12:56:04 2013 +0200
Adding prefix method to TaskAPI
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/Task.py | 25 +++++++++++++++++++++++++
1 files changed, 25 insertions(+), 0 deletions(-)
---
diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py
index 7a8ff63..d42c47c 100644
--- a/lnst/Controller/Task.py
+++ b/lnst/Controller/Task.py
@@ -212,6 +212,23 @@ class MachineAPI(object):
iface = self._m.get_interface(interface_id)
return IpAddr(iface, addr_number)
+ def get_prefix(self, interface_id, addr_number=0):
+ """
+ Returns an IP address prefix (netmask)
+ of the interface.
+
+ :param interface_id: which interface
+ :type interface_id: string
+
+ :param interface_id: which address
+ :type interface_id: int
+
+ :return: netmask (e.g., 24).
+ :rtype: str
+ """
+ iface = self._m.get_interface(interface_id)
+ return Prefix(iface, addr_number)
+
class ModuleAPI(object):
""" An API class representing a module. """
@@ -309,3 +326,11 @@ class Hwaddr(ValueAPI):
class Devname(ValueAPI):
def _resolve(self):
return self._iface.get_devname()
+
+class Prefix(ValueAPI):
+ def __init__(self, iface, addr=0):
+ self._iface = iface
+ self._addr = addr
+
+ def _resolve(self):
+ return self._iface.get_prefix(self._addr)
10 years, 7 months
[lnst] Adding prefix template function
by Jiří Pírko
commit 686c68611e430f6ed08fdb7a1c6d919230df5530
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Wed Sep 11 12:56:03 2013 +0200
Adding prefix template function
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Common/XmlTemplates.py | 53 ++++++++++++++++++++++++++++++++++++++++++-
lnst/Controller/Machine.py | 11 ++++++++-
2 files changed, 62 insertions(+), 2 deletions(-)
---
diff --git a/lnst/Common/XmlTemplates.py b/lnst/Common/XmlTemplates.py
index 57acf00..41dbc0d 100644
--- a/lnst/Common/XmlTemplates.py
+++ b/lnst/Common/XmlTemplates.py
@@ -120,6 +120,56 @@ class DevnameFunc(TemplateFunc):
% (m_id, if_id)
raise XmlTemplateError(msg)
+class PrefixFunc(TemplateFunc):
+ def _check_args(self, args):
+ if len(args) > 3:
+ msg = "Function prefix() takes at most 3 arguments, %d passed" \
+ % len(args)
+ raise XmlTemplateError(msg)
+ if len(args) < 2:
+ msg = "Function prefix() must have at least 2 arguments, %d " \
+ "passed" % len(args)
+ raise XmlTemplateError(msg)
+
+ if len(args) == 3:
+ try:
+ int(args[2])
+ except ValueError:
+ msg = "The third argument of prefix() function must be an " \
+ "integer"
+ raise XmlTemplateError(msg)
+
+ def _implementation(self):
+ m_id = self._args[0]
+ if_id = self._args[1]
+ addr = 0
+ if len(self._args) == 3:
+ addr = self._args[2]
+
+ try:
+ machine = self._machines[m_id]
+ except KeyError:
+ msg = "First parameter of function prefix() is invalid: " \
+ "Machine %s does not exist." % m_id
+ raise XmlTemplateError(msg)
+
+ try:
+ iface = machine.get_interface(if_id)
+ except MachineError:
+ msg = "Second parameter of function prefix() is invalid: "\
+ "Interface %s does not exist." % if_id
+ raise XmlTemplateError(msg)
+
+ try:
+ return iface.get_prefix(int(addr))
+ except IndexError:
+ msg = "There is no address with index %s on machine %s, " \
+ "interface %s." % (addr, m_id, if_id)
+ raise XmlTemplateError(msg)
+ except PrefixMissingError:
+ msg = "Address with the index %s for the interface %s on machine" \
+ "%s does not contain any prefix" % (addr, m_id, if_id)
+
class HwaddrFunc(TemplateFunc):
def _check_args(self, args):
if len(args) != 2:
@@ -157,7 +207,8 @@ class XmlTemplates:
_alias_re = "\{\$([a-zA-Z0-9_]+)\}"
_func_re = "\{([a-zA-Z0-9_]+)\(([^\(\)]*)\)\}"
- _func_map = {"ip": IpFunc, "hwaddr": HwaddrFunc, "devname": DevnameFunc}
+ _func_map = {"ip": IpFunc, "hwaddr": HwaddrFunc, "devname": DevnameFunc, \
+ "prefix": PrefixFunc }
def __init__(self, definitions=None):
if definitions:
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py
index f212824..96f3657 100644
--- a/lnst/Controller/Machine.py
+++ b/lnst/Controller/Machine.py
@@ -32,6 +32,9 @@ from lnst.Common.ConnectionHandler import ConnectionHandler
class MachineError(Exception):
pass
+class PrefixMissingError(Exception):
+ pass
+
class Machine(object):
""" Slave machine abstraction
@@ -384,7 +387,13 @@ class Interface(object):
self._addresses.append(addr)
def get_address(self, num):
- return self._addresses[num]
+ return self._addresses[num].split('/')[0]
+
+ def get_prefix(self, num):
+ try:
+ return self._addresses[num].split('/')[1]
+ except IndexError:
+ raise PrefixMissingError
def _get_config(self):
config = {"hwaddr": self._hwaddr, "type": self._type,
10 years, 7 months
[PATCH 1/4] SlavePool: fix exception when param not present
by Ondrej Lichtner
From: Ondrej Lichtner <olichtne(a)redhat.com>
If a machine in the recipe contained a parameter that was not defined
on a pool machine that was being compared lnst would fail with a
KeyError exception. This commit fixes that.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
---
lnst/Controller/SlavePool.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py
index cd9e508..170c9ac 100644
--- a/lnst/Controller/SlavePool.py
+++ b/lnst/Controller/SlavePool.py
@@ -371,7 +371,8 @@ class SetupMapper:
# check machine properties
for prop_name, prop_value in template_machine["params"].iteritems():
- if pool_machine["params"][prop_name] != prop_value:
+ if prop_name not in pool_machine["params"] or\
+ pool_machine["params"][prop_name] != prop_value:
return False
# check number of devices
@@ -660,7 +661,8 @@ class SetupMapper:
def _machine_matches(self, tm, pm):
for prop_name, prop_value in tm["params"].iteritems():
- if pm["params"][prop_name] != prop_value:
+ if prop_name not in pm["params"] or\
+ pm["params"][prop_name] != prop_value:
return False
return True
--
1.8.3.1
10 years, 7 months
[PATCH] NetTestCommand: set_fail bug fix
by Ondrej Lichtner
From: Ondrej Lichtner <olichtne(a)redhat.com>
This commit fixes the use of function set_fail in several places in the
NetTestCommand module. These changes were overlooked when the change in
result reporting happened, sorry for that...
For ExecCmd the message that was previously there was removed as we
agreed on the meeting. The stderr report log was moved into debug logs
as well.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
---
lnst/Common/ExecCmd.py | 2 +-
lnst/Common/NetTestCommand.py | 9 ++++++---
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/lnst/Common/ExecCmd.py b/lnst/Common/ExecCmd.py
index 174ea39..31f3759 100644
--- a/lnst/Common/ExecCmd.py
+++ b/lnst/Common/ExecCmd.py
@@ -61,7 +61,7 @@ def exec_cmd(cmd, die_on_err=True, log_outputs=True, report_stderr=False):
if data_stdout:
log_output(logging.debug, "Stdout", data_stdout)
if data_stderr:
- log_output(logging.error, "Stderr", data_stderr)
+ log_output(logging.debug, "Stderr", data_stderr)
if subp.returncode and die_on_err:
err = ExecCmdFail(cmd, subp.returncode, data_stderr,report_stderr)
logging.error(err)
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py
index d6f6f0f..965f675 100644
--- a/lnst/Common/NetTestCommand.py
+++ b/lnst/Common/NetTestCommand.py
@@ -378,7 +378,7 @@ class NetTestCommandExec(NetTestCommandGeneric):
logging.info("Command probably intentionally killed. Passing.")
self.set_pass()
else:
- self.set_fail("Command failed to execute")
+ self.set_fail()
def _format_cmd_res_header(self):
cmd_type = self._command["type"]
@@ -413,14 +413,17 @@ class NetTestCommandConfig(NetTestCommandGeneric):
option_abspath[0:6] != "/proc/":
err = "Wrong config option %s. Only /proc or /sys paths are " \
"allowed." % option
- self.set_fail(err)
+ res_data = {"msg": err}
+ self.set_fail(res_data)
return
try:
prev_val = self._retrive_option(option)
self._set_option(option, value)
except ExecCmdFail:
- self.set_fail("Unable to set %s config option!" % option)
+ err = "Unable to set %s config option!" % option
+ res_data = {"msg": err}
+ self.set_fail(res_data)
return
if "persistent" in self._command:
--
1.8.3.1
10 years, 7 months