Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions crmsh/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ def check_unsupported_resource_agents(handler: CheckResultHandler):
('* ' + ':'.join(x for x in resource_agent if x is not None) for resource_agent in class_unsupported_resource_agents)
)
_check_ocfs2(handler, cib)
_check_obseleted_sap_ascs_ers_mount(handler, cib)
_check_obsolete_sap_ascs_ers_ensa1(handler, cib)


def _check_saphana_resource_agent(handler: CheckResultHandler, resource_agents: typing.Iterable[cibquery.ResourceAgent]):
Expand Down Expand Up @@ -430,3 +432,102 @@ def _check_ocfs2(handler: CheckResultHandler, cib: lxml.etree.Element):
if cibquery.has_primitive_filesystem_with_fstype(cib, 'ocfs2'):
handler.handle_problem(False, 'OCFS2 is not supported in SLES 16. Please use GFS2.', [
])


def _check_obseleted_sap_ascs_ers_mount(handler: CheckResultHandler, cib: lxml.etree.Element):
"""
Checks for Filesystem resources that are part of a resource group with an
SAPInstance resource where IS_ERS=true, and the Filesystem resource starts
before the SAPInstance resource. This setup is not supported for ENSA2.
"""
# 1. Find all SAPInstance primitives with IS_ERS=true
ers_primitives = cib.xpath(
'/cib/configuration/resources//primitive[@class="ocf" and @provider="suse" and @type="SAPInstance"]'
'[instance_attributes/nvpair[@name="IS_ERS" and @value="true"]]'
)
if not ers_primitives:
return

# 2. Find all Filesystem primitive IDs
fs_ids = {
p.get('id') for p in cib.xpath('/cib/configuration/resources//primitive[@class="ocf" and @provider="heartbeat" and @type="Filesystem"]')
}
if not fs_ids:
return

# 3. Find all groups in the CIB
groups = cib.xpath('/cib/configuration/resources//group')
for group in groups:
group_id = group.get('id')
# Primitives returned by xpath are in document order
group_primitives_ids = [p.get('id') for p in group.xpath('.//primitive')]
group_primitive_id_set = set(group_primitives_ids)

# Check if the group contains both an ERS instance and a Filesystem resource
ers_in_group = [p for p in ers_primitives if p.get('id') in group_primitive_id_set]
if not ers_in_group:
continue
fs_in_group = [fs_id for fs_id in fs_ids if fs_id in group_primitive_id_set]
if not ers_in_group or not fs_in_group:
continue

# 4. Check ordering constraints for each pair
for ers_primitive in ers_in_group:
ers_id = ers_primitive.get('id')
for fs_id in fs_in_group:
ers_index = group_primitives_ids.index(ers_id)
fs_index = group_primitives_ids.index(fs_id)
if fs_index < ers_index:
# Implicit order is fs -> ers
handler.handle_problem(
False,
"Cluster-controlled filesystem setup for SAP ENSA2 is not supported in SLES 16. Please migrate to simple-mount setup.",
[f'* Filesystem resource "{fs_id}" is ordered to start before SAPInstance ERS resource "{ers_id}" in group "{group_id}".']
)


def _check_obsolete_sap_ascs_ers_ensa1(handler: CheckResultHandler, cib: lxml.etree.Element):
"""
Checks for obsolete SAP ASCS/ERS ENSA1 setups.

This setup is identified by the combination of:
1. An SAPInstance primitive with IS_ERS=true.
2. Another SAPInstance primitive for ASCS.
3. A location rule on the ASCS instance with score 2000 and an expression
like 'runs_ers_... eq 1'.
"""
ers_primitives = cib.xpath(
'/cib/configuration/resources//primitive[@class="ocf" and @provider="suse" and @type="SAPInstance"]'
'[instance_attributes/nvpair[@name="IS_ERS" and @value="true"]]'
)
if not ers_primitives:
return

ascs_primitives = cib.xpath(
'/cib/configuration/resources//primitive[@class="ocf" and @provider="suse" and @type="SAPInstance"]'
'[not(instance_attributes/nvpair[@name="IS_ERS" and @value="true"])]'
)
if not ascs_primitives:
return

for ascs_primitive in ascs_primitives:
ascs_id = ascs_primitive.get('id')
# Find location constraints for this ASCS resource that contain the specific rule structure.
# This xpath finds a location for the ascs_id, with a rule with score 2000,
# which has an expression child with the specified attributes.
xpath_expr = (
f"/cib/configuration/constraints/rsc_location[@rsc='{ascs_id}']/"
"rule[@score='2000']/"
"expression[@operation='eq' and starts-with(@attribute, 'runs_ers_') and @value='1']"
)
expressions = cib.xpath(xpath_expr)

if expressions:
# We found at least one matching expression, so the pattern is present.
expression_attribute = expressions[0].get('attribute')
handler.handle_problem(
False,
"Obsolete SAP ASCS/ERS ENSA1 setup detected. Please migrate to a simple-mount setup.",
[f'* SAPInstance resource "{ascs_id}" has a location constraint with a rule matching the ENSA1 pattern (attribute "{expression_attribute}").']
)
return
111 changes: 111 additions & 0 deletions test/unittests/test_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import unittest
from unittest import mock

import lxml.etree

from crmsh import migration, cibquery


Expand Down Expand Up @@ -66,3 +68,112 @@ def check_fn(x):
self._handler.handle_problem.reset_mock()
check_fn('foo 1.0.0')
self._handler.handle_problem.assert_called()


class TestCheckObsoletedSapAscsErsMount(unittest.TestCase):
XML_DATA = '''<?xml version="1.0" ?>
<cib crm_feature_set="3.16.1" validate-with="pacemaker-3.9" epoch="9" num_updates="0" admin_epoch="0" cib-last-written="Fri Sep 26 15:02:50 2025" update-origin="ha-1-1" update-client="cibadmin" update-user="root" have-quorum="1" dc-uuid="1">
<configuration>
<crm_config>
<cluster_property_set id="cib-bootstrap-options">
<nvpair id="cib-bootstrap-options-have-watchdog" name="have-watchdog" value="false"/>
<nvpair id="cib-bootstrap-options-dc-version" name="dc-version" value="2.1.5+20221208.a3f44794f-150500.4.9-2.1.5+20221208.a3f44794f"/>
<nvpair id="cib-bootstrap-options-cluster-infrastructure" name="cluster-infrastructure" value="corosync"/>
<nvpair id="cib-bootstrap-options-cluster-name" name="cluster-name" value="hacluster"/>
<nvpair name="stonith-enabled" value="false" id="cib-bootstrap-options-stonith-enabled"/>
</cluster_property_set>
</crm_config>
<nodes>
<node id="1" uname="ha-1-1"/>
</nodes>
<resources>
<group id="ers_group">
<primitive id="fs_ers" class="ocf" provider="heartbeat" type="Filesystem">
<instance_attributes id="fs_ers-instance_attributes">
<nvpair name="device" value="tmpfs" id="fs_ers-instance_attributes-device"/>
<nvpair name="directory" value="/mnt/dummy" id="fs_ers-instance_attributes-directory"/>
<nvpair name="fstype" value="tmpfs" id="fs_ers-instance_attributes-fstype"/>
</instance_attributes>
</primitive>
<primitive id="ers_instance" class="ocf" provider="suse" type="SAPInstance">
<instance_attributes id="ers_instance-instance_attributes">
<nvpair name="IS_ERS" value="true" id="ers_instance-instance_attributes-IS_ERS"/>
</instance_attributes>
</primitive>
</group>
</resources>
<rsc_defaults>
<meta_attributes id="build-resource-defaults">
<nvpair id="build-resource-stickiness" name="resource-stickiness" value="1"/>
<nvpair name="migration-threshold" value="3" id="rsc-options-migration-threshold"/>
</meta_attributes>
</rsc_defaults>
<op_defaults>
<meta_attributes id="op-options">
<nvpair name="timeout" value="600" id="op-options-timeout"/>
<nvpair name="record-pending" value="true" id="op-options-record-pending"/>
</meta_attributes>
</op_defaults>
</configuration>
</cib>
'''

def setUp(self):
self.handler = mock.Mock(migration.CheckResultHandler)
self.cib = lxml.etree.fromstring(self.XML_DATA)

def test_check_obseleted_sap_ascs_ers_mount(self):
migration._check_obseleted_sap_ascs_ers_mount(self.handler, self.cib)
self.handler.handle_problem.assert_called_once_with(
False,
"Cluster-controlled filesystem setup for SAP ENSA2 is not supported in SLES 16. Please migrate to simple-mount setup.",
['* Filesystem resource "fs_ers" is ordered to start before SAPInstance ERS resource "ers_instance" in group "ers_group".']
)


class TestCheckObsoleteSapAscsErsEnsa1(unittest.TestCase):
XML_DATA = '''<?xml version="1.0" ?>
<cib crm_feature_set="3.16.1" validate-with="pacemaker-3.9" epoch="9" num_updates="0" admin_epoch="0" cib-last-written="Fri Sep 26 15:02:50 2025" update-origin="ha-1-1" update-client="cibadmin" update-user="root" have-quorum="1" dc-uuid="1">
<configuration>
<crm_config>
<cluster_property_set id="cib-bootstrap-options">
<nvpair id="cib-bootstrap-options-have-watchdog" name="have-watchdog" value="false"/>
<nvpair id="cib-bootstrap-options-dc-version" name="dc-version" value="2.1.5+20221208.a3f44794f-150500.4.9-2.1.5+20221208.a3f44794f"/>
<nvpair id="cib-bootstrap-options-cluster-infrastructure" name="cluster-infrastructure" value="corosync"/>
<nvpair id="cib-bootstrap-options-cluster-name" name="cluster-name" value="hacluster"/>
<nvpair name="stonith-enabled" value="false" id="cib-bootstrap-options-stonith-enabled"/>
</cluster_property_set>
</crm_config>
<nodes>
<node id="1" uname="ha-1-1"/>
</nodes>
<resources>
<primitive id="ascs_instance" class="ocf" provider="suse" type="SAPInstance" />
<primitive id="ers_instance" class="ocf" provider="suse" type="SAPInstance">
<instance_attributes id="ers_instance-instance_attributes">
<nvpair name="IS_ERS" value="true" id="ers_instance-instance_attributes-IS_ERS"/>
</instance_attributes>
</primitive>
</resources>
<constraints>
<rsc_location id="loc_failover_to_ers" rsc="ascs_instance">
<rule score="2000" id="loc_failover_to_ers-rule">
<expression operation="eq" attribute="runs_ers_ers_instance" value="1" id="loc_failover_to_ers-rule-expression"/>
</rule>
</rsc_location>
</constraints>
</configuration>
</cib>
'''

def setUp(self):
self.handler = mock.Mock(migration.CheckResultHandler)
self.cib = lxml.etree.fromstring(self.XML_DATA)

def test_check_obsolete_sap_ascs_ers_ensa1(self):
migration._check_obsolete_sap_ascs_ers_ensa1(self.handler, self.cib)
self.handler.handle_problem.assert_called_once_with(
False,
"Obsolete SAP ASCS/ERS ENSA1 setup detected. Please migrate to a simple-mount setup.",
['* SAPInstance resource "ascs_instance" has a location constraint with a rule matching the ENSA1 pattern (attribute "runs_ers_ers_instance").']
)