diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | HACKING | 14 | ||||
-rw-r--r-- | LICENSE | 6 | ||||
-rw-r--r-- | README | 6 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | cardinality.rng.expected | 54 | ||||
-rw-r--r-- | cardinality.rng.orig (renamed from cardinality.rng) | 0 | ||||
-rw-r--r-- | cardinality.rng.trang | 52 | ||||
-rw-r--r-- | conv08.rng.expected | 5 | ||||
-rw-r--r-- | conv08.rng.orig (renamed from conv08.rng) | 0 | ||||
-rw-r--r-- | conv08.rng.trang | 7 | ||||
-rw-r--r-- | corosync.rnc | 684 | ||||
-rw-r--r-- | corosync.rng.expected | 958 | ||||
-rw-r--r-- | corosync.rng.trang | 907 | ||||
-rw-r--r-- | corosync.trang.rng | 907 | ||||
-rw-r--r-- | lex.py | 1454 | ||||
l--------- | patron.rng.expected | 1 | ||||
-rw-r--r-- | patron.rng.orig (renamed from patron.rng) | 0 | ||||
l--------- | patron.rng.trang | 1 | ||||
-rw-r--r-- | regextest.rng.expected | 27 | ||||
l--------- | regextest.rng.trang | 1 | ||||
-rw-r--r-- | res08.rng.expected | 8 | ||||
-rw-r--r-- | res08.rng.orig (renamed from res08.rng) | 0 | ||||
l--------- | res08.rng.trang | 1 | ||||
-rw-r--r-- | rnc_tokenize.py | 294 | ||||
-rwxr-xr-x | rnctree.py | 483 | ||||
-rwxr-xr-x | tests.sh | 52 | ||||
-rw-r--r-- | unused/conv09.rng (renamed from conv09.rng) | 0 | ||||
-rw-r--r-- | unused/curious.dtd (renamed from curious.dtd) | 0 | ||||
-rw-r--r-- | unused/curious.xml (renamed from curious.xml) | 0 | ||||
-rw-r--r-- | unused/patron-2.rng (renamed from patron-2.rng) | 0 | ||||
-rw-r--r-- | unused/patron-i1.xml (renamed from patron-i1.xml) | 0 | ||||
-rw-r--r-- | unused/patron-i2.xml (renamed from patron-i2.xml) | 0 | ||||
-rw-r--r-- | unused/patron-i3.xml (renamed from patron-i3.xml) | 0 | ||||
-rw-r--r-- | unused/patron-v1.xml (renamed from patron-v1.xml) | 0 | ||||
-rw-r--r-- | unused/patron-v2.xml (renamed from patron-v2.xml) | 0 | ||||
-rw-r--r-- | unused/res09.rng (renamed from res09.rng) | 0 | ||||
-rw-r--r-- | unused/spectest.xml (renamed from spectest.xml) | 0 | ||||
-rw-r--r-- | unused/split.xsl (renamed from split.xsl) | 0 | ||||
-rw-r--r-- | unused/testSuite.rng (renamed from testSuite.rng) | 0 | ||||
-rw-r--r-- | unused/unused-counterparts/patron.xsd (renamed from patron.xsd) | 0 | ||||
-rw-r--r-- | unused/unused-counterparts/regextest.xml (renamed from regextest.xml) | 0 | ||||
-rw-r--r-- | unused/unused-counterparts/xsdtest.xml (renamed from xsdtest.xml) | 0 | ||||
-rw-r--r-- | unused/unused-counterparts/xsdtest.xsl (renamed from xsdtest.xsl) | 0 | ||||
l--------- | xsdtest.rng.expected | 1 | ||||
-rw-r--r-- | xsdtest.rng.orig (renamed from xsdtest.rng) | 0 | ||||
l--------- | xsdtest.rng.trang | 1 |
47 files changed, 5226 insertions, 702 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81a36de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.pyo +*.testrun @@ -0,0 +1,14 @@ +Please, any time the script does not work for your use-case and you want +to fix it and voluntarily provide a patch to be integrated, follow these +guidelines: + +1/ some kind of minimal test case should be added to the test suite + +2/ no test from the original test suite should be broken after + applying the patch unless there is a pretty good reason to change + something and then, new expected results have to be provided + +3/ try to comply with style (and use checkers like pep8, pyflakes) + + +Refactoring effort is welcome as well (please respect 2/ and 3/). @@ -0,0 +1,6 @@ +Original project released to the Public Domain by David Mertz. + +Extended under revised BSD license as stated in the modified files. + +Bundled is lex.py from PLY project by David M. Beazley (Dabeaz LLC), +license of which (also revised BSD) is included in the respective file. @@ -3,8 +3,8 @@ Example files and programming tools for working with RELAX NG Some of these files are part of James Clark's test suite (see clark.html for some details). -All of the examples and scripts (unless indicated otherwise within -the file) are released to the Public Domain. +All of the examples and scripts, unless indicated otherwise within +the file, are released to the Public Domain. +++ @@ -15,3 +15,5 @@ and place them in a working directory (or in your $PYTHONPATH): rnctree.py rnc_tokenize.py lex.py + +Update: ... or clone the repo or wait for the package @@ -0,0 +1 @@ +- packagify (with upfront notification of PyPI maintainer) diff --git a/cardinality.rng.expected b/cardinality.rng.expected new file mode 100644 index 0000000..f49540b --- /dev/null +++ b/cardinality.rng.expected @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <start> + <element name="bar"> + <group> + <ref name="fivefoo"/> + <ref name="upto25foo"/> + </group> + </element> + </start> + <define name="fivefoo"> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + </define> + <define name="maybefoo"> + <optional> + <element name="foo"> + <empty/> + </element> + </optional> + </define> + <define name="upto25foo"> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + </define> +</grammar> diff --git a/cardinality.rng b/cardinality.rng.orig index 765d3e1..765d3e1 100644 --- a/cardinality.rng +++ b/cardinality.rng.orig diff --git a/cardinality.rng.trang b/cardinality.rng.trang new file mode 100644 index 0000000..5bd72ca --- /dev/null +++ b/cardinality.rng.trang @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <start> + <element name="bar"> + <ref name="fivefoo"/> + <ref name="upto25foo"/> + </element> + </start> + <define name="fivefoo"> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + <element name="foo"> + <empty/> + </element> + </define> + <define name="maybefoo"> + <optional> + <element name="foo"> + <empty/> + </element> + </optional> + </define> + <define name="upto25foo"> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <optional> + <ref name="fivefoo"/> + </optional> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + <ref name="maybefoo"/> + </define> +</grammar> diff --git a/conv08.rng.expected b/conv08.rng.expected new file mode 100644 index 0000000..7fcd25c --- /dev/null +++ b/conv08.rng.expected @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<include href="res08.rnc" xmlns="http://relaxng.org/ns/structure/1.0"/> +<define name="foo.body"> + <text/> +</define> diff --git a/conv08.rng b/conv08.rng.orig index eeb207c..eeb207c 100644 --- a/conv08.rng +++ b/conv08.rng.orig diff --git a/conv08.rng.trang b/conv08.rng.trang new file mode 100644 index 0000000..5a50bc2 --- /dev/null +++ b/conv08.rng.trang @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <include href="res08.expected"/> + <define name="foo.body"> + <text/> + </define> +</grammar> diff --git a/corosync.rnc b/corosync.rnc new file mode 100644 index 0000000..7e90d2c --- /dev/null +++ b/corosync.rnc @@ -0,0 +1,684 @@ +# Copyright 2013 Red Hat, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the Red Hat, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +# +# style guide (RELAX NG Compact only): +# * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence +# separator, AsciiDoc formatting is used within them +# * sort everything you can to ease the lookup + +namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" +namespace a4doc = "http://people.redhat.com/jpokorny/ns/a4doc" + +start = corosync + +corosync = + element corosync { + (logging? + & nodelist? + & quorum? + & totem + & uidgid?) + } + +macro.logging_attributes = + # CFG: corosync.conf, cluster.conf + ## This specifies whether debug output is + ## logged for this particular logger. Also can contain value trace, what + ## is highest level of debug informations. + [ a:defaultValue = "off" ] + attribute debug {"off"|"on"}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the logfile level for this particular subsystem. Ignored + ## if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug' + ## (same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'. + [ a:defaultValue = "info" ] + attribute logfile_priority {"alert" + |"crit" + |"debug" + |"emerg" + |"err" + |"info" + |"notice" + |"warning" + }?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the syslog facility type + ## that will be used for any messages sent to syslog. Options are + ## 'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', + ## 'local6' and 'local7'. + [ a:defaultValue = "daemon" ] + attribute syslog_facility {"daemon" + |"local0" + |"local1" + |"local2" + |"local3" + |"local4" + |"local5" + |"local6" + |"local7" + }?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the syslog level for this + ## particular subsystem. Ignored if *debug* is 'on'. Possible values are: + ## 'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err', + ## 'info', 'notice' and 'warning'. + [ a:defaultValue = "info" ] + attribute syslog_priority {"alert" + |"crit" + |"debug" + |"emerg" + |"err" + |"info" + |"notice" + |"warning" + }?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the destination + ## of logging output. + ## + ## Please note, if you are using *to_logfile* and want to rotate the file, + ## use `logrotate(8)` with the option `copytruncate`, e.g. + ## + ## ---- + ## /var/log/corosync.log { + ## missingok + ## compress + ## notifempty + ## daily + ## rotate 7 + ## copytruncate + ## } + ## ---- + [ a:defaultValue = "no" ] + attribute to_logfile {"no"|"yes"}?, + + # CFG: corosync.conf + ## This specifies the destination + ## of logging output. + [ a:defaultValue = "yes" ] + attribute to_stderr {"no"|"yes"}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the destination + ## of logging output. + [ a:defaultValue = "yes" ] + attribute to_syslog {"no"|"yes"}? + +logging = + element logging { + # CFG: corosync.conf + ## This specifies that a timestamp is placed + ## on all log messages. + [ a:defaultValue = "off" ] + attribute timestamp {"off"|"on"}?, + + # CFG: corosync.conf + ## This specifies that file and line should + ## be printed. + [ a:defaultValue = "off" ] + attribute fileline {"off"|"on"}?, + + # CFG: corosync.conf + ## This specifies that the code function name + ## should be printed. + [ a:defaultValue = "off" ] + attribute function_name {"off"|"on"}?, + + macro.logging_attributes, + logger_subsys* + } + +logger_subsys = + element logger_subsys { + macro.logging_attributes, + + # CFG: corosync.conf, cluster.conf + ## This specifies the subsystem identity + ## (name) for which logging is specified. This is the name used by + ## a service in the `log_init` call. E.g., 'CPG'. + ## This option is required. + attribute subsys {text} + } + +nodelist = + element nodelist { + node? + } + +node = + element node { + # XXX: implied check + # CFG: corosync.conf, cluster.conf + ## This configuration option is optional when + ## using IPv4 and required when using IPv6. This is a 32 bit value + ## specifying the node identifier delivered to the cluster membership + ## service. If this is not specified with IPv4, the node id will be + ## determined from the 32 bit IP address the system to which the system + ## is bound with ring identifier of 0. The node identifier value of zero + ## is reserved and should not be used. + attribute noid {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies IP address of one of the nodes for particular ring + ## as denoted by its number (instead 0, there can be higher numbers). + attribute ring0_addr {text}, + attribute ring1_addr {text}?, + attribute ring2_addr {text}?, + attribute ring3_addr {text}?, + attribute ring4_addr {text}?, + attribute ring5_addr {text}?, + attribute ring6_addr {text}?, + attribute ring7_addr {text}?, + attribute ring8_addr {text}?, + attribute ring9_addr {text}? + # NOTE: Augeas lens for corosync.conf counts on X = 0..9 only + } + +quorum = + element quorum { + # CFG: corosync.conf + ## This enables Downscale feature + ## (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute allow_downscale {"0"|"1"}?, + + # CFG: corosync.conf + ## This enables Auto Tie Breaker feature + ## (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute auto_tie_breaker {"0"|"1"}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the number of expected votes, overriding the number + ## implied by the number of *node* items within *nodes*. + attribute expected_votes {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This enables Last Man Standing feature + ## (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute last_man_standing {"0"|"1"}?, + + # CFG: corosync.conf + ## This specifies the tunable for Last Man + ## Standing feature (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute last_man_standing_window {xsd:nonNegativeInteger}?, + + # CFG: corosync.conf + ## This specifies the quorum algorithm to use. + ## As of now, only 'corosync_votequorum' is supported. + attribute provider {"corosync_votequorum"}?, + + # CFG: corosync.conf, cluster.conf + ## This enables two node cluster operations + ## (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute two_node {"0"|"1"}?, + + # CFG: corosync.conf + ## This enables Wait For All feature + ## (see `votequorum(5)`). + [ a:defaultValue = "0" ] + attribute wait_for_all {"0"|"1"}? + } + +totem = + element totem { + # CFG: corosync.conf + ## This configuration option is optional and + ## is only relevant when no *nodeid* is specified. Some corosync clients + ## require a signed 32 bit nodeid that is greater than zero however by + ## default corosync uses all 32 bits of the IPv4 address space when + ## generating a nodeid. Set this option to 'yes' to force the high bit + ## to be zero and therefor ensure the nodeid is a positive signed 32 bit + ## integer. + [ a:defaultValue = "no" + a4doc:discretion-hint = + "The clusters behavior is undefined if this option is enabled" + ~ " on only a subset of the cluster (for example during a rolling" + ~ " upgrade)." ] + attribute clear_node_high_bit {"no"|"yes"}?, + + # CFG: corosync.conf, cluster.conf + # NOTE: not a direct mapping in cluster.conf (top-level tag instead) + ## This specifies the name of cluster and it's + ## used for automatic generating of multicast address. + attribute cluster_name {text}?, + + # XXX: implied check + # CFG: corosync.conf, cluster.conf + ## This timeout specifies in milliseconds how + ## long to wait for consensus to be achieved before starting a new round + ## of membership configuration. The minimum value for *consensus* must be + ## 1.2 * *token*. + ## + ## This value will be automatically calculated at 1.2 * *token* if + ## the user doesn't specify a *consensus* value. + ## + ## For two node clusters, a *consensus* larger then the *join* timeout but + ## less then *token* is safe. For three node or larger clusters, + ## *consensus* should be larger then token. There is an increasing risk + ## of odd membership changes, which still guarantee virtual synchrony, + ## as node count grows if *consensus* is less than *token*. + [ a:defaultValue = "1200" ] + attribute consensus {xsd:unsignedInt}?, + + # XXX: missing nss? + # CFG: corosync.conf + ## This specifies which cipher should be used + ## to encrypt all messages. Valid values are 'none' (no encryption), + ## 'aes256', 'aes192', 'aes128' and '3des'. + [ a:defaultValue = "aes256" ] + attribute crypto_cipher {"3des"|"aes128"|"aes192"|"aes256"|"none"}?, + + # CFG: undocumented + attribute crypto_compat {"2.0"|"2.2"}?, + + # CFG: corosync.conf + ## This specifies which HMAC authentication + ## should be used to authenticate all messages. Valid values are 'none' + ## (no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'. + [ a:defaultValue = "sha1" ] + attribute crypto_hash {"none"|"md5"|"sha1"|"sha256"|"sha384"|"sha512"}?, + + # CFG: undocumented + attribute crypto_type {"3des"|"aes128"|"aes192"|"aes256"|"nss"}?, + + # CFG: corosync.conf + ## This timeout specifies in milliseconds how + ## long to wait before checking that a network interface is back up after + ## it has been downed. + [ a:defaultValue = "1000" ] + attribute downcheck {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This constant specifies how many rotations + ## of the token without receiving any of the messages when messages should + ## be received may occur before a new configuration is formed. + [ a:defaultValue = "2500" ] + attribute fail_recv_const {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## Configures the optional HeartBeating + ## mechanism for faster failure detection. Keep in mind that engaging this + ## mechanism in lossy networks could cause faulty loss declaration as + ## the mechanism relies on the network for heartbeating. + ## + ## So as a rule of thumb use this mechanism if you require improved + ## failure in low to medium utilized networks. + ## + ## This constant specifies the number of heartbeat failures the system + ## should tolerate before declaring heartbeat failure, e.g., 3. + ## Also if this value is not set or is 0 then the heartbeat mechanism is + ## not engaged in the system and token rotation is the method of failure + ## detection. Zero disables the mechanism. + [ a:defaultValue = "0" ] + attribute heartbeat_failures_allowed {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This timeout specifies in milliseconds + ## how long the token should be held by the representative when + ## the protocol is under low utilization. + [ a:defaultValue = "180" + a4doc:danger-hint = + "It is not recommended to override this value without guidance" + ~ " from the corosync community." ] + attribute hold {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This timeout specifies in milliseconds how + ## long to wait for join messages in the membership protocol. + [ a:defaultValue = "50" ] + attribute join {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This constant specifies the maximum number + ## of messages that may be sent by one processor on receipt of the token. + ## The *max_messages* parameter is limited to 256000 / *netmtu* to prevent + ## overflow of the kernel transmit buffers. + [ a:defaultValue = "17" ] + attribute max_messages {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This constant specifies in milliseconds + ## the approximate delay that your network takes to transport one packet + ## from one machine to another. This value is to be set by system engineers + ## and please don't change it if not sure as this effects the failure + ## detection mechanism using heartbeat. + [ a:defaultValue = "50" ] + attribute max_network_delay {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This timeout specifies in milliseconds how + ## long to wait before checking for a partition when no multicast traffic + ## is being sent. If multicast traffic is being sent, the merge detection + ## happens automatically as a function of the protocol. + [ a:defaultValue = "200" ] + attribute merge {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This constant defines the maximum number + ## of times on receipt of a token a message is checked for retransmission + ## before a retransmission occurs. This parameter is useful to modify for + ## switches that delay multicast packets compared to unicast packets. + ## The default setting works well for nearly all modern switches. + [ a:defaultValue = "5" ] + attribute miss_count_const {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the network maximum transmit + ## unit. To set this value beyond 1500, the regular frame MTU, requires + ## ethernet devices that support large, or also called jumbo, frames. + ## If any device in the network doesn't support large frames, the protocol + ## will not operate properly. The hosts must also have their mtu size set + ## from 1500 to whatever frame size is specified here. + ## + ## Please note while some NICs or switches + ## claim large frame support, they support 9000 MTU as the maximum frame + ## size including the IP header. Setting the *netmtu* and host MTUs to 9000 + ## will cause totem to use the full 9000 bytes of the frame. Then Linux + ## will add a 18 byte header moving the full frame size to 9018. + ## As a result some hardware will not operate properly with this size + ## of data. A *netmtu* of 8982 seems to work for the few large frame devices + ## that have been tested. Some manufacturers claim large frame support + ## when in fact they support frame sizes of 4500 bytes. + ## + ## When sending multicast traffic, if the network frequently reconfigures, + ## chances are that some device in the network doesn't support large frames. + ## + ## Choose hardware carefully if intending to use large frame support. + [ a:defaultValue = "1500" ] + attribute netmtu {xsd:unsignedInt}?, + + # CFG: undocumented + attribute nodeid {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This specifies the time in milliseconds + ## to check if the failed ring can be auto-recovered. + [ a:defaultValue = "1000" ] + attribute rrp_autorecovery_check_timeout {xsd:unsignedInt}?, + + # XXX: implied check: active,passive -> count(interface) <= 2 + # CFG: corosync.conf, cluster.conf + ## This specifies the mode of redundant ring, + ## which may be 'none', 'active', or 'passive'. Active replication offers + ## none`, active, or passive. Active replication offers + ## slightly lower latency from transmit to delivery in faulty network + ## environments but with less performance. Passive replication may nearly + ## double the speed of the totem protocol if the protocol doesn't become + ## CPU bound. The final option is none, in which case only one network + ## interface will be used to operate the totem protocol. + ## + ## If only one *interface* directive is specified, 'none' is automatically + ## chosen. If multiple *interface* directives are specified, only 'active' + ## or 'passive' may be chosen. + ## + ## The maximum number of *interface* directives that is allowed for either + ## mode ('active' or 'passive') is 2. + attribute rrp_mode {"active"|"none"|"passive"}?, + + # CFG: corosync.conf + ## This specifies the number of times + ## a problem is detected with multicast before setting the link faulty for + ## passive RRP mode. This variable is unused in active RRP mode. + ## + ## The default is 10 times *rrp_problem_count_threshold*. + attribute rrp_problem_count_mcast_threshold {xsd:unsignedInt}?, + + # XXX: implied check + # CFG: corosync.conf, cluster.conf + ## This specifies the number of times + ## a problem is detected with a link before setting the link faulty. + ## Once a link is set faulty, no more data is transmitted upon it. Also, + ## the problem counter is no longer decremented when the problem count + ## timeout expires. + ## + ## A problem is detected whenever all tokens from the proceeding + ## processor have not been received within the *rrp_token_expired_timeout*. + ## The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be + ## at least 50 milliseconds less then the *token* timeout, or a complete + ## reconfiguration may occur. + [ a:defaultValue = "10" ] + attribute rrp_problem_count_threshold {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This specifies the time in milliseconds + ## to wait before decrementing the problem count by 1 for a particular ring + ## to ensure a link is not marked faulty for transient network failures. + [ a:defaultValue = "2000" ] + attribute rrp_problem_count_timeout {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This specifies the time in milliseconds + ## to increment the problem counter for the redundant ring protocol after + ## not having received a token from all rings for a particular processor. + ## + ## This value will automatically be calculated from the *token* timeout + ## and *problem_count_threshold* but may be overridden. + [ a:defaultValue = "47" + a4doc:danger-hint = + "It is not recommended to override this value without guidance" + ~ " from the corosync community." ] + attribute rrp_token_expired_timeout {xsd:unsignedInt}?, + + # XXX: implied check/migration to current items + # CFG: corosync.conf, cluster.conf + ## This specifies that HMAC/SHA1 authentication should be used + ## to authenticate all messages. It further specifies that all data + ## should be encrypted with the nss library and aes256 encryption + ## algorithm to protect data from eavesdropping. + ## + ## Enabling this option adds a encryption header to every message sent + ## by totem which reduces total throughput. Also encryption and + ## authentication consume extra CPU cycles in corosync. + [ a:defaultValue = "on" + a4doc:deprecation-hint = + "It's recomended to use combination of *crypto_cipher* and *crypto_hash*." + ] + attribute secauth {"off"|"on"}?, + + # CFG: corosync.conf + ## This timeout specifies in milliseconds + ## an upper range between 0 and *send_join* to wait before sending a join + ## message. For eprecationtions with less then 32 nodes, this parameter + ## is not necessary. For larger rings, this parameter is necessary + ## to ensure the NIC is not overflowed with join messages on formation of + ## a new ring. A reasonable value for large rings (128 nodes) would be + ## 80msec. Other timer values must also change if this value is changed. + [ a:defaultValue = "0" + a4doc:danger-hint = + "Seek advice from the corosync mailing list if trying to run" + ~ " larger configurations." ] + attribute send_join {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This constant specifies how many rotations + ## of the token without any multicast traffic should occur before the hold + ## timer is started. + [ a:defaultValue = "30" ] + attribute seqno_unchanged_const {xsd:unsignedInt}?, + + # CFG: undocumented + attribute threads {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This timeout specifies in milliseconds + ## until a token loss is declared after not receiving a token. This is + ## the time spent detecting a failure of a processor in the current + ## configuration. Reforming a new configuration takes about 50 + ## milliseconds in addition to this timeout. + [ a:defaultValue = "1000" ] + attribute token {xsd:unsignedInt}?, + + # CFG: corosync.conf + ## This timeout specifies in milliseconds + ## after how long before receiving a token the token is retransmitted. + ## This will be automatically calculated if token is modified. + [ a:defaultValue = "238" + a4doc:danger-hint = + "It is not recommended to override this value without guidance" + ~ " from the corosync community." ] + attribute token_retransmit {xsd:unsignedInt}?, + + # CFG: corosync.conf, cluster.conf + ## This value identifies how many token + ## retransmits should be attempted before forming a new configuration. + ## If this value is set, retransmit and hold will be automatically + ## calculated from *retransmits_before_loss* and token. + [ a:defaultValue = "4" ] + attribute token_retransmits_before_loss_const {xsd:unsignedInt}?, + + # CFG: corosync.conf + [ a:defaultValue = "udp" ] + attribute transport {"iba"|"udp"|"udpu"}?, + ## This option controls the transport + ## mechanism used. If the interface to which corosync is binding is + ## an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter + ## may be specified. To avoid the use of multicast entirely, a unicast + ## transport parameter 'udpu' can be specified. This requires specifying + ## the list of members in *nodelist* directive, that could potentially make + ## up the membership before deployment. + + # CFG: corosync.conf + ## This specifies the version of + ## the configuration file. Currently the only valid value for this + ## option is '2'. + attribute version {xsd:unsignedInt}, + + # CFG: corosync.conf + ## This option controls the virtual + ## synchrony filter type used to identify a primary component. + ## The preferred choice is YKD dynamic linear voting, however, for + ## clusters larger then 32 nodes YKD consumes alot of memory. For large + ## scale clusters that are created by changing the MAX_PROCESSORS_COUNT + ## #define in the C code totem.h file, the virtual synchrony filter 'none' + ## is recommended but then AMF and DLCK services (which are currently + ## experimental) are not safe for use. + [ a:defaultValue = "ykd" ] + attribute vsftype {"none"|"ykd"}?, + + # CFG: corosync.conf, cluster.conf + ## This constant specifies the maximum number + ## of messages that may be sent on one token rotation. If all processors + ## perform equally well, this value could be large (300), which would + ## introduce higher latency from origination to delivery for very large + ## rings. To reduce latency in large rings (16+), the defaults are a safe + ## compromise. If 1 or more slow processor(s) are present among fast + ## processors, *window_size* should be no larger then 256000 / *netmtu* + ## to avoid overflow of the kernel receive buffers. The user is notified + ## of this by the display of a retransmit list in the notification logs. + ## There is no loss of data, but performance is reduced when these errors + ## occur. + [ a:defaultValue = "50" ] + attribute window_size {xsd:unsignedInt}?, + + interface* + } + +interface = + element interface { + # CFG: corosync.conf, cluster.conf + ## This specifies the network address + ## the corosync executive should bind to. + ## *bindnetaddr* should be an IP address configured on the system, or + ## a network address. + ## + ## For example, if the local interface is `192.168.5.92` with netmask + ## `255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or + ## `192.168.5.0`. If the local interface is `192.168.5.92` with netmask + ## `255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`, + ## and so forth. + ## + ## This may also be an IPv6 address, in which case IPv6 networking will be + ## used. In this case, the exact address must be specified and there is no + ## automatic selection of the network interface within a specific subnet + ## as with IPv4. + ## + ## If IPv6 networking is used, the *nodeid* field in *nodelist* must be + ## specified. + attribute bindnetaddr {text}?, + + # CFG: corosync.conf + ## This is optional and can be set to 'yes'. If it is set to 'yes', + ## the broadcast address will be used for communication. If this option + ## is set, *mcastaddr* should not be set. + [ a:defaultValue = "no" ] + attribute broadcast {"no"|"yes"}?, + + # CFG: corosync.conf, cluster.conf + ## This is the multicast address used + ## by corosync executive. The default should work for most networks, but + ## the network administrator should be queried about a multicast address + ## to use. Avoid `224.x.x.x` because this is a "config" multicast address. + ## + ## This may also be an IPv6 multicast address, in which case IPv6 networking + ## will be used. If IPv6 networking is used, the *nodeid* field in + ## *nodelist* must be specified. + ## + ## It's not needed to use this option if *cluster_name* option is used. + ## If both options are used, *mcastaddr* has higher priority. + attribute mcastaddr {text}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the UDP port number. + ## It is possible to use the same multicast address on a network with + ## the corosync services configured for different UDP ports. Please note + ## corosync uses two UDP ports *mcastport* (for mcast receives) and + ## *mcastport* - 1 (for mcast sends). If you have multiple clusters + ## on the same network using the same *mcastaddr* please configure + ## the **mcastport**s with a gap. + attribute mcastport {xsd:unsignedShort}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the ring number for + ## the interface. When using the redundant ring protocol, each interface + ## should specify separate ring numbers to uniquely identify to + ## the membership protocol which interface to use for which redundant ring. + ## The *ringnumber* must start at 0. + attribute ringnumber {xsd:unsignedByte}?, + + # CFG: corosync.conf, cluster.conf + ## This specifies the Time To Live (TTL). + ## If you run your cluster on a routed network then the default of '1' will + ## be too small. This option provides a way to increase this up to '255'. + ## The valid range is '0..255'. Note that this is only valid on multicast + ## transport types. + [ a:defaultValue = "1" ] + attribute ttl {xsd:unsignedByte}? + } + +#uidgid = +# element uidgid { +# attribute uid {text}?, +# attribute gid {text}? +# } diff --git a/corosync.rng.expected b/corosync.rng.expected new file mode 100644 index 0000000..3e4ca2f --- /dev/null +++ b/corosync.rng.expected @@ -0,0 +1,958 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0" + datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" + xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" + xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc"> + <!-- + Copyright 2013 Red Hat, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Red Hat, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + + style guide (RELAX NG Compact only): + * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence + separator, AsciiDoc formatting is used within them + * sort everything you can to ease the lookup + --> + <start> + <ref name="corosync"/> + </start> + <define name="corosync"> + <element name="corosync"> + <interleave> + <optional> + <ref name="logging"/> + </optional> + <optional> + <ref name="nodelist"/> + </optional> + <optional> + <ref name="quorum"/> + </optional> + <ref name="totem"/> + <optional> + <ref name="uidgid"/> + </optional> + </interleave> + </element> + </define> + <define name="macro.logging_attributes"> + <optional> + <attribute name="debug" a:defaultValue="off"> + <a:documentation>This specifies whether debug output is +logged for this particular logger. Also can contain value trace, what +is highest level of debug informations.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="logfile_priority" a:defaultValue="info"> + <a:documentation>This specifies the logfile level for this particular subsystem. Ignored +if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug' +(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="syslog_facility" a:defaultValue="daemon"> + <a:documentation>This specifies the syslog facility type +that will be used for any messages sent to syslog. Options are +'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', +'local6' and 'local7'.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>daemon</value> + <value>local0</value> + <value>local1</value> + <value>local2</value> + <value>local3</value> + <value>local4</value> + <value>local5</value> + <value>local6</value> + <value>local7</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="syslog_priority" a:defaultValue="info"> + <a:documentation>This specifies the syslog level for this +particular subsystem. Ignored if *debug* is 'on'. Possible values are: +'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err', +'info', 'notice' and 'warning'.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="to_logfile" a:defaultValue="no"> + <a:documentation>This specifies the destination +of logging output. + +Please note, if you are using *to_logfile* and want to rotate the file, +use `logrotate(8)` with the option `copytruncate`, e.g. + +---- +/var/log/corosync.log { + missingok + compress + notifempty + daily + rotate 7 + copytruncate +} +----</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="to_stderr" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="to_syslog" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + </define> + <define name="logging"> + <element name="logging"> + <group> + <optional> + <attribute name="timestamp" a:defaultValue="off"> + <a:documentation>This specifies that a timestamp is placed +on all log messages.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="fileline" a:defaultValue="off"> + <a:documentation>This specifies that file and line should +be printed.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="function_name" a:defaultValue="off"> + <a:documentation>This specifies that the code function name +should be printed.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <ref name="macro.logging_attributes"/> + <zeroOrMore> + <ref name="logger_subsys"/> + </zeroOrMore> + </group> + </element> + </define> + <define name="logger_subsys"> + <element name="logger_subsys"> + <group> + <ref name="macro.logging_attributes"/> + <attribute name="subsys"> + <a:documentation>This specifies the subsystem identity +(name) for which logging is specified. This is the name used by +a service in the `log_init` call. E.g., 'CPG'. +This option is required.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <text/> + </attribute> + </group> + </element> + </define> + <define name="nodelist"> + <element name="nodelist"> + <optional> + <ref name="node"/> + </optional> + </element> + </define> + <define name="node"> + <element name="node"> + <group> + <optional> + <attribute name="noid"> + <a:documentation>This configuration option is optional when +using IPv4 and required when using IPv6. This is a 32 bit value +specifying the node identifier delivered to the cluster membership +service. If this is not specified with IPv4, the node id will be +determined from the 32 bit IP address the system to which the system +is bound with ring identifier of 0. The node identifier value of zero +is reserved and should not be used.</a:documentation> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <data type="unsignedInt"/> + </attribute> + </optional> + <attribute name="ring0_addr"> + <a:documentation>This specifies IP address of one of the nodes for particular ring +as denoted by its number (instead 0, there can be higher numbers).</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <text/> + </attribute> + <optional> + <attribute name="ring1_addr"/> + </optional> + <optional> + <attribute name="ring2_addr"/> + </optional> + <optional> + <attribute name="ring3_addr"/> + </optional> + <optional> + <attribute name="ring4_addr"/> + </optional> + <optional> + <attribute name="ring5_addr"/> + </optional> + <optional> + <attribute name="ring6_addr"/> + </optional> + <optional> + <attribute name="ring7_addr"/> + </optional> + <optional> + <attribute name="ring8_addr"/> + </optional> + <optional> + <attribute name="ring9_addr"/> + </optional> + </group> + <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only --> + </element> + </define> + <define name="quorum"> + <element name="quorum"> + <group> + <optional> + <attribute name="allow_downscale" a:defaultValue="0"> + <a:documentation>This enables Downscale feature +(see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="auto_tie_breaker" a:defaultValue="0"> + <a:documentation>This enables Auto Tie Breaker feature +(see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="expected_votes"> + <a:documentation>This specifies the number of expected votes, overriding the number +implied by the number of *node* items within *nodes*.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="last_man_standing" a:defaultValue="0"> + <a:documentation>This enables Last Man Standing feature +(see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="last_man_standing_window" a:defaultValue="0"> + <a:documentation>This specifies the tunable for Last Man +Standing feature (see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf --> + <data type="nonNegativeInteger"/> + </attribute> + </optional> + <optional> + <attribute name="provider"> + <a:documentation>This specifies the quorum algorithm to use. +As of now, only 'corosync_votequorum' is supported.</a:documentation> + <!-- CFG: corosync.conf --> + <value>corosync_votequorum</value> + </attribute> + </optional> + <optional> + <attribute name="two_node" a:defaultValue="0"> + <a:documentation>This enables two node cluster operations +(see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="wait_for_all" a:defaultValue="0"> + <a:documentation>This enables Wait For All feature +(see `votequorum(5)`).</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + </group> + </element> + </define> + <define name="totem"> + <element name="totem"> + <group> + <optional> + <attribute name="clear_node_high_bit" a:defaultValue="no" + a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade)."> + <a:documentation>This configuration option is optional and +is only relevant when no *nodeid* is specified. Some corosync clients +require a signed 32 bit nodeid that is greater than zero however by +default corosync uses all 32 bits of the IPv4 address space when +generating a nodeid. Set this option to 'yes' to force the high bit +to be zero and therefor ensure the nodeid is a positive signed 32 bit +integer.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="cluster_name"> + <a:documentation>This specifies the name of cluster and it's +used for automatic generating of multicast address.</a:documentation> + <!-- + CFG: corosync.conf, cluster.conf + NOTE: not a direct mapping in cluster.conf (top-level tag instead) + --> + <text/> + </attribute> + </optional> + <optional> + <attribute name="consensus" a:defaultValue="1200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for consensus to be achieved before starting a new round +of membership configuration. The minimum value for *consensus* must be +1.2 * *token*. + +This value will be automatically calculated at 1.2 * *token* if +the user doesn't specify a *consensus* value. + +For two node clusters, a *consensus* larger then the *join* timeout but +less then *token* is safe. For three node or larger clusters, +*consensus* should be larger then token. There is an increasing risk +of odd membership changes, which still guarantee virtual synchrony, +as node count grows if *consensus* is less than *token*.</a:documentation> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="crypto_cipher" a:defaultValue="aes256"> + <a:documentation>This specifies which cipher should be used +to encrypt all messages. Valid values are 'none' (no encryption), +'aes256', 'aes192', 'aes128' and '3des'.</a:documentation> + <!-- + XXX: missing nss? + CFG: corosync.conf + --> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>none</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="crypto_compat"> + <!-- CFG: undocumented --> + <choice> + <value>2.0</value> + <value>2.2</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="crypto_hash" a:defaultValue="sha1"> + <a:documentation>This specifies which HMAC authentication +should be used to authenticate all messages. Valid values are 'none' +(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>none</value> + <value>md5</value> + <value>sha1</value> + <value>sha256</value> + <value>sha384</value> + <value>sha512</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="crypto_type"> + <!-- CFG: undocumented --> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>nss</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="downcheck" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking that a network interface is back up after +it has been downed.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="fail_recv_const" a:defaultValue="2500"> + <a:documentation>This constant specifies how many rotations +of the token without receiving any of the messages when messages should +be received may occur before a new configuration is formed.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="heartbeat_failures_allowed" a:defaultValue="0"> + <a:documentation>Configures the optional HeartBeating +mechanism for faster failure detection. Keep in mind that engaging this +mechanism in lossy networks could cause faulty loss declaration as +the mechanism relies on the network for heartbeating. + +So as a rule of thumb use this mechanism if you require improved +failure in low to medium utilized networks. + +This constant specifies the number of heartbeat failures the system +should tolerate before declaring heartbeat failure, e.g., 3. +Also if this value is not set or is 0 then the heartbeat mechanism is +not engaged in the system and token rotation is the method of failure +detection. Zero disables the mechanism.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="hold" a:defaultValue="180" + a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +how long the token should be held by the representative when +the protocol is under low utilization.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="join" a:defaultValue="50"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for join messages in the membership protocol.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="max_messages" a:defaultValue="17"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent by one processor on receipt of the token. +The *max_messages* parameter is limited to 256000 / *netmtu* to prevent +overflow of the kernel transmit buffers.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="max_network_delay" a:defaultValue="50"> + <a:documentation>This constant specifies in milliseconds +the approximate delay that your network takes to transport one packet +from one machine to another. This value is to be set by system engineers +and please don't change it if not sure as this effects the failure +detection mechanism using heartbeat.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="merge" a:defaultValue="200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking for a partition when no multicast traffic +is being sent. If multicast traffic is being sent, the merge detection +happens automatically as a function of the protocol.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="miss_count_const" a:defaultValue="5"> + <a:documentation>This constant defines the maximum number +of times on receipt of a token a message is checked for retransmission +before a retransmission occurs. This parameter is useful to modify for +switches that delay multicast packets compared to unicast packets. +The default setting works well for nearly all modern switches.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="netmtu" a:defaultValue="1500"> + <a:documentation>This specifies the network maximum transmit +unit. To set this value beyond 1500, the regular frame MTU, requires +ethernet devices that support large, or also called jumbo, frames. +If any device in the network doesn't support large frames, the protocol +will not operate properly. The hosts must also have their mtu size set +from 1500 to whatever frame size is specified here. + +Please note while some NICs or switches +claim large frame support, they support 9000 MTU as the maximum frame +size including the IP header. Setting the *netmtu* and host MTUs to 9000 +will cause totem to use the full 9000 bytes of the frame. Then Linux +will add a 18 byte header moving the full frame size to 9018. +As a result some hardware will not operate properly with this size +of data. A *netmtu* of 8982 seems to work for the few large frame devices +that have been tested. Some manufacturers claim large frame support +when in fact they support frame sizes of 4500 bytes. + +When sending multicast traffic, if the network frequently reconfigures, +chances are that some device in the network doesn't support large frames. + +Choose hardware carefully if intending to use large frame support.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="nodeid"> + <!-- CFG: undocumented --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000"> + <a:documentation>This specifies the time in milliseconds +to check if the failed ring can be auto-recovered.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="rrp_mode"> + <a:documentation>This specifies the mode of redundant ring, +which may be 'none', 'active', or 'passive'. Active replication offers +none`, active, or passive. Active replication offers +slightly lower latency from transmit to delivery in faulty network +environments but with less performance. Passive replication may nearly +double the speed of the totem protocol if the protocol doesn't become +CPU bound. The final option is none, in which case only one network +interface will be used to operate the totem protocol. + +If only one *interface* directive is specified, 'none' is automatically +chosen. If multiple *interface* directives are specified, only 'active' +or 'passive' may be chosen. + +The maximum number of *interface* directives that is allowed for either +mode ('active' or 'passive') is 2.</a:documentation> + <!-- + XXX: implied check: active,passive -> count(interface) <= 2 + CFG: corosync.conf, cluster.conf + --> + <choice> + <value>active</value> + <value>none</value> + <value>passive</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="rrp_problem_count_mcast_threshold"> + <a:documentation>This specifies the number of times +a problem is detected with multicast before setting the link faulty for +passive RRP mode. This variable is unused in active RRP mode. + +The default is 10 times *rrp_problem_count_threshold*.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="rrp_problem_count_threshold" a:defaultValue="10"> + <a:documentation>This specifies the number of times +a problem is detected with a link before setting the link faulty. +Once a link is set faulty, no more data is transmitted upon it. Also, +the problem counter is no longer decremented when the problem count +timeout expires. + +A problem is detected whenever all tokens from the proceeding +processor have not been received within the *rrp_token_expired_timeout*. +The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be +at least 50 milliseconds less then the *token* timeout, or a complete +reconfiguration may occur.</a:documentation> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="rrp_problem_count_timeout" a:defaultValue="2000"> + <a:documentation>This specifies the time in milliseconds +to wait before decrementing the problem count by 1 for a particular ring +to ensure a link is not marked faulty for transient network failures.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="rrp_token_expired_timeout" a:defaultValue="47" + a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This specifies the time in milliseconds +to increment the problem counter for the redundant ring protocol after +not having received a token from all rings for a particular processor. + +This value will automatically be calculated from the *token* timeout +and *problem_count_threshold* but may be overridden.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="secauth" a:defaultValue="on" + a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*."> + <a:documentation>This specifies that HMAC/SHA1 authentication should be used +to authenticate all messages. It further specifies that all data +should be encrypted with the nss library and aes256 encryption +algorithm to protect data from eavesdropping. + +Enabling this option adds a encryption header to every message sent +by totem which reduces total throughput. Also encryption and +authentication consume extra CPU cycles in corosync.</a:documentation> + <!-- + XXX: implied check/migration to current items + CFG: corosync.conf, cluster.conf + --> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="send_join" a:defaultValue="0" + a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations."> + <a:documentation>This timeout specifies in milliseconds +an upper range between 0 and *send_join* to wait before sending a join +message. For eprecationtions with less then 32 nodes, this parameter +is not necessary. For larger rings, this parameter is necessary +to ensure the NIC is not overflowed with join messages on formation of +a new ring. A reasonable value for large rings (128 nodes) would be +80msec. Other timer values must also change if this value is changed.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="seqno_unchanged_const" a:defaultValue="30"> + <a:documentation>This constant specifies how many rotations +of the token without any multicast traffic should occur before the hold +timer is started.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="threads"> + <!-- CFG: undocumented --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="token" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds +until a token loss is declared after not receiving a token. This is +the time spent detecting a failure of a processor in the current +configuration. Reforming a new configuration takes about 50 +milliseconds in addition to this timeout.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="token_retransmit" a:defaultValue="238" + a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +after how long before receiving a token the token is retransmitted. +This will be automatically calculated if token is modified.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="token_retransmits_before_loss_const" a:defaultValue="4"> + <a:documentation>This value identifies how many token +retransmits should be attempted before forming a new configuration. +If this value is set, retransmit and hold will be automatically +calculated from *retransmits_before_loss* and token.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <attribute name="transport" a:defaultValue="udp"> + <!-- CFG: corosync.conf --> + <choice> + <value>iba</value> + <value>udp</value> + <value>udpu</value> + </choice> + </attribute> + </optional> + <attribute name="version"> + <a:documentation>This option controls the transport +mechanism used. If the interface to which corosync is binding is +an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter +may be specified. To avoid the use of multicast entirely, a unicast +transport parameter 'udpu' can be specified. This requires specifying +the list of members in *nodelist* directive, that could potentially make +up the membership before deployment.</a:documentation> + <a:documentation>This specifies the version of +the configuration file. Currently the only valid value for this +option is '2'.</a:documentation> + <!-- CFG: corosync.conf --> + <data type="unsignedInt"/> + </attribute> + <optional> + <attribute name="vsftype" a:defaultValue="ykd"> + <a:documentation>This option controls the virtual +synchrony filter type used to identify a primary component. +The preferred choice is YKD dynamic linear voting, however, for +clusters larger then 32 nodes YKD consumes alot of memory. For large +scale clusters that are created by changing the MAX_PROCESSORS_COUNT +#define in the C code totem.h file, the virtual synchrony filter 'none' +is recommended but then AMF and DLCK services (which are currently +experimental) are not safe for use.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>none</value> + <value>ykd</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="window_size" a:defaultValue="50"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent on one token rotation. If all processors +perform equally well, this value could be large (300), which would +introduce higher latency from origination to delivery for very large +rings. To reduce latency in large rings (16+), the defaults are a safe +compromise. If 1 or more slow processor(s) are present among fast +processors, *window_size* should be no larger then 256000 / *netmtu* +to avoid overflow of the kernel receive buffers. The user is notified +of this by the display of a retransmit list in the notification logs. +There is no loss of data, but performance is reduced when these errors +occur.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedInt"/> + </attribute> + </optional> + <zeroOrMore> + <ref name="interface"/> + </zeroOrMore> + </group> + </element> + </define> + <define name="interface"> + <element name="interface"> + <group> + <optional> + <attribute name="bindnetaddr"> + <a:documentation>This specifies the network address +the corosync executive should bind to. +*bindnetaddr* should be an IP address configured on the system, or +a network address. + +For example, if the local interface is `192.168.5.92` with netmask +`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or +`192.168.5.0`. If the local interface is `192.168.5.92` with netmask +`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`, +and so forth. + +This may also be an IPv6 address, in which case IPv6 networking will be +used. In this case, the exact address must be specified and there is no +automatic selection of the network interface within a specific subnet +as with IPv4. + +If IPv6 networking is used, the *nodeid* field in *nodelist* must be +specified.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <text/> + </attribute> + </optional> + <optional> + <attribute name="broadcast" a:defaultValue="no"> + <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes', +the broadcast address will be used for communication. If this option +is set, *mcastaddr* should not be set.</a:documentation> + <!-- CFG: corosync.conf --> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <attribute name="mcastaddr"> + <a:documentation>This is the multicast address used +by corosync executive. The default should work for most networks, but +the network administrator should be queried about a multicast address +to use. Avoid `224.x.x.x` because this is a "config" multicast address. + +This may also be an IPv6 multicast address, in which case IPv6 networking +will be used. If IPv6 networking is used, the *nodeid* field in +*nodelist* must be specified. + +It's not needed to use this option if *cluster_name* option is used. +If both options are used, *mcastaddr* has higher priority.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <text/> + </attribute> + </optional> + <optional> + <attribute name="mcastport"> + <a:documentation>This specifies the UDP port number. +It is possible to use the same multicast address on a network with +the corosync services configured for different UDP ports. Please note +corosync uses two UDP ports *mcastport* (for mcast receives) and +*mcastport* - 1 (for mcast sends). If you have multiple clusters +on the same network using the same *mcastaddr* please configure +the **mcastport**s with a gap.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedShort"/> + </attribute> + </optional> + <optional> + <attribute name="ringnumber"> + <a:documentation>This specifies the ring number for +the interface. When using the redundant ring protocol, each interface +should specify separate ring numbers to uniquely identify to +the membership protocol which interface to use for which redundant ring. +The *ringnumber* must start at 0.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedByte"/> + </attribute> + </optional> + <optional> + <attribute name="ttl" a:defaultValue="1"> + <a:documentation>This specifies the Time To Live (TTL). +If you run your cluster on a routed network then the default of '1' will +be too small. This option provides a way to increase this up to '255'. +The valid range is '0..255'. Note that this is only valid on multicast +transport types.</a:documentation> + <!-- CFG: corosync.conf, cluster.conf --> + <data type="unsignedByte"/> + </attribute> + </optional> + </group> + </element> + </define> + <!-- + uidgid = + element uidgid { + attribute uid {text}?, + attribute gid {text}? + } + --> +</grammar> diff --git a/corosync.rng.trang b/corosync.rng.trang new file mode 100644 index 0000000..e53e6a0 --- /dev/null +++ b/corosync.rng.trang @@ -0,0 +1,907 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + style guide (RELAX NG Compact only): + * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence + separator, AsciiDoc formatting is used within them + * sort everything you can to ease the lookup +--> +<grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <start> + <ref name="corosync"/> + </start> + <define name="corosync"> + <element name="corosync"> + <interleave> + <optional> + <ref name="logging"/> + </optional> + <optional> + <ref name="nodelist"/> + </optional> + <optional> + <ref name="quorum"/> + </optional> + <ref name="totem"/> + <optional> + <ref name="uidgid"/> + </optional> + </interleave> + </element> + </define> + <define name="macro.logging_attributes"> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="debug" a:defaultValue="off"> + <a:documentation>This specifies whether debug output is +logged for this particular logger. Also can contain value trace, what +is highest level of debug informations.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="logfile_priority" a:defaultValue="info"> + <a:documentation>This specifies the logfile level for this particular subsystem. Ignored +if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug' +(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="syslog_facility" a:defaultValue="daemon"> + <a:documentation>This specifies the syslog facility type +that will be used for any messages sent to syslog. Options are +'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', +'local6' and 'local7'.</a:documentation> + <choice> + <value>daemon</value> + <value>local0</value> + <value>local1</value> + <value>local2</value> + <value>local3</value> + <value>local4</value> + <value>local5</value> + <value>local6</value> + <value>local7</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="syslog_priority" a:defaultValue="info"> + <a:documentation>This specifies the syslog level for this +particular subsystem. Ignored if *debug* is 'on'. Possible values are: +'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err', +'info', 'notice' and 'warning'.</a:documentation> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="to_logfile" a:defaultValue="no"> + <a:documentation>This specifies the destination +of logging output. + +Please note, if you are using *to_logfile* and want to rotate the file, +use `logrotate(8)` with the option `copytruncate`, e.g. + +---- +/var/log/corosync.log { + missingok + compress + notifempty + daily + rotate 7 + copytruncate +} +----</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="to_stderr" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="to_syslog" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + </define> + <define name="logging"> + <element name="logging"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="timestamp" a:defaultValue="off"> + <a:documentation>This specifies that a timestamp is placed +on all log messages.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="fileline" a:defaultValue="off"> + <a:documentation>This specifies that file and line should +be printed.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="function_name" a:defaultValue="off"> + <a:documentation>This specifies that the code function name +should be printed.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <ref name="macro.logging_attributes"/> + <zeroOrMore> + <ref name="logger_subsys"/> + </zeroOrMore> + </element> + </define> + <define name="logger_subsys"> + <element name="logger_subsys"> + <ref name="macro.logging_attributes"/> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="subsys"> + <a:documentation>This specifies the subsystem identity +(name) for which logging is specified. This is the name used by +a service in the `log_init` call. E.g., 'CPG'. +This option is required.</a:documentation> + </attribute> + </element> + </define> + <define name="nodelist"> + <element name="nodelist"> + <optional> + <ref name="node"/> + </optional> + </element> + </define> + <define name="node"> + <element name="node"> + <group> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="noid"> + <a:documentation>This configuration option is optional when +using IPv4 and required when using IPv6. This is a 32 bit value +specifying the node identifier delivered to the cluster membership +service. If this is not specified with IPv4, the node id will be +determined from the 32 bit IP address the system to which the system +is bound with ring identifier of 0. The node identifier value of zero +is reserved and should not be used.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ring0_addr"> + <a:documentation>This specifies IP address of one of the nodes for particular ring +as denoted by its number (instead 0, there can be higher numbers).</a:documentation> + </attribute> + <optional> + <attribute name="ring1_addr"/> + </optional> + <optional> + <attribute name="ring2_addr"/> + </optional> + <optional> + <attribute name="ring3_addr"/> + </optional> + <optional> + <attribute name="ring4_addr"/> + </optional> + <optional> + <attribute name="ring5_addr"/> + </optional> + <optional> + <attribute name="ring6_addr"/> + </optional> + <optional> + <attribute name="ring7_addr"/> + </optional> + <optional> + <attribute name="ring8_addr"/> + </optional> + <optional> + <attribute name="ring9_addr"/> + </optional> + </group> + <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only --> + </element> + </define> + <define name="quorum"> + <element name="quorum"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="allow_downscale" a:defaultValue="0"> + <a:documentation>This enables Downscale feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="auto_tie_breaker" a:defaultValue="0"> + <a:documentation>This enables Auto Tie Breaker feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="expected_votes"> + <a:documentation>This specifies the number of expected votes, overriding the number +implied by the number of *node* items within *nodes*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="last_man_standing" a:defaultValue="0"> + <a:documentation>This enables Last Man Standing feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="last_man_standing_window" a:defaultValue="0"> + <a:documentation>This specifies the tunable for Last Man +Standing feature (see `votequorum(5)`).</a:documentation> + <data type="nonNegativeInteger"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="provider"> + <a:documentation>This specifies the quorum algorithm to use. +As of now, only 'corosync_votequorum' is supported.</a:documentation> + <value>corosync_votequorum</value> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="two_node" a:defaultValue="0"> + <a:documentation>This enables two node cluster operations +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="wait_for_all" a:defaultValue="0"> + <a:documentation>This enables Wait For All feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + </element> + </define> + <define name="totem"> + <element name="totem"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="clear_node_high_bit" a:defaultValue="no" a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade)."> + <a:documentation>This configuration option is optional and +is only relevant when no *nodeid* is specified. Some corosync clients +require a signed 32 bit nodeid that is greater than zero however by +default corosync uses all 32 bits of the IPv4 address space when +generating a nodeid. Set this option to 'yes' to force the high bit +to be zero and therefor ensure the nodeid is a positive signed 32 bit +integer.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- + CFG: corosync.conf, cluster.conf + NOTE: not a direct mapping in cluster.conf (top-level tag instead) + --> + <attribute name="cluster_name"> + <a:documentation>This specifies the name of cluster and it's +used for automatic generating of multicast address.</a:documentation> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="consensus" a:defaultValue="1200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for consensus to be achieved before starting a new round +of membership configuration. The minimum value for *consensus* must be +1.2 * *token*. + +This value will be automatically calculated at 1.2 * *token* if +the user doesn't specify a *consensus* value. + +For two node clusters, a *consensus* larger then the *join* timeout but +less then *token* is safe. For three node or larger clusters, +*consensus* should be larger then token. There is an increasing risk +of odd membership changes, which still guarantee virtual synchrony, +as node count grows if *consensus* is less than *token*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: missing nss? + CFG: corosync.conf + --> + <attribute name="crypto_cipher" a:defaultValue="aes256"> + <a:documentation>This specifies which cipher should be used +to encrypt all messages. Valid values are 'none' (no encryption), +'aes256', 'aes192', 'aes128' and '3des'.</a:documentation> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>none</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="crypto_compat"> + <choice> + <value>2.0</value> + <value>2.2</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="crypto_hash" a:defaultValue="sha1"> + <a:documentation>This specifies which HMAC authentication +should be used to authenticate all messages. Valid values are 'none' +(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation> + <choice> + <value>none</value> + <value>md5</value> + <value>sha1</value> + <value>sha256</value> + <value>sha384</value> + <value>sha512</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="crypto_type"> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>nss</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="downcheck" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking that a network interface is back up after +it has been downed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="fail_recv_const" a:defaultValue="2500"> + <a:documentation>This constant specifies how many rotations +of the token without receiving any of the messages when messages should +be received may occur before a new configuration is formed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="heartbeat_failures_allowed" a:defaultValue="0"> + <a:documentation>Configures the optional HeartBeating +mechanism for faster failure detection. Keep in mind that engaging this +mechanism in lossy networks could cause faulty loss declaration as +the mechanism relies on the network for heartbeating. + +So as a rule of thumb use this mechanism if you require improved +failure in low to medium utilized networks. + +This constant specifies the number of heartbeat failures the system +should tolerate before declaring heartbeat failure, e.g., 3. +Also if this value is not set or is 0 then the heartbeat mechanism is +not engaged in the system and token rotation is the method of failure +detection. Zero disables the mechanism.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="hold" a:defaultValue="180" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +how long the token should be held by the representative when +the protocol is under low utilization.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="join" a:defaultValue="50"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for join messages in the membership protocol.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="max_messages" a:defaultValue="17"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent by one processor on receipt of the token. +The *max_messages* parameter is limited to 256000 / *netmtu* to prevent +overflow of the kernel transmit buffers.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="max_network_delay" a:defaultValue="50"> + <a:documentation>This constant specifies in milliseconds +the approximate delay that your network takes to transport one packet +from one machine to another. This value is to be set by system engineers +and please don't change it if not sure as this effects the failure +detection mechanism using heartbeat.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="merge" a:defaultValue="200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking for a partition when no multicast traffic +is being sent. If multicast traffic is being sent, the merge detection +happens automatically as a function of the protocol.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="miss_count_const" a:defaultValue="5"> + <a:documentation>This constant defines the maximum number +of times on receipt of a token a message is checked for retransmission +before a retransmission occurs. This parameter is useful to modify for +switches that delay multicast packets compared to unicast packets. +The default setting works well for nearly all modern switches.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="netmtu" a:defaultValue="1500"> + <a:documentation>This specifies the network maximum transmit +unit. To set this value beyond 1500, the regular frame MTU, requires +ethernet devices that support large, or also called jumbo, frames. +If any device in the network doesn't support large frames, the protocol +will not operate properly. The hosts must also have their mtu size set +from 1500 to whatever frame size is specified here. + +Please note while some NICs or switches +claim large frame support, they support 9000 MTU as the maximum frame +size including the IP header. Setting the *netmtu* and host MTUs to 9000 +will cause totem to use the full 9000 bytes of the frame. Then Linux +will add a 18 byte header moving the full frame size to 9018. +As a result some hardware will not operate properly with this size +of data. A *netmtu* of 8982 seems to work for the few large frame devices +that have been tested. Some manufacturers claim large frame support +when in fact they support frame sizes of 4500 bytes. + +When sending multicast traffic, if the network frequently reconfigures, +chances are that some device in the network doesn't support large frames. + +Choose hardware carefully if intending to use large frame support.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="nodeid"> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000"> + <a:documentation>This specifies the time in milliseconds +to check if the failed ring can be auto-recovered.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check: active,passive -> count(interface) <= 2 + CFG: corosync.conf, cluster.conf + --> + <attribute name="rrp_mode"> + <a:documentation>This specifies the mode of redundant ring, +which may be 'none', 'active', or 'passive'. Active replication offers +none`, active, or passive. Active replication offers +slightly lower latency from transmit to delivery in faulty network +environments but with less performance. Passive replication may nearly +double the speed of the totem protocol if the protocol doesn't become +CPU bound. The final option is none, in which case only one network +interface will be used to operate the totem protocol. + +If only one *interface* directive is specified, 'none' is automatically +chosen. If multiple *interface* directives are specified, only 'active' +or 'passive' may be chosen. + +The maximum number of *interface* directives that is allowed for either +mode ('active' or 'passive') is 2.</a:documentation> + <choice> + <value>active</value> + <value>none</value> + <value>passive</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_problem_count_mcast_threshold"> + <a:documentation>This specifies the number of times +a problem is detected with multicast before setting the link faulty for +passive RRP mode. This variable is unused in active RRP mode. + +The default is 10 times *rrp_problem_count_threshold*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="rrp_problem_count_threshold" a:defaultValue="10"> + <a:documentation>This specifies the number of times +a problem is detected with a link before setting the link faulty. +Once a link is set faulty, no more data is transmitted upon it. Also, +the problem counter is no longer decremented when the problem count +timeout expires. + +A problem is detected whenever all tokens from the proceeding +processor have not been received within the *rrp_token_expired_timeout*. +The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be +at least 50 milliseconds less then the *token* timeout, or a complete +reconfiguration may occur.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_problem_count_timeout" a:defaultValue="2000"> + <a:documentation>This specifies the time in milliseconds +to wait before decrementing the problem count by 1 for a particular ring +to ensure a link is not marked faulty for transient network failures.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_token_expired_timeout" a:defaultValue="47" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This specifies the time in milliseconds +to increment the problem counter for the redundant ring protocol after +not having received a token from all rings for a particular processor. + +This value will automatically be calculated from the *token* timeout +and *problem_count_threshold* but may be overridden.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check/migration to current items + CFG: corosync.conf, cluster.conf + --> + <attribute name="secauth" a:defaultValue="on" a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*."> + <a:documentation>This specifies that HMAC/SHA1 authentication should be used +to authenticate all messages. It further specifies that all data +should be encrypted with the nss library and aes256 encryption +algorithm to protect data from eavesdropping. + +Enabling this option adds a encryption header to every message sent +by totem which reduces total throughput. Also encryption and +authentication consume extra CPU cycles in corosync.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="send_join" a:defaultValue="0" a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations."> + <a:documentation>This timeout specifies in milliseconds +an upper range between 0 and *send_join* to wait before sending a join +message. For eprecationtions with less then 32 nodes, this parameter +is not necessary. For larger rings, this parameter is necessary +to ensure the NIC is not overflowed with join messages on formation of +a new ring. A reasonable value for large rings (128 nodes) would be +80msec. Other timer values must also change if this value is changed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="seqno_unchanged_const" a:defaultValue="30"> + <a:documentation>This constant specifies how many rotations +of the token without any multicast traffic should occur before the hold +timer is started.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="threads"> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="token" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds +until a token loss is declared after not receiving a token. This is +the time spent detecting a failure of a processor in the current +configuration. Reforming a new configuration takes about 50 +milliseconds in addition to this timeout.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="token_retransmit" a:defaultValue="238" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +after how long before receiving a token the token is retransmitted. +This will be automatically calculated if token is modified.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="token_retransmits_before_loss_const" a:defaultValue="4"> + <a:documentation>This value identifies how many token +retransmits should be attempted before forming a new configuration. +If this value is set, retransmit and hold will be automatically +calculated from *retransmits_before_loss* and token.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="transport" a:defaultValue="udp"> + <choice> + <value>iba</value> + <value>udp</value> + <value>udpu</value> + </choice> + </attribute> + </optional> + <attribute name="version"> + <a:documentation>This option controls the transport +mechanism used. If the interface to which corosync is binding is +an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter +may be specified. To avoid the use of multicast entirely, a unicast +transport parameter 'udpu' can be specified. This requires specifying +the list of members in *nodelist* directive, that could potentially make +up the membership before deployment.</a:documentation> + <!-- CFG: corosync.conf --> + <a:documentation>This specifies the version of +the configuration file. Currently the only valid value for this +option is '2'.</a:documentation> + <data type="unsignedInt"/> + </attribute> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="vsftype" a:defaultValue="ykd"> + <a:documentation>This option controls the virtual +synchrony filter type used to identify a primary component. +The preferred choice is YKD dynamic linear voting, however, for +clusters larger then 32 nodes YKD consumes alot of memory. For large +scale clusters that are created by changing the MAX_PROCESSORS_COUNT +#define in the C code totem.h file, the virtual synchrony filter 'none' +is recommended but then AMF and DLCK services (which are currently +experimental) are not safe for use.</a:documentation> + <choice> + <value>none</value> + <value>ykd</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="window_size" a:defaultValue="50"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent on one token rotation. If all processors +perform equally well, this value could be large (300), which would +introduce higher latency from origination to delivery for very large +rings. To reduce latency in large rings (16+), the defaults are a safe +compromise. If 1 or more slow processor(s) are present among fast +processors, *window_size* should be no larger then 256000 / *netmtu* +to avoid overflow of the kernel receive buffers. The user is notified +of this by the display of a retransmit list in the notification logs. +There is no loss of data, but performance is reduced when these errors +occur.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <zeroOrMore> + <ref name="interface"/> + </zeroOrMore> + </element> + </define> + <define name="interface"> + <element name="interface"> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="bindnetaddr"> + <a:documentation>This specifies the network address +the corosync executive should bind to. +*bindnetaddr* should be an IP address configured on the system, or +a network address. + +For example, if the local interface is `192.168.5.92` with netmask +`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or +`192.168.5.0`. If the local interface is `192.168.5.92` with netmask +`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`, +and so forth. + +This may also be an IPv6 address, in which case IPv6 networking will be +used. In this case, the exact address must be specified and there is no +automatic selection of the network interface within a specific subnet +as with IPv4. + +If IPv6 networking is used, the *nodeid* field in *nodelist* must be +specified.</a:documentation> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="broadcast" a:defaultValue="no"> + <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes', +the broadcast address will be used for communication. If this option +is set, *mcastaddr* should not be set.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="mcastaddr"> + <a:documentation>This is the multicast address used +by corosync executive. The default should work for most networks, but +the network administrator should be queried about a multicast address +to use. Avoid `224.x.x.x` because this is a "config" multicast address. + +This may also be an IPv6 multicast address, in which case IPv6 networking +will be used. If IPv6 networking is used, the *nodeid* field in +*nodelist* must be specified. + +It's not needed to use this option if *cluster_name* option is used. +If both options are used, *mcastaddr* has higher priority.</a:documentation> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="mcastport"> + <a:documentation>This specifies the UDP port number. +It is possible to use the same multicast address on a network with +the corosync services configured for different UDP ports. Please note +corosync uses two UDP ports *mcastport* (for mcast receives) and +*mcastport* - 1 (for mcast sends). If you have multiple clusters +on the same network using the same *mcastaddr* please configure +the **mcastport**s with a gap.</a:documentation> + <data type="unsignedShort"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ringnumber"> + <a:documentation>This specifies the ring number for +the interface. When using the redundant ring protocol, each interface +should specify separate ring numbers to uniquely identify to +the membership protocol which interface to use for which redundant ring. +The *ringnumber* must start at 0.</a:documentation> + <data type="unsignedByte"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ttl" a:defaultValue="1"> + <a:documentation>This specifies the Time To Live (TTL). +If you run your cluster on a routed network then the default of '1' will +be too small. This option provides a way to increase this up to '255'. +The valid range is '0..255'. Note that this is only valid on multicast +transport types.</a:documentation> + <data type="unsignedByte"/> + </attribute> + </optional> + </element> + </define> +</grammar> +<!-- + uidgid = + element uidgid { + attribute uid {text}?, + attribute gid {text}? + } +--> diff --git a/corosync.trang.rng b/corosync.trang.rng new file mode 100644 index 0000000..e53e6a0 --- /dev/null +++ b/corosync.trang.rng @@ -0,0 +1,907 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + style guide (RELAX NG Compact only): + * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence + separator, AsciiDoc formatting is used within them + * sort everything you can to ease the lookup +--> +<grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + <start> + <ref name="corosync"/> + </start> + <define name="corosync"> + <element name="corosync"> + <interleave> + <optional> + <ref name="logging"/> + </optional> + <optional> + <ref name="nodelist"/> + </optional> + <optional> + <ref name="quorum"/> + </optional> + <ref name="totem"/> + <optional> + <ref name="uidgid"/> + </optional> + </interleave> + </element> + </define> + <define name="macro.logging_attributes"> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="debug" a:defaultValue="off"> + <a:documentation>This specifies whether debug output is +logged for this particular logger. Also can contain value trace, what +is highest level of debug informations.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="logfile_priority" a:defaultValue="info"> + <a:documentation>This specifies the logfile level for this particular subsystem. Ignored +if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug' +(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="syslog_facility" a:defaultValue="daemon"> + <a:documentation>This specifies the syslog facility type +that will be used for any messages sent to syslog. Options are +'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', +'local6' and 'local7'.</a:documentation> + <choice> + <value>daemon</value> + <value>local0</value> + <value>local1</value> + <value>local2</value> + <value>local3</value> + <value>local4</value> + <value>local5</value> + <value>local6</value> + <value>local7</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="syslog_priority" a:defaultValue="info"> + <a:documentation>This specifies the syslog level for this +particular subsystem. Ignored if *debug* is 'on'. Possible values are: +'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err', +'info', 'notice' and 'warning'.</a:documentation> + <choice> + <value>alert</value> + <value>crit</value> + <value>debug</value> + <value>emerg</value> + <value>err</value> + <value>info</value> + <value>notice</value> + <value>warning</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="to_logfile" a:defaultValue="no"> + <a:documentation>This specifies the destination +of logging output. + +Please note, if you are using *to_logfile* and want to rotate the file, +use `logrotate(8)` with the option `copytruncate`, e.g. + +---- +/var/log/corosync.log { + missingok + compress + notifempty + daily + rotate 7 + copytruncate +} +----</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="to_stderr" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="to_syslog" a:defaultValue="yes"> + <a:documentation>This specifies the destination +of logging output.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + </define> + <define name="logging"> + <element name="logging"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="timestamp" a:defaultValue="off"> + <a:documentation>This specifies that a timestamp is placed +on all log messages.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="fileline" a:defaultValue="off"> + <a:documentation>This specifies that file and line should +be printed.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="function_name" a:defaultValue="off"> + <a:documentation>This specifies that the code function name +should be printed.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <ref name="macro.logging_attributes"/> + <zeroOrMore> + <ref name="logger_subsys"/> + </zeroOrMore> + </element> + </define> + <define name="logger_subsys"> + <element name="logger_subsys"> + <ref name="macro.logging_attributes"/> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="subsys"> + <a:documentation>This specifies the subsystem identity +(name) for which logging is specified. This is the name used by +a service in the `log_init` call. E.g., 'CPG'. +This option is required.</a:documentation> + </attribute> + </element> + </define> + <define name="nodelist"> + <element name="nodelist"> + <optional> + <ref name="node"/> + </optional> + </element> + </define> + <define name="node"> + <element name="node"> + <group> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="noid"> + <a:documentation>This configuration option is optional when +using IPv4 and required when using IPv6. This is a 32 bit value +specifying the node identifier delivered to the cluster membership +service. If this is not specified with IPv4, the node id will be +determined from the 32 bit IP address the system to which the system +is bound with ring identifier of 0. The node identifier value of zero +is reserved and should not be used.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ring0_addr"> + <a:documentation>This specifies IP address of one of the nodes for particular ring +as denoted by its number (instead 0, there can be higher numbers).</a:documentation> + </attribute> + <optional> + <attribute name="ring1_addr"/> + </optional> + <optional> + <attribute name="ring2_addr"/> + </optional> + <optional> + <attribute name="ring3_addr"/> + </optional> + <optional> + <attribute name="ring4_addr"/> + </optional> + <optional> + <attribute name="ring5_addr"/> + </optional> + <optional> + <attribute name="ring6_addr"/> + </optional> + <optional> + <attribute name="ring7_addr"/> + </optional> + <optional> + <attribute name="ring8_addr"/> + </optional> + <optional> + <attribute name="ring9_addr"/> + </optional> + </group> + <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only --> + </element> + </define> + <define name="quorum"> + <element name="quorum"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="allow_downscale" a:defaultValue="0"> + <a:documentation>This enables Downscale feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="auto_tie_breaker" a:defaultValue="0"> + <a:documentation>This enables Auto Tie Breaker feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="expected_votes"> + <a:documentation>This specifies the number of expected votes, overriding the number +implied by the number of *node* items within *nodes*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="last_man_standing" a:defaultValue="0"> + <a:documentation>This enables Last Man Standing feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="last_man_standing_window" a:defaultValue="0"> + <a:documentation>This specifies the tunable for Last Man +Standing feature (see `votequorum(5)`).</a:documentation> + <data type="nonNegativeInteger"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="provider"> + <a:documentation>This specifies the quorum algorithm to use. +As of now, only 'corosync_votequorum' is supported.</a:documentation> + <value>corosync_votequorum</value> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="two_node" a:defaultValue="0"> + <a:documentation>This enables two node cluster operations +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="wait_for_all" a:defaultValue="0"> + <a:documentation>This enables Wait For All feature +(see `votequorum(5)`).</a:documentation> + <choice> + <value>0</value> + <value>1</value> + </choice> + </attribute> + </optional> + </element> + </define> + <define name="totem"> + <element name="totem"> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="clear_node_high_bit" a:defaultValue="no" a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade)."> + <a:documentation>This configuration option is optional and +is only relevant when no *nodeid* is specified. Some corosync clients +require a signed 32 bit nodeid that is greater than zero however by +default corosync uses all 32 bits of the IPv4 address space when +generating a nodeid. Set this option to 'yes' to force the high bit +to be zero and therefor ensure the nodeid is a positive signed 32 bit +integer.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- + CFG: corosync.conf, cluster.conf + NOTE: not a direct mapping in cluster.conf (top-level tag instead) + --> + <attribute name="cluster_name"> + <a:documentation>This specifies the name of cluster and it's +used for automatic generating of multicast address.</a:documentation> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="consensus" a:defaultValue="1200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for consensus to be achieved before starting a new round +of membership configuration. The minimum value for *consensus* must be +1.2 * *token*. + +This value will be automatically calculated at 1.2 * *token* if +the user doesn't specify a *consensus* value. + +For two node clusters, a *consensus* larger then the *join* timeout but +less then *token* is safe. For three node or larger clusters, +*consensus* should be larger then token. There is an increasing risk +of odd membership changes, which still guarantee virtual synchrony, +as node count grows if *consensus* is less than *token*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: missing nss? + CFG: corosync.conf + --> + <attribute name="crypto_cipher" a:defaultValue="aes256"> + <a:documentation>This specifies which cipher should be used +to encrypt all messages. Valid values are 'none' (no encryption), +'aes256', 'aes192', 'aes128' and '3des'.</a:documentation> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>none</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="crypto_compat"> + <choice> + <value>2.0</value> + <value>2.2</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="crypto_hash" a:defaultValue="sha1"> + <a:documentation>This specifies which HMAC authentication +should be used to authenticate all messages. Valid values are 'none' +(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation> + <choice> + <value>none</value> + <value>md5</value> + <value>sha1</value> + <value>sha256</value> + <value>sha384</value> + <value>sha512</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="crypto_type"> + <choice> + <value>3des</value> + <value>aes128</value> + <value>aes192</value> + <value>aes256</value> + <value>nss</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="downcheck" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking that a network interface is back up after +it has been downed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="fail_recv_const" a:defaultValue="2500"> + <a:documentation>This constant specifies how many rotations +of the token without receiving any of the messages when messages should +be received may occur before a new configuration is formed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="heartbeat_failures_allowed" a:defaultValue="0"> + <a:documentation>Configures the optional HeartBeating +mechanism for faster failure detection. Keep in mind that engaging this +mechanism in lossy networks could cause faulty loss declaration as +the mechanism relies on the network for heartbeating. + +So as a rule of thumb use this mechanism if you require improved +failure in low to medium utilized networks. + +This constant specifies the number of heartbeat failures the system +should tolerate before declaring heartbeat failure, e.g., 3. +Also if this value is not set or is 0 then the heartbeat mechanism is +not engaged in the system and token rotation is the method of failure +detection. Zero disables the mechanism.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="hold" a:defaultValue="180" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +how long the token should be held by the representative when +the protocol is under low utilization.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="join" a:defaultValue="50"> + <a:documentation>This timeout specifies in milliseconds how +long to wait for join messages in the membership protocol.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="max_messages" a:defaultValue="17"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent by one processor on receipt of the token. +The *max_messages* parameter is limited to 256000 / *netmtu* to prevent +overflow of the kernel transmit buffers.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="max_network_delay" a:defaultValue="50"> + <a:documentation>This constant specifies in milliseconds +the approximate delay that your network takes to transport one packet +from one machine to another. This value is to be set by system engineers +and please don't change it if not sure as this effects the failure +detection mechanism using heartbeat.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="merge" a:defaultValue="200"> + <a:documentation>This timeout specifies in milliseconds how +long to wait before checking for a partition when no multicast traffic +is being sent. If multicast traffic is being sent, the merge detection +happens automatically as a function of the protocol.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="miss_count_const" a:defaultValue="5"> + <a:documentation>This constant defines the maximum number +of times on receipt of a token a message is checked for retransmission +before a retransmission occurs. This parameter is useful to modify for +switches that delay multicast packets compared to unicast packets. +The default setting works well for nearly all modern switches.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="netmtu" a:defaultValue="1500"> + <a:documentation>This specifies the network maximum transmit +unit. To set this value beyond 1500, the regular frame MTU, requires +ethernet devices that support large, or also called jumbo, frames. +If any device in the network doesn't support large frames, the protocol +will not operate properly. The hosts must also have their mtu size set +from 1500 to whatever frame size is specified here. + +Please note while some NICs or switches +claim large frame support, they support 9000 MTU as the maximum frame +size including the IP header. Setting the *netmtu* and host MTUs to 9000 +will cause totem to use the full 9000 bytes of the frame. Then Linux +will add a 18 byte header moving the full frame size to 9018. +As a result some hardware will not operate properly with this size +of data. A *netmtu* of 8982 seems to work for the few large frame devices +that have been tested. Some manufacturers claim large frame support +when in fact they support frame sizes of 4500 bytes. + +When sending multicast traffic, if the network frequently reconfigures, +chances are that some device in the network doesn't support large frames. + +Choose hardware carefully if intending to use large frame support.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="nodeid"> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000"> + <a:documentation>This specifies the time in milliseconds +to check if the failed ring can be auto-recovered.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check: active,passive -> count(interface) <= 2 + CFG: corosync.conf, cluster.conf + --> + <attribute name="rrp_mode"> + <a:documentation>This specifies the mode of redundant ring, +which may be 'none', 'active', or 'passive'. Active replication offers +none`, active, or passive. Active replication offers +slightly lower latency from transmit to delivery in faulty network +environments but with less performance. Passive replication may nearly +double the speed of the totem protocol if the protocol doesn't become +CPU bound. The final option is none, in which case only one network +interface will be used to operate the totem protocol. + +If only one *interface* directive is specified, 'none' is automatically +chosen. If multiple *interface* directives are specified, only 'active' +or 'passive' may be chosen. + +The maximum number of *interface* directives that is allowed for either +mode ('active' or 'passive') is 2.</a:documentation> + <choice> + <value>active</value> + <value>none</value> + <value>passive</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_problem_count_mcast_threshold"> + <a:documentation>This specifies the number of times +a problem is detected with multicast before setting the link faulty for +passive RRP mode. This variable is unused in active RRP mode. + +The default is 10 times *rrp_problem_count_threshold*.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check + CFG: corosync.conf, cluster.conf + --> + <attribute name="rrp_problem_count_threshold" a:defaultValue="10"> + <a:documentation>This specifies the number of times +a problem is detected with a link before setting the link faulty. +Once a link is set faulty, no more data is transmitted upon it. Also, +the problem counter is no longer decremented when the problem count +timeout expires. + +A problem is detected whenever all tokens from the proceeding +processor have not been received within the *rrp_token_expired_timeout*. +The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be +at least 50 milliseconds less then the *token* timeout, or a complete +reconfiguration may occur.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_problem_count_timeout" a:defaultValue="2000"> + <a:documentation>This specifies the time in milliseconds +to wait before decrementing the problem count by 1 for a particular ring +to ensure a link is not marked faulty for transient network failures.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="rrp_token_expired_timeout" a:defaultValue="47" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This specifies the time in milliseconds +to increment the problem counter for the redundant ring protocol after +not having received a token from all rings for a particular processor. + +This value will automatically be calculated from the *token* timeout +and *problem_count_threshold* but may be overridden.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- + XXX: implied check/migration to current items + CFG: corosync.conf, cluster.conf + --> + <attribute name="secauth" a:defaultValue="on" a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*."> + <a:documentation>This specifies that HMAC/SHA1 authentication should be used +to authenticate all messages. It further specifies that all data +should be encrypted with the nss library and aes256 encryption +algorithm to protect data from eavesdropping. + +Enabling this option adds a encryption header to every message sent +by totem which reduces total throughput. Also encryption and +authentication consume extra CPU cycles in corosync.</a:documentation> + <choice> + <value>off</value> + <value>on</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="send_join" a:defaultValue="0" a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations."> + <a:documentation>This timeout specifies in milliseconds +an upper range between 0 and *send_join* to wait before sending a join +message. For eprecationtions with less then 32 nodes, this parameter +is not necessary. For larger rings, this parameter is necessary +to ensure the NIC is not overflowed with join messages on formation of +a new ring. A reasonable value for large rings (128 nodes) would be +80msec. Other timer values must also change if this value is changed.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="seqno_unchanged_const" a:defaultValue="30"> + <a:documentation>This constant specifies how many rotations +of the token without any multicast traffic should occur before the hold +timer is started.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: undocumented --> + <attribute name="threads"> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="token" a:defaultValue="1000"> + <a:documentation>This timeout specifies in milliseconds +until a token loss is declared after not receiving a token. This is +the time spent detecting a failure of a processor in the current +configuration. Reforming a new configuration takes about 50 +milliseconds in addition to this timeout.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="token_retransmit" a:defaultValue="238" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community."> + <a:documentation>This timeout specifies in milliseconds +after how long before receiving a token the token is retransmitted. +This will be automatically calculated if token is modified.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="token_retransmits_before_loss_const" a:defaultValue="4"> + <a:documentation>This value identifies how many token +retransmits should be attempted before forming a new configuration. +If this value is set, retransmit and hold will be automatically +calculated from *retransmits_before_loss* and token.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="transport" a:defaultValue="udp"> + <choice> + <value>iba</value> + <value>udp</value> + <value>udpu</value> + </choice> + </attribute> + </optional> + <attribute name="version"> + <a:documentation>This option controls the transport +mechanism used. If the interface to which corosync is binding is +an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter +may be specified. To avoid the use of multicast entirely, a unicast +transport parameter 'udpu' can be specified. This requires specifying +the list of members in *nodelist* directive, that could potentially make +up the membership before deployment.</a:documentation> + <!-- CFG: corosync.conf --> + <a:documentation>This specifies the version of +the configuration file. Currently the only valid value for this +option is '2'.</a:documentation> + <data type="unsignedInt"/> + </attribute> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="vsftype" a:defaultValue="ykd"> + <a:documentation>This option controls the virtual +synchrony filter type used to identify a primary component. +The preferred choice is YKD dynamic linear voting, however, for +clusters larger then 32 nodes YKD consumes alot of memory. For large +scale clusters that are created by changing the MAX_PROCESSORS_COUNT +#define in the C code totem.h file, the virtual synchrony filter 'none' +is recommended but then AMF and DLCK services (which are currently +experimental) are not safe for use.</a:documentation> + <choice> + <value>none</value> + <value>ykd</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="window_size" a:defaultValue="50"> + <a:documentation>This constant specifies the maximum number +of messages that may be sent on one token rotation. If all processors +perform equally well, this value could be large (300), which would +introduce higher latency from origination to delivery for very large +rings. To reduce latency in large rings (16+), the defaults are a safe +compromise. If 1 or more slow processor(s) are present among fast +processors, *window_size* should be no larger then 256000 / *netmtu* +to avoid overflow of the kernel receive buffers. The user is notified +of this by the display of a retransmit list in the notification logs. +There is no loss of data, but performance is reduced when these errors +occur.</a:documentation> + <data type="unsignedInt"/> + </attribute> + </optional> + <zeroOrMore> + <ref name="interface"/> + </zeroOrMore> + </element> + </define> + <define name="interface"> + <element name="interface"> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="bindnetaddr"> + <a:documentation>This specifies the network address +the corosync executive should bind to. +*bindnetaddr* should be an IP address configured on the system, or +a network address. + +For example, if the local interface is `192.168.5.92` with netmask +`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or +`192.168.5.0`. If the local interface is `192.168.5.92` with netmask +`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`, +and so forth. + +This may also be an IPv6 address, in which case IPv6 networking will be +used. In this case, the exact address must be specified and there is no +automatic selection of the network interface within a specific subnet +as with IPv4. + +If IPv6 networking is used, the *nodeid* field in *nodelist* must be +specified.</a:documentation> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf --> + <attribute name="broadcast" a:defaultValue="no"> + <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes', +the broadcast address will be used for communication. If this option +is set, *mcastaddr* should not be set.</a:documentation> + <choice> + <value>no</value> + <value>yes</value> + </choice> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="mcastaddr"> + <a:documentation>This is the multicast address used +by corosync executive. The default should work for most networks, but +the network administrator should be queried about a multicast address +to use. Avoid `224.x.x.x` because this is a "config" multicast address. + +This may also be an IPv6 multicast address, in which case IPv6 networking +will be used. If IPv6 networking is used, the *nodeid* field in +*nodelist* must be specified. + +It's not needed to use this option if *cluster_name* option is used. +If both options are used, *mcastaddr* has higher priority.</a:documentation> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="mcastport"> + <a:documentation>This specifies the UDP port number. +It is possible to use the same multicast address on a network with +the corosync services configured for different UDP ports. Please note +corosync uses two UDP ports *mcastport* (for mcast receives) and +*mcastport* - 1 (for mcast sends). If you have multiple clusters +on the same network using the same *mcastaddr* please configure +the **mcastport**s with a gap.</a:documentation> + <data type="unsignedShort"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ringnumber"> + <a:documentation>This specifies the ring number for +the interface. When using the redundant ring protocol, each interface +should specify separate ring numbers to uniquely identify to +the membership protocol which interface to use for which redundant ring. +The *ringnumber* must start at 0.</a:documentation> + <data type="unsignedByte"/> + </attribute> + </optional> + <optional> + <!-- CFG: corosync.conf, cluster.conf --> + <attribute name="ttl" a:defaultValue="1"> + <a:documentation>This specifies the Time To Live (TTL). +If you run your cluster on a routed network then the default of '1' will +be too small. This option provides a way to increase this up to '255'. +The valid range is '0..255'. Note that this is only valid on multicast +transport types.</a:documentation> + <data type="unsignedByte"/> + </attribute> + </optional> + </element> + </define> +</grammar> +<!-- + uidgid = + element uidgid { + attribute uid {text}?, + attribute gid {text}? + } +--> @@ -1,289 +1,313 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # ply: lex.py # -# Author: David M. Beazley (beazley@cs.uchicago.edu) -# Department of Computer Science -# University of Chicago -# Chicago, IL 60637 -# -# Copyright (C) 2001, David M. Beazley -# -# $Header: /cvsroot/ply/ply/lex.py,v 1.7 2002/12/12 21:16:33 beazley Exp $ -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# See the file COPYING for a complete copy of the LGPL. +# Copyright (C) 2001-2011, +# David M. Beazley (Dabeaz LLC) +# All rights reserved. # +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: # -# This module automatically constructs a lexical analysis module from regular -# expression rules defined in a user-defined module. The idea is essentially the same -# as that used in John Aycock's Spark framework, but the implementation works -# at the module level rather than requiring the use of classes. +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. # -# This module tries to provide an interface that is closely modeled after -# the traditional lex interface in Unix. It also differs from Spark -# in that: -# -# - It provides more extensive error checking and reporting if -# the user supplies a set of regular expressions that can't -# be compiled or if there is any other kind of a problem in -# the specification. -# -# - The interface is geared towards LALR(1) and LR(1) parser -# generators. That is tokens are generated one at a time -# rather than being generated in advanced all in one step. -# -# There are a few limitations of this module -# -# - The module interface makes it somewhat awkward to support more -# than one lexer at a time. Although somewhat inelegant from a -# design perspective, this is rarely a practical concern for -# most compiler projects. -# -# - The lexer requires that the entire input text be read into -# a string before scanning. I suppose that most machines have -# enough memory to make this a minor issues, but it makes -# the lexer somewhat difficult to use in interactive sessions -# or with streaming data. -# -#----------------------------------------------------------------------------- - -r""" -lex.py - -This module builds lex-like scanners based on regular expression rules. -To use the module, simply write a collection of regular expression rules -and actions like this: - -# lexer.py -import lex - -# Define a list of valid tokens -tokens = ( - 'IDENTIFIER', 'NUMBER', 'PLUS', 'MINUS' - ) - -# Define tokens as functions -def t_IDENTIFIER(t): - r' ([a-zA-Z_](\w|_)* ' - return t - -def t_NUMBER(t): - r' \d+ ' - return t - -# Some simple tokens with no actions -t_PLUS = r'\+' -t_MINUS = r'-' - -# Initialize the lexer -lex.lex() - -The tokens list is required and contains a complete list of all valid -token types that the lexer is allowed to produce. Token types are -restricted to be valid identifiers. This means that 'MINUS' is a valid -token type whereas '-' is not. - -Rules are defined by writing a function with a name of the form -t_rulename. Each rule must accept a single argument which is -a token object generated by the lexer. This token has the following -attributes: - - t.type = type string of the token. This is initially set to the - name of the rule without the leading t_ - t.value = The value of the lexeme. - t.lineno = The value of the line number where the token was encountered - -For example, the t_NUMBER() rule above might be called with the following: - - t.type = 'NUMBER' - t.value = '42' - t.lineno = 3 - -Each rule returns the token object it would like to supply to the -parser. In most cases, the token t is returned with few, if any -modifications. To discard a token for things like whitespace or -comments, simply return nothing. For instance: - -def t_whitespace(t): - r' \s+ ' - pass - -For faster lexing, you can also define this in terms of the ignore set like this: - -t_ignore = ' \t' - -The characters in this string are ignored by the lexer. Use of this feature can speed -up parsing significantly since scanning will immediately proceed to the next token. - -lex requires that the token returned by each rule has an attribute -t.type. Other than this, rules are free to return any kind of token -object that they wish and may construct a new type of token object -from the attributes of t (provided the new object has the required -type attribute). - -If illegal characters are encountered, the scanner executes the -function t_error(t) where t is a token representing the rest of the -string that hasn't been matched. If this function isn't defined, a -LexError exception is raised. The .text attribute of this exception -object contains the part of the string that wasn't matched. - -The t.skip(n) method can be used to skip ahead n characters in the -input stream. This is usually only used in the error handling rule. -For instance, the following rule would print an error message and -continue: - -def t_error(t): - print "Illegal character in input %s" % t.value[0] - t.skip(1) - -Of course, a nice scanner might wish to skip more than one character -if the input looks very corrupted. - -The lex module defines a t.lineno attribute on each token that can be used -to track the current line number in the input. The value of this -variable is not modified by lex so it is up to your lexer module -to correctly update its value depending on the lexical properties -of the input language. To do this, you might write rules such as -the following: - -def t_newline(t): - r' \n+ ' - t.lineno += t.value.count("\n") - -To initialize your lexer so that it can be used, simply call the lex.lex() -function in your rule file. If there are any errors in your -specification, warning messages or an exception will be generated to -alert you to the problem. - -(dave: this needs to be rewritten) -To use the newly constructed lexer from another module, simply do -this: - - import lex - import lexer - plex.input("position = initial + rate*60") +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- - while 1: - token = plex.token() # Get a token - if not token: break # No more tokens - ... do whatever ... +__version__ = "3.5" +__tabversion__ = "3.5" # Version of table file used -Assuming that the module 'lexer' has initialized plex as shown -above, parsing modules can safely import 'plex' without having -to import the rule file or any additional imformation about the -scanner you have defined. -""" +import re, sys, types, copy, os -# ----------------------------------------------------------------------------- +# This tuple contains known string types +try: + # Python 2.6 + StringTypes = (types.StringType, types.UnicodeType) +except AttributeError: + # Python 3.0 + StringTypes = (str, bytes) +# Extract the code attribute of a function. Different implementations +# are for Python 2/3 compatibility. -__version__ = "1.3" +if sys.version_info[0] < 3: + def func_code(f): + return f.func_code +else: + def func_code(f): + return f.__code__ -import re, types, sys, copy +# This regular expression is used to match valid token names +_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') + +# Exception thrown when invalid token encountered and no default error +# handler is defined. -# Exception thrown when invalid token encountered and no default class LexError(Exception): def __init__(self,message,s): self.args = (message,) self.text = s -# Token class -class LexToken: +# Token class. This class is used to represent the tokens produced. +class LexToken(object): def __str__(self): - return "LexToken(%s,%r,%d)" % (self.type,self.value,self.lineno) + return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) def __repr__(self): return str(self) - def skip(self,n): - try: - self._skipn += n - except AttributeError: - self._skipn = n + +# This object is a stand-in for a logging object created by the +# logging module. + +class PlyLogger(object): + def __init__(self,f): + self.f = f + def critical(self,msg,*args,**kwargs): + self.f.write((msg % args) + "\n") + + def warning(self,msg,*args,**kwargs): + self.f.write("WARNING: "+ (msg % args) + "\n") + + def error(self,msg,*args,**kwargs): + self.f.write("ERROR: " + (msg % args) + "\n") + + info = critical + debug = critical + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self,name): + return self + def __call__(self,*args,**kwargs): + return self # ----------------------------------------------------------------------------- -# Lexer class +# === Lexing Engine === +# +# The following Lexer class implements the lexer runtime. There are only +# a few public methods and attributes: # # input() - Store a new string in the lexer # token() - Get the next token +# clone() - Clone the lexer +# +# lineno - Current line number +# lexpos - Current position in the input string # ----------------------------------------------------------------------------- class Lexer: def __init__(self): - self.lexre = None # Master regular expression - self.lexdata = None # Actual input data (as a string) - self.lexpos = 0 # Current position in input text - self.lexlen = 0 # Length of the input text - self.lexindexfunc = [ ] # Reverse mapping of groups to functions and types - self.lexerrorf = None # Error rule (if any) - self.lextokens = None # List of valid tokens - self.lexignore = None # Ignored characters - self.lineno = 1 # Current line number - self.debug = 0 # Debugging mode - self.optimize = 0 # Optimized mode - self.token = self.errtoken - - def __copy__(self): - c = Lexer() - c.lexre = self.lexre - c.lexdata = self.lexdata - c.lexpos = self.lexpos - c.lexlen = self.lexlen - c.lenindexfunc = self.lexindexfunc - c.lexerrorf = self.lexerrorf - c.lextokens = self.lextokens - c.lexignore = self.lexignore - c.lineno = self.lineno - c.optimize = self.optimize - c.token = c.realtoken + self.lexre = None # Master regular expression. This is a list of + # tuples (re,findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules + self.lexretext = None # Current regular expression strings + self.lexstatere = {} # Dictionary mapping lexer states to master regexs + self.lexstateretext = {} # Dictionary mapping lexer states to regex strings + self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names + self.lexstate = "INITIAL" # Current lexer state + self.lexstatestack = [] # Stack of lexer states + self.lexstateinfo = None # State information + self.lexstateignore = {} # Dictionary of ignored characters for each state + self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexreflags = 0 # Optional re compile flags + self.lexdata = None # Actual input data (as a string) + self.lexpos = 0 # Current position in input text + self.lexlen = 0 # Length of the input text + self.lexerrorf = None # Error rule (if any) + self.lextokens = None # List of valid tokens + self.lexignore = "" # Ignored characters + self.lexliterals = "" # Literal characters that can be passed through + self.lexmodule = None # Module + self.lineno = 1 # Current line number + self.lexoptimize = 0 # Optimized mode + + def clone(self,object=None): + c = copy.copy(self) + + # If the object parameter has been supplied, it means we are attaching the + # lexer to a new object. In this case, we have to rebind all methods in + # the lexstatere and lexstateerrorf tables. + + if object: + newtab = { } + for key, ritem in self.lexstatere.items(): + newre = [] + for cre, findex in ritem: + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object,f[0].__name__),f[1])) + newre.append((cre,newfindex)) + newtab[key] = newre + c.lexstatere = newtab + c.lexstateerrorf = { } + for key, ef in self.lexstateerrorf.items(): + c.lexstateerrorf[key] = getattr(object,ef.__name__) + c.lexmodule = object + return c + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self,tabfile,outputdir=""): + if isinstance(tabfile,types.ModuleType): + return + basetabfilename = tabfile.split(".")[-1] + filename = os.path.join(outputdir,basetabfilename)+".py" + tf = open(filename,"w") + tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) + tf.write("_tabversion = %s\n" % repr(__tabversion__)) + tf.write("_lextokens = %s\n" % repr(self.lextokens)) + tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) + tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) + tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) + + tabre = { } + # Collect all functions in the initial state + initial = self.lexstatere["INITIAL"] + initialfuncs = [] + for part in initial: + for f in part[1]: + if f and f[0]: + initialfuncs.append(f) + + for key, lre in self.lexstatere.items(): + titem = [] + for i in range(len(lre)): + titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i]))) + tabre[key] = titem + + tf.write("_lexstatere = %s\n" % repr(tabre)) + tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) + + taberr = { } + for key, ef in self.lexstateerrorf.items(): + if ef: + taberr[key] = ef.__name__ + else: + taberr[key] = None + tf.write("_lexstateerrorf = %s\n" % repr(taberr)) + tf.close() + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self,tabfile,fdict): + if isinstance(tabfile,types.ModuleType): + lextab = tabfile + else: + if sys.version_info[0] < 3: + exec("import %s as lextab" % tabfile) + else: + env = { } + exec("import %s as lextab" % tabfile, env,env) + lextab = env['lextab'] + + if getattr(lextab,"_tabversion","0.0") != __tabversion__: + raise ImportError("Inconsistent PLY version") + + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = { } + self.lexstateretext = { } + for key,lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for i in range(len(lre)): + titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict))) + txtitem.append(lre[i][0]) + self.lexstatere[key] = titem + self.lexstateretext[key] = txtitem + self.lexstateerrorf = { } + for key,ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[key] = fdict[ef] + self.begin('INITIAL') # ------------------------------------------------------------ # input() - Push a new string into the lexer # ------------------------------------------------------------ def input(self,s): - if not isinstance(s,types.StringType): - raise ValueError, "Expected a string" + # Pull off the first character to see if s looks like a string + c = s[:1] + if not isinstance(c,StringTypes): + raise ValueError("Expected a string") self.lexdata = s self.lexpos = 0 self.lexlen = len(s) - self.token = self.realtoken - - # Change the token routine to point to realtoken() - global token - if token == self.errtoken: - token = self.token # ------------------------------------------------------------ - # errtoken() - Return error if token is called with no data + # begin() - Changes the lexing state + # ------------------------------------------------------------ + def begin(self,state): + if not state in self.lexstatere: + raise ValueError("Undefined state") + self.lexre = self.lexstatere[state] + self.lexretext = self.lexstateretext[state] + self.lexignore = self.lexstateignore.get(state,"") + self.lexerrorf = self.lexstateerrorf.get(state,None) + self.lexstate = state + # ------------------------------------------------------------ - def errtoken(self): - raise RuntimeError, "No input string given with input()" - + # push_state() - Changes the lexing state and saves old on stack # ------------------------------------------------------------ - # token() - Return the next token from the Lexer + def push_state(self,state): + self.lexstatestack.append(self.lexstate) + self.begin(state) + + # ------------------------------------------------------------ + # pop_state() - Restores the previous state + # ------------------------------------------------------------ + def pop_state(self): + self.begin(self.lexstatestack.pop()) + + # ------------------------------------------------------------ + # current_state() - Returns the current lexing state + # ------------------------------------------------------------ + def current_state(self): + return self.lexstate + + # ------------------------------------------------------------ + # skip() - Skip ahead n characters + # ------------------------------------------------------------ + def skip(self,n): + self.lexpos += n + + # ------------------------------------------------------------ + # opttoken() - Return the next token from the Lexer # # Note: This function has been carefully implemented to be as fast # as possible. Don't make changes unless you really know what # you are doing # ------------------------------------------------------------ - def realtoken(self): + def token(self): # Make local copies of frequently referenced attributes lexpos = self.lexpos lexlen = self.lexlen lexignore = self.lexignore lexdata = self.lexdata - + while lexpos < lexlen: # This code provides some short-circuit code for whitespace, tabs, and other ignored characters if lexdata[lexpos] in lexignore: @@ -291,361 +315,709 @@ class Lexer: continue # Look for a regular expression match - m = self.lexre.match(lexdata,lexpos) - if m: - i = m.lastindex - lexpos = m.end() + for lexre,lexindexfunc in self.lexre: + m = lexre.match(lexdata,lexpos) + if not m: continue + + # Create a token for return tok = LexToken() tok.value = m.group() tok.lineno = self.lineno - tok.lexer = self - func,tok.type = self.lexindexfunc[i] + tok.lexpos = lexpos + + i = m.lastindex + func,tok.type = lexindexfunc[i] + if not func: - self.lexpos = lexpos - return tok - + # If no token type was set, it's an ignored token + if tok.type: + self.lexpos = m.end() + return tok + else: + lexpos = m.end() + break + + lexpos = m.end() + # If token is processed by a function, call it + + tok.lexer = self # Set additional attributes useful in token rules + self.lexmatch = m self.lexpos = lexpos + newtok = func(tok) - self.lineno = tok.lineno # Update line number - + # Every function must return a token, if nothing, we just move to next token - if not newtok: continue - + if not newtok: + lexpos = self.lexpos # This is here in case user has updated lexpos. + lexignore = self.lexignore # This is here in case there was a state change + break + # Verify type of the token. If not in the token map, raise an error - if not self.optimize: - if not self.lextokens.has_key(newtok.type): - raise LexError, ("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( - func.func_code.co_filename, func.func_code.co_firstlineno, + if not self.lexoptimize: + if not newtok.type in self.lextokens: + raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func_code(func).co_filename, func_code(func).co_firstlineno, func.__name__, newtok.type),lexdata[lexpos:]) return newtok + else: + # No match, see if in literals + if lexdata[lexpos] in self.lexliterals: + tok = LexToken() + tok.value = lexdata[lexpos] + tok.lineno = self.lineno + tok.type = tok.value + tok.lexpos = lexpos + self.lexpos = lexpos + 1 + return tok - # No match. Call t_error() if defined. - if self.lexerrorf: - tok = LexToken() - tok.value = self.lexdata[lexpos:] - tok.lineno = self.lineno - tok.type = "error" - tok.lexer = self - oldpos = lexpos - newtok = self.lexerrorf(tok) - lexpos += getattr(tok,"_skipn",0) - if oldpos == lexpos: - # Error method didn't change text position at all. This is an error. + # No match. Call t_error() if defined. + if self.lexerrorf: + tok = LexToken() + tok.value = self.lexdata[lexpos:] + tok.lineno = self.lineno + tok.type = "error" + tok.lexer = self + tok.lexpos = lexpos self.lexpos = lexpos - raise LexError, ("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) - if not newtok: continue - self.lexpos = lexpos - return newtok + newtok = self.lexerrorf(tok) + if lexpos == self.lexpos: + # Error method didn't change text position at all. This is an error. + raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: continue + return newtok - self.lexpos = lexpos - raise LexError, ("No match found", lexdata[lexpos:]) + self.lexpos = lexpos + raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) - # No more input data self.lexpos = lexpos + 1 + if self.lexdata is None: + raise RuntimeError("No input string given with input()") return None - + # Iterator interface + def __iter__(self): + return self + + def next(self): + t = self.token() + if t is None: + raise StopIteration + return t + + __next__ = next + +# ----------------------------------------------------------------------------- +# ==== Lex Builder === +# +# The functions and classes below are used to collect lexing information +# and build a Lexer object from it. +# ----------------------------------------------------------------------------- + # ----------------------------------------------------------------------------- -# validate_file() +# _get_regex(func) # -# This checks to see if there are duplicated t_rulename() functions or strings -# in the parser input file. This is done using a simple regular expression -# match on each line in the filename. +# Returns the regular expression assigned to a function either as a doc string +# or as a .regex attribute attached by the @TOKEN decorator. # ----------------------------------------------------------------------------- -def validate_file(filename): - import os.path - base,ext = os.path.splitext(filename) - if ext != '.py': return 1 # No idea what the file is. Return OK +def _get_regex(func): + return getattr(func,"regex",func.__doc__) +# ----------------------------------------------------------------------------- +# get_caller_module_dict() +# +# This function returns a dictionary containing all of the symbols defined within +# a caller further down the call stack. This is used to get the environment +# associated with the yacc() call if none was provided. +# ----------------------------------------------------------------------------- + +def get_caller_module_dict(levels): try: - f = open(filename) - lines = f.readlines() - f.close() - except IOError: - return 1 # Oh well - - fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') - sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') - counthash = { } - linen = 1 - noerror = 1 - for l in lines: - m = fre.match(l) - if not m: - m = sre.match(l) - if m: - name = m.group(1) - prev = counthash.get(name) - if not prev: - counthash[name] = linen - else: - print "%s:%d: Rule %s redefined. Previously defined on line %d" % (filename,linen,name,prev) - noerror = 0 - linen += 1 - return noerror + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + while levels > 0: + f = f.f_back + levels -= 1 + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + + return ldict + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- + +def _funcs_to_names(funclist,namelist): + result = [] + for f,name in zip(funclist,namelist): + if f and f[0]: + result.append((name, f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- + +def _names_to_funcs(namelist,fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]],n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _form_master_re() +# +# This function takes a list of all of the regex components and attempts to +# form the master regular expression. Given limitations in the Python re +# module, it may be necessary to break the master regex into separate expressions. +# ----------------------------------------------------------------------------- + +def _form_master_re(relist,reflags,ldict,toknames): + if not relist: return [] + regex = "|".join(relist) + try: + lexre = re.compile(regex,re.VERBOSE | reflags) + + # Build the index to function map for the matching engine + lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) + lexindexnames = lexindexfunc[:] + + for f,i in lexre.groupindex.items(): + handle = ldict.get(f,None) + if type(handle) in (types.FunctionType, types.MethodType): + lexindexfunc[i] = (handle,toknames[f]) + lexindexnames[i] = f + elif handle is not None: + lexindexnames[i] = f + if f.find("ignore_") > 0: + lexindexfunc[i] = (None,None) + else: + lexindexfunc[i] = (None, toknames[f]) + + return [(lexre,lexindexfunc)],[regex],[lexindexnames] + except Exception: + m = int(len(relist)/2) + if m == 0: m = 1 + llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames) + rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames) + return llist+rlist, lre+rre, lnames+rnames + +# ----------------------------------------------------------------------------- +# def _statetoken(s,names) +# +# Given a declaration name s of the form "t_" and a dictionary whose keys are +# state names, this function returns a tuple (states,tokenname) where states +# is a tuple of state names and tokenname is the name of the token. For example, +# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') +# ----------------------------------------------------------------------------- + +def _statetoken(s,names): + nonstate = 1 + parts = s.split("_") + for i in range(1,len(parts)): + if not parts[i] in names and parts[i] != 'ANY': break + if i > 1: + states = tuple(parts[1:i]) + else: + states = ('INITIAL',) + + if 'ANY' in states: + states = tuple(names) + + tokenname = "_".join(parts[i:]) + return (states,tokenname) + # ----------------------------------------------------------------------------- -# _read_lextab(module) +# LexerReflect() # -# Reads lexer table from a lextab file instead of using introspection. +# This class represents information needed to build a lexer as extracted from a +# user's input file. # ----------------------------------------------------------------------------- +class LexerReflect(object): + def __init__(self,ldict,log=None,reflags=0): + self.ldict = ldict + self.error_func = None + self.tokens = [] + self.reflags = reflags + self.stateinfo = { 'INITIAL' : 'inclusive'} + self.files = {} + self.error = 0 + + if log is None: + self.log = PlyLogger(sys.stderr) + else: + self.log = log + + # Get all of the basic information + def get_all(self): + self.get_tokens() + self.get_literals() + self.get_states() + self.get_rules() + + # Validate all of the information + def validate_all(self): + self.validate_tokens() + self.validate_literals() + self.validate_rules() + return self.error -def _read_lextab(lexer, fdict, module): - exec "import %s as lextab" % module - lexer.lexre = re.compile(lextab._lexre, re.VERBOSE) - lexer.lexindexfunc = lextab._lextab - for i in range(len(lextab._lextab)): - t = lexer.lexindexfunc[i] - if t: - if t[0]: - lexer.lexindexfunc[i] = (fdict[t[0]],t[1]) - lexer.lextokens = lextab._lextokens - lexer.lexignore = lextab._lexignore - if lextab._lexerrorf: - lexer.lexerrorf = fdict[lextab._lexerrorf] + # Get the tokens map + def get_tokens(self): + tokens = self.ldict.get("tokens",None) + if not tokens: + self.log.error("No token list is defined") + self.error = 1 + return + + if not isinstance(tokens,(list, tuple)): + self.log.error("tokens must be a list or tuple") + self.error = 1 + return + if not tokens: + self.log.error("tokens is empty") + self.error = 1 + return + + self.tokens = tokens + + # Validate the tokens + def validate_tokens(self): + terminals = {} + for n in self.tokens: + if not _is_identifier.match(n): + self.log.error("Bad token name '%s'",n) + self.error = 1 + if n in terminals: + self.log.warning("Token '%s' multiply defined", n) + terminals[n] = 1 + + # Get the literals specifier + def get_literals(self): + self.literals = self.ldict.get("literals","") + if not self.literals: + self.literals = "" + + # Validate literals + def validate_literals(self): + try: + for c in self.literals: + if not isinstance(c,StringTypes) or len(c) > 1: + self.log.error("Invalid literal %s. Must be a single character", repr(c)) + self.error = 1 + + except TypeError: + self.log.error("Invalid literals specification. literals must be a sequence of characters") + self.error = 1 + + def get_states(self): + self.states = self.ldict.get("states",None) + # Build statemap + if self.states: + if not isinstance(self.states,(tuple,list)): + self.log.error("states must be defined as a tuple or list") + self.error = 1 + else: + for s in self.states: + if not isinstance(s,tuple) or len(s) != 2: + self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s)) + self.error = 1 + continue + name, statetype = s + if not isinstance(name,StringTypes): + self.log.error("State name %s must be a string", repr(name)) + self.error = 1 + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name) + self.error = 1 + continue + if name in self.stateinfo: + self.log.error("State '%s' already defined",name) + self.error = 1 + continue + self.stateinfo[name] = statetype + + # Get all of the symbols with a t_ prefix and sort them into various + # categories (functions, strings, error functions, and ignore characters) + + def get_rules(self): + tsymbols = [f for f in self.ldict if f[:2] == 't_' ] + + # Now build up a list of functions and a list of strings + + self.toknames = { } # Mapping of symbols to token names + self.funcsym = { } # Symbols defined as functions + self.strsym = { } # Symbols defined as strings + self.ignore = { } # Ignore strings by state + self.errorf = { } # Error functions by state + + for s in self.stateinfo: + self.funcsym[s] = [] + self.strsym[s] = [] + + if len(tsymbols) == 0: + self.log.error("No rules of the form t_rulename are defined") + self.error = 1 + return + + for f in tsymbols: + t = self.ldict[f] + states, tokname = _statetoken(f,self.stateinfo) + self.toknames[f] = tokname + + if hasattr(t,"__call__"): + if tokname == 'error': + for s in states: + self.errorf[s] = t + elif tokname == 'ignore': + line = func_code(t).co_firstlineno + file = func_code(t).co_filename + self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__) + self.error = 1 + else: + for s in states: + self.funcsym[s].append((f,t)) + elif isinstance(t, StringTypes): + if tokname == 'ignore': + for s in states: + self.ignore[s] = t + if "\\" in t: + self.log.warning("%s contains a literal backslash '\\'",f) + + elif tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", f) + self.error = 1 + else: + for s in states: + self.strsym[s].append((f,t)) + else: + self.log.error("%s not defined as a function or string", f) + self.error = 1 + + # Sort the functions by line number + for f in self.funcsym.values(): + if sys.version_info[0] < 3: + f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno)) + else: + # Python 3.0 + f.sort(key=lambda x: func_code(x[1]).co_firstlineno) + + # Sort the strings by regular expression length + for s in self.strsym.values(): + if sys.version_info[0] < 3: + s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) + else: + # Python 3.0 + s.sort(key=lambda x: len(x[1]),reverse=True) + + # Validate all of the t_rules collected + def validate_rules(self): + for state in self.stateinfo: + # Validate all rules defined by functions + + + + for fname, f in self.funcsym[state]: + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + self.files[file] = 1 + + tokname = self.toknames[fname] + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = func_code(f).co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) + self.error = 1 + continue + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) + self.error = 1 + continue + + if not _get_regex(f): + self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__) + self.error = 1 + continue + + try: + c = re.compile("(?P<%s>%s)" % (fname, _get_regex(f)), re.VERBOSE | self.reflags) + if c.match(""): + self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__) + self.error = 1 + except re.error: + _etype, e, _etrace = sys.exc_info() + self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e) + if '#' in _get_regex(f): + self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__) + self.error = 1 + + # Validate all rules defined by strings + for name,r in self.strsym[state]: + tokname = self.toknames[name] + if tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", name) + self.error = 1 + continue + + if not tokname in self.tokens and tokname.find("ignore_") < 0: + self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname) + self.error = 1 + continue + + try: + c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags) + if (c.match("")): + self.log.error("Regular expression for rule '%s' matches empty string",name) + self.error = 1 + except re.error: + _etype, e, _etrace = sys.exc_info() + self.log.error("Invalid regular expression for rule '%s'. %s",name,e) + if '#' in r: + self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name) + self.error = 1 + + if not self.funcsym[state] and not self.strsym[state]: + self.log.error("No rules defined for state '%s'",state) + self.error = 1 + + # Validate the error function + efunc = self.errorf.get(state,None) + if efunc: + f = efunc + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + self.files[file] = 1 + + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = func_code(f).co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) + self.error = 1 + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) + self.error = 1 + + for f in self.files: + self.validate_file(f) + + + # ----------------------------------------------------------------------------- + # validate_file() + # + # This checks to see if there are duplicated t_rulename() functions or strings + # in the parser input file. This is done using a simple regular expression + # match on each line in the given file. + # ----------------------------------------------------------------------------- + + def validate_file(self,filename): + import os.path + base,ext = os.path.splitext(filename) + if ext != '.py': return # No idea what the file is. Return OK + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return # Couldn't find the file. Don't worry about it + + fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') + sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') + + counthash = { } + linen = 1 + for l in lines: + m = fre.match(l) + if not m: + m = sre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev) + self.error = 1 + linen += 1 + # ----------------------------------------------------------------------------- # lex(module) # # Build all of the regular expression rules from definitions in the supplied module # ----------------------------------------------------------------------------- -def lex(module=None,debug=0,optimize=0,lextab="lextab"): +def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None): + global lexer ldict = None - regex = "" - error = 0 - files = { } - lexer = Lexer() - lexer.debug = debug - lexer.optimize = optimize + stateinfo = { 'INITIAL' : 'inclusive'} + lexobj = Lexer() + lexobj.lexoptimize = optimize global token,input - + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + if debug: + if debuglog is None: + debuglog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the lexer + if object: module = object + if module: - if not isinstance(module, types.ModuleType): - raise ValueError,"Expected a module" - - ldict = module.__dict__ - + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = dict(_items) else: - # No module given. We might be able to get information from the caller. - try: - raise RuntimeError - except RuntimeError: - e,b,t = sys.exc_info() - f = t.tb_frame - f = f.f_back # Walk out to our calling function - ldict = f.f_globals # Grab its globals dictionary + ldict = get_caller_module_dict(2) + + # Collect parser information from the dictionary + linfo = LexerReflect(ldict,log=errorlog,reflags=reflags) + linfo.get_all() + if not optimize: + if linfo.validate_all(): + raise SyntaxError("Can't build lexer") if optimize and lextab: try: - _read_lextab(lexer,ldict, lextab) - if not lexer.lexignore: lexer.lexignore = "" - token = lexer.token - input = lexer.input - return lexer - + lexobj.readtab(lextab,ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + except ImportError: pass - - # Get the tokens map - tokens = ldict.get("tokens",None) - if not tokens: - raise SyntaxError,"lex: module does not define 'tokens'" - if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)): - raise SyntaxError,"lex: tokens must be a list or tuple." + + # Dump some basic debugging information + if debug: + debuglog.info("lex: tokens = %r", linfo.tokens) + debuglog.info("lex: literals = %r", linfo.literals) + debuglog.info("lex: states = %r", linfo.stateinfo) # Build a dictionary of valid token names - lexer.lextokens = { } - if not optimize: + lexobj.lextokens = { } + for n in linfo.tokens: + lexobj.lextokens[n] = 1 - # Utility function for verifying tokens - def is_identifier(s): - for c in s: - if not (c.isalnum() or c == '_'): return 0 - return 1 - - for n in tokens: - if not is_identifier(n): - print "lex: Bad token name '%s'" % n - error = 1 - if lexer.lextokens.has_key(n): - print "lex: Warning. Token '%s' multiply defined." % n - lexer.lextokens[n] = None + # Get literals specification + if isinstance(linfo.literals,(list,tuple)): + lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) else: - for n in tokens: lexer.lextokens[n] = None - + lexobj.lexliterals = linfo.literals - if debug: - print "lex: tokens = '%s'" % lexer.lextokens.keys() - - # Get a list of symbols with the t_ prefix - tsymbols = [f for f in ldict.keys() if f[:2] == 't_'] - - # Now build up a list of functions and a list of strings - fsymbols = [ ] - ssymbols = [ ] - for f in tsymbols: - if isinstance(ldict[f],types.FunctionType): - fsymbols.append(ldict[f]) - elif isinstance(ldict[f],types.StringType): - ssymbols.append((f,ldict[f])) - else: - print "lex: %s not defined as a function or string" % f - error = 1 - - # Sort the functions by line number - fsymbols.sort(lambda x,y: cmp(x.func_code.co_firstlineno,y.func_code.co_firstlineno)) - - # Sort the strings by regular expression length - ssymbols.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) - - # Check for non-empty symbols - if len(fsymbols) == 0 and len(ssymbols) == 0: - raise SyntaxError,"lex: no rules of the form t_rulename are defined." - - # Add all of the rules defined with actions first - for f in fsymbols: - - line = f.func_code.co_firstlineno - file = f.func_code.co_filename - files[file] = None - - if not optimize: - if f.func_code.co_argcount > 1: - print "%s:%d: Rule '%s' has too many arguments." % (file,line,f.__name__) - error = 1 - continue - - if f.func_code.co_argcount < 1: - print "%s:%d: Rule '%s' requires an argument." % (file,line,f.__name__) - error = 1 - continue + # Get the stateinfo dictionary + stateinfo = linfo.stateinfo - if f.__name__ == 't_ignore': - print "%s:%d: Rule '%s' must be defined as a string." % (file,line,f.__name__) - error = 1 - continue - - if f.__name__ == 't_error': - lexer.lexerrorf = f - continue + regexs = { } + # Build the master regular expressions + for state in stateinfo: + regex_list = [] - if f.__doc__: - if not optimize: - try: - c = re.compile(f.__doc__, re.VERBOSE) - except re.error,e: - print "%s:%d: Invalid regular expression for rule '%s'. %s" % (file,line,f.__name__,e) - error = 1 - continue + # Add rules defined by functions first + for fname, f in linfo.funcsym[state]: + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + regex_list.append("(?P<%s>%s)" % (fname,_get_regex(f))) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,_get_regex(f), state) - if debug: - print "lex: Adding rule %s -> '%s'" % (f.__name__,f.__doc__) + # Now add all of the simple rules + for name,r in linfo.strsym[state]: + regex_list.append("(?P<%s>%s)" % (name,r)) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state) - # Okay. The regular expression seemed okay. Let's append it to the master regular - # expression we're building - - if (regex): regex += "|" - regex += "(?P<%s>%s)" % (f.__name__,f.__doc__) - else: - print "%s:%d: No regular expression defined for rule '%s'" % (file,line,f.__name__) + regexs[state] = regex_list - # Now add all of the simple rules - for name,r in ssymbols: + # Build the master regular expressions - if name == 't_ignore': - lexer.lexignore = r - continue - - if not optimize: - if name == 't_error': - raise SyntaxError,"lex: Rule 't_error' must be defined as a function" - error = 1 - continue - - if not lexer.lextokens.has_key(name[2:]): - print "lex: Rule '%s' defined for an unspecified token %s." % (name,name[2:]) - error = 1 - continue - try: - c = re.compile(r,re.VERBOSE) - except re.error,e: - print "lex: Invalid regular expression for rule '%s'. %s" % (name,e) - error = 1 - continue - if debug: - print "lex: Adding rule %s -> '%s'" % (name,r) - - if regex: regex += "|" - regex += "(?P<%s>%s)" % (name,r) + if debug: + debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====") - if not optimize: - for f in files.keys(): - if not validate_file(f): - error = 1 - try: + for state in regexs: + lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames) + lexobj.lexstatere[state] = lexre + lexobj.lexstateretext[state] = re_text + lexobj.lexstaterenames[state] = re_names if debug: - print "lex: regex = '%s'" % regex - lexer.lexre = re.compile(regex, re.VERBOSE) + for i in range(len(re_text)): + debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i]) + + # For inclusive states, we need to add the regular expressions from the INITIAL state + for state,stype in stateinfo.items(): + if state != "INITIAL" and stype == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) + + lexobj.lexstateinfo = stateinfo + lexobj.lexre = lexobj.lexstatere["INITIAL"] + lexobj.lexretext = lexobj.lexstateretext["INITIAL"] + lexobj.lexreflags = reflags + + # Set up ignore variables + lexobj.lexstateignore = linfo.ignore + lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") + + # Set up error functions + lexobj.lexstateerrorf = linfo.errorf + lexobj.lexerrorf = linfo.errorf.get("INITIAL",None) + if not lexobj.lexerrorf: + errorlog.warning("No t_error rule is defined") + + # Check state information for ignore and error rules + for s,stype in stateinfo.items(): + if stype == 'exclusive': + if not s in linfo.errorf: + errorlog.warning("No error rule is defined for exclusive state '%s'", s) + if not s in linfo.ignore and lexobj.lexignore: + errorlog.warning("No ignore rule is defined for exclusive state '%s'", s) + elif stype == 'inclusive': + if not s in linfo.errorf: + linfo.errorf[s] = linfo.errorf.get("INITIAL",None) + if not s in linfo.ignore: + linfo.ignore[s] = linfo.ignore.get("INITIAL","") - # Build the index to function map for the matching engine - lexer.lexindexfunc = [ None ] * (max(lexer.lexre.groupindex.values())+1) - for f,i in lexer.lexre.groupindex.items(): - handle = ldict[f] - if isinstance(handle,types.FunctionType): - lexer.lexindexfunc[i] = (handle,handle.__name__[2:]) - else: - # If rule was specified as a string, we build an anonymous - # callback function to carry out the action - lexer.lexindexfunc[i] = (None,f[2:]) - - # If a lextab was specified, we create a file containing the precomputed - # regular expression and index table - - if lextab and optimize: - lt = open(lextab+".py","w") - lt.write("# %s.py. This file automatically created by PLY. Don't edit.\n" % lextab) - lt.write("_lexre = %s\n" % repr(regex)) - lt.write("_lextab = [\n"); - for i in range(0,len(lexer.lexindexfunc)): - t = lexer.lexindexfunc[i] - if t: - if t[0]: - lt.write(" ('%s',%s),\n"% (t[0].__name__, repr(t[1]))) - else: - lt.write(" (None,%s),\n" % repr(t[1])) - else: - lt.write(" None,\n") - - lt.write("]\n"); - lt.write("_lextokens = %s\n" % repr(lexer.lextokens)) - lt.write("_lexignore = %s\n" % repr(lexer.lexignore)) - if (lexer.lexerrorf): - lt.write("_lexerrorf = %s\n" % repr(lexer.lexerrorf.__name__)) - else: - lt.write("_lexerrorf = None\n") - lt.close() - - except re.error,e: - print "lex: Fatal error. Unable to compile regular expression rules. %s" % e - error = 1 - if error: - raise SyntaxError,"lex: Unable to build lexer." - if not lexer.lexerrorf: - print "lex: Warning. no t_error rule is defined." - - if not lexer.lexignore: lexer.lexignore = "" - # Create global versions of the token() and input() functions - token = lexer.token - input = lexer.input - - return lexer + token = lexobj.token + input = lexobj.input + lexer = lexobj + + # If in optimize mode, we write the lextab + if lextab and optimize: + lexobj.writetab(lextab,outputdir) + + return lexobj # ----------------------------------------------------------------------------- -# run() +# runmain() # # This runs the lexer as a main program # ----------------------------------------------------------------------------- @@ -658,7 +1030,7 @@ def runmain(lexer=None,data=None): data = f.read() f.close() except IndexError: - print "Reading from standard input (type EOF to end):" + sys.stdout.write("Reading from standard input (type EOF to end):\n") data = sys.stdin.read() if lexer: @@ -670,12 +1042,28 @@ def runmain(lexer=None,data=None): _token = lexer.token else: _token = token - + while 1: tok = _token() if not tok: break - print "(%s,'%s',%d)" % (tok.type, tok.value, tok.lineno) - - + sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos)) + +# ----------------------------------------------------------------------------- +# @TOKEN(regex) +# +# This decorator function can be used to set the regex expression on a function +# when its docstring might need to be set in an alternative way +# ----------------------------------------------------------------------------- + +def TOKEN(r): + def set_regex(f): + if hasattr(r,"__call__"): + f.regex = _get_regex(r) + else: + f.regex = r + return f + return set_regex +# Alternative spelling of the TOKEN decorator +Token = TOKEN diff --git a/patron.rng.expected b/patron.rng.expected new file mode 120000 index 0000000..f9c028b --- /dev/null +++ b/patron.rng.expected @@ -0,0 +1 @@ +patron.rng.orig
\ No newline at end of file diff --git a/patron.rng b/patron.rng.orig index 582ad0f..582ad0f 100644 --- a/patron.rng +++ b/patron.rng.orig diff --git a/patron.rng.trang b/patron.rng.trang new file mode 120000 index 0000000..fd81865 --- /dev/null +++ b/patron.rng.trang @@ -0,0 +1 @@ +patron.rng.expected
\ No newline at end of file diff --git a/regextest.rng.expected b/regextest.rng.expected new file mode 100644 index 0000000..62f372d --- /dev/null +++ b/regextest.rng.expected @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- A schema for regextest.rnc in RELAX NG compact syntax --> +<element name="testSuite" xmlns="http://relaxng.org/ns/structure/1.0"> + <zeroOrMore> + <element name="testCase"> + <choice> + <group> + <element name="correct"> + <text/> + </element> + <zeroOrMore> + <element> + <choice> + <name>valid</name> + <name>invalid</name> + </choice> + <text/> + </element> + </zeroOrMore> + </group> + <element name="incorrect"> + <text/> + </element> + </choice> + </element> + </zeroOrMore> +</element> diff --git a/regextest.rng.trang b/regextest.rng.trang new file mode 120000 index 0000000..e67489e --- /dev/null +++ b/regextest.rng.trang @@ -0,0 +1 @@ +regextest.rng.expected
\ No newline at end of file diff --git a/res08.rng.expected b/res08.rng.expected new file mode 100644 index 0000000..5fedeab --- /dev/null +++ b/res08.rng.expected @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <start> + <element name="foo"> + <ref name="foo.body"/> + </element> + </start> +</grammar> diff --git a/res08.rng b/res08.rng.orig index 556656a..556656a 100644 --- a/res08.rng +++ b/res08.rng.orig diff --git a/res08.rng.trang b/res08.rng.trang new file mode 120000 index 0000000..918e35a --- /dev/null +++ b/res08.rng.trang @@ -0,0 +1 @@ +res08.rng.expected
\ No newline at end of file diff --git a/rnc_tokenize.py b/rnc_tokenize.py index 20cfc34..554e5ec 100644 --- a/rnc_tokenize.py +++ b/rnc_tokenize.py @@ -1,108 +1,265 @@ #!/usr/bin/env python - # Define the tokenizer for RELAX NG compact syntax # This file released to the Public Domain by David Mertz +# +# Extended under revised BSD license by Jan Pokorny (jpokorny@redhat.com) +# Copyright 2013 Red Hat, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the Red Hat, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +# TODO: update positions so they can be used for debugging + +import re +#try: +# from ply import lex +#except ImportError: +# import lex import lex -tokens = tuple(''' - ELEM ATTR EMPTY TEXT KEYWORD LITERAL ANNOTATION COMMENT - BEG_PAREN END_PAREN BEG_BODY END_BODY EQUAL NAME CHOICE SEQ - INTERLEAVE ANY SOME MAYBE WHITESPACE TODO DATATAG PATTERN - DEFAULT_NS NS DATATYPES NS_ANNOTATION START DEFINE + + +# +# tokens declaration +# + +# square (left + right delimiter) together with single-node tree replacement +pair_rules = tuple(l.replace(' ', '').split(':') for l in ''' + BEG_BODY : END_BODY : BODY + BEG_PAREN : END_PAREN : GROUP + BEG_ANNO : END_ANNO : NS_ANNOTATION + '''.strip().splitlines()) +pair_tokens = reduce(lambda a, b: a + tuple(b[:2]), pair_rules, ()) + +quant_tokens = tuple(''' + ANY + MAYBE + SOME '''.split()) -reserved = { - 'element' : 'ELEM', - 'attribute' : 'ATTR', - 'empty' : 'EMPTY', - 'text' : 'TEXT', - 'div' : 'TODO', - 'external' : 'TODO', - 'grammar' : 'TODO', - 'include' : 'TODO', - 'inherit' : 'TODO', - 'list' : 'TODO', - 'mixed' : 'TODO', - 'notAllowed' : 'TODO', - 'parent' : 'TODO', - 'string' : 'TODO', - 'token' : 'TODO', +immediate_tokens = tuple(''' + ANNOTATION + CHOICE + COMMENT + DATATAG + DEFINE + EQUAL + INTERLEAVE + LITERAL + NAME + PATTERN + SEQ + WHITESPACE + '''.split()) + quant_tokens + +# http://relaxng.org/compact-20021121.html#syntax +keywords = { + 'attribute': 'ATTR', + 'default': 'DEFAULT_NS', + 'datatypes': 'DATATYPES', + 'div': 'DIV', + 'element': 'ELEM', + 'empty': 'EMPTY', + 'external': 'EXTERNAL', + 'grammar': 'GRAMMAR', + 'include': 'INCLUDE', + 'inherit': 'INHERIT', + 'list': 'LIST', + 'mixed': 'MIXED', + 'namespace': 'NS', + 'notAllowed': 'NOTALLOWED', + 'parent': 'PARENT', + 'start': 'START', + 'string': 'STRING', + 'text': 'TEXT', + 'token': 'TOKEN', } -def t_START(t): - r"(?im)^start\s*=\s*.*$" - t.value = t.value.split('=')[1].strip() - return t +tokens = immediate_tokens + pair_tokens + tuple(keywords.values()) + + +# +# tokens definition +# + +# RELAX NG/XML datatype NCName is defined in +# http://books.xmlschemata.org/relaxng/ch19-77215.html +# http://www.w3.org/TR/REC-xml-names/#NT-NCName +# which can be resolved as +# <NCName> ::= <Start> <NonStart>* +# where +# <Start> ::= [A-Z] | "_" | [a-z] | [#xC0-#xD6] | ... +# (see http://www.w3.org/TR/REC-xml/#NT-NameStartChar) +# <NonStart> ::= <Start> | "-" | "." | [0-9] | #xB7 | .. +# (see http://www.w3.org/TR/REC-xml/#NT-NameChar) +# +# NOTE: skipping [\u10000-\uEFFFF] as it won't get lex'd (???) +NCName_start = "(?:[A-Z_a-z]" \ + u"|[\u00C0-\u00D6]" \ + u"|[\u00D8-\u00F6]" \ + u"|[\u00F8-\u02FF]" \ + u"|[\u0370-\u037D]" \ + u"|[\u037F-\u1FFF]" \ + u"|[\u200C-\u200D]" \ + u"|[\u2070-\u218F]" \ + u"|[\u2C00-\u2FEF]" \ + u"|[\u3001-\uD7FF]" \ + u"|[\uF900-\uFDCF]" \ + u"|[\uFDF0-\uFFFD]" \ + ")" +NCName_nonstart = NCName_start + "|(?:[-.0-9]" \ + u"|\u00B7" \ + u"|[\u0300-\u036F]" \ + u"|[\u203F-\u2040]" \ + ")" +NCName = NCName_start + "(?:" + NCName_nonstart + ")*" + +# lex internals + +t_ignore = " \t\n" + + +def t_error(t): + try: + t.lexer.skip(1) + except AttributeError: + # works in historic version of PLY + t.skip(1) + +# immediate tokens + +t_ANY = r'\*' +t_CHOICE = r'\|' +t_EQUAL = r'=' +t_INTERLEAVE = r'&' +t_MAYBE = r'\?' +t_SEQ = r',' +t_SOME = r'\+' +t_WHITESPACE = r'\s+' -def t_DEFINE(t): - r"(?im)^[\w-]+\s*=" - t.value = t.value.split('=')[0].strip() - return t def t_ANNOTATION(t): - r"(?im)^\#\# .*$" - t.value = t.value[3:] + r"\#\#[ \t]?.*" + t.value = t.value.replace('# ', '#', 1).split('##', 1)[1].rstrip() return t + def t_COMMENT(t): - r"(?im)^\# .*$" - t.value = t.value[2:] + r"\#[ \t]?.*" + t.value = t.value.replace('# ', '#', 1).split('#', 1)[1].rstrip() return t -def t_DEFAULT_NS(t): - r"(?im)default\s+namespace\s*=\s*.*$" - t.value = t.value.split('=')[1].strip() - return t def t_DATATYPES(t): - r"(?im)datatypes\s+xsd\s*=\s*.*$" - t.value = t.value.split('=')[1].strip() + r"datatypes\s+xsd\s*=\s*.*" + t.value = t.value.split('=', 1)[1].strip() return t + def t_DATATAG(t): r"xsd:\w+" - t.value = t.value.split(':')[1] + t.value = t.value.split(':', 1)[1] + return t + + +def t_DEFAULT_NS(t): + r"default\s+namespace\s*=\s*.*" + t.value = t.value.split('=', 1)[1].strip() + return t + + +def t_INCLUDE(t): + t.value = t.value.split('"', 1)[1][:-1] + return t +t_INCLUDE.__doc__ = r'include\s*"' + NCName + '"' + + +def t_LITERAL(t): + r'".+?"(?:\s*[~]\s*".+?")*' + t.value = ' '.join(i.strip(' \n"') for i in t.value.split('~')) return t + +def t_NAME(t): + # "In order to use a keyword as an identifier, it must be quoted with \." + t.value = t.value[2:] + return t +t_NAME.__doc__ = r"\\[.]" + NCName + + +def t_NS(t): + r"namespace\s+.*" + t.value = t.value.split(None, 1)[1] + return t + + def t_PATTERN(t): r'{\s*pattern\s*=\s*".*"\s*}' - t.value = t.value[:-1].split('=')[1].strip()[1:-1] + t.value = t.value[:-1].split('=', 1)[1].strip()[1:-1] return t -def t_NS(t): - r"(?im)^namespace\s+.*$" - t.value = t.value.split(None,1)[1] + +# these two last (and in this order) for reason +def t_DEFINE(t): + t.value = t.value.split('=', 1)[0].strip() return t +t_DEFINE.__doc__ = NCName + "\s*=" + def t_ID(t): - r"[\w:_-]+" - t.type = reserved.get(t.value,'NAME') # Check for reserved words + t.type = keywords.get(t.value, 'NAME') # Check for keywords return t +t_ID.__doc__ = NCName -def t_LITERAL(t): - r'".+?"' - t.value = t.value[1:-1] - return t -t_BEG_PAREN = r"\(" -t_END_PAREN = r"\)" -t_BEG_BODY = r"{" -t_END_BODY = r"}" -t_EQUAL = r"=" -t_CHOICE = r"[|]" -t_SEQ = r"," -t_INTERLEAVE= r"&" -t_ANY = r"[*]" -t_SOME = r"[+]" -t_MAYBE = r"[?]" -t_WHITESPACE= r"\s+" -t_ignore = " \t\n\r" +# pair tokens + +t_BEG_ANNO = r'\[' +t_END_ANNO = r'\]' +t_BEG_BODY = r'{' +t_END_BODY = r'}' +t_BEG_PAREN = r'\(' +t_END_PAREN = r'\)' + + +# +# processing +# + +def preprocess(rnc): + # 2.2. BOM stripping + if len(rnc) >= 2 and ord(rnc[0]) == 0xFE and ord(rnc[1]) == 0xFF: + rnc = rnc[2:] + # 2.3 Newline normalization + rnc = re.sub(r"(?:\n|\r\n?)", "\n", rnc, re.MULTILINE) + # TODO: 2.4 Escape interpretation + return rnc -def t_error(t): - t.skip(1) def token_list(rnc): lex.lex() - lex.input(rnc) + lex.input(preprocess(rnc)) ts = [] while 1: t = lex.token() @@ -111,7 +268,8 @@ def token_list(rnc): ts.append(t) return ts -if __name__=='__main__': + +if __name__ == '__main__': import sys del t_ignore tokens = token_list(sys.stdin.read()) @@ -1,49 +1,129 @@ #!/usr/bin/env python # Convert an RELAX NG compact syntax schema to a Node tree # This file released to the Public Domain by David Mertz -from __future__ import generators +# +# Extended under revised BSD license by Jan Pokorny (jpokorny@redhat.com) +# Copyright 2013 Red Hat, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the Red Hat, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +# Differences when compared to trang output +# 1. comments placement +# 2. sometimes superfluous <group> +# 3. context-free dichotomy (diff conv08.rng.{expected,trang}) +# plenty of others (it's not the primary goal to achieve 1:1 trang match) + +# XXX: each AST node has its own subclass, knows how to XMLize itself, ...? + + import sys -from rnc_tokenize import token_list +from rnc_tokenize import tokens, pair_rules, keywords, token_list + +# ONE ... default cardinality of one +# DIRECT ... denotes that the usage of NAME is <name>, not <ref name=...> +quant_tokens_aux = tuple(''' + DIRECT + ONE + '''.split()) +# AST nodes not directly matching the tokens +parse_constructs = tuple(''' + ROOT + '''.split()) + tuple(r[2] for r in pair_rules) -class ParseError(SyntaxError): pass +for t in tokens + quant_tokens_aux + parse_constructs: + globals()[t] = t -for t in """ - ANY SOME MAYBE ONE BODY ANNOTATION ELEM ATTR GROUP LITERAL - NAME COMMENT TEXT EMPTY INTERLEAVE CHOICE SEQ ROOT - DEFAULT_NS NS DATATYPES DATATAG PATTERN START DEFINE - """.split(): globals()[t] = t +keyword_list = keywords.values() -PAIRS = {'BEG_BODY': ('END_BODY', BODY), - 'BEG_PAREN': ('END_PAREN', GROUP), - 'BEG_ANNO': ('END_ANNO', ANNOTATION)} +PAIRS = {r[0]: tuple(r[1:]) for r in pair_rules} -TAGS = {ONE: 'group', - SOME: 'oneOrMore', - MAYBE: 'optional', - ANY: 'zeroOrMore'} +TAGS = { + ONE: 'group', + SOME: 'oneOrMore', + MAYBE: 'optional', + ANY: 'zeroOrMore', + ELEM: 'element', + ATTR: 'attribute', + NAME: 'ref', +} + +URI_DATATYPES = "http://www.w3.org/2001/XMLSchema-datatypes" +URI_ANNOTATIONS = "http://relaxng.org/ns/compatibility/annotations/1.0" DEFAULT_NAMESPACE = None -DATATYPE_LIB = [0, '"http://www.w3.org/2001/XMLSchema-datatypes"'] +DATATYPE_LIB = [0, '"' + URI_DATATYPES + '"'] OTHER_NAMESPACE = {} CONTEXT_FREE = 0 -try: enumerate -except: enumerate = lambda seq: zip(range(len(seq)), seq) +# debugging +for i, n in enumerate(""" + D_NOTHING + D_TO_NODES + D_MATCH_PAIR + D_TYPE_BODIES + D_NEST_DEFINES + D_SCAN_NS +""".split()): + globals()[n] = i and 2 << (i - 1) or 0 +dlist = [] +#dlist.append(D_TO_NODES) +#dlist.append(D_MATCH_PAIR) +#dlist.append(D_TYPE_BODIES) +#dlist.append(D_NEST_DEFINES) +#dlist.append(D_SCAN_NS) +debug = reduce(lambda a, b: a | b, dlist, D_NOTHING) + + +def try_debug(what, nodes): + if debug & globals().get('D_' + what, D_NOTHING): + print what + for node in nodes: + print node.prettyprint() + + nodetypes = lambda nl: tuple(map(lambda n: n.type, nl)) toNodes = lambda toks: map(lambda t: Node(t.type, t.value), toks) +class ParseError(SyntaxError): + pass + + class Node(object): __slots__ = ('type', 'value', 'name', 'quant') - def __iter__(self): yield self + def __iter__(self): + yield self __len__ = lambda self: 1 - def __init__(self, type='', value=[], name=None, quant=ONE): - self.type = type - self.value = value - self.name = name + def __init__(self, type='', value=None, name=None, quant=ONE): + self.type = type + self.value = value if value is not None else [] + self.name = name self.quant = quant def format(self, indent=0): @@ -73,26 +153,58 @@ class Node(object): else: return self.add_ns(self.xmlnode()) + def collect_annot(self, x): + ret = {} + if isinstance(x.value, basestring): + return ret + + name, value = None, None + for node in x.value: + if node.type != NS_ANNOTATION: + break + for i, inner in enumerate(node.value): + if i % 3 == 0 and inner.type == NAME: + name = inner.value + elif i % 3 == 1 and inner.type == DEFINE: + name += ':' + inner.value + elif i % 3 == 2 and inner.type == LITERAL: + value = inner.value + if ret.setdefault(name, value) is not value: + assert 0, "redefinition of %s" % name + name, value = None, None + elif i % 3 == 0 and i > 0: + break + else: + assert 0, "NS_ANNOTATION body does not match" + return [n + '="' + v + '"' for n, v in ret.iteritems()] + def xmlnode(self, indent=0): out = [] write = out.append if self.type == ROOT: write('<?xml version="1.0" encoding="UTF-8"?>') - for x in self.value: + for i, x in enumerate(self.value): if not isinstance(x, Node): raise TypeError("Unhappy Node.value: " + repr(x)) - elif x.type == START: - startelem = '<start><ref name="%s"/></start>' % x.value - write(' ' * indent + startelem) + if x.type == START: + write(' ' * indent + '<start>') + if (x.name is not None): + write(' ' * (indent + 1) + '<ref name="%s"/>' % x.name) + else: + write(x.xmlnode(indent + 1)) + write(' ' * indent + '</start>') elif x.type == DEFINE: write(' ' * indent + '<define name="%s">' % x.name) write(x.xmlnode(indent + 1)) write(' ' * indent + '</define>') - elif x.type == NAME: - write(' ' * indent + '<ref name="%s"/>' % x.value) elif x.type == COMMENT: - write(' ' * indent + '<!-- %s -->' % x.value) + comments = x.value.split('\n') + if len(comments) == 1: + c = ' ' + comments[0] + ' ' + else: + c = ('\n' + ' ' * (indent + 1)).join([''] + comments + ['']) + write(' ' * indent + '<!--%s-->' % c) elif x.type == LITERAL: write(' ' * indent + '<value>%s</value>' % x.value) elif x.type == ANNOTATION: @@ -102,14 +214,14 @@ class Node(object): write(' ' * indent + '<interleave>') write(x.xmlnode(indent + 1)) write(' ' * indent + '</interleave>') - elif x.type == SEQ: - write(x.xmlnode(indent + 1)) elif x.type == CHOICE: write(' ' * indent + '<choice>') write(x.xmlnode(indent + 1)) write(' ' * indent + '</choice>') - elif x.type == GROUP: - write(x.xmlnode(indent)) + elif x.type in (GROUP, SEQ): + write(' ' * indent + '<group>') + write(x.xmlnode(indent + 1)) + write(' ' * indent + '</group>') elif x.type == TEXT: write(' ' * indent + '<text/>') elif x.type == EMPTY: @@ -123,24 +235,32 @@ class Node(object): p = '<param name="pattern">%s</param>' % x.value write(' ' * (indent + 1) + p) write(' ' * indent + '</data>') - elif x.type == ELEM: - if x.quant == ONE: - write(' ' * indent + '<element name="%s">' % x.name) - write(x.xmlnode(indent + 1)) - write(' ' * indent + '</element>') + elif x.type == INCLUDE: + write(' ' * indent + '<include href="%s"/>' % x.value) + elif x.type == NAME and x.quant == DIRECT: + assert x.type == NAME + write(' ' * indent + '<name>%s</name>' % x.value) + elif x.type in (ATTR, ELEM, NAME): + a = ('\n' + ' ' * (indent + 3)).join(self.collect_annot(x)) + name_n_annot = '%s' % (' ' + a).rstrip() + name = x.value if x.type == NAME else x.name + if name: + name_n_annot = ' name="%s"' % name + name_n_annot + + indent_inner = indent + if x.quant != ONE: + write(' ' * indent_inner + '<%s>' % TAGS[x.quant]) + indent_inner += 1 + tag, rest = TAGS[x.type], name_n_annot + if x.type == NAME or x.type == ATTR and x.value[0].type == TEXT: + write(' ' * indent_inner + '<%s%s/>' % (tag, rest)) else: - write(' ' * indent + '<%s>' % TAGS[x.quant]) - write(' ' * (indent + 1) + '<element name="%s">' % x.name) - write(x.xmlnode(indent + 2)) - write(' ' * (indent + 1) + '</element>') - write(' ' * indent + '</%s>' % TAGS[x.quant]) - elif x.type == ATTR: - if x.value[0].type == TEXT: - write(' ' * indent + '<attribute name="%s"/>' % x.name) - elif x.value[0].type == EMPTY: - write(' ' * indent + '<attribute name="%s">' % x.name) - write(' ' * (indent + 1) + '<empty/>') - write(' ' * indent + '</attribute>') + write(' ' * indent_inner + '<%s%s>' % (tag, rest)) + write(x.xmlnode(indent_inner + 1)) + write(' ' * indent_inner + '</%s>' % tag) + if x.quant != ONE: + indent_inner -= 1 + write(' ' * indent_inner + '</%s>' % TAGS[x.quant]) return '\n'.join(out) @@ -156,7 +276,11 @@ class Node(object): ltpos = line.find('<') if ltpos >= 0 and line[ltpos + 1] not in ('!', '?'): # We've got an element tag, not PI or comment - new = line[:line.find('>')] + tail = '>' + new = line[:line.find(tail)] + if new.endswith('/'): + new = new[:-1] + tail = '/' + tail new += ' xmlns="http://relaxng.org/ns/structure/1.0"' if DEFAULT_NAMESPACE is not None: new += '\n ns=%s' % DEFAULT_NAMESPACE @@ -164,7 +288,7 @@ class Node(object): new += '\n datatypeLibrary=%s' % DATATYPE_LIB[1] for ns, url in OTHER_NAMESPACE.items(): new += '\n xmlns:%s=%s' % (ns, url) - new += '>' + new += tail lines[i] = new break return '\n'.join(lines) @@ -188,21 +312,38 @@ def findmatch(beg, nodes, offset): level = 1 end = PAIRS[beg][0] for i, t in enumerate(nodes[offset:]): - if t.type == beg: level += 1 - elif t.type == end: level -= 1 + if t.type == beg: + level += 1 + elif t.type == end: + level -= 1 if level == 0: return i + offset raise EOFError("No closing token encountered for %s @ %d" - % (beg, offset)) + % (beg, offset)) + +# +# 1st pass in the pipe +# def match_pairs(nodes): + """<left paren., []> + <tokens> + <right paren., []> --> <ent., <tokens>> + + Other effects: + - merge comments/annotations + """ newnodes = [] i = 0 while 1: - if i >= len(nodes): break + if i >= len(nodes): + break node = nodes[i] if node.type in PAIRS.keys(): + # TOKEN, etc. -> NAME where suitable + # (keyword-like names do not need to be escaped in some cases) + if node.type == 'BEG_BODY' and newnodes[-1].type in keyword_list: + if newnodes[-2].type in (ELEM, ATTR): + newnodes[-1].type = NAME # Look for enclosing brackets match = findmatch(node.type, nodes, i + 1) matchtype = PAIRS[node.type][1] @@ -210,69 +351,139 @@ def match_pairs(nodes): node.value = match_pairs(node.value) newnodes.append(node) i = match + 1 + elif (node.type in (COMMENT, ANNOTATION) and i > 0 + and newnodes[-1].type == node.type): + # merge comments/annotations + newnodes[-1].value += "\n" + node.value + i += 1 else: newnodes.append(node) i += 1 - if i >= len(nodes): break + if i >= len(nodes): + break if nodes[i].type in (ANY, SOME, MAYBE): newnodes[-1].quant = nodes[i].type i += 1 + nodes[:] = newnodes return nodes +# +# 2nd pass in the pipe +# + def type_bodies(nodes): + """Another (main) de-linearization""" newnodes = [] i = 0 while 1: - if i >= len(nodes): break - if nodetypes(nodes[i:i + 3]) == (ELEM, NAME, BODY) or \ - nodetypes(nodes[i:i + 3]) == (ATTR, NAME, BODY): + if i >= len(nodes): + break + if (nodetypes(nodes[i:i + 3]) == (ELEM, NAME, BODY) + or nodetypes(nodes[i:i + 3]) == (ATTR, NAME, BODY)): name, body = nodes[i + 1].value, nodes[i + 2] value, quant = type_bodies(body.value), body.quant node = Node(nodes[i].type, value, name, quant) newnodes.append(node) + if not name: + assert False i += 3 + # "element a|b" cases + elif (nodetypes(nodes[i:i + 3]) == (ELEM, NAME, CHOICE) + or nodetypes(nodes[i:i + 3]) == (ATTR, NAME, CHOICE)): + # see nameClass (choice of nameClass+) + # XXX: very simplified + if nodes[i].type == ATTR: + assert False + node_type = nodes[i].type + value = [nodes[i + 1]] + i += 2 + while nodetypes(nodes[i:i + 2]) == (CHOICE, NAME): + value.extend(type_bodies(nodes[i:i + 2])) + i += 2 + # re-mark quant as we do not want "ref" output here + for v in value: + if v.type == NAME: + v.quant = DIRECT + assert len(nodes) >= i and nodes[i].type == BODY + value.extend(type_bodies(nodes[i].value)) + node = Node(node_type, value, None, nodes[i].quant) + i += 1 + newnodes.append(node) elif nodetypes(nodes[i:i + 2]) == (DATATAG, PATTERN): node = Node(DATATAG, nodes[i + 1].value, nodes[i].value) newnodes.append(node) i += 2 - elif nodes[i] == DEFINE: - print nodes[i:] else: - if nodes[i].type == GROUP: # Recurse into groups - value = type_bodies(nodes[i].value) - nodes[i] = Node(GROUP, value, None, nodes[i].quant) - newnodes.append(nodes[i]) + n = nodes[i] + if n.type == GROUP: # Recurse into groups + value = type_bodies(n.value) + if len(value) > 1 and n.type: + n = Node(GROUP, value, None, n.quant) + newnodes.append(n) i += 1 nodes[:] = newnodes return nodes -def nest_defines(nodes): - "Attach groups to named patterns" +# +# 3rd pass in the pipe +# + +def _nest_annotations(nodes, mapping, delim=None): + """Helper to move comments/annotations down into attributes/elements + + Uses non-tail recursion to proceed the tree bottom-up as + otherwise there would be confusion if the annotations are + newly added (and thus should be kept) or the original ones + to be moved. + + Mapping is partially defined + token-type |-> accumulator-list for token-type + for token-types covering annotations (ANNOTATION, NS_ANNOTATION) + and is used to pass unconsumed annotations down the tree. + + Returns triplet: number of consumed nodes, filtered nodes, mapping. + + Note that mapping should contain empty lists only when the recursion + returns back to the initiator (XXX: little bit of sanity checking, + we cannot speak about proper validation here). + """ + # XXX: unclean, yes newnodes = [] - i = 0 - while 1: - if i >= len(nodes): break - node = nodes[i] - newnodes.append(node) - if node.type == DEFINE: - group = [] - while (i + 1) < len(nodes) and nodes[i + 1].type != DEFINE: - group.append(nodes[i + 1]) - i += 1 - node.name = node.value - node.value = Node(GROUP, group) - i += 1 + for i, n in enumerate(nodes): + if delim and n.type == delim: + break + + if not isinstance(n.value, str): # no recurse to terminal str + if n.type in (ELEM, ATTR): + mapping_rec = {n: [] for n in + (ANNOTATION, NS_ANNOTATION, COMMENT)} + else: + mapping_rec = mapping + _nest_annotations(n.value, mapping_rec) + + if n.type in (ELEM, ATTR): # annot. consumer (guarded in recursion) + n.value = (mapping['NS_ANNOTATION'] + mapping['ANNOTATION'] + + mapping['COMMENT'] + n.value) + mapping['NS_ANNOTATION'][:], mapping['ANNOTATION'][:] = [], [] + mapping['COMMENT'][:] = [] + elif i == len(nodes) - 1 and n.type == COMMENT and not delim: + # comment at the end of the nodelist, but only if not top-level + newnodes.append(n) + continue + + mapping.get(n.type, newnodes).append(n) + nodes[:] = newnodes - return nodes + return i, nodes, mapping -def intersperse(nodes): - "Look for interleaved, choice, or sequential nodes in groups/bodies" +def _intersperse(nodes): + """Look for interleaved, choice, or sequential nodes in groups/bodies""" for node in nodes: - if node.type in (ELEM, ATTR, GROUP, LITERAL): + if node.type in (ELEM, ATTR, GROUP, LITERAL): # XXX: literal? val = node.value ntypes = [n.type for n in val if not isinstance(val, str)] inters = [t for t in ntypes if t in (INTERLEAVE, CHOICE, SEQ)] @@ -281,43 +492,117 @@ def intersperse(nodes): raise ParseError("Ambiguity in sequencing: %s" % node) if len(inters) > 0: intertype = inters.keys()[0] - items = [] + outer_items, last_ntype, internode = [], None, None + simplify = node.type == GROUP for pat in node.value: - if pat.type != intertype: - items.append(pat) - node.value = Node(intertype, items) + if pat.type == intertype: + if internode is None: + internode = Node(intertype, [outer_items.pop()]) + outer_items.append(internode) + # otherwise drop it + elif last_ntype == intertype: + internode.value.append(pat) + else: + outer_items.append(pat) + if pat.type in (COMMENT, ANNOTATION): + # these are not interesting wrt. last type + continue + elif pat.quant not in (ONE, MAYBE): + simplify = False + last_ntype = pat.type + + if (simplify and len(outer_items) == 1 + and outer_items[0] is internode): + node.type, node.value = internode.type, internode.value + else: + node.value = outer_items if not isinstance(node.value, str): # No recurse to terminal str - intersperse(node.value) + _intersperse(node.value) + return nodes + + +def nest_defines(nodes): + """Attach groups to named patterns + + Other effects: + - annotations are properly nested + - comments are nested + """ + newnodes = [] + i = 0 + group, annotations, ns_annotations, comments = [], [], [], [] + mapping = dict(ANNOTATION=annotations, NS_ANNOTATION=ns_annotations, + COMMENT=comments) + while i < len(nodes): + node = nodes[i] + newnodes.append(node) + group[:], annotations[:], ns_annotations[:], comments[:] = [], [], [], [] + if node.type == DEFINE: + j, group[:], mapping = _nest_annotations(nodes[i + 1:], mapping, DEFINE) + i += j + node.name = node.value + grp = _intersperse([Node(GROUP, group[:])])[0] + if len(grp.value) > 1 and grp.type != SEQ: + node.value = [grp] + else: + node.value = grp.value[:] + # when _nest_annotations returned *not* due to reaching DEFINE, + # but trailing comments are tolerated + if i + 1 > len(nodes) or nodes[i + 1].type not in (DEFINE, COMMENT): + break + elif node.type == ELEM: + # top-level element + _intersperse(Node(GROUP, [node])) + i += 1 + nodes[:] = newnodes return nodes +# +# 4th pass in the pipe +# + def scan_NS(nodes): - "Look for any namespace configuration lines" + """Look for any namespace configuration lines + + Other effects: + - DEFINE(start) --> START + """ global DEFAULT_NAMESPACE, OTHER_NAMESPACE, CONTEXT_FREE for node in nodes: if node.type == DEFAULT_NS: DEFAULT_NAMESPACE = node.value elif node.type == NS: - ns, url = map(str.strip, node.value.split('=')) + ns, url = map(str.strip, node.value.split('=', 1)) OTHER_NAMESPACE[ns] = url elif node.type == ANNOTATION and 'a' not in OTHER_NAMESPACE: - OTHER_NAMESPACE['a'] =\ - '"http://relaxng.org/ns/compatibility/annotations/1.0"' + OTHER_NAMESPACE['a'] = '"' + URI_ANNOTATIONS + '"' elif node.type == DATATYPES: DATATYPE_LIB[:] = [1, node.value] - elif node.type == START: + elif not CONTEXT_FREE and node.type == DEFINE and node.name == 'start': CONTEXT_FREE = 1 + node.type = START + node.name = None def make_nodetree(tokens): + """Wraps the pipe of conversion passes""" nodes = toNodes(tokens) + try_debug('TO_NODES', nodes) + match_pairs(nodes) + try_debug('MATCH_PAIR', nodes) + type_bodies(nodes) + try_debug('TYPE_BODIES', nodes) + nest_defines(nodes) - intersperse(nodes) + try_debug('NEST_DEFINES', nodes) + scan_NS(nodes) - root = Node(ROOT, nodes) - return root + try_debug('SCAN_NS', nodes) + + return Node(ROOT, nodes) if __name__ == '__main__': diff --git a/tests.sh b/tests.sh new file mode 100755 index 0000000..4bb66bb --- /dev/null +++ b/tests.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Copyright 2013 Red Hat, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the Red Hat, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +# TODO: the output will never be 1:1 to that from trang, so a subset of +# test suite has to be run against manually checked outputs +set -u + +SCRIPT=./rnc2rng +DIFF="diff -q" +KNOWNTOFAIL="xsdtest.rng" # colon-separated +KNOWNTOFAIL=":${KNOWNTOFAIL}:" + +run() { + for rnc in *.rnc; do + local rng="$(echo "${rnc}" | sed 's|rnc$|rng|')" + echo ":${KNOWNTOFAIL}:" | grep -Fq "${rng}" && continue + ${SCRIPT} "${rnc}" > "${rng}.testrun" + ${DIFF} ${rng}{.expected,.testrun} + [ $? -eq 0 ] && echo "${rnc} OK" || echo "${rnc} FAIL" + done +} + +clean() { + rm -vf "*.testrun" +} + +[ $# -eq 0 ] && run || $1 diff --git a/conv09.rng b/unused/conv09.rng index 27628ec..27628ec 100644 --- a/conv09.rng +++ b/unused/conv09.rng diff --git a/curious.dtd b/unused/curious.dtd index 03b6a63..03b6a63 100644 --- a/curious.dtd +++ b/unused/curious.dtd diff --git a/curious.xml b/unused/curious.xml index 6d95294..6d95294 100644 --- a/curious.xml +++ b/unused/curious.xml diff --git a/patron-2.rng b/unused/patron-2.rng index 324138e..324138e 100644 --- a/patron-2.rng +++ b/unused/patron-2.rng diff --git a/patron-i1.xml b/unused/patron-i1.xml index 976be53..976be53 100644 --- a/patron-i1.xml +++ b/unused/patron-i1.xml diff --git a/patron-i2.xml b/unused/patron-i2.xml index f0b38c5..f0b38c5 100644 --- a/patron-i2.xml +++ b/unused/patron-i2.xml diff --git a/patron-i3.xml b/unused/patron-i3.xml index fba69c8..fba69c8 100644 --- a/patron-i3.xml +++ b/unused/patron-i3.xml diff --git a/patron-v1.xml b/unused/patron-v1.xml index 1676598..1676598 100644 --- a/patron-v1.xml +++ b/unused/patron-v1.xml diff --git a/patron-v2.xml b/unused/patron-v2.xml index 06b37bb..06b37bb 100644 --- a/patron-v2.xml +++ b/unused/patron-v2.xml diff --git a/res09.rng b/unused/res09.rng index 46efdaf..46efdaf 100644 --- a/res09.rng +++ b/unused/res09.rng diff --git a/spectest.xml b/unused/spectest.xml index b43d460..b43d460 100644 --- a/spectest.xml +++ b/unused/spectest.xml diff --git a/split.xsl b/unused/split.xsl index bef75f0..bef75f0 100644 --- a/split.xsl +++ b/unused/split.xsl diff --git a/testSuite.rng b/unused/testSuite.rng index 6626f3a..6626f3a 100644 --- a/testSuite.rng +++ b/unused/testSuite.rng diff --git a/patron.xsd b/unused/unused-counterparts/patron.xsd index 420bb3d..420bb3d 100644 --- a/patron.xsd +++ b/unused/unused-counterparts/patron.xsd diff --git a/regextest.xml b/unused/unused-counterparts/regextest.xml index b6f896d..b6f896d 100644 --- a/regextest.xml +++ b/unused/unused-counterparts/regextest.xml diff --git a/xsdtest.xml b/unused/unused-counterparts/xsdtest.xml index 2fe94b6..2fe94b6 100644 --- a/xsdtest.xml +++ b/unused/unused-counterparts/xsdtest.xml diff --git a/xsdtest.xsl b/unused/unused-counterparts/xsdtest.xsl index 1290b16..1290b16 100644 --- a/xsdtest.xsl +++ b/unused/unused-counterparts/xsdtest.xsl diff --git a/xsdtest.rng.expected b/xsdtest.rng.expected new file mode 120000 index 0000000..a988708 --- /dev/null +++ b/xsdtest.rng.expected @@ -0,0 +1 @@ +xsdtest.rng.orig
\ No newline at end of file diff --git a/xsdtest.rng b/xsdtest.rng.orig index bd760d1..bd760d1 100644 --- a/xsdtest.rng +++ b/xsdtest.rng.orig diff --git a/xsdtest.rng.trang b/xsdtest.rng.trang new file mode 120000 index 0000000..14b5115 --- /dev/null +++ b/xsdtest.rng.trang @@ -0,0 +1 @@ +xsdtest.rng.expected
\ No newline at end of file |