From c8e1da85fc97a91c0bb94d18ad156b02fde32bee Mon Sep 17 00:00:00 2001 From: Oyvind Albrigtsen Date: Wed, 19 Jan 2022 09:18:01 +0100 Subject: [PATCH] - fence_openstack: add support for reading config from clouds.yaml and openrc Resolves: rhbz#2041933, rhbz#2041935 --- ...1935-1-fence_openstack-clouds-openrc.patch | 313 ++++++++++++++++++ ...1935-2-fence_openstack-clouds-openrc.patch | 171 ++++++++++ fence-agents.spec | 11 +- 3 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 bz2041933-bz2041935-1-fence_openstack-clouds-openrc.patch create mode 100644 bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch diff --git a/bz2041933-bz2041935-1-fence_openstack-clouds-openrc.patch b/bz2041933-bz2041935-1-fence_openstack-clouds-openrc.patch new file mode 100644 index 0000000..06363d8 --- /dev/null +++ b/bz2041933-bz2041935-1-fence_openstack-clouds-openrc.patch @@ -0,0 +1,313 @@ +From 2f741df2ce73da85bbd205d861b527aa141d9776 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Fri, 14 Jan 2022 14:47:41 +0100 +Subject: [PATCH 1/2] fencing: add source_env() + +--- + lib/fencing.py.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/lib/fencing.py.py b/lib/fencing.py.py +index d85b23568..55e38c407 100644 +--- a/lib/fencing.py.py ++++ b/lib/fencing.py.py +@@ -1143,6 +1143,14 @@ def fence_logout(conn, logout_string, sleep=0): + 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): + +From fe2183a97e0a5734702e9cba8da21f01afd8f577 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Fri, 14 Jan 2022 14:54:10 +0100 +Subject: [PATCH 2/2] fence_openstack: add support for reading config from + clouds.yaml and openrc + +--- + agents/openstack/fence_openstack.py | 116 ++++++++++++++++++++---- + tests/data/metadata/fence_openstack.xml | 32 +++++-- + 2 files changed, 126 insertions(+), 22 deletions(-) + mode change 100755 => 100644 agents/openstack/fence_openstack.py + +diff --git a/agents/openstack/fence_openstack.py b/agents/openstack/fence_openstack.py +old mode 100755 +new mode 100644 +index 36b353b52..d3a4be3aa +--- a/agents/openstack/fence_openstack.py ++++ b/agents/openstack/fence_openstack.py +@@ -8,7 +8,7 @@ + + sys.path.append("@FENCEAGENTSLIBDIR@") + from fencing import * +-from fencing import fail_usage, run_delay ++from fencing import fail_usage, run_delay, source_env + + try: + from novaclient import client +@@ -26,6 +26,23 @@ def translate_status(instance_status): + return "off" + return "unknown" + ++def get_cloud(options): ++ import os, yaml ++ ++ clouds_yaml = os.path.expanduser("~/.config/openstack/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"]) +@@ -153,7 +170,7 @@ def define_new_opts(): + "getopt": ":", + "longopt": "auth-url", + "help": "--auth-url=[authurl] Keystone Auth URL", +- "required": "1", ++ "required": "0", + "shortdesc": "Keystone Auth URL", + "order": 2, + } +@@ -161,7 +178,7 @@ def define_new_opts(): + "getopt": ":", + "longopt": "project-name", + "help": "--project-name=[project] Tenant Or Project Name", +- "required": "1", ++ "required": "0", + "shortdesc": "Keystone Project", + "default": "admin", + "order": 3, +@@ -184,13 +201,38 @@ def define_new_opts(): + "default": "Default", + "order": 5, + } ++ all_opt["clouds-yaml"] = { ++ "getopt": ":", ++ "longopt": "clouds-yaml", ++ "help": "--clouds-yaml=[clouds-yaml] Path to the clouds.yaml config file", ++ "required": "0", ++ "shortdesc": "clouds.yaml config file", ++ "default": "~/.config/openstack/clouds.yaml", ++ "order": 6, ++ } ++ all_opt["cloud"] = { ++ "getopt": ":", ++ "longopt": "cloud", ++ "help": "--cloud=[cloud] Openstack cloud (from clouds.yaml).", ++ "required": "0", ++ "shortdesc": "Cloud from clouds.yaml", ++ "order": 7, ++ } ++ all_opt["openrc"] = { ++ "getopt": ":", ++ "longopt": "openrc", ++ "help": "--openrc=[openrc] Path to the openrc config file", ++ "required": "0", ++ "shortdesc": "openrc config file", ++ "order": 8, ++ } + all_opt["uuid"] = { + "getopt": ":", + "longopt": "uuid", + "help": "--uuid=[uuid] Replaced by -n, --plug", + "required": "0", + "shortdesc": "Replaced by port/-n/--plug", +- "order": 6, ++ "order": 9, + } + all_opt["cacert"] = { + "getopt": ":", +@@ -199,7 +241,7 @@ def define_new_opts(): + "required": "0", + "shortdesc": "SSL X.509 certificates file", + "default": "", +- "order": 7, ++ "order": 10, + } + all_opt["apitimeout"] = { + "getopt": ":", +@@ -209,7 +251,7 @@ def define_new_opts(): + "shortdesc": "Timeout in seconds to use for API calls, default is 60.", + "required": "0", + "default": 60, +- "order": 8, ++ "order": 11, + } + + +@@ -218,11 +260,16 @@ def main(): + + device_opt = [ + "login", ++ "no_login", + "passwd", ++ "no_password", + "auth-url", + "project-name", + "user-domain-name", + "project-domain-name", ++ "clouds-yaml", ++ "cloud", ++ "openrc", + "port", + "no_port", + "uuid", +@@ -265,19 +312,56 @@ def main(): + + run_delay(options) + +- 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"] ++ if options.get("--clouds-yaml"): ++ if not os.path.exists(os.path.expanduser(options["--clouds-yaml"])): ++ fail_usage("Failed: {} does not exist".format(options.get("--clouds-yaml"))) ++ if not options.get("--cloud"): ++ fail_usage("Failed: \"cloud\" not specified") ++ cloud = get_cloud(options) ++ username = cloud.get("username") ++ password = cloud.get("password") ++ projectname = cloud.get("project_name") ++ auth_url = None ++ try: ++ auth_url = cloud.get("auth_url") ++ except KeyError: ++ fail_usage("Failed: You have to set the Keystone service endpoint for authorization") ++ user_domain_name = cloud.get("user_domain_name") ++ project_domain_name = cloud.get("project_domain_name") ++ caverify = cloud.get("verify") ++ if caverify in [True, False]: ++ options["--ssl-insecure"] = caverify ++ else: ++ options["--cacert"] = caverify ++ if options.get("--openrc") and os.path.exists(os.path.expanduser(options["--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, +diff --git a/tests/data/metadata/fence_openstack.xml b/tests/data/metadata/fence_openstack.xml +index c8dc2e60f..55a57b4d7 100644 +--- a/tests/data/metadata/fence_openstack.xml ++++ b/tests/data/metadata/fence_openstack.xml +@@ -8,7 +8,7 @@ + + Fencing action + +- ++ + + + Login name +@@ -48,27 +48,27 @@ + + Use SSL connection without verifying certificate + +- ++ + + + Login name + +- ++ + + + Keystone Auth URL + +- ++ + + + Keystone Auth URL + +- ++ + + + Keystone Project + +- ++ + + + Keystone Project +@@ -93,6 +93,26 @@ + + Keystone Project Domain Name + ++ ++ ++ ++ clouds.yaml config file ++ ++ ++ ++ ++ clouds.yaml config file ++ ++ ++ ++ ++ Cloud from clouds.yaml ++ ++ ++ ++ ++ openrc config file ++ + + + diff --git a/bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch b/bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch new file mode 100644 index 0000000..6daa2bb --- /dev/null +++ b/bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch @@ -0,0 +1,171 @@ +From 7d9572ec947d23fa18ac530f07fe33ba148c9634 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Mon, 17 Jan 2022 14:32:53 +0100 +Subject: [PATCH] fence_openstack: fix issues with new clouds.yaml/openrc + parameters - hardcoded clouds.yaml paths to work like the openstack cli + client (used by the resource agents) + +--- + agents/openstack/fence_openstack.py | 55 +++++++++++-------------- + tests/data/metadata/fence_openstack.xml | 10 ----- + 2 files changed, 25 insertions(+), 40 deletions(-) + +diff --git a/agents/openstack/fence_openstack.py b/agents/openstack/fence_openstack.py +index d3a4be3aa..666016d78 100644 +--- a/agents/openstack/fence_openstack.py ++++ b/agents/openstack/fence_openstack.py +@@ -3,6 +3,7 @@ + import atexit + import logging + import sys ++import os + + import urllib3 + +@@ -27,9 +28,15 @@ def translate_status(instance_status): + return "unknown" + + def get_cloud(options): +- import os, yaml ++ import yaml + +- clouds_yaml = os.path.expanduser("~/.config/openstack/clouds.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: +@@ -201,22 +208,13 @@ def define_new_opts(): + "default": "Default", + "order": 5, + } +- all_opt["clouds-yaml"] = { +- "getopt": ":", +- "longopt": "clouds-yaml", +- "help": "--clouds-yaml=[clouds-yaml] Path to the clouds.yaml config file", +- "required": "0", +- "shortdesc": "clouds.yaml config file", +- "default": "~/.config/openstack/clouds.yaml", +- "order": 6, +- } + all_opt["cloud"] = { + "getopt": ":", + "longopt": "cloud", +- "help": "--cloud=[cloud] Openstack cloud (from clouds.yaml).", ++ "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).", + "required": "0", + "shortdesc": "Cloud from clouds.yaml", +- "order": 7, ++ "order": 6, + } + all_opt["openrc"] = { + "getopt": ":", +@@ -224,7 +222,7 @@ def define_new_opts(): + "help": "--openrc=[openrc] Path to the openrc config file", + "required": "0", + "shortdesc": "openrc config file", +- "order": 8, ++ "order": 7, + } + all_opt["uuid"] = { + "getopt": ":", +@@ -232,7 +230,7 @@ def define_new_opts(): + "help": "--uuid=[uuid] Replaced by -n, --plug", + "required": "0", + "shortdesc": "Replaced by port/-n/--plug", +- "order": 9, ++ "order": 8, + } + all_opt["cacert"] = { + "getopt": ":", +@@ -241,7 +239,7 @@ def define_new_opts(): + "required": "0", + "shortdesc": "SSL X.509 certificates file", + "default": "", +- "order": 10, ++ "order": 9, + } + all_opt["apitimeout"] = { + "getopt": ":", +@@ -251,7 +249,7 @@ def define_new_opts(): + "shortdesc": "Timeout in seconds to use for API calls, default is 60.", + "required": "0", + "default": 60, +- "order": 11, ++ "order": 10, + } + + +@@ -267,7 +265,6 @@ def main(): + "project-name", + "user-domain-name", + "project-domain-name", +- "clouds-yaml", + "cloud", + "openrc", + "port", +@@ -312,28 +309,26 @@ def main(): + + run_delay(options) + +- if options.get("--clouds-yaml"): +- if not os.path.exists(os.path.expanduser(options["--clouds-yaml"])): +- fail_usage("Failed: {} does not exist".format(options.get("--clouds-yaml"))) +- if not options.get("--cloud"): +- fail_usage("Failed: \"cloud\" not specified") ++ if options.get("--cloud"): + cloud = get_cloud(options) +- username = cloud.get("username") +- password = cloud.get("password") +- projectname = cloud.get("project_name") ++ 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_url") ++ 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("user_domain_name") +- project_domain_name = cloud.get("project_domain_name") ++ 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 +- if options.get("--openrc") and os.path.exists(os.path.expanduser(options["--openrc"])): ++ 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") +diff --git a/tests/data/metadata/fence_openstack.xml b/tests/data/metadata/fence_openstack.xml +index 55a57b4d7..67b2191b7 100644 +--- a/tests/data/metadata/fence_openstack.xml ++++ b/tests/data/metadata/fence_openstack.xml +@@ -93,16 +93,6 @@ + + Keystone Project Domain Name + +- +- +- +- clouds.yaml config file +- +- +- +- +- clouds.yaml config file +- + + + diff --git a/fence-agents.spec b/fence-agents.spec index 2fe9a68..33c5428 100644 --- a/fence-agents.spec +++ b/fence-agents.spec @@ -59,7 +59,7 @@ Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") Version: 4.10.0 -Release: 15%{?alphatag:.%{alphatag}}%{?dist} +Release: 16%{?alphatag:.%{alphatag}}%{?dist} License: GPLv2+ and LGPLv2+ URL: https://github.com/ClusterLabs/fence-agents Source0: https://fedorahosted.org/releases/f/e/fence-agents/%{name}-%{version}.tar.gz @@ -229,6 +229,8 @@ Patch12: bz2022334-fence_zvmip-add-ssl-tls-support.patch Patch13: bz2029791-1-fence_openstack-add-ssl-insecure.patch Patch14: bz2029791-2-fence_openstack-cacert-default.patch Patch15: bz2000954-3-fence_kubevirt-get-namespace-from-context.patch +Patch16: bz2041933-bz2041935-1-fence_openstack-clouds-openrc.patch +Patch17: bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch %global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hpblade ibmblade 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 @@ -355,6 +357,8 @@ BuildRequires: %{systemd_units} %patch13 -p1 %patch14 -p1 %patch15 -p1 -F2 +%patch16 -p1 -F1 +%patch17 -p1 # prevent compilation of something that won't get used anyway sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac @@ -1423,6 +1427,11 @@ are located on corosync cluster nodes. %endif %changelog +* Wed Jan 19 2022 Oyvind Albrigtsen - 4.10.0-16 +- fence_openstack: add support for reading config from clouds.yaml + and openrc + Resolves: rhbz#2041933, rhbz#2041935 + * Mon Jan 17 2022 Oyvind Albrigtsen - 4.10.0-15 - fence_kubevirt: new fence agent Resolves: rhbz#2000954