The RecipeParse class was renamed to RecipeParser and completely
rewritten to use the lxml etree instead of the Document Object Model for
processing XML files.
The etree supports document validation using the RelaxNG schema. Using
this lead to significant simplification of the parsing code.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
lnst/Controller/NetTestController.py | 7 +-
lnst/Controller/RecipeParse.py | 394 -----------------------------------
lnst/Controller/RecipeParser.py | 255 +++++++++++++++++++++++
3 files changed, 259 insertions(+), 397 deletions(-)
delete mode 100644 lnst/Controller/RecipeParse.py
create mode 100644 lnst/Controller/RecipeParser.py
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index fee06ed..805346c 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -28,7 +28,7 @@ from lnst.Common.Utils import wait_for, md5sum, dir_md5sum,
create_tar_archive
from lnst.Common.Utils import check_process_running, bool_it
from lnst.Common.NetTestCommand import NetTestCommandContext, NetTestCommand
from lnst.Common.NetTestCommand import str_command, CommandException
-from lnst.Controller.RecipeParse import RecipeParse, RecipeError
+from lnst.Controller.RecipeParser import RecipeParser, RecipeError
from lnst.Controller.SlavePool import SlavePool
from lnst.Controller.Machine import Machine, MachineError
from lnst.Common.ConnectionHandler import send_data, recv_data
@@ -64,9 +64,9 @@ class NetTestController:
mac_pool_range = lnst_config.get_option('environment',
'mac_pool_range')
self._mac_pool = MacPool(mac_pool_range[0], mac_pool_range[1])
- parser = RecipeParse(recipe_path)
+ parser = RecipeParser(recipe_path)
parser.set_machines(self._machines)
- self._recipe = parser.parse_recipe()
+ self._recipe = parser.parse()
modules_dirs = lnst_config.get_option('environment',
'module_dirs')
tools_dirs = lnst_config.get_option('environment', 'tool_dirs')
@@ -255,6 +255,7 @@ class NetTestController:
if not os.path.isfile(path):
msg = "Task file '%s' not found." % path
raise RecipeError(msg, task_data)
+ continue
task["commands"] = []
for cmd_data in task_data["commands"]:
diff --git a/lnst/Controller/RecipeParse.py b/lnst/Controller/RecipeParse.py
deleted file mode 100644
index 8e0aed7..0000000
--- a/lnst/Controller/RecipeParse.py
+++ /dev/null
@@ -1,394 +0,0 @@
-"""
-This module defines RecipeParse class useful to parse xml recipes
-
-Copyright 2011 Red Hat, Inc.
-Licensed under the GNU General Public License, version 2 as
-published by the Free Software Foundation; see COPYING for details.
-"""
-
-__author__ = """
-jpirko(a)redhat.com (Jiri Pirko)
-"""
-
-import logging
-import os
-import re
-from lnst.Common.NetUtils import normalize_hwaddr
-from lnst.Common.Utils import bool_it
-from lnst.Common.RecipePath import RecipePath
-from lnst.Common.XmlProcessing import XmlDomTreeInit, XmlProcessingError
-from lnst.Common.XmlProcessing import XmlData, XmlCollection
-from lnst.Common.XmlParser import LnstParser
-
-class RecipeError(XmlProcessingError):
- pass
-
-class RecipeParse(LnstParser):
- def __init__(self, recipe_filepath):
- super(RecipeParse, self).__init__()
-
- self._filepath = recipe_filepath
- self._rp = RecipePath(None, self._filepath)
- self._include_root = self._rp.get_root()
-
- def parse_recipe(self):
- dom_init = XmlDomTreeInit()
- rp = self._rp
- self._xml_dom = node = dom_init.parse_string(rp.to_str(), rp.abs_path())
-
- if node.nodeType == node.DOCUMENT_NODE:
- scheme = {"lnstrecipe": self._lnstrecipe}
- self._process_child_nodes(node, scheme)
- else:
- raise ValueError("Passed object is not a XML document")
-
- return self._data
-
- def _lnstrecipe(self, node, params):
- if self._data == None:
- self._data = XmlData(node)
- else:
- msg = "Only a single <lnstrecipe> tag allowed in the
document."
- raise RecipeError(msg, node)
-
- scheme = {"machines": self._machines,
- "switches": self._switches,
- "task": self._task}
- self._process_child_nodes(node, scheme)
-
- def _machines(self, node, params):
- if "machines" not in self._data:
- self._data["machines"] = XmlCollection(node)
- else:
- msg = "Only a single <machines> child allowed in
<lnstrecipe>."
- raise RecipeError(msg, node)
-
- scheme = {"machine": self._machine}
- self._process_child_nodes(node, scheme)
-
- def _machine(self, node, params):
- subparser = MachineParse(self)
- m = subparser.parse(node)
- self._data["machines"].append(m)
-
- def _switches(self, node, params):
- if "switches" not in self._data:
- self._data["switches"] = XmlCollection(node)
- else:
- msg = "Only a single <switches> child allowed in
<lnstrecipe>."
- raise RecipeError(msg, node)
-
- scheme = {"switch": self._switch}
- self._process_child_nodes(node, scheme)
-
- def _switch(self, node, params):
- subparser = MachineParse(self)
- s = subparser.parse(node)
- self._data["switches"].append(s)
-
- def _task(self, node, params):
- if "tasks" not in self._data:
- self._data["tasks"] = XmlCollection(node)
-
- subparser = TaskParse(self)
- task = subparser.parse(node)
- self._data["tasks"].append(task)
-
-class MachineParse(LnstParser):
- def parse(self, node):
- self._data = XmlData(node)
- self._data["id"] = self._get_attribute(node, "id")
-
- scheme = {"params": self._params,
- "interfaces": self._interfaces}
- self._process_child_nodes(node, scheme)
-
- return self._data
-
- def _params(self, node, params):
- if "params" in self._data:
- msg = "Only a single <params> child allowed under
<machine>."
- raise RecipeError(msg, node)
-
- subparser = ParamsParse(self)
- self._data["params"] = subparser.parse(node)
-
- def _interfaces(self, node, params):
- if "interfaces" not in self._data:
- self._data["interfaces"] = XmlCollection(node)
- else:
- msg = "Only a single <interfaces> child allowed under
<machine>."
- raise RecipeError(msg, node)
-
- scheme = {"eth": self._interface,
- "bond": self._interface,
- "team": self._interface,
- "vlan": self._interface,
- "macvlan": self._interface,
- "bridge": self._interface}
- self._process_child_nodes(node, scheme)
-
- def _interface(self, node, params):
- subparser = InterfaceParse(self)
- iface = subparser.parse(node)
- self._data["interfaces"].append(iface)
-
-class InterfaceParse(LnstParser):
- def parse(self, node):
- self._data = iface = XmlData(node)
- self._data["id"] = if_id = self._get_attribute(node, "id")
-
- iface["type"] = str(node.tagName)
-
- scheme = {"addresses": self._addresses}
- if iface["type"] in ["bond", "bridge",
"vlan", "macvlan", "team"]:
- scheme["slaves"] = self._slaves
- scheme["options"] = self._options
-
- if self._has_attribute(node, "network"):
- msg = "Attribute network is not supported by type '%s'
" + \
- "interfaces" % iface["type"]
- raise RecipeError(msg, node)
- elif iface["type"] == "eth":
- iface["network"] = self._get_attribute(node, "network")
-
- scheme["params"] = self._params
-
- self._process_child_nodes(node, scheme)
-
- return iface
-
- def _params(self, node, params):
- if "params" in self._data:
- msg = "Only a single <params> child allowed under
<%s>." \
- % self._data["type"]
- raise RecipeError(msg, node)
-
- subparser = ParamsParse(self)
- self._data["params"] = subparser.parse(node)
-
- def _addresses(self, node, params):
- self._list_init(node, params, "addresses", {"address":
self._address})
-
- def _address(self, node, params):
- if self._has_attribute(node, "value"):
- addr = self._get_attribute(node, "value")
- else:
- addr = self._get_text_content(node)
-
- self._data["addresses"].append(addr)
-
- def _options(self, node, params):
- self._list_init(node, params, "options", {"option":
self._option})
-
- def _option(self, node, params):
- option = XmlData(node)
- option["name"] = self._get_attribute(node, "name")
-
- if self._has_attribute(node, "value"):
- option["value"] = self._get_attribute(node, "value")
- else:
- option["value"] = self._get_text_content(node)
-
- self._data["options"].append(option)
-
- def _slaves(self, node, params):
- self._list_init(node, params, "slaves", {"slave":
self._slave})
-
- def _slave(self, node, params):
- slave = XmlData(node)
- if self._has_attribute(node, "id"):
- slave["id"] = self._get_attribute(node, "id")
- else:
- slave["id"] = self._get_text_content(node)
-
- scheme = {"options": self._slave_options}
- params = {"slave": slave}
- self._process_child_nodes(node, scheme, params)
-
- self._data["slaves"].append(slave)
-
- def _slave_options(self, node, params):
- if "options" not in params["slave"]:
- params["slave"]["options"] = XmlCollection(node)
- else:
- msg = "Only a single <options> child allowed under
<slave>."
- raise RecipeError(msg, node)
-
- scheme = {"option": self._slave_option}
- self._process_child_nodes(node, scheme, params)
-
- def _slave_option(self, node, params):
- option = XmlData(node)
- option["name"] = self._get_attribute(node, "name")
-
- if self._has_attribute(node, "value"):
- option["value"] = self._get_attribute(node, "value")
- else:
- option["value"] = self._get_text_content(node)
-
- params["slave"]["options"].append(option)
-
- def _list_init(self, node, params, node_name, scheme):
- if node_name not in self._data:
- self._data[node_name] = XmlCollection(node)
- else:
- msg = "Only a single <%s> child allowed under <%s>." \
- % (node_name, self._data["type"])
- raise RecipeError(msg, node)
-
- self._process_child_nodes(node, scheme, params)
-
-class ParamsParse(LnstParser):
- def parse(self, node):
- self._data = XmlCollection(node)
- scheme = {"param": self._param}
- self._process_child_nodes(node, scheme)
- return self._data
-
- def _param(self, node, params):
- name = self._get_attribute(node, "name")
-
- if self._has_attribute(node, "value"):
- value = self._get_attribute(node, "value")
- else:
- value = self._get_text_content(node)
-
- param = XmlData(node)
- param["name"] = name
- param["value"] = value
- self._data.append(param)
-
-class TaskParse(LnstParser):
- def parse(self, node):
- self._data = commands = XmlCollection(node)
- task = XmlData(node)
- task["commands"] = commands
-
- if self._has_attribute(node, "quit_on_fail"):
- task["quit_on_fail"] = self._get_attribute(node,
"quit_on_fail")
-
- if self._has_attribute(node, "label"):
- task["label"] = self._get_attribute(node, "label")
-
- if self._has_attribute(node, "python"):
- task["python"] = self._get_attribute(node, "python")
- return task
-
- scheme = {"config": self._config,
- "run": self._run,
- "ctl_wait": self._ctl_wait,
- "wait": self._wait,
- "intr": self._intr,
- "kill": self._kill}
-
- self._process_child_nodes(node, scheme)
- return task
-
- def _options(self, node, params):
- subparser = OptionsParse(self)
- opts = subparser.parse(node)
- params["cmd"]["options"] = opts
-
- def _config(self, node, params):
- cmd = XmlData(node)
- cmd["type"] = "config"
- cmd["machine"] = self._get_attribute(node, "machine")
-
- if self._has_attribute(node, "persistent"):
- cmd["persistent"] = self._get_attribute(node,
"persistent")
-
- if self._has_attribute(node, "option"):
- cmd["options"] = XmlCollection(node)
- if self._has_attribute(node, "value"):
- opt = XmlData(node)
- opt["name"] = self._get_attribute(node, "option")
- opt["value"] = self._get_attribute(node, "value")
-
- cmd["options"] = XmlCollection(node)
- cmd["options"].append(opt)
- else:
- raise RecipeError("Missing option value.", cmd)
- else:
- params = {"cmd": cmd}
- scheme = {"options": self._options}
- self._process_child_nodes(node, scheme, params)
-
- self._data.append(cmd)
-
- def _run(self, node, params):
- cmd = XmlData(node)
-
- has_module = self._has_attribute(node, "module")
- has_command = self._has_attribute(node, "command")
- has_from = self._has_attribute(node, "from")
-
- if (has_module and has_command) or (has_module and has_from):
- msg = "Invalid combination of attributes."
- raise RecipeError(msg, cmd)
-
- if has_module:
- cmd["type"] = "test"
- cmd["module"] = self._get_attribute(node, "module")
-
- params = {"cmd": cmd}
- scheme = {"options": self._options}
- self._process_child_nodes(node, scheme, params)
- elif has_command:
- cmd["type"] = "exec"
- cmd["command"] = self._get_attribute(node, "command")
-
- if self._has_attribute(node, "from"):
- cmd["from"] = self._get_attribute(node, "from")
-
- cmd["machine"] = self._get_attribute(node, "machine")
-
- if self._has_attribute(node, "bg_id"):
- cmd["bg_id"] = self._get_attribute(node, "bg_id")
-
- if self._has_attribute(node, "timeout"):
- cmd["timeout"] = self._get_attribute(node, "timeout")
-
- if self._has_attribute(node, "expect"):
- cmd["expect"] = self._get_attribute(node, "expect")
-
- self._data.append(cmd)
-
- def _ctl_wait(self, node, param):
- cmd = XmlData(node)
- cmd["type"] = "ctl_wait"
- cmd["seconds"] = self._get_attribute(node, "seconds")
- self._data.append(cmd)
-
- def _signal_cmd(self, node, signal_name):
- cmd = XmlData(node)
- cmd["type"] = signal_name
- cmd["machine"] = self._get_attribute(node, "machine")
- cmd["bg_id"] = self._get_attribute(node, "bg_id")
- self._data.append(cmd)
- return cmd
-
- def _intr(self, node, param):
- self._signal_cmd(node, "intr")
-
- def _kill(self, node, param):
- self._signal_cmd(node, "kill")
-
- def _wait(self, node, param):
- self._signal_cmd(node, "wait")
-
-class OptionsParse(LnstParser):
- def parse(self, node):
- self._data = opts = XmlCollection(node)
-
- scheme = {"option": self._option}
- self._process_child_nodes(node, scheme)
-
- return self._data
-
- def _option(self, node, params):
- option = XmlData(node)
- option["name"] = self._get_attribute(node, "name")
- option["value"] = self._get_attribute(node, "value")
-
- self._data.append(option)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py
new file mode 100644
index 0000000..9bebe8c
--- /dev/null
+++ b/lnst/Controller/RecipeParser.py
@@ -0,0 +1,255 @@
+"""
+This module defines RecipeParser class useful to parse xml recipes
+
+Copyright 2013 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+rpazdera(a)redhat.com (Radek Pazdera)
+"""
+
+import logging
+import os
+import re
+import sys
+from lnst.Common.Config import lnst_config
+from lnst.Common.NetUtils import normalize_hwaddr
+from lnst.Common.Utils import bool_it
+from lnst.Common.RecipePath import RecipePath
+from lnst.Common.XmlParser import XmlParser
+from lnst.Common.XmlProcessing import XmlProcessingError, XmlData, XmlCollection
+from lnst.Common.XmlTemplates import XmlTemplates, XmlTemplateError
+
+class RecipeError(XmlProcessingError):
+ pass
+
+class RecipeParser(XmlParser):
+ def __init__(self, recipe_path):
+ recipe_path = RecipePath(None, recipe_path).abs_path()
+ super(RecipeParser, self).__init__("schema-recipe.rng", recipe_path)
+
+ def _process(self, lnst_recipe):
+ recipe = XmlData(lnst_recipe)
+
+ # machines
+ machines_tag = lnst_recipe.find("machines")
+ if machines_tag is not None:
+ machines = recipe["machines"] = XmlCollection(machines_tag)
+ for machine_tag in machines_tag:
+ machines.append(self._process_machine(machine_tag))
+
+ # tasks
+ tasks = recipe["tasks"] = XmlCollection()
+ task_tags = lnst_recipe.findall("task")
+ for task_tag in task_tags:
+ tasks.append(self._process_task(task_tag))
+
+ return recipe
+
+ def _process_machine(self, machine_tag):
+ machine = XmlData(machine_tag)
+ machine["id"] = self._get_attribute(machine_tag, "id")
+
+ # params
+ params_tag = machine_tag.find("params")
+ params = self._process_params(params_tag)
+ if len(params) > 0:
+ machine["params"] = params
+
+ # interfaces
+ interfaces_tag = machine_tag.find("interfaces")
+ if interfaces_tag is not None and len(interfaces_tag) > 0:
+ machine["interfaces"] = XmlCollection(interfaces_tag)
+ for interface_tag in interfaces_tag:
+ interface = self._process_interface(interface_tag)
+ machine["interfaces"].append(interface)
+
+ return machine
+
+ def _process_params(self, params_tag):
+ params = XmlCollection(params_tag)
+ if params_tag is not None:
+ for param_tag in params_tag:
+ param = XmlData(param_tag)
+ param["name"] = self._get_attribute(param_tag,
"name")
+ param["value"] = self._get_attribute(param_tag,
"value")
+ params.append(param)
+
+ return params
+
+ def _process_interface(self, iface_tag):
+ iface = XmlData(iface_tag)
+ iface["id"] = self._get_attribute(iface_tag, "id")
+ iface["type"] = iface_tag.tag
+
+ if iface["type"] == "eth":
+ iface["network"] = self._get_attribute(iface_tag,
"network")
+
+ # params
+ params_tag = iface_tag.find("params")
+ params = self._process_params(params_tag)
+ if len(params) > 0:
+ iface["params"] = params
+
+ # addresses
+ addresses_tag = iface_tag.find("addresses")
+ if addresses_tag is not None and len(addresses_tag) > 0:
+ iface["addresses"] = XmlCollection(addresses_tag)
+ for addr_tag in addresses_tag:
+ if self._has_attribute(addr_tag, "value"):
+ addr = self._get_attribute(addr_tag, "value")
+ else:
+ addr = self._get_content(addr_tag)
+ iface["addresses"].append(addr)
+
+
+ if iface["type"] in ["bond", "bridge",
"vlan", "macvlan", "team"]:
+ # slaves
+ slaves_tag = iface_tag.find("slaves")
+ if slaves_tag is not None and len(slaves_tag) > 0:
+ iface["slaves"] = XmlCollection(slaves_tag)
+ for slave_tag in slaves_tag:
+ slave = XmlData(slave_tag)
+ slave["id"] = self._get_attribute(slave_tag,
"id")
+
+ # slave options
+ opts_tag = slave_tag.find("options")
+ opts = self._proces_options(opts_tag)
+ if len(opts) > 0:
+ slave["options"] = opts
+
+ iface["slaves"].append(slave)
+
+ # interface options
+ opts_tag = iface_tag.find("options")
+ opts = self._proces_options(opts_tag)
+ if len(opts) > 0:
+ iface["options"] = opts
+
+ return iface
+
+ def _proces_options(self, opts_tag):
+ options = XmlCollection(opts_tag)
+ if opts_tag is not None:
+ for opt_tag in opts_tag:
+ opt = XmlData(opt_tag)
+ opt["name"] = self._get_attribute(opt_tag, "name")
+ if self._has_attribute(opt_tag, "value"):
+ opt["value"] = self._get_attribute(opt_tag,
"value")
+ else:
+ opt["value"] = self._get_content(opt_tag)
+ options.append(opt)
+
+ return options
+
+ def _process_task(self, task_tag):
+ task = XmlData(task_tag)
+
+ if self._has_attribute(task_tag, "quit_on_fail"):
+ task["quit_on_fail"] = self._get_attribute(task_tag,
"quit_on_fail")
+
+ if self._has_attribute(task_tag, "python"):
+ task["python"] = self._get_attribute(task_tag, "python")
+ return task
+
+ if len(task_tag) > 0:
+ task["commands"] = XmlCollection(task_tag)
+ for cmd_tag in task_tag:
+ if cmd_tag.tag == "run":
+ cmd = self._process_run_cmd(cmd_tag)
+ elif cmd_tag.tag == "config":
+ cmd = self._process_config_cmd(cmd_tag)
+ elif cmd_tag.tag == "ctl_wait":
+ cmd = self._process_ctl_wait_cmd(cmd_tag)
+ elif cmd_tag.tag in ["wait", "intr",
"kill"]:
+ cmd = self._process_signal_cmd(cmd_tag)
+ else:
+ msg = "Unknown command '%s'." % cmd_tag.tag
+ raise RecipeError(msg, cmd_tag)
+
+ task["commands"].append(cmd)
+
+ return task
+
+ def _process_run_cmd(self, cmd_tag):
+ cmd = XmlData(cmd_tag)
+ cmd["machine"] = self._get_attribute(cmd_tag, "machine")
+
+ has_module = self._has_attribute(cmd_tag, "module")
+ has_command = self._has_attribute(cmd_tag, "command")
+ has_from = self._has_attribute(cmd_tag, "from")
+
+ if (has_module and has_command) or (has_module and has_from):
+ msg = "Invalid combination of attributes."
+ raise RecipeError(msg, cmd)
+
+ if has_module:
+ cmd["type"] = "test"
+ cmd["module"] = self._get_attribute(cmd_tag, "module")
+
+ # options
+ opts_tag = cmd_tag.find("options")
+ opts = self._proces_options(opts_tag)
+ if len(opts) > 0:
+ cmd["options"] = opts
+ elif has_command:
+ cmd["type"] = "exec"
+ cmd["command"] = self._get_attribute(cmd_tag, "command")
+
+ if self._has_attribute(cmd_tag, "from"):
+ cmd["from"] = self._get_attribute(cmd_tag, "from")
+
+ if self._has_attribute(cmd_tag, "bg_id"):
+ cmd["bg_id"] = self._get_attribute(cmd_tag, "bg_id")
+
+ if self._has_attribute(cmd_tag, "timeout"):
+ cmd["timeout"] = self._get_attribute(cmd_tag, "timeout")
+
+ if self._has_attribute(cmd_tag, "expect"):
+ cmd["expect"] = self._get_attribute(cmd_tag, "expect")
+
+ return cmd
+
+ def _process_config_cmd(self, cmd_tag):
+ cmd = XmlData(cmd_tag)
+ cmd["type"] = "config"
+ cmd["machine"] = self._get_attribute(cmd_tag, "machine")
+
+ if self._has_attribute(cmd_tag, "persistent"):
+ cmd["persistent"] = self._get_attribute(cmd_tag,
"persistent")
+
+ # inline option
+ if self._has_attribute(cmd_tag, "option"):
+ cmd["options"] = XmlCollection(cmd_tag)
+ if self._has_attribute(cmd_tag, "value"):
+ opt = XmlData(cmd_tag)
+ opt["name"] = self._get_attribute(cmd_tag, "option")
+ opt["value"] = self._get_attribute(cmd_tag, "value")
+
+ cmd["options"] = XmlCollection(cmd_tag)
+ cmd["options"].append(opt)
+ else:
+ raise RecipeError("Missing option value.", cmd)
+ else:
+ # options
+ opts_tag = cmd_tag.find("options")
+ opts = self._proces_options(opts_tag)
+ if len(opts) > 0:
+ cmd["options"] = opts
+
+ return cmd
+
+ def _process_ctl_wait_cmd(self, cmd_tag):
+ cmd = XmlData(cmd_tag)
+ cmd["type"] = "ctl_wait"
+ cmd["seconds"] = self._get_attribute(cmd_tag, "seconds")
+ return cmd
+
+ def _process_signal_cmd(self, cmd_tag):
+ cmd = XmlData(cmd_tag)
+ cmd["type"] = cmd_tag.tag
+ cmd["machine"] = self._get_attribute(cmd_tag, "machine")
+ cmd["bg_id"] = self._get_attribute(cmd_tag, "bg_id")
+ return cmd
--
1.8.3.1