diff options
Diffstat (limited to 'cts/lab/CIB.py')
-rw-r--r-- | cts/lab/CIB.py | 518 |
1 files changed, 0 insertions, 518 deletions
diff --git a/cts/lab/CIB.py b/cts/lab/CIB.py deleted file mode 100644 index 5981654..0000000 --- a/cts/lab/CIB.py +++ /dev/null @@ -1,518 +0,0 @@ -""" CIB generator for Pacemaker's Cluster Test Suite (CTS) -""" - -__copyright__ = "Copyright 2008-2023 the Pacemaker project contributors" -__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY" - -import os -import warnings -import tempfile - -from pacemaker.buildoptions import BuildOptions -from pacemaker._cts.CTS import CtsLab - - -class CibBase(object): - def __init__(self, Factory, tag, _id, **kwargs): - self.tag = tag - self.name = _id - self.kwargs = kwargs - self.children = [] - self.Factory = Factory - - def __repr__(self): - return "%s-%s" % (self.tag, self.name) - - def add_child(self, child): - self.children.append(child) - - def __setitem__(self, key, value): - if value: - self.kwargs[key] = value - else: - self.kwargs.pop(key, None) - -from cts.cib_xml import * - - -class ConfigBase(object): - cts_cib = None - version = "unknown" - Factory = None - - def __init__(self, CM, factory, tmpfile=None): - self.CM = CM - self.Factory = factory - - if not tmpfile: - warnings.filterwarnings("ignore") - f=tempfile.NamedTemporaryFile(delete=True) - f.close() - tmpfile = f.name - warnings.resetwarnings() - - self.Factory.tmpfile = tmpfile - - def version(self): - return self.version - - def NextIP(self): - ip = self.CM.Env["IPBase"] - if ":" in ip: - (prefix, sep, suffix) = ip.rpartition(":") - suffix = str(hex(int(suffix, 16)+1)).lstrip("0x") - else: - (prefix, sep, suffix) = ip.rpartition(".") - suffix = str(int(suffix)+1) - - ip = prefix + sep + suffix - self.CM.Env["IPBase"] = ip - return ip.strip() - - -class CIB12(ConfigBase): - version = "pacemaker-1.2" - counter = 1 - - def _show(self, command=""): - output = "" - (_, result) = self.Factory.rsh(self.Factory.target, "HOME=/root CIB_file="+self.Factory.tmpfile+" cibadmin -Ql "+command, verbose=1) - for line in result: - output += line - self.Factory.debug("Generated Config: "+line) - return output - - def NewIP(self, name=None, standard="ocf"): - if self.CM.Env["IPagent"] == "IPaddr2": - ip = self.NextIP() - if not name: - if ":" in ip: - (prefix, sep, suffix) = ip.rpartition(":") - name = "r"+suffix - else: - name = "r"+ip - - r = Resource(self.Factory, name, self.CM.Env["IPagent"], standard) - r["ip"] = ip - - if ":" in ip: - r["cidr_netmask"] = "64" - r["nic"] = "eth0" - else: - r["cidr_netmask"] = "32" - - else: - if not name: - name = "r%s%d" % (self.CM.Env["IPagent"], self.counter) - self.counter = self.counter + 1 - r = Resource(self.Factory, name, self.CM.Env["IPagent"], standard) - - r.add_op("monitor", "5s") - return r - - def get_node_id(self, node_name): - """ Check the cluster configuration for a node ID. """ - - # We can't account for every possible configuration, - # so we only return a node ID if: - # * The node is specified in /etc/corosync/corosync.conf - # with "ring0_addr:" equal to node_name and "nodeid:" - # explicitly specified. - # In all other cases, we return 0. - node_id = 0 - - # awkward command: use } as record separator - # so each corosync.conf "object" is one record; - # match the "node {" record that has "ring0_addr: node_name"; - # then print the substring of that record after "nodeid:" - (rc, output) = self.Factory.rsh(self.Factory.target, - r"""awk -v RS="}" """ - r"""'/^(\s*nodelist\s*{)?\s*node\s*{.*(ring0_addr|name):\s*%s(\s+|$)/""" - r"""{gsub(/.*nodeid:\s*/,"");gsub(/\s+.*$/,"");print}' %s""" - % (node_name, BuildOptions.COROSYNC_CONFIG_FILE), verbose=1) - - if rc == 0 and len(output) == 1: - try: - node_id = int(output[0]) - except ValueError: - node_id = 0 - - return node_id - - def install(self, target): - old = self.Factory.tmpfile - - # Force a rebuild - self.cts_cib = None - - self.Factory.tmpfile = BuildOptions.CIB_DIR + "/cib.xml" - self.contents(target) - self.Factory.rsh(self.Factory.target, "chown " + BuildOptions.DAEMON_USER + " " + self.Factory.tmpfile) - - self.Factory.tmpfile = old - - def contents(self, target=None): - # fencing resource - if self.cts_cib: - return self.cts_cib - - if target: - self.Factory.target = target - - self.Factory.rsh(self.Factory.target, "HOME=/root cibadmin --empty %s > %s" % (self.version, self.Factory.tmpfile)) - self.num_nodes = len(self.CM.Env["nodes"]) - - no_quorum = "stop" - if self.num_nodes < 3: - no_quorum = "ignore" - self.Factory.log("Cluster only has %d nodes, configuring: no-quorum-policy=ignore" % self.num_nodes) - - # We don't need a nodes section unless we add attributes - stn = None - - # Fencing resource - # Define first so that the shell doesn't reject every update - if self.CM.Env["DoFencing"]: - - # Define the "real" fencing device - st = Resource(self.Factory, "Fencing", ""+self.CM.Env["stonith-type"], "stonith") - - # Set a threshold for unreliable stonith devices such as the vmware one - st.add_meta("migration-threshold", "5") - st.add_op("monitor", "120s", timeout="120s") - st.add_op("stop", "0", timeout="60s") - st.add_op("start", "0", timeout="60s") - - # For remote node tests, a cluster node is stopped and brought back up - # as a remote node with the name "remote-OLDNAME". To allow fencing - # devices to fence these nodes, create a list of all possible node names. - all_node_names = [ prefix+n for n in self.CM.Env["nodes"] for prefix in ('', 'remote-') ] - - # Add all parameters specified by user - entries = self.CM.Env["stonith-params"].split(',') - for entry in entries: - try: - (name, value) = entry.split('=', 1) - except ValueError: - print("Warning: skipping invalid fencing parameter: %s" % entry) - continue - - # Allow user to specify "all" as the node list, and expand it here - if name in [ "hostlist", "pcmk_host_list" ] and value == "all": - value = ' '.join(all_node_names) - - st[name] = value - - st.commit() - - # Test advanced fencing logic - if True: - stf_nodes = [] - stt_nodes = [] - attr_nodes = {} - - # Create the levels - stl = FencingTopology(self.Factory) - for node in self.CM.Env["nodes"]: - # Remote node tests will rename the node - remote_node = "remote-" + node - - # Randomly assign node to a fencing method - ftype = self.CM.Env.random_gen.choice(["levels-and", "levels-or ", "broadcast "]) - - # For levels-and, randomly choose targeting by node name or attribute - by = "" - if ftype == "levels-and": - node_id = self.get_node_id(node) - if node_id == 0 or self.CM.Env.random_gen.choice([True, False]): - by = " (by name)" - else: - attr_nodes[node] = node_id - by = " (by attribute)" - - self.CM.log(" - Using %s fencing for node: %s%s" % (ftype, node, by)) - - if ftype == "levels-and": - # If targeting by name, add a topology level for this node - if node not in attr_nodes: - stl.level(1, node, "FencingPass,Fencing") - - # Always target remote nodes by name, otherwise we would need to add - # an attribute to the remote node only during remote tests (we don't - # want nonexistent remote nodes showing up in the non-remote tests). - # That complexity is not worth the effort. - stl.level(1, remote_node, "FencingPass,Fencing") - - # Add the node (and its remote equivalent) to the list of levels-and nodes. - stt_nodes.extend([node, remote_node]) - - elif ftype == "levels-or ": - for n in [ node, remote_node ]: - stl.level(1, n, "FencingFail") - stl.level(2, n, "Fencing") - stf_nodes.extend([node, remote_node]) - - # If any levels-and nodes were targeted by attribute, - # create the attributes and a level for the attribute. - if attr_nodes: - stn = Nodes(self.Factory) - for (node_name, node_id) in list(attr_nodes.items()): - stn.add_node(node_name, node_id, { "cts-fencing" : "levels-and" }) - stl.level(1, None, "FencingPass,Fencing", "cts-fencing", "levels-and") - - # Create a Dummy agent that always passes for levels-and - if len(stt_nodes): - stt = Resource(self.Factory, "FencingPass", "fence_dummy", "stonith") - stt["pcmk_host_list"] = " ".join(stt_nodes) - # Wait this many seconds before doing anything, handy for letting disks get flushed too - stt["random_sleep_range"] = "30" - stt["mode"] = "pass" - stt.commit() - - # Create a Dummy agent that always fails for levels-or - if len(stf_nodes): - stf = Resource(self.Factory, "FencingFail", "fence_dummy", "stonith") - stf["pcmk_host_list"] = " ".join(stf_nodes) - # Wait this many seconds before doing anything, handy for letting disks get flushed too - stf["random_sleep_range"] = "30" - stf["mode"] = "fail" - stf.commit() - - # Now commit the levels themselves - stl.commit() - - o = Option(self.Factory) - o["stonith-enabled"] = self.CM.Env["DoFencing"] - o["start-failure-is-fatal"] = "false" - o["pe-input-series-max"] = "5000" - o["shutdown-escalation"] = "5min" - o["batch-limit"] = "10" - o["dc-deadtime"] = "5s" - o["no-quorum-policy"] = no_quorum - - if self.CM.Env["DoBSC"]: - o["ident-string"] = "Linux-HA TEST configuration file - REMOVEME!!" - - o.commit() - - o = OpDefaults(self.Factory) - o["timeout"] = "90s" - o.commit() - - # Commit the nodes section if we defined one - if stn is not None: - stn.commit() - - # Add an alerts section if possible - if self.Factory.rsh.exists_on_all(self.CM.Env["notification-agent"], self.CM.Env["nodes"]): - alerts = Alerts(self.Factory) - alerts.add_alert(self.CM.Env["notification-agent"], - self.CM.Env["notification-recipient"]) - alerts.commit() - - # Add resources? - if self.CM.Env["CIBResource"]: - self.add_resources() - - if self.CM.cluster_monitor == 1: - mon = Resource(self.Factory, "cluster_mon", "ocf", "ClusterMon", "pacemaker") - mon.add_op("start", "0", requires="nothing") - mon.add_op("monitor", "5s", requires="nothing") - mon["update"] = "10" - mon["extra_options"] = "-r -n" - mon["user"] = "abeekhof" - mon["htmlfile"] = "/suse/abeekhof/Export/cluster.html" - mon.commit() - - #self._create('''location prefer-dc cluster_mon rule -INFINITY: \#is_dc eq false''') - - # generate cib - self.cts_cib = self._show() - - if self.Factory.tmpfile != BuildOptions.CIB_DIR + "/cib.xml": - self.Factory.rsh(self.Factory.target, "rm -f "+self.Factory.tmpfile) - - return self.cts_cib - - def add_resources(self): - # Per-node resources - for node in self.CM.Env["nodes"]: - name = "rsc_"+node - r = self.NewIP(name) - r.prefer(node, "100") - r.commit() - - # Migrator - # Make this slightly sticky (since we have no other location constraints) to avoid relocation during Reattach - m = Resource(self.Factory, "migrator","Dummy", "ocf", "pacemaker") - m["passwd"] = "whatever" - m.add_meta("resource-stickiness","1") - m.add_meta("allow-migrate", "1") - m.add_op("monitor", "P10S") - m.commit() - - # Ping the test exerciser - p = Resource(self.Factory, "ping-1","ping", "ocf", "pacemaker") - p.add_op("monitor", "60s") - p["host_list"] = self.CM.Env["cts-exerciser"] - p["name"] = "connected" - p["debug"] = "true" - - c = Clone(self.Factory, "Connectivity", p) - c["globally-unique"] = "false" - c.commit() - - # promotable clone resource - s = Resource(self.Factory, "stateful-1", "Stateful", "ocf", "pacemaker") - s.add_op("monitor", "15s", timeout="60s") - s.add_op("monitor", "16s", timeout="60s", role="Promoted") - ms = Clone(self.Factory, "promotable-1", s) - ms["promotable"] = "true" - ms["clone-max"] = self.num_nodes - ms["clone-node-max"] = 1 - ms["promoted-max"] = 1 - ms["promoted-node-max"] = 1 - - # Require connectivity to run the promotable clone - r = Rule(self.Factory, "connected", "-INFINITY", op="or") - r.add_child(Expression(self.Factory, "m1-connected-1", "connected", "lt", "1")) - r.add_child(Expression(self.Factory, "m1-connected-2", "connected", "not_defined", None)) - ms.prefer("connected", rule=r) - - ms.commit() - - # Group Resource - g = Group(self.Factory, "group-1") - g.add_child(self.NewIP()) - - if self.CM.Env["have_systemd"]: - sysd = Resource(self.Factory, "petulant", - "pacemaker-cts-dummyd@10", "service") - sysd.add_op("monitor", "P10S") - g.add_child(sysd) - else: - g.add_child(self.NewIP()) - - g.add_child(self.NewIP()) - - # Make group depend on the promotable clone - g.after("promotable-1", first="promote", then="start") - g.colocate("promotable-1", "INFINITY", withrole="Promoted") - - g.commit() - - # LSB resource - lsb = Resource(self.Factory, "lsb-dummy", "LSBDummy", "lsb") - lsb.add_op("monitor", "5s") - - # LSB with group - lsb.after("group-1") - lsb.colocate("group-1") - - lsb.commit() - - -class CIB20(CIB12): - version = "pacemaker-2.5" - -class CIB30(CIB12): - version = "pacemaker-3.7" - -#class HASI(CIB10): -# def add_resources(self): -# # DLM resource -# self._create('''primitive dlm ocf:pacemaker:controld op monitor interval=120s''') -# self._create('''clone dlm-clone dlm meta globally-unique=false interleave=true''') - - # O2CB resource -# self._create('''primitive o2cb ocf:ocfs2:o2cb op monitor interval=120s''') -# self._create('''clone o2cb-clone o2cb meta globally-unique=false interleave=true''') -# self._create('''colocation o2cb-with-dlm INFINITY: o2cb-clone dlm-clone''') -# self._create('''order start-o2cb-after-dlm mandatory: dlm-clone o2cb-clone''') - - -class ConfigFactory(object): - def __init__(self, CM): - self.CM = CM - self.rsh = self.CM.rsh - self.register("pacemaker12", CIB12, CM, self) - self.register("pacemaker20", CIB20, CM, self) - self.register("pacemaker30", CIB30, CM, self) -# self.register("hae", HASI, CM, self) - if not self.CM.Env["ListTests"]: - self.target = self.CM.Env["nodes"][0] - self.tmpfile = None - - def log(self, args): - self.CM.log("cib: %s" % args) - - def debug(self, args): - self.CM.debug("cib: %s" % args) - - def register(self, methodName, constructor, *args, **kargs): - """register a constructor""" - _args = [constructor] - _args.extend(args) - setattr(self, methodName, ConfigFactoryItem(*_args, **kargs)) - - def unregister(self, methodName): - """unregister a constructor""" - delattr(self, methodName) - - def createConfig(self, name="pacemaker-1.0"): - if name == "pacemaker-1.0": - name = "pacemaker10"; - elif name == "pacemaker-1.2": - name = "pacemaker12"; - elif name == "pacemaker-2.0": - name = "pacemaker20"; - elif name.startswith("pacemaker-3."): - name = "pacemaker30"; - elif name == "hasi": - name = "hae"; - - if hasattr(self, name): - return getattr(self, name)() - else: - self.CM.log("Configuration variant '%s' is unknown. Defaulting to latest config" % name) - - return self.pacemaker30() - - -class ConfigFactoryItem(object): - def __init__(self, function, *args, **kargs): - self._function = function - self._args = args - self._kargs = kargs - - def __call__(self, *args, **kargs): - """call function""" - _args = list(self._args) - _args.extend(args) - _kargs = self._kargs.copy() - _kargs.update(kargs) - return self._function(*_args,**_kargs) - -if __name__ == '__main__': - """ Unit test (pass cluster node names as command line arguments) """ - - import cts.CM_corosync - import sys - - if len(sys.argv) < 2: - print("Usage: %s <node> ..." % sys.argv[0]) - sys.exit(1) - - args = [ - "--nodes", " ".join(sys.argv[1:]), - "--clobber-cib", - "--populate-resources", - "--stack", "corosync", - "--test-ip-base", "fe80::1234:56:7890:1000", - "--stonith", "rhcs", - ] - env = CtsLab(args) - cm = CM_corosync.crm_corosync() - CibFactory = ConfigFactory(cm) - cib = CibFactory.createConfig("pacemaker-3.0") - print(cib.contents()) |