Skip to content

Commit 05e436f

Browse files
committed
podman-etcd: Allow startup as learner when local revision is missing
- Allow startup as learner when local revision is missing (occurs when etcd was learner previously) - Use reboot lifetime for cluster attributes to ensure data freshness across restarts - Add retry logic for peer attribute queries since peer data may not be available immediately after restart - Improve file existence validation and error messaging for revision.json fixes: OCPBUGS-60273
1 parent 7c74960 commit 05e436f

File tree

1 file changed

+102
-25
lines changed

1 file changed

+102
-25
lines changed

heartbeat/podman-etcd

Lines changed: 102 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -641,11 +641,19 @@ etcd_pod_container_exists() {
641641

642642
attribute_node_cluster_id()
643643
{
644+
# Get or update local cluster_id from revision.json file.
645+
# Fails if file is missing, or the cluster_id can not be parsed from it.
644646
local action="$1"
645647
local value
646-
if ! value=$(jq -r ".clusterId" /var/lib/etcd/revision.json); then
648+
649+
if [ ! -f "$REV_JSON" ]; then
650+
ocf_log warn "could not '$action' cluster_id: revision.json not found"
651+
return $OCF_ERR_GENERIC
652+
fi
653+
654+
if ! value=$(jq -r ".clusterId" "$REV_JSON"); then
647655
rc=$?
648-
ocf_log err "could not get cluster_id, error code: $rc"
656+
ocf_log err "could not parse cluster_id, error code: $rc"
649657
return "$rc"
650658
fi
651659

@@ -654,7 +662,7 @@ attribute_node_cluster_id()
654662
echo "$value"
655663
;;
656664
update)
657-
if ! crm_attribute --type nodes --node "$NODENAME" --name "cluster_id" --update "$value"; then
665+
if ! crm_attribute --lifetime reboot --type nodes --node "$NODENAME" --name "cluster_id" --update "$value"; then
658666
rc=$?
659667
ocf_log err "could not update cluster_id, error code: $rc"
660668
return "$rc"
@@ -670,20 +678,50 @@ attribute_node_cluster_id()
670678
attribute_node_cluster_id_peer()
671679
{
672680
local nodename
681+
local value
682+
local retries=0
673683

674684
nodename=$(get_peer_node_name)
675-
crm_attribute --query --type nodes --node "$nodename" --name "cluster_id" | awk -F"value=" '{print $2}'
685+
686+
while [ $retries -lt $CIB_MAX_RETRIES ]; do
687+
if value=$(crm_attribute --query --lifetime reboot --type nodes --node "$nodename" --name "cluster_id" 2>/dev/null | awk -F"value=" '{print $2}'); then
688+
if [ -n "$value" ] && [ "$value" != "null" ]; then
689+
echo "$value"
690+
return 0
691+
fi
692+
fi
693+
694+
retries=$((retries + 1))
695+
if [ $retries -lt $CIB_MAX_RETRIES ]; then
696+
ocf_log info "peer cluster_id not available yet, retrying in ${CIB_RETRY_DELAY}s (attempt $retries/$CIB_MAX_RETRIES)"
697+
sleep $CIB_RETRY_DELAY
698+
fi
699+
done
700+
701+
ocf_log warn "peer cluster_id not available after $CIB_MAX_RETRIES retries"
702+
return $OCF_ERR_GENERIC
676703
}
677704

678705
attribute_node_revision()
679706
{
707+
# Get or update local revision from revision.json file.
708+
# Fails if file is missing, or the revision can not be parsed from it.
680709
local action="$1"
681710
local value
682-
local attribute="revision"
683711

684-
if ! value=$(jq -r ".maxRaftIndex" /var/lib/etcd/revision.json); then
712+
if [ "$action" != "get" ] && [ "$action" != "update" ]; then
713+
ocf_log err "unsupported action: '$action' for attribute_node_revision"
714+
return "$OCF_ERR_GENERIC"
715+
fi
716+
717+
if [ ! -f "$REV_JSON" ]; then
718+
ocf_log warn "could not '$action' revision: revision.json not found"
719+
return $OCF_ERR_GENERIC
720+
fi
721+
722+
if ! value=$(jq -r ".maxRaftIndex" "$REV_JSON"); then
685723
rc=$?
686-
ocf_log err "could not get $attribute, error code: $rc"
724+
ocf_log err "could not parse maxRaftIndex from existing revision.json, error code: $rc"
687725
return "$rc"
688726
fi
689727

@@ -692,24 +730,40 @@ attribute_node_revision()
692730
echo "$value"
693731
;;
694732
update)
695-
if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$value"; then
733+
if ! crm_attribute --lifetime reboot --type nodes --node "$NODENAME" --name "revision" --update "$value"; then
696734
rc=$?
697-
ocf_log err "could not update etcd $revision, error code: $rc"
735+
ocf_log err "could not update etcd revision, error code: $rc"
698736
return "$rc"
699737
fi
700738
;;
701-
*)
702-
ocf_log err "unsupported $action for attribute_node_revision"
703-
return "$OCF_ERR_GENERIC"
704-
;;
705739
esac
706740
}
707741

708742
attribute_node_revision_peer()
709743
{
710744
local nodename
745+
local value
746+
local retries=0
747+
711748
nodename=$(get_peer_node_name)
712-
crm_attribute --query --type nodes --node "$nodename" --name "revision" | awk -F"value=" '{print $2}'
749+
750+
while [ $retries -lt $CIB_MAX_RETRIES ]; do
751+
if value=$(crm_attribute --query --lifetime reboot --type nodes --node "$nodename" --name "revision" 2>/dev/null | awk -F"value=" '{print $2}'); then
752+
if [ -n "$value" ] && [ "$value" != "null" ]; then
753+
echo "$value"
754+
return 0
755+
fi
756+
fi
757+
758+
retries=$((retries + 1))
759+
if [ $retries -lt $CIB_MAX_RETRIES ]; then
760+
ocf_log info "peer revision not available yet, retrying in ${CIB_RETRY_DELAY}s (attempt $retries/$CIB_MAX_RETRIES)"
761+
sleep $CIB_RETRY_DELAY
762+
fi
763+
done
764+
765+
ocf_log warn "peer revision not available after $CIB_MAX_RETRIES retries"
766+
return $OCF_ERR_GENERIC
713767
}
714768

715769
attribute_node_member_id()
@@ -1248,20 +1302,30 @@ run_new_container()
12481302

12491303
compare_revision()
12501304
{
1251-
# Compare local revision (from disk) against peer revision (from CIB).
1252-
# returns "older", "equal" or "newer"
1305+
# Compare local revision (from disk) against peer revision (from CIB), returning "older", "equal", or "newer" accordingly.
1306+
# If local revision is missing, but peer revision exists, returns "older" to allow starting as learner, assuming that
1307+
# the lack of local revision means the etcd member was a learner in the previous lifecycle.
1308+
# Fails otherwise.
12531309
local revision
12541310
local peer_node_name
12551311
local peer_revision
12561312

1257-
revision=$(attribute_node_revision get)
1258-
peer_revision=$(attribute_node_revision_peer)
1259-
1260-
if [ "$revision" = "" ] || [ "$revision" = "null" ] || [ "$peer_revision" = "" ] || [ "$peer_revision" = "null" ]; then
1261-
ocf_log err "could not compare revisions: '$NODENAME' local revision='$revision', peer revision='$peer_revision'"
1313+
if ! peer_revision=$(attribute_node_revision_peer); then
1314+
return "$OCF_ERR_GENERIC"
1315+
elif [ "$peer_revision" = "" ] || [ "$peer_revision" = "null" ]; then
1316+
ocf_log err "peer revision is empty or null"
12621317
return "$OCF_ERR_GENERIC"
12631318
fi
12641319

1320+
# Handle the scenario where only the local revision data is missing
1321+
revision=$(attribute_node_revision get)
1322+
if [ "$revision" = "" ] || [ "$revision" = "null" ]; then
1323+
ocf_log info "$NODENAME local revision missing but peer has valid revision '$peer_revision' - can start as learner"
1324+
echo "older"
1325+
return "$OCF_SUCCESS"
1326+
fi
1327+
1328+
# Normal revision comparison when both exist
12651329
if [ "$revision" -gt "$peer_revision" ]; then
12661330
ocf_log info "$NODENAME revision: '$revision' is newer than peer revision: '$peer_revision'"
12671331
echo "newer"
@@ -1325,7 +1389,7 @@ can_reuse_container() {
13251389

13261390

13271391
# If the container does not exist it cannot be reused
1328-
if ! container_exists; then
1392+
if ! container_exists; then
13291393
OCF_RESKEY_reuse=0
13301394
return "$OCF_SUCCESS"
13311395
fi
@@ -1336,7 +1400,7 @@ can_reuse_container() {
13361400
OCF_RESKEY_reuse=0
13371401
return "$OCF_SUCCESS"
13381402
fi
1339-
1403+
13401404
if ! filtered_original_pod_manifest=$(filter_pod_manifest "$OCF_RESKEY_pod_manifest"); then
13411405
return $OCF_ERR_GENERIC
13421406
fi
@@ -1505,15 +1569,25 @@ podman_start()
15051569
fi
15061570
;;
15071571
2)
1508-
# TODO: can we start "normally", regardless the revisions, if the container-id is the same on both nodes?
1572+
# TODO: Consider starting normally, regardless revision *IF* the cluster-id is the same.
1573+
# NOTE: cluster-id does not change during force-new-cluster (https://github.com/openshift/etcd/pull/339)
15091574
ocf_log info "peer starting"
15101575
if [ "$revision_compare_result" = "newer" ]; then
15111576
set_force_new_cluster
15121577
elif [ "$revision_compare_result" = "older" ]; then
15131578
ocf_log info "$NODENAME shall join as learner"
15141579
JOIN_AS_LEARNER=true
15151580
else
1516-
if [ "$(attribute_node_cluster_id get)" = "$(attribute_node_cluster_id_peer)" ]; then
1581+
local local_cluster_id peer_cluster_id
1582+
if ! local_cluster_id=$(attribute_node_cluster_id get); then
1583+
return "$OCF_ERR_GENERIC"
1584+
fi
1585+
1586+
if ! peer_cluster_id=$(attribute_node_cluster_id_peer); then
1587+
return "$OCF_ERR_GENERIC"
1588+
fi
1589+
1590+
if [ "$local_cluster_id" = "$peer_cluster_id" ]; then
15171591
ocf_log info "same cluster_id and revision: start normal"
15181592
else
15191593
ocf_exit_reason "same revision but different cluster id"
@@ -1860,6 +1934,9 @@ CONTAINER=$OCF_RESKEY_name
18601934
POD_MANIFEST_COPY="${OCF_RESKEY_config_location}/pod.yaml"
18611935
ETCD_CONFIGURATION_FILE="${OCF_RESKEY_config_location}/config.yaml"
18621936
ETCD_BACKUP_FILE="${OCF_RESKEY_backup_location}/config-previous.tar.gz"
1937+
REV_JSON="/var/lib/etcd/revision.json"
1938+
CIB_MAX_RETRIES=2
1939+
CIB_RETRY_DELAY=2
18631940

18641941
# Note: we currently monitor podman containers by with the "podman exec"
18651942
# command, so make sure that invocation is always valid by enforcing the

0 commit comments

Comments
 (0)