Page MenuHomeClusterLabs Projects

fence_ibm_vpc.py
No OneTemporary

fence_ibm_vpc.py

#!@PYTHON@ -tt
import sys
import pycurl, io, json
import logging
import atexit
import hashlib
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS, EC_GENERIC_ERROR
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, options, 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, options, 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, options, 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):
import os, errno
try:
# FIPS requires usedforsecurity=False and might not be
# available on all distros: https://bugs.python.org/issue9216
hash = hashlib.sha256(options["--apikey"].encode("utf-8"), usedforsecurity=False).hexdigest()
except (AttributeError, TypeError):
hash = hashlib.sha256(options["--apikey"].encode("utf-8")).hexdigest()
file_path = options["--token-file"].replace("[hash]", hash)
token = None
if not os.path.isdir(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
# For security, remove file with potentially elevated mode
try:
os.remove(file_path)
except OSError:
pass
try:
oldumask = os.umask(0)
file_handle = os.open(file_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
except OSError as e:
if e.errno == errno.EEXIST: # Failed as the file already exists.
logging.error("Failed: File already exists: {}".format(e))
sys.exit(EC_GENERIC_ERROR)
else: # Something unexpected went wrong
logging.error("Failed: Unable to open file: {}".format(e))
sys.exit(EC_GENERIC_ERROR)
else: # No exception, so the file must have been created successfully.
with os.fdopen(file_handle, 'w') as file_obj:
try:
conn.setopt(pycurl.HTTPHEADER, [
"Content-Type: application/x-www-form-urlencoded",
"User-Agent: curl",
])
token = send_command(conn, options, "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 as e:
logging.error("Failed: Unable to authenticate: {}".format(e))
fail(EC_LOGIN_DENIED)
file_obj.write(token)
finally:
os.umask(oldumask)
return token
def set_bearer_token(conn, bearer_token):
conn.setopt(pycurl.HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer {}".format(bearer_token),
"User-Agent: curl",
])
return conn
def connect(opt):
conn = pycurl.Curl()
bearer_token = ""
## 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)
conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"]))
# get bearer token
try:
try:
# FIPS requires usedforsecurity=False and might not be
# available on all distros: https://bugs.python.org/issue9216
hash = hashlib.sha256(opt["--apikey"].encode("utf-8"), usedforsecurity=False).hexdigest()
except (AttributeError, TypeError):
hash = hashlib.sha256(opt["--apikey"].encode("utf-8")).hexdigest()
f = open(opt["--token-file"].replace("[hash]", hash))
bearer_token = f.read()
f.close()
except IOError:
bearer_token = get_bearer_token(conn, opt)
# set auth token for later requests
conn = set_bearer_token(conn, bearer_token)
return conn
def disconnect(conn):
conn.close()
def send_command(conn, options, 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)
# auth if token has expired
if rc in [400, 401, 415]:
tokenconn = pycurl.Curl()
token = get_bearer_token(tokenconn, options)
tokenconn.close()
conn = set_bearer_token(conn, token)
# flush web_buffer
web_buffer.close()
web_buffer = io.BytesIO()
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["region"] = {
"getopt" : ":",
"longopt" : "region",
"help" : "--region=[region] Region",
"required" : "1",
"shortdesc" : "Region",
"order" : 0
}
all_opt["proxy"] = {
"getopt" : ":",
"longopt" : "proxy",
"help" : "--proxy=[http://<URL>:<PORT>] Proxy: 'http://<URL>:<PORT>'",
"required" : "0",
"default": "",
"shortdesc" : "Network proxy",
"order" : 0
}
all_opt["limit"] = {
"getopt" : ":",
"longopt" : "limit",
"help" : "--limit=[number] Limit number of nodes returned by API",
"required" : "0",
"default": 50,
"shortdesc" : "Number of nodes returned by API",
"order" : 0
}
all_opt["token_file"] = {
"getopt" : ":",
"longopt" : "token-file",
"help" : "--token-file=[path] Path to the token cache file\n"
"\t\t\t\t (Default: @FENCETMPDIR@/fence_ibm_vpc/[hash].token)\n"
"\t\t\t\t [hash] will be replaced by a hashed value",
"required" : "0",
"default": "@FENCETMPDIR@/fence_ibm_vpc/[hash].token",
"shortdesc" : "Path to the token cache file",
"order" : 0
}
def main():
device_opt = [
"apikey",
"region",
"proxy",
"limit",
"token_file",
"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 a Power 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()

File Metadata

Mime Type
text/x-script.python
Expires
Thu, Jun 26, 6:50 PM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1959438
Default Alt Text
fence_ibm_vpc.py (8 KB)

Event Timeline