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

