Basicaly the whole parser was changed. The parser no longer directs the
execution of the controller. This means the controller hooks were
completely removed and all the semantic checks were moved to
NetTestController.
Apart from that, this commit also contains many structural changes in
the recipe XML file. The <commans_sequence> tag changed to <task>
and the all of the command were changed as well.
For the new format, please refer to the example recipes or to the
documentation that is available online.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
lnst/Controller/RecipeParse.py | 566 +++++++++++++++--------------------------
1 file changed, 207 insertions(+), 359 deletions(-)
diff --git a/lnst/Controller/RecipeParse.py b/lnst/Controller/RecipeParse.py
index e706597..b357124 100644
--- a/lnst/Controller/RecipeParse.py
+++ b/lnst/Controller/RecipeParse.py
@@ -16,8 +16,8 @@ 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, XmlData
-from lnst.Common.XmlProcessing import XmlProcessingError
+from lnst.Common.XmlProcessing import XmlDomTreeInit, XmlProcessingError
+from lnst.Common.XmlProcessing import XmlData, XmlCollection
from lnst.Common.XmlParser import LnstParser
class RecipeError(XmlProcessingError):
@@ -31,190 +31,95 @@ class RecipeParse(LnstParser):
self._rp = RecipePath(None, self._filepath)
self._include_root = self._rp.get_root()
- def first_pass(self):
+ def parse_recipe(self):
dom_init = XmlDomTreeInit()
rp = self._rp
- self._xml_dom = dom_init.parse_string(rp.to_str(), rp.abs_path())
-
- first_pass = FirstPass(self)
- first_pass.parse(self._xml_dom)
-
- def parse_recipe(self):
- self.first_pass()
- self._trigger_event("provisioning_requirements_ready", {})
-
- second_pass = SecondPass(self)
- second_pass.parse(self._xml_dom)
-
-
-class FirstPass(LnstParser):
- """
- Purpose of the first pass through the recipe is to detect
- machine requirements for provisioning.
-
- The purpose is generic, but the first pass exist only to
- detect provisioning at the moment.
- """
+ self._xml_dom = node = dom_init.parse_string(rp.to_str(), rp.abs_path())
- def parse(self, node):
if node.nodeType == node.DOCUMENT_NODE:
scheme = {"lnstrecipe": self._lnstrecipe}
- self._process_child_nodes(node, scheme,
- default_handler=self._ignore_tag)
+ self._process_child_nodes(node, scheme)
else:
- raise XmlProcessingError("Passed object is not a XML document")
-
- def _lnstrecipe(self, node, params):
- scheme = {"machines": self._machines}
- self._process_child_nodes(node, scheme,
- default_handler=self._ignore_tag)
-
- def _machines(self, node, params):
- if not "machines" in self._data:
- self._data["machines"] = {}
-
- scheme = {"machine": self._machine}
- self._process_child_nodes(node, scheme,
- default_handler=self._ignore_tag)
-
- def _machine(self, node, params):
- m_id = self._get_attribute(node, "id")
- if not m_id in self._data["machines"]:
- self._data["machines"][m_id] = {}
-
- if not "params" in self._data["machines"][m_id]:
- self._data["machines"][m_id]["params"] = {}
-
- if not "interfaces" in self._data["machines"][m_id]:
- self._data["machines"][m_id]["interfaces"] = {}
-
- target = self._data["machines"][m_id]["params"]
- params = {"id": m_id, "target": target}
- scheme = {"params": self._params,
- "interfaces": self._interfaces}
- self._process_child_nodes(node, scheme, params,
- default_handler=self._ignore_tag)
-
- def _params(self, node, params):
- subparser = ParamsParse(self)
- subparser.set_params_dict(params["target"])
- subparser.parse(node)
-
- def _interfaces(self, node, params):
- params = {"id": params["id"]}
- 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, params)
-
- def _interface(self, node, params):
- m_id = params["id"]
- machine = self._data["machines"][m_id]
-
- if_id = self._get_attribute(node, "id")
- if_type = node.tagName
-
- if if_id in machine["interfaces"]:
- msg = "Two interfaces with the same id '%s', " % if_id
- raise XmlProcessingError(msg)
-
- # Matching works with eth devices only
- if if_type != "eth":
- return
-
- if not if_id in machine["interfaces"]:
- machine["interfaces"][if_id] = {}
- iface = machine["interfaces"][if_id]
+ raise ValueError("Passed object is not a XML document")
- iface["type"] = if_type
- iface["network"] = self._get_attribute(node, "network")
+ return self._data
- if not "params" in machine["interfaces"][if_id]:
- machine["interfaces"][if_id]["params"] = {}
-
- target = machine["interfaces"][if_id]["params"]
- params = {"target": target}
- scheme = {"params": self._params}
- self._process_child_nodes(node, scheme, params,
- default_handler=self._ignore_tag)
-
- def _ignore_tag(self, node, params):
- pass
-
-
-class SecondPass(LnstParser):
- """
- Second pass makes sure all recognized values from the recipe
- are properly saved into the self._data.
-
- This is where the real parsing is done.
- """
-
- def parse(self, xml_dom):
- if xml_dom.nodeType == xml_dom.DOCUMENT_NODE:
- scheme = {"lnstrecipe": self._lnstrecipe}
- self._process_child_nodes(xml_dom, scheme)
+ def _lnstrecipe(self, node, params):
+ if self._data == None:
+ self._data = XmlData(node)
else:
- raise XmlProcessingError("Passed object is not a XML document")
+ msg = "Only a single <lnstrecipe> tag allowed in the
document."
+ raise RecipeError(msg, node)
- def _lnstrecipe(self, node, params):
scheme = {"machines": self._machines,
"switches": self._switches,
- "command_sequence": self._command_sequence}
+ "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)
- subparser.set_type("host")
- subparser.parse(node)
+ 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)
- subparser.set_type("switch")
- subparser.parse(node)
-
- def _command_sequence(self, node, params):
- if not "sequences" in self._data:
- self._data["sequences"] = []
+ s = subparser.parse(node)
+ self._data["switches"].append(s)
- subparser = CommandSequenceParse(self)
- subparser.parse(node)
+ 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):
- _target = "machines"
-
- def set_type(self, machine_type):
- if machine_type == "host":
- self._target = "machines"
- elif machine_type == "switch":
- self._target = "switches"
- else:
- raise XmlProcessingError("Unknown machine type")
-
def parse(self, node):
- self._id = self._get_attribute(node, "id")
-
- recipe = self._data
- self._machine = recipe["machines"][self._id]
- self._machine["netconfig"] = {}
+ self._data = XmlData(node)
+ self._data["id"] = self._get_attribute(node, "id")
- scheme = {"params": self._ignore_tag,
+ 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,
@@ -225,29 +130,15 @@ class MachineParse(LnstParser):
def _interface(self, node, params):
subparser = InterfaceParse(self)
- subparser.set_machine(self._id, self._machine)
- subparser.parse(node)
-
- def _ignore_tag(self, node, params):
- pass
+ iface = subparser.parse(node)
+ self._data["interfaces"].append(iface)
class InterfaceParse(LnstParser):
- _machine_id = None
- _machine = None
- _iface = None
-
- def set_machine(self, machine_id, machine):
- self._machine_id = machine_id
- self._machine = machine
-
def parse(self, node):
- if_id = self._get_attribute(node, "id")
-
- if not if_id in self._machine["interfaces"]:
- self._machine["interfaces"][if_id] = {}
- self._iface = iface = self._machine["interfaces"][if_id]
+ self._data = iface = XmlData(node)
+ self._data["id"] = if_id = self._get_attribute(node, "id")
- iface["type"] = node.tagName
+ iface["type"] = str(node.tagName)
scheme = {"addresses": self._addresses}
if iface["type"] in ["bond", "bridge",
"vlan", "macvlan", "team"]:
@@ -257,22 +148,24 @@ class InterfaceParse(LnstParser):
if self._has_attribute(node, "network"):
msg = "Attribute network is not supported by type '%s'
" + \
"interfaces" % iface["type"]
- raise XmlProcessingError(msg)
+ raise RecipeError(msg, node)
elif iface["type"] == "eth":
iface["network"] = self._get_attribute(node, "network")
- scheme["params"] = self._ignore_tag
+ scheme["params"] = self._params
self._process_child_nodes(node, scheme)
- try:
- event_params = {"machine_id": self._machine_id, "if_id":
if_id}
- self._trigger_event("interface_config_ready", event_params)
- except Exception as exc:
- msg = "Unable to configure interface %s on machine %s [%s]." % \
- (if_id, self._machine_id, str(exc))
- logging.error(XmlProcessingError(str(msg), node))
- raise
+ 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})
@@ -283,70 +176,75 @@ class InterfaceParse(LnstParser):
else:
addr = self._get_text_content(node)
- self._iface["addresses"].append(addr)
+ self._data["addresses"].append(addr)
def _options(self, node, params):
self._list_init(node, params, "options", {"option":
self._option})
def _option(self, node, params):
- name = self._get_attribute(node, "name")
+ option = XmlData(node)
+ option["name"] = self._get_attribute(node, "name")
if self._has_attribute(node, "value"):
- value = self._get_attribute(node, "value")
+ option["value"] = self._get_attribute(node, "value")
else:
- value = self._get_text_content(node)
+ option["value"] = self._get_text_content(node)
- self._iface["options"].append((name, value))
+ self._data["options"].append(option)
def _slaves(self, node, params):
- self._iface["slave_options"] = {}
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")
+ slave["id"] = self._get_attribute(node, "id")
else:
- slave_id = self._get_text_content(node)
-
- self._iface["slaves"].append(slave_id)
-
- self._iface["slave_options"][slave_id] = []
- self._slave_opts = self._iface["slave_options"][slave_id]
+ 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):
- name = self._get_attribute(node, "name")
+ option = XmlData(node)
+ option["name"] = self._get_attribute(node, "name")
if self._has_attribute(node, "value"):
- value = self._get_attribute(node, "value")
+ option["value"] = self._get_attribute(node, "value")
else:
- value = self._get_text_content(node)
+ option["value"] = self._get_text_content(node)
- self._slave_opts.append((name, value))
+ params["slave"]["options"].append(option)
def _list_init(self, node, params, node_name, scheme):
- self._iface[node_name] = []
+ 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)
- def _ignore_tag(self, node, params):
- pass
-
class ParamsParse(LnstParser):
- _params = None
-
- def set_target(self, target):
- self._params = target
-
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")
@@ -359,190 +257,140 @@ class ParamsParse(LnstParser):
param = XmlData(node)
param["name"] = name
param["value"] = value
- self._params.append(param)
+ self._data.append(param)
-class CommandSequenceParse(LnstParser):
+class TaskParse(LnstParser):
def parse(self, node):
- sequences = self._data["sequences"]
- sequences.append({})
- seq_num = len(sequences) - 1
- sequences[seq_num]["commands"] = []
-
- self._seq_num = seq_num
- self._seq_node = node
+ self._data = commands = XmlCollection(node)
+ task = XmlData(node)
+ task["commands"] = commands
if self._has_attribute(node, "quit_on_fail"):
- quit_on_fail = self._get_attribute(node, "quit_on_fail")
- sequences[seq_num]["quit_on_fail"] = quit_on_fail
- else:
- sequences[seq_num]["quit_on_fail"] = "no"
+ task["quit_on_fail"] = self._get_attribute(node,
"quit_on_fail")
+
+ if self._has_attribute(node, "label"):
+ task["label"] = self._get_attribute(node, "label")
+
+ scheme = {"config": self._config,
+ "exec": self._shell_cmd,
+ "test": self._test,
+ "ctl_wait": self._ctl_wait,
+ "wait": self._wait,
+ "intr": self._intr,
+ "kill": self._kill}
- scheme = {"command": self._command}
self._process_child_nodes(node, scheme)
+ return task
+
+ def _options(self, node, params):
+ subparser = OptionsParse(self)
+ opts = subparser.parse(node)
+ params["cmd"]["options"] = opts
- self._check_sequence(sequences[seq_num])
-
- def _command(self, node, params):
- subparser = CommandParse(self)
- subparser.set_seq_num(self._seq_num)
- subparser.parse(node)
-
- def _check_sequence(self, sequence):
- err = False
- bg_ids = {}
- for i, command in enumerate(sequence["commands"]):
- machine_id = command["machine_id"]
- if not machine_id in bg_ids:
- bg_ids[machine_id] = set()
-
- cmd_type = command["type"]
- if cmd_type in ["wait", "intr", "kill"]:
- bg_id = command["value"]
- if bg_id in bg_ids[machine_id]:
- bg_ids[machine_id].remove(bg_id)
- else:
- logging.error("Found command \"%s\" for bg_id
\"%s\" on "
- "machine \"%s\" which was not previously
"
- "defined", cmd_type, bg_id, machine_id)
- err = True
-
- if "bg_id" in command:
- bg_id = command["bg_id"]
- 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 "
- "\"%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 "
- "command to it", bg_id, machine_id)
- err = True
- if err:
- msg = "Incorrect command sequence"
- raise XmlProcessingError(msg, self._seq_node)
-
-
-class CommandParse(LnstParser):
- _seq_num = None
- _cmd_num = None
-
- def set_seq_num(self, num):
- self._seq_num = num
+ def _config(self, node, params):
+ cmd = XmlData(node)
+ cmd["type"] = "config"
+ cmd["machine"] = self._get_attribute(node, "machine")
- def parse(self, node):
- recipe = self._data
- command = {}
-
recipe["sequences"][self._seq_num]["commands"].append(command)
- self._cmd_num =
len(recipe["sequences"][self._seq_num]["commands"]) - 1
+ if self._has_attribute(node, "persistent"):
+ cmd["persistent"] = self._get_attribute(node,
"persistent")
- if self._has_attribute(node, "machine_id"):
- machine_id = self._get_attribute(node, "machine_id")
+ 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.", node)
else:
- machine_id = None
+ params = {"cmd": cmd}
+ scheme = {"options": self._options}
+ self._process_child_nodes(node, scheme, params)
- if machine_id and machine_id not in recipe["machines"]:
- raise XmlProcessingError("Invalid machine_id", node)
+ self._data.append(cmd)
- command["machine_id"] = machine_id
- command["type"] = self._get_attribute(node, "type")
+ def _shell_cmd(self, node, params):
+ cmd = XmlData(node)
+ cmd["type"] = "exec"
+ cmd["machine"] = self._get_attribute(node, "machine")
- if (command["type"] != "ctl_wait" and not machine_id) or\
- (machine_id and machine_id not in recipe["machines"]):
- raise XmlProcessingError("Invalid machine_id", node)
+ cmd["command"] = self._get_attribute(node, "command")
- command["value"] = None
- if self._has_attribute(node, "value"):
- command["value"] = self._get_attribute(node, "value")
+ if self._has_attribute(node, "from"):
+ cmd["from"] = self._get_attribute(node, "from")
- if self._has_attribute(node, "pass_result"):
- command["pass_result"] = self._get_attribute(node,
"pass_result",
- bool_it)
- if command["type"] == "system_config":
- msg = "Invalid attribute pass_result for command
system_config"
- raise XmlProcessingError(msg, node)
+ if self._has_attribute(node, "bg_id"):
+ cmd["bg_id"] = self._get_attribute(node, "bg_id")
if self._has_attribute(node, "timeout"):
- command["timeout"] = self._get_attribute(node, "timeout",
int)
+ cmd["timeout"] = self._get_attribute(node, "timeout")
- if self._has_attribute(node, "bg_id"):
- command["bg_id"] = self._get_attribute(node, "bg_id")
+ if self._has_attribute(node, "pass_result"):
+ cmd["pass_result"] = self._get_attribute(node,
"pass_result")
- if command["type"] in ["system_config", "wait",
"intr", "kill"]:
- msg = "Command %s cannot be run in the background!" \
- % command["type"]
- raise XmlProcessingError(msg, node)
+ self._data.append(cmd)
- if self._has_attribute(node, "desc"):
- command["desc"] = self._get_attribute(node, "desc")
+ def _test(self, node, params):
+ cmd = XmlData(node)
+ cmd["type"] = "test"
+ cmd["machine"] = self._get_attribute(node, "machine")
- if command["type"] == "system_config":
- if self._has_attribute(node, "option"):
- command["option"] = self._get_attribute(node,
"option")
+ cmd["module"] = self._get_attribute(node, "module")
- if self._has_attribute(node, "persistent"):
- command["persistent"] = self._get_attribute(node,
"persistent",
- bool_it)
- else:
- command["persistent"] = False
- elif command["type"] == "exec":
- if self._has_attribute(node, "from"):
- command["from"] = self._get_attribute(node, "from")
- elif command["type"] == "ctl_wait":
- if command["machine_id"] != None:
- msg = "Invalid attribute machine_id for command ctl_wait"
- raise XmlProcessingError(msg, node)
-
- try:
- command["value"] = int(command["value"])
- except ValueError:
- msg = "Invalid value for command ctl_wait"
- raise XmlProcessingError(msg, node)
- for key in command.keys():
- if key != "type" and key != "value" and\
- key != "desc" and key != "machine_id":
- msg = "Invalid attribute %s for command ctl_wait" % key
- raise XmlProcessingError(msg, node)
+ 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, "pass_result"):
+ cmd["pass_result"] = self._get_attribute(node,
"pass_result")
+
+ params = {"cmd": cmd}
scheme = {"options": self._options}
- self._process_child_nodes(node, scheme)
+ self._process_child_nodes(node, scheme, params)
- def _options(self, node, params):
- seq = self._seq_num
- cmd = self._cmd_num
-
self._data["sequences"][seq]["commands"][cmd]["options"] =
{}
+ self._data.append(cmd)
- scheme = {"option": self._option}
- self._process_child_nodes(node, scheme)
+ 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 _option(self, node, params):
- seq = self._seq_num
- cmd = self._cmd_num
- options =
self._data["sequences"][seq]["commands"][cmd]["options"]
+ 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
- name = self._get_attribute(node, "name")
- if not name in options:
- options[name] = []
+ def _intr(self, node, param):
+ self._signal_cmd(node, "intr")
- option = {}
- options[name].append(option)
+ def _kill(self, node, param):
+ self._signal_cmd(node, "kill")
- if self._has_attribute(node, "type"):
- opt_type = self._get_attribute(node, "type")
- option["type"] = opt_type
- else:
- opt_type = "default"
+ def _wait(self, node, param):
+ self._signal_cmd(node, "wait")
- if opt_type == "default":
- if self._has_attribute(node, "value"):
- value = self._get_attribute(node, "value")
- else:
- value = self._get_text_content(node)
+class OptionsParse(LnstParser):
+ def parse(self, node):
+ self._data = opts = XmlCollection(node)
- option["value"] = value
- else:
- msg = "Unknown option type \"%s\"" % opt_type
- raise XmlProcessingError(msg, 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)
--
1.8.3.1