import Oracle_OSS resource-agents-4.10.0-111.el9_8.7
This commit is contained in:
parent
94a2d6e30d
commit
21d0c04053
@ -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
|
||||
@ -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>
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user