Page MenuHomeClusterLabs Projects

No OneTemporary

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

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:21 PM (12 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002597
Default Alt Text
(26 KB)

Event Timeline