From 961cde12916bf45788fc65c566ffb7a31c7c8bf3 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Tue, 21 Oct 2025 06:50:05 +0000 Subject: [PATCH] import OL resource-agents-4.10.0-71.el9_6.11 --- ...stem-fix-issue-with-Vormetric-mounts.patch | 1 - ...113108-podman-etcd-add-oom-parameter.patch | 85 +++ ...cd-preserve-containers-for-debugging.patch | 686 ++++++++++++++++++ ...n-etcd-wrap-ipv6-address-in-brackets.patch | 193 +++++ ...de-force_new_cluster-attribute-check.patch | 186 +++++ ...d-automatic-learner-member-promotion.patch | 321 ++++++++ ...602-portblock-fix-version-detection.patch} | 0 ... => RHEL-88430-1-podman-etcd-new-ra.patch} | 4 +- ...remove-unused-actions-from-metadata.patch} | 0 ...n-etcd-fix-listen-peer-urls-binding.patch} | 0 ...81-Filesystem-add-support-for-aznfs.patch} | 0 SPECS/resource-agents.spec | 60 +- 12 files changed, 1520 insertions(+), 16 deletions(-) create mode 100644 SOURCES/RHEL-113108-podman-etcd-add-oom-parameter.patch create mode 100644 SOURCES/RHEL-113810-podman-etcd-preserve-containers-for-debugging.patch create mode 100644 SOURCES/RHEL-113813-podman-etcd-wrap-ipv6-address-in-brackets.patch create mode 100644 SOURCES/RHEL-116208-podman-etcd-add-cluster-wide-force_new_cluster-attribute-check.patch create mode 100644 SOURCES/RHEL-119502-podman-etcd-add-automatic-learner-member-promotion.patch rename SOURCES/{RHEL-79819-portblock-fix-version-detection.patch => RHEL-86602-portblock-fix-version-detection.patch} (100%) rename SOURCES/{RHEL-88429-1-podman-etcd-new-ra.patch => RHEL-88430-1-podman-etcd-new-ra.patch} (99%) rename SOURCES/{RHEL-88429-2-podman-etcd-remove-unused-actions-from-metadata.patch => RHEL-88430-2-podman-etcd-remove-unused-actions-from-metadata.patch} (100%) rename SOURCES/{RHEL-88429-3-podman-etcd-fix-listen-peer-urls-binding.patch => RHEL-88430-3-podman-etcd-fix-listen-peer-urls-binding.patch} (100%) rename SOURCES/{RHEL-88035-Filesystem-add-support-for-aznfs.patch => RHEL-92481-Filesystem-add-support-for-aznfs.patch} (100%) diff --git a/SOURCES/RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch b/SOURCES/RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch index e27ffc8..25bf022 100644 --- a/SOURCES/RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch +++ b/SOURCES/RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch @@ -22,4 +22,3 @@ index d10e5a714..515a3919d 100755 } determine_blockdevice() { - diff --git a/SOURCES/RHEL-113108-podman-etcd-add-oom-parameter.patch b/SOURCES/RHEL-113108-podman-etcd-add-oom-parameter.patch new file mode 100644 index 0000000..f72576a --- /dev/null +++ b/SOURCES/RHEL-113108-podman-etcd-add-oom-parameter.patch @@ -0,0 +1,85 @@ +From d08a7f74427ea2cf7d355a0f7f6d8f583e2d0cba Mon Sep 17 00:00:00 2001 +From: Carlo Lobrano +Date: Thu, 3 Jul 2025 12:22:12 +0200 +Subject: [PATCH] OCPBUGS-58324: podman-etcd Add OOM score adjustment for etcd + containers + +This change introduces a new `oom` parameter to the `podman-etcd` OCF +agent. This allows tuning the Out-Of-Memory (OOM) score adjustment for +the etcd container. + +The `oom` parameter accepts integer values from -1000 to 1000, +defaulting to -997 (system-node-critical equivalent). + +see https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/#node-out-of-memory-behavior + +Key changes: +- Added `OCF_RESKEY_oom` parameter to agent definition (`content type="integer"`). +- Integrated `--oom-score-adj` option into `podman_start()`. +- Implemented input validation for `oom` in `podman_validate()`, + ensuring values are within the [-1000:1000] range. +--- + heartbeat/podman-etcd | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index 6762112ec..884b7c579 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -45,6 +45,7 @@ OCF_RESKEY_nic_default="br-ex" + OCF_RESKEY_authfile_default="/var/lib/kubelet/config.json" + OCF_RESKEY_allow_pull_default="1" + OCF_RESKEY_reuse_default="0" ++OCF_RESKEY_oom_default="-997" + + : ${OCF_RESKEY_image=${OCF_RESKEY_image_default}} + : ${OCF_RESKEY_pod_manifest=${OCF_RESKEY_pod_manifest_default}} +@@ -53,6 +54,7 @@ OCF_RESKEY_reuse_default="0" + : ${OCF_RESKEY_authfile=${OCF_RESKEY_authfile_default}} + : ${OCF_RESKEY_allow_pull=${OCF_RESKEY_allow_pull_default}} + : ${OCF_RESKEY_reuse=${OCF_RESKEY_reuse_default}} ++: ${OCF_RESKEY_oom=${OCF_RESKEY_oom_default}} + + ####################################################################### + +@@ -230,6 +232,16 @@ to stop the container before pacemaker. + drop-in dependency + + ++ ++ ++ ++Tune the host's Out-Of-Memory (OOM) preferences for containers (accepts values from -1000 to 1000). ++Default to same OOM score as system-node-critical ++https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/#node-out-of-memory-behavior ++ ++OOM for container ++ ++ + + + +@@ -1226,7 +1238,10 @@ podman_start() + fi + + podman_create_mounts +- local run_opts="-d --name=${CONTAINER}" ++ local run_opts="--detach --name=${CONTAINER}" ++ ++ run_opts="$run_opts --oom-score-adj=${OCF_RESKEY_oom}" ++ + # check to see if the container has already started + podman_simple_status + if [ $? -eq $OCF_SUCCESS ]; then +@@ -1513,6 +1528,11 @@ podman_validate() + exit $OCF_ERR_CONFIGURED + fi + ++ if [ "$OCF_RESKEY_oom" -lt -1000 ] || [ "$OCF_RESKEY_oom" -gt 1000 ]; then ++ ocf_exit_reason "'oom' value ${OCF_RESKEY_oom} is out of range [-1000:1000]" ++ exit $OCF_ERR_CONFIGURED ++ fi ++ + return $OCF_SUCCESS + } + diff --git a/SOURCES/RHEL-113810-podman-etcd-preserve-containers-for-debugging.patch b/SOURCES/RHEL-113810-podman-etcd-preserve-containers-for-debugging.patch new file mode 100644 index 0000000..ae4fd06 --- /dev/null +++ b/SOURCES/RHEL-113810-podman-etcd-preserve-containers-for-debugging.patch @@ -0,0 +1,686 @@ +From 6e9200dc2ffc89382188794742361985309936b2 Mon Sep 17 00:00:00 2001 +From: Carlo Lobrano +Date: Wed, 23 Jul 2025 09:34:13 +0200 +Subject: [PATCH] podman-etcd: preserve containers for debugging + +This change modifies the agent to keep stopped containers for log +inspection and debugging, with supporting changes to enable this +behavior. + +* Conditionally reuse existing containers when configuration unchanged +* Move etcd inline configuration flags to external file to allow + restarts without container recreation (mainly for the + force-new-cluster flag) +* Archive previous container renaming it into *-previous, and its + configuration files into /var/lib/etcd/config-previous.tar.gz archive. + The tar.gz archive consists in: + * the pod manifest created by CEO, used to generated the Etc + configuration file + * the Etcd configuration file + * the auth json file + Only one copy is maintained to limit disk usage. +* Both configuration and backup files location is configurable with 2 + new input arguments. + +Signed-off-by: Carlo Lobrano +--- + heartbeat/podman-etcd | 438 ++++++++++++++++++++++++++++++++---------- + 1 file changed, 336 insertions(+), 102 deletions(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index 4969fbaaf..33804414a 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -46,6 +46,8 @@ OCF_RESKEY_authfile_default="/var/lib/kubelet/config.json" + OCF_RESKEY_allow_pull_default="1" + OCF_RESKEY_reuse_default="0" + OCF_RESKEY_oom_default="-997" ++OCF_RESKEY_config_location_default="/var/lib/etcd" ++OCF_RESKEY_backup_location_default="/var/lib/etcd" + + : ${OCF_RESKEY_image=${OCF_RESKEY_image_default}} + : ${OCF_RESKEY_pod_manifest=${OCF_RESKEY_pod_manifest_default}} +@@ -55,6 +57,9 @@ OCF_RESKEY_oom_default="-997" + : ${OCF_RESKEY_allow_pull=${OCF_RESKEY_allow_pull_default}} + : ${OCF_RESKEY_reuse=${OCF_RESKEY_reuse_default}} + : ${OCF_RESKEY_oom=${OCF_RESKEY_oom_default}} ++: ${OCF_RESKEY_config_location=${OCF_RESKEY_config_location_default}} ++: ${OCF_RESKEY_backup_location=${OCF_RESKEY_backup_location_default}} ++ + + ####################################################################### + +@@ -242,6 +247,23 @@ https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/# + OOM for container + + ++ ++ ++ ++The directory where the resource agent stores its state files, such as the generated etcd configuration and a copy of the pod manifest. ++ ++Resource agent state directory ++ ++ ++ ++ ++ ++The directory where the resource agent stores its backups. ++ ++Resource agent backup directory ++ ++ ++ + + + +@@ -309,42 +331,52 @@ container_exists() + return 1 + } + +-remove_container() ++# archive_current_container archives the current ++# podman etcd container and its configuration files. ++archive_current_container() + { +- local rc +- local execids ++ # don't attempt to archive a container that doesn't exist ++ if ! container_exists; then ++ return ++ fi + +- if ocf_is_true "$OCF_RESKEY_reuse"; then +- # never remove the container if we have reuse enabled. +- return 0 ++ # delete any container named "*-previous", or we won't be able to archive the current container. ++ if podman inspect "${CONTAINER}-previous" >/dev/null 2>&1; then ++ ocf_log info "removing old archived container '$CONTAINER-previous'" ++ if ! ocf_run podman rm --volumes --force "$CONTAINER-previous"; then ++ ocf_log warn "could not remove old archived container (podman rm failed, error code: $?). Won't be able to archive current container" ++ return ++ fi + fi + +- if ! container_exists; then +- # don't attempt to remove a container that doesn't exist +- return 0 ++ ocf_log info "archiving '$CONTAINER' container as '$CONTAINER-previous' for debugging purposes" ++ if ! ocf_run podman rename "$CONTAINER" "$CONTAINER-previous"; then ++ ocf_log err "could not archive container '$CONTAINER', error code: $?" ++ return + fi +- ocf_log notice "Cleaning up inactive container, ${CONTAINER}." +- ocf_run podman rm -v "$CONTAINER" +- rc=$? +- if [ $rc -ne 0 ]; then +- if [ $rc -eq 2 ]; then +- if podman inspect --format '{{.State.Status}}' "$CONTAINER" | grep -wq "stopping"; then +- ocf_log err "Inactive container ${CONTAINER} is stuck in 'stopping' state. Force-remove it." +- ocf_run podman rm -f "$CONTAINER" +- rc=$? +- fi +- fi +- # due to a podman bug (rhbz#1841485), sometimes a stopped +- # container can still be associated with Exec sessions, in +- # which case the "podman rm" has to be forced +- execids=$(podman inspect "$CONTAINER" --format '{{len .ExecIDs}}') +- if [ "$execids" -ne "0" ]; then +- ocf_log warn "Inactive container ${CONTAINER} has lingering exec sessions. Force-remove it." +- ocf_run podman rm -f "$CONTAINER" +- rc=$? ++ ++ # archive corresponding etcd configuration files ++ local files_to_archive="" ++ for file in "$OCF_RESKEY_authfile" "$POD_MANIFEST_COPY" "$ETCD_CONFIGURATION_FILE"; do ++ if [ -f "$file" ]; then ++ files_to_archive="$files_to_archive $file" ++ else ++ ocf_log warn "file '$file' is missing and won't be archived" + fi ++ done ++ ++ if [ -z "$files_to_archive" ]; then ++ ocf_log warn "could not find any file to archive." ++ return ++ fi ++ ++ # NOTE: tar will override any existing archive as wanted ++ # shellcheck disable=SC2086 ++ if ! ocf_run tar --create --verbose --gzip --file "$ETCD_BACKUP_FILE" $files_to_archive; then ++ ocf_log warn "container archived successfully, but configuration backup failed (error: $?). Container debugging available, but without matching configuration files" ++ else ++ ocf_log info "container configuration also archived in '$ETCD_BACKUP_FILE'" + fi +- return $rc + } + + # Correctly wraps an ipv6 in [] for url otherwise use return normal ipv4 address. +@@ -365,6 +397,7 @@ attribute_node_ip() + local attribute="node_ip" + local ip_addr name + ++ # TODO: We can retrieve both the local and peer IP addresses from this map, which eliminates the need to use CIB to share them between nodes + for node in $(echo "$OCF_RESKEY_node_ip_map" | sed "s/\s//g;s/;/ /g"); do + name=$(echo "$node" | cut -d: -f1) + # ignore other nodes +@@ -375,7 +408,7 @@ attribute_node_ip() + done + + if [ -z "$ip_addr" ]; then +- ocf_log err "ip address was empty when querying (getent ahosts) for hostname: $(hostname -f)" ++ ocf_log err "could not get local ip address from node_ip_map: '$OCF_RESKEY_node_ip_map'" + return 1 + fi + +@@ -384,9 +417,9 @@ attribute_node_ip() + echo "$ip_addr" + ;; + update) +- if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$value"; then ++ if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$ip_addr"; then + rc="$?" +- ocf_log err "could not set $attribute to $value, error code: $rc" ++ ocf_log err "could not set $attribute to $ip_addr, error code: $rc" + return "$rc" + fi + ;; +@@ -428,6 +461,48 @@ get_env_from_manifest() { + echo "$env_var_value" + } + ++# etcd configuration file expects duration to be expressed in nanoseconds ++convert_duration_in_nanoseconds() { ++ local duration=$1 ++ local value unit nanoseconds ++ ++ if [ -z "$duration" ]; then ++ ocf_log err "convert_duration_in_nanoseconds: no duration provided" ++ return 1 ++ fi ++ ++ if ! echo "$duration" | grep -qE '^[0-9]+[numµ]?s$'; then ++ ocf_log err "convert_duration_in_nanoseconds: invalid duration format \"$duration\". Expected format: where unit is one of s, ms, us, µs, ns" ++ return 1 ++ fi ++ ++ # Extract numeric value and unit from duration string ++ value=$(echo "$duration" | sed 's/[^0-9]*$//') ++ unit=$(echo "$duration" | sed 's/^[0-9]*//') ++ ++ case "$unit" in ++ ns) ++ nanoseconds=$value ++ ;; ++ us|µs) ++ nanoseconds=$((value * 1000)) ++ ;; ++ ms) ++ nanoseconds=$((value * 1000000)) ++ ;; ++ s) ++ nanoseconds=$((value * 1000000000)) ++ ;; ++ *) ++ # this should not happen as the input is already validated ++ ocf_log err "convert_duration_in_nanoseconds: unknown duration unit \"$unit\"" ++ return 1 ++ ;; ++ esac ++ ++ echo "$nanoseconds" ++} ++ + prepare_env() { + local name ip ipurl standalone_node + +@@ -457,9 +532,14 @@ prepare_env() { + ETCDCTL_API=$(get_env_from_manifest "ETCDCTL_API") + ETCD_CIPHER_SUITES=$(get_env_from_manifest "ETCD_CIPHER_SUITES") + ETCD_DATA_DIR=$(get_env_from_manifest "ETCD_DATA_DIR") ++ if [ ! -d "$ETCD_DATA_DIR" ]; then ++ ocf_log err "could not find data-dir at path \"$ETCD_DATA_DIR\"" ++ return "$OCF_ERR_ARGS" ++ else ++ ocf_log info "using data-dir: $ETCD_DATA_DIR" ++ fi + ETCD_ELECTION_TIMEOUT=$(get_env_from_manifest "ETCD_ELECTION_TIMEOUT") + ETCD_ENABLE_PPROF=$(get_env_from_manifest "ETCD_ENABLE_PPROF") +- ETCD_EXPERIMENTAL_MAX_LEARNERS=$(get_env_from_manifest "ETCD_EXPERIMENTAL_MAX_LEARNERS") + ETCD_EXPERIMENTAL_WARNING_APPLY_DURATION=$(get_env_from_manifest "ETCD_EXPERIMENTAL_WARNING_APPLY_DURATION") + ETCD_EXPERIMENTAL_WATCH_PROGRESS_NOTIFY_INTERVAL=$(get_env_from_manifest "ETCD_EXPERIMENTAL_WATCH_PROGRESS_NOTIFY_INTERVAL") + ETCD_HEARTBEAT_INTERVAL=$(get_env_from_manifest "ETCD_HEARTBEAT_INTERVAL") +@@ -475,6 +555,62 @@ prepare_env() { + LISTEN_METRICS_URLS="0.0.0.0" + } + ++ ++generate_etcd_configuration() { ++ if is_force_new_cluster; then ++ # The embedded newline is required for correct YAML formatting. ++ FORCE_NEW_CLUSTER_CONFIG="force-new-cluster: true ++force-new-cluster-bump-amount: 1000000000" ++ else ++ FORCE_NEW_CLUSTER_CONFIG="force-new-cluster: false" ++ fi ++ ++ cat > "$ETCD_CONFIGURATION_FILE" << EOF ++logger: zap ++log-level: info ++snapshot-count: 10000 ++name: $NODENAME ++data-dir: $ETCD_DATA_DIR ++$FORCE_NEW_CLUSTER_CONFIG ++socket-reuse-address: $ETCD_SOCKET_REUSE_ADDRESS ++election-timeout: $ETCD_ELECTION_TIMEOUT ++enable-pprof: $ETCD_ENABLE_PPROF ++heartbeat-interval: $ETCD_HEARTBEAT_INTERVAL ++quota-backend-bytes: $ETCD_QUOTA_BACKEND_BYTES ++initial-advertise-peer-urls: "$NODEIPURL:2380" ++listen-peer-urls: "$(ip_url ${LISTEN_PEER_URLS}):2380" ++listen-client-urls: "$(ip_url ${LISTEN_CLIENT_URLS}):2379,unixs://${NODEIP}:0" ++initial-cluster: $ETCD_INITIAL_CLUSTER ++initial-cluster-state: $ETCD_INITIAL_CLUSTER_STATE ++client-transport-security: ++ cert-file: /etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.crt ++ key-file: /etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.key ++ client-cert-auth: true ++ trusted-ca-file: $SERVER_CACERT ++peer-transport-security: ++ cert-file: $ETCD_PEER_CERT ++ key-file: $ETCD_PEER_KEY ++ client-cert-auth: true ++ trusted-ca-file: $SERVER_CACERT ++advertise-client-urls: "$NODEIPURL:2379" ++listen-metrics-urls: "$(ip_url ${LISTEN_METRICS_URLS}):9978" ++metrics: extensive ++experimental-initial-corrupt-check: true ++experimental-max-learners: 1 ++experimental-warning-apply-duration: $(convert_duration_in_nanoseconds "$ETCD_EXPERIMENTAL_WARNING_APPLY_DURATION") ++experimental-watch-progress-notify-interval: $(convert_duration_in_nanoseconds "$ETCD_EXPERIMENTAL_WATCH_PROGRESS_NOTIFY_INTERVAL") ++EOF ++ ++ { ++ if [ -n "$ETCD_CIPHER_SUITES" ]; then ++ echo "cipher-suites:" ++ echo "$ETCD_CIPHER_SUITES" | tr ',' '\n' | while read -r cipher; do ++ echo " - \"$cipher\"" ++ done ++ fi ++ } >> "$ETCD_CONFIGURATION_FILE" ++} ++ + archive_data_folder() + { + # TODO: use etcd snapshots +@@ -634,7 +770,7 @@ add_member_as_learner() + local endpoint_url=$(ip_url $(attribute_node_ip get)) + local peer_url=$(ip_url $member_ip) + +- ocf_log info "add $member_name ($member_ip) to the member list as learner" ++ ocf_log info "add $member_name ($member_ip, $endpoint_url) to the member list as learner" + out=$(podman exec "${CONTAINER}" etcdctl --endpoints="$endpoint_url:2379" member add "$member_name" --peer-urls="$peer_url:2380" --learner) + rc=$? + if [ $rc -ne 0 ]; then +@@ -1104,18 +1240,18 @@ compare_revision() + peer_revision=$(attribute_node_revision_peer) + + if [ "$revision" = "" ] || [ "$revision" = "null" ] || [ "$peer_revision" = "" ] || [ "$peer_revision" = "null" ]; then +- ocf_log err "could not compare revisions: $NODENAME local revision: $revision, peer revision: $peer_revision" ++ ocf_log err "could not compare revisions: '$NODENAME' local revision='$revision', peer revision='$peer_revision'" + return "$OCF_ERR_GENERIC" + fi + + if [ "$revision" -gt "$peer_revision" ]; then +- ocf_log info "$NODENAME revision: $revision is newer than peer revision: $peer_revision" ++ ocf_log info "$NODENAME revision: '$revision' is newer than peer revision: '$peer_revision'" + echo "newer" + elif [ "$revision" -eq "$peer_revision" ]; then +- ocf_log info "$NODENAME revision: $revision is equal to peer revision: $peer_revision" ++ ocf_log info "$NODENAME revision: '$revision' is equal to peer revision: '$peer_revision'" + echo "equal" + else +- ocf_log info "$NODENAME revision: $revision is older than peer revision: $peer_revision" ++ ocf_log info "$NODENAME revision: '$revision' is older than peer revision: '$peer_revision'" + echo "older" + fi + return "$OCF_SUCCESS" +@@ -1144,6 +1280,100 @@ ensure_pod_manifest_exists() + return "$OCF_SUCCESS" + } + ++filter_pod_manifest() { ++ # Remove pod-version related fields from POD manifest ++ local pod_manifest="$1" ++ local temporary_file ++ local jq_filter='del(.metadata.labels.revision) | .spec.containers[] |= ( .env |= map(select( .name != "ETCD_STATIC_POD_VERSION" ))) | .spec.volumes |= map( select( .name != "resource-dir" ))' ++ ++ if ! temporary_file=$(mktemp); then ++ ocf_log err "could not create temporary file for '$pod_manifest', error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ if ! jq "$jq_filter" "$pod_manifest" > "$temporary_file"; then ++ ocf_log err "could not remove pod version related data from '$pod_manifest', error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ echo "$temporary_file" ++} ++ ++can_reuse_container() { ++ # Decide whether to reuse the existing container or create a new one based on etcd pod manifest changes. ++ # NOTE: explicitly ignore POD version and POD version related data, as the content might be the same even if the revision number has changed. ++ local cp_rc ++ local diff_rc ++ local filtered_original_pod_manifest ++ local filtered_copy_pod_manifest ++ ++ ++ # If the container does not exist it cannot be reused ++ if ! container_exists; then ++ OCF_RESKEY_reuse=0 ++ return "$OCF_SUCCESS" ++ fi ++ ++ # If the manifest copy doesn't exist, we need a new container. ++ if [ ! -f "$POD_MANIFEST_COPY" ]; then ++ ocf_log info "a working copy of $OCF_RESKEY_pod_manifest was not found. A new etcd container will be created." ++ OCF_RESKEY_reuse=0 ++ return "$OCF_SUCCESS" ++ fi ++ ++ if ! filtered_original_pod_manifest=$(filter_pod_manifest "$OCF_RESKEY_pod_manifest"); then ++ return $OCF_ERR_GENERIC ++ fi ++ if ! filtered_copy_pod_manifest=$(filter_pod_manifest "$POD_MANIFEST_COPY"); then ++ return $OCF_ERR_GENERIC ++ fi ++ ++ ocf_log info "comparing $OCF_RESKEY_pod_manifest with local copy $POD_MANIFEST_COPY" ++ ocf_run diff -s "$filtered_original_pod_manifest" "$filtered_copy_pod_manifest" ++ diff_rc="$?" ++ # clean up temporary files ++ rm -f "$filtered_original_pod_manifest" "$filtered_copy_pod_manifest" ++ case "$diff_rc" in ++ 0) ++ ocf_log info "Reusing the existing etcd container" ++ OCF_RESKEY_reuse=1 ++ ;; ++ 1) ++ ocf_log info "Etcd pod manifest changes detected: creating a new etcd container to apply the changes" ++ if ! ocf_run cp -p "$OCF_RESKEY_pod_manifest" "$POD_MANIFEST_COPY"; then ++ cp_rc="$?" ++ ocf_log err "Could not create a working copy of $OCF_RESKEY_pod_manifest, rc: $cp_rc" ++ return "$OCF_ERR_GENERIC" ++ fi ++ ocf_log info "A working copy of $OCF_RESKEY_pod_manifest was created" ++ OCF_RESKEY_reuse=0 ++ ;; ++ *) ++ ocf_log err "Could not check if etcd pod manifest has changed, diff rc: $diff_rc" ++ return "$OCF_ERR_GENERIC" ++ ;; ++ esac ++ ++ return "$OCF_SUCCESS" ++} ++ ++ensure_pod_manifest_copy_exists() { ++ local cp_rc ++ ++ if [ -f "$POD_MANIFEST_COPY" ]; then ++ return "$OCF_SUCCESS" ++ fi ++ ++ # If the manifest copy doesn't exist, create it and ensure a new container. ++ if ! ocf_run cp -p "$OCF_RESKEY_pod_manifest" "$POD_MANIFEST_COPY"; then ++ cp_rc="$?" ++ ocf_log err "Could not create a working copy of $OCF_RESKEY_pod_manifest, rc: $cp_rc" ++ return "$OCF_ERR_GENERIC" ++ fi ++ ++ ocf_log info "a new working copy of $OCF_RESKEY_pod_manifest was created" ++ ++ return "$OCF_SUCCESS" ++} ++ + podman_start() + { + local cid +@@ -1173,6 +1403,13 @@ podman_start() + return $OCF_ERR_GENERIC + fi + ++ # check if the container has already started ++ podman_simple_status ++ if [ $? -eq $OCF_SUCCESS ]; then ++ ocf_log info "the '$CONTAINER' has already started. Nothing to do" ++ return "$OCF_SUCCESS" ++ fi ++ + if ! ensure_pod_manifest_exists; then + ocf_exit_reason "could not find etcd pod manifest ($OCF_RESKEY_pod_manifest)" + return "$OCF_ERR_GENERIC" +@@ -1186,8 +1423,9 @@ podman_start() + ocf_log info "static pod was running: start normally" + else + if is_force_new_cluster; then +- ocf_log notice "$NODENAME marked to force-new-cluster" ++ ocf_log notice "'$NODENAME' marked to force-new-cluster" + else ++ ocf_log info "'$NODENAME' is not marked to force-new-cluster" + # When the local agent starts, we can infer the cluster state by counting + # how many agents are starting or already active: + # - 1 active agent: it's the peer (we are just starting) +@@ -1195,6 +1433,7 @@ podman_start() + # - 0 active agents, 2 starting: both agents are starting simultaneously + local active_resources_count + active_resources_count=$(echo "$OCF_RESKEY_CRM_meta_notify_active_resource" | wc -w) ++ ocf_log info "found '$active_resources_count' active etcd resources (meta notify environment variable: '$OCF_RESKEY_CRM_meta_notify_active_resource')" + case "$active_resources_count" in + 1) + if [ "$(attribute_learner_node get)" = "$(get_peer_node_name)" ]; then +@@ -1205,17 +1444,17 @@ podman_start() + fi + ;; + 0) ++ # count how many agents are starting now ++ local start_resources_count ++ 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" + fi +- +- # count how many agents are starting now +- local start_resources_count +- start_resources_count=$(echo "$OCF_RESKEY_CRM_meta_notify_start_resource" | wc -w) +- + case "$start_resources_count" in + 1) + ocf_log debug "peer not starting: ensure we can start a new cluster" +@@ -1231,6 +1470,7 @@ podman_start() + fi + ;; + 2) ++ # TODO: can we start "normally", regardless the revisions, if the container-id is the same on both nodes? + ocf_log info "peer starting" + if [ "$revision_compare_result" = "newer" ]; then + set_force_new_cluster +@@ -1263,7 +1503,7 @@ podman_start() + fi + + podman_create_mounts +- local run_opts="--detach --name=${CONTAINER}" ++ local run_opts="--detach --name=${CONTAINER} --replace" + + run_opts="$run_opts --oom-score-adj=${OCF_RESKEY_oom}" + +@@ -1297,61 +1537,59 @@ podman_start() + archive_data_folder + fi + +- prepare_env ++ ocf_log info "check for changes in pod manifest to decide if the container should be reused or replaced" ++ if ! can_reuse_container ; then ++ rc="$?" ++ ocf_log err "could not determine etcd container reuse strategy, rc: $rc" ++ return "$rc" ++ fi ++ ++ # Archive current container and its configuration before creating ++ # new configuration files. ++ if ! ocf_is_true "$OCF_RESKEY_reuse"; then ++ # Log archive container failures but don't block, as the priority ++ # is ensuring the etcd container starts successfully. ++ archive_current_container ++ fi ++ ++ if ! ensure_pod_manifest_copy_exists; then ++ return $OCF_ERR_GENERIC ++ fi ++ ++ if ! prepare_env; then ++ ocf_log err "Could not prepare environment for podman, error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ ++ if ! generate_etcd_configuration; then ++ ocf_log err "Could not generate etcd configuration, error code: $?" ++ return $OCF_ERR_GENERIC ++ fi + +- # add etcd-specific opts + run_opts="$run_opts \ +- --network=host \ +- -v /etc/kubernetes/static-pod-resources/etcd-certs:/etc/kubernetes/static-pod-certs \ +- -v /var/lib/etcd:/var/lib/etcd \ +- --env ALL_ETCD_ENDPOINTS=$ALL_ETCD_ENDPOINTS \ +- --env ETCD_CIPHER_SUITES=$ETCD_CIPHER_SUITES \ +- --env ETCD_DATA_DIR=$ETCD_DATA_DIR \ +- --env ETCD_ELECTION_TIMEOUT=$ETCD_ELECTION_TIMEOUT \ +- --env ETCD_ENABLE_PPROF=$ETCD_ENABLE_PPROF \ +- --env ETCD_EXPERIMENTAL_MAX_LEARNERS=$ETCD_EXPERIMENTAL_MAX_LEARNERS \ +- --env ETCD_EXPERIMENTAL_WARNING_APPLY_DURATION=$ETCD_EXPERIMENTAL_WARNING_APPLY_DURATION \ +- --env ETCD_EXPERIMENTAL_WATCH_PROGRESS_NOTIFY_INTERVAL=$ETCD_EXPERIMENTAL_WATCH_PROGRESS_NOTIFY_INTERVAL \ +- --env ETCD_HEARTBEAT_INTERVAL=$ETCD_HEARTBEAT_INTERVAL \ +- --env ETCD_INITIAL_CLUSTER=$ETCD_INITIAL_CLUSTER \ +- --env ETCD_INITIAL_CLUSTER_STATE=$ETCD_INITIAL_CLUSTER_STATE \ +- --env ETCD_NAME=$NODENAME \ +- --env ETCD_QUOTA_BACKEND_BYTES=$ETCD_QUOTA_BACKEND_BYTES \ +- --env ETCD_SOCKET_REUSE_ADDRESS=$ETCD_SOCKET_REUSE_ADDRESS \ +- --env ETCDCTL_API=$ETCDCTL_API \ +- --env ETCDCTL_CACERT=$SERVER_CACERT \ +- --env ETCDCTL_CERT=$ETCD_PEER_CERT \ +- --env ETCDCTL_KEY=$ETCD_PEER_KEY \ +- --authfile=$OCF_RESKEY_authfile \ +- --security-opt label=disable" ++ --network=host \ ++ -v /etc/kubernetes/static-pod-resources/etcd-certs:/etc/kubernetes/static-pod-certs \ ++ -v /var/lib/etcd:/var/lib/etcd \ ++ --env ETCDCTL_API=$ETCDCTL_API \ ++ --env ETCDCTL_CACERT=$SERVER_CACERT \ ++ --env ETCDCTL_CERT=$ETCD_PEER_CERT \ ++ --env ETCDCTL_KEY=$ETCD_PEER_KEY \ ++ --authfile=$OCF_RESKEY_authfile \ ++ --security-opt label=disable" + if [ -n "$OCF_RESKEY_run_opts" ]; then + run_opts="$run_opts $OCF_RESKEY_run_opts" + fi + +- OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd --logger=zap \ +- --log-level=info \ +- --experimental-initial-corrupt-check=true \ +- --snapshot-count=10000 \ +- --initial-advertise-peer-urls=$NODEIPURL:2380 \ +- --cert-file=/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.crt \ +- --key-file=/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.key \ +- --trusted-ca-file=$SERVER_CACERT \ +- --client-cert-auth=true \ +- --peer-cert-file=$ETCD_PEER_CERT \ +- --peer-key-file=$ETCD_PEER_KEY \ +- --peer-trusted-ca-file=$SERVER_CACERT \ +- --peer-client-cert-auth=true \ +- --advertise-client-urls=$NODEIPURL:2379 \ +- --listen-client-urls=$(ip_url ${LISTEN_CLIENT_URLS}):2379,unixs://${NODEIP}:0 \ +- --listen-peer-urls=$(ip_url ${LISTEN_PEER_URLS}):2380 \ +- --metrics=extensive \ +- --listen-metrics-urls=$(ip_url ${LISTEN_METRICS_URLS}):9978" +- if [ -n "$OCF_RESKEY_run_cmd_opts" ]; then +- OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd $OCF_RESKEY_run_cmd_opts" ++ if [ -f "$ETCD_CONFIGURATION_FILE" ]; then ++ ocf_log info "using etcd configuration file: $ETCD_CONFIGURATION_FILE" ++ else ++ ocf_log err "could not find $ETCD_CONFIGURATION_FILE" ++ return "$OCF_ERR_GENERIC" + fi + +- if is_force_new_cluster; then +- OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd --force-new-cluster" ++ OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd --config-file=$ETCD_CONFIGURATION_FILE" ++ if [ -n "$OCF_RESKEY_run_cmd_opts" ]; then ++ OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd $OCF_RESKEY_run_cmd_opts" + fi + + if [ "$OCF_RESKEY_image" = "$OCF_RESKEY_image_default" ]; then +@@ -1377,9 +1615,7 @@ podman_start() + ocf_log info "starting existing container $CONTAINER." + ocf_run podman start "$CONTAINER" + else +- # make sure any previous container matching our container name is cleaned up first. +- # we already know at this point it wouldn't be running +- remove_container ++ ocf_log info "starting new container $CONTAINER." + run_new_container "$run_opts" "$OCF_RESKEY_image" "$OCF_RESKEY_run_cmd" + if [ $? -eq 125 ]; then + return $OCF_ERR_GENERIC +@@ -1439,7 +1675,6 @@ podman_stop() + local rc + podman_simple_status + if [ $? -eq $OCF_NOT_RUNNING ]; then +- remove_container + ocf_log info "could not leave members list: etcd container not running" + return $OCF_SUCCESS + fi +@@ -1475,7 +1710,7 @@ podman_stop() + ocf_run podman kill "$CONTAINER" + rc=$? + else +- ocf_log debug "waiting $timeout second[s] before killing container" ++ ocf_log info "waiting $timeout second[s] before killing container" + ocf_run podman stop -t="$timeout" "$CONTAINER" + rc=$? + # on stop, systemd will automatically delete any transient +@@ -1496,11 +1731,6 @@ podman_stop() + fi + fi + +- if ! remove_container; then +- ocf_exit_reason "Failed to remove stopped container, ${CONTAINER}, based on image, ${OCF_RESKEY_image}." +- return $OCF_ERR_GENERIC +- fi +- + return $OCF_SUCCESS + } + +@@ -1532,6 +1762,7 @@ podman_validate() + check_binary oc + check_binary podman + check_binary jq ++ check_binary tar + + if [ -z "$OCF_RESKEY_node_ip_map" ]; then + ocf_exit_reason "'node_ip_map' option is required" +@@ -1589,6 +1820,9 @@ else + fi + + CONTAINER=$OCF_RESKEY_name ++POD_MANIFEST_COPY="${OCF_RESKEY_config_location}/pod.yaml" ++ETCD_CONFIGURATION_FILE="${OCF_RESKEY_config_location}/config.yaml" ++ETCD_BACKUP_FILE="${OCF_RESKEY_backup_location}/config-previous.tar.gz" + + # Note: we currently monitor podman containers by with the "podman exec" + # command, so make sure that invocation is always valid by enforcing the diff --git a/SOURCES/RHEL-113813-podman-etcd-wrap-ipv6-address-in-brackets.patch b/SOURCES/RHEL-113813-podman-etcd-wrap-ipv6-address-in-brackets.patch new file mode 100644 index 0000000..b544e6d --- /dev/null +++ b/SOURCES/RHEL-113813-podman-etcd-wrap-ipv6-address-in-brackets.patch @@ -0,0 +1,193 @@ +From 11cdff8c886c72c83c26e48e46a8620c06e4c2f0 Mon Sep 17 00:00:00 2001 +From: E Hila +Date: Tue, 9 Sep 2025 06:06:12 -0400 +Subject: [PATCH] OCPBUGS-60977: podman-etcd: wrap ipv6 address in brackets for + attribute_node_ip (#2068) + +When trying to determine the node ip address we need to make sure we account for ipv6 and dualstack deployments, and accordingly wrap ipv6 in brackets so it correctly resolves. Since the node ip mapping is provided by the controller, we parse out the IP address of the node from there and use a helper function for building URLs with ports to correctly use brackets for ipv6 ip addresses. + +Signed-off-by: ehila +--- + heartbeat/podman-etcd | 77 ++++++++++++++++++++++++++++--------------- + 1 file changed, 51 insertions(+), 26 deletions(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index 884b7c579..4969fbaaf 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -347,21 +347,41 @@ remove_container() + return $rc + } + ++# Correctly wraps an ipv6 in [] for url otherwise use return normal ipv4 address. ++ip_url() { ++ local ip_addr=$1 ++ local value ++ if echo "$ip_addr" | grep -q ":" ; then ++ value="[$ip_addr]" ++ else ++ value="$ip_addr" ++ fi ++ echo "https://$value" ++} ++ + attribute_node_ip() + { + local action="$1" + local attribute="node_ip" +- local value ++ local ip_addr name + +- if ! value=$(ip -brief addr show "$OCF_RESKEY_nic" | awk '{gsub("/.*", "", $3); print $3}'); then +- rc=$? +- ocf_log err "could not get node ip, error code: $rc" +- return "$rc" ++ for node in $(echo "$OCF_RESKEY_node_ip_map" | sed "s/\s//g;s/;/ /g"); do ++ name=$(echo "$node" | cut -d: -f1) ++ # ignore other nodes ++ if [ "$name" != "$NODENAME" ]; then ++ continue ++ fi ++ ip_addr=$(echo "$node" | cut -d: -f2-) # Grab everything after the first : this covers ipv4/ipv6 ++ done ++ ++ if [ -z "$ip_addr" ]; then ++ ocf_log err "ip address was empty when querying (getent ahosts) for hostname: $(hostname -f)" ++ return 1 + fi + + case "$action" in + get) +- echo "$value" ++ echo "$ip_addr" + ;; + update) + if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$value"; then +@@ -409,26 +429,28 @@ get_env_from_manifest() { + } + + prepare_env() { +- local name ip standalone_node ++ local name ip ipurl standalone_node + + NODEIP="$(attribute_node_ip get)" ++ NODEIPURL=$(ip_url $NODEIP) + + if is_force_new_cluster; then +- ALL_ETCD_ENDPOINTS="https://$NODEIP:2379" ++ ALL_ETCD_ENDPOINTS="$NODEIPURL:2379" + ETCD_INITIAL_CLUSTER_STATE="new" +- ETCD_INITIAL_CLUSTER="$NODENAME=https://$NODEIP:2380" ++ ETCD_INITIAL_CLUSTER="$NODENAME=$NODEIPURL:2380" + else + ETCD_INITIAL_CLUSTER_STATE="existing" + for node in $(echo "$OCF_RESKEY_node_ip_map" | sed "s/\s//g;s/;/ /g"); do +- name=$(echo "$node" | awk -F":" '{print $1}') +- ip=$(echo "$node" | awk -F":" '{print $2}') ++ name=$(echo "$node" | cut -d: -f1) ++ ip=$(echo "$node" | cut -d: -f2-) # Grab everything after the first : this covers ipv4/ipv6 ++ ipurl="$(ip_url $ip)" + if [ -z "$name" ] || [ -z "$ip" ]; then + ocf_exit_reason "name or ip missing for 1 or more nodes" + exit $OCF_ERR_CONFIGURED + fi + +- [ -z "$ALL_ETCD_ENDPOINTS" ] && ALL_ETCD_ENDPOINTS="https://$ip:2379" || ALL_ETCD_ENDPOINTS="$ALL_ETCD_ENDPOINTS,https://$ip:2379" +- [ -z "$ETCD_INITIAL_CLUSTER" ] && ETCD_INITIAL_CLUSTER="$name=https://$ip:2380" || ETCD_INITIAL_CLUSTER="$ETCD_INITIAL_CLUSTER,$name=https://$ip:2380" ++ [ -z "$ALL_ETCD_ENDPOINTS" ] && ALL_ETCD_ENDPOINTS="$ipurl:2379" || ALL_ETCD_ENDPOINTS="$ALL_ETCD_ENDPOINTS,$ipurl:2379" ++ [ -z "$ETCD_INITIAL_CLUSTER" ] && ETCD_INITIAL_CLUSTER="$name=$ipurl:2380" || ETCD_INITIAL_CLUSTER="$ETCD_INITIAL_CLUSTER,$name=$ipurl:2380" + done + fi + +@@ -609,9 +631,11 @@ add_member_as_learner() + local rc + local member_name=$1 + local member_ip=$2 ++ local endpoint_url=$(ip_url $(attribute_node_ip get)) ++ local peer_url=$(ip_url $member_ip) + + ocf_log info "add $member_name ($member_ip) to the member list as learner" +- out=$(podman exec "${CONTAINER}" etcdctl --endpoints="https://$(attribute_node_ip get):2379" member add "$member_name" --peer-urls="https://$member_ip:2380" --learner) ++ out=$(podman exec "${CONTAINER}" etcdctl --endpoints="$endpoint_url:2379" member add "$member_name" --peer-urls="$peer_url:2380" --learner) + rc=$? + if [ $rc -ne 0 ]; then + ocf_log err "could not add $member_name as learner, error code: $rc" +@@ -806,14 +830,15 @@ get_peer_node_name() { + + get_all_etcd_endpoints() { + for node in $(echo "$OCF_RESKEY_node_ip_map" | sed "s/\s//g;s/;/ /g"); do +- name=$(echo "$node" | awk -F":" '{print $1}') +- ip=$(echo "$node" | awk -F":" '{print $2}') ++ name=$(echo "$node" | cut -d: -f1) ++ ip=$(echo "$node" | cut -d: -f2-) # Grab everything after the first : this covers ipv4/ipv6 ++ ipurl="$(ip_url $ip)" + if [ -z "$name" ] || [ -z "$ip" ]; then + ocf_exit_reason "name or ip missing for 1 or more nodes" + exit $OCF_ERR_CONFIGURED + fi + +- [ -z "$ALL_ETCD_ENDPOINTS" ] && ALL_ETCD_ENDPOINTS="https://$ip:2379" || ALL_ETCD_ENDPOINTS="$ALL_ETCD_ENDPOINTS,https://$ip:2379" ++ [ -z "$ALL_ETCD_ENDPOINTS" ] && ALL_ETCD_ENDPOINTS="$ipurl:2379" || ALL_ETCD_ENDPOINTS="$ALL_ETCD_ENDPOINTS,$ipurl:2379" + done + echo "$ALL_ETCD_ENDPOINTS" + } +@@ -831,7 +856,7 @@ get_member_list_json() { + # Get the list of members visible to the current node + local this_node_endpoint + +- this_node_endpoint="https://$(attribute_node_ip get):2379" ++ this_node_endpoint="$(ip_url $(attribute_node_ip get)):2379" + podman exec "${CONTAINER}" etcdctl member list --endpoints="$this_node_endpoint" -w json + } + +@@ -886,14 +911,14 @@ check_peers() + # ] + # } + for node in $(echo "$OCF_RESKEY_node_ip_map" | sed "s/\s//g;s/;/ /g"); do +- name=$(echo "$node" | awk -F":" '{print $1}') ++ name=$(echo "$node" | cut -d: -f1) + # do not check itself + if [ "$name" = "$NODENAME" ]; then + continue + fi + + # Check by IP instead of Name since "learner" members appear only in peerURLs, not by Name. +- ip=$(echo "$node" | awk -F":" '{print $2}') ++ ip=$(echo "$node" | cut -d: -f2-) # Grab everything after the first : this covers ipv4/ipv6 + id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .peerURLs | map(test(\"$ip\")) | any).ID") + if [ -z "$id" ]; then + ocf_log info "$name is not in the members list" +@@ -1307,7 +1332,7 @@ podman_start() + --log-level=info \ + --experimental-initial-corrupt-check=true \ + --snapshot-count=10000 \ +- --initial-advertise-peer-urls=https://${NODEIP}:2380 \ ++ --initial-advertise-peer-urls=$NODEIPURL:2380 \ + --cert-file=/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.crt \ + --key-file=/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-${NODENAME}.key \ + --trusted-ca-file=$SERVER_CACERT \ +@@ -1316,11 +1341,11 @@ podman_start() + --peer-key-file=$ETCD_PEER_KEY \ + --peer-trusted-ca-file=$SERVER_CACERT \ + --peer-client-cert-auth=true \ +- --advertise-client-urls=https://${NODEIP}:2379 \ +- --listen-client-urls=https://${LISTEN_CLIENT_URLS}:2379,unixs://${NODEIP}:0 \ +- --listen-peer-urls=https://${LISTEN_PEER_URLS}:2380 \ ++ --advertise-client-urls=$NODEIPURL:2379 \ ++ --listen-client-urls=$(ip_url ${LISTEN_CLIENT_URLS}):2379,unixs://${NODEIP}:0 \ ++ --listen-peer-urls=$(ip_url ${LISTEN_PEER_URLS}):2380 \ + --metrics=extensive \ +- --listen-metrics-urls=https://${LISTEN_METRICS_URLS}:9978" ++ --listen-metrics-urls=$(ip_url ${LISTEN_METRICS_URLS}):9978" + if [ -n "$OCF_RESKEY_run_cmd_opts" ]; then + OCF_RESKEY_run_cmd="$OCF_RESKEY_run_cmd $OCF_RESKEY_run_cmd_opts" + fi +@@ -1430,7 +1455,7 @@ podman_stop() + ocf_log info "last member. Not leaving the member list" + else + ocf_log info "leaving members list as member with ID $member_id" +- endpoint="https://$(attribute_node_ip get):2379" ++ endpoint="$(ip_url $(attribute_node_ip get)):2379" + if ! ocf_run podman exec "$CONTAINER" etcdctl member remove "$member_id" --endpoints="$endpoint"; then + rc=$? + ocf_log err "error leaving members list, error code: $rc" diff --git a/SOURCES/RHEL-116208-podman-etcd-add-cluster-wide-force_new_cluster-attribute-check.patch b/SOURCES/RHEL-116208-podman-etcd-add-cluster-wide-force_new_cluster-attribute-check.patch new file mode 100644 index 0000000..5938026 --- /dev/null +++ b/SOURCES/RHEL-116208-podman-etcd-add-cluster-wide-force_new_cluster-attribute-check.patch @@ -0,0 +1,186 @@ +From 1afdd91b2961061937fc802c575304ede8d79286 Mon Sep 17 00:00:00 2001 +From: Carlo Lobrano +Date: Wed, 10 Sep 2025 16:56:56 +0200 +Subject: [PATCH] podman-etcd: Add cluster-wide force_new_cluster attribute + checking + +Implement cluster-wide validation of force_new_cluster attribute to resolve +race conditions during automated cluster recovery. The enhancement ensures +agents check for the cluster-wide attribute before falling back to local +etcd revision comparison. + +Key changes: +- Enhanced get_force_new_cluster() to query all cluster nodes +- Ensure force_new_cluster is not set in both nodes to prevent + conflicting recovery attempts +- Updated startup logic to prioritize cluster-wide attribute checking + +fixes OCPBUGS-61117 +--- + heartbeat/podman-etcd | 107 ++++++++++++++++++++++++++++-------------- + 1 file changed, 72 insertions(+), 35 deletions(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index 33804414a..f3a6da5e2 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -794,54 +794,72 @@ set_force_new_cluster() + return $rc + } + ++# get_force_new_cluster returns a space-separated list of nodes that have the force_new_cluster attribute set. ++# Return values: ++# - Exit code 0 with non-empty output: One or more nodes have the force_new_cluster attribute set ++# - Exit code 0 with empty output: No nodes have the force_new_cluster attribute set ++# - Exit code 1 with empty output: Error occurred while querying the cluster nodes + get_force_new_cluster() + { +- crm_attribute --lifetime reboot --query --name "force_new_cluster" | awk -F"value=" '{print $2}' ++ local node nodes value ++ local holders="" ++ ++ if ! nodes=$(crm_node -l | awk '{print $2}'); then ++ ocf_log err "could not get force_new_cluster attribute, crm_node error code: $?" ++ return 1 ++ fi ++ if [ -z "$nodes" ]; then ++ ocf_log err "could not get force_new_cluster attribute, the list of nodes is empty" ++ return 1 ++ fi ++ ++ for node in $nodes; do ++ if ! value=$(crm_attribute --query --lifetime reboot --name "force_new_cluster" --node "$node" 2>/dev/null | awk -F'value=' '{print $2}' | tr -d "'"); then ++ ocf_log err "could not get force_new_cluster attribute, crm_attribut error code: $?" ++ return 1 ++ fi ++ if [ -n "$value" ]; then ++ holders="$holders$node " ++ fi ++ done ++ echo "$holders" + } + ++ + clear_force_new_cluster() + { +- local force_new_cluster_node +- +- force_new_cluster_node=$(get_force_new_cluster) +- if [ -z "$force_new_cluster_node" ]; then +- ocf_log info "$NODENAME: force_new_cluster attribute not set" ++ # only the holder of "force_new_cluster" attribute can delete it ++ if ! is_force_new_cluster; then ++ ocf_log info "force_new_cluster unset or not owned by $NODENAME" + return $OCF_SUCCESS + fi + +- # only the holder of "force_new_cluster" attribute can delete it +- if [ "$NODENAME" = "$force_new_cluster_node" ]; then +- crm_attribute --lifetime reboot --name "force_new_cluster" --delete +- rc=$? +- if [ $rc -ne 0 ]; then +- ocf_log err "could not clear force_new_cluster attribute, error code: $rc" +- else +- ocf_log info "$NODENAME: force_new_cluster attribute cleared" +- fi +- return $rc +- else +- ocf_log info "$NODENAME does not hold force_new_cluster ($force_new_cluster_node has it)" +- return $OCF_SUCCESS ++ if ! crm_attribute --delete --lifetime reboot --node "$NODENAME" --name "force_new_cluster"; then ++ ocf_log err "could not clear force_new_cluster attribute, error code: $?" ++ return $OCF_ERR_GENERIC + fi ++ ++ ocf_log info "$NODENAME: force_new_cluster attribute cleared" ++ return $OCF_SUCCESS + } + ++ + is_force_new_cluster() + { +- # Return 0 if 'force_new_cluster' is set and the value matches the current node name, 1 otherwise. +- local value ++ # Return 0 if 'force_new_cluster' is set on the current node, 1 otherwise. ++ local fnc_holders + +- value=$(get_force_new_cluster) +- if [ -z "$value" ]; then +- ocf_log debug "force_new_cluster attribute is not set" +- return 1 ++ if ! fnc_holders=$(get_force_new_cluster); then ++ ocf_exit_reason "is_force_new_cluster: Failed to get force_new_cluster node holders" ++ exit $OCF_ERR_GENERIC + fi + +- if [ "$value" = "$NODENAME" ]; then ++ if echo "$fnc_holders" | grep -q -w "$NODENAME"; then + ocf_log debug "$NODENAME has force_new_cluster set" + return 0 + fi + +- ocf_log info "force_new_cluster attribute set on peer node $value" ++ ocf_log debug "force_new_cluster attribute is not set on $NODENAME" + return 1 + } + +@@ -1415,17 +1433,34 @@ podman_start() + return "$OCF_ERR_GENERIC" + fi + +- # force-new-cluster property is a runtime-scoped flag that instructs the agent to force a new cluster-of-1. +- # Since this attribute is configured with a reboot-lifetime, it is automatically cleared when the machine reboots. +- # If the agent detects during its start that this property is set, it indicates that the flag was explicitly set +- # during the current node boot session, implying a deliberate request to recover the cluster. + if ocf_is_true "$pod_was_running"; then + ocf_log info "static pod was running: start normally" + else +- if is_force_new_cluster; then +- ocf_log notice "'$NODENAME' marked to force-new-cluster" ++ local fnc_holders ++ if ! fnc_holders=$(get_force_new_cluster); then ++ ocf_exit_reason "Failed to get force_new_cluster node holders" ++ return "$OCF_ERR_GENERIC" ++ fi ++ ++ local fnc_holder_count ++ fnc_holder_count=$(echo "$fnc_holders" | wc -w) ++ if [ "$fnc_holder_count" -gt 1 ]; then ++ ocf_exit_reason "force_new_cluster attribute is set on multiple nodes ($fnc_holders)" ++ return "$OCF_ERR_GENERIC" ++ fi ++ ++ if [ "$fnc_holder_count" -eq 1 ]; then ++ if echo "$fnc_holders" | grep -q -w "$NODENAME"; then ++ # Attribute is set on the local node. ++ ocf_log notice "$NODENAME marked to force-new-cluster" ++ JOIN_AS_LEARNER=false ++ else ++ # Attribute is set on a peer node. ++ ocf_log info "$NODENAME shall join as learner because force_new_cluster is set on peer $fnc_holders" ++ JOIN_AS_LEARNER=true ++ fi + else +- ocf_log info "'$NODENAME' is not marked to force-new-cluster" ++ ocf_log info "no node is marked to force-new-cluster" + # When the local agent starts, we can infer the cluster state by counting + # how many agents are starting or already active: + # - 1 active agent: it's the peer (we are just starting) +@@ -1522,7 +1557,7 @@ podman_start() + for try in $(seq $retries); do + learner_node=$(attribute_learner_node get) + if [ "$NODENAME" != "$learner_node" ]; then +- ocf_log info "$learner_node is not in the member list yet. Retry in $poll_interval_sec seconds." ++ ocf_log info "$NODENAME is not in the member list yet. Retry in $poll_interval_sec seconds." + sleep $poll_interval_sec + continue + fi +@@ -1673,6 +1708,8 @@ podman_stop() + { + local timeout=60 + local rc ++ ++ ocf_log notice "podman-etcd stop" + podman_simple_status + if [ $? -eq $OCF_NOT_RUNNING ]; then + ocf_log info "could not leave members list: etcd container not running" diff --git a/SOURCES/RHEL-119502-podman-etcd-add-automatic-learner-member-promotion.patch b/SOURCES/RHEL-119502-podman-etcd-add-automatic-learner-member-promotion.patch new file mode 100644 index 0000000..74795e5 --- /dev/null +++ b/SOURCES/RHEL-119502-podman-etcd-add-automatic-learner-member-promotion.patch @@ -0,0 +1,321 @@ +From a31f15104fc712cd25f8a59d49f1bbcdbbbc5434 Mon Sep 17 00:00:00 2001 +From: Carlo Lobrano +Date: Tue, 30 Sep 2025 11:54:44 +0200 +Subject: [PATCH 1/2] Refactor(podman-etcd): improve peer checking and + leadership loss detection + +The check_peers function is broken up into smaller, more manageable +functions. This refactoring separates the logic for detecting a loss of +cluster leadership from the logic for managing peer membership. + +The main function is renamed to check_peer as there is only 1 peer to +check (it was check_peers). +--- + heartbeat/podman-etcd | 78 +++++++++++++++++++++++++------------------ + 1 file changed, 45 insertions(+), 33 deletions(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index f3a6da5e2..3d1e4c520 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -1014,42 +1014,35 @@ get_member_list_json() { + podman exec "${CONTAINER}" etcdctl member list --endpoints="$this_node_endpoint" -w json + } + +-check_peers() ++detect_cluster_leadership_loss() + { +- # Check peers endpoint status and locally accessible member list +- local member_list_json +- +- if ! container_exists; then +- # we need a running container to execute etcdctl. +- return $OCF_SUCCESS ++ endpoint_status_json=$(get_endpoint_status_json) ++ ocf_log info "endpoint status: $endpoint_status_json" ++ ++ count_endpoints=$(printf "%s" "$endpoint_status_json" | jq -r ".[].Endpoint" | wc -l) ++ if [ "$count_endpoints" -eq 1 ]; then ++ ocf_log info "one endpoint only: checking status errors" ++ endpoint_status_errors=$(printf "%s" "$endpoint_status_json" | jq -r ".[0].Status.errors") ++ if echo "$endpoint_status_errors" | grep -q "no leader"; then ++ set_force_new_cluster ++ set_standalone_node ++ ocf_exit_reason "$NODENAME must force a new cluster" ++ return $OCF_ERR_GENERIC ++ fi ++ if [ "$endpoint_status_errors" != "null" ]; then ++ ocf_log err "unmanaged endpoint status error: $endpoint_status_errors" ++ fi + fi + +- member_list_json=$(get_member_list_json) +- rc=$? +- ocf_log debug "member list: $member_list_json" +- if [ $rc -ne 0 ]; then +- ocf_log info "podman failed to get member list, error code: $rc" +- +- endpoint_status_json=$(get_endpoint_status_json) +- ocf_log info "endpoint status: $endpoint_status_json" +- +- count_endpoints=$(printf "%s" "$endpoint_status_json" | jq -r ".[].Endpoint" | wc -l) +- if [ "$count_endpoints" -eq 1 ]; then +- ocf_log info "one endpoint only: checking status errors" +- endpoint_status_errors=$(printf "%s" "$endpoint_status_json" | jq -r ".[0].Status.errors") +- if echo "$endpoint_status_errors" | grep -q "no leader"; then +- set_force_new_cluster +- set_standalone_node +- ocf_exit_reason "$NODENAME must force a new cluster" +- return $OCF_ERR_GENERIC +- fi +- if [ "$endpoint_status_errors" != "null" ]; then +- ocf_log err "unmanaged endpoint status error: $endpoint_status_errors" +- fi +- fi ++ return $OCF_SUCCESS ++} + +- return $OCF_SUCCESS +- fi ++manage_peer_membership() ++{ ++ # Read etcd member list to detect the status of the peer member. ++ # If the peer is missing from the member list, it will be added back as learner ++ # If the peer is back in the member list, we ensure that the related CIB attributes (standalone and learner_node) are reset ++ local member_list_json="$1" + + # Example of .members[] instance fields in member list json format: + # NOTE that "name" is present in voting members only, while "isLearner" in learner members only +@@ -1083,6 +1076,25 @@ check_peers() + clear_standalone_and_learner_if_not_learners "$member_list_json" + fi + done ++} ++ ++check_peer() ++{ ++ # Check peers endpoint status and locally accessible member list ++ local member_list_json ++ ++ # we need a running container to execute etcdctl. ++ if ! container_exists; then ++ return $OCF_SUCCESS ++ fi ++ ++ if ! member_list_json=$(get_member_list_json); then ++ ocf_log info "podman failed to get member list, error code: $?" ++ detect_cluster_leadership_loss ++ return $? ++ fi ++ ++ manage_peer_membership "$member_list_json" + return $OCF_SUCCESS + } + +@@ -1124,7 +1136,7 @@ podman_monitor() + # monitor operation to fail. + # TODO: move this inside check_peers where we already query member list json + attribute_node_member_id update +- if ! check_peers; then ++ if ! check_peer; then + return $OCF_ERR_GENERIC + fi + + +From de7c73a933cefb8f7b9e810bd23c3d12f6d6f29a Mon Sep 17 00:00:00 2001 +From: Carlo Lobrano +Date: Tue, 30 Sep 2025 18:38:06 +0200 +Subject: [PATCH 2/2] OCPBUGS-42808: podman-etcd: add automatic learner member + promotion + +Automatically promote etcd learner members to voting members when detected. +Includes refactored member management functions and improved validation. +--- + heartbeat/podman-etcd | 108 ++++++++++++++++++++++++++++++------------ + 1 file changed, 79 insertions(+), 29 deletions(-) + +diff --git a/heartbeat/podman-etcd b/heartbeat/podman-etcd +index 3d1e4c520..e1425ec02 100755 +--- a/heartbeat/podman-etcd ++++ b/heartbeat/podman-etcd +@@ -712,6 +712,22 @@ attribute_node_revision_peer() + crm_attribute --query --type nodes --node "$nodename" --name "revision" | awk -F"value=" '{print $2}' + } + ++# Converts a decimal number to hexadecimal format with validation ++# Args: $1 - decimal number (test for non-negative integer too) ++# Returns: 0 on success, OCF_ERR_GENERIC on invalid input ++# Outputs: hexadecimal representation to stdout ++decimal_to_hex() { ++ local dec=$1 ++ ++ if ! echo "$dec" | grep -q "^[1-9][0-9]*$"; then ++ ocf_log err "Invalid member ID format: '$dec' (expected decimal number)" ++ return $OCF_ERR_GENERIC ++ fi ++ ++ printf "%x" "$dec" ++ return $OCF_SUCCESS ++} ++ + attribute_node_member_id() + { + local action="$1" +@@ -737,7 +753,7 @@ attribute_node_member_id() + return "$rc" + fi + +- local value ++ local value value_hex + if ! value=$(echo -n "$member_list_json" | jq -r ".header.member_id"); then + rc=$? + ocf_log err "could not get $attribute from member list JSON, error code: $rc" +@@ -745,8 +761,11 @@ attribute_node_member_id() + fi + + # JSON member_id is decimal, while etcdctl command needs the hex version +- value=$(printf "%x" "$value") +- if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$value"; then ++ if ! value_hex=$(decimal_to_hex "$value"); then ++ ocf_log err "could not convert decimal member_id '$value' to hex, error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ if ! crm_attribute --type nodes --node "$NODENAME" --name "$attribute" --update "$value_hex"; then + rc=$? + ocf_log err "could not update etcd $attribute, error code: $rc" + return "$rc" +@@ -905,42 +924,70 @@ clear_standalone_node() + crm_attribute --name "standalone_node" --delete + } + +-clear_standalone_and_learner_if_not_learners() ++ ++# Promotes an etcd learner member to a voting member ++# Args: $1 - learner member ID in decimal format ++# Returns: OCF_SUCCESS (even on expected promotion failures), OCF_ERR_GENERIC on conversion errors ++# Note: Promotion failures are expected and logged as info (peer may not be up-to-date) ++promote_learner_member() ++{ ++ local learner_member_id=$1 ++ ++ # JSON member_id is decimal, while etcdctl command needs the hex version ++ if ! learner_member_id_hex=$(decimal_to_hex "$learner_member_id"); then ++ ocf_log err "could not convert decimal member_id '$learner_member_id' to hex, error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ if ! ocf_run podman exec "${CONTAINER}" etcdctl member promote "$learner_member_id_hex" 2>&1; then ++ # promotion is expected to fail if the peer is not yet up-to-date ++ ocf_log info "could not promote member $learner_member_id_hex, error code: $?" ++ return $OCF_SUCCESS ++ fi ++ ocf_log info "successfully promoted member '$learner_member_id_hex'" ++ return $OCF_SUCCESS ++} ++ ++# Reconciles etcd cluster member states ++# Promotes learner members or clears standalone/learner attributes as needed ++# Args: $1 - member list JSON from etcdctl ++# Returns: OCF_SUCCESS on completion, OCF_ERR_GENERIC on errors ++# Note: Only operates when exactly 2 started members are present ++reconcile_member_state() + { + local rc + local member_list_json="$1" + +- number_of_members=$(printf "%s" "$member_list_json" | jq -r ".members[].ID" | wc -l) +- if [ "$number_of_members" -ne 2 ]; then +- ocf_log info "could not clear standalone_node, nor learner_node properties: found $number_of_members members, need 2" ++ # count only the started members, which have the ".name" JSON field ++ number_of_started_members=$(printf "%s" "$member_list_json" | jq -r ".members[].name | select(. != null)" | wc -l) ++ if [ "$number_of_started_members" -ne 2 ]; then ++ ocf_log info "could not clear standalone_node, nor learner_node properties: found $number_of_started_members members, need 2" + return $OCF_SUCCESS + fi + +- id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .isLearner==true ).ID") ++ learner_member_id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .isLearner==true ).ID") + rc=$? + if [ $rc -ne 0 ]; then + ocf_log err "could not get isLearner field from member list, error code: $rc" + return $rc + fi + +- if [ -z "$id" ]; then +- clear_standalone_node +- rc=$? +- if [ $rc -ne 0 ]; then +- ocf_og error "could not clear standalone_node attribute, error code: $rc" +- return $rc +- fi ++ if [ -n "$learner_member_id" ]; then ++ promote_learner_member "$learner_member_id" ++ return $? + fi +- if [ -z "$id" ]; then +- attribute_learner_node clear +- rc=$? +- if [ $rc -ne 0 ]; then +- ocf_og error "could not clear learner_node attribute, error code: $rc" +- return $rc ++ ++ if [ -z "$learner_member_id" ]; then ++ if ! clear_standalone_node; then ++ ocf_log error "could not clear standalone_node attribute, error code: $?" ++ return $OCF_ERR_GENERIC ++ fi ++ if ! attribute_learner_node clear; then ++ ocf_log error "could not clear learner_node attribute, error code: $?" ++ return $OCF_ERR_GENERIC + fi + fi + +- return $rc ++ return $OCF_SUCCESS + } + + attribute_learner_node() +@@ -1019,7 +1066,7 @@ detect_cluster_leadership_loss() + endpoint_status_json=$(get_endpoint_status_json) + ocf_log info "endpoint status: $endpoint_status_json" + +- count_endpoints=$(printf "%s" "$endpoint_status_json" | jq -r ".[].Endpoint" | wc -l) ++ count_endpoints=$(printf "%s" "$endpoint_status_json" | jq -r ".[].Endpoint" | wc -l) + if [ "$count_endpoints" -eq 1 ]; then + ocf_log info "one endpoint only: checking status errors" + endpoint_status_errors=$(printf "%s" "$endpoint_status_json" | jq -r ".[0].Status.errors") +@@ -1037,11 +1084,14 @@ detect_cluster_leadership_loss() + return $OCF_SUCCESS + } + ++ ++# Manages etcd peer membership by detecting and handling missing or rejoining peers ++# Adds missing peers as learners and reconciles member states when peers rejoin ++# Args: $1 - member list JSON from etcdctl ++# Returns: OCF_SUCCESS on completion, OCF_ERR_GENERIC on errors ++# Note: Iterates through all peer nodes to ensure proper cluster membership + manage_peer_membership() + { +- # Read etcd member list to detect the status of the peer member. +- # If the peer is missing from the member list, it will be added back as learner +- # If the peer is back in the member list, we ensure that the related CIB attributes (standalone and learner_node) are reset + local member_list_json="$1" + + # Example of .members[] instance fields in member list json format: +@@ -1066,14 +1116,14 @@ manage_peer_membership() + + # Check by IP instead of Name since "learner" members appear only in peerURLs, not by Name. + ip=$(echo "$node" | cut -d: -f2-) # Grab everything after the first : this covers ipv4/ipv6 +- id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .peerURLs | map(test(\"$ip\")) | any).ID") +- if [ -z "$id" ]; then ++ peer_member_id=$(printf "%s" "$member_list_json" | jq -r ".members[] | select( .peerURLs | map(test(\"$ip\")) | any).ID") ++ if [ -z "$peer_member_id" ]; then + ocf_log info "$name is not in the members list" + add_member_as_learner "$name" "$ip" + set_standalone_node + else + ocf_log debug "$name is in the members list by IP: $ip" +- clear_standalone_and_learner_if_not_learners "$member_list_json" ++ reconcile_member_state "$member_list_json" + fi + done + } diff --git a/SOURCES/RHEL-79819-portblock-fix-version-detection.patch b/SOURCES/RHEL-86602-portblock-fix-version-detection.patch similarity index 100% rename from SOURCES/RHEL-79819-portblock-fix-version-detection.patch rename to SOURCES/RHEL-86602-portblock-fix-version-detection.patch diff --git a/SOURCES/RHEL-88429-1-podman-etcd-new-ra.patch b/SOURCES/RHEL-88430-1-podman-etcd-new-ra.patch similarity index 99% rename from SOURCES/RHEL-88429-1-podman-etcd-new-ra.patch rename to SOURCES/RHEL-88430-1-podman-etcd-new-ra.patch index f294582..455f232 100644 --- a/SOURCES/RHEL-88429-1-podman-etcd-new-ra.patch +++ b/SOURCES/RHEL-88430-1-podman-etcd-new-ra.patch @@ -19,7 +19,7 @@ index bc8935782..0d34c7c65 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -187,6 +187,7 @@ man_MANS = ocf_heartbeat_AoEtarget.7 \ - ocf_heartbeat_pgsqlms.7 \ + ocf_heartbeat_pgsql.7 \ ocf_heartbeat_pingd.7 \ ocf_heartbeat_podman.7 \ + ocf_heartbeat_podman-etcd.7 \ @@ -31,7 +31,7 @@ index 5c41e0038..839505af9 100644 --- a/heartbeat/Makefile.am +++ b/heartbeat/Makefile.am @@ -159,6 +159,7 @@ ocf_SCRIPTS = AoEtarget \ - pgsqlms \ + pgsql \ pingd \ podman \ + podman-etcd \ diff --git a/SOURCES/RHEL-88429-2-podman-etcd-remove-unused-actions-from-metadata.patch b/SOURCES/RHEL-88430-2-podman-etcd-remove-unused-actions-from-metadata.patch similarity index 100% rename from SOURCES/RHEL-88429-2-podman-etcd-remove-unused-actions-from-metadata.patch rename to SOURCES/RHEL-88430-2-podman-etcd-remove-unused-actions-from-metadata.patch diff --git a/SOURCES/RHEL-88429-3-podman-etcd-fix-listen-peer-urls-binding.patch b/SOURCES/RHEL-88430-3-podman-etcd-fix-listen-peer-urls-binding.patch similarity index 100% rename from SOURCES/RHEL-88429-3-podman-etcd-fix-listen-peer-urls-binding.patch rename to SOURCES/RHEL-88430-3-podman-etcd-fix-listen-peer-urls-binding.patch diff --git a/SOURCES/RHEL-88035-Filesystem-add-support-for-aznfs.patch b/SOURCES/RHEL-92481-Filesystem-add-support-for-aznfs.patch similarity index 100% rename from SOURCES/RHEL-88035-Filesystem-add-support-for-aznfs.patch rename to SOURCES/RHEL-92481-Filesystem-add-support-for-aznfs.patch diff --git a/SPECS/resource-agents.spec b/SPECS/resource-agents.spec index cf9ec57..85be28b 100644 --- a/SPECS/resource-agents.spec +++ b/SPECS/resource-agents.spec @@ -45,7 +45,7 @@ Name: resource-agents Summary: Open Source HA Reusable Cluster Resource Scripts Version: 4.10.0 -Release: 71%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.6 +Release: 71%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.11 License: GPLv2+ and LGPLv2+ URL: https://github.com/ClusterLabs/resource-agents Source0: %{upstream_prefix}-%{upstream_version}.tar.gz @@ -148,12 +148,17 @@ Patch95: RHEL-66292-2-aws-agents-reuse-imds-token-improvements.patch Patch96: RHEL-68739-awsvip-add-interface-parameter.patch Patch97: RHEL-69734-1-openstack-cinder-volume-wait-for-volume-to-be-available.patch Patch98: RHEL-69734-2-openstack-cinder-volume-fix-detach-not-working-during-start-action.patch -Patch105: RHEL-79819-portblock-fix-version-detection.patch -Patch106: RHEL-88035-Filesystem-add-support-for-aznfs.patch -Patch107: RHEL-88429-1-podman-etcd-new-ra.patch -Patch108: RHEL-88429-2-podman-etcd-remove-unused-actions-from-metadata.patch -Patch109: RHEL-88429-3-podman-etcd-fix-listen-peer-urls-binding.patch -Patch110: RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch +Patch99: RHEL-86602-portblock-fix-version-detection.patch +Patch100: RHEL-88430-1-podman-etcd-new-ra.patch +Patch101: RHEL-88430-2-podman-etcd-remove-unused-actions-from-metadata.patch +Patch102: RHEL-88430-3-podman-etcd-fix-listen-peer-urls-binding.patch +Patch103: RHEL-92481-Filesystem-add-support-for-aznfs.patch +Patch104: RHEL-101705-Filesystem-fix-issue-with-Vormetric-mounts.patch +Patch105: RHEL-113108-podman-etcd-add-oom-parameter.patch +Patch106: RHEL-113813-podman-etcd-wrap-ipv6-address-in-brackets.patch +Patch107: RHEL-113810-podman-etcd-preserve-containers-for-debugging.patch +Patch108: RHEL-116208-podman-etcd-add-cluster-wide-force_new_cluster-attribute-check.patch +Patch109: RHEL-119502-podman-etcd-add-automatic-learner-member-promotion.patch # bundled ha-cloud-support libs Patch500: ha-cloud-support-aliyun.patch @@ -376,12 +381,17 @@ exit 1 %patch -p1 -P 96 %patch -p1 -P 97 %patch -p1 -P 98 +%patch -p1 -P 99 +%patch -p1 -P 100 -F1 +%patch -p1 -P 101 +%patch -p1 -P 102 +%patch -p1 -P 103 +%patch -p1 -P 104 %patch -p1 -P 105 %patch -p1 -P 106 %patch -p1 -P 107 %patch -p1 -P 108 %patch -p1 -P 109 -%patch -p1 -P 110 # bundled ha-cloud-support libs %patch -p1 -P 500 @@ -711,19 +721,43 @@ rm -rf %{buildroot}/usr/share/doc/resource-agents %{_usr}/lib/ocf/lib/heartbeat/OCF_*.pm %changelog -* Tue Aug 05 2025 Alan Steinberg - 4.10.0-71.6 +* Wed Oct 15 2025 Oyvind Albrigtsen - 4.10.0-71.11 +- podman-etcd: add automatic learner member promotion + + Resolves: RHEL-119502 + +* Mon Sep 22 2025 Oyvind Albrigtsen - 4.10.0-71.8 +- podman-etcd: wrap ipv6 address in brackets +- podman-etcd: preserve containers for debugging +- podman-etcd: add cluster-wide force_new_cluster attribute check + + Resolves: RHEL-113813, RHEL-113810, RHEL-116208 + +* Mon Sep 8 2025 Oyvind Albrigtsen - 4.10.0-71.7 +- podman-etcd: add oom parameter to be able to tune the Out-Of-Memory (OOM) + score for etcd containers + + Resolves: RHEL-113108 + +* Thu Jul 3 2025 Oyvind Albrigtsen - 4.10.0-71.6 - Filesystem: fix issue with Vormetric mounts Resolves: RHEL-101705 -* Mon Apr 28 2025 Oyvind Albrigtsen - 4.10.0-71.5 +* Tue May 20 2025 Oyvind Albrigtsen - 4.10.0-71.5 - podman-etcd: new resource agent + + Resolves: RHEL-88430 + +* Mon May 19 2025 Oyvind Albrigtsen - 4.10.0-71.4 - Filesystem: add support for aznfs + + Resolves: RHEL-92481 + +* Wed Apr 9 2025 Oyvind Albrigtsen - 4.10.0-71.1 - portblock: fix iptables version detection - Resolves: RHEL-88429 - Resolves: RHEL-88035 - Resolves: HEL-79819 + Resolves: RHEL-86602 * Fri Jan 10 2025 Oyvind Albrigtsen - 4.10.0-71 - openstack-cinder-volume: wait for volume to be available