diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py
new file mode 100755
index 000000000..6649771ea
--- /dev/null
+++ b/agents/ibm_powervs/fence_ibm_powervs.py
@@ -0,0 +1,202 @@
+#!@PYTHON@ -tt
+
+import sys
+import pycurl, io, json
+import logging
+import atexit
+sys.path.append("@FENCEAGENTSLIBDIR@")
+from fencing import *
+from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS
+
+state = {
+ "ACTIVE": "on",
+ "SHUTOFF": "off",
+ "ERROR": "unknown"
+}
+
+def get_list(conn, options):
+ outlets = {}
+
+ try:
+ command = "cloud-instances/{}/pvm-instances".format(options["--instance"])
+ res = send_command(conn, command)
+ except Exception as e:
+ logging.debug("Failed: {}".format(e))
+ return outlets
+
+ for r in res["pvmInstances"]:
+ if "--verbose" in options:
+ logging.debug(json.dumps(r, indent=2))
+ outlets[r["pvmInstanceID"]] = (r["serverName"], state[r["status"]])
+
+ return outlets
+
+def get_power_status(conn, options):
+ try:
+ command = "cloud-instances/{}/pvm-instances/{}".format(
+ options["--instance"], options["--plug"])
+ res = send_command(conn, command)
+ result = get_list(conn, options)[options["--plug"]][1]
+ except KeyError as e:
+ logging.debug("Failed: Unable to get status for {}".format(e))
+ fail(EC_STATUS)
+
+ return result
+
+def set_power_status(conn, options):
+ action = {
+ "on" : '{"action" : "start"}',
+ "off" : '{"action" : "immediate-shutdown"}',
+ }[options["--action"]]
+
+ try:
+ send_command(conn, "cloud-instances/{}/pvm-instances/{}/action".format(
+ options["--instance"], options["--plug"]), "POST", action)
+ except Exception as e:
+ logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e))
+ fail(EC_STATUS)
+
+def connect(opt):
+ conn = pycurl.Curl()
+
+ ## setup correct URL
+ conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/"
+
+ if opt["--verbose-level"] > 1:
+ conn.setopt(pycurl.VERBOSE, 1)
+
+ conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"]))
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+
+ # set auth token for later requests
+ conn.setopt(pycurl.HTTPHEADER, [
+ "Content-Type: application/json",
+ "Authorization: Bearer {}".format(opt["--token"]),
+ "CRN: {}".format(opt["--crn"]),
+ "User-Agent: curl",
+ ])
+
+ return conn
+
+def disconnect(conn):
+ conn.close()
+
+def send_command(conn, command, method="GET", action=None):
+ url = conn.base_url + command
+
+ conn.setopt(pycurl.URL, url.encode("ascii"))
+
+ web_buffer = io.BytesIO()
+
+ if method == "GET":
+ conn.setopt(pycurl.POST, 0)
+ if method == "POST":
+ conn.setopt(pycurl.POSTFIELDS, action)
+ if method == "DELETE":
+ conn.setopt(pycurl.CUSTOMREQUEST, "DELETE")
+
+ conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write)
+
+ try:
+ conn.perform()
+ except Exception as e:
+ raise(e)
+
+ rc = conn.getinfo(pycurl.HTTP_CODE)
+ result = web_buffer.getvalue().decode("UTF-8")
+
+ web_buffer.close()
+
+ if rc != 200:
+ if len(result) > 0:
+ raise Exception("{}: {}".format(rc,
+ result["value"]["messages"][0]["default_message"]))
+ else:
+ raise Exception("Remote returned {} for request to {}".format(rc, url))
+
+ if len(result) > 0:
+ result = json.loads(result)
+
+ logging.debug("url: {}".format(url))
+ logging.debug("method: {}".format(method))
+ logging.debug("response code: {}".format(rc))
+ logging.debug("result: {}\n".format(result))
+
+ return result
+
+def define_new_opts():
+ all_opt["token"] = {
+ "getopt" : ":",
+ "longopt" : "token",
+ "help" : "--token=[token] Bearer Token",
+ "required" : "1",
+ "shortdesc" : "Bearer Token",
+ "order" : 0
+ }
+ all_opt["crn"] = {
+ "getopt" : ":",
+ "longopt" : "crn",
+ "help" : "--crn=[crn] CRN",
+ "required" : "1",
+ "shortdesc" : "CRN",
+ "order" : 0
+ }
+ all_opt["instance"] = {
+ "getopt" : ":",
+ "longopt" : "instance",
+ "help" : "--instance=[instance] PowerVS Instance",
+ "required" : "1",
+ "shortdesc" : "PowerVS Instance",
+ "order" : 0
+ }
+ all_opt["region"] = {
+ "getopt" : ":",
+ "longopt" : "region",
+ "help" : "--region=[region] Region",
+ "required" : "1",
+ "shortdesc" : "Region",
+ "order" : 0
+ }
+
+
+def main():
+ device_opt = [
+ "token",
+ "crn",
+ "instance",
+ "region",
+ "port",
+ "no_password",
+ ]
+
+ atexit.register(atexit_handler)
+ define_new_opts()
+
+ all_opt["shell_timeout"]["default"] = "15"
+ all_opt["power_timeout"]["default"] = "30"
+ all_opt["power_wait"]["default"] = "1"
+
+ options = check_input(device_opt, process_input(device_opt))
+
+ docs = {}
+ docs["shortdesc"] = "Fence agent for IBM PowerVS"
+ docs["longdesc"] = """fence_ibm_powervs is an I/O Fencing agent which can be \
+used with IBM PowerVS to fence virtual machines."""
+ docs["vendorurl"] = "https://www.ibm.com"
+ show_docs(options, docs)
+
+ ####
+ ## Fence operations
+ ####
+ run_delay(options)
+
+ conn = connect(options)
+ atexit.register(disconnect, conn)
+
+ result = fence_action(conn, options, set_power_status, get_power_status, get_list)
+
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()
diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py
new file mode 100755
index 000000000..9f84f7b2d
--- /dev/null
+++ b/agents/ibm_vpc/fence_ibm_vpc.py
@@ -0,0 +1,230 @@
+#!@PYTHON@ -tt
+
+import sys
+import pycurl, io, json
+import logging
+import atexit
+sys.path.append("@FENCEAGENTSLIBDIR@")
+from fencing import *
+from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS
+
+state = {
+ "running": "on",
+ "stopped": "off",
+ "starting": "unknown",
+ "stopping": "unknown",
+ "restarting": "unknown",
+ "pending": "unknown",
+}
+
+def get_list(conn, options):
+ outlets = {}
+
+ try:
+ command = "instances?version=2021-05-25&generation=2&limit={}".format(options["--limit"])
+ res = send_command(conn, command)
+ except Exception as e:
+ logging.debug("Failed: Unable to get list: {}".format(e))
+ return outlets
+
+ for r in res["instances"]:
+ if options["--verbose-level"] > 1:
+ logging.debug("Node:\n{}".format(json.dumps(r, indent=2)))
+ logging.debug("Status: " + state[r["status"]])
+ outlets[r["id"]] = (r["name"], state[r["status"]])
+
+ return outlets
+
+def get_power_status(conn, options):
+ try:
+ command = "instances/{}?version=2021-05-25&generation=2".format(options["--plug"])
+ res = send_command(conn, command)
+ result = state[res["status"]]
+ if options["--verbose-level"] > 1:
+ logging.debug("Result:\n{}".format(json.dumps(res, indent=2)))
+ logging.debug("Status: " + result)
+ except Exception as e:
+ logging.debug("Failed: Unable to get status for {}: {}".format(options["--plug"], e))
+ fail(EC_STATUS)
+
+ return result
+
+def set_power_status(conn, options):
+ action = {
+ "on" : '{"type" : "start"}',
+ "off" : '{"type" : "stop"}',
+ }[options["--action"]]
+
+ try:
+ command = "instances/{}/actions?version=2021-05-25&generation=2".format(options["--plug"])
+ send_command(conn, command, "POST", action, 201)
+ except Exception as e:
+ logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e))
+ fail(EC_STATUS)
+
+def get_bearer_token(conn, options):
+ token = None
+ try:
+ conn.setopt(pycurl.HTTPHEADER, [
+ "Content-Type: application/x-www-form-urlencoded",
+ "User-Agent: curl",
+ ])
+ token = send_command(conn, "https://iam.cloud.ibm.com/identity/token", "POST", "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={}".format(options["--apikey"]))["access_token"]
+ except Exception:
+ logging.error("Failed: Unable to authenticate")
+ fail(EC_LOGIN_DENIED)
+
+ return token
+
+def connect(opt):
+ conn = pycurl.Curl()
+
+ ## setup correct URL
+ conn.base_url = "https://" + opt["--region"] + ".iaas.cloud.ibm.com/v1/"
+
+ if opt["--verbose-level"] > 1:
+ conn.setopt(pycurl.VERBOSE, 1)
+
+ conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"]))
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+
+ # get bearer token
+ bearer_token = get_bearer_token(conn, opt)
+
+ # set auth token for later requests
+ conn.setopt(pycurl.HTTPHEADER, [
+ "Content-Type: application/json",
+ "Authorization: Bearer {}".format(bearer_token),
+ "User-Agent: curl",
+ ])
+
+ return conn
+
+def disconnect(conn):
+ conn.close()
+
+def send_command(conn, command, method="GET", action=None, expected_rc=200):
+ if not command.startswith("https"):
+ url = conn.base_url + command
+ else:
+ url = command
+
+ conn.setopt(pycurl.URL, url.encode("ascii"))
+
+ web_buffer = io.BytesIO()
+
+ if method == "GET":
+ conn.setopt(pycurl.POST, 0)
+ if method == "POST":
+ conn.setopt(pycurl.POSTFIELDS, action)
+ if method == "DELETE":
+ conn.setopt(pycurl.CUSTOMREQUEST, "DELETE")
+
+ conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write)
+
+ try:
+ conn.perform()
+ except Exception as e:
+ raise(e)
+
+ rc = conn.getinfo(pycurl.HTTP_CODE)
+ result = web_buffer.getvalue().decode("UTF-8")
+
+ web_buffer.close()
+
+ # actions (start/stop/reboot) report 201 when they've been created
+ if rc != expected_rc:
+ logging.debug("rc: {}, result: {}".format(rc, result))
+ if len(result) > 0:
+ raise Exception("{}: {}".format(rc,
+ result["value"]["messages"][0]["default_message"]))
+ else:
+ raise Exception("Remote returned {} for request to {}".format(rc, url))
+
+ if len(result) > 0:
+ result = json.loads(result)
+
+ logging.debug("url: {}".format(url))
+ logging.debug("method: {}".format(method))
+ logging.debug("response code: {}".format(rc))
+ logging.debug("result: {}\n".format(result))
+
+ return result
+
+def define_new_opts():
+ all_opt["apikey"] = {
+ "getopt" : ":",
+ "longopt" : "apikey",
+ "help" : "--apikey=[key] API Key",
+ "required" : "1",
+ "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",
+ "help" : "--region=[region] Region",
+ "required" : "1",
+ "shortdesc" : "Region",
+ "order" : 0
+ }
+ all_opt["limit"] = {
+ "getopt" : ":",
+ "longopt" : "limit",
+ "help" : "--limit=[number] Limit number of nodes returned by API",
+ "required" : "1",
+ "default": 50,
+ "shortdesc" : "Number of nodes returned by API",
+ "order" : 0
+ }
+
+
+def main():
+ device_opt = [
+ "apikey",
+ "instance",
+ "region",
+ "limit",
+ "port",
+ "no_password",
+ ]
+
+ atexit.register(atexit_handler)
+ define_new_opts()
+
+ all_opt["shell_timeout"]["default"] = "15"
+ all_opt["power_timeout"]["default"] = "30"
+ all_opt["power_wait"]["default"] = "1"
+
+ options = check_input(device_opt, process_input(device_opt))
+
+ docs = {}
+ docs["shortdesc"] = "Fence agent for IBM Cloud VPC"
+ docs["longdesc"] = """fence_ibm_vpc is an I/O Fencing agent which can be \
+used with IBM Cloud VPC to fence virtual machines."""
+ docs["vendorurl"] = "https://www.ibm.com"
+ show_docs(options, docs)
+
+ ####
+ ## Fence operations
+ ####
+ run_delay(options)
+
+ conn = connect(options)
+ atexit.register(disconnect, conn)
+
+ result = fence_action(conn, options, set_power_status, get_power_status, get_list)
+
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()
diff --git a/fence-agents.spec.in b/fence-agents.spec.in
index b403934c6..d675b272d 100644
--- a/fence-agents.spec.in
+++ b/fence-agents.spec.in
@@ -56,6 +56,8 @@ fence-agents-heuristics-ping \\
fence-agents-hpblade \\
fence-agents-ibmblade \\
fence-agents-ibmz \\
+fence-agents-ibm-powervs \\
+fence-agents-ibm-vpc \\
fence-agents-ifmib \\
fence-agents-ilo-moonshot \\
fence-agents-ilo-mp \\
@@ -717,6 +719,28 @@ Web Services REST API.
%{_sbindir}/fence_ibmz
%{_mandir}/man8/fence_ibmz.8*
+%package ibm-powervs
+License: GPLv2+ and LGPLv2+
+Summary: Fence agent for IBM PowerVS
+Requires: fence-agents-common = %{version}-%{release}
+BuildArch: noarch
+%description ibm-powervs
+Fence agent for IBM PowerVS that are accessed via REST API.
+%files ibm-powervs
+%{_sbindir}/fence_ibm_powervs
+%{_mandir}/man8/fence_ibm_powervs.8*
+
+%package ibm-vpc
+License: GPLv2+ and LGPLv2+
+Summary: Fence agent for IBM Cloud VPC
+Requires: fence-agents-common = %{version}-%{release}
+BuildArch: noarch
+%description ibm-vpc
+Fence agent for IBM Cloud VPC that are accessed via REST API.
+%files ibm-vpc
+%{_sbindir}/fence_ibm_vpc
+%{_mandir}/man8/fence_ibm_vpc.8*
+
%package ifmib
License: GPLv2+ and LGPLv2+
Summary: Fence agent for devices with IF-MIB interfaces
diff --git a/tests/data/metadata/fence_ibm_powervs.xml b/tests/data/metadata/fence_ibm_powervs.xml
new file mode 100644
index 000000000..fe86331bd
--- /dev/null
+++ b/tests/data/metadata/fence_ibm_powervs.xml
@@ -0,0 +1,134 @@
+
+
+fence_ibm_powervs is an I/O Fencing agent which can be used with IBM PowerVS to fence virtual machines.
+https://www.ibm.com
+
+
+
+
+ CRN
+
+
+
+
+ PowerVS Instance
+
+
+
+
+ Region
+
+
+
+
+ Bearer Token
+
+
+
+
+ Fencing action
+
+
+
+
+ Physical plug number on device, UUID or identification of machine
+
+
+
+
+ Physical plug number on device, UUID or identification of machine
+
+
+
+
+ Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.
+
+
+
+
+ Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity.
+
+
+
+
+ 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).
+
+
+
+
+ Write debug information to given file
+
+
+
+
+ Write debug information to given file
+
+
+
+
+ Display version information and exit
+
+
+
+
+ Display help and exit
+
+
+
+
+ Separator for CSV created by 'list' operation
+
+
+
+
+ Wait X seconds before fencing is started
+
+
+
+
+ Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)
+
+
+
+
+ Wait X seconds for cmd prompt after login
+
+
+
+
+ Test X seconds for status change after ON/OFF
+
+
+
+
+ Wait X seconds after issuing ON/OFF
+
+
+
+
+ Wait X seconds for cmd prompt after issuing command
+
+
+
+
+ Sleep X seconds between status calls during a STONITH action
+
+
+
+
+ Count of attempts to retry power on
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml
new file mode 100644
index 000000000..926efcaa0
--- /dev/null
+++ b/tests/data/metadata/fence_ibm_vpc.xml
@@ -0,0 +1,134 @@
+
+
+fence_ibm_vpc is an I/O Fencing agent which can be used with IBM Cloud VPC to fence virtual machines.
+https://www.ibm.com
+
+
+
+
+ API Key
+
+
+
+
+ Cloud Instance
+
+
+
+
+ Number of nodes returned by API
+
+
+
+
+ Region
+
+
+
+
+ Fencing action
+
+
+
+
+ Physical plug number on device, UUID or identification of machine
+
+
+
+
+ Physical plug number on device, UUID or identification of machine
+
+
+
+
+ Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.
+
+
+
+
+ Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity.
+
+
+
+
+ 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).
+
+
+
+
+ Write debug information to given file
+
+
+
+
+ Write debug information to given file
+
+
+
+
+ Display version information and exit
+
+
+
+
+ Display help and exit
+
+
+
+
+ Separator for CSV created by 'list' operation
+
+
+
+
+ Wait X seconds before fencing is started
+
+
+
+
+ Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)
+
+
+
+
+ Wait X seconds for cmd prompt after login
+
+
+
+
+ Test X seconds for status change after ON/OFF
+
+
+
+
+ Wait X seconds after issuing ON/OFF
+
+
+
+
+ Wait X seconds for cmd prompt after issuing command
+
+
+
+
+ Sleep X seconds between status calls during a STONITH action
+
+
+
+
+ Count of attempts to retry power on
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+