Hello everyone,
this is the final part of the refactoring/redesign that happened on the controller over the last month or two.
This series adds a dependency on python-lxml. However, the library is quite common and in wide-spread use.
Apart from the formating of some error messages, there should be no visible changes to LNST from the outside.
Cheers -Radek
Radek Pazdera (10): schema: Updating the cmd-sequence to task schema: Removing 'source' and changing 'defines' schema: Adding a schema for slavemachine xml install: Adding schemes to the distribution XmlParser: Moving the parser from DOM to etree XmlProcessing: Updating the objects to support etree XmlProcessing: Removing the XmlDomTreeInit XmlTemplates: Changing to support etree controller: Rewriting recipe parsing controller: Rewriting slavemachine parsing
install/lnst-ctl.conf.in | 65 +++-- lnst/Common/Config.py | 5 + lnst/Common/XmlParser.py | 303 ++++---------------- lnst/Common/XmlProcessing.py | 101 ++----- lnst/Common/XmlTemplates.py | 104 +++---- lnst/Controller/NetTestController.py | 7 +- lnst/Controller/RecipeParse.py | 394 -------------------------- lnst/Controller/RecipeParser.py | 255 +++++++++++++++++ lnst/Controller/SlaveMachineParse.py | 72 ----- lnst/Controller/SlaveMachineParser.py | 69 +++++ lnst/Controller/SlavePool.py | 18 +- recipe-schema.rng | 515 ---------------------------------- schema-recipe.rng | 391 ++++++++++++++++++++++++++ schema-sm.rng | 82 ++++++ setup.py | 8 +- 15 files changed, 995 insertions(+), 1394 deletions(-) delete mode 100644 lnst/Controller/RecipeParse.py create mode 100644 lnst/Controller/RecipeParser.py delete mode 100644 lnst/Controller/SlaveMachineParse.py create mode 100644 lnst/Controller/SlaveMachineParser.py delete mode 100644 recipe-schema.rng create mode 100644 schema-recipe.rng create mode 100644 schema-sm.rng
The command sequence changed to task. This patch updates the relax-ng scheme to reflect those changes.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- recipe-schema.rng | 299 ++++++++++++++++++++++-------------------------------- 1 file changed, 122 insertions(+), 177 deletions(-)
diff --git a/recipe-schema.rng b/recipe-schema.rng index 9f109af..23b2236 100644 --- a/recipe-schema.rng +++ b/recipe-schema.rng @@ -10,7 +10,7 @@ <ref name="machines"/>
<oneOrMore> - <ref name="command_sequence"/> + <ref name="task"/> </oneOrMore> </interleave> </element> @@ -49,10 +49,17 @@ </element> </define>
- <define name="command_sequence"> - <element name="command_sequence"> + <define name="task"> + <element name="task"> <optional> - <attribute name="source"/> + <attribute name="label"/> + </optional> + + <optional> + <choice> + <attribute name="source"/> + <attribute name="python"/> + </choice> </optional>
<optional> @@ -60,6 +67,8 @@ <choice> <value>yes</value> <value>no</value> + <value>true</value> + <value>false</value> </choice> </attribute> </optional> @@ -70,7 +79,22 @@ </zeroOrMore>
<zeroOrMore> - <ref name="command"/> + <ref name="config"/> + </zeroOrMore> + <zeroOrMore> + <ref name="run"/> + </zeroOrMore> + <zeroOrMore> + <ref name="wait"/> + </zeroOrMore> + <zeroOrMore> + <ref name="intr"/> + </zeroOrMore> + <zeroOrMore> + <ref name="kill"/> + </zeroOrMore> + <zeroOrMore> + <ref name="ctl_wait"/> </zeroOrMore> </interleave> </element> @@ -313,203 +337,124 @@ </element> </define>
- <define name="command"> - <element name="command"> - <choice> - <ref name="ctl_wait"/> - <ref name="exec"/> - <ref name="system_config"/> - <ref name="test"/> - <ref name="control_commands"/> - </choice> + <define name="config"> + <element name="config"> + <attribute name="machine"/>
<optional> - <ref name="options"/> + <attribute name="option"/> + <attribute name="value"/> </optional> - </element> - </define> - - <define name="ctl_wait"> - <attribute name="type"> - <value>ctl_wait</value> - </attribute> - - <optional> - <attribute name="value"> - <data type="integer"/> - </attribute> - </optional> - </define>
- <define name="exec"> - <attribute name="machine_id"/> - - <attribute name="type"> - <value>exec</value> - </attribute> - - <optional> - <attribute name="bg_id"/> - </optional> - - <optional> - <attribute name="value"/> - </optional> - - <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> - - <optional> - <attribute name="option"/> - </optional> + <!-- Should config have timeout? --> + <optional> + <attribute name="timeout"> + <data type="integer"/> + </attribute> + </optional>
- <optional> - <attribute name="desc"/> - </optional> + <optional> + <attribute name="persistent"> + <choice> + <value>true</value> + <value>false</value> + <value>yes</value> + <value>no</value> + <value>1</value> + <value>0</value> + </choice> + </attribute> + </optional>
- <optional> - <attribute name="from"/> - </optional> + <optional> + <attribute name="desc"/> + </optional>
- <optional> - <attribute name="source"/> - </optional> + <optional> + <attribute name="source"/> + </optional>
- <optional> - <attribute name="pass_result"> - <choice> - <value>true</value> - <value>false</value> - </choice> - </attribute> - </optional> + <optional> + <ref name="options"/> + </optional> + </element> </define>
- <define name="system_config"> - <attribute name="machine_id"/> + <define name="run"> + <element name="run"> + <attribute name="machine"/>
- <attribute name="type"> - <value>system_config</value> - </attribute> + <choice> + <attribute name="module"/> + <ref name="run_command"/> + </choice>
- <optional> - <attribute name="value"/> - </optional> + <optional> + <attribute name="bg_id"/> + </optional>
- <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> + <optional> + <attribute name="expect"/> + </optional>
- <optional> - <attribute name="option"/> - </optional> + <optional> + <attribute name="timeout"> + <data type="integer"/> + </attribute> + </optional>
- <optional> - <attribute name="persistent"> - <choice> - <value>true</value> - <value>false</value> - </choice> - </attribute> - </optional> + <optional> + <attribute name="desc"/> + </optional>
- <optional> - <attribute name="desc"/> - </optional> + <optional> + <attribute name="source"/> + </optional>
- <optional> - <attribute name="source"/> - </optional> + <optional> + <ref name="options"/> + </optional> + </element> </define>
- <define name="test"> - <attribute name="machine_id"/> - - <attribute name="type"> - <value>test</value> - </attribute> - - <optional> - <attribute name="bg_id"/> - </optional> - - <optional> - <attribute name="value"/> - </optional> - - <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> - - <optional> - <attribute name="option"/> - </optional> - - <optional> - <attribute name="desc"/> - </optional> - - <optional> - <attribute name="source"/> - </optional> - - <optional> - <attribute name="pass_result"> - <choice> - <value>true</value> - <value>false</value> - </choice> - </attribute> - </optional> + <define name="run_command"> + <choice> + <attribute name="command"/> + <ref name="run_tool"/> + </choice> </define>
- <define name="control_commands"> - <attribute name="machine_id"/> - - <attribute name="type"> - <choice> - <value>wait</value> - <value>intr</value> - <value>kill</value> - </choice> - </attribute> - - <optional> - <attribute name="value"/> - </optional> + <define name="run_tool"> + <attribute name="command"/> + <attribute name="from"/> + </define>
- <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> + <define name="wait"> + <element name="wait"> + <ref name="signal_command"/> + </element> + </define>
- <optional> - <attribute name="option"/> - </optional> + <define name="intr"> + <element name="intr"> + <ref name="signal_command"/> + </element> + </define>
- <optional> - <attribute name="desc"/> - </optional> + <define name="kill"> + <element name="kill"> + <ref name="signal_command"/> + </element> + </define>
- <optional> - <attribute name="source"/> - </optional> + <define name="signal_command"> + <attribute name="machine"/> + <attribute name="bg_id"/> + </define>
- <optional> - <attribute name="pass_result"> - <choice> - <value>true</value> - <value>false</value> - </choice> - </attribute> - </optional> + <define name="ctl_wait"> + <element name="ctl_wait"> + <attribute name="seconds"/> + </element> </define> </grammar>
The 'source' attribute will no longer be supported (it will be replaced by XInclude).
This patch also changes the 'define' tags in the scheme from zeroOrMore to optional, which results in better error reports.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- recipe-schema.rng | 460 ------------------------------------------------------ schema-recipe.rng | 391 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+), 460 deletions(-) delete mode 100644 recipe-schema.rng create mode 100644 schema-recipe.rng
diff --git a/recipe-schema.rng b/recipe-schema.rng deleted file mode 100644 index 23b2236..0000000 --- a/recipe-schema.rng +++ /dev/null @@ -1,460 +0,0 @@ -<grammar xmlns="http://relaxng.org/ns/structure/1.0" - datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes%22%3E - <start> - <element name="lnstrecipe"> - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <ref name="machines"/> - - <oneOrMore> - <ref name="task"/> - </oneOrMore> - </interleave> - </element> - </start> - - <define name="define"> - <element name="define"> - <optional> - <attribute name="source"/> - </optional> - <zeroOrMore> - <element name="alias"> - <attribute name="name"/> - <attribute name="value"/> - <text/> - </element> - </zeroOrMore> - </element> - </define> - - <define name="machines"> - <element name="machines"> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <oneOrMore> - <ref name="machine"/> - </oneOrMore> - </interleave> - </element> - </define> - - <define name="task"> - <element name="task"> - <optional> - <attribute name="label"/> - </optional> - - <optional> - <choice> - <attribute name="source"/> - <attribute name="python"/> - </choice> - </optional> - - <optional> - <attribute name="quit_on_fail"> - <choice> - <value>yes</value> - <value>no</value> - <value>true</value> - <value>false</value> - </choice> - </attribute> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <ref name="config"/> - </zeroOrMore> - <zeroOrMore> - <ref name="run"/> - </zeroOrMore> - <zeroOrMore> - <ref name="wait"/> - </zeroOrMore> - <zeroOrMore> - <ref name="intr"/> - </zeroOrMore> - <zeroOrMore> - <ref name="kill"/> - </zeroOrMore> - <zeroOrMore> - <ref name="ctl_wait"/> - </zeroOrMore> - </interleave> - </element> - </define> - - <define name="machine"> - <element name="machine"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <ref name="params"/> - </zeroOrMore> - - <element name="interfaces"> - <optional> - <attribute name="source"/> - </optional> - <zeroOrMore> - <choice> - <ref name="eth"/> - <ref name="bond"/> - <ref name="bridge"/> - <ref name="vlan"/> - <ref name="macvlan"/> - <ref name="team"/> - </choice> - </zeroOrMore> - </element> - </interleave> - </element> - </define> - - <define name="eth"> - <element name="eth"> - <attribute name="id"/> - <attribute name="network"/> - <optional> - <attribute name="source"/> - </optional> - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <ref name="params"/> - </zeroOrMore> - - <optional> - <ref name="addresses"/> - </optional> - </interleave> - </element> - </define> - - <define name="bond"> - <element name="bond"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - <ref name="softdevice"/> - </element> - </define> - - <define name="bridge"> - <element name="bridge"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - <ref name="softdevice"/> - </element> - </define> - - <define name="vlan"> - <element name="vlan"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - <ref name="softdevice"/> - </element> - </define> - - <define name="macvlan"> - <element name="macvlan"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - <ref name="softdevice"/> - </element> - </define> - - <define name="team"> - <element name="team"> - <attribute name="id"/> - <optional> - <attribute name="source"/> - </optional> - <ref name="softdevice"/> - </element> - </define> - - <define name="softdevice"> - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <optional> - <ref name="options"/> - </optional> - - <optional> - <element name="slaves"> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <element name="slave"> - <attribute name="id"/> - - <optional> - <attribute name="source"/> - </optional> - - <optional> - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <ref name="options"/> - </zeroOrMore> - </interleave> - </optional> - - </element> - </zeroOrMore> - </interleave> - </element> - </optional> - - <optional> - <ref name="addresses"/> - </optional> - </interleave> - </define> - - <define name="options"> - <element name="options"> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <element name="option"> - <optional> - <attribute name="source"/> - </optional> - <attribute name="name"/> - <choice> - <attribute name="value"/> - <text/> - </choice> - </element> - </zeroOrMore> - </interleave> - </element> - </define> - - <define name="addresses"> - <element name="addresses"> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <element name="address"> - <optional> - <attribute name="source"/> - </optional> - <attribute name="value"/> - <text/> - </element> - </zeroOrMore> - </interleave> - </element> - </define> - - <define name="params"> - <element name="params"> - <optional> - <attribute name="source"/> - </optional> - - <interleave> - <zeroOrMore> - <ref name="define"/> - </zeroOrMore> - - <zeroOrMore> - <element name="param"> - <optional> - <attribute name="source"/> - </optional> - <attribute name="name"/> - <attribute name="value"/> - <text/> - </element> - </zeroOrMore> - </interleave> - </element> - </define> - - <define name="config"> - <element name="config"> - <attribute name="machine"/> - - <optional> - <attribute name="option"/> - <attribute name="value"/> - </optional> - - <!-- Should config have timeout? --> - <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> - - <optional> - <attribute name="persistent"> - <choice> - <value>true</value> - <value>false</value> - <value>yes</value> - <value>no</value> - <value>1</value> - <value>0</value> - </choice> - </attribute> - </optional> - - <optional> - <attribute name="desc"/> - </optional> - - <optional> - <attribute name="source"/> - </optional> - - <optional> - <ref name="options"/> - </optional> - </element> - </define> - - <define name="run"> - <element name="run"> - <attribute name="machine"/> - - <choice> - <attribute name="module"/> - <ref name="run_command"/> - </choice> - - <optional> - <attribute name="bg_id"/> - </optional> - - <optional> - <attribute name="expect"/> - </optional> - - <optional> - <attribute name="timeout"> - <data type="integer"/> - </attribute> - </optional> - - <optional> - <attribute name="desc"/> - </optional> - - <optional> - <attribute name="source"/> - </optional> - - <optional> - <ref name="options"/> - </optional> - </element> - </define> - - <define name="run_command"> - <choice> - <attribute name="command"/> - <ref name="run_tool"/> - </choice> - </define> - - <define name="run_tool"> - <attribute name="command"/> - <attribute name="from"/> - </define> - - <define name="wait"> - <element name="wait"> - <ref name="signal_command"/> - </element> - </define> - - <define name="intr"> - <element name="intr"> - <ref name="signal_command"/> - </element> - </define> - - <define name="kill"> - <element name="kill"> - <ref name="signal_command"/> - </element> - </define> - - <define name="signal_command"> - <attribute name="machine"/> - <attribute name="bg_id"/> - </define> - - <define name="ctl_wait"> - <element name="ctl_wait"> - <attribute name="seconds"/> - </element> - </define> -</grammar> diff --git a/schema-recipe.rng b/schema-recipe.rng new file mode 100644 index 0000000..7e417cb --- /dev/null +++ b/schema-recipe.rng @@ -0,0 +1,391 @@ +<grammar xmlns="http://relaxng.org/ns/structure/1.0" + datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes%22%3E + <start> + <element name="lnstrecipe"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <ref name="machines"/> + + <oneOrMore> + <ref name="task"/> + </oneOrMore> + </interleave> + </element> + </start> + + <define name="define"> + <element name="define"> + <oneOrMore> + <element name="alias"> + <attribute name="name"/> + <attribute name="value"/> + <text/> + </element> + </oneOrMore> + </element> + </define> + + <define name="machines"> + <element name="machines"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <oneOrMore> + <ref name="machine"/> + </oneOrMore> + </interleave> + </element> + </define> + + <define name="task"> + <element name="task"> + <optional> + <attribute name="label"/> + </optional> + + <optional> + <attribute name="python"/> + </optional> + + <optional> + <attribute name="quit_on_fail"> + <choice> + <value>yes</value> + <value>no</value> + <value>true</value> + <value>false</value> + </choice> + </attribute> + </optional> + + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="config"/> + </zeroOrMore> + <zeroOrMore> + <ref name="run"/> + </zeroOrMore> + <zeroOrMore> + <ref name="wait"/> + </zeroOrMore> + <zeroOrMore> + <ref name="intr"/> + </zeroOrMore> + <zeroOrMore> + <ref name="kill"/> + </zeroOrMore> + <zeroOrMore> + <ref name="ctl_wait"/> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="machine"> + <element name="machine"> + <attribute name="id"/> + + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="params"/> + </zeroOrMore> + + <element name="interfaces"> + <zeroOrMore> + <choice> + <ref name="eth"/> + <ref name="bond"/> + <ref name="bridge"/> + <ref name="vlan"/> + <ref name="macvlan"/> + <ref name="team"/> + </choice> + </zeroOrMore> + </element> + </interleave> + </element> + </define> + + <define name="eth"> + <element name="eth"> + <attribute name="id"/> + <attribute name="network"/> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="params"/> + </zeroOrMore> + + <optional> + <ref name="addresses"/> + </optional> + </interleave> + </element> + </define> + + <define name="bond"> + <element name="bond"> + <attribute name="id"/> + <ref name="softdevice"/> + </element> + </define> + + <define name="bridge"> + <element name="bridge"> + <attribute name="id"/> + <ref name="softdevice"/> + </element> + </define> + + <define name="vlan"> + <element name="vlan"> + <attribute name="id"/> + <ref name="softdevice"/> + </element> + </define> + + <define name="macvlan"> + <element name="macvlan"> + <attribute name="id"/> + <ref name="softdevice"/> + </element> + </define> + + <define name="team"> + <element name="team"> + <attribute name="id"/> + <ref name="softdevice"/> + </element> + </define> + + <define name="softdevice"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <optional> + <ref name="options"/> + </optional> + + <optional> + <element name="slaves"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <element name="slave"> + <attribute name="id"/> + + <optional> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="options"/> + </zeroOrMore> + </interleave> + </optional> + + </element> + </zeroOrMore> + </interleave> + </element> + </optional> + + <optional> + <ref name="addresses"/> + </optional> + </interleave> + </define> + + <define name="options"> + <element name="options"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <element name="option"> + <attribute name="name"/> + <choice> + <attribute name="value"/> + <text/> + </choice> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="addresses"> + <element name="addresses"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <element name="address"> + <attribute name="value"/> + <text/> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="params"> + <element name="params"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <element name="param"> + <attribute name="name"/> + <choice> + <attribute name="value"/> + <text/> + </choice> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="config"> + <element name="config"> + <attribute name="machine"/> + + <optional> + <attribute name="option"/> + <attribute name="value"/> + </optional> + + <!-- Should config have timeout? --> + <optional> + <attribute name="timeout"> + <data type="integer"/> + </attribute> + </optional> + + <optional> + <attribute name="persistent"> + <choice> + <value>true</value> + <value>false</value> + <value>yes</value> + <value>no</value> + <value>1</value> + <value>0</value> + </choice> + </attribute> + </optional> + + <optional> + <attribute name="desc"/> + </optional> + + <optional> + <ref name="options"/> + </optional> + </element> + </define> + + <define name="run"> + <element name="run"> + <attribute name="machine"/> + + <choice> + <attribute name="module"/> + <ref name="run_command"/> + </choice> + + <optional> + <attribute name="bg_id"/> + </optional> + + <optional> + <attribute name="expect"/> + </optional> + + <optional> + <attribute name="timeout"> + <data type="integer"/> + </attribute> + </optional> + + <optional> + <attribute name="desc"/> + </optional> + + <optional> + <ref name="options"/> + </optional> + </element> + </define> + + <define name="run_command"> + <choice> + <attribute name="command"/> + <ref name="run_tool"/> + </choice> + </define> + + <define name="run_tool"> + <attribute name="command"/> + <attribute name="from"/> + </define> + + <define name="wait"> + <element name="wait"> + <ref name="signal_command"/> + </element> + </define> + + <define name="intr"> + <element name="intr"> + <ref name="signal_command"/> + </element> + </define> + + <define name="kill"> + <element name="kill"> + <ref name="signal_command"/> + </element> + </define> + + <define name="signal_command"> + <attribute name="machine"/> + <attribute name="bg_id"/> + </define> + + <define name="ctl_wait"> + <element name="ctl_wait"> + <attribute name="seconds"/> + </element> + </define> +</grammar>
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- schema-sm.rng | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 schema-sm.rng
diff --git a/schema-sm.rng b/schema-sm.rng new file mode 100644 index 0000000..454dde1 --- /dev/null +++ b/schema-sm.rng @@ -0,0 +1,82 @@ +<grammar xmlns="http://relaxng.org/ns/structure/1.0" + datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes%22%3E + <start> + <element name="slavemachine"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="params"/> + </zeroOrMore> + + <zeroOrMore> + <ref name="interfaces"/> + </zeroOrMore> + </interleave> + </element> + </start> + + <define name="define"> + <element name="define"> + <oneOrMore> + <element name="alias"> + <attribute name="name"/> + <attribute name="value"/> + <text/> + </element> + </oneOrMore> + </element> + </define> + + <define name="params"> + <element name="params"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <element name="param"> + <attribute name="name"/> + <choice> + <attribute name="value"/> + <text/> + </choice> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="interfaces"> + <element name="interfaces"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="eth"/> + </zeroOrMore> + </interleave> + </element> + </define> + + <define name="eth"> + <element name="eth"> + <attribute name="id"/> + <attribute name="network"/> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <oneOrMore> + <ref name="params"/> + </oneOrMore> + </interleave> + </element> + </define> +</grammar>
The schemes need to be installed with the controller so it can use them during the runtime. This patch adds them to the setup.py script.
By default, they are stored in /usr/share/lnst/, but the path can be configured in the controller config file using the 'resource_dir' option.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- install/lnst-ctl.conf.in | 65 +++++++++++++++++++++++++++--------------------- lnst/Common/Config.py | 5 ++++ setup.py | 8 ++++-- 3 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/install/lnst-ctl.conf.in b/install/lnst-ctl.conf.in index 576a562..c95a264 100644 --- a/install/lnst-ctl.conf.in +++ b/install/lnst-ctl.conf.in @@ -1,45 +1,52 @@ -#This file is used for system-wide controller configuration. It serves mainly -#to set the default values which will be used when no user configuration is -#found. Users should set their own configuration in the file +# This file is used for system-wide controller configuration. It serves mainly +# to set the default values which will be used when no user configuration is +# found. Users should set their own configuration in the file # $HOME$/.lnst/lnst-ctl.conf which will be automatically created when it -#doesn't exist. +# doesn't exist.
-#The section environment contains generic options mostly related to the -#machine the controller is being run on. +# The section environment contains generic options mostly related to the +# machine the controller is being run on. [environment]
-#mac_pool_range is used to specify the range of mac addresses that -#will be used when creating new devices in virtual machines. -#The option accepts two valid mac addresses separated by white spaces. +# 'mac_pool_range' is used to specify the range of mac addresses that will +# be used when creating new devices in virtual machines. The option accepts +# two valid mac addresses separated by white spaces. mac_pool_range = 52:54:01:00:00:01 52:54:01:FF:FF:FF
-#The rpcport options specifies the default port we will be connecting to, when -#connecting to slave rpc servers. -#The option accepts a single integer value. +# The 'rpcport' options specifies the default port we will be connecting to, +# when connecting to slave rpc servers. The option accepts a single integer +# value. rpcport = 9999
-#machine_pool_dirs specifies where the controller should look for specifications -#of machines that can be used for matching templates inside recipes. This -#option accepts a variable number of directory paths separated by white spaces. -#It is also important to note that you can use the operator += to add to the -#list of directories. If the operator = is used, the previous list is replaced. +# This option specifies where the controller should look for specifications +# of machines that can be used for matching templates inside recipes. It +# accepts a variable number of directory paths separated by white spaces. +# It is also important to note that you can use the operator += to add to +# the list of directories. If the operator = is used, the previous list is +# replaced. machine_pool_dirs =
-#test_tool_dirs specifies where the controller looks for custom tools that -#are used in tests. Every tool has it's own subdirectory in one of the -#directories in this list. The option behaves the same way as the option -#machine_pool_dirs, it accepts a list of directory paths and both operators, +# 'test_tool_dirs' specifies where the controller looks for custom tools that +# are used in tests. Every tool has it's own subdirectory in one of the +# directories in this list. The option behaves the same way as the option +# machine_pool_dirs, it accepts a list of directory paths and both operators, # += and = can be used test_tool_dirs = @ctl_tools_locations@
-#test_module_dirs specifies where the controller looks for custom test modules -#the test modules need to be named Test<name>.py otherwise they won't be -#recognized. The behaviour of the option is the same as test_tool_dirs, it -#accepts a list of directory paths and both operators, += and = can be used. +# 'test_module_dirs' specifies where the controller looks for custom test +# modules the test modules need to be named Test<name>.py otherwise they won't +# be recognized. The behaviour of the option is the same as test_tool_dirs, it +# accepts a list of directory paths and both operators, += and = can be used. test_module_dirs = @ctl_modules_locations@
-#log_dir option redirects the location of where logs are stored. Every run of -#the application creates its own timestamped subdirectory in the path specified -#in this option. The option accepts one directory path that will be created if -#it doesn't exist. You cannot use the operator += here. +# The 'resource_dir' option specifies, where are the generic resources +# for LNST (such as XML schemas) installed. This option is set during the +# installation and generally doesn't need to be changed (unless you know +# what you're doing!). +resource_dir = @ctl_resource_dir@ + +# The 'log_dir' option redirects the location of where logs are stored. Every +# run of the application creates its own timestamped subdirectory in the path +# specified in this option. The option accepts one directory path that will +# be created if it doesn't exist. You cannot use the operator += here. log_dir = @ctl_logs_dir@ diff --git a/lnst/Common/Config.py b/lnst/Common/Config.py index 67921d4..40e70da 100644 --- a/lnst/Common/Config.py +++ b/lnst/Common/Config.py @@ -64,6 +64,11 @@ class Config(): "additive" : False, "action" : self.optionPath, "name" : "log_dir"} + self.options['environment']['resource_dir'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPath, + "name" : "resource_dir"}
self.colours_scheme()
diff --git a/setup.py b/setup.py index 9fc6ebe..8f12344 100755 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ MAN_DIR = "/usr/share/man/man1/"
CTL_MODULES_LOCATIONS = "/usr/share/lnst/test_modules/" CTL_TOOLS_LOCATIONS = "/usr/share/lnst/test_tools/" +CTL_RESOURCE_DIR = "/usr/share/lnst/" CTL_LOGS_DIR = "~/.lnst/logs/"
SLAVE_LOGS_DIR = "/var/log/lnst" @@ -57,6 +58,7 @@ TEMPLATES_VALUES = {
"ctl_modules_locations": CTL_MODULES_LOCATIONS, "ctl_tools_locations": CTL_TOOLS_LOCATIONS, +"ctl_resource_dir": CTL_RESOURCE_DIR, "ctl_logs_dir": CTL_LOGS_DIR,
"slave_logs_dir": SLAVE_LOGS_DIR, @@ -143,9 +145,11 @@ MULTICAST_TEST_TOOLS = [
MAN_PAGES = [(MAN_DIR, ["install/lnst-ctl.1.gz", "install/lnst-slave.1.gz"])]
-CONFIG = [("/etc/", ["install/lnst-ctl.conf", "install/lnst-slave.conf"])] +CONFIG = [(CONF_DIR, ["install/lnst-ctl.conf", "install/lnst-slave.conf"])]
-DATA_FILES = CONFIG + TEST_MODULES + MULTICAST_TEST_TOOLS + MAN_PAGES +SCHEMAS = [(CTL_RESOURCE_DIR, ["schema-recipe.rng", "schema-sm.rng"])] + +DATA_FILES = CONFIG + TEST_MODULES + MULTICAST_TEST_TOOLS + MAN_PAGES + SCHEMAS
setup(name="lnst", version="git",
In this patch, the old and big DOM parser is removed and replaced by a way more lighweight variant using the etree.
With the etree, LNST can use the RelaxNG schemes to verify the XML syntax so almost all error checks were removed from the code.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- lnst/Common/XmlParser.py | 303 ++++++++++------------------------------------- 1 file changed, 60 insertions(+), 243 deletions(-)
diff --git a/lnst/Common/XmlParser.py b/lnst/Common/XmlParser.py index 86c9959..68d3c76 100644 --- a/lnst/Common/XmlParser.py +++ b/lnst/Common/XmlParser.py @@ -11,261 +11,78 @@ rpazdera@redhat.com (Radek Pazdera) """
import os +import sys import logging -from xml.dom.minidom import parseString -from xml import sax +from lxml import etree from lnst.Common.XmlTemplates import XmlTemplates, XmlTemplateError -from lnst.Common.RecipePath import RecipePath -from lnst.Common.XmlProcessing import XmlProcessingError, XmlDomTreeInit -from lnst.Common.XmlProcessing import XmlData +from lnst.Common.XmlProcessing import XmlProcessingError, XmlData
class XmlParser(object): - """ Parent class for XML processors - - This class handles manipulation of XML DOM objects - that are used for processing XML files. - - The standard DOM objects are extended with position data - (file name, line number and column number) that can be - used in error reporting. - """ - - def _process_child_nodes(self, parent, scheme, params=None, - default_handler=None): - child_nodes = parent.childNodes - - if not params: - params = {} - - for node in child_nodes: - if node.nodeType == node.COMMENT_NODE or \ - node.nodeType == node.TEXT_NODE: - continue - elif node.nodeType == node.ELEMENT_NODE: - node_name = node.nodeName - if node_name in scheme: - handler = scheme[node_name] - self._process_node(node, handler, params) - elif default_handler: - self._process_node(node, default_handler, params) - else: - msg = "Unexpected '%s' tag under '%s'" % (node_name, - parent.nodeName) - raise XmlProcessingError(msg, node) + def __init__(self, schema_file, xml_path): + # locate the schema file + # try git path + dirname = os.path.dirname(sys.argv[0]) + schema_path = os.path.join(dirname, schema_file) + if not os.path.exists(schema_path): + # try configuration + res_dir = lnst_config.get_option("environment", "resource_dir") + schema_path = os.path.join(res_dir, schema_file) + + if not os.path.exists(schema_path): + raise Exception("The recipe schema file was not found. " + \ + "Your LNST installation is corrupt!") + + self._template_proc = XmlTemplates() + + self._path = xml_path + relaxng_doc = etree.parse(schema_path) + self._schema = etree.RelaxNG(relaxng_doc) + + def parse(self): + try: + doc = etree.parse(self._path) + doc.xinclude() + except Exception as err: + # A workaround for cases when lxml (quite strangely) + # sets the filename to <string>. + if err.error_log[0].filename == "<string>": + filename = self._path else: - msg = "Only XML elements are allowed here!" - raise XmlProcessingError(msg, node) - - def _process_node(self, node, handler, params): - handler(node, params) - - @staticmethod - def _convert_string(node, string, conversion_cb): - if conversion_cb: - try: - converted = conversion_cb(string) - except ValueError as err: - raise XmlProcessingError("Conversion error: " + str(err), node) - return converted - - return string - - def _has_attribute(self, node, attr_name): - return node.hasAttribute(attr_name) - - def _get_attribute(self, node, attr_name, conversion_cb=None): - if not self._has_attribute(node, attr_name): - msg = "Expected attribute '%s' missing" % attr_name - raise XmlProcessingError(msg, node) - attr_val = str(node.getAttribute(attr_name)) - return self._convert_string(node, attr_val, conversion_cb) - - def _get_text_content(self, node, conversion_cb=None): - content = [] - for child in node.childNodes: - if child.nodeType == child.TEXT_NODE: - content.append(child.nodeValue) - - text = str(''.join(content).strip()) - return self._convert_string(node, text, conversion_cb) - - def _get_all_attributes(self, node): - res = {} - for i in range(0, node.attributes.length): - attr = node.attributes.item(i) - res[attr.name] = attr.value + filename = err.error_log[0].filename + loc = {"file": os.path.basename(filename), + "line": err.error_log[0].line, + "col": err.error_log[0].column} + exc = XmlProcessingError(err.error_log[0].message) + exc.set_loc(loc) + raise exc
- return res + root_tag = doc.getroot() + self._template_proc.process_aliases(root_tag)
-class LnstParser(XmlParser): - """ Enhanced XmlParser - - This class enhances XmlParser with advanced features that are - used in parsing LNST XML files. All (sub)parsers should - use this as their base class. - """ - - _data = None - _template_proc = None - _include_root = None - _events_enabled = None - _event_handlers = None - - def __init__(self, parent=None): - super(LnstParser, self).__init__() - - if parent: - self._data = parent._data - self._template_proc = parent._template_proc - self._include_root = parent._include_root - self._events_enabled = parent._events_enabled - self._event_handlers = parent._event_handlers - else: - self._data = None - self._template_proc = XmlTemplates() - self._include_root = os.getcwd() - self._events_enabled = True - self._event_handlers = {} + 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
- def set_target(self, data_dict): - self._data = data_dict + return self._process(root_tag)
- def get_data(self): - return self._data + def _process(self, root_tag): + pass
def set_machines(self, machines): self._template_proc.set_machines(machines)
- def set_definitions(self, defs): - self._template_proc.set_definitions(defs) - - def set_include_root(self, include_root_path): - self._include_root = include_root_path - - def enable_events(self): - self._events_enabled = True - - def disable_events(self): - self._events_enabled = False - - def register_event_handler(self, event_id, handler): - self._event_handlers[event_id] = handler - - def _trigger_event(self, event_id, args): - if not self._events_enabled: - return - - try: - handler = self._event_handlers[event_id] - except KeyError as err: - logging.warn("No handler found for %s event, ignoring", event_id) - return - - handler(**args) + def _has_attribute(self, element, attr): + return attr in element.attrib
- def _process_child_nodes(self, node, scheme, params=None, - default_handler=None, new_ns_level=True): - scheme["define"] = self._define_handler + def _get_attribute(self, element, attr): + return self._template_proc.expand_functions(element.attrib[attr])
- if not params: - params = {} - - if new_ns_level: - self._template_proc.add_namespace_level() - - parent = super(LnstParser, self) - result = parent._process_child_nodes(node, scheme, params, - default_handler) - - if new_ns_level: - self._template_proc.drop_namespace_level() - - return result - - def _process_node(self, node, handler, params): - old_include_root = None - if self._has_attribute(node, "source"): - source = str(self._get_attribute(node, "source")) - - source_rp = RecipePath(self._include_root, source) - - old_include_root = self._include_root - self._include_root = source_rp.get_root() - xmlstr = source_rp.to_str() - - dom_init = XmlDomTreeInit() - try: - dom = dom_init.parse_string(xmlstr, - filename=source_rp.abs_path()) - except IOError as err: - msg = "Unable to resolve include: %s" % str(err) - raise XmlProcessingError(msg, node) - - loaded_node = None - try: - loaded_node = dom.getElementsByTagName(node.nodeName)[0] - except Exception: - msg = ("No '%s' element present in included file '%s'." - % (node.nodeName, source_rp.abs_path())) - raise XmlProcessingError(msg, node) - - old_attrs = self._get_all_attributes(node) - - parent = node.parentNode - parent.replaceChild(loaded_node, node) - node = loaded_node - - # copy all of the original attributes to the sourced node - for name, value in old_attrs.iteritems(): - # do not overwrite sourced attributes - if not node.hasAttribute(name): - node.setAttribute(name, value) - - parent = super(LnstParser, self) - parent._process_node(node, handler, params) - - if old_include_root: - self._include_root = old_include_root - - def _get_attribute(self, node, attr_name, conversion_cb=None): - parent = super(LnstParser, self) - raw_attr_val = parent._get_attribute(node, attr_name) - - try: - attr_val = self._template_proc.expand_string(raw_attr_val, node) - except XmlTemplateError as err: - raise XmlProcessingError(str(err), node) - - return self._convert_string(node, attr_val, conversion_cb) - - def _get_text_content(self, node, conversion_cb=None): - parent = super(LnstParser, self) - raw_content = parent._get_text_content(node) - - try: - content = self._template_proc.expand_string(raw_content, node) - except XmlTemplateError as err: - raise XmlProcessingError(str(err), node) - - return self._convert_string(node, content, conversion_cb) - - def _define_handler(self, node, params): - scheme = {"alias": self._alias_handler} - self._process_child_nodes(node, scheme, new_ns_level=False) - - def _alias_handler(self, node, params): - if self._has_attribute(node, "name"): - name = str(self._get_attribute(node, "name")) - else: - msg = "Alias tag must have the 'name' attribute" - raise XmlProcessingError(msg, node) - - if self._has_attribute(node, "value"): - value = str(self._get_attribute(node, "value")) - else: - value = self._get_text_content(node) - - try: - self._template_proc.define_alias(name, value) - except XmlTemplateError as err: - raise XmlProcessingError(str(err), node) + def _get_content(self, element): + text = etree.tostring(element, method="text") + return self._template_proc.expand_functions(text)
The XmlProcessingError, XmlCollection, and XmlData objects were updated, so they support the etree nodes instead of the DOM nodes that were previously used.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- lnst/Common/XmlProcessing.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/lnst/Common/XmlProcessing.py b/lnst/Common/XmlProcessing.py index a709659..9c3e5fe 100644 --- a/lnst/Common/XmlProcessing.py +++ b/lnst/Common/XmlProcessing.py @@ -24,18 +24,25 @@ class XmlProcessingError(Exception):
def __init__(self, msg, obj=None): super(XmlProcessingError, self).__init__() - self._msg = msg
if obj and hasattr(obj, "loc"): self.set_loc(obj.loc)
- #logging.error(self.__str__()) + loc = {} + if obj: + if 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) +
def set_loc(self, loc): self._filename = loc["file"] self._line = loc["line"] - #self._col = loc["col"] + if "col" in loc: + self._col = loc["col"]
def __str__(self): line = "" @@ -82,8 +89,15 @@ class XmlDataIterator: class XmlCollection(list): def __init__(self, node=None): super(XmlCollection, self).__init__() - if node: - self.loc = node.loc + if node is not None: + if hasattr(node, "loc"): + self.loc = node.loc + elif hasattr(node, "base") and node.base != None: + loc = {} + loc["file"] = os.path.basename(node.base) + if hasattr(node, "sourceline"): + loc["line"] = node.sourceline + self.loc = loc
def __getitem__(self, key): value = super(XmlCollection, self).__getitem__(key) @@ -99,8 +113,15 @@ class XmlCollection(list): class XmlData(dict): def __init__(self, node=None): super(XmlData, self).__init__() - if node: - self.loc = node.loc + if node is not None: + if hasattr(node, "loc"): + self.loc = node.loc + elif hasattr(node, "base") and node.base != None: + loc = {} + loc["file"] = os.path.basename(node.base) + if hasattr(node, "sourceline"): + loc["line"] = node.sourceline + self.loc = loc
def __getitem__(self, key): value = super(XmlData, self).__getitem__(key)
Removing some old DOM-tree related code.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- lnst/Common/XmlProcessing.py | 66 -------------------------------------------- 1 file changed, 66 deletions(-)
diff --git a/lnst/Common/XmlProcessing.py b/lnst/Common/XmlProcessing.py index 9c3e5fe..f4d9105 100644 --- a/lnst/Common/XmlProcessing.py +++ b/lnst/Common/XmlProcessing.py @@ -12,8 +12,6 @@ rpazdera@redhat.com (Radek Pazdera)
import os import logging -from xml.dom.minidom import parseString -from xml import sax
class XmlProcessingError(Exception): """ Exception thrown on parsing errors """ @@ -188,67 +186,3 @@ class XmlTemplateString(object):
def add_part(self, part): self._parts.append(part) - -class XmlDomTreeInit: - """ Handles creation/initialization of DOM trees - - It allows you to parse XML file or string into a DOM tree. - It also adds an extra parameter to each node of the tree - called `loc' which can be used to determine where exactly - was the element placed in the original source XML file. - This is useful for error reporting. - """ - - _sax = None - _filename = None - __orig_set_content_handler = None - - def __init__(self): - self._init_sax() - - def _init_sax(self): - parser = sax.make_parser() - self.__orig_set_content_handler = parser.setContentHandler - parser.setContentHandler = self.__set_content_handler - self._sax = parser - - def __set_content_handler(self, dom_handler): - def start_element_ns(name, tag_name , attrs): - orig_start_cb(name, tag_name, attrs) - cur_elem = dom_handler.elementStack[-1] - loc = {"file": self._filename, - "line": self._sax.getLineNumber(), - "col": self._sax.getColumnNumber()} - cur_elem.loc = loc - - orig_start_cb = dom_handler.startElementNS - dom_handler.startElementNS = start_element_ns - self.__orig_set_content_handler(dom_handler) - - @staticmethod - def _load_file(filename): - handle = open(filename, "r") - data = handle.read() - handle.close() - return data - - def parse_file(self, xml_filepath): - xml_text = self._load_file(xml_filepath) - filename = os.path.basename(xml_filepath) - return self.parse_string(xml_text, filename) - - def parse_string(self, xml_text, filename="xml_string"): - self._filename = os.path.basename(filename) - try: - dom = parseString(xml_text, self._sax) - except sax.SAXParseException as err: - loc = {"file": self._filename, - "line": err.getLineNumber(), - "col": err.getColumnNumber()} - exc = XmlProcessingError(err.getMessage()) - exc.set_loc(loc) - raise exc - - loc = {"file": self._filename, "line": 0, "col": 0} - dom.loc = loc - return dom
The XML parser changed from DOM to etree. This patch changes the XmlTemplates class to support etree. Some older code was also removed.
Alias and template functions processing is now handled separately. Aliases are preprocessed before the document is parsed, while template functions are expanded during the parsing.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- lnst/Common/XmlTemplates.py | 104 +++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 49 deletions(-)
diff --git a/lnst/Common/XmlTemplates.py b/lnst/Common/XmlTemplates.py index e1f0f86..daa1254 100644 --- a/lnst/Common/XmlTemplates.py +++ b/lnst/Common/XmlTemplates.py @@ -239,37 +239,63 @@ class XmlTemplates: err = "'%s' is not defined here" % name raise XmlTemplateError(err)
- def expand_dom(self, node): - """ - Traverse DOM tree from `node' down and expand any - templates along the way. + def process_aliases(self, element): + """ Expand aliases within an element and its children + + This method will iterate through the element tree that is + passed and expand aliases in all the text content and + attributes. """ + if element.text != None: + element.text = self.expand_aliases(element.text)
- if node.nodeType == node.ELEMENT_NODE: - i = 0 - num_attributes = node.attributes.length - while(i < num_attributes): - attr = node.attributes.item(i) - attr.value = self.expand_string(str(attr.value)) - i += 1 - elif node.nodeType == node.TEXT_NODE: - node.data = self.expand_string(str(node.data)) + if element.tail != None: + element.tail = self.expand_aliases(element.tail)
- for child in node.childNodes: - self.expand_dom(child) + for name, value in element.attrib.iteritems(): + element.set(name, self.expand_aliases(value))
- def expand_group(self, group): - """ - Behaves exactly the same as the `expand' method, but it - operates on a group of DOM nodes stored within a list, - rather than a single node. - """ + if element.tag == "define": + for alias in element.getchildren(): + name = alias.attrib["name"] + value = alias.attrib["value"] + self.define_alias(name, value) + parent = element.getparent() + parent.remove(element) + return + + self.add_namespace_level() + + for child in element.getchildren(): + self.process_aliases(child) + + self.drop_namespace_level() + + def expand_aliases(self, string): + while True: + alias_match = re.search(self._alias_re, string) + + if alias_match: + template = alias_match.group(0) + result = self._process_alias_template(template) + string = string.replace(template, result) + else: + break + + return string + + def _process_alias_template(self, string): + result = None + + alias_match = re.match(self._alias_re, string) + if alias_match: + alias_name = alias_match.group(1) + result = self._find_definition(alias_name)
- for node in group: - self.expand_dom(node) + return result
- def expand_string(self, string, node=None): - """ Process a string and expand it into a XmlTemplateString. """ + def expand_functions(self, string, node=None): + """ Process a string and expand it into a XmlTemplateString """
parts = self._partition_string(string) value = XmlTemplateString(node=node) @@ -282,9 +308,8 @@ class XmlTemplates: def _partition_string(self, string): """ Process templates in a string
- This method will process and expand all templates contained - within a string. It handles both aliases and template - function. + This method will process and expand all template functions + in a string.
The function returns an array of string partitions and unresolved template functions for further processing. @@ -292,16 +317,6 @@ class XmlTemplates:
result = None
- while True: - alias_match = re.search(self._alias_re, string) - - if alias_match: - template = alias_match.group(0) - result = self._process_alias_template(template) - string = string.replace(template, result) - else: - break - func_match = re.search(self._func_re, string) if func_match: prefix = string[0:func_match.start(0)] @@ -315,16 +330,6 @@ class XmlTemplates:
return [string]
- def _process_alias_template(self, string): - result = None - - alias_match = re.match(self._alias_re, string) - if alias_match: - alias_name = alias_match.group(1) - result = self._find_definition(alias_name) - - return result - def _process_func_template(self, string): func_match = re.match(self._func_re, string) if func_match: @@ -350,4 +355,5 @@ class XmlTemplates: func = self._func_map[func_name](param_values, self._machines) return func else: - raise RuntimeError("The passed string is not a template function.") + msg = "The passed string is not a template function." + raise XmlTemplateError(msg)
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@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@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@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
With the changes in recipe parser, the slave machine parser has been changed to use etree as well. Again, due to validation using the schema, the code is now much simpler.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- lnst/Controller/SlaveMachineParse.py | 72 ----------------------------------- lnst/Controller/SlaveMachineParser.py | 69 +++++++++++++++++++++++++++++++++ lnst/Controller/SlavePool.py | 18 ++------- 3 files changed, 73 insertions(+), 86 deletions(-) delete mode 100644 lnst/Controller/SlaveMachineParse.py create mode 100644 lnst/Controller/SlaveMachineParser.py
diff --git a/lnst/Controller/SlaveMachineParse.py b/lnst/Controller/SlaveMachineParse.py deleted file mode 100644 index 709c478..0000000 --- a/lnst/Controller/SlaveMachineParse.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -This module defines SlaveMachineParse class useful to parse XML machine -descriptions for the slave pool - -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@redhat.com (Radek Pazdera) -""" - -import logging -import os -import re -from lnst.Controller.RecipeParse import ParamsParse -from lnst.Common.XmlParser import LnstParser -from lnst.Common.XmlProcessing import XmlDomTreeInit, XmlProcessingError -from lnst.Common.XmlProcessing import XmlData, XmlCollection - -class SlaveMachineError(XmlProcessingError): - pass - -class SlaveMachineParse(LnstParser): - def parse(self, node): - self._data = XmlData(node) - 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 <slavemachine>." - raise SlaveMachineError(msg, node) - - subparser = ParamsParse(self) - self._data["params"] = subparser.parse(node) - - def _interfaces(self, node, params): - if not "interfaces" in self._data: - self._data["interfaces"] = XmlCollection(node) - else: - msg = "Only a single <interfaces> child allowed under <slavemachine>." - raise SlaveMachineError(msg, node) - - scheme = {"eth": self._eth} - self._process_child_nodes(node, scheme) - - def _eth(self, node, params): - machine = self._data - - iface = XmlData(node) - iface["id"] = self._get_attribute(node, "id") - iface["network"] = self._get_attribute(node, "network") - iface["type"] = "eth" - - # parse interface parameters - scheme = {"params": self._iface_params} - params = {"iface": iface} - self._process_child_nodes(node, scheme, params) - - machine["interfaces"].append(iface) - - def _iface_params(self, node, params): - if "params" in params["iface"]: - msg = "Only a single <params> child allowed under <interface>." - raise SlaveMachineError(msg, node) - - subparser = ParamsParse(self) - params["iface"]["params"] = subparser.parse(node) diff --git a/lnst/Controller/SlaveMachineParser.py b/lnst/Controller/SlaveMachineParser.py new file mode 100644 index 0000000..02374cb --- /dev/null +++ b/lnst/Controller/SlaveMachineParser.py @@ -0,0 +1,69 @@ +""" +This module defines SlaveMachineParser class useful to parse XML machine +descriptions for the slave pool + +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@redhat.com (Radek Pazdera) +""" + +import logging +import os +import re +from lxml import etree +from lnst.Common.XmlParser import XmlParser +from lnst.Common.XmlProcessing import XmlProcessingError, XmlData, XmlCollection + +class SlaveMachineError(XmlProcessingError): + pass + +class SlaveMachineParser(XmlParser): + def __init__(self, sm_path): + super(SlaveMachineParser, self).__init__("schema-sm.rng", sm_path) + + def _process(self, sm_tag): + sm = XmlData(sm_tag) + + # params + params_tag = sm_tag.find("params") + params = self._process_params(params_tag) + if len(params) > 0: + sm["params"] = params + + # interfaces + interfaces_tag = sm_tag.find("interfaces") + if interfaces_tag is not None and len(interfaces_tag) > 0: + sm["interfaces"] = XmlCollection(interfaces_tag) + for eth_tag in interfaces_tag: + interface = self._process_interface(eth_tag) + sm["interfaces"].append(interface) + + return sm + + 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["network"] = self._get_attribute(iface_tag, "network") + iface["type"] = "eth" + + # interface parameters + params_tag = iface_tag.find("params") + params = self._process_params(params_tag) + if len(params) > 0: + iface["params"] = params + + return iface diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py index e8baf83..5872b0c 100644 --- a/lnst/Controller/SlavePool.py +++ b/lnst/Controller/SlavePool.py @@ -18,13 +18,12 @@ import os import re import copy from xml.dom import minidom +from lnst.Common.Config import lnst_config from lnst.Common.NetUtils import normalize_hwaddr from lnst.Common.NetUtils import test_tcp_connection -from lnst.Common.XmlProcessing import XmlDomTreeInit from lnst.Common.XmlProcessing import XmlProcessingError, XmlData from lnst.Controller.Machine import Machine -from lnst.Common.Config import lnst_config -from lnst.Controller.SlaveMachineParse import SlaveMachineParse +from lnst.Controller.SlaveMachineParse import SlaveMachineParser from lnst.Controller.SlaveMachineParse import SlaveMachineError
class SlavePool: @@ -56,20 +55,11 @@ class SlavePool:
def add_file(self, filepath): if os.path.isfile(filepath) and re.search(".xml$", filepath, re.I): - dom_init = XmlDomTreeInit() - dom = dom_init.parse_file(filepath) - dirname, basename = os.path.split(filepath) - - parser = SlaveMachineParse() - parser.set_include_root(dirname) - parser.disable_events() - m_id = re.sub(".[xX][mM][lL]$", "", basename)
- slavemachine = dom.getElementsByTagName("slavemachine")[0] - xml_data = parser.parse(slavemachine) - + parser = SlaveMachineParser(filepath) + xml_data = parser.parse() machine_spec = self._process_machine_xml_data(m_id, xml_data)
if self._pool_checks:
All applied.
Thanks a lot Radek!
Wed, Aug 28, 2013 at 04:41:20PM CEST, rpazdera@redhat.com wrote:
Hello everyone,
this is the final part of the refactoring/redesign that happened on the controller over the last month or two.
This series adds a dependency on python-lxml. However, the library is quite common and in wide-spread use.
Apart from the formating of some error messages, there should be no visible changes to LNST from the outside.
Cheers -Radek
Radek Pazdera (10): schema: Updating the cmd-sequence to task schema: Removing 'source' and changing 'defines' schema: Adding a schema for slavemachine xml install: Adding schemes to the distribution XmlParser: Moving the parser from DOM to etree XmlProcessing: Updating the objects to support etree XmlProcessing: Removing the XmlDomTreeInit XmlTemplates: Changing to support etree controller: Rewriting recipe parsing controller: Rewriting slavemachine parsing
install/lnst-ctl.conf.in | 65 +++-- lnst/Common/Config.py | 5 + lnst/Common/XmlParser.py | 303 ++++---------------- lnst/Common/XmlProcessing.py | 101 ++----- lnst/Common/XmlTemplates.py | 104 +++---- lnst/Controller/NetTestController.py | 7 +- lnst/Controller/RecipeParse.py | 394 -------------------------- lnst/Controller/RecipeParser.py | 255 +++++++++++++++++ lnst/Controller/SlaveMachineParse.py | 72 ----- lnst/Controller/SlaveMachineParser.py | 69 +++++ lnst/Controller/SlavePool.py | 18 +- recipe-schema.rng | 515 ---------------------------------- schema-recipe.rng | 391 ++++++++++++++++++++++++++ schema-sm.rng | 82 ++++++ setup.py | 8 +- 15 files changed, 995 insertions(+), 1394 deletions(-) delete mode 100644 lnst/Controller/RecipeParse.py create mode 100644 lnst/Controller/RecipeParser.py delete mode 100644 lnst/Controller/SlaveMachineParse.py create mode 100644 lnst/Controller/SlaveMachineParser.py delete mode 100644 recipe-schema.rng create mode 100644 schema-recipe.rng create mode 100644 schema-sm.rng
-- 1.8.3.1
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
lnst-developers@lists.fedorahosted.org