Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fence-agents for openSUSE:Factory checked in at 2024-11-26 20:56:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fence-agents (Old) and /work/SRC/openSUSE:Factory/.fence-agents.new.28523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fence-agents" Tue Nov 26 20:56:26 2024 rev:79 rq:1226483 version:4.15.0+git.1731052905.05fd299e Changes: -------- --- /work/SRC/openSUSE:Factory/fence-agents/fence-agents.changes 2024-09-20 17:13:23.063162148 +0200 +++ /work/SRC/openSUSE:Factory/.fence-agents.new.28523/fence-agents.changes 2024-11-26 20:57:44.255016655 +0100 @@ -1,0 +2,11 @@ +Tue Nov 19 10:48:42 UTC 2024 - vark...@suse.com + +- Update to version 4.15.0+git.1731052905.05fd299e: + * fence_nutanix_ahv: Add fence agent support for Nutanix AHV Cluster (#600) + * fencing: fix "?" typo in required field for the login parameter + * fence_ibm_powervs: add private endpoint and token file support (#597) + * fence_mpath: fix 0x-format patch causing unfencing issue, and use re.MULTILINE to avoid duplicating device dev/key lines in /run/cluster/mpath.devices + * fence_bladecenter/fence_raritan_px3: use r"" on regex string to avoid SyntaxWarning + * fence_scsi: preempt clears all devices on the mpath device, so only run it for the first device + +------------------------------------------------------------------- Old: ---- fence-agents-4.15.0+git.1724675137.ca9ae93a.tar.xz New: ---- fence-agents-4.15.0+git.1731052905.05fd299e.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fence-agents.spec ++++++ --- /var/tmp/diff_new_pack.eoENkc/_old 2024-11-26 20:57:45.011048062 +0100 +++ /var/tmp/diff_new_pack.eoENkc/_new 2024-11-26 20:57:45.011048062 +0100 @@ -19,7 +19,7 @@ %define agent_list aliyun alom apc apc_snmp aws azure_arm bladecenter brocade cisco_mds cisco_ucs compute docker drac5 dummy eaton_snmp eaton_ssh emerson eps evacuate gce hds_cb hpblade ibmblade ibmz ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan ironic kdump ldom lpar mpath netio openstack powerman pve raritan rcd_serial redfish rhevm rsa rsb sanbox2 sbd scsi vbox virsh vmware vmware_rest wti xenapi zvm Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") -Version: 4.15.0+git.1724675137.ca9ae93a +Version: 4.15.0+git.1731052905.05fd299e Release: 0 License: GPL-2.0-or-later AND LGPL-2.0-or-later Group: Productivity/Clustering/HA ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.eoENkc/_old 2024-11-26 20:57:45.051049723 +0100 +++ /var/tmp/diff_new_pack.eoENkc/_new 2024-11-26 20:57:45.055049889 +0100 @@ -3,6 +3,6 @@ <param name="url">git://github.com/ClusterLabs/fence-agents.git</param> <param name="changesrevision">8d746be92f191aa289f13a3703031c122a5e6cf3</param></service><service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/fence-agents</param> - <param name="changesrevision">ca9ae93a90e28876a010e7a065f62a387b857ad2</param></service></servicedata> + <param name="changesrevision">05fd299e094c6981b4c5b943dee03a29e78ee016</param></service></servicedata> (No newline at EOF) ++++++ fence-agents-4.15.0+git.1724675137.ca9ae93a.tar.xz -> fence-agents-4.15.0+git.1731052905.05fd299e.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/bladecenter/fence_bladecenter.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/bladecenter/fence_bladecenter.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/bladecenter/fence_bladecenter.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/bladecenter/fence_bladecenter.py 2024-11-08 09:01:45.000000000 +0100 @@ -96,7 +96,7 @@ ## ## Operate the fencing device ###### - conn = fence_login(options, "(username\s*:\s*)") + conn = fence_login(options, r"(username\s*:\s*)") result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) fence_logout(conn, "exit") sys.exit(result) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/ibm_powervs/fence_ibm_powervs.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/ibm_powervs/fence_ibm_powervs.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/ibm_powervs/fence_ibm_powervs.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/ibm_powervs/fence_ibm_powervs.py 2024-11-08 09:01:45.000000000 +0100 @@ -1,13 +1,14 @@ #!@PYTHON@ -tt import sys -import pycurl, io, json +import pycurl +import io +import json import logging import atexit -import time + sys.path.append("@FENCEAGENTSLIBDIR@") -from fencing import * -from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS +from fencing import all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, fail, run_delay, EC_STATUS state = { "ACTIVE": "on", @@ -18,15 +19,35 @@ } def get_token(conn, options): - try: - command = "identity/token" - action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) - res = send_command(conn, command, "POST", action, printResult=False) - except Exception as e: - logging.debug("Failed: {}".format(e)) - return "TOKEN_IS_MISSING_OR_WRONG" - - return res["access_token"] + try: + if options["--token"][0] == '@': + key_file = options["--token"][1:] + try: + # read the API key from a file + with open(key_file, "r") as f: + try: + keys = json.loads(f.read()) + # data seems to be in json format + # return the value of the item with the key 'Apikey' + api_key = keys.get("Apikey", "") + if not api_key: + # backward compatibility: former key name was 'apikey' + api_key = keys.get("apikey", "") + # data is text, return as is + except ValueError: + api_key = f.read().strip() + except FileNotFoundError: + logging.debug("Failed: Cannot open file {}".format(key_file)) + return "TOKEN_IS_MISSING_OR_WRONG" + else: + api_key = options["--token"] + command = "identity/token" + action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(api_key) + res = send_command(conn, command, "POST", action, printResult=False) + except Exception as e: + logging.debug("Failed: {}".format(e)) + return "TOKEN_IS_MISSING_OR_WRONG" + return res["access_token"] def get_list(conn, options): outlets = {} @@ -41,7 +62,7 @@ for r in res["pvmInstances"]: if options["--verbose-level"] > 1: logging.debug(json.dumps(r, indent=2)) - outlets[r["pvmInstanceID"]] = (r["serverName"], state[r["status"]]) + outlets[r["pvmInstanceID"]] = (r["serverName"], state.get(r["status"], "unknown")) return outlets @@ -97,7 +118,7 @@ else: logging.debug("Failed: Unable to cycle with {} for {}".format(options["--action"], e)) fail(EC_STATUS) - return True + return True def connect(opt, token): conn = pycurl.Curl() @@ -130,7 +151,10 @@ conn = pycurl.Curl() # setup correct URL - conn.base_url = "https://iam.cloud.ibm.com/" + if opt["--api-type"] == "private": + conn.base_url = "https://private.iam.cloud.ibm.com/" + else: + conn.base_url = "https://iam.cloud.ibm.com/" if opt["--verbose-level"] > 1: conn.setopt(pycurl.VERBOSE, 1) @@ -265,9 +289,9 @@ define_new_opts() all_opt["shell_timeout"]["default"] = "500" - all_opt["power_timeout"]["default"] = "30" - all_opt["power_wait"]["default"] = "1" - all_opt["stonith_status_sleep"]["default"] = "2" + all_opt["power_timeout"]["default"] = "120" + all_opt["power_wait"]["default"] = "15" + all_opt["stonith_status_sleep"]["default"] = "10" all_opt["api-type"]["default"] = "private" all_opt["proxy"]["default"] = "" @@ -275,8 +299,8 @@ docs = {} docs["shortdesc"] = "Fence agent for IBM PowerVS" - docs["longdesc"] = """fence_ibm_powervs is a Power Fencing agent which can be \ -used with IBM PowerVS to fence virtual machines.""" + docs["longdesc"] = """fence_ibm_powervs is a power fencing agent for \ +IBM Power Virtual Server (IBM PowerVS) to fence virtual server instances.""" docs["vendorurl"] = "https://www.ibm.com" show_docs(options, docs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/mpath/fence_mpath.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/mpath/fence_mpath.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/mpath/fence_mpath.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/mpath/fence_mpath.py 2024-11-08 09:01:45.000000000 +0100 @@ -40,7 +40,7 @@ if options["--plug"] not in get_registration_keys(options, dev): count += 1 logging.debug("Failed to register key "\ - + options["--plug"] + "on device " + dev + "\n") + + options["--plug"] + " on device " + dev + "\n") continue dev_write(options, dev) @@ -147,8 +147,9 @@ store_fh = open(file_path, "a+") except IOError: fail_usage("Failed: Cannot open file \""+ file_path + "\"") + store_fh.seek(0) out = store_fh.read() - if not re.search(r"^" + dev + r"\s+", out): + if not re.search(r"^{}\s+{}$".format(dev, options["--plug"]), out, flags=re.MULTILINE): store_fh.write(dev + "\t" + options["--plug"] + "\n") store_fh.close() @@ -332,7 +333,7 @@ fail_usage("Failed: No devices found") options["devices"] = [d for d in re.split(r"\s*,\s*|\s+", options["--devices"].strip()) if d] - options["--plug"] = re.sub(r"^0x0*|^0+", "", options["--plug"]) + options["--plug"] = re.sub(r"^0x0*|^0+", "", options.get("--plug", "")) # Input control END result = fence_action(None, options, set_status, get_status) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/nutanix_ahv/fence_nutanix_ahv.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/nutanix_ahv/fence_nutanix_ahv.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/nutanix_ahv/fence_nutanix_ahv.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/nutanix_ahv/fence_nutanix_ahv.py 2024-11-08 09:01:45.000000000 +0100 @@ -0,0 +1,574 @@ +#!@PYTHON@ -tt + +# AHV Fence agent +# Compatible with Nutanix v4 API + + +import atexit +import logging +import sys +import time +import uuid +import requests + +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"} + + +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 + + def request(self, url, method='GET', headers=None, **kwargs): + session = requests.Session() + session.auth = (self.username, self.password) + + if self.disable_warnings: + requests.packages.urllib3.disable_warnings() + + if headers: + session.headers.update(headers) + + response = None + + try: + logging.debug("Sending %s request to %s", method, url) + response = 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() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/raritan_px3/fence_raritan_px3.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/raritan_px3/fence_raritan_px3.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/raritan_px3/fence_raritan_px3.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/raritan_px3/fence_raritan_px3.py 2024-11-08 09:01:45.000000000 +0100 @@ -157,7 +157,7 @@ docs["vendorurl"] = "http://www.raritan.com/" show_docs(options, docs) - conn = fence_login(options, re_login_string="Username.*") + conn = fence_login(options, re_login_string=r"Username.*") px3_get_outlet_list(conn, options) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/scsi/fence_scsi.py new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/scsi/fence_scsi.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/agents/scsi/fence_scsi.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/agents/scsi/fence_scsi.py 2024-11-08 09:01:45.000000000 +0100 @@ -131,11 +131,13 @@ return run_cmd(options, options["--sg_turs-path"] + " " + dev)["rc"] -def register_dev(options, dev, key): +def register_dev(options, dev, key, do_preempt=True): dev = os.path.realpath(dev) if re.search(r"^dm", dev[5:]): - for slave in get_mpath_slaves(dev): - register_dev(options, slave, key) + devices = get_mpath_slaves(dev) + register_dev(options, devices[0], key) + for device in devices[1:]: + register_dev(options, device, key, False) return True # Check if any registration exists for the key already. We track this in @@ -153,7 +155,7 @@ # If key matches, make sure it matches with the connection that # exists right now. To do this, we can issue a preempt with same key # which should replace the old invalid entries from the target. - if not preempt(options, key, dev, key): + if do_preempt and not preempt(options, key, dev, key): return False # If there was no reservation, we need to issue another registration diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/fence-agents.spec.in new/fence-agents-4.15.0+git.1731052905.05fd299e/fence-agents.spec.in --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/fence-agents.spec.in 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/fence-agents.spec.in 2024-11-08 09:01:45.000000000 +0100 @@ -70,6 +70,7 @@ fence-agents-lpar \\ fence-agents-mpath \\ fence-agents-netio \\ +fence-agents-nutanix-ahv \\ fence-agents-ovh \\ fence-agents-ovm \\ fence-agents-redfish \\ @@ -930,6 +931,18 @@ %{_sbindir}/fence_netio %{_mandir}/man8/fence_netio.8* +%package nutanix-ahv +License: GPL-2.0-or-later AND LGPL-2.0-or-later +Summary: Fence agent for Nutanix AHV +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: GPL-2.0-or-later AND LGPL-2.0-or-later diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/lib/fencing.py.py new/fence-agents-4.15.0+git.1731052905.05fd299e/lib/fencing.py.py --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/lib/fencing.py.py 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/lib/fencing.py.py 2024-11-08 09:01:45.000000000 +0100 @@ -124,7 +124,7 @@ "getopt" : "l:", "longopt" : "username", "help" : "-l, --username=[name] Login name", - "required" : "?", + "required" : "0", "order" : 1}, "no_login" : { "getopt" : "", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/tests/data/metadata/fence_ibm_powervs.xml new/fence-agents-4.15.0+git.1731052905.05fd299e/tests/data/metadata/fence_ibm_powervs.xml --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/tests/data/metadata/fence_ibm_powervs.xml 2024-08-26 14:25:37.000000000 +0200 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/tests/data/metadata/fence_ibm_powervs.xml 2024-11-08 09:01:45.000000000 +0100 @@ -1,6 +1,6 @@ <?xml version="1.0" ?> <resource-agent name="fence_ibm_powervs" shortdesc="Fence agent for IBM PowerVS" > -<longdesc>fence_ibm_powervs is a Power Fencing agent which can be used with IBM PowerVS to fence virtual machines.</longdesc> +<longdesc>fence_ibm_powervs is a power fencing agent for IBM Power Virtual Server (IBM PowerVS) to fence virtual server instances.</longdesc> <vendor-url>https://www.ibm.com</vendor-url> <parameters> <parameter name="api-type" unique="0" required="0" deprecated="1"> @@ -122,12 +122,12 @@ </parameter> <parameter name="power_timeout" unique="0" required="0"> <getopt mixed="--power-timeout=[seconds]" /> - <content type="second" default="30" /> + <content type="second" default="120" /> <shortdesc lang="en">Test X seconds for status change after ON/OFF</shortdesc> </parameter> <parameter name="power_wait" unique="0" required="0"> <getopt mixed="--power-wait=[seconds]" /> - <content type="second" default="1" /> + <content type="second" default="15" /> <shortdesc lang="en">Wait X seconds after issuing ON/OFF</shortdesc> </parameter> <parameter name="shell_timeout" unique="0" required="0"> @@ -137,7 +137,7 @@ </parameter> <parameter name="stonith_status_sleep" unique="0" required="0"> <getopt mixed="--stonith-status-sleep=[seconds]" /> - <content type="second" default="2" /> + <content type="second" default="10" /> <shortdesc lang="en">Sleep X seconds between status calls during a STONITH action</shortdesc> </parameter> <parameter name="retry_on" unique="0" required="0"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.15.0+git.1724675137.ca9ae93a/tests/data/metadata/fence_nutanix_ahv.xml new/fence-agents-4.15.0+git.1731052905.05fd299e/tests/data/metadata/fence_nutanix_ahv.xml --- old/fence-agents-4.15.0+git.1724675137.ca9ae93a/tests/data/metadata/fence_nutanix_ahv.xml 1970-01-01 01:00:00.000000000 +0100 +++ new/fence-agents-4.15.0+git.1731052905.05fd299e/tests/data/metadata/fence_nutanix_ahv.xml 2024-11-08 09:01:45.000000000 +0100 @@ -0,0 +1,201 @@ +<?xml version="1.0" ?> +<resource-agent name="fence_nutanix_ahv" shortdesc="Fencing agent for Nutanix AHV Cluster VMs." > +<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.</longdesc> +<vendor-url>https://www.nutanix.com</vendor-url> +<parameters> + <parameter name="action" unique="0" required="1"> + <getopt mixed="-o, --action=[action]" /> + <content type="string" default="reboot" /> + <shortdesc lang="en">Fencing action</shortdesc> + </parameter> + <parameter name="ip" unique="0" required="1" obsoletes="ipaddr"> + <getopt mixed="-a, --ip=[ip]" /> + <content type="string" /> + <shortdesc lang="en">IP address or hostname of fencing device</shortdesc> + </parameter> + <parameter name="ipaddr" unique="0" required="1" deprecated="1"> + <getopt mixed="-a, --ip=[ip]" /> + <content type="string" /> + <shortdesc lang="en">IP address or hostname of fencing device</shortdesc> + </parameter> + <parameter name="ipport" unique="0" required="0"> + <getopt mixed="-u, --ipport=[port]" /> + <content type="integer" default="80" /> + <shortdesc lang="en">TCP/UDP port to use for connection with device</shortdesc> + </parameter> + <parameter name="login" unique="0" required="1" deprecated="1"> + <getopt mixed="-l, --username=[name]" /> + <content type="string" /> + <shortdesc lang="en">Login name</shortdesc> + </parameter> + <parameter name="method" unique="0" required="0"> + <getopt mixed="-m, --method=[method]" /> + <content type="select" default="onoff" > + <option value="onoff" /> + <option value="cycle" /> + </content> + <shortdesc lang="en">Method to fence</shortdesc> + </parameter> + <parameter name="notls" unique="0" required="0"> + <getopt mixed="-t, --notls" /> + <content type="boolean" /> + <shortdesc lang="en">Disable TLS negotiation and force SSL3.0. This should only be used for devices that do not support TLS1.0 and up.</shortdesc> + </parameter> + <parameter name="passwd" unique="0" required="0" deprecated="1"> + <getopt mixed="-p, --password=[password]" /> + <content type="string" /> + <shortdesc lang="en">Login password or passphrase</shortdesc> + </parameter> + <parameter name="passwd_script" unique="0" required="0" deprecated="1"> + <getopt mixed="-S, --password-script=[script]" /> + <content type="string" /> + <shortdesc lang="en">Script to run to retrieve password</shortdesc> + </parameter> + <parameter name="password" unique="0" required="0" obsoletes="passwd"> + <getopt mixed="-p, --password=[password]" /> + <content type="string" /> + <shortdesc lang="en">Login password or passphrase</shortdesc> + </parameter> + <parameter name="password_script" unique="0" required="0" obsoletes="passwd_script"> + <getopt mixed="-S, --password-script=[script]" /> + <content type="string" /> + <shortdesc lang="en">Script to run to retrieve password</shortdesc> + </parameter> + <parameter name="plug" unique="0" required="1" obsoletes="port"> + <getopt mixed="-n, --plug=[id]" /> + <content type="string" /> + <shortdesc lang="en">Physical plug number on device, UUID or identification of machine</shortdesc> + </parameter> + <parameter name="port" unique="0" required="1" deprecated="1"> + <getopt mixed="-n, --plug=[id]" /> + <content type="string" /> + <shortdesc lang="en">Physical plug number on device, UUID or identification of machine</shortdesc> + </parameter> + <parameter name="ssl" unique="0" required="0"> + <getopt mixed="-z, --ssl" /> + <content type="boolean" /> + <shortdesc lang="en">Use SSL connection with verifying certificate</shortdesc> + </parameter> + <parameter name="ssl_insecure" unique="0" required="0"> + <getopt mixed="--ssl-insecure" /> + <content type="boolean" /> + <shortdesc lang="en">Use SSL connection without verifying certificate</shortdesc> + </parameter> + <parameter name="ssl_secure" unique="0" required="0"> + <getopt mixed="--ssl-secure" /> + <content type="boolean" /> + <shortdesc lang="en">Use SSL connection with verifying certificate</shortdesc> + </parameter> + <parameter name="username" unique="0" required="1" obsoletes="login"> + <getopt mixed="-l, --username=[name]" /> + <content type="string" /> + <shortdesc lang="en">Login name</shortdesc> + </parameter> + <parameter name="filter" unique="0" required="0"> + <getopt mixed=" + --filter=[filter]" /> + <content type="string" /> + <shortdesc lang="en">Filter list, get_liste.g: "name eq 'node1-vm'"</shortdesc> + </parameter> + <parameter name="quiet" unique="0" required="0"> + <getopt mixed="-q, --quiet" /> + <content type="boolean" /> + <shortdesc lang="en">Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.</shortdesc> + </parameter> + <parameter name="verbose" unique="0" required="0"> + <getopt mixed="-v, --verbose" /> + <content type="boolean" /> + <shortdesc lang="en">Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity.</shortdesc> + </parameter> + <parameter name="verbose_level" unique="0" required="0"> + <getopt mixed="--verbose-level" /> + <content type="integer" /> + <shortdesc lang="en">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).</shortdesc> + </parameter> + <parameter name="debug" unique="0" required="0" deprecated="1"> + <getopt mixed="-D, --debug-file=[debugfile]" /> + <content type="string" /> + <shortdesc lang="en">Write debug information to given file</shortdesc> + </parameter> + <parameter name="debug_file" unique="0" required="0" obsoletes="debug"> + <getopt mixed="-D, --debug-file=[debugfile]" /> + <shortdesc lang="en">Write debug information to given file</shortdesc> + </parameter> + <parameter name="version" unique="0" required="0"> + <getopt mixed="-V, --version" /> + <content type="boolean" /> + <shortdesc lang="en">Display version information and exit</shortdesc> + </parameter> + <parameter name="help" unique="0" required="0"> + <getopt mixed="-h, --help" /> + <content type="boolean" /> + <shortdesc lang="en">Display help and exit</shortdesc> + </parameter> + <parameter name="plug_separator" unique="0" required="0"> + <getopt mixed="--plug-separator=[char]" /> + <content type="string" default="," /> + <shortdesc lang="en">Separator for plug parameter when specifying more than 1 plug</shortdesc> + </parameter> + <parameter name="separator" unique="0" required="0"> + <getopt mixed="-C, --separator=[char]" /> + <content type="string" default="," /> + <shortdesc lang="en">Separator for CSV created by 'list' operation</shortdesc> + </parameter> + <parameter name="delay" unique="0" required="0"> + <getopt mixed="--delay=[seconds]" /> + <content type="second" default="0" /> + <shortdesc lang="en">Wait X seconds before fencing is started</shortdesc> + </parameter> + <parameter name="disable_timeout" unique="0" required="0"> + <getopt mixed="--disable-timeout=[true/false]" /> + <content type="string" /> + <shortdesc lang="en">Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)</shortdesc> + </parameter> + <parameter name="login_timeout" unique="0" required="0"> + <getopt mixed="--login-timeout=[seconds]" /> + <content type="second" default="5" /> + <shortdesc lang="en">Wait X seconds for cmd prompt after login</shortdesc> + </parameter> + <parameter name="power_timeout" unique="0" required="0"> + <getopt mixed="--power-timeout=[seconds]" /> + <content type="second" default="60" /> + <shortdesc lang="en">Test X seconds for status change after ON/OFF</shortdesc> + </parameter> + <parameter name="power_wait" unique="0" required="0"> + <getopt mixed="--power-wait=[seconds]" /> + <content type="second" default="0" /> + <shortdesc lang="en">Wait X seconds after issuing ON/OFF</shortdesc> + </parameter> + <parameter name="shell_timeout" unique="0" required="0"> + <getopt mixed="--shell-timeout=[seconds]" /> + <content type="second" default="3" /> + <shortdesc lang="en">Wait X seconds for cmd prompt after issuing command</shortdesc> + </parameter> + <parameter name="stonith_status_sleep" unique="0" required="0"> + <getopt mixed="--stonith-status-sleep=[seconds]" /> + <content type="second" default="1" /> + <shortdesc lang="en">Sleep X seconds between status calls during a STONITH action</shortdesc> + </parameter> + <parameter name="retry_on" unique="0" required="0"> + <getopt mixed="--retry-on=[attempts]" /> + <content type="integer" default="1" /> + <shortdesc lang="en">Count of attempts to retry power on</shortdesc> + </parameter> + <parameter name="gnutlscli_path" unique="0" required="0"> + <getopt mixed="--gnutlscli-path=[path]" /> + <shortdesc lang="en">Path to gnutls-cli binary</shortdesc> + </parameter> +</parameters> +<actions> + <action name="on" automatic="0"/> + <action name="off" /> + <action name="reboot" /> + <action name="status" /> + <action name="list" /> + <action name="list-status" /> + <action name="monitor" /> + <action name="metadata" /> + <action name="manpage" /> + <action name="validate-all" /> +</actions> +</resource-agent>