diff --git a/SOURCES/RHEL-107529-fence_ibm_vpc-add-apikey-file-support.patch b/SOURCES/RHEL-107505-fence_ibm_vpc-add-apikey-file-support.patch
similarity index 100%
rename from SOURCES/RHEL-107529-fence_ibm_vpc-add-apikey-file-support.patch
rename to SOURCES/RHEL-107505-fence_ibm_vpc-add-apikey-file-support.patch
diff --git a/SOURCES/RHEL-92695-1-fence_sbd-improve-error-handling.patch b/SOURCES/RHEL-13088-fence_sbd-improve-error-handling.patch
similarity index 100%
rename from SOURCES/RHEL-92695-1-fence_sbd-improve-error-handling.patch
rename to SOURCES/RHEL-13088-fence_sbd-improve-error-handling.patch
diff --git a/SOURCES/RHEL-68321-1-fence_nutanix_ahv.patch b/SOURCES/RHEL-68321-1-fence_nutanix_ahv.patch
new file mode 100644
index 0000000..15509f2
--- /dev/null
+++ b/SOURCES/RHEL-68321-1-fence_nutanix_ahv.patch
@@ -0,0 +1,790 @@
+--- a/agents/nutanix_ahv/fence_nutanix_ahv.py 1970-01-01 01:00:00.000000000 +0100
++++ b/agents/nutanix_ahv/fence_nutanix_ahv.py 2025-02-25 16:27:56.973414013 +0100
+@@ -0,0 +1,583 @@
++#!@PYTHON@ -tt
++
++# AHV Fence agent
++# Compatible with Nutanix v4 API
++
++
++import atexit
++import logging
++import sys
++import time
++import uuid
++import requests
++from requests.adapters import HTTPAdapter
++from requests.packages.urllib3.util.retry import Retry
++
++sys.path.append("@FENCEAGENTSLIBDIR@")
++from fencing import *
++from fencing import fail, EC_LOGIN_DENIED, EC_GENERIC_ERROR, EC_TIMED_OUT, run_delay, EC_BAD_ARGS
++
++
++V4_VERSION = '4.0'
++MIN_TIMEOUT = 60
++PC_PORT = 9440
++POWER_STATES = {"ON": "on", "OFF": "off", "PAUSED": "off", "UNKNOWN": "unknown"}
++MAX_RETRIES = 5
++
++
++class NutanixClientException(Exception):
++ pass
++
++
++class AHVFenceAgentException(Exception):
++ pass
++
++
++class TaskTimedOutException(Exception):
++ pass
++
++
++class InvalidArgsException(Exception):
++ pass
++
++
++class NutanixClient:
++ def __init__(self, username, password, disable_warnings=False):
++ self.username = username
++ self.password = password
++ self.valid_status_codes = [200, 202]
++ self.disable_warnings = disable_warnings
++ self.session = requests.Session()
++ self.session.auth = (self.username, self.password)
++
++ retry_strategy = Retry(total=MAX_RETRIES,
++ backoff_factor=1,
++ status_forcelist=[429, 500, 503])
++
++ self.session.mount("https://", HTTPAdapter(max_retries=retry_strategy))
++
++ def request(self, url, method='GET', headers=None, **kwargs):
++
++ if self.disable_warnings:
++ requests.packages.urllib3.disable_warnings()
++
++ if headers:
++ self.session.headers.update(headers)
++
++ response = None
++
++ try:
++ logging.debug("Sending %s request to %s", method, url)
++ response = self.session.request(method, url, **kwargs)
++ response.raise_for_status()
++ except requests.exceptions.SSLError as err:
++ logging.error("Secure connection failed, verify SSL certificate")
++ logging.error("Error message: %s", err)
++ raise NutanixClientException("Secure connection failed") from err
++ except requests.exceptions.RequestException as err:
++ logging.error("API call failed: %s", response.text)
++ logging.error("Error message: %s", err)
++ raise NutanixClientException(f"API call failed: {err}") from err
++ except Exception as err:
++ logging.error("API call failed: %s", response.text)
++ logging.error("Unknown error %s", err)
++ raise NutanixClientException(f"API call failed: {err}") from err
++
++ if response.status_code not in self.valid_status_codes:
++ logging.error("API call returned status code %s", response.status_code)
++ raise NutanixClientException(f"API call failed: {response}")
++
++ return response
++
++
++class NutanixV4Client(NutanixClient):
++ def __init__(self, host=None, username=None, password=None,
++ verify=True, disable_warnings=False):
++ self.host = host
++ self.username = username
++ self.password = password
++ self.verify = verify
++ self.base_url = f"https://{self.host}:{PC_PORT}/api"
++ self.vm_url = f"{self.base_url}/vmm/v{V4_VERSION}/ahv/config/vms"
++ self.task_url = f"{self.base_url}/prism/v{V4_VERSION}/config/tasks"
++ super().__init__(username, password, disable_warnings)
++
++ def _get_headers(self, vm_uuid=None):
++ resp = None
++ headers = {'Accept':'application/json',
++ 'Content-Type': 'application/json'}
++
++ if vm_uuid:
++ try:
++ resp = self._get_vm(vm_uuid)
++ except AHVFenceAgentException as err:
++ logging.error("Unable to retrieve etag")
++ raise AHVFenceAgentException from err
++
++ etag_str = resp.headers['Etag']
++ request_id = str(uuid.uuid1())
++ headers['If-Match'] = etag_str
++ headers['Ntnx-Request-Id'] = request_id
++
++ return headers
++
++ def _get_all_vms(self, filter_str=None, limit=None):
++ vm_url = self.vm_url
++
++ if filter_str and limit:
++ vm_url = f"{vm_url}?$filter={filter_str}&$limit={limit}"
++ elif filter_str and not limit:
++ vm_url = f"{vm_url}?$filter={filter_str}"
++ elif limit and not filter_str:
++ vm_url = f"{vm_url}?$limit={limit}"
++
++ logging.debug("Getting info for all VMs, %s", vm_url)
++ header_str = self._get_headers()
++
++ try:
++ resp = self.request(url=vm_url, method='GET',
++ headers=header_str, verify=self.verify)
++ except NutanixClientException as err:
++ logging.error("Unable to retrieve VM info")
++ raise AHVFenceAgentException from err
++
++ vms = resp.json()
++ return vms
++
++ def _get_vm_uuid(self, vm_name):
++ vm_uuid = None
++ resp = None
++
++ if not vm_name:
++ logging.error("VM name was not provided")
++ raise AHVFenceAgentException("VM name not provided")
++
++ try:
++ filter_str = f"name eq '{vm_name}'"
++ resp = self._get_all_vms(filter_str=filter_str)
++ except AHVFenceAgentException as err:
++ logging.error("Failed to get VM info for VM %s", vm_name)
++ raise AHVFenceAgentException from err
++
++ if not resp or not isinstance(resp, dict):
++ logging.error("Failed to retrieve VM UUID for VM %s", vm_name)
++ raise AHVFenceAgentException(f"Failed to get VM UUID for {vm_name}")
++
++ if 'data' not in resp:
++ err = f"Error: Unsuccessful match for VM name: {vm_name}"
++ logging.error("Failed to retrieve VM UUID for VM %s", vm_name)
++ raise AHVFenceAgentException(err)
++
++ for vm in resp['data']:
++ if vm['name'] == vm_name:
++ vm_uuid = vm['extId']
++ break
++
++ return vm_uuid
++
++ def _get_vm(self, vm_uuid):
++ if not vm_uuid:
++ logging.error("VM UUID was not provided")
++ raise AHVFenceAgentException("VM UUID not provided")
++
++ vm_url = self.vm_url + f"/{vm_uuid}"
++ logging.debug("Getting config information for VM, %s", vm_uuid)
++
++ try:
++ header_str = self._get_headers()
++ resp = self.request(url=vm_url, method='GET',
++ headers=header_str, verify=self.verify)
++ except NutanixClientException as err:
++ logging.error("Failed to retrieve VM details "
++ "for VM UUID: %s", vm_uuid)
++ raise AHVFenceAgentException from err
++ except AHVFenceAgentException as err:
++ logging.error("Failed to retrieve etag from headers")
++ raise AHVFenceAgentException from err
++
++ return resp
++
++ def _power_on_off_vm(self, power_state=None, vm_uuid=None):
++ resp = None
++ vm_url = None
++
++ if not vm_uuid:
++ logging.error("VM UUID was not provided")
++ raise AHVFenceAgentException("VM UUID not provided")
++ if not power_state:
++ logging.error("Requested VM power state is None")
++ raise InvalidArgsException
++
++ power_state = power_state.lower()
++
++ if power_state == 'on':
++ vm_url = self.vm_url + f"/{vm_uuid}/$actions/power-on"
++ logging.debug("Sending request to power on VM, %s", vm_uuid)
++ elif power_state == 'off':
++ vm_url = self.vm_url + f"/{vm_uuid}/$actions/power-off"
++ logging.debug("Sending request to power off VM, %s", vm_uuid)
++ else:
++ logging.error("Invalid power state specified: %s", power_state)
++ raise InvalidArgsException
++
++ try:
++ headers_str = self._get_headers(vm_uuid)
++ resp = self.request(url=vm_url, method='POST',
++ headers=headers_str, verify=self.verify)
++ except NutanixClientException as err:
++ logging.error("Failed to power off VM %s", vm_uuid)
++ raise AHVFenceAgentException from err
++ except AHVFenceAgentException as err:
++ logging.error("Failed to retrieve etag from headers")
++ raise AHVFenceAgentException from err
++
++ return resp
++
++ def _power_cycle_vm(self, vm_uuid):
++ if not vm_uuid:
++ logging.error("VM UUID was not provided")
++ raise AHVFenceAgentException("VM UUID not provided")
++
++ resp = None
++ vm_url = self.vm_url + f"/{vm_uuid}/$actions/power-cycle"
++ logging.debug("Sending request to power cycle VM, %s", vm_uuid)
++
++ try:
++ header_str = self._get_headers(vm_uuid)
++ resp = self.request(url=vm_url, method='POST',
++ headers=header_str, verify=self.verify)
++ except NutanixClientException as err:
++ logging.error("Failed to power on VM %s", vm_uuid)
++ raise AHVFenceAgentException from err
++ except AHVFenceAgentException as err:
++ logging.error("Failed to retrieve etag from headers")
++ raise AHVFenceAgentException from err
++
++ return resp
++
++ def _wait_for_task(self, task_uuid, timeout=None):
++ if not task_uuid:
++ logging.error("Task UUID was not provided")
++ raise AHVFenceAgentException("Task UUID not provided")
++
++ task_url = f"{self.task_url}/{task_uuid}"
++ header_str = self._get_headers()
++ task_resp = None
++ interval = 5
++ task_status = None
++
++ if not timeout:
++ timeout = MIN_TIMEOUT
++ else:
++ try:
++ timeout = int(timeout)
++ except ValueError:
++ timeout = MIN_TIMEOUT
++
++ while task_status != 'SUCCEEDED':
++ if timeout <= 0:
++ raise TaskTimedOutException(f"Task timed out: {task_uuid}")
++
++ time.sleep(interval)
++ timeout = timeout - interval
++
++ try:
++ task_resp = self.request(url=task_url, method='GET',
++ headers=header_str, verify=self.verify)
++ task_status = task_resp.json()['data']['status']
++ except NutanixClientException as err:
++ logging.error("Unable to retrieve task status")
++ raise AHVFenceAgentException from err
++ except Exception as err:
++ logging.error("Unknown error")
++ raise AHVFenceAgentException from err
++
++ if task_status == 'FAILED':
++ raise AHVFenceAgentException(f"Task failed, task uuid: {task_uuid}")
++
++ def list_vms(self, filter_str=None, limit=None):
++ vms = None
++ vm_list = {}
++
++ try:
++ vms = self._get_all_vms(filter_str, limit)
++ except NutanixClientException as err:
++ logging.error("Failed to retrieve VM list")
++ raise AHVFenceAgentException from err
++
++ if not vms or not isinstance(vms, dict):
++ logging.error("Failed to retrieve VM list")
++ raise AHVFenceAgentException("Unable to get VM list")
++
++ if 'data' not in vms:
++ err = "Got invalid or empty VM list"
++ logging.debug(err)
++ else:
++ for vm in vms['data']:
++ vm_name = vm['name']
++ ext_id = vm['extId']
++ power_state = vm['powerState']
++ vm_list[vm_name] = (ext_id, power_state)
++
++ return vm_list
++
++ def get_power_state(self, vm_name=None, vm_uuid=None):
++ resp = None
++ power_state = None
++
++ if not vm_name and not vm_uuid:
++ logging.error("Require at least one of VM name or VM UUID")
++ raise InvalidArgsException("No arguments provided")
++
++ if not vm_uuid:
++ try:
++ vm_uuid = self._get_vm_uuid(vm_name)
++ except AHVFenceAgentException as err:
++ logging.error("Unable to retrieve UUID of VM, %s", vm_name)
++ raise AHVFenceAgentException from err
++
++ try:
++ resp = self._get_vm(vm_uuid)
++ except AHVFenceAgentException as err:
++ logging.error("Unable to retrieve power state of VM %s", vm_uuid)
++ raise AHVFenceAgentException from err
++
++ try:
++ power_state = resp.json()['data']['powerState']
++ except AHVFenceAgentException as err:
++ logging.error("Failed to retrieve power state of VM %s", vm_uuid)
++ raise AHVFenceAgentException from err
++
++ return POWER_STATES[power_state]
++
++ def set_power_state(self, vm_name=None, vm_uuid=None,
++ power_state='off', timeout=None):
++ resp = None
++ current_power_state = None
++ power_state = power_state.lower()
++
++ if not timeout:
++ timeout = MIN_TIMEOUT
++
++ if not vm_name and not vm_uuid:
++ logging.error("Require at least one of VM name or VM UUID")
++ raise InvalidArgsException("No arguments provided")
++
++ if not vm_uuid:
++ vm_uuid = self._get_vm_uuid(vm_name)
++
++ try:
++ current_power_state = self.get_power_state(vm_uuid=vm_uuid)
++ except AHVFenceAgentException as err:
++ raise AHVFenceAgentException from err
++
++ if current_power_state.lower() == power_state.lower():
++ logging.debug("VM already powered %s", power_state.lower())
++ return
++
++ if power_state.lower() == 'on':
++ resp = self._power_on_off_vm(power_state, vm_uuid)
++ elif power_state.lower() == 'off':
++ resp = self._power_on_off_vm(power_state, vm_uuid)
++
++ task_id = resp.json()['data']['extId']
++
++ try:
++ self._wait_for_task(task_id, timeout)
++ except AHVFenceAgentException as err:
++ logging.error("Failed to power %s VM", power_state.lower())
++ logging.error("VM power %s task failed", power_state.lower())
++ raise AHVFenceAgentException from err
++ except TaskTimedOutException as err:
++ logging.error("Timed out powering %s VM %s",
++ power_state.lower(), vm_uuid)
++ raise TaskTimedOutException from err
++
++ logging.debug("Powered %s VM, %s successfully",
++ power_state.lower(), vm_uuid)
++
++ def power_cycle_vm(self, vm_name=None, vm_uuid=None, timeout=None):
++ resp = None
++ status = None
++
++ if not timeout:
++ timeout = MIN_TIMEOUT
++
++ if not vm_name and not vm_uuid:
++ logging.error("Require at least one of VM name or VM UUID")
++ raise InvalidArgsException("No arguments provided")
++
++ if not vm_uuid:
++ vm_uuid = self._get_vm_uuid(vm_name)
++
++ resp = self._power_cycle_vm(vm_uuid)
++ task_id = resp.json()['data']['extId']
++
++ try:
++ self._wait_for_task(task_id, timeout)
++ except AHVFenceAgentException as err:
++ logging.error("Failed to power-cycle VM %s", vm_uuid)
++ logging.error("VM power-cycle task failed with status, %s", status)
++ raise AHVFenceAgentException from err
++ except TaskTimedOutException as err:
++ logging.error("Timed out power-cycling VM %s", vm_uuid)
++ raise TaskTimedOutException from err
++
++
++ logging.debug("Power-cycled VM, %s", vm_uuid)
++
++
++def connect(options):
++ host = options["--ip"]
++ username = options["--username"]
++ password = options["--password"]
++ verify_ssl = True
++ disable_warnings = False
++
++ if "--ssl-insecure" in options:
++ verify_ssl = False
++ disable_warnings = True
++
++ client = NutanixV4Client(host, username, password,
++ verify_ssl, disable_warnings)
++
++ try:
++ client.list_vms(limit=1)
++ except AHVFenceAgentException as err:
++ logging.error("Connection to Prism Central Failed")
++ logging.error(err)
++ fail(EC_LOGIN_DENIED)
++
++ return client
++
++def get_list(client, options):
++ vm_list = None
++
++ filter_str = options.get("--filter", None)
++ limit = options.get("--limit", None)
++
++ try:
++ vm_list = client.list_vms(filter_str, limit)
++ except AHVFenceAgentException as err:
++ logging.error("Failed to list VMs")
++ logging.error(err)
++ fail(EC_GENERIC_ERROR)
++
++ return vm_list
++
++def get_power_status(client, options):
++ vmid = None
++ name = None
++ power_state = None
++
++ vmid = options.get("--uuid", None)
++ name = options.get("--plug", None)
++
++ if not vmid and not name:
++ logging.error("Need VM name or VM UUID for power op")
++ fail(EC_BAD_ARGS)
++ try:
++ power_state = client.get_power_state(vm_name=name, vm_uuid=vmid)
++ except AHVFenceAgentException:
++ fail(EC_GENERIC_ERROR)
++ except InvalidArgsException:
++ fail(EC_BAD_ARGS)
++
++ return power_state
++
++def set_power_status(client, options):
++ action = options["--action"].lower()
++ timeout = options.get("--power-timeout", None)
++ vmid = options.get("--uuid", None)
++ name = options.get("--plug", None)
++
++ if not name and not vmid:
++ logging.error("Need VM name or VM UUID to set power state of a VM")
++ fail(EC_BAD_ARGS)
++
++ try:
++ client.set_power_state(vm_name=name, vm_uuid=vmid,
++ power_state=action, timeout=timeout)
++ except AHVFenceAgentException as err:
++ logging.error(err)
++ fail(EC_GENERIC_ERROR)
++ except TaskTimedOutException as err:
++ logging.error(err)
++ fail(EC_TIMED_OUT)
++ except InvalidArgsException:
++ fail(EC_BAD_ARGS)
++
++def power_cycle(client, options):
++ timeout = options.get("--power-timeout", None)
++ vmid = options.get("--uuid", None)
++ name = options.get("--plug", None)
++
++ if not name and not vmid:
++ logging.error("Need VM name or VM UUID to set power cycling a VM")
++ fail(EC_BAD_ARGS)
++
++ try:
++ client.power_cycle_vm(vm_name=name, vm_uuid=vmid, timeout=timeout)
++ except AHVFenceAgentException as err:
++ logging.error(err)
++ fail(EC_GENERIC_ERROR)
++ except TaskTimedOutException as err:
++ logging.error(err)
++ fail(EC_TIMED_OUT)
++ except InvalidArgsException:
++ fail(EC_BAD_ARGS)
++
++def define_new_opts():
++ all_opt["filter"] = {
++ "getopt": ":",
++ "longopt": "filter",
++ "help": """
++ --filter=[filter] Filter list, list VMs actions.
++ --filter=\"name eq 'node1-vm'\"
++ --filter=\"startswith(name,'node')\"
++ --filter=\"name in ('node1-vm','node-3-vm')\" """,
++ "required": "0",
++ "shortdesc": "Filter list, get_list"
++ "e.g: \"name eq 'node1-vm'\"",
++ "order": 2
++ }
++
++def main():
++ device_opt = [
++ "ipaddr",
++ "login",
++ "passwd",
++ "ssl",
++ "notls",
++ "web",
++ "port",
++ "filter",
++ "method",
++ "disable_timeout",
++ "power_timeout"
++ ]
++
++ atexit.register(atexit_handler)
++ define_new_opts()
++
++ all_opt["power_timeout"]["default"] = str(MIN_TIMEOUT)
++ options = check_input(device_opt, process_input(device_opt))
++ docs = {}
++ docs["shortdesc"] = "Fencing agent for Nutanix AHV Cluster VMs."
++ docs["longdesc"] = """fence_ahv is a power fencing agent for \
++virtual machines deployed on Nutanix AHV cluster with the AHV cluster \
++being managed by Prism Central."""
++ docs["vendorurl"] = "https://www.nutanix.com"
++ show_docs(options, docs)
++ run_delay(options)
++ client = connect(options)
++
++ result = fence_action(client, options, set_power_status, get_power_status,
++ get_list, reboot_cycle_fn=power_cycle
++ )
++
++ sys.exit(result)
++
++
++if __name__ == "__main__":
++ main()
+--- a/tests/data/metadata/fence_nutanix_ahv.xml 1970-01-01 01:00:00.000000000 +0100
++++ b/tests/data/metadata/fence_nutanix_ahv.xml 2025-02-25 16:27:56.959413680 +0100
+@@ -0,0 +1,201 @@
++
++
++fence_ahv is a power fencing agent for virtual machines deployed on Nutanix AHV cluster with the AHV cluster being managed by Prism Central.
++https://www.nutanix.com
++
++
++
++
++ Fencing action
++
++
++
++
++ IP address or hostname of fencing device
++
++
++
++
++ IP address or hostname of fencing device
++
++
++
++
++ TCP/UDP port to use for connection with device
++
++
++
++
++ Login name
++
++
++
++
++
++
++
++ Method to fence
++
++
++
++
++ Disable TLS negotiation and force SSL3.0. This should only be used for devices that do not support TLS1.0 and up.
++
++
++
++
++ Login password or passphrase
++
++
++
++
++ Script to run to retrieve password
++
++
++
++
++ Login password or passphrase
++
++
++
++
++ Script to run to retrieve password
++
++
++
++
++ Physical plug number on device, UUID or identification of machine
++
++
++
++
++ Physical plug number on device, UUID or identification of machine
++
++
++
++
++ Use SSL connection with verifying certificate
++
++
++
++
++ Use SSL connection without verifying certificate
++
++
++
++
++ Use SSL connection with verifying certificate
++
++
++
++
++ Login name
++
++
++
++
++ Filter list, get_liste.g: "name eq 'node1-vm'"
++
++
++
++
++ Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.
++
++
++
++
++ Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity.
++
++
++
++
++ Level of debugging detail in output. Defaults to the number of --verbose flags specified on the command line, or to 1 if verbose=1 in a stonith device configuration (i.e., on stdin).
++
++
++
++
++ Write debug information to given file
++
++
++
++ Write debug information to given file
++
++
++
++
++ Display version information and exit
++
++
++
++
++ Display help and exit
++
++
++
++
++ Separator for plug parameter when specifying more than 1 plug
++
++
++
++
++ Separator for CSV created by 'list' operation
++
++
++
++
++ Wait X seconds before fencing is started
++
++
++
++
++ Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)
++
++
++
++
++ Wait X seconds for cmd prompt after login
++
++
++
++
++ Test X seconds for status change after ON/OFF
++
++
++
++
++ Wait X seconds after issuing ON/OFF
++
++
++
++
++ Wait X seconds for cmd prompt after issuing command
++
++
++
++
++ Sleep X seconds between status calls during a STONITH action
++
++
++
++
++ Count of attempts to retry power on
++
++
++
++ Path to gnutls-cli binary
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
diff --git a/SOURCES/RHEL-68321-2-fence_nutanix_ahv-update-metadata.patch b/SOURCES/RHEL-68321-2-fence_nutanix_ahv-update-metadata.patch
new file mode 100644
index 0000000..ccd6867
--- /dev/null
+++ b/SOURCES/RHEL-68321-2-fence_nutanix_ahv-update-metadata.patch
@@ -0,0 +1,39 @@
+From 345a609a34ea153e168b15d48eb8f2dd8d017f04 Mon Sep 17 00:00:00 2001
+From: Oyvind Albrigtsen
+Date: Wed, 23 Apr 2025 15:25:13 +0200
+Subject: [PATCH] fence_nutanix_ahv: update metadata to fix incorrect agent
+ name and align with other agents metadata
+
+---
+ agents/nutanix_ahv/fence_nutanix_ahv.py | 4 ++--
+ tests/data/metadata/fence_nutanix_ahv.xml | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/agents/nutanix_ahv/fence_nutanix_ahv.py b/agents/nutanix_ahv/fence_nutanix_ahv.py
+index 67e6d907c..c18d6a46e 100644
+--- a/agents/nutanix_ahv/fence_nutanix_ahv.py
++++ b/agents/nutanix_ahv/fence_nutanix_ahv.py
+@@ -563,8 +563,8 @@ def main():
+ all_opt["power_timeout"]["default"] = str(MIN_TIMEOUT)
+ options = check_input(device_opt, process_input(device_opt))
+ docs = {}
+- docs["shortdesc"] = "Fencing agent for Nutanix AHV Cluster VMs."
+- docs["longdesc"] = """fence_ahv is a power fencing agent for \
++ docs["shortdesc"] = "Fence agent for Nutanix AHV Cluster VMs."
++ docs["longdesc"] = """fence_nutanix_ahv is a Power Fencing agent for \
+ virtual machines deployed on Nutanix AHV cluster with the AHV cluster \
+ being managed by Prism Central."""
+ docs["vendorurl"] = "https://www.nutanix.com"
+diff --git a/tests/data/metadata/fence_nutanix_ahv.xml b/tests/data/metadata/fence_nutanix_ahv.xml
+index bbc307b1d..2de4b01ad 100644
+--- a/tests/data/metadata/fence_nutanix_ahv.xml
++++ b/tests/data/metadata/fence_nutanix_ahv.xml
+@@ -1,6 +1,6 @@
+
+-
+-fence_ahv is a power fencing agent for virtual machines deployed on Nutanix AHV cluster with the AHV cluster being managed by Prism Central.
++
++fence_nutanix_ahv is a Power Fencing agent for virtual machines deployed on Nutanix AHV cluster with the AHV cluster being managed by Prism Central.
+ https://www.nutanix.com
+
+
diff --git a/SOURCES/RHEL-109923-fence_aws-add-skipshutdown-parameter.patch b/SOURCES/RHEL-7601-fence_aws-add-skipshutdown-parameter.patch
similarity index 98%
rename from SOURCES/RHEL-109923-fence_aws-add-skipshutdown-parameter.patch
rename to SOURCES/RHEL-7601-fence_aws-add-skipshutdown-parameter.patch
index 747b97e..ca1548b 100644
--- a/SOURCES/RHEL-109923-fence_aws-add-skipshutdown-parameter.patch
+++ b/SOURCES/RHEL-7601-fence_aws-add-skipshutdown-parameter.patch
@@ -61,7 +61,7 @@ index 5459a06c4..cddca4580 100644
+ "help" : "--skip-os-shutdown=[true|false] Uses SkipOsShutdown flag",
+ "shortdesc" : "Use SkipOsShutdown flag to stop the EC2 instance",
+ "required" : "0",
-+ "default" : "false",
++ "default" : "true",
+ "order" : 8
+ }
@@ -84,7 +84,7 @@ index ad471c797..c53873bbe 100644
+
+
-+
++
+ Use SkipOsShutdown flag to stop the EC2 instance
+
diff --git a/SOURCES/RHEL-92695-2-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch b/SOURCES/RHEL-79798-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch
similarity index 100%
rename from SOURCES/RHEL-92695-2-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch
rename to SOURCES/RHEL-79798-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch
diff --git a/SOURCES/RHEL-96183-fence_kubevirt-force-off.patch b/SOURCES/RHEL-82193-fence_kubevirt-force-off.patch
similarity index 100%
rename from SOURCES/RHEL-96183-fence_kubevirt-force-off.patch
rename to SOURCES/RHEL-82193-fence_kubevirt-force-off.patch
diff --git a/SOURCES/RHEL-83488-fence_ibm_vpc-refresh-bearer-token.patch b/SOURCES/RHEL-83255-fence_ibm_vpc-refresh-bearer-token.patch
similarity index 100%
rename from SOURCES/RHEL-83488-fence_ibm_vpc-refresh-bearer-token.patch
rename to SOURCES/RHEL-83255-fence_ibm_vpc-refresh-bearer-token.patch
diff --git a/SOURCES/RHEL-84448-fence_compute-fence_evacuate-dont-use-deprecated-getargspec.patch b/SOURCES/RHEL-84448-fence_compute-fence_evacuate-dont-use-deprecated-getargspec.patch
new file mode 100644
index 0000000..8942c92
--- /dev/null
+++ b/SOURCES/RHEL-84448-fence_compute-fence_evacuate-dont-use-deprecated-getargspec.patch
@@ -0,0 +1,38 @@
+From deadda03cb331b766d83e332f06af9a53906a2a8 Mon Sep 17 00:00:00 2001
+From: Oyvind Albrigtsen
+Date: Fri, 21 Mar 2025 12:21:06 +0100
+Subject: [PATCH] fence_compute/fence_evacuate: dont use deprecated
+ inspect.getargspec()
+
+/usr/sbin/fence_compute:288: DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()
+---
+ agents/compute/fence_compute.py | 2 +-
+ agents/evacuate/fence_evacuate.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/agents/compute/fence_compute.py b/agents/compute/fence_compute.py
+index d862dca3f..01b535bb3 100644
+--- a/agents/compute/fence_compute.py
++++ b/agents/compute/fence_compute.py
+@@ -285,7 +285,7 @@ def create_nova_connection(options):
+
+ nova_versions = [ "2.11", "2" ]
+ for version in nova_versions:
+- clientargs = inspect.getargspec(client.Client).varargs
++ clientargs = inspect.getfullargspec(client.Client).varargs
+ # Some versions of Openstack prior to Ocata only
+ # supported positional arguments for username,
+ # password, and tenant.
+diff --git a/agents/evacuate/fence_evacuate.py b/agents/evacuate/fence_evacuate.py
+index 53d6fd15b..1ea020f69 100644
+--- a/agents/evacuate/fence_evacuate.py
++++ b/agents/evacuate/fence_evacuate.py
+@@ -221,7 +221,7 @@ def create_nova_connection(options):
+
+ versions = [ "2.11", "2" ]
+ for version in versions:
+- clientargs = inspect.getargspec(client.Client).varargs
++ clientargs = inspect.getfullargspec(client.Client).varargs
+
+ # Some versions of Openstack prior to Ocata only
+ # supported positional arguments for username,
diff --git a/SOURCES/RHEL-88568-fence_ibm_powervs-fix-plaintext-token-file-support.patch b/SOURCES/RHEL-88568-fence_ibm_powervs-fix-plaintext-token-file-support.patch
new file mode 100644
index 0000000..3eeb9c7
--- /dev/null
+++ b/SOURCES/RHEL-88568-fence_ibm_powervs-fix-plaintext-token-file-support.patch
@@ -0,0 +1,21 @@
+From 988cbd9fb600261106d5da1db6a7bf9cde218eaa Mon Sep 17 00:00:00 2001
+From: Oyvind Albrigtsen
+Date: Fri, 25 Apr 2025 16:38:03 +0200
+Subject: [PATCH] fence_ibm_powervs: fix plaintext token file support
+
+---
+ agents/ibm_powervs/fence_ibm_powervs.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py
+index ec9a0c11d..d408e8727 100755
+--- a/agents/ibm_powervs/fence_ibm_powervs.py
++++ b/agents/ibm_powervs/fence_ibm_powervs.py
+@@ -35,6 +35,7 @@ def get_token(conn, options):
+ api_key = keys.get("apikey", "")
+ # data is text, return as is
+ except ValueError:
++ f.seek(0)
+ api_key = f.read().strip()
+ except FileNotFoundError:
+ logging.debug("Failed: Cannot open file {}".format(key_file))
diff --git a/SOURCES/RHEL-95903-pkg_resources-suppress-UserWarning.patch b/SOURCES/RHEL-95901-pkg_resources-suppress-UserWarning.patch
similarity index 100%
rename from SOURCES/RHEL-95903-pkg_resources-suppress-UserWarning.patch
rename to SOURCES/RHEL-95901-pkg_resources-suppress-UserWarning.patch
diff --git a/SPECS/fence-agents.spec b/SPECS/fence-agents.spec
index 103b481..73d72dc 100644
--- a/SPECS/fence-agents.spec
+++ b/SPECS/fence-agents.spec
@@ -57,7 +57,7 @@
Name: fence-agents
Summary: Set of unified programs capable of host isolation ("fencing")
Version: 4.10.0
-Release: 86%{?alphatag:.%{alphatag}}%{?dist}.11
+Release: 98%{?alphatag:.%{alphatag}}%{?dist}
License: GPLv2+ and LGPLv2+
URL: https://github.com/ClusterLabs/fence-agents
Source0: https://fedorahosted.org/releases/f/e/fence-agents/%{name}-%{version}.tar.gz
@@ -244,12 +244,16 @@ Patch59: RHEL-56138-fence_mpath-1-support-hex-key-format.patch
Patch60: RHEL-56138-fence_mpath-2-fix-unfencing-issue-use-MULTILINE-avoid-duplicates.patch
Patch61: RHEL-62206-fence_ibm_powervs-add-private-endpoint-and-token-file-support.patch
Patch62: RHEL-76493-fence_azure_arm-use-azure-identity.patch
-Patch63: RHEL-83488-fence_ibm_vpc-refresh-bearer-token.patch
-Patch64: RHEL-92695-1-fence_sbd-improve-error-handling.patch
-Patch65: RHEL-92695-2-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch
-Patch66: RHEL-107529-fence_ibm_vpc-add-apikey-file-support.patch
-Patch67: RHEL-109923-fence_aws-add-skipshutdown-parameter.patch
-Patch68: RHEL-96183-fence_kubevirt-force-off.patch
+Patch63: RHEL-83255-fence_ibm_vpc-refresh-bearer-token.patch
+Patch64: RHEL-84448-fence_compute-fence_evacuate-dont-use-deprecated-getargspec.patch
+Patch65: RHEL-79798-fence_sbd-get-devices-from-SBD_DEVICE-if-devices-parameter-isnt-set.patch
+Patch66: RHEL-68321-1-fence_nutanix_ahv.patch
+Patch67: RHEL-68321-2-fence_nutanix_ahv-update-metadata.patch
+Patch68: RHEL-88568-fence_ibm_powervs-fix-plaintext-token-file-support.patch
+Patch69: RHEL-13088-fence_sbd-improve-error-handling.patch
+Patch70: RHEL-82193-fence_kubevirt-force-off.patch
+Patch71: RHEL-107505-fence_ibm_vpc-add-apikey-file-support.patch
+Patch72: RHEL-7601-fence_aws-add-skipshutdown-parameter.patch
### HA support libs/utils ###
# all archs
@@ -257,9 +261,9 @@ Patch1000: bz2217902-1-kubevirt-fix-bundled-dateutil-CVE-2007-4559.patch
# cloud (x86_64 only)
Patch2000: bz2217902-2-aws-azure-fix-bundled-dateutil-CVE-2007-4559.patch
Patch2001: RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch
-Patch2002: RHEL-95903-pkg_resources-suppress-UserWarning.patch
+Patch2002: RHEL-95901-pkg_resources-suppress-UserWarning.patch
-%global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti
+%global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath nutanix_ahv redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti
%ifarch x86_64
%global testagents virsh heuristics_ping aliyun aws azure_arm gce openstack virt
%endif
@@ -438,8 +442,12 @@ BuildRequires: %{systemd_units}
%patch -p1 -P 64
%patch -p1 -P 65
%patch -p1 -P 66
-%patch -p1 -P 67 -F2
+%patch -p1 -P 67
%patch -p1 -P 68
+%patch -p1 -P 69
+%patch -p1 -P 70
+%patch -p1 -P 71
+%patch -p1 -P 72 -F2
# prevent compilation of something that won't get used anyway
sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac
@@ -1283,6 +1291,19 @@ Device Mapper Multipath.
%{_datadir}/cluster/fence_mpath_check*
%{_mandir}/man8/fence_mpath.8*
+%package nutanix-ahv
+License: GPL-2.0-or-later AND LGPL-2.0-or-later
+Summary: Fence agent for Nutanix AHV
+Requires: python3-requests
+Requires: fence-agents-common = %{version}-%{release}
+BuildArch: noarch
+Obsoletes: fence-agents < 3.1.13
+%description nutanix-ahv
+Fence agent for Nutanix AHV clusters.
+%files nutanix-ahv
+%{_sbindir}/fence_nutanix_ahv
+%{_mandir}/man8/fence_nutanix_ahv.8*
+
%ifarch x86_64 ppc64le
%package openstack
License: GPLv2+ and LGPLv2+
@@ -1547,31 +1568,45 @@ are located on corosync cluster nodes.
%endif
%changelog
-* Thu Sep 11 2025 Oyvind Albrigtsen - 4.10.0-86.11
-- fence_kubevirt: use hard poweroff
- Resolves: RHEL-96183
-
-* Tue Aug 19 2025 Oyvind Albrigtsen - 4.10.0-86.10
+* Tue Aug 19 2025 Oyvind Albrigtsen - 4.10.0-98
- fence_aws: add "skip_os_shutdown" parameter to allow hard poweroff
- Resolves: RHEL-109923
+ Resolves: RHEL-7601
-* Tue Aug 12 2025 Oyvind Albrigtsen - 4.10.0-86.9
+* Tue Aug 12 2025 Oyvind Albrigtsen - 4.10.0-97
- fence_ibm_vpc: add apikey file support
- Resolves: RHEL-107529
+ Resolves: RHEL-107505
-* Thu Jul 17 2025 Oyvind Albrigtsen - 4.10.0-86.8
+* Thu Jun 26 2025 Oyvind Albrigtsen - 4.10.0-96
- bundled setuptools: fix CVE-2025-47273
- Resolves: RHEL-95903
+ Resolves: RHEL-95901
-* Wed May 21 2025 Oyvind Albrigtsen - 4.10.0-86.4
-- fence_sbd: improve error handling and get devices from SBD_DEVICE env
- variable if devices parameter isnt set
- Resolves: RHEL-92695
+* Thu Jun 12 2025 Oyvind Albrigtsen - 4.10.0-95
+- fence_kubevirt: use hard poweroff
+ Resolves: RHEL-82193
-* Fri Mar 14 2025 Oyvind Albrigtsen - 4.10.0-86.3
+* Wed May 21 2025 Oyvind Albrigtsen - 4.10.0-92
+- fence_sbd: improve error handling
+ Resolves: RHEL-13088
+
+* Mon Apr 28 2025 Oyvind Albrigtsen - 4.10.0-91
+- fence_ibm_powervs: fix plaintext token file support
+ Resolves: RHEL-88568
+
+* Wed Apr 23 2025 Oyvind Albrigtsen - 4.10.0-90
+- fence_nutanix_ahv: new fence agent
+ Resolves: RHEL-68321
+
+* Tue Mar 25 2025 Oyvind Albrigtsen - 4.10.0-88
+- fence_compute/fence_evacuate: dont use deprecated inspect.getargspec()
+ Resolves: RHEL-84448
+- fence_sbd: get devices from SBD_DEVICE env variable if devices
+ parameter isnt set
+ Resolves: RHEL-79798
+
+* Fri Mar 14 2025 Oyvind Albrigtsen - 4.10.0-87
- fence_ibm_vpc: refresh bearer-token if token data is corrupt, and
avoid edge-case of writing empty token file
- Resolves: RHEL-83488
+ Resolves: RHEL-83255
* Tue Mar 11 2025 Oyvind Albrigtsen - 4.10.0-86
- bundled jinja2: fix CVE-2025-27516