Compare commits
No commits in common. "c8" and "c8s" have entirely different histories.
@ -1,62 +0,0 @@
|
|||||||
3297473a9d57e93ff378eab173990c1b64673c01 SOURCES/Jinja2-3.0.2.tar.gz
|
|
||||||
e1b766b2b1601fde67b3b19ed2f13b9746bb1cca SOURCES/MarkupSafe-2.0.1.tar.gz
|
|
||||||
3880207fdf0db1eeadf976092c8fbf80a7335c24 SOURCES/PyJWT-2.4.0.tar.gz
|
|
||||||
a8c40a3ae9d4c159382a58db3153d83e5521c51e SOURCES/PyYAML-6.0.tar.gz
|
|
||||||
0c47ce98be5a519023c16e10027ed1268c489fcc SOURCES/adal-1.2.7.tar.gz
|
|
||||||
0a56f6d9ed2014a363486d33b63eca094379be06 SOURCES/aliyun-python-sdk-core-2.13.1.tar.gz
|
|
||||||
c2a98b9a1562d223a76514f05028488ca000c395 SOURCES/aliyun-python-sdk-ecs-4.9.3.tar.gz
|
|
||||||
f14647a4d37a9a254c4e711b95a7654fc418e41e SOURCES/aliyun-python-sdk-vpc-3.0.2.tar.gz
|
|
||||||
1f493a02d15374027ae2bcb2ea4daf5b907c528b SOURCES/azure-common-1.1.28.zip
|
|
||||||
acfa532a6e6acc7311d697b3835a6190796ab5b4 SOURCES/azure-core-1.24.2.zip
|
|
||||||
46d2d17d958ae305ced32fdd6aa847b0cdf31989 SOURCES/azure-identity-1.10.0.zip
|
|
||||||
2a2e61ec1805165b19c4652b316225ced9b1ca28 SOURCES/azure-mgmt-compute-27.2.0.zip
|
|
||||||
f73acf29dd33e65a37b13dc8048832cbdd9e8306 SOURCES/azure-mgmt-core-1.3.2.zip
|
|
||||||
d4672e130177a9b330cf40c5bded8ed3aa4b1143 SOURCES/azure-mgmt-network-20.0.0.zip
|
|
||||||
2512ff4ef016cad0b916006f6acf2a309f908c4d SOURCES/botocore-1.23.46.tar.gz
|
|
||||||
0d12f48faa727f0979e9ad5c4c80dfa32b73caff SOURCES/cachetools-4.2.4.tar.gz
|
|
||||||
ec7e8dd8ef95edfdb83a1ea040b8b88507b47615 SOURCES/certifi-2023.7.22.tar.gz
|
|
||||||
c42a46cd11f6153f299cf10e9c236e8b2a143c21 SOURCES/cffi-1.15.1.tar.gz
|
|
||||||
2384f6cfba4685d901262e073a4455d4cf76d102 SOURCES/chardet-4.0.0.tar.gz
|
|
||||||
865df92e66e5dc7b940144cbad8115c07dc8784f SOURCES/charset-normalizer-2.0.7.tar.gz
|
|
||||||
eb8be696115458f9368432525e9cae11d0f6bebf SOURCES/cryptography-3.3.2.tar.gz
|
|
||||||
e2561df8e7ff9113dab118a651371dd88dab0142 SOURCES/fence-agents-4.2.1.tar.gz
|
|
||||||
81c165cd2a388d5a6cd415308edaf11ee5bf42cd SOURCES/flit_core-3.10.1.tar.gz
|
|
||||||
f4e578dc0ed68d6667d7b36cdfc2647d55e9858f SOURCES/google-auth-2.3.0.tar.gz
|
|
||||||
74ec77d2e2ef6b2ef8503e6e398faa6f3ba298ae SOURCES/httplib2-0.19.1-py3-none-any.whl
|
|
||||||
08c0449533fc94462f78652dea209099754d9ee4 SOURCES/idna-3.3.tar.gz
|
|
||||||
ea36ce1c780dd44f01225dca7f9995a6685a60cc SOURCES/isodate-0.6.1.tar.gz
|
|
||||||
356c48dfea2214dd9e7e2b222a99dddfe9c0d05c SOURCES/jmespath-0.10.0.tar.gz
|
|
||||||
d06a9547b1a87e9c51b0a7c708189d993f2e3d89 SOURCES/kubernetes-12.0.1.tar.gz
|
|
||||||
373683b6adda29af3b1334a69bf149b85deb7899 SOURCES/msal-1.27.0.tar.gz
|
|
||||||
04e016bd1fa4ed6ddb852095a45d4f8c81a5b54a SOURCES/msal-extensions-1.0.0.tar.gz
|
|
||||||
00c5509205e59ebae09e5d3fe068ab61588e9b4a SOURCES/msrest-0.7.1.zip
|
|
||||||
bcedc87fc73e9738cbc8b0435513c01b6eb0b5c2 SOURCES/msrestazure-0.6.4.tar.gz
|
|
||||||
f6efa66f6106b069b5c0e0cf8cc677e4e96c91ca SOURCES/oauthlib-3.1.1.tar.gz
|
|
||||||
7e2f8f4cebf309ef6aaf740ee9073276d6937802 SOURCES/oauthlib-3.2.2.tar.gz
|
|
||||||
570d69d8c108ebb8aee562389d13b07dfb61ce25 SOURCES/openshift-0.12.1.tar.gz
|
|
||||||
bccbc1bf76a9db46998eb8e1ffa2f2a2baf9237a SOURCES/packaging-21.2-py3-none-any.whl
|
|
||||||
a243525070cf70f22a185447ebd3e1435dabb218 SOURCES/pip-21.3.1.tar.gz
|
|
||||||
67c8e65065f751c87cadf973a0a2d7b2cfad3bfa SOURCES/poetry-core-1.0.8.tar.gz
|
|
||||||
4f099f76d500f4ca500a17a3a647f84b7a796e54 SOURCES/portalocker-2.7.0.tar.gz
|
|
||||||
e0fa19f8fda46a1fa2253477499b116b33f67175 SOURCES/pyasn1-0.4.8.tar.gz
|
|
||||||
43b89feb6864fe359aae89120627165219de313b SOURCES/pyasn1-modules-0.2.8.tar.gz
|
|
||||||
0ae93d89b69fab48af3a407a2f8663bcea270c3d SOURCES/pycparser-2.20.tar.gz
|
|
||||||
c55d177e9484d974c95078d4ae945f89ba2c7251 SOURCES/pycryptodome-3.20.0.tar.gz
|
|
||||||
c8307f47e3b75a2d02af72982a2dfefa3f56e407 SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl
|
|
||||||
c2ba10c775b7a52a4b57cac4d4110a0c0f812a82 SOURCES/python-dateutil-2.8.2.tar.gz
|
|
||||||
1dc2fa004aa6517f1620e55d8a7b8e68a9cf2a47 SOURCES/python-string-utils-1.0.0.tar.gz
|
|
||||||
8c7a89d183d3e9b70bf91ba5b75eccf7111b9d8d SOURCES/requests-2.26.0.tar.gz
|
|
||||||
96c46fc47e75d06fa7ead4d7fb1ae7d58d68989f SOURCES/requests-2.27.1.tar.gz
|
|
||||||
f139aed770519b6a095b8fdc888d03955cbe9d8e SOURCES/requests-oauthlib-1.3.0.tar.gz
|
|
||||||
024bb67bc625557fd15b70e1c8e7d6abf5aa75dd SOURCES/requests-oauthlib-2.0.0.tar.gz
|
|
||||||
e8a53067e03fe1b6682fd99a40a7359396a06daa SOURCES/rsa-4.7.2.tar.gz
|
|
||||||
d1011ff44cd5a045de0460c1b79ec65592e86860 SOURCES/ruamel.yaml-0.17.16.tar.gz
|
|
||||||
27de97227bbbde5a9f571f9fad223578d7bdf7cc SOURCES/ruamel.yaml.clib-0.2.6.tar.gz
|
|
||||||
d5354718cb8c9330d3abc27445467ce8a5ed9d70 SOURCES/setuptools-58.3.0.tar.gz
|
|
||||||
0f34eba670121f9c41939ca8d805687de359f71c SOURCES/setuptools_scm-6.4.2.tar.gz
|
|
||||||
06fa0bb50f2a4e2917fd14c21e9d2d5508ce0163 SOURCES/six-1.16.0.tar.gz
|
|
||||||
2a2b9fb5137976719423b1bb8241b83eb32bb7b0 SOURCES/tomli-1.1.0.tar.gz
|
|
||||||
0fab53e2ff16ae6370cfcf72e3e251535a5ebe74 SOURCES/typing_extensions-4.1.1.tar.gz
|
|
||||||
84e2852d8da1655373f7ce5e7d5d3e256b62b4e4 SOURCES/urllib3-1.26.18.tar.gz
|
|
||||||
540f083782c584989c1a0f69ffd69ba7aae07db6 SOURCES/websocket-client-1.2.1.tar.gz
|
|
||||||
f9f3d980579b88baaacdb6c4a3cc4466b9353030 SOURCES/wheel-0.37.1.tar.gz
|
|
||||||
72
.gitignore
vendored
72
.gitignore
vendored
@ -1,62 +1,10 @@
|
|||||||
SOURCES/Jinja2-3.0.2.tar.gz
|
/*.tar.?z*
|
||||||
SOURCES/MarkupSafe-2.0.1.tar.gz
|
/*.rpm
|
||||||
SOURCES/PyJWT-2.4.0.tar.gz
|
/*.txt
|
||||||
SOURCES/PyYAML-6.0.tar.gz
|
/*.whl
|
||||||
SOURCES/adal-1.2.7.tar.gz
|
/*.zip
|
||||||
SOURCES/aliyun-python-sdk-core-2.13.1.tar.gz
|
/.*.swp
|
||||||
SOURCES/aliyun-python-sdk-ecs-4.9.3.tar.gz
|
/.build-*.log
|
||||||
SOURCES/aliyun-python-sdk-vpc-3.0.2.tar.gz
|
/*/
|
||||||
SOURCES/azure-common-1.1.28.zip
|
!/tests/
|
||||||
SOURCES/azure-core-1.24.2.zip
|
/tests/*.retry
|
||||||
SOURCES/azure-identity-1.10.0.zip
|
|
||||||
SOURCES/azure-mgmt-compute-27.2.0.zip
|
|
||||||
SOURCES/azure-mgmt-core-1.3.2.zip
|
|
||||||
SOURCES/azure-mgmt-network-20.0.0.zip
|
|
||||||
SOURCES/botocore-1.23.46.tar.gz
|
|
||||||
SOURCES/cachetools-4.2.4.tar.gz
|
|
||||||
SOURCES/certifi-2023.7.22.tar.gz
|
|
||||||
SOURCES/cffi-1.15.1.tar.gz
|
|
||||||
SOURCES/chardet-4.0.0.tar.gz
|
|
||||||
SOURCES/charset-normalizer-2.0.7.tar.gz
|
|
||||||
SOURCES/cryptography-3.3.2.tar.gz
|
|
||||||
SOURCES/fence-agents-4.2.1.tar.gz
|
|
||||||
SOURCES/flit_core-3.10.1.tar.gz
|
|
||||||
SOURCES/google-auth-2.3.0.tar.gz
|
|
||||||
SOURCES/httplib2-0.19.1-py3-none-any.whl
|
|
||||||
SOURCES/idna-3.3.tar.gz
|
|
||||||
SOURCES/isodate-0.6.1.tar.gz
|
|
||||||
SOURCES/jmespath-0.10.0.tar.gz
|
|
||||||
SOURCES/kubernetes-12.0.1.tar.gz
|
|
||||||
SOURCES/msal-1.27.0.tar.gz
|
|
||||||
SOURCES/msal-extensions-1.0.0.tar.gz
|
|
||||||
SOURCES/msrest-0.7.1.zip
|
|
||||||
SOURCES/msrestazure-0.6.4.tar.gz
|
|
||||||
SOURCES/oauthlib-3.1.1.tar.gz
|
|
||||||
SOURCES/oauthlib-3.2.2.tar.gz
|
|
||||||
SOURCES/openshift-0.12.1.tar.gz
|
|
||||||
SOURCES/packaging-21.2-py3-none-any.whl
|
|
||||||
SOURCES/pip-21.3.1.tar.gz
|
|
||||||
SOURCES/poetry-core-1.0.8.tar.gz
|
|
||||||
SOURCES/portalocker-2.7.0.tar.gz
|
|
||||||
SOURCES/pyasn1-0.4.8.tar.gz
|
|
||||||
SOURCES/pyasn1-modules-0.2.8.tar.gz
|
|
||||||
SOURCES/pycparser-2.20.tar.gz
|
|
||||||
SOURCES/pycryptodome-3.20.0.tar.gz
|
|
||||||
SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl
|
|
||||||
SOURCES/python-dateutil-2.8.2.tar.gz
|
|
||||||
SOURCES/python-string-utils-1.0.0.tar.gz
|
|
||||||
SOURCES/requests-2.26.0.tar.gz
|
|
||||||
SOURCES/requests-2.27.1.tar.gz
|
|
||||||
SOURCES/requests-oauthlib-1.3.0.tar.gz
|
|
||||||
SOURCES/requests-oauthlib-2.0.0.tar.gz
|
|
||||||
SOURCES/rsa-4.7.2.tar.gz
|
|
||||||
SOURCES/ruamel.yaml-0.17.16.tar.gz
|
|
||||||
SOURCES/ruamel.yaml.clib-0.2.6.tar.gz
|
|
||||||
SOURCES/setuptools-58.3.0.tar.gz
|
|
||||||
SOURCES/setuptools_scm-6.4.2.tar.gz
|
|
||||||
SOURCES/six-1.16.0.tar.gz
|
|
||||||
SOURCES/tomli-1.1.0.tar.gz
|
|
||||||
SOURCES/typing_extensions-4.1.1.tar.gz
|
|
||||||
SOURCES/urllib3-1.26.18.tar.gz
|
|
||||||
SOURCES/websocket-client-1.2.1.tar.gz
|
|
||||||
SOURCES/wheel-0.37.1.tar.gz
|
|
||||||
|
|||||||
790
RHEL-110964-1-fence_nutanix_ahv.patch
Normal file
790
RHEL-110964-1-fence_nutanix_ahv.patch
Normal file
@ -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 @@
|
||||||
|
+<?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>
|
||||||
39
RHEL-110964-2-fence_nutanix_ahv-update-metadata.patch
Normal file
39
RHEL-110964-2-fence_nutanix_ahv-update-metadata.patch
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
From 345a609a34ea153e168b15d48eb8f2dd8d017f04 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
|
||||||
|
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 @@
|
||||||
|
<?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>
|
||||||
|
+<resource-agent name="fence_nutanix_ahv" shortdesc="Fence agent for Nutanix AHV Cluster VMs." >
|
||||||
|
+<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.</longdesc>
|
||||||
|
<vendor-url>https://www.nutanix.com</vendor-url>
|
||||||
|
<parameters>
|
||||||
|
<parameter name="action" unique="0" required="1">
|
||||||
84
RHEL-5397-3-fence_scsi-fix-run_cmd.patch
Normal file
84
RHEL-5397-3-fence_scsi-fix-run_cmd.patch
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
--- a/agents/scsi/fence_scsi.py 2024-01-03 14:06:10.155417318 +0100
|
||||||
|
+++ b/agents/scsi/fence_scsi.py 2024-01-03 14:07:40.737369588 +0100
|
||||||
|
@@ -84,14 +84,14 @@
|
||||||
|
# check if host is ready to execute actions
|
||||||
|
def do_action_monitor(options):
|
||||||
|
# Check if required binaries are installed
|
||||||
|
- if bool(run_cmd(options, options["--sg_persist-path"] + " -V")["err"]):
|
||||||
|
+ if bool(run_cmd(options, options["--sg_persist-path"] + " -V")["rc"]):
|
||||||
|
logging.error("Unable to run " + options["--sg_persist-path"])
|
||||||
|
return 1
|
||||||
|
- elif bool(run_cmd(options, options["--sg_turs-path"] + " -V")["err"]):
|
||||||
|
+ elif bool(run_cmd(options, options["--sg_turs-path"] + " -V")["rc"]):
|
||||||
|
logging.error("Unable to run " + options["--sg_turs-path"])
|
||||||
|
return 1
|
||||||
|
elif ("--devices" not in options and
|
||||||
|
- bool(run_cmd(options, options["--vgs-path"] + " --version")["err"])):
|
||||||
|
+ bool(run_cmd(options, options["--vgs-path"] + " --version")["rc"])):
|
||||||
|
logging.error("Unable to run " + options["--vgs-path"])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@@ -102,11 +102,13 @@
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
-#run command, returns dict, ret["err"] = exit code; ret["out"] = output
|
||||||
|
+# run command, returns dict, ret["rc"] = exit code; ret["out"] = output;
|
||||||
|
+# ret["err"] = error
|
||||||
|
def run_cmd(options, cmd):
|
||||||
|
ret = {}
|
||||||
|
- (ret["err"], ret["out"], _) = run_command(options, cmd)
|
||||||
|
+ (ret["rc"], ret["out"], ret["err"]) = run_command(options, cmd)
|
||||||
|
ret["out"] = "".join([i for i in ret["out"] if i is not None])
|
||||||
|
+ ret["err"] = "".join([i for i in ret["err"] if i is not None])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@@ -122,11 +124,11 @@
|
||||||
|
def preempt_abort(options, host, dev):
|
||||||
|
reset_dev(options,dev)
|
||||||
|
cmd = options["--sg_persist-path"] + " -n -o -A -T 5 -K " + host + " -S " + options["--key"] + " -d " + dev
|
||||||
|
- return not bool(run_cmd(options, cmd)["err"])
|
||||||
|
+ return not bool(run_cmd(options, cmd)["rc"])
|
||||||
|
|
||||||
|
|
||||||
|
def reset_dev(options, dev):
|
||||||
|
- return run_cmd(options, options["--sg_turs-path"] + " " + dev)["err"]
|
||||||
|
+ return run_cmd(options, options["--sg_turs-path"] + " " + dev)["rc"]
|
||||||
|
|
||||||
|
|
||||||
|
def register_dev(options, dev, key):
|
||||||
|
@@ -171,13 +173,13 @@
|
||||||
|
reset_dev(options, dev)
|
||||||
|
cmd = options["--sg_persist-path"] + " -n -o -I -S " + key + " -d " + dev
|
||||||
|
cmd += " -Z" if "--aptpl" in options else ""
|
||||||
|
- return not bool(run_cmd(options, cmd)["err"])
|
||||||
|
+ return not bool(run_cmd(options, cmd)["rc"])
|
||||||
|
|
||||||
|
|
||||||
|
def reserve_dev(options, dev):
|
||||||
|
reset_dev(options,dev)
|
||||||
|
cmd = options["--sg_persist-path"] + " -n -o -R -T 5 -K " + options["--key"] + " -d " + dev
|
||||||
|
- return not bool(run_cmd(options, cmd)["err"])
|
||||||
|
+ return not bool(run_cmd(options, cmd)["rc"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_reservation_key(options, dev, fail=True):
|
||||||
|
@@ -201,7 +203,7 @@
|
||||||
|
opts = "-y "
|
||||||
|
cmd = options["--sg_persist-path"] + " -n -i " + opts + "-k -d " + dev
|
||||||
|
out = run_cmd(options, cmd)
|
||||||
|
- if out["err"]:
|
||||||
|
+ if out["rc"]:
|
||||||
|
fail_usage("Cannot get registration keys", fail)
|
||||||
|
if not fail:
|
||||||
|
return []
|
||||||
|
@@ -319,7 +321,7 @@
|
||||||
|
"--options vg_attr,pv_name "+\
|
||||||
|
"--config 'global { locking_type = 0 } devices { preferred_names = [ \"^/dev/dm\" ] }'"
|
||||||
|
out = run_cmd(options, cmd)
|
||||||
|
- if out["err"]:
|
||||||
|
+ if out["rc"]:
|
||||||
|
fail_usage("Failed: Cannot get shared devices")
|
||||||
|
for line in out["out"].splitlines():
|
||||||
|
vg_attr, pv_name = line.strip().split(":")
|
||||||
@ -1,4 +0,0 @@
|
|||||||
azure-mgmt-compute
|
|
||||||
azure-mgmt-network
|
|
||||||
azure-identity
|
|
||||||
msrestazure
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user