import Oracle_OSS resource-agents-4.16.0-22.el10_1.9

This commit is contained in:
AlmaLinux RelEng Bot 2026-04-29 07:36:57 -04:00
parent 4eeaacdd08
commit 7e7123baaa
15 changed files with 2180 additions and 13 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,197 @@
From a4e496e5e6d9abde1b071fa2dfa1c6e7ba899cf1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edmund=20H=C3=A4fele?= <edmund.haefele@de.ibm.com>
Date: Thu, 30 Oct 2025 13:03:22 +0100
Subject: [PATCH] Update powervs-move-ip
- Add `iflabel` argument.
- Increase maximum number of retries for HTTP requests to four.
---
heartbeat/powervs-move-ip.in | 66 +++++++++++++++++++++++++-----------
1 file changed, 47 insertions(+), 19 deletions(-)
diff --git a/heartbeat/powervs-move-ip.in b/heartbeat/powervs-move-ip.in
index d55979e52..e2250c998 100755
--- a/heartbeat/powervs-move-ip.in
+++ b/heartbeat/powervs-move-ip.in
@@ -50,11 +50,13 @@ RESOURCE_OPTIONS = (
"use_token_cache",
"monitor_api",
"device",
+ "iflabel",
"proxy",
)
IP_CMD = "/usr/sbin/ip"
+IFLABEL_MAX_LEN = 15 # Maximum character limit for interface labels
REQUESTS_TIMEOUT = 5 # Timeout for requests calls
-HTTP_MAX_RETRIES = 3 # Maximum number of retries for HTTP requests
+HTTP_MAX_RETRIES = 4 # Maximum number of retries for HTTP requests
HTTP_BACKOFF_FACTOR = 0.3 # Sleep (factor * (2^number of previous retries)) secs
HTTP_STATUS_FORCE_RETRIES = (500, 502, 503, 504) # HTTP status codes to retry on
HTTP_RETRY_ALLOWED_METHODS = frozenset({"GET", "POST", "PUT", "DELETE"})
@@ -154,13 +156,13 @@ def ip_check_device(device):
return False
-def ip_alias_add(ip, device):
+def ip_alias_add(ip, device, label=None):
"""Add an IP alias to the given device."""
ip_cidr = f"{ip}/{CIDR_NETMASK}"
ocf.logger.debug(
- f"[ip_alias_add]: adding IP alias '{ip_cidr}' to interface '{device}'"
+ f"[ip_alias_add]: adding IP alias '{ip_cidr}' with label '{label}' to interface '{device}'"
)
- _ = ip_address_add(ip_cidr, device)
+ _ = ip_address_add(ip_cidr, device, label)
def ip_alias_remove(ip):
@@ -522,6 +524,7 @@ class PowerCloudRoute(PowerCloudAPI):
region="",
route_host_map="",
device="",
+ iflabel="",
proxy="",
monitor_api="",
use_token_cache="",
@@ -543,6 +546,7 @@ class PowerCloudRoute(PowerCloudAPI):
self.route_info = self._get_route_info()
self.route_name = self.route_info["name"]
self.device = self._get_device_name(device)
+ self.iflabel = self._make_iflabel(iflabel)
def _get_ip_info(self, ip):
"""Validate the given IP address and return its standard form."""
@@ -588,7 +592,7 @@ class PowerCloudRoute(PowerCloudAPI):
nodename = (
hostname
if not self._is_remote_route
- else next((h for h in route_map if h != hostname), None)
+ else next((host for host in route_map if host != hostname), None)
)
if not nodename or nodename not in route_map:
@@ -646,6 +650,21 @@ class PowerCloudRoute(PowerCloudAPI):
ocf.OCF_ERR_CONFIGURED,
)
+ def _make_iflabel(self, label=None):
+ """Constructs an interface label in the format 'device:label' if both are provided."""
+ if not label or self._is_remote_route:
+ return None
+
+ iflabel = f"{self.device}:{label}"
+
+ if len(iflabel) > IFLABEL_MAX_LEN:
+ raise PowerCloudRouteError(
+ f"_make_iflabel: interface label '{iflabel}' exceeds limit of {IFLABEL_MAX_LEN} characters",
+ ocf.OCF_ERR_CONFIGURED,
+ )
+
+ return iflabel
+
def _set_route_enabled(self, enabled: bool):
"""Enable or disable the PowerVS network route."""
resource = f"/v1/routes/{self.route_id}"
@@ -706,6 +725,7 @@ def start_action(
use_token_cache="",
monitor_api="",
device="",
+ iflabel="",
proxy="",
):
"""Assign the service IP.
@@ -730,7 +750,7 @@ def start_action(
local_route = create_route_instance(resource_options)
# Add IP alias
- ip_alias_add(ip, local_route.device)
+ ip_alias_add(ip, local_route.device, local_route.iflabel)
# Enable local route
ocf.logger.debug(f"[start_action]: enabling local route '{local_route.route_name}'")
@@ -758,6 +778,7 @@ def stop_action(
use_token_cache="",
monitor_api="",
device="",
+ iflabel="",
proxy="",
):
"""Remove the service IP.
@@ -810,6 +831,7 @@ def monitor_action(
use_token_cache="",
monitor_api="",
device="",
+ iflabel="",
proxy="",
):
"""Monitor the service IP.
@@ -829,15 +851,11 @@ def monitor_action(
interface_name = ip_find_device(ip)
if not use_extended_monitor:
- if interface_name:
- ocf.logger.debug(
- f"[monitor_action]: IP alias '{ip}' is active'"
- )
+ if interface_name:
+ ocf.logger.debug(f"[monitor_action]: IP alias '{ip}' is active'")
return ocf.OCF_SUCCESS
- else:
- ocf.logger.debug(
- f"[monitor_action]: IP alias '{ip}' is not active"
- )
+ else:
+ ocf.logger.debug(f"[monitor_action]: IP alias '{ip}' is not active")
return ocf.OCF_NOT_RUNNING
remote_route = create_route_instance(
@@ -893,6 +911,7 @@ def validate_all_action(
use_token_cache="",
monitor_api="",
device="",
+ iflabel="",
proxy="",
):
"""Validate resource agent parameters.
@@ -914,12 +933,10 @@ def main():
Resource Agent to move an IP address from one Power Virtual Server instance to another.
Prerequisites:
- 1. Red Hat Enterprise Linux 9.4 or higher
-
- 2. Two-node cluster
+ 1. Two-node cluster
- Distributed across two PowerVS workspaces in separate data centers within the same region.
- 3. IBM Cloud API Key:
+ 2. IBM Cloud API Key:
- Create a service API key with privileges for both workspaces.
- Save the key in a file and copy it to both cluster nodes using the same path and filename.
- Reference the key file path in the resource definition.
@@ -932,7 +949,7 @@ def main():
"powervs-move-ip",
shortdesc="Manages Power Virtual Server overlay IP routes.",
longdesc=agent_description,
- version=1.00,
+ version=1.01,
)
agent.add_parameter(
@@ -1011,6 +1028,17 @@ def main():
default="",
required=False,
)
+ agent.add_parameter(
+ "iflabel",
+ shortdesc="Network interface label",
+ longdesc=(
+ "A custom suffix for the IP address label. "
+ "It is appended to the interface name in the format device:label. "
+ "The full label must not exceed 15 characters. "
+ ),
+ content_type="string",
+ required=False,
+ )
agent.add_parameter(
"proxy",
shortdesc="Proxy",

View File

@ -0,0 +1,19 @@
--- a/heartbeat/ocf-shellfuncs.in 2025-09-29 14:01:55.762931795 +0200
+++ b/heartbeat/ocf-shellfuncs.in 2025-09-29 14:09:28.651731793 +0200
@@ -1093,6 +1093,16 @@
echo $1
}
+ocf_promotion_score() {
+ ocf_version_cmp "$OCF_RESKEY_crm_feature_set" "3.10.0"
+ res=$?
+ if [ $res -eq 2 ] || [ $res -eq 1 ] || ! have_binary "crm_master"; then
+ ${HA_SBIN_DIR}/crm_attribute -p ${OCF_RESOURCE_INSTANCE} $@
+ else
+ ${HA_SBIN_DIR}/crm_master -l reboot $@
+ fi
+}
+
__ocf_set_defaults "$@"
: ${OCF_TRACE_RA:=$OCF_RESKEY_trace_ra}

View File

@ -0,0 +1,362 @@
--- a/heartbeat/portblock 2025-09-30 09:52:13.967530030 +0200
+++ b/heartbeat/portblock 2025-09-30 09:52:49.018382542 +0200
@@ -4,6 +4,7 @@
#
# Author: Sun Jiang Dong (initial version)
# Philipp Reisner (per-IP filtering)
+# Sebastian Baszczyj (nftables code)
#
# License: GNU General Public License (GPL)
#
@@ -43,11 +44,15 @@
#######################################################################
CMD=`basename $0`
TICKLETCP=$HA_BIN/tickle_tcp
+TABLE="portblock"
+# Promotion scores
+SCORE_UNPROMOTED=5
+SCORE_PROMOTED=10
usage()
{
cat <<END >&2
- usage: $CMD {start|stop|status|monitor|meta-data|validate-all}
+ usage: $CMD {start|stop|promote|demote|status|monitor|meta-data|validate-all}
$CMD is used to temporarily block ports using iptables.
@@ -86,8 +91,8 @@
NOTE: iptables is Linux-specific.
An additional feature in the portblock RA is the tickle ACK function
- enabled by specifying the tickle_dir parameter. The tickle ACK
- triggers the clients to faster reconnect their TCP connections to the
+ enabled by specifying the tickle_dir parameter. The tickle ACK
+ triggers the clients to faster reconnect their TCP connections to the
fail-overed server.
Please note that this feature is often used for the floating IP fail-
@@ -95,7 +100,7 @@
It doesn't support the cluster alias IP scenario.
When using the tickle ACK function, in addition to the normal usage
- of portblock RA, the parameter tickle_dir must be specified in the
+ of portblock RA, the parameter tickle_dir must be specified in the
action=unblock instance of the portblock resources.
For example, you may stack resources like below:
portblock action=block
@@ -103,18 +108,18 @@
portblock action=unblock tickle_dir=/tickle/state/dir
If you want to tickle all the TCP connections which connected to _one_
- floating IP but different ports, no matter how many portblock resources
- you have defined, you should enable tickles for _one_ portblock
+ floating IP but different ports, no matter how many portblock resources
+ you have defined, you should enable tickles for _one_ portblock
resource(action=unblock) only.
-
- The tickle_dir is a location which stores the established TCP
- connections. It can be a shared directory(which is cluster-visible to
+
+ The tickle_dir is a location which stores the established TCP
+ connections. It can be a shared directory(which is cluster-visible to
all nodes) or a local directory.
If you use the shared directory, you needn't do any other things.
If you use the local directory, you must also specify the sync_script
paramater. We recommend you to use csync2 as the sync_script.
- For example, if you use the local directory /tmp/tickle as tickle_dir,
- you could setup the csync2 as the csync2 documentation says and
+ For example, if you use the local directory /tmp/tickle as tickle_dir,
+ you could setup the csync2 as the csync2 documentation says and
configure your /etc/csync2/csync2.cfg like:
group ticklegroup {
host node1;
@@ -137,15 +142,19 @@
<version>1.0</version>
<longdesc lang="en">
-Resource script for portblock. It is used to temporarily block ports
+Resource script for portblock. It is used to block ports
using iptables. In addition, it may allow for faster TCP reconnects
for clients on failover. Use that if there are long lived TCP
connections to an HA service. This feature is enabled by setting the
tickle_dir parameter and only in concert with action set to unblock.
Note that the tickle ACK function is new as of version 3.0.2 and
hasn't yet seen widespread use.
+
+In Promotable mode, the promote action unblocks the port(s) on the Promoted node
+and blocks the port(s) on the Unpromoted node(s) when action=unblock, and vice versa
+when action=block.
</longdesc>
-<shortdesc lang="en">Block and unblocks access to TCP and UDP ports</shortdesc>
+<shortdesc lang="en">Blocks and unblocks access to TCP and UDP ports</shortdesc>
<parameters>
<parameter name="protocol" unique="0" required="1">
@@ -167,6 +176,10 @@
<parameter name="action" unique="0" required="1">
<longdesc lang="en">
The action (block/unblock) to be done on the protocol::portno.
+
+In Promotable mode it is the action for the promote action,
+and the opposite action will be used for the start and demote
+actions.
</longdesc>
<shortdesc lang="en">action</shortdesc>
<content type="string" default="${OCF_RESKEY_action_default}" />
@@ -202,7 +215,7 @@
<parameter name="tickle_dir" unique="0" required="0">
<longdesc lang="en">
-The shared or local directory (_must_ be absolute path) which
+The shared or local directory (_must_ be absolute path) which
stores the established TCP connections.
</longdesc>
<shortdesc lang="en">Tickle directory</shortdesc>
@@ -236,6 +249,8 @@
<actions>
<action name="start" timeout="20s" />
<action name="stop" timeout="20s" />
+<action name="promote" timeout="10s"/>
+<action name="demote" timeout="10s"/>
<action name="status" depth="0" timeout="10s" interval="10s" />
<action name="monitor" depth="0" timeout="10s" interval="10s" />
<action name="meta-data" timeout="5s" />
@@ -269,9 +284,9 @@
# iptables 1.8.9 briefly broke the output format, returning the
# numeric protocol value instead of a string. Support both variants.
if [ "$1" = "tcp" ]; then
- local prot="(tcp|6)"
+ local prot="\(tcp\|6\)"
else
- local prot="(udp|17)"
+ local prot="\(udp\|17\)"
fi
echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$"
}
@@ -281,7 +296,7 @@
{
[ "$4" = "OUTPUT" ] && ds="s" || ds="d"
PAT=$(active_grep_pat "$1" "$2" "$3" "$ds")
- $IPTABLES $wait -n -L "$4" | grep -qE "$PAT"
+ $IPTABLES $wait -n -L "$4" | grep -q "$PAT"
}
# netstat -tn and ss -Htn, split on whitespace and colon,
@@ -397,6 +412,17 @@
rc=$OCF_NOT_RUNNING
;;
esac
+ elif ocf_is_ms; then
+ case $5 in
+ block)
+ SayInactive $*
+ rc=$OCF_NOT_RUNNING
+ ;;
+ *)
+ SayActive $*
+ rc=$OCF_SUCCESS
+ ;;
+ esac
else
case $5 in
block)
@@ -493,18 +519,21 @@
{
ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start
case $5 in
- block) IptablesBLOCK "$@";;
+ block) IptablesBLOCK "$@"
+ rc=$?
+ ;;
unblock)
IptablesUNBLOCK "$@"
rc=$?
tickle_remote
#ignore run_tickle_tcp exit code!
- return $rc
;;
- *) usage; return 1;
+ *) usage; return $OCF_ERR_CONFIGURED ;
esac
- return $?
+ ocf_is_ms && ocf_promotion_score -v $SCORE_UNPROMOTED -N $nodename
+
+ return $rc
}
#IptablesStop {udp|tcp} portno,portno ip {in|out|both} {block|unblock}
@@ -512,17 +541,73 @@
{
ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop
case $5 in
- block) IptablesUNBLOCK "$@";;
+ block) IptablesUNBLOCK "$@"
+ rc=$?
+ ;;
unblock)
save_tcp_connections
IptablesBLOCK "$@"
+ rc=$?
;;
- *) usage; return 1;;
+ *) usage; return $OCF_ERR_CONFIGURED ;;
esac
+ ocf_is_ms && ocf_promotion_score -D -N $nodename
+
+ return $rc
+}
+
+IptablesPromote() {
+ IptablesStatus "$@"
+ rc=$?
+ if [ $rc -eq $OCF_SUCCESS ] && [ $promotion_score -eq $SCORE_PROMOTED ]; then
+ ocf_log info "Promote: resource already promoted."
+ return $rc
+ elif [ $rc -ne $OCF_SUCCESS ] && [ $rc -ne $OCF_NOT_RUNNING ]; then
+ ocf_exit_reason "Promote: IptablesStatus failed with rc: $rc."
+ return $rc
+ fi
+ case $5 in
+ block) IptablesBLOCK "$@"
+ rc=$?
+ ;;
+ unblock)
+ IptablesUNBLOCK "$@"
+ rc=$?
+ tickle_remote
+ #ignore run_tickle_tcp exit code!
+ ;;
+ *) usage; return $OCF_ERR_CONFIGURED ;
+ esac
+ ocf_promotion_score -v $SCORE_PROMOTED -N $nodename
return $?
}
+IptablesDemote() {
+ IptablesStatus "$@"
+ rc=$?
+ if [ $rc -eq $OCF_SUCCESS ] && [ $promotion_score -eq $SCORE_UNPROMOTED ]; then
+ ocf_log info "Demote: resource already demoted."
+ return $rc
+ elif [ $rc -ne $OCF_SUCCESS ] && [ $rc -ne $OCF_NOT_RUNNING ]; then
+ ocf_exit_reason "Demote: IptablesStatus failed with rc: $rc."
+ return $rc
+ fi
+ case $5 in
+ block)
+ save_tcp_connections
+ IptablesBLOCK "$@"
+ rc=$?
+ ;;
+ unblock) IptablesUNBLOCK "$@"
+ rc=$?
+ ;;
+ *) usage; return $OCF_ERR_CONFIGURED ;;
+ esac
+ ocf_promotion_score -v $SCORE_UNPROMOTED -N $nodename
+ return $rc
+}
+
#
# Check if the port is valid, this function code is not decent, but works
#
@@ -558,17 +643,17 @@
fi
if [ ! -d "$OCF_RESKEY_tickle_dir" ]; then
ocf_log err "The tickle dir doesn't exist!"
- exit $OCF_ERR_INSTALLED
+ exit $OCF_ERR_INSTALLED
fi
fi
case $action in
- block|unblock)
+ block|unblock)
;;
- *)
+ *)
ocf_log err "Invalid action $action!"
exit $OCF_ERR_CONFIGURED
- ;;
+ ;;
esac
if ocf_is_true $reset_local_on_unblock_stop; then
@@ -591,7 +676,7 @@
exit $OCF_ERR_ARGS
fi
-case $1 in
+case $__OCF_ACTION in
meta-data) meta_data
exit $OCF_SUCCESS
;;
@@ -605,12 +690,12 @@
if [ -z "$OCF_RESKEY_protocol" ]; then
ocf_log err "Please set OCF_RESKEY_protocol"
exit $OCF_ERR_CONFIGURED
-fi
+fi
if [ -z "$OCF_RESKEY_portno" ]; then
ocf_log err "Please set OCF_RESKEY_portno"
exit $OCF_ERR_CONFIGURED
-fi
+fi
if [ -z "$OCF_RESKEY_action" ]; then
ocf_log err "Please set OCF_RESKEY_action"
@@ -632,6 +717,7 @@
action=$OCF_RESKEY_action
ip=$OCF_RESKEY_ip
reset_local_on_unblock_stop=$OCF_RESKEY_reset_local_on_unblock_stop
+nodename=$(ocf_local_nodename)
# If "tickle" is enabled, we need to record the list of currently established
@@ -647,17 +733,35 @@
fi
fi
-case $1 in
- start)
- IptablesStart $protocol $portno $ip $direction $action
+if ocf_is_ms; then
+ promotion_score=$(ocf_promotion_score -G -N $nodename -q 2> /dev/null)
+ if { [ "$__OCF_ACTION" = "monitor" ] && [ "$promotion_score" = "$SCORE_UNPROMOTED" ]; } || [ "$__OCF_ACTION" = "demote" ] || [ "$__OCF_ACTION" = "start" ]; then
+ case $action in
+ block) action="unblock" ;;
+ unblock) action="block" ;;
+ esac
+ fi
+fi
+
+case $__OCF_ACTION in
+ start)
+ IptablesStart "$protocol" "$portno" "$ip" "$direction" "$action"
+ ;;
+
+ stop)
+ IptablesStop "$protocol" "$portno" "$ip" "$direction" "$action"
+ ;;
+
+ promote)
+ IptablesPromote "$protocol" "$portno" "$ip" "$direction" "$action"
;;
- stop)
- IptablesStop $protocol $portno $ip $direction $action
+ demote)
+ IptablesDemote "$protocol" "$portno" "$ip" "$direction" "$action"
;;
- status|monitor)
- IptablesStatus $protocol $portno $ip $direction $action
+ status|monitor)
+ IptablesStatus "$protocol" "$portno" "$ip" "$direction" "$action"
;;
validate-all)

View File

@ -0,0 +1,180 @@
--- a/heartbeat/portblock 2025-10-21 09:27:41.753028260 +0200
+++ b/heartbeat/portblock 2025-10-21 09:28:55.573855995 +0200
@@ -28,6 +28,8 @@
OCF_RESKEY_portno_default=""
OCF_RESKEY_direction_default="in"
OCF_RESKEY_action_default=""
+OCF_RESKEY_method_default="drop"
+OCF_RESKEY_status_check_default="rule"
OCF_RESKEY_ip_default="0.0.0.0/0"
OCF_RESKEY_reset_local_on_unblock_stop_default="false"
OCF_RESKEY_tickle_dir_default=""
@@ -37,6 +39,8 @@
: ${OCF_RESKEY_portno=${OCF_RESKEY_portno_default}}
: ${OCF_RESKEY_direction=${OCF_RESKEY_direction_default}}
: ${OCF_RESKEY_action=${OCF_RESKEY_action_default}}
+: ${OCF_RESKEY_method=${OCF_RESKEY_method_default}}
+: ${OCF_RESKEY_status_check=${OCF_RESKEY_status_check_default}}
: ${OCF_RESKEY_ip=${OCF_RESKEY_ip_default}}
: ${OCF_RESKEY_reset_local_on_unblock_stop=${OCF_RESKEY_reset_local_on_unblock_stop_default}}
: ${OCF_RESKEY_tickle_dir=${OCF_RESKEY_tickle_dir_default}}
@@ -185,6 +189,26 @@
<content type="string" default="${OCF_RESKEY_action_default}" />
</parameter>
+<parameter name="method" unique="0" required="0">
+<longdesc lang="en">
+Block method:
+drop: Use DROP rule.
+reject: Use REJECT rule w/conntrack to clear connections when blocking.
+</longdesc>
+<shortdesc lang="en">Block method</shortdesc>
+<content type="string" default="${OCF_RESKEY_method_default}" />
+</parameter>
+
+<parameter name="status_check" unique="0" required="0">
+<longdesc lang="en">
+Status check:
+rule: Check rule.
+pseudo: Check pseudo status when rule is absent.
+</longdesc>
+<shortdesc lang="en">Status check</shortdesc>
+<content type="string" default="${OCF_RESKEY_status_check_default}" />
+</parameter>
+
<parameter name="reset_local_on_unblock_stop" unique="0" required="0">
<longdesc lang="en">
If for some reason the long lived server side TCP sessions won't be cleaned up
@@ -253,6 +277,7 @@
<action name="demote" timeout="10s"/>
<action name="status" depth="0" timeout="10s" interval="10s" />
<action name="monitor" depth="0" timeout="10s" interval="10s" />
+<action name="monitor" depth="0" timeout="10s" interval="9s" role="Promoted" />
<action name="meta-data" timeout="5s" />
<action name="validate-all" timeout="5s" />
</actions>
@@ -288,7 +313,11 @@
else
local prot="\(udp\|17\)"
fi
- echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$"
+ if [ "$method" = "DROP" ]; then
+ echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$"
+ else
+ echo "^REJECT${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}${w}ctstate${w}NEW,RELATED,ESTABLISHED${w}reject-with${w}tcp-reset$"
+ fi
}
#chain_isactive {udp|tcp} portno,portno ip chain
@@ -374,17 +403,17 @@
SayActive()
{
- ocf_log debug "$CMD DROP rule [$*] is running (OK)"
+ ocf_log debug "$CMD $method rule [$*] is running (OK)"
}
SayConsideredActive()
{
- ocf_log debug "$CMD DROP rule [$*] considered to be running (OK)"
+ ocf_log debug "$CMD $method rule [$*] considered to be running (OK)"
}
SayInactive()
{
- ocf_log debug "$CMD DROP rule [$*] is inactive"
+ ocf_log debug "$CMD $method rule [$*] is inactive"
}
#IptablesStatus {udp|tcp} portno,portno ip {in|out|both} {block|unblock}
@@ -405,14 +434,18 @@
case $5 in
block)
SayActive $*
- rc=$OCF_SUCCESS
+ if [ "$__OCF_ACTION" = "monitor" ] && [ "$promotion_score" = "$SCORE_PROMOTED" ]; then
+ rc=$OCF_RUNNING_MASTER
+ else
+ rc=$OCF_SUCCESS
+ fi
;;
*)
SayInactive $*
rc=$OCF_NOT_RUNNING
;;
esac
- elif ocf_is_ms; then
+ elif [ "$OCF_RESKEY_status_check" = "rule" ]; then
case $5 in
block)
SayInactive $*
@@ -420,7 +453,11 @@
;;
*)
SayActive $*
- rc=$OCF_SUCCESS
+ if [ "$__OCF_ACTION" = "monitor" ] && [ "$promotion_score" = "$SCORE_PROMOTED" ]; then
+ rc=$OCF_RUNNING_MASTER
+ else
+ rc=$OCF_SUCCESS
+ fi
;;
esac
else
@@ -461,7 +498,11 @@
: Chain already in desired state
else
[ "$chain" = "OUTPUT" ] && ds="s" || ds="d"
- $IPTABLES $wait "$op" "$chain" -p "$proto" -${ds} "$ip" -m multiport --${ds}ports "$ports" -j DROP
+ if [ "$method" = "DROP" ]; then
+ $IPTABLES $wait "$op" "$chain" -p "$proto" -${ds} "$ip" -m multiport --${ds}ports "$ports" -j DROP
+ else
+ $IPTABLES $wait "$op" "$chain" -p "$proto" -${ds} "$ip" -m multiport --${ds}ports "$ports" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j REJECT --reject-with tcp-reset
+ fi
fi
}
@@ -486,7 +527,11 @@
$IPTABLES $wait -I OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset
tickle_local
fi
- $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP
+ if [ "$method" = "DROP" ]; then
+ $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP
+ else
+ $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j REJECT --reject-with tcp-reset
+ fi
rc_in=$?
if $try_reset ; then
$IPTABLES $wait -D OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset
@@ -718,6 +763,13 @@
ip=$OCF_RESKEY_ip
reset_local_on_unblock_stop=$OCF_RESKEY_reset_local_on_unblock_stop
nodename=$(ocf_local_nodename)
+case "$OCF_RESKEY_method" in
+ drop) method="DROP" ;;
+ reject) method="REJECT" ;;
+ *) ocf_log err "method: $OCF_RESKEY_method not supported"
+ exit $OCF_ERR_CONFIGURED
+ ;;
+esac
# If "tickle" is enabled, we need to record the list of currently established
@@ -743,6 +795,8 @@
fi
fi
+IptablesValidateAll
+
case $__OCF_ACTION in
start)
IptablesStart "$protocol" "$portno" "$ip" "$direction" "$action"
@@ -765,7 +819,6 @@
;;
validate-all)
- IptablesValidateAll
;;
*) usage

View File

@ -0,0 +1,156 @@
--- a/heartbeat/portblock 2026-02-27 08:43:50.813925268 +0100
+++ b/heartbeat/portblock 2026-02-27 08:44:40.481824601 +0100
@@ -29,12 +29,17 @@
OCF_RESKEY_direction_default="in"
OCF_RESKEY_action_default=""
OCF_RESKEY_method_default="drop"
-OCF_RESKEY_status_check_default="rule"
OCF_RESKEY_ip_default="0.0.0.0/0"
OCF_RESKEY_reset_local_on_unblock_stop_default="false"
OCF_RESKEY_tickle_dir_default=""
OCF_RESKEY_sync_script_default=""
+if ocf_is_ms; then
+ OCF_RESKEY_status_check_default="rule"
+else
+ OCF_RESKEY_status_check_default="pseudo"
+fi
+
: ${OCF_RESKEY_protocol=${OCF_RESKEY_protocol_default}}
: ${OCF_RESKEY_portno=${OCF_RESKEY_portno_default}}
: ${OCF_RESKEY_direction=${OCF_RESKEY_direction_default}}
@@ -401,6 +406,10 @@
done
}
+# A long time ago, these messages needed to go to stdout,
+# "running" / "OK" being the trigger string
+# for heartbeat in haresources mode.
+# Now they are still useful for debugging.
SayActive()
{
ocf_log debug "$CMD $method rule [$*] is running (OK)"
@@ -416,6 +425,11 @@
ocf_log debug "$CMD $method rule [$*] is inactive"
}
+SayConsideredInactive()
+{
+ ocf_log debug "$CMD $method rule [$*] considered to be inactive"
+}
+
#IptablesStatus {udp|tcp} portno,portno ip {in|out|both} {block|unblock}
IptablesStatus() {
local rc
@@ -441,8 +455,17 @@
fi
;;
*)
- SayInactive $*
- rc=$OCF_NOT_RUNNING
+ if [ "$OCF_RESKEY_status_check" != "rule" ] \
+ && test -e "$state_file" && test "$inverse_state_file" -nt "$state_file"; then
+ # rule present, action=unblock, unblock statefile present,
+ # block state file more recent.
+ # apparently an unusual setup: unblock first, block later
+ SayConsideredActive $*
+ rc=$OCF_SUCCESS
+ else
+ SayInactive $*
+ rc=$OCF_NOT_RUNNING
+ fi
;;
esac
elif [ "$OCF_RESKEY_status_check" = "rule" ]; then
@@ -454,6 +477,7 @@
*)
SayActive $*
if [ "$__OCF_ACTION" = "monitor" ] && [ "$promotion_score" = "$SCORE_PROMOTED" ]; then
+ save_tcp_connections
rc=$OCF_RUNNING_MASTER
else
rc=$OCF_SUCCESS
@@ -463,7 +487,10 @@
else
case $5 in
block)
- if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then
+ if test -e "$state_file" && test "$inverse_state_file" -nt "$state_file"; then
+ # rule NOT present, action=block, block state file present,
+ # unblock state file more recent.
+ # expected setup: block first, unblock later
SayConsideredActive $*
rc=$OCF_SUCCESS
else
@@ -472,13 +499,15 @@
fi
;;
*)
- if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then
+ if test -e "$state_file" ; then
+ # rule NOT present, action=unblock, unblock state file present
SayActive $*
- #This is only run on real monitor events.
+ # This is only run on real monitor events (state file present).
save_tcp_connections
rc=$OCF_SUCCESS
else
- SayInactive $*
+ # rule NOT present, action=unblock, unblock state file NOT present
+ SayConsideredInactive $*
rc=$OCF_NOT_RUNNING
fi
;;
@@ -562,7 +591,7 @@
#IptablesStart {udp|tcp} portno,portno ip {in|out|both} {block|unblock}
IptablesStart()
{
- ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start
+ ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start "$state_file"
case $5 in
block) IptablesBLOCK "$@"
rc=$?
@@ -584,7 +613,8 @@
#IptablesStop {udp|tcp} portno,portno ip {in|out|both} {block|unblock}
IptablesStop()
{
- ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop
+ ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop "$state_file"
+
case $5 in
block) IptablesUNBLOCK "$@"
rc=$?
@@ -797,6 +827,33 @@
IptablesValidateAll
+# State file name for ha_pseudo_resource
+#
+# The expected usage of this agent is to pair a "block" with an "unblock",
+# and order startup and configuration of some service between these.
+#
+# The established idiom is to have two separate instances with inverse actions.
+# To "reliably" report the status of "block" during a monitor action,
+# it is not sufficient to check the existence of the blocking rule.
+#
+# It is also insufficient to rely on the pseudo resource state file
+# of this instance only.
+#
+# To know our actual expectation, we need to check the state file of the
+# "inverse" instance as well.
+#
+# Because we don't know the OCF_RESOURCE_INSTANCE value of the other instance,
+# we override the state file name for both instances to something derived from
+# our parameters.
+#
+# This should give use the same "global state" view as the "promotion score"
+# does for the promotable clone variant of this agent.
+#
+[ "$action" = block ] && inverse_action=unblock || inverse_action=block
+state_file_base=$(echo "portblock_${protocol}_${portno}_${ip}_${direction}" | tr -c '0-9a-zA-Z._' _)
+state_file=${HA_RSCTMP}/${state_file_base}_${action}
+inverse_state_file=${HA_RSCTMP}/${state_file_base}_${inverse_action}
+
case $__OCF_ACTION in
start)
IptablesStart "$protocol" "$portno" "$ip" "$direction" "$action"

View File

@ -0,0 +1,54 @@
From 8f5c5a2a472ab404b6fd15ff492e72904dc8ac20 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Thu, 22 Jan 2026 07:37:40 +0100
Subject: [PATCH] powervs-move-ip/powervs-subnet: fix error logging
---
heartbeat/powervs-move-ip.in | 4 ++--
heartbeat/powervs-subnet.in | 10 ++++++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/heartbeat/powervs-move-ip.in b/heartbeat/powervs-move-ip.in
index e2250c998..0eea89f1d 100755
--- a/heartbeat/powervs-move-ip.in
+++ b/heartbeat/powervs-move-ip.in
@@ -310,9 +310,9 @@ class PowerCloudTokenManager:
return json.load(f)
finally:
fcntl.flock(f, fcntl.LOCK_UN)
- except (json.JSONDecodeError, FileNotFoundError, PermissionError):
+ except (json.JSONDecodeError, FileNotFoundError, PermissionError) as e:
ocf.logger.warning(
- "[PowerCloudTokenManager] _read_cache: failed to read token cache read due to missing file or malformed JSON."
+ f"[PowerCloudTokenManager] _read_cache: failed to read token cache read due to missing file or malformed JSON: '{e}'"
)
return {}
diff --git a/heartbeat/powervs-subnet.in b/heartbeat/powervs-subnet.in
index 062b1235e..b8f3864e9 100755
--- a/heartbeat/powervs-subnet.in
+++ b/heartbeat/powervs-subnet.in
@@ -837,8 +837,9 @@ def start_action(
if rc != ocf.OCF_SUCCESS:
return rc
- if monitor_action(**res_options) != ocf.OCF_SUCCESS:
- raise PowerCloudAPIError(f"start_action: start subnet: {ws.subnet_name} failed")
+ rc = monitor_action(**res_options)
+ if rc != ocf.OCF_SUCCESS:
+ raise PowerCloudAPIError(f"start_action: start subnet: {ws.subnet_name} failed", rc)
ocf.logger.info(
f"start_action: finished, added connection {conn_name} for subnet {ws.subnet_name}"
@@ -872,8 +873,9 @@ def stop_action(
ws.subnet_remove()
- if monitor_action(**res_options) != ocf.OCF_NOT_RUNNING:
- raise PowerCloudAPIError(f"stop_action: stop subnet {ws.subnet_name} failed")
+ rc = monitor_action(**res_options)
+ if rc != ocf.OCF_NOT_RUNNING:
+ raise PowerCloudAPIError(f"stop_action: stop subnet {ws.subnet_name} failed", rc)
ocf.logger.info(
f"stop_action: finished, deleted connection for subnet {ws.subnet_name}"

View File

@ -0,0 +1,43 @@
From 0b4bf9c23eb60455da6c6a16c1df19282ab2a8b5 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Fri, 9 Jan 2026 12:56:14 +0100
Subject: [PATCH] powervs-subnet: wait until IP is activated before running
monitor-check
---
heartbeat/powervs-subnet.in | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/heartbeat/powervs-subnet.in b/heartbeat/powervs-subnet.in
index 84e86c0c4..062b1235e 100755
--- a/heartbeat/powervs-subnet.in
+++ b/heartbeat/powervs-subnet.in
@@ -243,7 +243,16 @@ class nmcli:
@staticmethod
def up(name, **kwargs):
- return nmcli._nmcli_cmd("connection", "up", name, **kwargs)
+ nmcli._nmcli_cmd("connection", "up", name, **kwargs)
+
+ for i in range(1, 10):
+ time.sleep(1)
+ status = nmcli._nmcli_cmd("connection", "show", name, **kwargs)
+ if len(status.get("IP4.ADDRESS[1]", "")) > 0:
+ return ocf.OCF_SUCCESS
+ ocf.logger.warning(f"nmcli.connection.up: check {i} of 10: IP not yet available.")
+
+ return ocf.OCF_ERR_GENERIC
@staticmethod
def find(match_key, match_value):
@@ -824,7 +833,9 @@ def start_action(
conn_options.update({"802-3-ethernet.mtu": "9000", "ethtool.feature-tso": "on"})
nmcli.connection.add(conn_name, options=conn_options)
- nmcli.connection.up(conn_name)
+ rc = nmcli.connection.up(conn_name)
+ if rc != ocf.OCF_SUCCESS:
+ return rc
if monitor_action(**res_options) != ocf.OCF_SUCCESS:
raise PowerCloudAPIError(f"start_action: start subnet: {ws.subnet_name} failed")

View File

@ -45,7 +45,7 @@
Name: resource-agents
Summary: Open Source HA Reusable Cluster Resource Scripts
Version: 4.16.0
Release: 22%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.7
Release: 22%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}.9
License: GPL-2.0-or-later AND LGPL-2.1-or-later
URL: https://github.com/ClusterLabs/resource-agents
Source0: %{upstream_prefix}-%{upstream_version}.tar.gz
@ -77,12 +77,20 @@ Patch24: RHEL-85014-IPaddr2-add-link-status-DOWN-LOWERLAYERDOWN-check.patch
Patch25: RHEL-99743-Filesystem-remove-validate-all-fstype-check.patch
Patch26: RHEL-97216-Filesystem-fix-issue-with-Vormetric-mounts.patch
Patch27: RHEL-102728-ocf-shellfuncs-remove-extra-sleep-from-curl_retry.patch
Patch28: RHEL-88431-1-podman-etcd-new-ra.patch
Patch29: RHEL-88431-2-podman-etcd-remove-unused-actions-from-metadata.patch
Patch30: RHEL-88431-3-podman-etcd-fix-listen-peer-urls-binding.patch
Patch31: RHEL-113104-podman-etcd-add-oom-parameter.patch
Patch51: RHEL-112443-1-nginx-fix-validate-warnings.patch
Patch56: RHEL-112443-2-nginx-restore-selinux-context-for-pid-file-during-validate-all-action.patch
Patch28: RHEL-113751-1-podman-etcd-new-ra.patch
Patch29: RHEL-113751-2-podman-etcd-remove-unused-actions-from-metadata.patch
Patch30: RHEL-113751-3-podman-etcd-fix-listen-peer-urls-binding.patch
Patch31: RHEL-113106-podman-etcd-add-oom-parameter.patch
Patch32: RHEL-126121-1-nginx-fix-validate-warnings.patch
Patch33: RHEL-126121-2-nginx-restore-selinux-context-for-pid-file-during-validate-all-action.patch
Patch34: RHEL-114494-1-powervs-move-ip-new-ra.patch
Patch35: RHEL-114494-2-powervs-move-ip-add-iflabel-parameter.patch
Patch36: RHEL-81238-powervs-subnet-wait-for-IP.patch
Patch37: RHEL-143528-powervs-move-ip-powervs-subnet-fix-error-logging.patch
Patch38: RHEL-116197-1-ocf-shellfuncs-add-ocf_promotion_score.patch
Patch39: RHEL-116197-2-portblock-add-promotable-support.patch
Patch40: RHEL-116197-3-portblock-fixes-add-method-and-status_check-parameters.patch
Patch41: RHEL-116197-4-portblock-check-inverse-action.patch
# bundled ha-cloud-support libs
Patch500: ha-cloud-support-aliyun.patch
@ -273,8 +281,16 @@ exit 1
%patch -p1 -P 29
%patch -p1 -P 30
%patch -p1 -P 31
%patch -p1 -P 51
%patch -p1 -P 56
%patch -p1 -P 32
%patch -p1 -P 33
%patch -p1 -P 34
%patch -p1 -P 35
%patch -p1 -P 36
%patch -p1 -P 37
%patch -p1 -P 38 -F1
%patch -p1 -P 39
%patch -p1 -P 40
%patch -p1 -P 41
# bundled ha-cloud-support libs
%patch -p1 -P 500
@ -605,18 +621,31 @@ rm -rf %{buildroot}/usr/share/doc/resource-agents
%{_usr}/lib/ocf/lib/heartbeat/OCF_*.pm
%changelog
* Fri Dec 19 2025 Alan Steinberg <alan.steinberg@oracle.com> - 4.16.0-22.7
* Fri Feb 27 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-22.9
- portblock: add promotable support, and method and status_check
parameters
Resolves: RHEL-116197
* Mon Jan 26 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-22.8
- powervs-move-ip: new resource agent
- powervs-subnet: new resource agent
- powervs-move-ip/powervs-subnet: fix error logging
Resolves: RHEL-114494, RHEL-81238, RHEL-143528
* Fri Dec 5 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-22.7
- nginx: fix validate warnings, and restore SELinux context for
pid-file during validate-all action
Resolves: RHEL-112443
Resolves: RHEL-126121
* Wed Nov 19 2025 Alan Steinberg <alan.steinberg@oracle.com> - 4.16.0-22.1
* Tue Sep 9 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-22.2
- podman-etcd: new resource agent
- podman-etcd: add oom parameter to be able to tune the Out-Of-Memory (OOM)
score for etcd containers
Resolves: RHEL-88431, RHEL-113104
Resolves: RHEL-113751, RHEL-113106
* Tue Jul 15 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-22
- ocf-shellfuncs/AWS agents: dont sleep after the final try in