diff options
Diffstat (limited to 'agents/ecloud/fence_ecloud.py')
-rw-r--r-- | agents/ecloud/fence_ecloud.py | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/agents/ecloud/fence_ecloud.py b/agents/ecloud/fence_ecloud.py new file mode 100644 index 0000000..0707e10 --- /dev/null +++ b/agents/ecloud/fence_ecloud.py @@ -0,0 +1,169 @@ +#!@PYTHON@ -tt +# +# Fence agent for eCloud and eCloud VPC +# https://www.ans.co.uk/cloud-and-infrastructure/ecloud/ +# +# Copyright (c) 2022 ANS Group Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. + +import sys +import time +import atexit +import logging +import requests +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import run_delay, fail_usage, fail, EC_TIMED_OUT + +API_BASE = "https://api.ukfast.io/ecloud" +API_MONITOR = API_BASE + "/ping" +API_VPC_INSTANCE_DATA = API_BASE + "/v2/instances/:ID" +API_VPC_POWER_ON = API_BASE + "/v2/instances/:ID/power-on" +API_VPC_POWER_OFF = API_BASE + "/v2/instances/:ID/power-off" +API_V1_INSTANCE_DATA = API_BASE + "/v1/vms/:ID" +API_V1_POWER_ON = API_BASE + "/v1/vms/:ID/power-on" +API_V1_POWER_OFF = API_BASE + "/v1/vms/:ID/power-off" + + +def set_power_fn(conn, options): + logging.debug("setting power {}".format(options['--action'])) + del conn + + action = options['--action'] + vpc = options['--ecloud-vpc'] + plug = options['--plug'] + + url = fence_url(vpc, action, plug) + hdrs = headers(options['--apikey']) + + logging.info("executing '{}' action on '{}'".format(action, plug)) + + retries = 0 + while True: + resp = requests.put(url, headers=hdrs) + if resp.status_code == 409: + # If we attempt to power the instance back on too soon after powering it off, + # e.g. during a reboot, the API will return a 409 because while the power status + # has changed, the task is still executing. Retry the action until we exceed + # retries or get a different status code. + if retries >= 6: + logging.error("timed out trying to execute '{}' action after repeated 409 codes from API", action) + fail(EC_TIMED_OUT) + + time.sleep(2) + retries += 1 + continue + + if resp.status_code != 202: + logging.error("unexpected status code '{}' from endpoint '{}': {}".format( + resp.status_code, url, resp.text + )) + + break + + +def get_power_fn(conn, options): + logging.debug("getting power state") + del conn + + vpc = options['--ecloud-vpc'] + plug = options['--plug'] + + url = instance_data_url(vpc, plug) + hdrs = headers(options['--apikey']) + + resp = requests.get(url, headers=hdrs) + if resp.status_code != 200: + logging.error("unexpected status code ('{}') from endpoint '{}': {}".format( + resp.status_code, url, resp.text + )) + return "bad status {}".format(resp.status_code) + + instance = resp.json()['data'] + if vpc: + logging.debug("power state return value: {}".format(instance['online'])) + return "on" if instance['online'] else "off" + else: + if instance['power_status'] == "Online": + return "on" + elif instance['power_status'] == "Offline": + return "off" + else: + # Could be 'Unknown' or other value + return instance['power_status'] + + +def headers(apikey): + return { + "Authorization": apikey, + "User-Agent": "fence_ecloud" + } + + +def itp(url, plug): + return url.replace(':ID', plug) + + +def fence_url(vpc, action, plug): + if action == "on": + return itp(API_VPC_POWER_ON, plug) if vpc else itp(API_V1_POWER_ON, plug) + if action == "off": + return itp(API_VPC_POWER_OFF, plug) if vpc else itp(API_V1_POWER_OFF, plug) + + fail_usage("no available API configured for action '{}'".format(action)) + + +def instance_data_url(vpc, plug): + return itp(API_VPC_INSTANCE_DATA, plug) if vpc else itp(API_V1_INSTANCE_DATA, plug) + + +def main(): + device_opt = ["apikey", "port", "no_login", "no_password"] + + all_opt["apikey"] = { + "getopt": ":", + "longopt": "apikey", + "help": "--apikey=[key] eCloud API Key", + "required": "1", + "shortdesc": "API Key", + "order": 0, + } + all_opt["port"]["help"] = "-n, --plug=[instance] Instance ID (VPC) or server ID (v1)" + + atexit.register(atexit_handler) + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence Agent for ANS eCloud" + docs["longdesc"] = "fence_ecloud is a fence agent for use with the ANS \ +eCloud platform which is compatible with eCloud VPC and eCloud v1." + docs["vendorurl"] = "https://www.ans.co.uk" + show_docs(options, docs) + + if options['--action'] in ['on', 'off', 'reboot', 'status']: + plug = options['--plug'] + + options['--ecloud-vpc'] = True + if not plug.startswith("i-"): + options['--ecloud-vpc'] = False + + run_delay(options) + fence_action(None, options, set_power_fn, get_power_fn) + + +if __name__ == '__main__': + main() |