import Oracle_OSS resource-agents-4.10.0-111.el9_8.7

This commit is contained in:
AlmaLinux RelEng Bot 2026-07-20 19:59:18 -04:00
parent 94a2d6e30d
commit 21d0c04053
3 changed files with 310 additions and 1 deletions

View File

@ -0,0 +1,214 @@
From 1ce0c683b5eee74d1aee4a7d6f27ee9cb9e322df Mon Sep 17 00:00:00 2001
From: Jeremy Poulin <jpoulin@redhat.com>
Date: Tue, 16 Jun 2026 11:06:17 -0400
Subject: [PATCH] OCPBUGS-84695: fix(podman-etcd): single-node crash recovery
and hardened detection
Fix single-node etcd containers staying down permanently after crashes by
adding recovery logic and hardening cluster topology detection.
Problem:
When a single-node etcd container crashes, container_health_check() returns
"failed-wait-for-peer" which maps to OCF_SUCCESS in monitor. Pacemaker thinks
the resource is healthy while etcd remains down indefinitely because there is
no peer to initiate recovery via force_new_cluster.
Changes:
Single-node crash recovery:
- Detect single-node topology during container failure in container_health_check()
- Set standalone_node attribute to ensure proper single-node startup on restart
- Return "failed-restart-now" to trigger immediate Pacemaker restart
- Fixes PR review feedback: ClusterLabs/resource-agents#2155 (#r3466461583, #r3473146886)
Hardened topology detection:
- Add is_single_node() helper to detect cluster topology from node_ip_map
- Use exact field matching to avoid false matches in node names
- Add defensive checks for invalid node_ip_map states (empty, malformed)
- Fail-safe behavior: ambiguous states default to multi-node mode
- Add verbose logging for all topology decisions
Enhanced single-node support:
- Handle single-node clusters in peer management (skip peer operations, clean up stale learners)
- Skip revision comparison during single-node startup (no peer to compare with)
- Maintain standalone_node attribute correctly across cluster topology changes
- Document standalone_node behavior as persistent marker for force_new_cluster decisions
Improved observability:
- Log topology detection decisions with context (node_ip_map contents, peer counts)
- Log invalid states with details for troubleshooting
- Add comments explaining standalone_node and force_new_cluster interaction
Fixes: single-node container crash recovery, single-node cluster startup and operation
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---
heartbeat/podman-etcd | 123 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 116 insertions(+), 7 deletions(-)
diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd
index 0195633af..bad94aa44 100755
--- a/heartbeat/podman-etcd
+++ b/heartbeat/podman-etcd
@@ -1267,6 +1267,41 @@ clear_standalone_node()
crm_attribute --name "standalone_node" --delete
}
+is_single_node()
+{
+ # Check if this is a single-node cluster by examining node_ip_map
+ # Returns 0 (true) if only one node is configured, 1 (false) otherwise
+ # When state is ambiguous or invalid, falls back to multi-node mode (safer)
+ local peer_count node_count
+
+ # Empty node_ip_map - cannot determine topology, fall back to multi-node
+ if [ -z "$OCF_RESKEY_node_ip_map" ]; then
+ ocf_log warn "node_ip_map is empty - cannot determine cluster topology, assuming multi-node operation"
+ return 1
+ fi
+
+ # Check if our node is in the map - if not, we're in an invalid state
+ # Use awk to match node name field exactly (before first colon) to avoid substring matches
+ if ! printf "%s\n" "$OCF_RESKEY_node_ip_map" | tr ';' '\n' | awk -F: -v node="$NODENAME" '$1 == node { found=1 } END { exit !found }'; then
+ ocf_log warn "node $NODENAME not found in node_ip_map ('$OCF_RESKEY_node_ip_map') - invalid state, assuming multi-node operation"
+ return 1
+ fi
+
+ # Count total nodes and peers
+ # Use awk to count entries and match node name field exactly (prevents substring matches like "node1" matching "node10")
+ node_count=$(printf "%s\n" "$OCF_RESKEY_node_ip_map" | tr ';' '\n' | awk 'NF { count++ } END { print count+0 }')
+ peer_count=$(printf "%s\n" "$OCF_RESKEY_node_ip_map" | tr ';' '\n' | awk -F: -v node="$NODENAME" 'NF && $1 != node { count++ } END { print count+0 }')
+
+ # Determine topology based on peer count
+ if [ "$peer_count" -eq 0 ]; then
+ ocf_log debug "Single-node cluster detected (node_count: $node_count, node_ip_map: '$OCF_RESKEY_node_ip_map')"
+ return 0
+ else
+ ocf_log debug "Multi-node cluster detected (node_count: $node_count, peers: $peer_count, node_ip_map: '$OCF_RESKEY_node_ip_map')"
+ return 1
+ fi
+}
+
# Promotes an etcd learner member to a voting member
# Args: $1 - learner member ID in decimal format
@@ -1538,6 +1573,39 @@ manage_peer_membership()
local peer_member_ip
local peer_member_id
+ # Check if this is a single-node cluster
+ if is_single_node; then
+ # Single-node cluster - no peer to manage
+ ocf_log info "Single-node cluster detected (node_ip_map: '$OCF_RESKEY_node_ip_map')"
+
+ # Clean up any learners from the member list (could be left over from 2→1 transition)
+ local learner_member_id
+ learner_member_id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .isLearner==true ).ID")
+ if [ -n "$learner_member_id" ]; then
+ ocf_log warn "Single-node cluster has learner member $learner_member_id - removing to ensure clean state"
+ local learner_member_id_hex
+ if learner_member_id_hex=$(decimal_to_hex "$learner_member_id"); then
+ local endpoint_url
+ endpoint_url=$(ip_url $(attribute_node_ip get))
+ if podman exec "${CONTAINER}" etcdctl --endpoints="$endpoint_url:2379" member remove "$learner_member_id_hex" 2>&1; then
+ ocf_log info "Successfully removed learner member $learner_member_id_hex"
+ attribute_learner_node clear
+ else
+ ocf_log warn "Failed to remove learner member $learner_member_id_hex"
+ fi
+ else
+ ocf_log err "Could not convert learner member ID to hex: $learner_member_id"
+ fi
+ fi
+
+ # Ensure standalone_node is set (may have been cleared during 2-node operation)
+ if ! is_standalone; then
+ ocf_log info "Setting standalone_node for single-node cluster"
+ set_standalone_node
+ fi
+ return $OCF_SUCCESS
+ fi
+
# Get peer node name and IP
peer_ip_map_entry=$(echo "$OCF_RESKEY_node_ip_map" | tr ';' '\n' | grep -vF "$NODENAME")
if [ -z "$peer_ip_map_entry" ]; then
@@ -1733,6 +1801,18 @@ container_health_check()
return
fi
+ # Check if this is a single-node cluster - no peer available for recovery
+ if is_single_node; then
+ ocf_log warn "Single-node cluster detected with failed container - no peer available for recovery"
+ # Set standalone_node to ensure proper single-node startup on restart
+ if ! set_standalone_node; then
+ ocf_log err "Failed to set standalone_node attribute, error code: $?"
+ fi
+ # Trigger immediate restart instead of waiting for non-existent peer
+ echo "failed-restart-now"
+ return
+ fi
+
echo "failed-wait-for-peer"
}
@@ -2144,16 +2224,39 @@ podman_start()
start_resources_count=$(echo "$OCF_RESKEY_CRM_meta_notify_start_resource" | wc -w)
ocf_log info "found '$start_resources_count' starting etcd resources (meta notify environment variable: '$OCF_RESKEY_CRM_meta_notify_start_resource')"
- # we need to compare the revisions in any of the following branches
- # so call the function only once here
- if ! revision_compare_result=$(compare_revision); then
- ocf_log err "could not compare revisions, error code: $?"
- return "$OCF_ERR_GENERIC"
+ # Standalone node doesn't need peer comparison (matches active_resources_count=1 logic)
+ # Check this before is_single_node to handle drift reconciliation where standalone_node
+ # is set but node_ip_map may already include the peer being added
+ if is_standalone; then
+ ocf_log info "standalone node: skip peer comparison during recovery"
+ # Skip peer comparison - standalone_node is a persistent marker indicating we should
+ # force a new cluster on restart (set during quorum loss or single-node operation).
+ # revision_compare_result is intentionally left unset here - the is_single_node check
+ # in the start_resources_count case below will differentiate:
+ # - True single-node: start normally without force_new_cluster
+ # - Multi-node with standalone marker: set force_new_cluster (cold start after quorum loss)
+ # Note: This only forces new cluster when start_resources_count=1 (cold start).
+ # If active_resources_count=1 (peer already running), the new node joins as learner
+ # without force_new_cluster (handled in active_resources_count=1 case above).
+ elif is_single_node; then
+ ocf_log info "Single-node cluster: starting normally without revision comparison"
+ # No need to set force_new_cluster or compare revisions
+ else
+ # we need to compare the revisions in any of the following branches
+ # so call the function only once here
+ if ! revision_compare_result=$(compare_revision); then
+ ocf_log err "could not compare revisions, error code: $?"
+ return "$OCF_ERR_GENERIC"
+ fi
fi
case "$start_resources_count" in
1)
ocf_log debug "peer not starting: ensure we can start a new cluster"
- if [ "$revision_compare_result" != "older" ]; then
+ if is_single_node; then
+ # Single-node cluster: start normally without revision comparison
+ ocf_log info "Single-node cluster: starting normally"
+ set_standalone_node
+ elif [ "$revision_compare_result" != "older" ]; then
# If our revision is the same as or newer than the peer's last saved
# revision, and the peer agent isn't currently starting, we can
# restore e-quorum by forcing a new cluster.
@@ -2358,7 +2461,13 @@ podman_start()
fi
set_standalone_node
else
- ocf_log err "could not add peer as learner (peer node name: ${peer_node_name:-unknown}, peer ip: ${peer_node_ip:-unknown})"
+ # Check if this is a single-node cluster
+ if is_single_node; then
+ ocf_log info "Single-node cluster: setting standalone_node"
+ set_standalone_node
+ else
+ ocf_log err "could not add peer as learner (peer node name: ${peer_node_name:-unknown}, peer ip: ${peer_node_ip:-unknown})"
+ fi
fi
fi
return $OCF_SUCCESS

View File

@ -0,0 +1,81 @@
From fd1c9a1cfd4463dad843f666ad951e6616f1ccf0 Mon Sep 17 00:00:00 2001
From: Guilherme Felix <fguilher@amazon.com>
Date: Mon, 23 Mar 2026 19:22:49 +0000
Subject: [PATCH 1/2] aws-vpc-move-ip: Add awscli_timeout option
---
heartbeat/aws-vpc-move-ip | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/heartbeat/aws-vpc-move-ip b/heartbeat/aws-vpc-move-ip
index 2afc0ba537..3e798173f5 100755
--- a/heartbeat/aws-vpc-move-ip
+++ b/heartbeat/aws-vpc-move-ip
@@ -48,6 +48,7 @@ OCF_RESKEY_interface_default="eth0"
OCF_RESKEY_iflabel_default=""
OCF_RESKEY_monapi_default="false"
OCF_RESKEY_lookup_type_default="InstanceId"
+OCF_RESKEY_awscli_timeout_default=""
: ${OCF_RESKEY_awscli=${OCF_RESKEY_awscli_default}}
: ${OCF_RESKEY_auth_type=${OCF_RESKEY_auth_type_default}}
@@ -61,6 +62,7 @@ OCF_RESKEY_lookup_type_default="InstanceId"
: ${OCF_RESKEY_iflabel=${OCF_RESKEY_iflabel_default}}
: ${OCF_RESKEY_monapi=${OCF_RESKEY_monapi_default}}
: ${OCF_RESKEY_lookup_type=${OCF_RESKEY_lookup_type_default}}
+: ${OCF_RESKEY_awscli_timeout=${OCF_RESKEY_awscli_timeout_default}}
#######################################################################
@@ -211,6 +213,14 @@ curl sleep between tries
<content type="integer" default="${OCF_RESKEY_curl_sleep_default}" />
</parameter>
+<parameter name="awscli_timeout" unique="0">
+<longdesc lang="en">
+awscli cli-connect-timeout value
+</longdesc>
+<shortdesc lang="en">awscli cli-connect-timeout</shortdesc>
+<content type="integer" default="${OCF_RESKEY_curl_sleep_default}" />
+</parameter>
+
</parameters>
<actions>
@@ -490,7 +500,11 @@ if ! ocf_is_root; then
exit $OCF_ERR_PERM
fi
-AWSCLI_CMD="${OCF_RESKEY_awscli}"
+if [ -n "${OCF_RESKEY_awscli_timeout}" ]; then
+ AWSCLI_CMD="${OCF_RESKEY_awscli} --cli-connect-timeout ${OCF_RESKEY_awscli_timeout}"
+else
+ AWSCLI_CMD="${OCF_RESKEY_awscli}"
+fi
if [ "x${OCF_RESKEY_auth_type}" = "xkey" ]; then
AWSCLI_CMD="$AWSCLI_CMD --profile ${OCF_RESKEY_profile}"
elif [ "x${OCF_RESKEY_auth_type}" = "xrole" ]; then
From be4ad4dc4978875717b84b847bcd39386405bf51 Mon Sep 17 00:00:00 2001
From: Guilherme Felix <fguilher@amazon.com>
Date: Tue, 24 Mar 2026 16:08:41 +0000
Subject: [PATCH 2/2] aws-vpc-move-ip: Fix error in default value for
awscli_timeout
---
heartbeat/aws-vpc-move-ip | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/heartbeat/aws-vpc-move-ip b/heartbeat/aws-vpc-move-ip
index 3e798173f5..1348c59085 100755
--- a/heartbeat/aws-vpc-move-ip
+++ b/heartbeat/aws-vpc-move-ip
@@ -218,7 +218,7 @@ curl sleep between tries
awscli cli-connect-timeout value
</longdesc>
<shortdesc lang="en">awscli cli-connect-timeout</shortdesc>
-<content type="integer" default="${OCF_RESKEY_curl_sleep_default}" />
+<content type="integer" default="${OCF_RESKEY_awscli_timeout_default}" />
</parameter>
</parameters>

View File

@ -45,7 +45,7 @@
Name: resource-agents
Summary: Open Source HA Reusable Cluster Resource Scripts
Version: 4.10.0
Release: 111%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.5
Release: 111%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.7
License: GPLv2+ and LGPLv2+
URL: https://github.com/ClusterLabs/resource-agents
Source0: %{upstream_prefix}-%{upstream_version}.tar.gz
@ -210,6 +210,8 @@ Patch157: RHEL-177849-podman-etcd-fix-port-2380-binding-race.patch
Patch158: RHEL-177838-podman-etcd-fix-machine-deletion-deadlock.patch
Patch159: RHEL-177843-podman-etcd-fix-learner-start-deadlock.patch
Patch160: RHEL-188107-podman-etcd-remove-cert-monitoring.patch
Patch161: RHEL-193966-aws-vpc-move-ip-add-awscli_timeout-parameter.patch
Patch162: RHEL-193691-podman-etcd-add-support-for-single-node-operation.patch
# bundled ha-cloud-support libs
Patch500: ha-cloud-support-aliyun.patch
@ -515,6 +517,8 @@ exit 1
%patch -p1 -P 158
%patch -p1 -P 159
%patch -p1 -P 160
%patch -p1 -P 161
%patch -p1 -P 162
# bundled ha-cloud-support libs
%patch -p1 -P 500
@ -849,6 +853,16 @@ rm -rf %{buildroot}/usr/share/doc/resource-agents
%{_usr}/lib/ocf/lib/heartbeat/OCF_*.pm
%changelog
* Wed Jul 15 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.10.0-111.7
- podman-etcd: add support for single-node operation
Resolves: RHEL-193691
* Fri Jul 10 2026 Arslan Ahmad <arahmad@redhat.com> - 4.10.0-111.6
- aws-vpc-move-ip: add awscli_timeout parameter
Resolves: RHEL-193966
* Thu Jun 25 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.10.0-111.5
- podman-etcd: remove cert monitoring