Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4624424
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
26 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/python/pacemaker/_cts/cibxml.py b/python/pacemaker/_cts/cibxml.py
index 467a36ad3f..80b65c8b7e 100644
--- a/python/pacemaker/_cts/cibxml.py
+++ b/python/pacemaker/_cts/cibxml.py
@@ -1,391 +1,723 @@
-""" CIB XML generator for Pacemaker's Cluster Test Suite (CTS)
-"""
+""" CIB XML generator for Pacemaker's Cluster Test Suite (CTS) """
__all__ = [ "Alerts", "Clone", "Expression", "FencingTopology", "Group", "Nodes", "OpDefaults", "Option", "Resource", "Rule" ]
__copyright__ = "Copyright 2008-2023 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
def key_val_string(**kwargs):
""" Given keyword arguments as key=value pairs, construct a single string
containing all those pairs separated by spaces. This is suitable for
using in an XML element as a list of its attributes.
Any pairs that have value=None will be skipped.
Note that a dictionary can be passed to this function instead of kwargs
by using a construction like:
key_val_string(**{"a": 1, "b": 2})
"""
retval = ""
for (k, v) in kwargs.items():
if v is None:
continue
retval += ' %s="%s"' % (k, v)
return retval
def element(element_name, **kwargs):
""" Create an XML element string with the given element_name and attributes.
This element does not support having any children, so it will be closed
on the same line. The attributes are processed by key_val_string.
"""
return "<%s %s/>" % (element_name, key_val_string(**kwargs))
def containing_element(element_name, inner, **kwargs):
""" Like element, but surrounds some child text passed by the inner
parameter.
"""
attrs = key_val_string(**kwargs)
return "<%s %s>%s</%s>" % (element_name, attrs, inner, element_name)
class XmlBase:
+ """ A base class for deriving all kinds of XML sections in the CIB. This
+ class contains only the most basic operations common to all sections.
+ It is up to subclasses to provide most behavior.
+
+ Note that subclasses of this base class often have different sets of
+ arguments to their __init__ methods. In general this is not a great
+ practice, however it is so thoroughly used in these classes that trying
+ to straighten it out is likely to cause more bugs than just leaving it
+ alone for now.
+ """
+
def __init__(self, factory, tag, _id, **kwargs):
+ """ Create a new XmlBase instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ tag -- The XML element's start and end tag
+ _id -- A unique name for the element
+ kwargs -- Any additional key/value pairs that should be added to
+ this element as attributes
+ """
+
self._children = []
self._factory = factory
self._kwargs = kwargs
self._tag = tag
self.name = _id
def __repr__(self):
+ """ Return a short string description of this XML section """
+
return "%s-%s" % (self._tag, self.name)
def add_child(self, child):
+ """ Add an XML section as a child of this one """
+
self._children.append(child)
def __setitem__(self, key, value):
+ """ Add a key/value pair to this element, resulting in it becoming an
+ XML attribute. If value is None, remove the key.
+ """
+
if value:
self._kwargs[key] = value
else:
self._kwargs.pop(key, None)
def show(self):
+ """ Return a string representation of this XML section, including all
+ of its children
+ """
+
text = '''<%s''' % self._tag
if self.name:
text += ''' id="%s"''' % self.name
text += key_val_string(**self._kwargs)
if not self._children:
text += '''/>'''
return text
text += '''>'''
for c in self._children:
text += c.show()
text += '''</%s>''' % self._tag
return text
def _run(self, operation, xml, section, options=""):
+ """ Update the CIB on the cluster to include this XML section, including
+ all of its children
+
+ Arguments:
+
+ operation -- Whether this update is a "create" or "modify" operation
+ xml -- The XML to update the CIB with, typically the result
+ of calling show
+ section -- Which section of the CIB this update applies to (see
+ the --scope argument to cibadmin for allowed values)
+ options -- Extra options to pass to cibadmin
+ """
+
if self.name:
label = self.name
else:
label = "<%s>" % self._tag
self._factory.debug("Writing out %s" % label)
fixed = "HOME=/root CIB_file=%s" % self._factory.tmpfile
fixed += " cibadmin --%s --scope %s %s --xml-text '%s'" % (operation, section, options, xml)
(rc, _) = self._factory.rsh(self._factory.target, fixed)
if rc != 0:
raise RuntimeError("Configure call failed: %s" % fixed)
class InstanceAttributes(XmlBase):
- """ Create an <instance_attributes> section with name-value pairs """
+ """ A class that creates an <instance_attributes> XML section with
+ key/value pairs
+ """
- def __init__(self, factory, name, attrs):
- XmlBase.__init__(self, factory, "instance_attributes", name)
+ def __init__(self, factory, _id, attrs):
+ """ Create a new InstanceAttributes instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ attrs -- Key/value pairs to add as nvpair child elements
+ """
+
+ XmlBase.__init__(self, factory, "instance_attributes", _id)
# Create an <nvpair> for each attribute
for (attr, value) in attrs.items():
- self.add_child(XmlBase(factory, "nvpair", "%s-%s" % (name, attr),
+ self.add_child(XmlBase(factory, "nvpair", "%s-%s" % (_id, attr),
name=attr, value=value))
class Node(XmlBase):
- """ Create a <node> section with node attributes for one node """
+ """ A class that creates a <node> XML section for a single node, complete
+ with node attributes
+ """
def __init__(self, factory, node_name, node_id, node_attrs):
+ """ Create a new Node instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ node_name -- The value of the uname attribute for this node
+ node_id -- A unique name for the element
+ node_attrs -- Additional key/value pairs to set as instance
+ attributes for this node
+ """
+
XmlBase.__init__(self, factory, "node", node_id, uname=node_name)
self.add_child(InstanceAttributes(factory, "%s-1" % node_name, node_attrs))
class Nodes(XmlBase):
- """ Create a <nodes> section """
+ """ A class that creates a <nodes> XML section containing multiple Node
+ instances as children
+ """
def __init__(self, factory):
+ """ Create a new Nodes instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ """
+
XmlBase.__init__(self, factory, "nodes", None)
def add_node(self, node_name, node_id, node_attrs):
+ """ Add a child node element
+
+ Arguments:
+
+ node_name -- The value of the uname attribute for this node
+ node_id -- A unique name for the element
+ node_attrs -- Additional key/value pairs to set as instance
+ attributes for this node
+ """
+
self.add_child(Node(self._factory, node_name, node_id, node_attrs))
def commit(self):
+ """ Modify the CIB on the cluster to include this XML section """
+
self._run("modify", self.show(), "configuration", "--allow-create")
class FencingTopology(XmlBase):
+ """ A class that creates a <fencing-topology> XML section describing how
+ fencing is configured in the cluster
+ """
+
def __init__(self, factory):
+ """ Create a new FencingTopology instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ """
+
XmlBase.__init__(self, factory, "fencing-topology", None)
def level(self, index, target, devices, target_attr=None, target_value=None):
- # Generate XML ID (sanitizing target-by-attribute levels)
+ """ Generate a <fencing-level> XML element
+
+ index -- The order in which to attempt fencing-levels
+ (1 through 9). Levels are attempted in ascending
+ order until one succeeds.
+ target -- The name of a single node to which this level applies
+ devices -- A list of devices that must all be tried for this
+ level
+ target_attr -- The name of a node attribute that is set for nodes
+ to which this level applies
+ target_value -- The value of a node attribute that is set for nodes
+ to which this level applies
+ """
if target:
xml_id = "cts-%s.%d" % (target, index)
self.add_child(XmlBase(self._factory, "fencing-level", xml_id, target=target, index=index, devices=devices))
else:
xml_id = "%s-%s.%d" % (target_attr, target_value, index)
child = XmlBase(self._factory, "fencing-level", xml_id, index=index, devices=devices)
child["target-attribute"]=target_attr
child["target-value"]=target_value
self.add_child(child)
def commit(self):
+ """ Create this XML section in the CIB """
+
self._run("create", self.show(), "configuration", "--allow-create")
class Option(XmlBase):
- def __init__(self, factory, section="cib-bootstrap-options"):
- XmlBase.__init__(self, factory, "cluster_property_set", section)
+ """ A class that creates a <cluster_property_set> XML section of key/value
+ pairs for cluster-wide configuration settings
+ """
+
+ def __init__(self, factory, _id="cib-bootstrap-options"):
+ """ Create a new Option instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ """
+
+ XmlBase.__init__(self, factory, "cluster_property_set", _id)
def __setitem__(self, key, value):
+ """ Add a child nvpair element containing the given key/value pair """
+
self.add_child(XmlBase(self._factory, "nvpair", "cts-%s" % key, name=key, value=value))
def commit(self):
+ """ Modify the CIB on the cluster to include this XML section """
+
self._run("modify", self.show(), "crm_config", "--allow-create")
class OpDefaults(XmlBase):
+ """ A class that creates a <cts-op_defaults-meta> XML section of key/value
+ pairs for operation default settings
+ """
+
def __init__(self, factory):
+ """ Create a new OpDefaults instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ """
+
XmlBase.__init__(self, factory, "op_defaults", None)
self.meta = XmlBase(self._factory, "meta_attributes", "cts-op_defaults-meta")
self.add_child(self.meta)
def __setitem__(self, key, value):
+ """ Add a child nvpair meta_attribute element containing the given
+ key/value pair
+ """
+
self.meta.add_child(XmlBase(self._factory, "nvpair", "cts-op_defaults-%s" % key, name=key, value=value))
def commit(self):
+ """ Modify the CIB on the cluster to include this XML section """
+
self._run("modify", self.show(), "configuration", "--allow-create")
class Alerts(XmlBase):
+ """ A class that creates an <alerts> XML section """
+
def __init__(self, factory):
+ """ Create a new Alerts instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ """
+
XmlBase.__init__(self, factory, "alerts", None)
self._alert_count = 0
def add_alert(self, path, recipient):
+ """ Create a new alert as a child of this XML section
+
+ Arguments:
+
+ path -- The path to a script to be called when a cluster
+ event occurs
+ recipient -- An environment variable to be passed to the script
+ """
+
self._alert_count += 1
alert = XmlBase(self._factory, "alert", "alert-%d" % self._alert_count,
path=path)
recipient1 = XmlBase(self._factory, "recipient",
"alert-%d-recipient-1" % self._alert_count,
value=recipient)
alert.add_child(recipient1)
self.add_child(alert)
def commit(self):
+ """ Modify the CIB on the cluster to include this XML section """
+
self._run("modify", self.show(), "configuration", "--allow-create")
class Expression(XmlBase):
- def __init__(self, factory, name, attr, op, value=None):
- XmlBase.__init__(self, factory, "expression", name, attribute=attr, operation=op)
+ """ A class that creates an <expression> XML element as part of some
+ constraint rule
+ """
+
+ def __init__(self, factory, _id, attr, op, value=None):
+ """ Create a new Expression instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ attr -- The attribute to be tested
+ op -- The comparison to perform ("lt", "eq", "defined", etc.)
+ value -- Value for comparison (can be None for "defined" and
+ "not_defined" operations)
+ """
+
+ XmlBase.__init__(self, factory, "expression", _id, attribute=attr, operation=op)
if value:
self["value"] = value
class Rule(XmlBase):
- def __init__(self, factory, name, score, op="and", expr=None):
- XmlBase.__init__(self, factory, "rule", "%s" % name)
+ """ A class that creates a <rule> XML section consisting of one or more
+ expressions, as part of some constraint
+ """
+
+ def __init__(self, factory, _id, score, op="and", expr=None):
+ """ Create a new Rule instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ score -- If this rule is used in a location constraint and
+ evaluates to true, apply this score to the constraint
+ op -- If this rule contains more than one expression, use this
+ boolean op when evaluating
+ expr -- An Expression instance that can be added to this Rule
+ when it is created
+ """
+
+ XmlBase.__init__(self, factory, "rule", _id)
self["boolean-op"] = op
self["score"] = score
if expr:
self.add_child(expr)
class Resource(XmlBase):
- def __init__(self, factory, name, rtype, standard, provider=None):
- XmlBase.__init__(self, factory, "native", name)
+ """ A base class that creates all kinds of <resource> XML sections fully
+ describing a single cluster resource. This defaults to primitive
+ resources, but subclasses can create other types.
+ """
+
+ def __init__(self, factory, _id, rtype, standard, provider=None):
+ """ Create a new Resource instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ rtype -- The name of the resource agent
+ standard -- The standard the resource agent follows ("ocf",
+ "systemd", etc.)
+ provider -- The vendor providing the resource agent
+ """
+
+ XmlBase.__init__(self, factory, "native", _id)
self._provider = provider
self._rtype = rtype
self._standard = standard
self._meta = {}
self._op = []
self._param = {}
self._coloc = {}
self._needs = {}
self._scores = {}
if self._standard == "ocf" and not provider:
self._provider = "heartbeat"
elif self._standard == "lsb":
self._provider = None
def __setitem__(self, key, value):
+ """ Add a child nvpair element containing the given key/value pair as
+ an instance attribute
+ """
+
self._add_param(key, value)
- def add_op(self, name, interval, **kwargs):
- self._op.append(XmlBase(self._factory, "op", "%s-%s" % (name, interval),
- name=name, interval=interval, **kwargs))
+ def add_op(self, _id, interval, **kwargs):
+ """ Add an operation child XML element to this resource
+
+ Arguments:
+
+ _id -- A unique name for the element. Also, the action to
+ perform ("monitor", "start", "stop", etc.)
+ interval -- How frequently (in seconds) to perform the operation
+ kwargs -- Any additional key/value pairs that should be added to
+ this element as attributes
+ """
+
+ self._op.append(XmlBase(self._factory, "op", "%s-%s" % (_id, interval),
+ name=_id, interval=interval, **kwargs))
def _add_param(self, name, value):
+ """ Add a child nvpair element containing the given key/value pair as
+ an instance attribute
+ """
+
self._param[name] = value
def add_meta(self, name, value):
+ """ Add a child nvpair element containing the given key/value pair as
+ a meta attribute
+ """
+
self._meta[name] = value
def prefer(self, node, score="INFINITY", rule=None):
+ """ Add a location constraint where this resource prefers some node
+
+ Arguments:
+
+ node -- The name of the node to prefer
+ score -- Apply this score to the location constraint
+ rule -- A Rule instance to use in creating this constraint, instead
+ of creating a new rule
+ """
+
if not rule:
rule = Rule(self._factory, "prefer-%s-r" % node, score,
expr=Expression(self._factory, "prefer-%s-e" % node, "#uname", "eq", node))
self._scores[node] = rule
def after(self, resource, kind="Mandatory", first="start", then="start", **kwargs):
+ """ Create an ordering constraint between this resource and some other
+
+ Arguments:
+
+ resource -- The name of the dependent resource
+ kind -- How to enforce the constraint ("mandatory", "optional",
+ "serialize")
+ first -- The action that this resource must complete before the
+ then-action can be initiated for the dependent resource
+ ("start", "stop", "promote", "demote")
+ then -- The action that the dependent resource can execute only
+ after the first-action has completed (same values as
+ first)
+ kwargs -- Any additional key/value pairs that should be added to
+ this element as attributes
+ """
+
kargs = kwargs.copy()
kargs["kind"] = kind
if then:
kargs["first-action"] = "start"
kargs["then-action"] = then
if first:
kargs["first-action"] = first
self._needs[resource] = kargs
def colocate(self, resource, score="INFINITY", role=None, withrole=None, **kwargs):
+ """ Create a colocation constraint between this resource and some other
+
+ Arguments:
+
+ resource -- The name of the resource that should be located relative
+ this one
+ score -- Apply this score to the colocation constraint
+ role -- Apply this colocation constraint only to promotable clones
+ in this role ("started", "promoted", "unpromoted")
+ withrole -- Apply this colocation constraint only to with-rsc promotable
+ clones in this role
+ kwargs -- Any additional key/value pairs that should be added to
+ this element as attributes
+ """
+
kargs = kwargs.copy()
kargs["score"] = score
if role:
kargs["rsc-role"] = role
if withrole:
kargs["with-rsc-role"] = withrole
self._coloc[resource] = kargs
def _constraints(self):
+ """ Generate a <constraints> XML section containing all previously added
+ ordering and colocation constraints
+ """
+
text = "<constraints>"
for (k, v) in self._scores.items():
attrs = {"id": "prefer-%s" % k, "rsc": self.name}
text += containing_element("rsc_location", v.show(), **attrs)
for (k, kargs) in self._needs.items():
attrs = {"id": "%s-after-%s" % (self.name, k), "first": k, "then": self.name}
text += element("rsc_order", **attrs, **kargs)
for (k, kargs) in self._coloc.items():
attrs = {"id": "%s-with-%s" % (self.name, k), "rsc": self.name, "with-rsc": k}
text += element("rsc_colocation", **attrs)
text += "</constraints>"
return text
def show(self):
+ """ Return a string representation of this XML section, including all
+ of its children
+ """
+
text = '''<primitive id="%s" class="%s" type="%s"''' % (self.name, self._standard, self._rtype)
if self._provider:
text += ''' provider="%s"''' % self._provider
text += '''>'''
if len(self._meta) > 0:
nvpairs = ""
for (p, v) in self._meta.items():
attrs = {"id": "%s-%s" % (self.name, p), "name": p, "value": v}
nvpairs += element("nvpair", **attrs)
text += containing_element("meta_attributes", nvpairs,
id="%s-meta" % self.name)
if len(self._param) > 0:
nvpairs = ""
for (p, v) in self._param.items():
attrs = {"id": "%s-%s" % (self.name, p), "name": p, "value": v}
nvpairs += element("nvpair", **attrs)
text += containing_element("instance_attributes", nvpairs,
id="%s-params" % self.name)
if len(self._op) > 0:
text += '''<operations>'''
for o in self._op:
key = o.name
o.name = "%s-%s" % (self.name, key)
text += o.show()
o.name = key
text += '''</operations>'''
text += '''</primitive>'''
return text
def commit(self):
+ """ Modify the CIB on the cluster to include this XML section """
+
self._run("create", self.show(), "resources")
self._run("modify", self._constraints(), "constraints")
class Group(Resource):
- def __init__(self, factory, name):
- Resource.__init__(self, factory, name, None, None)
+ """ A specialized Resource subclass that creates a <group> XML section
+ describing a single group resource consisting of multiple child
+ primitive resources
+ """
+
+ def __init__(self, factory, _id):
+ """ Create a new Group instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ """
+
+ Resource.__init__(self, factory, _id, None, None)
self.tag = "group"
def __setitem__(self, key, value):
self.add_meta(key, value)
def show(self):
+ """ Return a string representation of this XML section, including all
+ of its children
+ """
+
text = '''<%s id="%s">''' % (self.tag, self.name)
if len(self._meta) > 0:
nvpairs = ""
for (p, v) in self._meta.items():
attrs = {"id": "%s-%s" % (self.name, p), "name": p, "value": v}
nvpairs += element("nvpair", **attrs)
text += containing_element("meta_attributes", nvpairs,
id="%s-meta" % self.name)
for c in self._children:
text += c.show()
text += '''</%s>''' % self.tag
return text
class Clone(Group):
- def __init__(self, factory, name, child=None):
- Group.__init__(self, factory, name)
+ """ A specialized Group subclass that creates a <clone> XML section
+ describing a clone resource containing multiple instances of a
+ single primitive resource
+ """
+
+ def __init__(self, factory, _id, child=None):
+ """ Create a new Clone instance
+
+ Arguments:
+
+ factory -- A CIB.ConfigFactory instance
+ _id -- A unique name for the element
+ child -- A Resource instance that can be added to this Clone
+ when it is created. Alternately, use add_child later.
+ Note that a Clone may only have one child.
+ """
+
+ Group.__init__(self, factory, _id)
self.tag = "clone"
if child:
self.add_child(child)
def add_child(self, child):
+ """ Add the given resource as a child of this Clone. Note that a
+ Clone resource only supports one child at a time.
+ """
+
if not self._children:
self._children.append(child)
else:
self._factory.log("Clones can only have a single child. Ignoring %s" % child.name)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:21 PM (17 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002597
Default Alt Text
(26 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment