diff options
Diffstat (limited to 'agents/xenapi')
-rw-r--r-- | agents/xenapi/fence_xenapi.py | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/agents/xenapi/fence_xenapi.py b/agents/xenapi/fence_xenapi.py new file mode 100644 index 0000000..10c8ee0 --- /dev/null +++ b/agents/xenapi/fence_xenapi.py @@ -0,0 +1,221 @@ +#!@PYTHON@ -tt +# +############################################################################# +# Copyright 2011 Matthew Clark +# This file is part of fence-xenserver +# +# fence-xenserver is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# fence-xenserver 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Please let me know if you are using this script so that I can work out +# whether I should continue support for it. mattjclark0407 at hotmail dot com +############################################################################# + +############################################################################# +# It's only just begun... +# Current status: completely usable. This script is now working well and, +# has a lot of functionality as a result of the fencing.py library and the +# XenAPI libary. + +############################################################################# +# Please let me know if you are using this script so that I can work out +# whether I should continue support for it. mattjclark0407 at hotmail dot com + +import sys +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import run_delay +import XenAPI + +EC_BAD_SESSION = 1 +# Find the status of the port given in the -U flag of options. +def get_power_fn(session, options): + if "--verbose" in options: + verbose = True + else: + verbose = False + + try: + # Get a reference to the vm specified in the UUID or vm_name/port parameter + vm = return_vm_reference(session, options) + # Query the VM for its' associated parameters + record = session.xenapi.VM.get_record(vm) + # Check that we are not trying to manipulate a template or a control + # domain as they show up as VM's with specific properties. + if not record["is_a_template"] and not record["is_control_domain"]: + status = record["power_state"] + if verbose: + print("UUID:", record["uuid"], "NAME:", record["name_label"], "POWER STATUS:", record["power_state"]) + # Note that the VM can be in the following states (from the XenAPI document) + # Halted: VM is offline and not using any resources. + # Paused: All resources have been allocated but the VM itself is paused and its vCPUs are not running + # Running: Running + # Paused: VM state has been saved to disk and it is nolonger running. Note that disks remain in-Use while + # We want to make sure that we only return the status "off" if the machine is actually halted as the status + # is checked before a fencing action. Only when the machine is Halted is it not consuming resources which + # may include whatever you are trying to protect with this fencing action. + return status == "Halted" and "off" or "on" + except Exception as exn: + print(str(exn)) + + return "Error" + +# Set the state of the port given in the -U flag of options. +def set_power_fn(session, options): + try: + # Get a reference to the vm specified in the UUID or vm_name/port parameter + vm = return_vm_reference(session, options) + # Query the VM for its' associated parameters + record = session.xenapi.VM.get_record(vm) + # Check that we are not trying to manipulate a template or a control + # domain as they show up as VM's with specific properties. + if not record["is_a_template"] and not record["is_control_domain"]: + if options["--action"] == "on": + # Start the VM + session.xenapi.VM.start(vm, False, True) + elif options["--action"] == "off": + # Force shutdown the VM + session.xenapi.VM.hard_shutdown(vm) + elif options["--action"] == "reboot": + # Force reboot the VM + session.xenapi.VM.hard_reboot(vm) + except Exception as exn: + print(str(exn)) + +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if "--verbose" in options: + verbose = True + else: + verbose = False + + try: + # Return an array of all the VM's on the host + vms = session.xenapi.VM.get_all() + for vm in vms: + # Query the VM for its' associated parameters + record = session.xenapi.VM.get_record(vm) + # Check that we are not trying to manipulate a template or a control + # domain as they show up as VM's with specific properties. + if not record["is_a_template"] and not record["is_control_domain"]: + name = record["name_label"] + uuid = record["uuid"] + status = record["power_state"] + result[uuid] = (name, status) + if verbose: + print("UUID:", record["uuid"], "NAME:", name, "POWER STATUS:", record["power_state"]) + except Exception as exn: + print(str(exn)) + + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["--session-url"] + username = options["--username"] + password = options["--password"] + + try: + # Create the XML RPC session to the specified URL. + session = XenAPI.Session(url) + # Login using the supplied credentials. + session.xenapi.login_with_password(username, password) + except Exception as exn: + print(str(exn)) + # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says that for no connectivity + # the exit value should be 1. It doesn't say anything about failed logins, so + # until I hear otherwise it is best to keep this exit the same to make sure that + # anything calling this script (that uses the same information in the web page + # above) knows that this is an error condition, not a msg signifying a down port. + sys.exit(EC_BAD_SESSION) + return session + +# return a reference to the VM by either using the UUID or the vm_name/port. If the UUID is set then +# this is tried first as this is the only properly unique identifier. +# Exceptions are not handled in this function, code that calls this must be ready to handle them. +def return_vm_reference(session, options): + if "--verbose" in options: + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if "--uuid" in options: + uuid = options["--uuid"].lower() + # When using the -n parameter for name, we get an error message (in verbose + # mode) that tells us that we didn't find a VM. To immitate that here we + # need to catch and re-raise the exception produced by get_by_uuid. + try: + return session.xenapi.VM.get_by_uuid(uuid) + except Exception: + if verbose: + print("No VM's found with a UUID of \"%s\"" % uuid) + raise + + # Case where the vm_name/port has been specified + if "--plug" in options: + vm_name = options["--plug"] + vm_arr = session.xenapi.VM.get_by_name_label(vm_name) + # Need to make sure that we only have one result as the vm_name may + # not be unique. Average case, so do it first. + if len(vm_arr) == 1: + return vm_arr[0] + else: + if len(vm_arr) == 0: + if verbose: + print("No VM's found with a name of \"%s\"" % vm_name) + # NAME_INVALID used as the XenAPI throws a UUID_INVALID if it can't find + # a VM with the specified UUID. This should make the output look fairly + # consistent. + raise Exception("NAME_INVALID") + else: + if verbose: + print("Multiple VM's have the name \"%s\", use UUID instead" % vm_name) + raise Exception("MULTIPLE_VMS_FOUND") + + # We should never get to this case as the input processing checks that either the UUID or + # the name parameter is set. Regardless of whether or not a VM is found the above if + # statements will return to the calling function (either by exception or by a reference + # to the VM). + raise Exception("VM_LOGIC_ERROR") + +def main(): + + device_opt = ["login", "passwd", "port", "no_login", "no_password", "session_url", "web"] + + atexit.register(atexit_handler) + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for Citrix XenServer over XenAPI" + docs["longdesc"] = "\ +fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \ +It uses the XenAPI, supplied by Citrix, to establish an XML-RPC session \ +to a XenServer host. Once the session is established, further XML-RPC \ +commands are issued in order to switch on, switch off, restart and query \ +the status of virtual machines running on the host." + docs["vendorurl"] = "http://www.xenproject.org" + show_docs(options, docs) + + run_delay(options) + + xen_session = connect_and_login(options) + result = fence_action(xen_session, options, set_power_fn, get_power_fn, get_outlet_list) + + sys.exit(result) + +if __name__ == "__main__": + main() |