715 lines
28 KiB
Diff
715 lines
28 KiB
Diff
|
|
From 849861d2bdf76f70c7ee0b97387a27082f8a3fdd Mon Sep 17 00:00:00 2001
|
||
|
|
From: Enno G <matrixfueller@gmail.com>
|
||
|
|
Date: Mon, 26 Jun 2023 11:05:59 +0200
|
||
|
|
Subject: [PATCH 16/46] fence_eaton_ssh: new fence agent for Eaton ePDU G3 over
|
||
|
|
SSH (#549)
|
||
|
|
|
||
|
|
* fence_eaton_ssh: Initial add
|
||
|
|
* Docker: Add dockerized build environment
|
||
|
|
* Fix incorrect repository path in configure.ac
|
||
|
|
---
|
||
|
|
README.md | 19 +-
|
||
|
|
agents/eaton_ssh/fence_eaton_ssh.py | 318 ++++++++++++++++++++++++
|
||
|
|
configure.ac | 2 +-
|
||
|
|
docker/Dockerfile | 34 +++
|
||
|
|
docker/README.md | 10 +
|
||
|
|
docker/entrypoint.sh | 8 +
|
||
|
|
fence-agents.spec.in | 13 +
|
||
|
|
tests/data/metadata/fence_eaton_ssh.xml | 206 +++++++++++++++
|
||
|
|
8 files changed, 603 insertions(+), 7 deletions(-)
|
||
|
|
create mode 100644 agents/eaton_ssh/fence_eaton_ssh.py
|
||
|
|
create mode 100644 docker/Dockerfile
|
||
|
|
create mode 100644 docker/README.md
|
||
|
|
create mode 100755 docker/entrypoint.sh
|
||
|
|
create mode 100644 tests/data/metadata/fence_eaton_ssh.xml
|
||
|
|
|
||
|
|
diff --git a/README.md b/README.md
|
||
|
|
index d9fcb94b..0f3ebbde 100644
|
||
|
|
--- a/README.md
|
||
|
|
+++ b/README.md
|
||
|
|
@@ -1,18 +1,25 @@
|
||
|
|
# Fence agents
|
||
|
|
|
||
|
|
-Fence agents were developed as device "drivers" which are able to prevent computers from destroying data on shared storage. Their aim is to isolate a corrupted computer, using one of three methods:
|
||
|
|
+Fence agents were developed as device "drivers" which are able to prevent computers from destroying data on shared
|
||
|
|
+storage. Their aim is to isolate a corrupted computer, using one of three methods:
|
||
|
|
|
||
|
|
- * Power - A computer that is switched off cannot corrupt data, but it is important to not do a "soft-reboot" as we won't know if this is possible. This also works for virtual machines when the fence device is a hypervisor.
|
||
|
|
- * Network - Switches can prevent routing to a given computer, so even if a computer is powered on it won't be able to harm the data.
|
||
|
|
+ * Power - A computer that is switched off cannot corrupt data, but it is important to not do a "soft-reboot" as we
|
||
|
|
+ won't know if this is possible. This also works for virtual machines when the fence device is a hypervisor.
|
||
|
|
+ * Network - Switches can prevent routing to a given computer, so even if a computer is powered on it won't be able to
|
||
|
|
+ harm the data.
|
||
|
|
* Configuration - Fibre-channel switches or SCSI devices allow us to limit who can write to managed disks.
|
||
|
|
|
||
|
|
-Fence agents do not use configuration files, as configuration management is outside of their scope. All of the configuration has to be specified either as command-line arguments or lines of standard input (see the complete list for more info).
|
||
|
|
+Fence agents do not use configuration files, as configuration management is outside of their scope. All of the
|
||
|
|
+configuration has to be specified either as command-line arguments or lines of standard input (see the complete list
|
||
|
|
+for more info).
|
||
|
|
|
||
|
|
-Because many fence agents are quite similar to each other, a fencing library (in Python) was developed. Please use it for further development. Creating or modifying a new fence agent should be quite simple using this library.
|
||
|
|
+Because many fence agents are quite similar to each other, a fencing library (in Python) was developed. Please use it
|
||
|
|
+for further development. Creating or modifying a new fence agent should be quite simple using this library.
|
||
|
|
|
||
|
|
## Where can I find more information?
|
||
|
|
|
||
|
|
* [ClusterLabs website](http://www.clusterlabs.org/)
|
||
|
|
* [User and developer documentation](https://github.com/ClusterLabs/fence-agents/tree/master/doc/FenceAgentAPI.md)
|
||
|
|
-* Mailing lists for [users](http://oss.clusterlabs.org/mailman/listinfo/users) and [developers](http://oss.clusterlabs.org/mailman/listinfo/developers)
|
||
|
|
+* Mailing lists for [users](http://oss.clusterlabs.org/mailman/listinfo/users) and
|
||
|
|
+ [developers](http://oss.clusterlabs.org/mailman/listinfo/developers)
|
||
|
|
* #clusterlabs IRC channel on [freenode](http://freenode.net/)
|
||
|
|
diff --git a/agents/eaton_ssh/fence_eaton_ssh.py b/agents/eaton_ssh/fence_eaton_ssh.py
|
||
|
|
new file mode 100644
|
||
|
|
index 00000000..8e536a2e
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/agents/eaton_ssh/fence_eaton_ssh.py
|
||
|
|
@@ -0,0 +1,318 @@
|
||
|
|
+#!@PYTHON@ -tt
|
||
|
|
+
|
||
|
|
+"""
|
||
|
|
+Plug numbering starts with 1! There were no tests performed so far with daisy chained PDUs.
|
||
|
|
+
|
||
|
|
+Example usage:
|
||
|
|
+ fence_eaton_ssh -v -a <IP> -l <USER> -p <PASSWORD> --login-timeout=60 --action status --plug 1
|
||
|
|
+"""
|
||
|
|
+
|
||
|
|
+#####
|
||
|
|
+##
|
||
|
|
+## The Following Agent Has Been Tested On:
|
||
|
|
+##
|
||
|
|
+## Model Firmware
|
||
|
|
+## +---------------------------------------------+
|
||
|
|
+## EMAB04 04.02.0001
|
||
|
|
+#####
|
||
|
|
+
|
||
|
|
+import enum
|
||
|
|
+import sys
|
||
|
|
+import atexit
|
||
|
|
+
|
||
|
|
+sys.path.append("@FENCEAGENTSLIBDIR@")
|
||
|
|
+from fencing import *
|
||
|
|
+from fencing import fail, EC_STATUS, EC_LOGIN_DENIED
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class FenceEatonPowerActions(enum.Enum):
|
||
|
|
+ """
|
||
|
|
+ Status of the plug on the PDU.
|
||
|
|
+ """
|
||
|
|
+ ERROR = -1
|
||
|
|
+ OFF = 0
|
||
|
|
+ ON = 1
|
||
|
|
+ PENDING_OFF = 2
|
||
|
|
+ PENDING_ON = 3
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_plug_names(conn, plug_ids, command_prompt, shell_timout):
|
||
|
|
+ """
|
||
|
|
+ Get the names of plugs via their ID.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param plug_ids: The list of plug IDs. Plugs start with the ID 1.
|
||
|
|
+ :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command.
|
||
|
|
+ :param shell_timeout: The maximum time the shell should wait for a response.
|
||
|
|
+ :returns: The name of the requested plugs.
|
||
|
|
+ """
|
||
|
|
+ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases.
|
||
|
|
+ result = {}
|
||
|
|
+ full_node_mapping = {}
|
||
|
|
+ conn.send_eol("get PDU.OutletSystem.Outlet[x].iName")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+ result_plug_names = conn.before.split("\n") # type: ignore
|
||
|
|
+ if len(result_plug_names) != 3:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ plug_names = result_plug_names.split("|")
|
||
|
|
+ for counter in range(1, len(plug_names)):
|
||
|
|
+ full_node_mapping[counter] = plug_names[counter]
|
||
|
|
+ for plug_id in plug_ids:
|
||
|
|
+ result[plug_id] = full_node_mapping[plug_id]
|
||
|
|
+ return result
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_plug_ids(conn, nodenames, command_prompt, shell_timout):
|
||
|
|
+ """
|
||
|
|
+ Get the IDs that map to the given nodenames. Non existing names are skipped.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param nodenames: The list of human readable names that should be converted to IDs.
|
||
|
|
+ :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command.
|
||
|
|
+ :param shell_timeout: The maximum time the shell should wait for a response.
|
||
|
|
+ :returns: A dictionary - possibly empty - where the keys are the node names and the values are the node IDs.
|
||
|
|
+ """
|
||
|
|
+ result = {}
|
||
|
|
+ full_node_mapping = {}
|
||
|
|
+ conn.send_eol("get PDU.OutletSystem.Outlet[x].iName")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+ result_plug_names = conn.before.split("\n") # type: ignore
|
||
|
|
+ if len(result_plug_names) != 3:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ plug_names = result_plug_names.split("|")
|
||
|
|
+ for counter in range(1, len(plug_names)):
|
||
|
|
+ full_node_mapping[plug_names[counter]] = counter
|
||
|
|
+ for node in nodenames:
|
||
|
|
+ if node in full_node_mapping:
|
||
|
|
+ result[node] = full_node_mapping[node]
|
||
|
|
+ return result
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_plug_count(conn, command_prompt, shell_timout):
|
||
|
|
+ """
|
||
|
|
+ Get the number of plugs that the PDU has.
|
||
|
|
+
|
||
|
|
+ In case the PDU is daisy chained this also contains the plugs of the other PDUs.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command.
|
||
|
|
+ :param shell_timeout: The maximum time the shell should wait for a response.
|
||
|
|
+ :returns: The number of plugs that the PDU has.
|
||
|
|
+ """
|
||
|
|
+ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases.
|
||
|
|
+ conn.send_eol("get PDU.OutletSystem.Outlet.Count")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+ result_plug_count = conn.before.split("\n") # type: ignore
|
||
|
|
+ if len(result_plug_count) != 3:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ return int(result_plug_count[1].strip())
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_plug_status(conn, plug_id, command_prompt, shell_timout):
|
||
|
|
+ """
|
||
|
|
+ Get the current status of the plug. The return value of this doesn't account for operations that will act via
|
||
|
|
+ schedules or a delay. As such the status is only valid at the time of retrieval.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1.
|
||
|
|
+ :returns: The current status of the plug.
|
||
|
|
+ """
|
||
|
|
+ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases.
|
||
|
|
+ conn.send_eol(f"get PDU.OutletSystem.Outlet[{plug_id}].PresentStatus.SwitchOnOff")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+ result_plug_status = conn.before.split("\n") # type: ignore
|
||
|
|
+ if len(result_plug_status) != 3:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ if result_plug_status[1].strip() == "0":
|
||
|
|
+ return FenceEatonPowerActions.OFF
|
||
|
|
+ elif result_plug_status[1].strip() == "1":
|
||
|
|
+ return FenceEatonPowerActions.ON
|
||
|
|
+ else:
|
||
|
|
+ return FenceEatonPowerActions.ERROR
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def power_on_plug(conn, plug_id, command_prompt, shell_timout, delay=0):
|
||
|
|
+ """
|
||
|
|
+ Powers on a plug with an optional delay.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1.
|
||
|
|
+ :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command.
|
||
|
|
+ :param shell_timeout: The maximum time the shell should wait for a response.
|
||
|
|
+ :param delay: The delay in seconds. Passing "-1" aborts the power off action.
|
||
|
|
+ """
|
||
|
|
+ conn.send_eol(f"set PDU.OutletSystem.Outlet[{plug_id}].DelayBeforeStartup {delay}")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def power_off_plug(conn, plug_id, command_prompt, shell_timout, delay=0):
|
||
|
|
+ """
|
||
|
|
+ Powers off a plug with an optional delay.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1.
|
||
|
|
+ :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command.
|
||
|
|
+ :param shell_timeout: The maximum time the shell should wait for a response.
|
||
|
|
+ :param delay: The delay in seconds. Passing "-1" aborts the power off action.
|
||
|
|
+ """
|
||
|
|
+ conn.send_eol(f"set PDU.OutletSystem.Outlet[{plug_id}].DelayBeforeShutdown {delay}")
|
||
|
|
+ conn.log_expect(command_prompt, shell_timout)
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_power_status(conn, options):
|
||
|
|
+ """
|
||
|
|
+ Retrieve the power status for the requested plug. Since we have a serial like interface via SSH we need to parse the
|
||
|
|
+ output of the SSH session manually.
|
||
|
|
+
|
||
|
|
+ If abnormal behavior is detected the method will exit via "fail()".
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param options: The option dictionary.
|
||
|
|
+ :returns: In case there is an error this method does not return but instead calls "sys.exit". Otherwhise one of
|
||
|
|
+ "off", "on" or "error" is returned.
|
||
|
|
+ """
|
||
|
|
+ if conn is None:
|
||
|
|
+ fail(EC_LOGIN_DENIED)
|
||
|
|
+
|
||
|
|
+ requested_plug = options.get("--plug", "")
|
||
|
|
+ if not requested_plug:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ plug_status = get_plug_status(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ int(requested_plug),
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ if plug_status == FenceEatonPowerActions.OFF:
|
||
|
|
+ return "off"
|
||
|
|
+ elif plug_status == FenceEatonPowerActions.ON:
|
||
|
|
+ return "on"
|
||
|
|
+ else:
|
||
|
|
+ return "error"
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def set_power_status(conn, options):
|
||
|
|
+ """
|
||
|
|
+ Set the power status for the requested plug. Only resposible for powering on and off.
|
||
|
|
+
|
||
|
|
+ If abnormal behavior is detected the method will exit via "fail()".
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param options: The option dictionary.
|
||
|
|
+ :returns: In case there is an error this method does not return but instead calls "sys.exit".
|
||
|
|
+ """
|
||
|
|
+ if conn is None:
|
||
|
|
+ fail(EC_LOGIN_DENIED)
|
||
|
|
+
|
||
|
|
+ requested_plug = options.get("--plug", "")
|
||
|
|
+ if not requested_plug:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+ requested_action = options.get("--action", "")
|
||
|
|
+ if not requested_action:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+
|
||
|
|
+ if requested_action == "off":
|
||
|
|
+ power_off_plug(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ int(requested_plug),
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ elif requested_action == "on":
|
||
|
|
+ power_on_plug(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ int(requested_plug),
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ else:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_outlet_list(conn, options):
|
||
|
|
+ """
|
||
|
|
+ Retrieves the list of plugs with their correspondin status.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param options: The option dictionary.
|
||
|
|
+ :returns: Keys are the Plug IDs which each have a Tuple with the alias for the plug and its status.
|
||
|
|
+ """
|
||
|
|
+ if conn is None:
|
||
|
|
+ fail(EC_LOGIN_DENIED)
|
||
|
|
+
|
||
|
|
+ result = {}
|
||
|
|
+ plug_count = get_plug_count(conn, options["--command-prompt"], int(options["--shell-timeout"])) # type: ignore
|
||
|
|
+ for counter in range(1, plug_count):
|
||
|
|
+ plug_names = get_plug_names(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ [counter],
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ plug_status_enum = get_plug_status(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ counter,
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ if plug_status_enum == FenceEatonPowerActions.OFF:
|
||
|
|
+ plug_status = "OFF"
|
||
|
|
+ elif plug_status_enum == FenceEatonPowerActions.ON:
|
||
|
|
+ plug_status = "ON"
|
||
|
|
+ else:
|
||
|
|
+ plug_status = None
|
||
|
|
+ result[str(counter)] = (plug_names[counter], plug_status)
|
||
|
|
+ return result
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def reboot_cycle(conn, options) -> None:
|
||
|
|
+ """
|
||
|
|
+ Responsible for power cycling a machine. Not responsible for singular on and off actions.
|
||
|
|
+
|
||
|
|
+ :param conn: The "fspawn" object.
|
||
|
|
+ :param options: The option dictionary.
|
||
|
|
+ """
|
||
|
|
+ requested_plug = options.get("--plug", "")
|
||
|
|
+ if not requested_plug:
|
||
|
|
+ fail(EC_STATUS)
|
||
|
|
+
|
||
|
|
+ power_off_plug(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ int(requested_plug),
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+ power_on_plug(
|
||
|
|
+ conn, # type: ignore
|
||
|
|
+ int(requested_plug),
|
||
|
|
+ options["--command-prompt"],
|
||
|
|
+ int(options["--shell-timeout"])
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def main():
|
||
|
|
+ """
|
||
|
|
+ Main entrypoint for the fence_agent.
|
||
|
|
+ """
|
||
|
|
+ device_opt = ["secure", "ipaddr", "login", "passwd", "port", "cmd_prompt"]
|
||
|
|
+ atexit.register(atexit_handler)
|
||
|
|
+ options = check_input(device_opt, process_input(device_opt))
|
||
|
|
+ options["--ssh"] = None
|
||
|
|
+ options["--ipport"] = 22
|
||
|
|
+ options["--command-prompt"] = "pdu#0>"
|
||
|
|
+
|
||
|
|
+ docs = {}
|
||
|
|
+ docs["shortdesc"] = "Fence agent for Eaton ePDU G3 over SSH"
|
||
|
|
+ docs["longdesc"] = "fence_eaton_ssh is a fence agent that connects to Eaton ePDU devices. It logs into \
|
||
|
|
+device via ssh and reboot a specified outlet."
|
||
|
|
+ docs["vendorurl"] = "https://www.eaton.com/"
|
||
|
|
+ show_docs(options, docs)
|
||
|
|
+
|
||
|
|
+ conn = fence_login(options)
|
||
|
|
+ result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list, reboot_cycle)
|
||
|
|
+ fence_logout(conn, "quit")
|
||
|
|
+ sys.exit(result)
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+if __name__ == "__main__":
|
||
|
|
+ main()
|
||
|
|
diff --git a/configure.ac b/configure.ac
|
||
|
|
index 65a9718d..8436ba25 100644
|
||
|
|
--- a/configure.ac
|
||
|
|
+++ b/configure.ac
|
||
|
|
@@ -567,7 +567,7 @@ if test "x$VERSION" = "xUNKNOWN"; then
|
||
|
|
configure was unable to determine the source tree's current version. This
|
||
|
|
generally happens when using git archive (or the github download button)
|
||
|
|
generated tarball/zip file. In order to workaround this issue, either use git
|
||
|
|
- clone https://github.com/ClusterLabs/fence-virt.git or use an official release
|
||
|
|
+ clone https://github.com/ClusterLabs/fence-agents.git or use an official release
|
||
|
|
tarball. Alternatively you can add a compatible version in a .tarball-version
|
||
|
|
file at the top of the source tree, wipe your autom4te.cache dir and generated
|
||
|
|
configure, and rerun autogen.sh.
|
||
|
|
diff --git a/docker/Dockerfile b/docker/Dockerfile
|
||
|
|
new file mode 100644
|
||
|
|
index 00000000..6ac9480c
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/docker/Dockerfile
|
||
|
|
@@ -0,0 +1,34 @@
|
||
|
|
+FROM opensuse/leap:15.5
|
||
|
|
+
|
||
|
|
+RUN zypper in -y \
|
||
|
|
+ git \
|
||
|
|
+ autoconf \
|
||
|
|
+ automake \
|
||
|
|
+ libtool \
|
||
|
|
+ make \
|
||
|
|
+ gcc \
|
||
|
|
+ libcorosync-devel \
|
||
|
|
+ libxslt1 \
|
||
|
|
+ libxslt-tools \
|
||
|
|
+ python3-devel \
|
||
|
|
+ python3-httplib2 \
|
||
|
|
+ python3-pexpect \
|
||
|
|
+ python3-pycurl \
|
||
|
|
+ python3-requests \
|
||
|
|
+ python3-suds-jurko \
|
||
|
|
+ python3-openwsman \
|
||
|
|
+ python3-boto3 \
|
||
|
|
+ python3-novaclient \
|
||
|
|
+ python3-keystoneclient \
|
||
|
|
+ mozilla-nss-devel \
|
||
|
|
+ mozilla-nspr-devel \
|
||
|
|
+ libvirt-devel \
|
||
|
|
+ libxml2-devel \
|
||
|
|
+ flex \
|
||
|
|
+ bison \
|
||
|
|
+ libuuid-devel \
|
||
|
|
+ systemd
|
||
|
|
+
|
||
|
|
+WORKDIR /code
|
||
|
|
+VOLUME /code
|
||
|
|
+ENTRYPOINT ["./docker/entrypoint.sh"]
|
||
|
|
diff --git a/docker/README.md b/docker/README.md
|
||
|
|
new file mode 100644
|
||
|
|
index 00000000..2aef7421
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/docker/README.md
|
||
|
|
@@ -0,0 +1,10 @@
|
||
|
|
+# Dockerfile to build the fence-agents locally
|
||
|
|
+
|
||
|
|
+Usage is as follows:
|
||
|
|
+
|
||
|
|
+```
|
||
|
|
+podman build -f docker/Dockerfile -t fence-agents:main .
|
||
|
|
+podman run -it -v $PWD:/code --rm localhost/fence-agents:main
|
||
|
|
+```
|
||
|
|
+
|
||
|
|
+In case you are running docker replace `podman` with `docker` and it should work the same.
|
||
|
|
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
|
||
|
|
new file mode 100755
|
||
|
|
index 00000000..caa778e2
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/docker/entrypoint.sh
|
||
|
|
@@ -0,0 +1,8 @@
|
||
|
|
+#!/usr/bin/bash
|
||
|
|
+
|
||
|
|
+echo "### Running autogen"
|
||
|
|
+./autogen.sh
|
||
|
|
+echo "### Running configure"
|
||
|
|
+./configure
|
||
|
|
+echo "### Running make"
|
||
|
|
+make
|
||
|
|
diff --git a/fence-agents.spec.in b/fence-agents.spec.in
|
||
|
|
index b6af20d9..343f1c1a 100644
|
||
|
|
--- a/fence-agents.spec.in
|
||
|
|
+++ b/fence-agents.spec.in
|
||
|
|
@@ -55,6 +55,7 @@ fence-agents-docker \\
|
||
|
|
fence-agents-drac \\
|
||
|
|
fence-agents-drac5 \\
|
||
|
|
fence-agents-eaton-snmp \\
|
||
|
|
+fence-agents-eaton-ssh \\
|
||
|
|
fence-agents-ecloud \\
|
||
|
|
fence-agents-emerson \\
|
||
|
|
fence-agents-eps \\
|
||
|
|
@@ -631,6 +632,18 @@ via the SNMP protocol.
|
||
|
|
%{_sbindir}/fence_eaton_snmp
|
||
|
|
%{_mandir}/man8/fence_eaton_snmp.8*
|
||
|
|
|
||
|
|
+%package eaton-ssh
|
||
|
|
+License: GPL-2.0-or-later AND LGPL-2.0-or-later
|
||
|
|
+Summary: Fence agent for Eaton network power switches
|
||
|
|
+Requires: fence-agents-common = %{version}-%{release}
|
||
|
|
+BuildArch: noarch
|
||
|
|
+%description eaton-ssh
|
||
|
|
+Fence agent for Eaton network power switches that are accessed
|
||
|
|
+via the serial protocol tunnel over SSH.
|
||
|
|
+%files eaton-ssh
|
||
|
|
+%{_sbindir}/fence_eaton_ssh
|
||
|
|
+%{_mandir}/man8/fence_eaton_ssh.8*
|
||
|
|
+
|
||
|
|
%package ecloud
|
||
|
|
License: GPL-2.0-or-later AND LGPL-2.0-or-later
|
||
|
|
Summary: Fence agent for eCloud and eCloud VPC
|
||
|
|
diff --git a/tests/data/metadata/fence_eaton_ssh.xml b/tests/data/metadata/fence_eaton_ssh.xml
|
||
|
|
new file mode 100644
|
||
|
|
index 00000000..a3be1ac6
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/tests/data/metadata/fence_eaton_ssh.xml
|
||
|
|
@@ -0,0 +1,206 @@
|
||
|
|
+<?xml version="1.0" ?>
|
||
|
|
+<resource-agent name="fence_eaton_ssh" shortdesc="Fence agent for Eaton ePDU G3 over SSH" >
|
||
|
|
+<longdesc>fence_eaton_ssh is a fence agent that connects to Eaton ePDU devices. It logs into device via ssh and reboot a specified outlet.</longdesc>
|
||
|
|
+<vendor-url>https://www.eaton.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="cmd_prompt" unique="0" required="0" deprecated="1">
|
||
|
|
+ <getopt mixed="-c, --command-prompt=[prompt]" />
|
||
|
|
+ <content type="string" />
|
||
|
|
+ <shortdesc lang="en">Force Python regex for command prompt</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="command_prompt" unique="0" required="0" obsoletes="cmd_prompt">
|
||
|
|
+ <getopt mixed="-c, --command-prompt=[prompt]" />
|
||
|
|
+ <content type="string" />
|
||
|
|
+ <shortdesc lang="en">Force Python regex for command prompt</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="identity_file" unique="0" required="0">
|
||
|
|
+ <getopt mixed="-k, --identity-file=[filename]" />
|
||
|
|
+ <shortdesc lang="en">Identity file (private key) for SSH</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="inet4_only" unique="0" required="0">
|
||
|
|
+ <getopt mixed="-4, --inet4-only" />
|
||
|
|
+ <content type="boolean" />
|
||
|
|
+ <shortdesc lang="en">Forces agent to use IPv4 addresses only</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="inet6_only" unique="0" required="0">
|
||
|
|
+ <getopt mixed="-6, --inet6-only" />
|
||
|
|
+ <content type="boolean" />
|
||
|
|
+ <shortdesc lang="en">Forces agent to use IPv6 addresses only</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" />
|
||
|
|
+ <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="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="secure" unique="0" required="0" deprecated="1">
|
||
|
|
+ <getopt mixed="-x, --ssh" />
|
||
|
|
+ <content type="boolean" />
|
||
|
|
+ <shortdesc lang="en">Use SSH connection</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="ssh" unique="0" required="0" obsoletes="secure">
|
||
|
|
+ <getopt mixed="-x, --ssh" />
|
||
|
|
+ <content type="boolean" />
|
||
|
|
+ <shortdesc lang="en">Use SSH connection</shortdesc>
|
||
|
|
+ </parameter>
|
||
|
|
+ <parameter name="ssh_options" unique="0" required="0">
|
||
|
|
+ <getopt mixed="--ssh-options=[options]" />
|
||
|
|
+ <content type="string" />
|
||
|
|
+ <shortdesc lang="en">SSH options to use</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="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="20" />
|
||
|
|
+ <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="ssh_path" unique="0" required="0">
|
||
|
|
+ <getopt mixed="--ssh-path=[path]" />
|
||
|
|
+ <shortdesc lang="en">Path to ssh 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>
|
||
|
|
--
|
||
|
|
2.25.1
|
||
|
|
|