Auto sync2gitlab import of fence-agents-4.2.1-95.el8.src.rpm

This commit is contained in:
James Antill 2022-05-31 14:27:43 -04:00
parent d123f4c97a
commit 0532b6e439
12 changed files with 1651 additions and 7 deletions

30
.gitignore vendored
View File

@ -36,3 +36,33 @@
/tomli-1.0.1.tar.gz
/urllib3-1.26.7.tar.gz
/websocket-client-1.2.1.tar.gz
/PyYAML-5.1.tar.gz
/charset_normalizer-2.0.4-py3-none-any.whl
/debtcollector-2.2.0-py3-none-any.whl
/idna-3.2-py3-none-any.whl
/importlib_metadata-4.8.3.tar.gz
/importlib_resources-5.4.0.tar.gz
/iso8601-0.1.16-py2.py3-none-any.whl
/keystoneauth1-4.3.1-py3-none-any.whl
/msgpack-1.0.2.tar.gz
/netaddr-0.8.0-py2.py3-none-any.whl
/netifaces-0.11.0.tar.gz
/os_service_types-1.7.0-py2.py3-none-any.whl
/oslo.config-8.7.1-py3-none-any.whl
/oslo.i18n-5.0.1-py3-none-any.whl
/oslo.serialization-4.2.0-py3-none-any.whl
/oslo.utils-4.10.0-py3-none-any.whl
/packaging-21.0-py3-none-any.whl
/pbr-5.6.0-py2.py3-none-any.whl
/prettytable-2.2.0-py3-none-any.whl
/python_keystoneclient-4.2.0-py3-none-any.whl
/python_novaclient-17.5.0-py3-none-any.whl
/pytz-2022.1.tar.gz
/requests-2.26.0-py2.py3-none-any.whl
/rfc3986-1.5.0-py2.py3-none-any.whl
/stevedore-3.4.0-py3-none-any.whl
/typing_extensions-3.10.0.2.tar.gz
/wcwidth-0.2.5-py2.py3-none-any.whl
/wheel-0.37.0-py2.py3-none-any.whl
/wrapt-1.12.1.tar.gz
/zipp-3.6.0.tar.gz

View File

@ -0,0 +1,17 @@
--- a/lib/fencing.py.py 2022-04-22 10:25:59.347139685 +0200
+++ b/lib/fencing.py.py 2022-04-22 10:26:05.127169492 +0200
@@ -1115,6 +1115,14 @@
except pexpect.ExceptionPexpect:
pass
+def source_env(env_file):
+ # POSIX: name shall not contain '=', value doesn't contain '\0'
+ output = subprocess.check_output("source {} && env -0".format(env_file), shell=True,
+ executable="/bin/sh")
+ # replace env
+ os.environ.clear()
+ os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0'))
+
# Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is
# in format a.b.c.d...z and returned dict has key only z
def array_to_dict(array):

View File

@ -0,0 +1,460 @@
--- a/agents/openstack/fence_openstack.py 2018-06-28 14:24:54.000000000 +0200
+++ b/agents/openstack/fence_openstack.py 2022-04-22 10:15:28.069884360 +0200
@@ -2,110 +2,303 @@
import atexit
import logging
-import os
-import re
import sys
-from pipes import quote
-sys.path.append("/usr/share/fence")
+import os
+
+import urllib3
+
+sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
-from fencing import fail_usage, is_executable, run_command, run_delay
-from keystoneclient.v3 import client as ksclient
-from novaclient import client as novaclient
-from keystoneclient import session as ksc_session
-from keystoneclient.auth.identity import v3
-
-def get_name_or_uuid(options):
- return options["--uuid"] if "--uuid" in options else options["--plug"]
-
-def get_power_status(_, options):
- output = nova_run_command(options, "status")
- if (output == 'ACTIVE'):
- return 'on'
- else:
- return 'off'
+from fencing import fail_usage, run_delay, source_env
+
+try:
+ from novaclient import client
+ from novaclient.exceptions import Conflict, NotFound
+except ImportError:
+ pass
+
+urllib3.disable_warnings(urllib3.exceptions.SecurityWarning)
+
+
+def translate_status(instance_status):
+ if instance_status == "ACTIVE":
+ return "on"
+ elif instance_status == "SHUTOFF":
+ return "off"
+ return "unknown"
+
+def get_cloud(options):
+ import yaml
+
+ clouds_yaml = "~/.config/openstack/clouds.yaml"
+ if not os.path.exists(os.path.expanduser(clouds_yaml)):
+ clouds_yaml = "/etc/openstack/clouds.yaml"
+ if not os.path.exists(os.path.expanduser(clouds_yaml)):
+ fail_usage("Failed: ~/.config/openstack/clouds.yaml and /etc/openstack/clouds.yaml does not exist")
+
+ clouds_yaml = os.path.expanduser(clouds_yaml)
+ if os.path.exists(clouds_yaml):
+ with open(clouds_yaml, "r") as yaml_stream:
+ try:
+ clouds = yaml.safe_load(yaml_stream)
+ except yaml.YAMLError as exc:
+ fail_usage("Failed: Unable to read: " + clouds_yaml)
+
+ cloud = clouds.get("clouds").get(options["--cloud"])
+ if not cloud:
+ fail_usage("Cloud: {} not found.".format(options["--cloud"]))
+
+ return cloud
+
+
+def get_nodes_list(conn, options):
+ logging.info("Running %s action", options["--action"])
+ result = {}
+ response = conn.servers.list(detailed=True)
+ if response is not None:
+ for item in response:
+ instance_id = item.id
+ instance_name = item.name
+ instance_status = item.status
+ result[instance_id] = (instance_name, translate_status(instance_status))
+ return result
+
+
+def get_power_status(conn, options):
+ logging.info("Running %s action on %s", options["--action"], options["--plug"])
+ server = None
+ try:
+ server = conn.servers.get(options["--plug"])
+ except NotFound as e:
+ fail_usage("Failed: Not Found: " + str(e))
+ if server is None:
+ fail_usage("Server %s not found", options["--plug"])
+ state = server.status
+ status = translate_status(state)
+ logging.info("get_power_status: %s (state: %s)" % (status, state))
+ return status
+
+
+def set_power_status(conn, options):
+ logging.info("Running %s action on %s", options["--action"], options["--plug"])
+ action = options["--action"]
+ server = None
+ try:
+ server = conn.servers.get(options["--plug"])
+ except NotFound as e:
+ fail_usage("Failed: Not Found: " + str(e))
+ if server is None:
+ fail_usage("Server %s not found", options["--plug"])
+ if action == "on":
+ logging.info("Starting instance " + server.name)
+ try:
+ server.start()
+ except Conflict as e:
+ fail_usage(e)
+ logging.info("Called start API call for " + server.id)
+ if action == "off":
+ logging.info("Stopping instance " + server.name)
+ try:
+ server.stop()
+ except Conflict as e:
+ fail_usage(e)
+ logging.info("Called stop API call for " + server.id)
+ if action == "reboot":
+ logging.info("Rebooting instance " + server.name)
+ try:
+ server.reboot("HARD")
+ except Conflict as e:
+ fail_usage(e)
+ logging.info("Called reboot hard API call for " + server.id)
+
+
+def nova_login(username, password, projectname, auth_url, user_domain_name,
+ project_domain_name, ssl_insecure, cacert, apitimeout):
+ legacy_import = False
+
+ try:
+ from keystoneauth1 import loading
+ from keystoneauth1 import session as ksc_session
+ from keystoneauth1.exceptions.discovery import DiscoveryFailure
+ from keystoneauth1.exceptions.http import Unauthorized
+ except ImportError:
+ try:
+ from keystoneclient import session as ksc_session
+ from keystoneclient.auth.identity import v3
+
+ legacy_import = True
+ except ImportError:
+ fail_usage("Failed: Keystone client not found or not accessible")
+
+ if not legacy_import:
+ loader = loading.get_plugin_loader("password")
+ auth = loader.load_from_options(
+ auth_url=auth_url,
+ username=username,
+ password=password,
+ project_name=projectname,
+ user_domain_name=user_domain_name,
+ project_domain_name=project_domain_name,
+ )
+ else:
+ auth = v3.Password(
+ auth_url=auth_url,
+ username=username,
+ password=password,
+ project_name=projectname,
+ user_domain_name=user_domain_name,
+ project_domain_name=project_domain_name,
+ cacert=cacert,
+ )
+
+ caverify=True
+ if ssl_insecure:
+ caverify=False
+ elif cacert:
+ caverify=cacert
+
+ session = ksc_session.Session(auth=auth, verify=caverify, timeout=apitimeout)
+ nova = client.Client("2", session=session, timeout=apitimeout)
+ apiversion = None
+ try:
+ apiversion = nova.versions.get_current()
+ except DiscoveryFailure as e:
+ fail_usage("Failed: Discovery Failure: " + str(e))
+ except Unauthorized as e:
+ fail_usage("Failed: Unauthorized: " + str(e))
+ except Exception as e:
+ logging.error(e)
+ logging.debug("Nova version: %s", apiversion)
+ return nova
-def set_power_status(_, options):
- nova_run_command(options, options["--action"])
- return
-
-def nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name):
- auth=v3.Password(username=username,password=password,project_name=projectname,user_domain_name=user_domain_name,project_domain_name=project_domain_name,auth_url=auth_url)
- session = ksc_session.Session(auth=auth)
- keystone = ksclient.Client(session=session)
- nova = novaclient.Client(session=session)
- return nova
-
-def nova_run_command(options,action,timeout=None):
- username=options["--username"]
- password=options["--password"]
- projectname=options["--project-name"]
- auth_url=options["--auth-url"]
- user_domain_name=options["--user-domain-name"]
- project_domain_name=options["--project-domain-name"]
- novaclient=nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name)
- server = novaclient.servers.get(options["--uuid"])
- if action == "status":
- return server.status
- if action == "on":
- server.start()
- if action == "off":
- server.stop()
- if action == "reboot":
- server.reboot('REBOOT_HARD')
def define_new_opts():
all_opt["auth-url"] = {
- "getopt" : ":",
- "longopt" : "auth-url",
- "help" : "--auth-url=[authurl] Keystone Auth URL",
- "required" : "1",
- "shortdesc" : "Keystone Auth URL",
- "order": 1
+ "getopt": ":",
+ "longopt": "auth-url",
+ "help": "--auth-url=[authurl] Keystone Auth URL",
+ "required": "0",
+ "shortdesc": "Keystone Auth URL",
+ "order": 2,
}
all_opt["project-name"] = {
- "getopt" : ":",
- "longopt" : "project-name",
- "help" : "--project-name=[project] Tenant Or Project Name",
- "required" : "1",
- "shortdesc" : "Keystone Project",
+ "getopt": ":",
+ "longopt": "project-name",
+ "help": "--project-name=[project] Tenant Or Project Name",
+ "required": "0",
+ "shortdesc": "Keystone Project",
"default": "admin",
- "order": 1
+ "order": 3,
}
all_opt["user-domain-name"] = {
- "getopt" : ":",
- "longopt" : "user-domain-name",
- "help" : "--user-domain-name=[user-domain] Keystone User Domain Name",
- "required" : "0",
- "shortdesc" : "Keystone User Domain Name",
+ "getopt": ":",
+ "longopt": "user-domain-name",
+ "help": "--user-domain-name=[domain] Keystone User Domain Name",
+ "required": "0",
+ "shortdesc": "Keystone User Domain Name",
"default": "Default",
- "order": 1
+ "order": 4,
}
all_opt["project-domain-name"] = {
- "getopt" : ":",
- "longopt" : "project-domain-name",
- "help" : "--project-domain-name=[project-domain] Keystone Project Domain Name",
- "required" : "0",
- "shortdesc" : "Keystone Project Domain Name",
+ "getopt": ":",
+ "longopt": "project-domain-name",
+ "help": "--project-domain-name=[domain] Keystone Project Domain Name",
+ "required": "0",
+ "shortdesc": "Keystone Project Domain Name",
"default": "Default",
- "order": 1
+ "order": 5,
+ }
+ all_opt["cloud"] = {
+ "getopt": ":",
+ "longopt": "cloud",
+ "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).",
+ "required": "0",
+ "shortdesc": "Cloud from clouds.yaml",
+ "order": 6,
+ }
+ all_opt["openrc"] = {
+ "getopt": ":",
+ "longopt": "openrc",
+ "help": "--openrc=[openrc] Path to the openrc config file",
+ "required": "0",
+ "shortdesc": "openrc config file",
+ "order": 7,
}
all_opt["uuid"] = {
- "getopt" : ":",
- "longopt" : "uuid",
- "help" : "--uuid=[uuid] UUID of the nova instance",
- "required" : "1",
- "shortdesc" : "UUID of the nova instance",
- "order": 1
+ "getopt": ":",
+ "longopt": "uuid",
+ "help": "--uuid=[uuid] Replaced by -n, --plug",
+ "required": "0",
+ "shortdesc": "Replaced by port/-n/--plug",
+ "order": 8,
+ }
+ all_opt["cacert"] = {
+ "getopt": ":",
+ "longopt": "cacert",
+ "help": "--cacert=[cacert] Path to the PEM file with trusted authority certificates (override global CA trust)",
+ "required": "0",
+ "shortdesc": "SSL X.509 certificates file",
+ "default": "",
+ "order": 9,
+ }
+ all_opt["apitimeout"] = {
+ "getopt": ":",
+ "type": "second",
+ "longopt": "apitimeout",
+ "help": "--apitimeout=[seconds] Timeout to use for API calls",
+ "shortdesc": "Timeout in seconds to use for API calls, default is 60.",
+ "required": "0",
+ "default": 60,
+ "order": 10,
}
+
def main():
+ conn = None
+
+ device_opt = [
+ "login",
+ "no_login",
+ "passwd",
+ "no_password",
+ "auth-url",
+ "project-name",
+ "user-domain-name",
+ "project-domain-name",
+ "cloud",
+ "openrc",
+ "port",
+ "no_port",
+ "uuid",
+ "ssl_insecure",
+ "cacert",
+ "apitimeout",
+ ]
+
atexit.register(atexit_handler)
- device_opt = ["login", "passwd", "auth-url", "project-name", "user-domain-name", "project-domain-name", "uuid"]
define_new_opts()
+ all_opt["port"]["required"] = "0"
+ all_opt["port"]["help"] = "-n, --plug=[UUID] UUID of the node to be fenced"
+ all_opt["port"]["shortdesc"] = "UUID of the node to be fenced."
+ all_opt["power_timeout"]["default"] = "60"
+
options = check_input(device_opt, process_input(device_opt))
+ # workaround to avoid regressions
+ if "--uuid" in options:
+ options["--plug"] = options["--uuid"]
+ del options["--uuid"]
+ elif ("--help" not in options
+ and options["--action"] in ["off", "on", "reboot", "status", "validate-all"]
+ and "--plug" not in options):
+ stop_after_error = False if options["--action"] == "validate-all" else True
+ fail_usage(
+ "Failed: You have to enter plug number or machine identification",
+ stop_after_error,
+ )
+
docs = {}
docs["shortdesc"] = "Fence agent for OpenStack's Nova service"
docs["longdesc"] = "fence_openstack is a Fencing agent \
@@ -116,9 +309,73 @@
run_delay(options)
- result = fence_action(None, options, set_power_status, get_power_status,None)
+ if options.get("--cloud"):
+ cloud = get_cloud(options)
+ username = cloud.get("auth").get("username")
+ password = cloud.get("auth").get("password")
+ projectname = cloud.get("auth").get("project_name")
+ auth_url = None
+ try:
+ auth_url = cloud.get("auth").get("auth_url")
+ except KeyError:
+ fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
+ user_domain_name = cloud.get("auth").get("user_domain_name")
+ project_domain_name = cloud.get("auth").get("project_domain_name")
+ caverify = cloud.get("verify")
+ if caverify in [True, False]:
+ options["--ssl-insecure"] = caverify
+ else:
+ options["--cacert"] = caverify
+ elif options.get("--openrc"):
+ if not os.path.exists(os.path.expanduser(options["--openrc"])):
+ fail_usage("Failed: {} does not exist".format(options.get("--openrc")))
+ source_env(options["--openrc"])
+ env = os.environ
+ username = env.get("OS_USERNAME")
+ password = env.get("OS_PASSWORD")
+ projectname = env.get("OS_PROJECT_NAME")
+ auth_url = None
+ try:
+ auth_url = env["OS_AUTH_URL"]
+ except KeyError:
+ fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
+ user_domain_name = env.get("OS_USER_DOMAIN_NAME")
+ project_domain_name = env.get("OS_PROJECT_DOMAIN_NAME")
+ else:
+ username = options["--username"]
+ password = options["--password"]
+ projectname = options["--project-name"]
+ auth_url = None
+ try:
+ auth_url = options["--auth-url"]
+ except KeyError:
+ fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
+ user_domain_name = options["--user-domain-name"]
+ project_domain_name = options["--project-domain-name"]
+
+ ssl_insecure = "--ssl-insecure" in options
+ cacert = options["--cacert"]
+ apitimeout = options["--apitimeout"]
+
+ try:
+ conn = nova_login(
+ username,
+ password,
+ projectname,
+ auth_url,
+ user_domain_name,
+ project_domain_name,
+ ssl_insecure,
+ cacert,
+ apitimeout,
+ )
+ except Exception as e:
+ fail_usage("Failed: Unable to connect to Nova: " + str(e))
+
+ # Operate the fencing device
+ result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list)
sys.exit(result)
+
if __name__ == "__main__":
main()
-

View File

@ -0,0 +1,10 @@
--- a/agents/openstack/fence_openstack.py 2021-06-03 11:48:01.000000000 +0200
+++ b/agents/openstack/fence_openstack.py 2021-06-30 09:13:16.598937530 +0200
@@ -11,6 +11,7 @@
from fencing import fail_usage, run_delay
try:
+ sys.path.insert(0, '/usr/lib/fence-agents/bundled/openstack')
from novaclient import client
from novaclient.exceptions import Conflict, NotFound
except ImportError:

View File

@ -0,0 +1,243 @@
From 999f2f8b4dc7d258679daf8c3f13d9b317ff4435 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Thu, 7 Apr 2022 13:11:12 +0200
Subject: [PATCH] all agents: unify ssl parameters to avoid having to use --ssl
when using --ssl-secure/--ssl-insecure for some agents
THIS MIGHT BREAK SETUPS USING fence_docker or fence_pve!
---
agents/cisco_ucs/fence_cisco_ucs.py | 9 +++++----
agents/docker/fence_docker.py | 9 ++++++---
agents/rhevm/fence_rhevm.py | 8 ++++----
agents/vmware_rest/fence_vmware_rest.py | 8 ++++----
agents/vmware_soap/fence_vmware_soap.py | 2 +-
agents/vmware_vcloud/fence_vmware_vcloud.py | 4 ++--
agents/zvm/fence_zvmip.py | 8 +++++---
lib/fencing.py.py | 6 ++++++
tests/data/metadata/fence_docker.xml | 4 ++--
13 files changed, 64 insertions(+), 31 deletions(-)
diff --git a/agents/cisco_ucs/fence_cisco_ucs.py b/agents/cisco_ucs/fence_cisco_ucs.py
index 2280dbbc7..b85379a73 100644
--- a/agents/cisco_ucs/fence_cisco_ucs.py
+++ b/agents/cisco_ucs/fence_cisco_ucs.py
@@ -99,7 +99,7 @@ def get_list(conn, options):
def send_command(opt, command, timeout):
## setup correct URL
- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt:
+ if "--ssl-secure" in opt or "--ssl-insecure" in opt:
url = "https:"
else:
url = "http:"
@@ -114,13 +114,14 @@ def send_command(opt, command, timeout):
conn.setopt(pycurl.POSTFIELDS, command.encode("ascii"))
conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write)
conn.setopt(pycurl.TIMEOUT, timeout)
- if "--ssl" in opt or "--ssl-secure" in opt:
+
+ if "--ssl-secure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
-
- if "--ssl-insecure" in opt:
+ elif "--ssl-insecure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 0)
conn.setopt(pycurl.SSL_VERIFYHOST, 0)
+
conn.perform()
result = web_buffer.getvalue().decode()
diff --git a/agents/docker/fence_docker.py b/agents/docker/fence_docker.py
index fef87da86..004402518 100644
--- a/agents/docker/fence_docker.py
+++ b/agents/docker/fence_docker.py
@@ -43,7 +43,7 @@ def get_list(conn, options):
def send_cmd(options, cmd, post = False):
- url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd)
+ url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl-secure" in options or "--ssl-insecure" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd)
conn = pycurl.Curl()
output_buffer = io.BytesIO()
if logging.getLogger().getEffectiveLevel() < logging.WARNING:
@@ -55,7 +55,8 @@ def send_cmd(options, cmd, post = False):
conn.setopt(pycurl.POSTFIELDSIZE, 0)
conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write)
conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"]))
- if "--ssl" in options:
+
+ if "--ssl-secure" in options:
if not (set(("--tlscert", "--tlskey", "--tlscacert")) <= set(options)):
fail_usage("Failed. If --ssl option is used, You have to also \
specify: --tlscert, --tlskey and --tlscacert")
@@ -63,7 +64,7 @@ def send_cmd(options, cmd, post = False):
conn.setopt(pycurl.SSLCERT, options["--tlscert"])
conn.setopt(pycurl.SSLKEY, options["--tlskey"])
conn.setopt(pycurl.CAINFO, options["--tlscacert"])
- else:
+ elif "--ssl-insecure" in options:
conn.setopt(pycurl.SSL_VERIFYPEER, 0)
conn.setopt(pycurl.SSL_VERIFYHOST, 0)
@@ -136,6 +137,8 @@ def main():
device_opt = ["ipaddr", "no_password", "no_login", "port", "method", "web", "tlscert", "tlskey", "tlscacert", "ssl", "api_version"]
+ all_opt["ssl"]["default"] = "1"
+
options = check_input(device_opt, process_input(device_opt))
docs = { }
diff --git a/agents/rhevm/fence_rhevm.py b/agents/rhevm/fence_rhevm.py
index 25aecbe58..5f74d06f6 100644
--- a/agents/rhevm/fence_rhevm.py
+++ b/agents/rhevm/fence_rhevm.py
@@ -85,7 +85,7 @@ def send_command(opt, command, method="GET"):
logging.debug("auto-detected API version: " + opt["--api-version"])
## setup correct URL
- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt:
+ if "--ssl-secure" in opt or "--ssl-insecure" in opt:
url = "https:"
else:
url = "http:"
@@ -126,11 +126,11 @@ def send_command(opt, command, method="GET"):
conn.setopt(pycurl.COOKIEJAR, cookie_file)
conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"]))
- if "--ssl" in opt or "--ssl-secure" in opt:
+
+ if "--ssl-secure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
-
- if "--ssl-insecure" in opt:
+ elif "--ssl-insecure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 0)
conn.setopt(pycurl.SSL_VERIFYHOST, 0)
diff --git a/agents/vmware_rest/fence_vmware_rest.py b/agents/vmware_rest/fence_vmware_rest.py
index e49fd5663..4b884fc62 100644
--- a/agents/vmware_rest/fence_vmware_rest.py
+++ b/agents/vmware_rest/fence_vmware_rest.py
@@ -69,7 +69,7 @@ def connect(opt):
conn = pycurl.Curl()
## setup correct URL
- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt:
+ if "--ssl-secure" in opt or "--ssl-insecure" in opt:
conn.base_url = "https:"
else:
conn.base_url = "http:"
@@ -89,11 +89,11 @@ def connect(opt):
conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"])
conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"]))
- if "--ssl" in opt or "--ssl-secure" in opt:
+
+ if "--ssl-secure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
-
- if "--ssl-insecure" in opt:
+ elif "--ssl-insecure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 0)
conn.setopt(pycurl.SSL_VERIFYHOST, 0)
diff --git a/agents/vmware_soap/fence_vmware_soap.py b/agents/vmware_soap/fence_vmware_soap.py
index 2cd45e0b3..51fb0f147 100644
--- a/agents/vmware_soap/fence_vmware_soap.py
+++ b/agents/vmware_soap/fence_vmware_soap.py
@@ -34,7 +34,7 @@ def send(self, request):
def soap_login(options):
run_delay(options)
- if "--ssl" in options or "--ssl-secure" in options or "--ssl-insecure" in options:
+ if "--ssl-secure" in options or "--ssl-insecure" in options:
if "--ssl-insecure" in options:
import ssl
import urllib3
diff --git a/agents/vmware_vcloud/fence_vmware_vcloud.py b/agents/vmware_vcloud/fence_vmware_vcloud.py
index 42372a83d..7626b82bb 100644
--- a/agents/vmware_vcloud/fence_vmware_vcloud.py
+++ b/agents/vmware_vcloud/fence_vmware_vcloud.py
@@ -60,7 +60,7 @@ def connect(opt):
conn = pycurl.Curl()
## setup correct URL
- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt:
+ if "--ssl-secure" in opt or "--ssl-insecure" in opt:
conn.base_url = "https:"
else:
conn.base_url = "http:"
@@ -76,7 +76,7 @@ def connect(opt):
conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"])
conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"]))
- if "--ssl" in opt or "--ssl-secure" in opt:
+ if "--ssl-secure" in opt:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
elif "--ssl-insecure" in opt:
diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py
index e8f849eda..90ca95d45 100644
--- a/agents/zvm/fence_zvmip.py
+++ b/agents/zvm/fence_zvmip.py
@@ -26,7 +26,7 @@ def open_socket(options):
except socket.gaierror:
fail(EC_LOGIN_DENIED)
- if "--ssl" in options:
+ if "--ssl-secure" in options or "--ssl-insecure" in options:
import ssl
sock = socket.socket()
sslcx = ssl.create_default_context()
@@ -132,7 +132,7 @@ def get_list_of_images(options, command, data_as_plug):
images = set()
if output_len > 3*INT4:
- recvflag = socket.MSG_WAITALL if "--ssl" not in options else 0
+ recvflag = socket.MSG_WAITALL if "--ssl-secure" not in options and "--ssl-insecure" not in options else 0
array_len = struct.unpack("!i", conn.recv(INT4))[0]
data = ""
diff --git a/lib/fencing.py.py b/lib/fencing.py.py
index 696388d55..b746ede8b 100644
--- a/lib/fencing.py.py
+++ b/lib/fencing.py.py
@@ -789,6 +789,12 @@ def check_input(device_opt, opt, other_conditions = False):
if "--password-script" in options:
options["--password"] = os.popen(options["--password-script"]).read().rstrip()
+ if "--ssl-secure" in options or "--ssl-insecure" in options:
+ options["--ssl"] = ""
+
+ if "--ssl" in options and "--ssl-insecure" not in options:
+ options["--ssl-secure"] = ""
+
if os.environ.get("PCMK_service") == "pacemaker-fenced" and "--disable-timeout" not in options:
options["--disable-timeout"] = "1"
diff --git a/tests/data/metadata/fence_docker.xml b/tests/data/metadata/fence_docker.xml
index 723e72280..51c7c470a 100644
--- a/tests/data/metadata/fence_docker.xml
+++ b/tests/data/metadata/fence_docker.xml
@@ -20,7 +20,7 @@
</parameter>
<parameter name="ipport" unique="0" required="0">
<getopt mixed="-u, --ipport=[port]" />
- <content type="integer" default="80" />
+ <content type="integer" default="443" />
<shortdesc lang="en">TCP/UDP port to use for connection with device</shortdesc>
</parameter>
<parameter name="method" unique="0" required="0">
@@ -43,7 +43,7 @@
</parameter>
<parameter name="ssl" unique="0" required="0">
<getopt mixed="-z, --ssl" />
- <content type="boolean" />
+ <content type="boolean" default="1" />
<shortdesc lang="en">Use SSL connection with verifying certificate</shortdesc>
</parameter>
<parameter name="ssl_insecure" unique="0" required="0">

View File

@ -0,0 +1,563 @@
--- fence-agents-4.2.1/agents/gce/fence_gce.py 2022-04-28 15:58:42.949723547 +0200
+++ fence-agents-4.2.1/agents/gce/fence_gce.py2 2022-04-28 15:59:21.054915265 +0200
@@ -1,10 +1,22 @@
#!@PYTHON@ -tt
+#
+# Requires the googleapiclient and oauth2client
+# RHEL 7.x: google-api-python-client==1.6.7 python-gflags==2.0 pyasn1==0.4.8 rsa==3.4.2 pysocks==1.7.1 httplib2==0.19.0
+# RHEL 8.x: pysocks==1.7.1 httplib2==0.19.0
+# SLES 12.x: python-google-api-python-client python-oauth2client python-oauth2client-gce pysocks==1.7.1 httplib2==0.19.0
+# SLES 15.x: python3-google-api-python-client python3-oauth2client pysocks==1.7.1 httplib2==0.19.0
+#
+
import atexit
import logging
+import json
+import re
import os
+import socket
import sys
import time
+
if sys.version_info >= (3, 0):
# Python 3 imports.
import urllib.parse as urlparse
@@ -15,7 +27,7 @@
import urllib2 as urlrequest
sys.path.append("@FENCEAGENTSLIBDIR@")
-from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action
+from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, run_command
try:
sys.path.insert(0, '/usr/lib/fence-agents/bundled/google')
import httplib2
@@ -30,6 +42,85 @@
METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
+INSTANCE_LINK = 'https://www.googleapis.com/compute/v1/projects/{}/zones/{}/instances/{}'
+
+def run_on_fail(options):
+ if "--runonfail" in options:
+ run_command(options, options["--runonfail"])
+
+def fail_fence_agent(options, message):
+ run_on_fail(options)
+ fail_usage(message)
+
+def raise_fence_agent(options, message):
+ run_on_fail(options)
+ raise Exception(message)
+
+#
+# Will use baremetalsolution setting or the environment variable
+# FENCE_GCE_URI_REPLACEMENTS to replace the uri for calls to *.googleapis.com.
+#
+def replace_api_uri(options, http_request):
+ uri_replacements = []
+ # put any env var replacements first, then baremetalsolution if in options
+ if "FENCE_GCE_URI_REPLACEMENTS" in os.environ:
+ logging.debug("FENCE_GCE_URI_REPLACEMENTS environment variable exists")
+ env_uri_replacements = os.environ["FENCE_GCE_URI_REPLACEMENTS"]
+ try:
+ uri_replacements_json = json.loads(env_uri_replacements)
+ if isinstance(uri_replacements_json, list):
+ uri_replacements = uri_replacements_json
+ else:
+ logging.warning("FENCE_GCE_URI_REPLACEMENTS exists, but is not a JSON List")
+ except ValueError as e:
+ logging.warning("FENCE_GCE_URI_REPLACEMENTS exists but is not valid JSON")
+ if "--baremetalsolution" in options:
+ uri_replacements.append(
+ {
+ "matchlength": 4,
+ "match": "https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)",
+ "replace": "https://baremetalsolution.googleapis.com/v1/projects/\\1/locations/\\2/instances/\\3:resetInstance\\4"
+ })
+ for uri_replacement in uri_replacements:
+ # each uri_replacement should have matchlength, match, and replace
+ if "matchlength" not in uri_replacement or "match" not in uri_replacement or "replace" not in uri_replacement:
+ logging.warning("FENCE_GCE_URI_REPLACEMENTS missing matchlength, match, or replace in %s" % uri_replacement)
+ continue
+ match = re.match(uri_replacement["match"], http_request.uri)
+ if match is None or len(match.groups()) != uri_replacement["matchlength"]:
+ continue
+ replaced_uri = re.sub(uri_replacement["match"], uri_replacement["replace"], http_request.uri)
+ match = re.match("https:\/\/.*.googleapis.com", replaced_uri)
+ if match is None or match.start() != 0:
+ logging.warning("FENCE_GCE_URI_REPLACEMENTS replace is not "
+ "targeting googleapis.com, ignoring it: %s" % replaced_uri)
+ continue
+ logging.debug("Replacing googleapis uri %s with %s" % (http_request.uri, replaced_uri))
+ http_request.uri = replaced_uri
+ break
+ return http_request
+
+def retry_api_execute(options, http_request):
+ replaced_http_request = replace_api_uri(options, http_request)
+ retries = 3
+ if options.get("--retries"):
+ retries = int(options.get("--retries"))
+ retry_sleep = 5
+ if options.get("--retrysleep"):
+ retry_sleep = int(options.get("--retrysleep"))
+ retry = 0
+ current_err = None
+ while retry <= retries:
+ if retry > 0:
+ time.sleep(retry_sleep)
+ try:
+ return replaced_http_request.execute()
+ except Exception as err:
+ current_err = err
+ logging.warning("Could not execute api call to: %s, retry: %s, "
+ "err: %s" % (replaced_http_request.uri, retry, str(err)))
+ retry += 1
+ raise current_err
def translate_status(instance_status):
@@ -43,86 +134,174 @@
def get_nodes_list(conn, options):
result = {}
+ if "--zone" not in options:
+ fail_fence_agent(options, "Failed: get_nodes_list: Please specify the --zone in the command")
try:
- instanceList = conn.instances().list(project=options["--project"], zone=options["--zone"]).execute()
- for instance in instanceList["items"]:
- result[instance["id"]] = (instance["name"], translate_status(instance["status"]))
+ for zone in options["--zone"].split(","):
+ instanceList = retry_api_execute(options, conn.instances().list(
+ project=options["--project"],
+ zone=zone))
+ for instance in instanceList["items"]:
+ result[instance["id"]] = (instance["name"], translate_status(instance["status"]))
except Exception as err:
- fail_usage("Failed: get_nodes_list: {}".format(str(err)))
+ fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err)))
return result
def get_power_status(conn, options):
+ logging.debug("get_power_status")
+ # if this is bare metal we need to just send back the opposite of the
+ # requested action: if on send off, if off send on
+ if "--baremetalsolution" in options:
+ if options.get("--action") == "on":
+ return "off"
+ else:
+ return "on"
+ # If zone is not listed for an entry we attempt to get it automatically
+ instance = options["--plug"]
+ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
+ instance_status = get_instance_power_status(conn, options, instance, zone)
+ # If any of the instances do not match the intended status we return the
+ # the opposite status so that the fence agent can change it.
+ if instance_status != options.get("--action"):
+ return instance_status
+
+ return options.get("--action")
+
+
+def get_instance_power_status(conn, options, instance, zone):
try:
- instance = conn.instances().get(
- project=options["--project"],
- zone=options["--zone"],
- instance=options["--plug"]).execute()
+ instance = retry_api_execute(
+ options,
+ conn.instances().get(project=options["--project"], zone=zone, instance=instance))
return translate_status(instance["status"])
except Exception as err:
- fail_usage("Failed: get_power_status: {}".format(str(err)))
+ fail_fence_agent(options, "Failed: get_instance_power_status: {}".format(str(err)))
-def wait_for_operation(conn, project, zone, operation):
+def check_for_existing_operation(conn, options, instance, zone, operation_type):
+ logging.debug("check_for_existing_operation")
+ if "--baremetalsolution" in options:
+ # There is no API for checking in progress operations
+ return False
+
+ project = options["--project"]
+ target_link = INSTANCE_LINK.format(project, zone, instance)
+ query_filter = '(targetLink = "{}") AND (operationType = "{}") AND (status = "RUNNING")'.format(target_link, operation_type)
+ result = retry_api_execute(
+ options,
+ conn.zoneOperations().list(project=project, zone=zone, filter=query_filter, maxResults=1))
+
+ if "items" in result and result["items"]:
+ logging.info("Existing %s operation found", operation_type)
+ return result["items"][0]
+
+
+def wait_for_operation(conn, options, zone, operation):
+ if 'name' not in operation:
+ logging.warning('Cannot wait for operation to complete, the'
+ ' requested operation will continue asynchronously')
+ return False
+
+ wait_time = 0
+ project = options["--project"]
while True:
- result = conn.zoneOperations().get(
+ result = retry_api_execute(options, conn.zoneOperations().get(
project=project,
zone=zone,
- operation=operation['name']).execute()
+ operation=operation['name']))
if result['status'] == 'DONE':
if 'error' in result:
- raise Exception(result['error'])
- return
+ raise_fence_agent(options, result['error'])
+ return True
+
+ if "--errortimeout" in options and wait_time > int(options["--errortimeout"]):
+ raise_fence_agent(options, "Operation did not complete before the timeout.")
+
+ if "--warntimeout" in options and wait_time > int(options["--warntimeout"]):
+ logging.warning("Operation did not complete before the timeout.")
+ if "--runonwarn" in options:
+ run_command(options, options["--runonwarn"])
+ return False
+
+ wait_time = wait_time + 1
time.sleep(1)
def set_power_status(conn, options):
+ logging.debug("set_power_status")
+ instance = options["--plug"]
+ # If zone is not listed for an entry we attempt to get it automatically
+ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
+ set_instance_power_status(conn, options, instance, zone, options["--action"])
+
+
+def set_instance_power_status(conn, options, instance, zone, action):
+ logging.info("Setting power status of %s in zone %s", instance, zone)
+ project = options["--project"]
+
try:
- if options["--action"] == "off":
- logging.info("Issuing poweroff of %s in zone %s" % (options["--plug"], options["--zone"]))
- operation = conn.instances().stop(
- project=options["--project"],
- zone=options["--zone"],
- instance=options["--plug"]).execute()
- wait_for_operation(conn, options["--project"], options["--zone"], operation)
- logging.info("Poweroff of %s in zone %s complete" % (options["--plug"], options["--zone"]))
- elif options["--action"] == "on":
- logging.info("Issuing poweron of %s in zone %s" % (options["--plug"], options["--zone"]))
- operation = conn.instances().start(
- project=options["--project"],
- zone=options["--zone"],
- instance=options["--plug"]).execute()
- wait_for_operation(conn, options["--project"], options["--zone"], operation)
- logging.info("Poweron of %s in zone %s complete" % (options["--plug"], options["--zone"]))
+ if action == "off":
+ logging.info("Issuing poweroff of %s in zone %s", instance, zone)
+ operation = check_for_existing_operation(conn, options, instance, zone, "stop")
+ if operation and "--earlyexit" in options:
+ return
+ if not operation:
+ operation = retry_api_execute(
+ options,
+ conn.instances().stop(project=project, zone=zone, instance=instance))
+ logging.info("Poweroff command completed, waiting for the operation to complete")
+ if wait_for_operation(conn, options, zone, operation):
+ logging.info("Poweroff of %s in zone %s complete", instance, zone)
+ elif action == "on":
+ logging.info("Issuing poweron of %s in zone %s", instance, zone)
+ operation = check_for_existing_operation(conn, options, instance, zone, "start")
+ if operation and "--earlyexit" in options:
+ return
+ if not operation:
+ operation = retry_api_execute(
+ options,
+ conn.instances().start(project=project, zone=zone, instance=instance))
+ if wait_for_operation(conn, options, zone, operation):
+ logging.info("Poweron of %s in zone %s complete", instance, zone)
except Exception as err:
- fail_usage("Failed: set_power_status: {}".format(str(err)))
-
+ fail_fence_agent(options, "Failed: set_instance_power_status: {}".format(str(err)))
def power_cycle(conn, options):
+ logging.debug("power_cycle")
+ instance = options["--plug"]
+ # If zone is not listed for an entry we attempt to get it automatically
+ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
+ return power_cycle_instance(conn, options, instance, zone)
+
+
+def power_cycle_instance(conn, options, instance, zone):
+ logging.info("Issuing reset of %s in zone %s", instance, zone)
+ project = options["--project"]
+
try:
- logging.info('Issuing reset of %s in zone %s' % (options["--plug"], options["--zone"]))
- operation = conn.instances().reset(
- project=options["--project"],
- zone=options["--zone"],
- instance=options["--plug"]).execute()
- wait_for_operation(conn, options["--project"], options["--zone"], operation)
- logging.info('Reset of %s in zone %s complete' % (options["--plug"], options["--zone"]))
+ operation = check_for_existing_operation(conn, options, instance, zone, "reset")
+ if operation and "--earlyexit" in options:
+ return True
+ if not operation:
+ operation = retry_api_execute(
+ options,
+ conn.instances().reset(project=project, zone=zone, instance=instance))
+ logging.info("Reset command sent, waiting for the operation to complete")
+ if wait_for_operation(conn, options, zone, operation):
+ logging.info("Reset of %s in zone %s complete", instance, zone)
return True
except Exception as err:
- logging.error("Failed: power_cycle: {}".format(str(err)))
- return False
-
-
-def get_instance(conn, project, zone, instance):
- request = conn.instances().get(
- project=project, zone=zone, instance=instance)
- return request.execute()
+ logging.exception("Failed: power_cycle")
+ raise err
-def get_zone(conn, project, instance):
+def get_zone(conn, options, instance):
+ logging.debug("get_zone");
+ project = options['--project']
fl = 'name="%s"' % instance
- request = conn.instances().aggregatedList(project=project, filter=fl)
+ request = replace_api_uri(options, conn.instances().aggregatedList(project=project, filter=fl))
while request is not None:
response = request.execute()
zones = response.get('items', {})
@@ -130,9 +309,9 @@
for inst in zone.get('instances', []):
if inst['name'] == instance:
return inst['zone'].split("/")[-1]
- request = conn.instances().aggregatedList_next(
- previous_request=request, previous_response=response)
- raise Exception("Unable to find instance %s" % (instance))
+ request = replace_api_uri(options, conn.instances().aggregatedList_next(
+ previous_request=request, previous_response=response))
+ raise_fence_agent(options, "Unable to find instance %s" % (instance))
def get_metadata(metadata_key, params=None, timeout=None):
@@ -149,6 +328,7 @@
Raises:
urlerror.HTTPError: raises when the GET request fails.
"""
+ logging.debug("get_metadata");
timeout = timeout or 60
metadata_url = os.path.join(METADATA_SERVER, metadata_key)
params = urlparse.urlencode(params or {})
@@ -178,12 +358,50 @@
all_opt["stackdriver-logging"] = {
"getopt" : "",
"longopt" : "stackdriver-logging",
- "help" : "--stackdriver-logging Enable Logging to Stackdriver. Using stackdriver logging requires additional libraries (google-cloud-logging).",
- "shortdesc" : "Stackdriver-logging support. Requires additional libraries (google-cloud-logging).",
- "longdesc" : "If enabled IP failover logs will be posted to stackdriver logging. Using stackdriver logging requires additional libraries (google-cloud-logging).",
+ "help" : "--stackdriver-logging Enable Logging to Stackdriver",
+ "shortdesc" : "Stackdriver-logging support.",
+ "longdesc" : "If enabled IP failover logs will be posted to stackdriver logging.",
"required" : "0",
"order" : 4
}
+ all_opt["baremetalsolution"] = {
+ "getopt" : "",
+ "longopt" : "baremetalsolution",
+ "help" : "--baremetalsolution Enable on bare metal",
+ "shortdesc" : "If enabled this is a bare metal offering from google.",
+ "required" : "0",
+ "order" : 5
+ }
+ all_opt["apitimeout"] = {
+ "getopt" : ":",
+ "type" : "second",
+ "longopt" : "apitimeout",
+ "help" : "--apitimeout=[seconds] Timeout to use for API calls",
+ "shortdesc" : "Timeout in seconds to use for API calls, default is 60.",
+ "required" : "0",
+ "default" : 60,
+ "order" : 6
+ }
+ all_opt["retries"] = {
+ "getopt" : ":",
+ "type" : "integer",
+ "longopt" : "retries",
+ "help" : "--retries=[retries] Number of retries on failure for API calls",
+ "shortdesc" : "Number of retries on failure for API calls, default is 3.",
+ "required" : "0",
+ "default" : 3,
+ "order" : 7
+ }
+ all_opt["retrysleep"] = {
+ "getopt" : ":",
+ "type" : "second",
+ "longopt" : "retrysleep",
+ "help" : "--retrysleep=[seconds] Time to sleep between API retries",
+ "shortdesc" : "Time to sleep in seconds between API retries, default is 5.",
+ "required" : "0",
+ "default" : 5,
+ "order" : 8
+ }
all_opt["serviceaccount"] = {
"getopt" : ":",
"longopt" : "serviceaccount",
@@ -192,13 +410,21 @@
"required" : "0",
"order" : 9
}
+ all_opt["plugzonemap"] = {
+ "getopt" : ":",
+ "longopt" : "plugzonemap",
+ "help" : "--plugzonemap=[plugzonemap] Comma separated zone map when fencing multiple plugs",
+ "shortdesc" : "Comma separated zone map when fencing multiple plugs.",
+ "required" : "0",
+ "order" : 10
+ }
all_opt["proxyhost"] = {
"getopt" : ":",
"longopt" : "proxyhost",
"help" : "--proxyhost=[proxy_host] The proxy host to use, if one is needed to access the internet (Example: 10.122.0.33)",
"shortdesc" : "If a proxy is used for internet access, the proxy host should be specified.",
"required" : "0",
- "order" : 10
+ "order" : 11
}
all_opt["proxyport"] = {
"getopt" : ":",
@@ -207,7 +433,49 @@
"help" : "--proxyport=[proxy_port] The proxy port to use, if one is needed to access the internet (Example: 3127)",
"shortdesc" : "If a proxy is used for internet access, the proxy port should be specified.",
"required" : "0",
- "order" : 11
+ "order" : 12
+ }
+ all_opt["earlyexit"] = {
+ "getopt" : "",
+ "longopt" : "earlyexit",
+ "help" : "--earlyexit Return early if reset is already in progress",
+ "shortdesc" : "If an existing reset operation is detected, the fence agent will return before the operation completes with a 0 return code.",
+ "required" : "0",
+ "order" : 13
+ }
+ all_opt["warntimeout"] = {
+ "getopt" : ":",
+ "type" : "second",
+ "longopt" : "warntimeout",
+ "help" : "--warntimeout=[warn_timeout] Timeout seconds before logging a warning and returning a 0 status code",
+ "shortdesc" : "If the operation is not completed within the timeout, the cluster operations are allowed to continue.",
+ "required" : "0",
+ "order" : 14
+ }
+ all_opt["errortimeout"] = {
+ "getopt" : ":",
+ "type" : "second",
+ "longopt" : "errortimeout",
+ "help" : "--errortimeout=[error_timeout] Timeout seconds before failing and returning a non-zero status code",
+ "shortdesc" : "If the operation is not completed within the timeout, cluster is notified of the operation failure.",
+ "required" : "0",
+ "order" : 15
+ }
+ all_opt["runonwarn"] = {
+ "getopt" : ":",
+ "longopt" : "runonwarn",
+ "help" : "--runonwarn=[run_on_warn] If a timeout occurs and warning is generated, run the supplied command",
+ "shortdesc" : "If a timeout would occur while running the agent, then the supplied command is run.",
+ "required" : "0",
+ "order" : 16
+ }
+ all_opt["runonfail"] = {
+ "getopt" : ":",
+ "longopt" : "runonfail",
+ "help" : "--runonfail=[run_on_fail] If a failure occurs, run the supplied command",
+ "shortdesc" : "If a failure would occur while running the agent, then the supplied command is run.",
+ "required" : "0",
+ "order" : 17
}
@@ -215,7 +483,9 @@
conn = None
device_opt = ["port", "no_password", "zone", "project", "stackdriver-logging",
- "method", "serviceaccount", "proxyhost", "proxyport"]
+ "method", "baremetalsolution", "apitimeout", "retries", "retrysleep",
+ "serviceaccount", "plugzonemap", "proxyhost", "proxyport", "earlyexit",
+ "warntimeout", "errortimeout", "runonwarn", "runonfail"]
atexit.register(atexit_handler)
@@ -259,6 +529,11 @@
logging.error('Couldn\'t import google.cloud.logging, '
'disabling Stackdriver-logging support')
+ # if apitimeout is defined we set the socket timeout, if not we keep the
+ # socket default which is 60s
+ if options.get("--apitimeout"):
+ socket.setdefaulttimeout(options["--apitimeout"])
+
# Prepare cli
try:
serviceaccount = options.get("--serviceaccount")
@@ -291,20 +566,39 @@
conn = googleapiclient.discovery.build(
'compute', 'v1', credentials=credentials, cache_discovery=False)
except Exception as err:
- fail_usage("Failed: Create GCE compute v1 connection: {}".format(str(err)))
+ fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}".format(str(err)))
# Get project and zone
if not options.get("--project"):
try:
options["--project"] = get_metadata('project/project-id')
except Exception as err:
- fail_usage("Failed retrieving GCE project. Please provide --project option: {}".format(str(err)))
+ fail_fence_agent(options, "Failed retrieving GCE project. Please provide --project option: {}".format(str(err)))
- if not options.get("--zone"):
- try:
- options["--zone"] = get_zone(conn, options['--project'], options['--plug'])
- except Exception as err:
- fail_usage("Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err)))
+ if "--baremetalsolution" in options:
+ options["--zone"] = "none"
+
+ # Populates zone automatically if missing from the command
+ zones = [] if not "--zone" in options else options["--zone"].split(",")
+ options["--plugzonemap"] = {}
+ if "--plug" in options:
+ for i, instance in enumerate(options["--plug"].split(",")):
+ if len(zones) == 1:
+ # If only one zone is specified, use it across all plugs
+ options["--plugzonemap"][instance] = zones[0]
+ continue
+
+ if len(zones) - 1 >= i:
+ # If we have enough zones specified with the --zone flag use the zone at
+ # the same index as the plug
+ options["--plugzonemap"][instance] = zones[i]
+ continue
+
+ try:
+ # In this case we do not have a zone specified so we attempt to detect it
+ options["--plugzonemap"][instance] = get_zone(conn, options, instance)
+ except Exception as err:
+ fail_fence_agent(options, "Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err)))
# Operate the fencing device
result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list, power_cycle)

View File

@ -0,0 +1,34 @@
From 6ea2c6b5d1cc51e93fa7084d76d9272512461e58 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Mon, 16 May 2022 11:01:21 +0200
Subject: [PATCH] fence_apc/fence_ilo_moonshot: add missing "import logging"
---
agents/apc/fence_apc.py | 1 +
agents/ilo_moonshot/fence_ilo_moonshot.py | 1 +
2 files changed, 2 insertions(+)
diff --git a/agents/apc/fence_apc.py b/agents/apc/fence_apc.py
index 901aad25e..3ea0f37d6 100644
--- a/agents/apc/fence_apc.py
+++ b/agents/apc/fence_apc.py
@@ -15,6 +15,7 @@
#####
import sys, re, time
+import logging
import atexit
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
diff --git a/agents/ilo_moonshot/fence_ilo_moonshot.py b/agents/ilo_moonshot/fence_ilo_moonshot.py
index 6f5cca320..1923eeb1c 100644
--- a/agents/ilo_moonshot/fence_ilo_moonshot.py
+++ b/agents/ilo_moonshot/fence_ilo_moonshot.py
@@ -1,6 +1,7 @@
#!@PYTHON@ -tt
import sys
+import logging
import atexit
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *

View File

@ -0,0 +1,65 @@
From e3dff8570b70f0c19eca84cf02f0aadd68e16599 Mon Sep 17 00:00:00 2001
From: Thomas Renninger <trenn@suse.com>
Date: Fri, 25 Feb 2022 14:05:42 +0100
Subject: [PATCH] fence_lpar: fix missing import logging, use fail_usage
and slightly re-factor code to avoid duplicate code lines.
Should be cleanup only, no functional change.
---
agents/lpar/fence_lpar.py | 39 ++++++++++++++++++---------------------
1 file changed, 18 insertions(+), 21 deletions(-)
diff --git a/agents/lpar/fence_lpar.py b/agents/lpar/fence_lpar.py
index ad18c6191..2046b0e4e 100644
--- a/agents/lpar/fence_lpar.py
+++ b/agents/lpar/fence_lpar.py
@@ -28,31 +28,28 @@ def _normalize_status(status):
def get_power_status(conn, options):
if options["--hmc-version"] == "3":
- conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n")
-
- # First line (command) may cause parsing issues if long
- conn.readline()
- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"]))
-
- try:
- status = re.compile("^" + options["--plug"] + ",(.*?),.*$",
- re.IGNORECASE | re.MULTILINE).search(conn.before).group(1)
- except AttributeError as e:
- logging.error("Failed: {}".format(str(e)))
- fail(EC_STATUS_HMC)
+ command = "lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n"
elif options["--hmc-version"] in ["4", "IVM"]:
- conn.send("lssyscfg -r lpar -m "+ options["--managed"] +
- " --filter 'lpar_names=" + options["--plug"] + "'\n")
+ command = "lssyscfg -r lpar -m "+ options["--managed"] + \
+ " --filter 'lpar_names=" + options["--plug"] + "'\n"
+ else:
+ # Bad HMC Version cannot be reached
+ fail(EC_STATUS_HMC)
- # First line (command) may cause parsing issues if long
- conn.readline()
- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"]))
+ conn.send(command)
+ # First line (command) may cause parsing issues if long
+ conn.readline()
+ conn.log_expect(options["--command-prompt"], int(options["--power-timeout"]))
- try:
+ try:
+ if options["--hmc-version"] == "3":
+ status = re.compile("^" + options["--plug"] + ",(.*?),.*$",
+ re.IGNORECASE | re.MULTILINE).search(conn.before).group(1)
+ elif options["--hmc-version"] in ["4", "IVM"]:
status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1)
- except AttributeError as e:
- logging.error("Failed: {}".format(str(e)))
- fail(EC_STATUS_HMC)
+ except AttributeError as e:
+ fail_usage("Command on HMC failed: {}\n{}".format(command, str(e)), False)
+ fail(EC_STATUS_HMC)
return _normalize_status(status)

View File

@ -0,0 +1,65 @@
From d0254345472c9415af1e06e9e8df2fe0ce464db0 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Mon, 2 May 2022 11:14:42 +0200
Subject: [PATCH] fence_ibm_vpc: remove unused instance parameter and make
limit optional
---
agents/ibm_vpc/fence_ibm_vpc.py | 11 +----------
tests/data/metadata/fence_ibm_vpc.xml | 7 +------
2 files changed, 2 insertions(+), 16 deletions(-)
diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py
index 9f84f7b2d..9e38e8301 100755
--- a/agents/ibm_vpc/fence_ibm_vpc.py
+++ b/agents/ibm_vpc/fence_ibm_vpc.py
@@ -161,14 +161,6 @@ def define_new_opts():
"shortdesc" : "API Key",
"order" : 0
}
- all_opt["instance"] = {
- "getopt" : ":",
- "longopt" : "instance",
- "help" : "--instance=[instance] Cloud Instance",
- "required" : "1",
- "shortdesc" : "Cloud Instance",
- "order" : 0
- }
all_opt["region"] = {
"getopt" : ":",
"longopt" : "region",
@@ -181,7 +173,7 @@ def define_new_opts():
"getopt" : ":",
"longopt" : "limit",
"help" : "--limit=[number] Limit number of nodes returned by API",
- "required" : "1",
+ "required" : "0",
"default": 50,
"shortdesc" : "Number of nodes returned by API",
"order" : 0
@@ -191,7 +183,6 @@ def define_new_opts():
def main():
device_opt = [
"apikey",
- "instance",
"region",
"limit",
"port",
diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml
index 926efcaa0..ee7151673 100644
--- a/tests/data/metadata/fence_ibm_vpc.xml
+++ b/tests/data/metadata/fence_ibm_vpc.xml
@@ -8,12 +8,7 @@
<content type="string" />
<shortdesc lang="en">API Key</shortdesc>
</parameter>
- <parameter name="instance" unique="0" required="1">
- <getopt mixed="--instance=[instance]" />
- <content type="string" />
- <shortdesc lang="en">Cloud Instance</shortdesc>
- </parameter>
- <parameter name="limit" unique="0" required="1">
+ <parameter name="limit" unique="0" required="0">
<getopt mixed="--limit=[number]" />
<content type="string" default="50" />
<shortdesc lang="en">Number of nodes returned by API</shortdesc>

View File

@ -87,7 +87,7 @@
Name: fence-agents
Summary: Set of unified programs capable of host isolation ("fencing")
Version: 4.2.1
Release: 89%{?alphatag:.%{alphatag}}%{?dist}
Release: 95%{?alphatag:.%{alphatag}}%{?dist}
License: GPLv2+ and LGPLv2+
Group: System Environment/Base
URL: https://github.com/ClusterLabs/fence-agents
@ -136,9 +136,40 @@ Source33: %{setuptools}-%{setuptools_version}.tar.gz
## required for installation
Source34: setuptools_scm-6.3.2.tar.gz
Source35: packaging-21.2-py3-none-any.whl
Source36: pyparsing-3.0.1.tar.gz
Source37: tomli-1.0.1.tar.gz
Source36: tomli-1.0.1.tar.gz
### END
# openstack
Source37: requirements-openstack.txt
Source38: PyYAML-5.1.tar.gz
Source39: charset_normalizer-2.0.4-py3-none-any.whl
Source40: debtcollector-2.2.0-py3-none-any.whl
Source41: idna-3.2-py3-none-any.whl
Source42: iso8601-0.1.16-py2.py3-none-any.whl
Source43: keystoneauth1-4.3.1-py3-none-any.whl
Source44: msgpack-1.0.2.tar.gz
Source45: netaddr-0.8.0-py2.py3-none-any.whl
Source46: netifaces-0.11.0.tar.gz
Source47: oslo.config-8.7.1-py3-none-any.whl
Source48: oslo.i18n-5.0.1-py3-none-any.whl
Source49: oslo.serialization-4.2.0-py3-none-any.whl
Source50: oslo.utils-4.10.0-py3-none-any.whl
Source51: os_service_types-1.7.0-py2.py3-none-any.whl
Source52: pbr-5.6.0-py2.py3-none-any.whl
Source53: packaging-21.0-py3-none-any.whl
Source54: prettytable-2.2.0-py3-none-any.whl
Source55: python_keystoneclient-4.2.0-py3-none-any.whl
Source56: python_novaclient-17.5.0-py3-none-any.whl
Source57: requests-2.26.0-py2.py3-none-any.whl
Source58: rfc3986-1.5.0-py2.py3-none-any.whl
Source59: stevedore-3.4.0-py3-none-any.whl
Source60: wcwidth-0.2.5-py2.py3-none-any.whl
Source61: wheel-0.37.0-py2.py3-none-any.whl
Source62: wrapt-1.12.1.tar.gz
Source63: importlib_metadata-4.8.3.tar.gz
Source64: pytz-2022.1.tar.gz
Source65: zipp-3.6.0.tar.gz
Source66: typing_extensions-3.10.0.2.tar.gz
Source67: importlib_resources-5.4.0.tar.gz
Patch0: fence_impilan-fence_ilo_ssh-add-ilo5-support.patch
Patch1: fence_mpath-watchdog-support.patch
@ -249,14 +280,22 @@ Patch105: bz1977588-4-fence_kubevirt-power-timeout-40s.patch
Patch106: bz1963163-fence_zvmip-add-ssl-tls-support.patch
Patch107: bz1977588-5-fence_kubevirt-get-namespace-from-context.patch
Patch108: bz2048857-fence_aws-botocore-bundled.patch
Patch109: bz1886074-1-fencing-source_env.patch
Patch110: bz1886074-2-fence_openstack.patch
Patch111: bz1886074-3-fence_openstack-bundled.patch
Patch112: bz2072421-all-agents-unify-ssl-parameters.patch
Patch113: bz2078244-fence_gce-update.patch
Patch114: bz2080994-fence_ibm_vpc-fix-parameters.patch
Patch115: bz2080729-1-fence_apc-fence_ilo_moonshot-import-logging.patch
Patch116: bz2080729-2-fence_lpar-fix-import-fail_usage.patch
%if 0%{?fedora} || 0%{?rhel} > 7
%global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hds_cb hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti
%ifarch x86_64
%global testagents virsh heuristics_ping aliyun aws azure_arm gce
%global testagents virsh heuristics_ping aliyun aws azure_arm gce openstack
%endif
%ifarch ppc64le
%global testagents virsh heuristics_ping
%global testagents virsh heuristics_ping openstack
%endif
%ifarch s390x
%global testagents virsh zvm heuristics_ping
@ -435,6 +474,14 @@ BuildRequires: python3-google-api-client python3-pip python3-wheel python3-jinja
%patch106 -p1
%patch107 -p1
%patch108 -p1
%patch109 -p1
%patch110 -p1
%patch111 -p1 -F1
%patch112 -p1
%patch113 -p1
%patch114 -p1
%patch115 -p1
%patch116 -p1
# prevent compilation of something that won't get used anyway
sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac
@ -539,6 +586,17 @@ popd
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt --no-index --find-links %{_sourcedir} openshift
rm -rf %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt/rsa*
# openstack
%ifarch x86_64 ppc64le
%{__python3} -m pip install --user --upgrade --no-index --find-links %{_sourcedir} setuptools
LIBS="%{_sourcedir}/requirements-openstack.txt"
for x in $LIBS; do
%{__python3} -m pip install --target %{buildroot}/%{_usr}/lib/%{name}/%{bundled_lib_dir}/$(echo $x | sed -E "s/.*requirements-(.*).txt/\1/") --no-index --find-links %{_sourcedir} -r $x
done
%endif
## tree fix up
# fix libfence permissions
chmod 0755 %{buildroot}%{_datadir}/fence/*.py
@ -1217,6 +1275,53 @@ Device Mapper Multipath.
%{_datadir}/cluster/fence_mpath_check*
%{_mandir}/man8/fence_mpath.8*
%ifarch x86_64 ppc64le
%package openstack
License: GPLv2+ and LGPLv2+ and ASL 2.0 and MIT and Python-2.0
Summary: Fence agent for OpenStack's Nova service
%if 0%{?fedora} || 0%{?centos} > 7 || 0%{?rhel} > 7 || 0%{?suse_version}
Requires: python3-requests
%else
Requires: python-requests
%endif
Requires: fence-agents-common = %{version}-%{release}
Provides: bundled(python-PyYAML) = 5.1
Provides: bundled(python-charset-normalizer) = 2.0.4
Provides: bundled(python-debtcollector) = 2.2.0
Provides: bundled(python-idna) = 3.2
Provides: bundled(python-iso8601) = 0.1.16
Provides: bundled(python-keystoneauth1) = 4.3.1
Provides: bundled(python-msgpack) = 1.0.2
Provides: bundled(python-netaddr) = 0.8.0
Provides: bundled(python-netifaces) = 0.11.0
Provides: bundled(python-oslo-config) = 8.7.1
Provides: bundled(python-oslo-i18n) = 5.0.1
Provides: bundled(python-oslo-serialization) = 4.2.0
Provides: bundled(python-oslo-utils) = 4.10.0
Provides: bundled(python-os-service-types) = 1.7.0
Provides: bundled(python-packaging) = 21.0
Provides: bundled(python-pbr) = 5.6.0
Provides: bundled(python-prettytable) = 2.2.0
Provides: bundled(python-keystoneclient) = 4.2.0
Provides: bundled(python-novaclient) = 17.5.0
Provides: bundled(python-requests) = 2.26.0
Provides: bundled(python-rfc3986) = 1.5.0
Provides: bundled(python-stevedore) = 3.4.0
Provides: bundled(python-wcwidth) = 0.2.5
Provides: bundled(python-wrapt) = 1.12.1
Provides: bundled(python-importlib-metadata) = 4.8.3
Provides: bundled(python-pytz) = 2022.1
Provides: bundled(python-zipp) = 3.6.0
Provides: bundled(python-typing-extensions) = 3.10.0.2
Provides: bundled(python-importlib-resources) = 5.4.0
%description openstack
Fence agent for OpenStack's Nova service.
%files openstack
%{_sbindir}/fence_openstack
%{_mandir}/man8/fence_openstack.8*
%{_usr}/lib/%{name}/%{bundled_lib_dir}/openstack
%endif
%package redfish
License: GPLv2+ and LGPLv2+
Group: System Environment/Base
@ -1401,10 +1506,30 @@ Fence agent for IBM z/VM over IP.
%endif
%changelog
* Fri Mar 4 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-89
* Tue May 17 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-95
- fence_apc/fence_ilo_moonshot/fence_lpar: add missing "import logging"
Resolves: rhbz#2080729
* Thu May 5 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-94
- fence_ibm_vpc: remove unused instance parameter and make limit
optional
Resolves: rhbz#2080994
* Thu Apr 28 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-93
- all agents: unify ssl parameters to avoid having to use --ssl when
using --ssl-secure/--ssl-insecure for some agents
Resolves: rhbz#2072421
- fence_gce: update fence agent
Resolves: rhbz#2078244
* Wed Apr 27 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-92
- fence_ibm_vpc: new fence agent
Resolves: rhbz#1728203
* Mon Apr 25 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-91
- fence_openstack: new fence agent
Resolves: rhbz#1886074
* Fri Feb 11 2022 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-88
- fence_aws: upgrade botocore to fix IMDSv2 support
Resolves: rhbz#2048857

View File

@ -0,0 +1,3 @@
python-novaclient
python-keystoneclient
PyYAML==5.1

31
sources
View File

@ -1,5 +1,6 @@
SHA512 (Jinja2-3.0.2.tar.gz) = cea7d24656bbc9117785886caee4eb28bc417bd7529152493ac2dd5041d798a1e30c0c5c619327817a708b1af8d08f13ae9125bbc7cb7d440045cb54d7bfbd9e
SHA512 (MarkupSafe-2.0.1.tar.gz) = 77249bda784111ece15d59eb3de1cbb37a58fb9f22902fe6b73fea9eb0f23857ccbe53dc55463278e3b91f78dc35e2b027fd823ca50d88d8985d5a98ce2327f1
SHA512 (PyYAML-5.1.tar.gz) = 8f27f92bdfa310a99dd6d83947332cc033fa18f0011998bb585ad5c4340a2da20d8c20bfdb53beaae15651198d1240c986818379b0a05b230f74d1f30f53e7fd
SHA512 (PyYAML-6.0.tar.gz) = b402993073282e7f4202823b051d364b91929362edd5b3aebe93b56833956ec9279c1ba82b97f8bc8a2b82d20e1060e4ec9fc90400a6ed902adce3e4f83a6e0e
SHA512 (aliyun-python-sdk-core-2.13.1.tar.gz) = c9eaccf3ed06ff2f5edc89d605511223650816ae3b192e6149f1b113d1d04c05c5220bdd588dc83024486748354436fec65fa59f5858befaa7cf9524dbb6da19
SHA512 (aliyun-python-sdk-ecs-4.9.3.tar.gz) = 83b35b7e774fa8892106f771731cc11ca823fe3d6f3e2f5bc5f075e475623573b9123e5ecb2a750d13ebcda9bc76242485636d2d3284c3eec89afcb4ec3070a4
@ -9,30 +10,58 @@ SHA512 (cachetools-4.2.4.tar.gz) = 29a6bb3a064e5603cd3e3882d8e5a6a6ef95ba3029716
SHA512 (certifi-2021.10.8.tar.gz) = 06dc41a471f16f6c52751854e82fb42011c9388651cff55761298b86ba437d431e6325ab039ef330f2b2c5f69f5ba43dc468e7ca3df205a8bb31468f43711fbe
SHA512 (chardet-4.0.0.tar.gz) = ebd7f420e1094445270db993f6373ffe7370419e002b0bb13299dc6c9b0f7c4e77b0f44f871fba6371e6869e7c86728514367db377e3137487a3acf50cb81e96
SHA512 (charset-normalizer-2.0.7.tar.gz) = 7096fa23fade52c8eee2577b87aa574deffe6f66e8986f71172f3b9212bd6c6fb17901cceab90144c9d07de8bc6f5e320497daa3d3f749f436788232d4cba088
SHA512 (charset_normalizer-2.0.4-py3-none-any.whl) = 75c725012838c70e0a8f6ce4bd446e1c03a20648a580131d26528676abcc4f3cfd9b7f0a49489c1694e9359b8f5a1b34926b2f4209e464743f60f3099416261b
SHA512 (debtcollector-2.2.0-py3-none-any.whl) = bc1b5a9e5c906149327ff7bfb22951d32e51731dbe3da26dac2084d36646b2ccd2524ffe25648937837aa3fe41335a896e021b95e0bd775d6132c5ec1803d525
SHA512 (fence-agents-4.2.1.tar.gz) = f9e2f8315120efdad4772b466caab38fb1d1fdb101baac28fb560fba279594ab2220c6470e8cda855eb6458e01841fc1c5664483ce3104986dc54b4e875d299f
SHA512 (google-auth-2.3.0.tar.gz) = cf0040d238880ea4bbad64f0a47311f2ed3922a7301a0d5287319b39ea8e76dca66dc78fd860cc12386b078bd2147a1cba01de97381420ef94cc44fca0c90ad1
SHA512 (httplib2-0.19.1-py3-none-any.whl) = 83b9b36fe27e6ac5b931f0889bd63f6f9e952cfb87baa09ccb0f8eeed9cae1932b002109832d21f54c5c0a204bd4e3d218e86a8f8104c8df9e29b76d9e0cf3d1
SHA512 (idna-3.2-py3-none-any.whl) = 10dc4905aec2c6c9cd0abfb4335cc13ef13809d817d0a58da3326cb5e3a8a91321a6225715887aa195f464776a4d5e61fd45b972dfaf67e7c8f874787191cbfb
SHA512 (idna-3.3.tar.gz) = 70b7cc8718e7d7899c75cfe476f044eae5a2fa03801fc9c12e3a092627ca943ffc4a578f9b8a55e181a11564835e125cfaaa577c02a6461dbb97366e620e53ad
SHA512 (importlib_metadata-4.8.3.tar.gz) = a2ef41a9024059fd9bb5f4571a59fbe55e16c9a0e14acc5c442c8b2579e53baf39b71e0bcefb80955786279620194f3b559f1207b9ffc0363f120feb798227cc
SHA512 (importlib_resources-5.4.0.tar.gz) = 9262c185bdf79fad0a11caabc5c5df836702a74e140a9f60ace857e3f6a60fdb091639b2fb7129b17bad5a46968f43dda7d176fe39af495370dd31f04891fb1b
SHA512 (iso8601-0.1.16-py2.py3-none-any.whl) = 23c9da7db50d70cf5814ff9cdf5bd674928553d24b718218b1487e8a545ebd0daab0f7f76693057a23b9811930252d61b5f6dc852afa722eb3f43b111ff46f81
SHA512 (jmespath-0.10.0.tar.gz) = 9e229b5809d2dd74eb7dbf518953f848175743fb0ee91ffc901777be2f4809cc0c4f4ba40890746533e344f64e900ec189d6a8c847c864fa47fbf67e5106a7bc
SHA512 (keystoneauth1-4.3.1-py3-none-any.whl) = ffda2c4b9cdf2be2776f55df716bd2148ae04716e6b38edc86ce06a2ceb26519dcfafcef51b1a0f3ea48492e26456166680fafb33e62c5d1db015063556c41c2
SHA512 (kubernetes-12.0.1.tar.gz) = ff4739dc185dbf46050e53382b9260a0a69c55b43820ccb1df498718c78d1bf42842f4ab1b6d0c3520c5edab45d9c9c9527aea9ccb0b6347f34e18202f9155a2
SHA512 (msgpack-1.0.2.tar.gz) = afe658c2cd5b443aae8fc3305eb3fd56811130062f95e283e3538cba27949d534de60171a927426fedb23b48a938de3f15d77057beda6c73e5204ef1130cd046
SHA512 (netaddr-0.8.0-py2.py3-none-any.whl) = 19ffe6d5bf9a1c41e1ef86c0a0acbc2764c311ab63d262769eea8791d8969160c12d20adb45f6d5d2a54f5e0a2e183afe9628e852cfbc152553f467b9ddde8e4
SHA512 (netifaces-0.11.0.tar.gz) = a53110efb78c89c4d72d002104866253a4c085dd27ff9f41d4cfe3811cc5619e7585ceda4e91e83cdd0645c40c745c61d205708ee9a34427b35f437a48f148e5
SHA512 (oauthlib-3.1.1.tar.gz) = 1c18f265a6017a6f2bf77507f5de095b439be36f309316993b547ded0f306461b058dcb85dc245bf26f65a9c77be44ca38f9053f3300a9b5cb92332b61a7f307
SHA512 (openshift-0.12.1.tar.gz) = 35a0ecfbc12d657f5f79d4c752a7c023a2a5e3fc5e7b377d9f11ce4a7d5f666ca5c6296f31adb44b7680d051af42d3638ced1092fb7e9146d2cc998f2c3a7b80
SHA512 (os_service_types-1.7.0-py2.py3-none-any.whl) = 8bb7bbd58336587b067e84e29757e1618684f6735f6a7fa26cde94cfdeab6b7f25b49ee895ac3cb2707a9fbcff2f91277edd056e428a4cd1def6599dce9660b7
SHA512 (oslo.config-8.7.1-py3-none-any.whl) = ef61cc587f02421d10e19dbe5f85e3b4651015c0cc26fe79fccc48927c9340686f552fc9842863118ea43a6bcd5db882c7f994cf5b17c43f36ebfa5882a0c979
SHA512 (oslo.i18n-5.0.1-py3-none-any.whl) = 6f58e88c9e3691016dec79588b4e68eb3ad651b74ff900eac26104cec4f6a87495e60ad57b6c8e9787caf1946cc9ed0d39c7e462ed5cf18639b1bd67b586a849
SHA512 (oslo.serialization-4.2.0-py3-none-any.whl) = 0a499cac0a8f9fea952ff60c6d528a6e446b336d34556e2e4f3cb682c99359b3cf45deb65169a38bfe2477610259b2917bc4baf735998365d47964c33cdd6884
SHA512 (oslo.utils-4.10.0-py3-none-any.whl) = 17f0b1c8af82261c3d738ddd14c866f8020274ca82925e6d268db0bf93b4faf388cbf6fe6269cce25e755ba17d202a3df0933e006b357635f26084d03822b70f
SHA512 (packaging-21.0-py3-none-any.whl) = 9f369d751435f37b7bbadaf3c8bcb80503b4274afd84d7c0c3c5199623e94a375cca420a4312babedc262f49995f8e3f1ad3d0d93578e74203db8561467ad8ad
SHA512 (packaging-21.2-py3-none-any.whl) = 620a077783da21db677eda413c7cfcd9a9112afd573deda853615fad5b7f79b0ddfae4a7ee5d69834ba45e2299ebf343c6398b8eb60bb04569883520ada4a381
SHA512 (pbr-5.6.0-py2.py3-none-any.whl) = a4d32f6660ac76150a5d9db51d0c1be64a9df32d7b97c106e2e4de2d1dcc0cb83be991847b491bcd5b65c5a0e41f47406fa1e8a185bd1eb4fd09fe80867b005a
SHA512 (prettytable-2.2.0-py3-none-any.whl) = 40e0bd5252dde9685de53cff11afc1f1b0477f7caf449aa12d6ee9589099b69d60f6c07fd71f00406702a9bc9c1373c3108c37c377b9ba53c28dc95cc6f39922
SHA512 (pyasn1-0.4.8.tar.gz) = e64e70b325c8067f87ace7c0673149e82fe564aa4b0fa146d29b43cb588ecd6e81b1b82803b8cfa7a17d3d0489b6d88b4af5afb3aa0052bf92e8a1769fe8f7b0
SHA512 (pyasn1-modules-0.2.8.tar.gz) = fdfcaa065deffdd732deaa1fa30dec2fc4a90ffe15bd12de40636ce0212f447611096d2f4e652ed786b5c47544439e6a93721fabe121f3320f13965692a1ca5b
SHA512 (pycryptodome-3.6.4.tar.gz) = b565acf2d4dad80842a677dac2e69719dedb870d93d35948f3ef04da120c89fdf80f5b08864c182e2537ff60bbce8487cec6bfe8bb9acc1833194a667932a5c6
SHA512 (pyparsing-2.4.7-py2.py3-none-any.whl) = acb6b4ff90254d73804621d302926deb69bc99ffde16d7aa16cba7d0af7a53c25b7197d422309d9e82a766704fd7ea4c8b078a48d2e7d8658a8b237266fe24f5
SHA512 (pyparsing-3.0.1.tar.gz) = 4a91daaf962a4d689ac26a712a83e8c4fbebc33270b6e46f0ae34fe247f6858d6334fbb59bdbbafa448703d24763fe1c86ce164e095de5003c7f1390960ba520
SHA512 (python-dateutil-2.8.2.tar.gz) = 6538858e4a3e2d1de1bf25b6d8b25e3a8d20bf60fb85e32d07ac491c90ce193e268bb5641371b8a79fb0f033a184bac9896b3bc643c1aca9ee9c6478286ac20c
SHA512 (python-string-utils-1.0.0.tar.gz) = 23ee48053848edd74915a985ee9edec48bbba468e228745f7d27b6a855c67f6b7ddf1cf71049458bf0b1c6c4d4f905ebacfac960597cbadbbe2daa1fe9472280
SHA512 (python_keystoneclient-4.2.0-py3-none-any.whl) = 7552edcd23c36f40decb37cf6160896dd2b3a9543338b35134da2830d3927004992f3b9d3e42bbddc2068d833f04acce53cc2c052571affdf735dc90aa314018
SHA512 (python_novaclient-17.5.0-py3-none-any.whl) = d369ba62d4b98958b3dc95ed3d702c8b7d8d70172108f0f1094c8d97561c90b9d8272974be688640d343e0a320254429e1f618d3ce0aaa9b6d9009bdf237daf9
SHA512 (pytz-2022.1.tar.gz) = 7737aa28626743026b7de932b27bf44d7b209f495d9dd7f90c6448ec271e007f23004fcd04982e2ca6d0a4075206e1dae7b8836319c299b5559c514366d518a6
SHA512 (requests-2.26.0-py2.py3-none-any.whl) = ab0b245535ed57b6b05699a5e032d6114be081e682ffd4cd0414f861d1b216db5bce640c3996bfce9cbdbca6490c999d5925cfe713c6026adb0d4b035084dc76
SHA512 (requests-2.26.0.tar.gz) = c3397d77f0d2f1afb05661c4b98adad6c1ddaf360906254150b33ab0d9479fd306905bd6d61b8cf8becd9a40bdcf9b03542e8267c644ef19f03f44bfca0bc461
SHA512 (requests-oauthlib-1.3.0.tar.gz) = 7ef206aaf50cd3289f04175ff3a08022d00aeaaba57a6580912f37c30880322ec71ae9f6af7edc99efe6043fc6c19ba2c9f6fc0f29c126be28cde22bb885510d
SHA512 (rfc3986-1.5.0-py2.py3-none-any.whl) = d9f4db81a24c2988c92531bf1bda126f6aa2fdc6f81eadc666adb1e555f5f87930e40f6f93a5c2a2ddc126be30ca30443c3c86d6e89b31150fdb77668a415f8a
SHA512 (rsa-4.7.2.tar.gz) = 63f561774dbaa10511167cba31e0f852e32b3250f2803edaa2729dc2b28baa2c42cb79dfbd49e38eb42ce82f665ed4c3d9dcc810c37380401e2c62202b1c7948
SHA512 (ruamel.yaml-0.17.16.tar.gz) = 61780127c23a2fa20c6ce29b27790b72538b5ea0370906d38f4cfe760c30a3756022a385d3f241dd88199358ff152d624dc372fbe7e1fd6d2959f873daf1263d
SHA512 (ruamel.yaml.clib-0.2.6.tar.gz) = 12307a3c3bae09cf65d9672894c9a869a7ed5483ca3afb9ee39d8bcbf1948b012a0dbf570e315cc8b9a8b55184de9e10324953ec4819d214379e01522ee13b20
SHA512 (setuptools-58.3.0.tar.gz) = 5a38231c2ce361ad45befbd0de34dd7dde9d15f25e7fe2b8cf595d43eeee86fb3c2400e5b2676b547a025217aad85e63b1db6d9778baa412eefb25b22170c587
SHA512 (setuptools_scm-6.3.2.tar.gz) = 9a16552803ef92367ad71007cf322737b5baa58b924083f04c860875bf6cb2e2bb4f43a7f89778b040c2eb55c5d32de479a918056519339820c6d0f1a6a386f0
SHA512 (six-1.16.0.tar.gz) = 076fe31c8f03b0b52ff44346759c7dc8317da0972403b84dfe5898179f55acdba6c78827e0f8a53ff20afe8b76432c6fe0d655a75c24259d9acbaa4d9e8015c0
SHA512 (stevedore-3.4.0-py3-none-any.whl) = c8450741760acd757ccbe697939e1aa36abe6e54f2cd2c2d4fdb83700c0e84268a07bf5ed7fdde67869e02ea9a9fbd83627403f0dfd950dcafd48402d58d46f4
SHA512 (tomli-1.0.1.tar.gz) = 2731ff827bda17471bf75a44b445062bd4c43adfc9f0fdab4f8953e559f60708bc3e3500b424bf914c5e472fc9afbab72316c5a3b47c3a7654b2eb5343e62d21
SHA512 (typing_extensions-3.10.0.2.tar.gz) = 171ada56758fb04328a2cafefe80b70fbf166f2d799406883df642927964925ca73f04d0646661cd4fd016dfb5e1856b84f362e3b404c27b368ca06a09cf87db
SHA512 (urllib3-1.26.7.tar.gz) = 6f5a5e6dd5ff99950fcc051495e0a698153b57e20b6c83d869b54c7fece9616909bcf2fe99efc40815f8722996ad93e430bf765ce5c629b912690c286014b86f
SHA512 (wcwidth-0.2.5-py2.py3-none-any.whl) = f83eb241322d171ff4014612184b155c43c71e54794a2a227c4adb43c697a669944bb6bb49e59aa192492822e3e3a44a944f57d428a9e25707f7deeb4ccf5cd9
SHA512 (websocket-client-1.2.1.tar.gz) = fdbeb7ac2add27478a17b388ac62e9378094a368f29749d8b63c274ee41836506369dddd083956f42f1f2d74948392b3ddd59b801c98f9e028c126bdb54c636b
SHA512 (wheel-0.37.0-py2.py3-none-any.whl) = 340ab4ad7337db653b173f4283fdacc3d5e8b3a1b133e3017d53c41f79f0cf7b327d8f530d07a0344435c4ffcdb10d36a4881586e6637658b70f88835ca8afa7
SHA512 (wrapt-1.12.1.tar.gz) = efc38c0adc4fcaa34499a996ea4a337a000a812254ec7ddaca38f0132020dd31afa9fe6bab3e6010df312282ab0ea7bd571b54215d8d27e0b97b270c08cf5a62
SHA512 (zipp-3.6.0.tar.gz) = 8e21b0a757ed9ebddeca43d3e4b64549dc4e59f781704626b0b2b0a117752b84798a0484cceedd4684ee0459643df4d0f479e64a143c8d9337d2b66951a7b227