New fencing script for Citrix XenServer and XCP.
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
Fencing script that uses the XenAPI to allow remote switch, status and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
--- fence/agents/xenapi/Makefile.am | 17 +++ fence/agents/xenapi/XenAPI.py | 209 ++++++++++++++++++++++++++++++++ fence/agents/xenapi/fence_xenapi.py | 227 +++++++++++++++++++++++++++++++++++ 3 files changed, 453 insertions(+), 0 deletions(-) create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755 fence/agents/xenapi/XenAPI.py create mode 100644 fence/agents/xenapi/fence_xenapi.py
fence/agents/xenapi/Makefile.am new file +17 -0
@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) + +man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)
fence/agents/xenapi/XenAPI.py new file +209 -0
209 lines not on this page · raw message
fence/agents/xenapi/fence_xenapi.py new file +227 -0
227 lines not on this page · raw message
Signature · 1 line
1.7.0.4
PATCH 2/3 Updated to include xenapi script.
--- configure.ac | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
configure.ac +1 -0
@@ -256,6 +256,7 @@ AC_CONFIG_FILES([Makefile fence/agents/vmware_soap/Makefile fence/agents/wti/Makefile fence/agents/xcat/Makefile + fence/agents/xenapi/Makefile fence/agents/zvm/Makefile doc/Makefile])
Signature · 1 line
1.7.0.4
PATCH 3/3 Updated to include xenapi script in Makefile.am.
In reply to Matt Clark--- fence/agents/Makefile.am | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) mode change 100644 => 100755 fence/agents/xenapi/fence_xenapi.py
fence/agents/Makefile.am +1 -0
@@ -40,4 +40,5 @@ SUBDIRS = lib \ vmware_soap \ wti \ xcat \ + xenapi \ zvm
fence/agents/xenapi/fence_xenapi.py +0 -0
Signature · 1 line
1.7.0.4
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
In reply to Matt ClarkHi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.py being a fence library needs to move into the libs/ section. It is not parsed/built/installed in this patch set.
You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of RELEASE_VERSION and BUILD_DATE at the end of the code for fence_xenapi. Those should be generated dynamically at build time to reflect the build info.
Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Cheers
Fabio
Quoted 481 lines · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
--- fence/agents/xenapi/Makefile.am | 17 +++ fence/agents/xenapi/XenAPI.py | 209 ++++++++++++++++++++++++++++++++ fence/agents/xenapi/fence_xenapi.py | 227 +++++++++++++++++++++++++++++++++++ 3 files changed, 453 insertions(+), 0 deletions(-) create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755 fence/agents/xenapi/XenAPI.py create mode 100644 fence/agents/xenapi/fence_xenapi.pyfence/agents/xenapi/Makefile.am new file +17
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) + +man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)fence/agents/xenapi/XenAPI.py new file +209
-0@@ -0,0 +1,209 @@ +#============================================================================ +# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU Lesser General Public +# License as published by the Free Software Foundation. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#============================================================================ +# Copyright (C) 2006 XenSource Inc. +#============================================================================ +# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self): + return dict([(str(i), self.details[i]) + for i in range(len(self.details))]) + + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP over Unix domain + sockets. """ + def connect(self): + path = self.host.replace("_", "/") + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(path) + +class UDSHTTP(httplib.HTTP): + _connection_class = UDSHTTPConnection + +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xend using + the Xen-API. + + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support will disappear + once there is a working Xen-API replacement for every call in the legacy + API. + """ + + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in') + else: + return result + raise xmlrpclib.Fault( + 500, 'Tried 3 times to get a valid session, but failed') + + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' % method)(*params)) + if result == _RECONNECT_AND_RETRY: + raise xmlrpclib.Fault( + 500, 'Received SESSION_INVALID when logging in') + self._session = result + self.last_login_method = method + self.last_login_params = params + + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else: + return xmlrpclib.ServerProxy.__getattr__(self, name) + +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result) + if result['Status'] == 'Success': + if 'Value' in result: + return result['Value'] + else: + raise xmlrpclib.Fault(500, + 'Missing Value in response from server') + else: + if 'ErrorDescription' in result: + if result['ErrorDescription'][0] == 'SESSION_INVALID': + return _RECONNECT_AND_RETRY + else: + raise Failure(result['ErrorDescription']) + else: + raise xmlrpclib.Fault( + 500, 'Missing ErrorDescription in response from server') + + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name): + if self.__name is None: + return _Dispatcher(self.__send, name) + else: + return _Dispatcher(self.__send, "%s.%s" % (self.__name, name)) + + def __call__(self, *args): + return self.__send(self.__name, args)fence/agents/xenapi/fence_xenapi.py new file +227
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +# +############################################################################# +# Copyright 2011 Matt 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 +sys.path.append("/usr/lib/fence") +from fencing import * +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 options.has_key("-v"): + 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, 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): + action = options["-o"].lower() + if options.has_key("-v"): + 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"]): + if( action == "on" ): + # Start the VM + session.xenapi.VM.start(vm, False, True) + elif( action == "off" ): + # Force shutdown the VM + session.xenapi.VM.hard_shutdown(vm) + elif( action == "reboot" ): + # Force reboot the VM + session.xenapi.VM.hard_reboot(vm) + except Exception, exn: + print str(exn); + +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + 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, exn: + print str(exn); + + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] + + 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, 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 options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].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,exn: + if verbose: print "No VM's found with a UUID of \"%s\"" %uuid + raise + + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + 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 = [ "help", "version", "agent", "quiet", "verbose", "debug", "action", + "login", "passwd", "passwd_script", "port", "test", "separator", + "no_login", "no_password", "power_timeout", "shell_timeout", + "login_timeout", "power_wait", "session_url", "uuid" ] + + atexit.register(atexit_handler) + + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServer virtual machines." + 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 sesssion \ +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." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device + result = fence_action(xenSession, options, set_power_fn, get_power_fn, get_outlet_list) + + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
Hi Fabio,
Looks like I had a few problems moving the code over to git. Your comments all sound perfectly valid. Do you want me to fix up and resubmit? Cheers,
Matt.
Quoted 503 lines
Date: Mon, 11 Apr 2011 07:01:29 +0200
From: fdinitto at redhat.com
To: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP.Hi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.pybeing a fence library needs to move into the libs/ section. It is not parsed/built/installed in this patch set.You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of
RELEASE_VERSIONandBUILD_DATEat the end of the code forfence_xenapi. Those should be generated dynamically at build time to reflect the build info.Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Cheers
FabioQuoted 473 lines · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
--- fence/agents/xenapi/Makefile.am | 17 +++ fence/agents/xenapi/XenAPI.py | 209 ++++++++++++++++++++++++++++++++ fence/agents/xenapi/fence_xenapi.py | 227 +++++++++++++++++++++++++++++++++++ 3 files changed, 453 insertions(+), 0 deletions(-) create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755 fence/agents/xenapi/XenAPI.py create mode 100644 fence/agents/xenapi/fence_xenapi.pyfence/agents/xenapi/Makefile.am new file +17
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) + +man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)fence/agents/xenapi/XenAPI.py new file +209
-0@@ -0,0 +1,209 @@ +#============================================================================ +# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU Lesser General Public +# License as published by the Free Software Foundation. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#============================================================================ +# Copyright (C) 2006 XenSource Inc. +#============================================================================ +# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self): + return dict([(str(i), self.details[i]) + for i in range(len(self.details))]) + + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP over Unix domain + sockets. """ + def connect(self): + path = self.host.replace("_", "/") + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(path) + +class UDSHTTP(httplib.HTTP): + _connection_class = UDSHTTPConnection + +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xend using + the Xen-API. + + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support will disappear + once there is a working Xen-API replacement for every call in the legacy + API. + """ + + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in') + else: + return result + raise xmlrpclib.Fault( + 500, 'Tried 3 times to get a valid session, but failed') + + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' % method)(*params)) + if result == _RECONNECT_AND_RETRY: + raise xmlrpclib.Fault( + 500, 'Received SESSION_INVALID when logging in') + self._session = result + self.last_login_method = method + self.last_login_params = params + + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else: + return xmlrpclib.ServerProxy.__getattr__(self, name) + +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result) + if result['Status'] == 'Success': + if 'Value' in result: + return result['Value'] + else: + raise xmlrpclib.Fault(500, + 'Missing Value in response from server') + else: + if 'ErrorDescription' in result: + if result['ErrorDescription'][0] == 'SESSION_INVALID': + return _RECONNECT_AND_RETRY + else: + raise Failure(result['ErrorDescription']) + else: + raise xmlrpclib.Fault( + 500, 'Missing ErrorDescription in response from server') + + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name): + if self.__name is None: + return _Dispatcher(self.__send, name) + else: + return _Dispatcher(self.__send, "%s.%s" % (self.__name, name)) + + def __call__(self, *args): + return self.__send(self.__name, args)fence/agents/xenapi/fence_xenapi.py new file +219
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +# +############################################################################# +# Copyright 2011 Matt 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 +sys.path.append("/usr/lib/fence") +from fencing import * +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 options.has_key("-v"): + 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, 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): + action = options["-o"].lower() + if options.has_key("-v"): + 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"]): + if( action == "on" ): + # Start the VM + session.xenapi.VM.start(vm, False, True) + elif( action == "off" ): + # Force shutdown the VM + session.xenapi.VM.hard_shutdown(vm) + elif( action == "reboot" ): + # Force reboot the VM + session.xenapi.VM.hard_reboot(vm) + except Exception, exn: + print str(exn); + +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + 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, exn: + print str(exn); + + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] + + 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, 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 options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].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,exn: + if verbose: print "No VM's found with a UUID of \"%s\"" %uuid + raise + + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + 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 = [ "help", "version", "agent", "quiet", "verbose", "debug", "action", + "login", "passwd", "passwd_script", "port", "test", "separator", + "no_login", "no_password", "power_timeout", "shell_timeout", + "login_timeout", "power_wait", "session_url", "uuid" ] + + atexit.register(atexit_handler) + + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServer virtual machines." + 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 sesssion \ +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." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device
Quoted 9 lines
fence/agents/xenapi/fence_xenapi.py line 227
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+ result = fence_action(xenSession, options, set_power_fn, get_power_fn, get_outlet_list) + + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/cluster-devel/attachments/20110412/ec690c55/attachment.htm>
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
Hi Matt,
Quoted 3 lines · Matt Clark
Hi Fabio,
Looks like I had a few problems moving the code over to git.
git mv <file> <tofile>
Quoted 2 lines
Your
comments all sound perfectly valid. Do you want me to fix up and resubmit?
Let's wait for Marek feedback first and keep the changes local in the meantime. The build bits are easy to fix on the fly anyway.
Fabio
Quoted 574 lines
Cheers,
Matt.Quoted 4 lines
Date: Mon, 11 Apr 2011 07:01:29 +0200
From: fdinitto at redhat.com
To: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for CitrixXenServer and XCP.
Quoted 22 lines
Hi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.pybeing a fence library needs to move into the libs/ section. It is not parsed/built/installed in this patch set.You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of
RELEASE_VERSIONandBUILD_DATEat the end of the code forfence_xenapi. Those should be generated dynamically at build time to reflect the build info.Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Quoted 5 lines
Cheers
FabioQuoted 1 line · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status
and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
Quoted 4 lines
Quoted 4 lines
---
fence/agents/xenapi/Makefile.am | 17 +++fence/agents/xenapi/XenAPI.py| 209 ++++++++++++++++++++++++++++++++fence/agents/xenapi/fence_xenapi.py| 227+++++++++++++++++++++++++++++++++++
Quoted 6 lines
Review
Quoted 6 lines
3 files changed, 453 insertions(+), 0 deletions(-)create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755
fence/agents/xenapi/XenAPI.pycreate mode 100644fence/agents/xenapi/fence_xenapi.py(quoted diff) +0
-0b/fence/agents/xenapi/Makefile.am
Quoted 15 lines
Quoted 15 lines
new file mode 100644
index 0000000..781975efence/agents/xenapi/Makefile.am new file +10
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) +Quoted 8 lines
Review
Quoted 8 lines
fence/agents/xenapi/Makefile.am new file +7
-0+man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)(quoted diff) +0
-0
b/fence/agents/xenapi/XenAPI.pyQuoted 6 lines
fence/agents/xenapi/XenAPI.py
Quoted 6 lines
new file mode 100755
index 0000000..4f27ef5fence/agents/xenapi/XenAPI.py new file +0
-0@@ -0,0 +1,209 @@+#============================================================================
Quoted 2 lines
fence/agents/xenapi/XenAPI.py line 2
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU LesserGeneral Public
Quoted 10 lines
fence/agents/xenapi/XenAPI.py
Quoted 10 lines
fence/agents/xenapi/XenAPI.py new file +10
-0+# License as published by the Free Software Foundation. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
Quoted 1 line
Quoted 1 line
+#============================================================================
Quoted 2 lines
Quoted 2 lines
+# Copyright (C) 2006 XenSource Inc.
+#============================================================================
Quoted 63 lines
Quoted 63 lines
fence/agents/xenapi/XenAPI.py new file +63
-0+# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])fence/agents/xenapi/XenAPI.py new file +6
-0+ + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP overUnix domain
Quoted 7 lines
Quoted 7 lines
+ sockets. """
+ def connect(self):
+ path =self.host.replace("_", "/")
+ self.sock =socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+self.sock.connect(path)fence/agents/xenapi/XenAPI.py new file +2
-0+ +class UDSHTTP(httplib.HTTP):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+
_connection_class= UDSHTTPConnectionfence/agents/xenapi/XenAPI.py new file +7
-0+ +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xendusing
Quoted 11 lines
fence/agents/xenapi/XenAPI.py
Quoted 11 lines
+ the Xen-API.
fence/agents/xenapi/XenAPI.py new file +10
-0+ + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support willdisappear
Quoted 1 line
Quoted 1 line
+ once there is a working Xen-API replacement for every call in the
legacy
Quoted 50 lines
Quoted 50 lines
+ API.
+ """fence/agents/xenapi/XenAPI.py new file +48
-0+ + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in') + else: + return result + raise xmlrpclib.Fault( + 500, 'Tried 3 times to get a valid session, but failed') + + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' % method)(*params)) + if result == _RECONNECT_AND_RETRY: + raise xmlrpclib.Fault( + 500, 'Received SESSION_INVALID when logging in') + self._session = result + self.last_login_method = method + self.last_login_params = params + + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else:Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return
xmlrpclib.ServerProxy.__getattr__(self, name)fence/agents/xenapi/XenAPI.py new file +7
-0+ +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response fromserver' + result)
Quoted 30 lines
Quoted 30 lines
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return_RECONNECT_AND_RETRY
+ else:
+ raiseFailure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')fence/agents/xenapi/XenAPI.py new file +15
-0+ + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name):Quoted 8 lines
Review
Quoted 8 lines
+ if self.__name is None:
+ return_Dispatcher(self.__send, name)
+ else:
+ return_Dispatcher(self.__send, "%s.%s" % (self.__name, name))fence/agents/xenapi/XenAPI.py new file +3
-0+ + def __call__(self, *args): + return self.__send(self.__name, args)(quoted diff) +0
-0
b/fence/agents/xenapi/fence_xenapi.pyQuoted 8 lines
fence/agents/xenapi/fence_xenapi.py line 2
Quoted 8 lines
new file mode 100644
index 0000000..0657f4efence/agents/xenapi/fence_xenapi.py new file +2
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +#+#############################################################################
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py new file +4
-0+# Copyright 2011 Matt Clark +# This file is part of fence-xenserver +# +# fence-xenserver is free software: you can redistribute it and/ormodify
Quoted 5 lines
Quoted 5 lines
fence/agents/xenapi/fence_xenapi.py new file +5
-0+# 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,Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+# 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 canwork out
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py line 20
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py new file +1
-0+# whether I should continue support for it. mattjclark0407 athotmail dot com
Quoted 1 line
Quoted 1 line
+#############################################################################
Quoted 2 lines
Quoted 2 lines
+
+#############################################################################
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# It's only just begun... +# Current status: completely usable. This script is now workingwell and,
Quoted 1 line
Quoted 1 line
+# has a lot of functionality as a result of the
fencing.pylibraryand the
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py line 29
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# XenAPI libary. ++#############################################################################
Quoted 1 line
Quoted 1 line
+# Please let me know if you are using this script so that I can
work out
Quoted 1 line
Quoted 1 line
+# whether I should continue support for it. mattjclark0407 at
hotmail dot com
Quoted 8 lines
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+ +import sys +sys.path.append("/usr/lib/fence") +from fencing import * +import XenAPI + +EC_BAD_SESSION = 1 +# Find the status of the port given in the -U flag of options.Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+def get_power_fn(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + try: + # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 8 lines
Quoted 8 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:",record["name_label"], "POWER STATUS:", record["power_state"]
Quoted 1 line
Quoted 1 line
+ # Note that the VM can be in the following states (from the XenAPI
document)
Quoted 2 lines
Quoted 2 lines
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself ispaused and its vCPUs are not running
Quoted 2 lines
Quoted 2 lines
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolongerrunning. Note that disks remain in-Use while
Quoted 1 line
Quoted 1 line
+ # We want to make sure that we only return the status "off" if the
machine is actually halted as the status
Quoted 1 line
Quoted 1 line
+ # is checked before a fencing action. Only when the machine is
Halted is it not consuming resources which
Quoted 1 line
Quoted 1 line
+ # may include whatever you are trying to protect with this fencing
action.
Quoted 8 lines
Quoted 8 lines
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)fence/agents/xenapi/fence_xenapi.py new file +5
-0+ + return "Error" + +# Set the state of the port given in the -U flag of options. +def set_power_fn(session, options):Quoted 8 lines
Quoted 8 lines
+ action = options["-o"].lower()
+ ifoptions.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 32 lines
Quoted 32 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+if( action == "on" ):
+ # Start the VM
+session.xenapi.VM.start(vm, False, True)
+elif( action == "off" ):
+ # Force shutdown the VM
+session.xenapi.VM.hard_shutdown(vm)
+elif( action == "reboot" ):
+ # Force reboot the VM
+session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +15
-0+ +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + 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);Quoted 8 lines
Quoted 8 lines
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])andnot(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, "POWERSTATUS:", record["power_state"]
Quoted 11 lines
Quoted 11 lines
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +9
-0+ + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] +Quoted 8 lines
Quoted 8 lines
+ 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, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says thatfor no connectivity
Quoted 1 line
Quoted 1 line
+ # the exit value should be 1. It doesn't say anything about failed
logins, so
Quoted 1 line
Quoted 1 line
+ # until I hear otherwise it is best to keep this exit the same to
make sure that
Quoted 1 line
Quoted 1 line
+ # anything calling this script (that uses the same information in
the web page
Quoted 1 line
Quoted 1 line
+ # above) knows that this is an error condition, not a msg
signifying a down port.
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
+
sys.exit(EC_BAD_SESSION);
+ return session;fence/agents/xenapi/fence_xenapi.py new file +2
-0+ +# return a reference to the VM by either using the UUID or thevm_name/port. If the UUID is set then
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# this is tried first as this is the only properly unique identifier. +# Exceptions are not handled in this function, code that calls thismust be ready to handle them.
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py new file +10
-0+def return_vm_reference(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].lower() + # When using the -n parameter for name, we get an error message(in verbose
Quoted 1 line
Quoted 1 line
+ # mode) that tells us that we didn't find a VM. To immitate that
here we
Quoted 12 lines
Quoted 12 lines
+ # need to catch and re-raise the exception produced by
get_by_uuid.
+ try:
+ returnsession.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of \"%s\"" %uuid
+ raisefence/agents/xenapi/fence_xenapi.py new file +6
-0+ + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + vm_arr = session.xenapi.VM.get_by_name_label(vm_name)Quoted 8 lines
Quoted 8 lines
+ # Need to make sure that we only have one result as the
vm_namemay
+ # not be unique. Average case, so do it first.
+ iflen(vm_arr)== 1:
+ return vm_arr[0]
+ else:
+ iflen(vm_arr)== 0:
+ if verbose: print "No VM's found with a name of \"%s\"" %vm_name
+ #NAME_INVALIDused as the XenAPI throws aUUID_INVALIDif itcan't find
Quoted 1 line
Quoted 1 line
+ # a VM with the specified UUID. This should make the output look
fairly
Quoted 4 lines
Quoted 4 lines
+ # consistent.
+ raiseException("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUIDinstead" %vm_name
Quoted 3 lines
Quoted 3 lines
+ raise
Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checksthat either the UUID or
Quoted 1 line
Quoted 1 line
+ # the name parameter is set. Regardless of whether or not a VM is
found the above if
Quoted 1 line
Quoted 1 line
+ # statements will return to the calling function (either by
exception or by a reference
Quoted 6 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 6 lines
+ # to the VM).
+ raiseException("VM_LOGIC_ERROR")fence/agents/xenapi/fence_xenapi.py new file +4
-0+ +def main(): + + device_opt = [ "help", "version", "agent", "quiet", "verbose","debug", "action",
Quoted 4 lines
Quoted 4 lines
+ "login", "passwd", "
passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ] +Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
+
atexit.register(atexit_handler)fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServervirtual machines."
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 3 lines
+ docs["longdesc"] = "\
fence/agents/xenapi/fence_xenapi.py new file +2
-0+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \ +It uses the XenAPI, supplied by Citrix, to establish an XML-RPCsesssion \
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+to a XenServer host. Once the session is established, further XML-RPC \ +commands are issued in order to switch on, switch off, restart andquery \
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+the status of virtual machines running on the host." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device + result = fence_action(xenSession, options, set_power_fn,
get_power_fn,get_outlet_list)Quoted 8 lines
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
Hi,
Quoted 4 lines · Fabio M. Di Nitto
Quoted 2 lines
Your
comments all sound perfectly valid. Do you want me to fix up and resubmit?Let's wait for Marek feedback first and keep the changes local in the meantime. The build bits are easy to fix on the fly anyway.
Fence agent looks fine for me, Matt fixed my objections before.
m,
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
In reply to Matt Clarkhi Matt,
Marek said the code is fine, can you please resubmit with the build fixes?
Thanks
fabio
Quoted 578 lines · Matt Clark
Hi Fabio,
Looks like I had a few problems moving the code over to git. Your comments all sound perfectly valid. Do you want me to fix up and resubmit?
Cheers,
Matt.Quoted 4 lines
Date: Mon, 11 Apr 2011 07:01:29 +0200
From: fdinitto at redhat.com
To: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for CitrixXenServer and XCP.
Quoted 22 lines
Hi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.pybeing a fence library needs to move into the libs/ section. It is not parsed/built/installed in this patch set.You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of
RELEASE_VERSIONandBUILD_DATEat the end of the code forfence_xenapi. Those should be generated dynamically at build time to reflect the build info.Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Quoted 5 lines
Cheers
FabioQuoted 1 line · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status
and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
Quoted 4 lines
Quoted 4 lines
---
fence/agents/xenapi/Makefile.am | 17 +++fence/agents/xenapi/XenAPI.py| 209 ++++++++++++++++++++++++++++++++fence/agents/xenapi/fence_xenapi.py| 227+++++++++++++++++++++++++++++++++++
Quoted 6 lines
Review
Quoted 6 lines
3 files changed, 453 insertions(+), 0 deletions(-)create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755
fence/agents/xenapi/XenAPI.pycreate mode 100644fence/agents/xenapi/fence_xenapi.py(quoted diff) +0
-0b/fence/agents/xenapi/Makefile.am
Quoted 15 lines
Quoted 15 lines
new file mode 100644
index 0000000..781975efence/agents/xenapi/Makefile.am new file +10
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) +Quoted 8 lines
Review
Quoted 8 lines
fence/agents/xenapi/Makefile.am new file +7
-0+man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)(quoted diff) +0
-0
b/fence/agents/xenapi/XenAPI.pyQuoted 6 lines
fence/agents/xenapi/XenAPI.py
Quoted 6 lines
new file mode 100755
index 0000000..4f27ef5fence/agents/xenapi/XenAPI.py new file +0
-0@@ -0,0 +1,209 @@+#============================================================================
Quoted 2 lines
fence/agents/xenapi/XenAPI.py line 2
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU LesserGeneral Public
Quoted 10 lines
fence/agents/xenapi/XenAPI.py
Quoted 10 lines
fence/agents/xenapi/XenAPI.py new file +10
-0+# License as published by the Free Software Foundation. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
Quoted 1 line
Quoted 1 line
+#============================================================================
Quoted 2 lines
Quoted 2 lines
+# Copyright (C) 2006 XenSource Inc.
+#============================================================================
Quoted 63 lines
Quoted 63 lines
fence/agents/xenapi/XenAPI.py new file +63
-0+# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])fence/agents/xenapi/XenAPI.py new file +6
-0+ + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP overUnix domain
Quoted 7 lines
Quoted 7 lines
+ sockets. """
+ def connect(self):
+ path =self.host.replace("_", "/")
+ self.sock =socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+self.sock.connect(path)fence/agents/xenapi/XenAPI.py new file +2
-0+ +class UDSHTTP(httplib.HTTP):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+
_connection_class= UDSHTTPConnectionfence/agents/xenapi/XenAPI.py new file +7
-0+ +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xendusing
Quoted 11 lines
fence/agents/xenapi/XenAPI.py
Quoted 11 lines
+ the Xen-API.
fence/agents/xenapi/XenAPI.py new file +10
-0+ + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support willdisappear
Quoted 1 line
Quoted 1 line
+ once there is a working Xen-API replacement for every call in the
legacy
Quoted 50 lines
Quoted 50 lines
+ API.
+ """fence/agents/xenapi/XenAPI.py new file +48
-0+ + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in') + else: + return result + raise xmlrpclib.Fault( + 500, 'Tried 3 times to get a valid session, but failed') + + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' % method)(*params)) + if result == _RECONNECT_AND_RETRY: + raise xmlrpclib.Fault( + 500, 'Received SESSION_INVALID when logging in') + self._session = result + self.last_login_method = method + self.last_login_params = params + + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else:Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return
xmlrpclib.ServerProxy.__getattr__(self, name)fence/agents/xenapi/XenAPI.py new file +7
-0+ +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response fromserver' + result)
Quoted 30 lines
Quoted 30 lines
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return_RECONNECT_AND_RETRY
+ else:
+ raiseFailure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')fence/agents/xenapi/XenAPI.py new file +15
-0+ + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name):Quoted 8 lines
Review
Quoted 8 lines
+ if self.__name is None:
+ return_Dispatcher(self.__send, name)
+ else:
+ return_Dispatcher(self.__send, "%s.%s" % (self.__name, name))fence/agents/xenapi/XenAPI.py new file +3
-0+ + def __call__(self, *args): + return self.__send(self.__name, args)(quoted diff) +0
-0
b/fence/agents/xenapi/fence_xenapi.pyQuoted 8 lines
fence/agents/xenapi/fence_xenapi.py line 2
Quoted 8 lines
new file mode 100644
index 0000000..0657f4efence/agents/xenapi/fence_xenapi.py new file +2
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +#+#############################################################################
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py new file +4
-0+# Copyright 2011 Matt Clark +# This file is part of fence-xenserver +# +# fence-xenserver is free software: you can redistribute it and/ormodify
Quoted 5 lines
Quoted 5 lines
fence/agents/xenapi/fence_xenapi.py new file +5
-0+# 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,Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+# 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 canwork out
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py line 20
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py new file +1
-0+# whether I should continue support for it. mattjclark0407 athotmail dot com
Quoted 1 line
Quoted 1 line
+#############################################################################
Quoted 2 lines
Quoted 2 lines
+
+#############################################################################
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# It's only just begun... +# Current status: completely usable. This script is now workingwell and,
Quoted 1 line
Quoted 1 line
+# has a lot of functionality as a result of the
fencing.pylibraryand the
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py line 29
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# XenAPI libary. ++#############################################################################
Quoted 1 line
Quoted 1 line
+# Please let me know if you are using this script so that I can
work out
Quoted 1 line
Quoted 1 line
+# whether I should continue support for it. mattjclark0407 at
hotmail dot com
Quoted 8 lines
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+ +import sys +sys.path.append("/usr/lib/fence") +from fencing import * +import XenAPI + +EC_BAD_SESSION = 1 +# Find the status of the port given in the -U flag of options.Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+def get_power_fn(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + try: + # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 8 lines
Quoted 8 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:",record["name_label"], "POWER STATUS:", record["power_state"]
Quoted 1 line
Quoted 1 line
+ # Note that the VM can be in the following states (from the XenAPI
document)
Quoted 2 lines
Quoted 2 lines
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself ispaused and its vCPUs are not running
Quoted 2 lines
Quoted 2 lines
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolongerrunning. Note that disks remain in-Use while
Quoted 1 line
Quoted 1 line
+ # We want to make sure that we only return the status "off" if the
machine is actually halted as the status
Quoted 1 line
Quoted 1 line
+ # is checked before a fencing action. Only when the machine is
Halted is it not consuming resources which
Quoted 1 line
Quoted 1 line
+ # may include whatever you are trying to protect with this fencing
action.
Quoted 8 lines
Quoted 8 lines
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)fence/agents/xenapi/fence_xenapi.py new file +5
-0+ + return "Error" + +# Set the state of the port given in the -U flag of options. +def set_power_fn(session, options):Quoted 8 lines
Quoted 8 lines
+ action = options["-o"].lower()
+ ifoptions.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 32 lines
Quoted 32 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+if( action == "on" ):
+ # Start the VM
+session.xenapi.VM.start(vm, False, True)
+elif( action == "off" ):
+ # Force shutdown the VM
+session.xenapi.VM.hard_shutdown(vm)
+elif( action == "reboot" ):
+ # Force reboot the VM
+session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +15
-0+ +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + 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);Quoted 8 lines
Quoted 8 lines
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])andnot(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, "POWERSTATUS:", record["power_state"]
Quoted 11 lines
Quoted 11 lines
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +9
-0+ + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] +Quoted 8 lines
Quoted 8 lines
+ 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, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says thatfor no connectivity
Quoted 1 line
Quoted 1 line
+ # the exit value should be 1. It doesn't say anything about failed
logins, so
Quoted 1 line
Quoted 1 line
+ # until I hear otherwise it is best to keep this exit the same to
make sure that
Quoted 1 line
Quoted 1 line
+ # anything calling this script (that uses the same information in
the web page
Quoted 1 line
Quoted 1 line
+ # above) knows that this is an error condition, not a msg
signifying a down port.
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
+
sys.exit(EC_BAD_SESSION);
+ return session;fence/agents/xenapi/fence_xenapi.py new file +2
-0+ +# return a reference to the VM by either using the UUID or thevm_name/port. If the UUID is set then
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# this is tried first as this is the only properly unique identifier. +# Exceptions are not handled in this function, code that calls thismust be ready to handle them.
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py new file +10
-0+def return_vm_reference(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].lower() + # When using the -n parameter for name, we get an error message(in verbose
Quoted 1 line
Quoted 1 line
+ # mode) that tells us that we didn't find a VM. To immitate that
here we
Quoted 12 lines
Quoted 12 lines
+ # need to catch and re-raise the exception produced by
get_by_uuid.
+ try:
+ returnsession.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of \"%s\"" %uuid
+ raisefence/agents/xenapi/fence_xenapi.py new file +6
-0+ + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + vm_arr = session.xenapi.VM.get_by_name_label(vm_name)Quoted 8 lines
Quoted 8 lines
+ # Need to make sure that we only have one result as the
vm_namemay
+ # not be unique. Average case, so do it first.
+ iflen(vm_arr)== 1:
+ return vm_arr[0]
+ else:
+ iflen(vm_arr)== 0:
+ if verbose: print "No VM's found with a name of \"%s\"" %vm_name
+ #NAME_INVALIDused as the XenAPI throws aUUID_INVALIDif itcan't find
Quoted 1 line
Quoted 1 line
+ # a VM with the specified UUID. This should make the output look
fairly
Quoted 4 lines
Quoted 4 lines
+ # consistent.
+ raiseException("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUIDinstead" %vm_name
Quoted 3 lines
Quoted 3 lines
+ raise
Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checksthat either the UUID or
Quoted 1 line
Quoted 1 line
+ # the name parameter is set. Regardless of whether or not a VM is
found the above if
Quoted 1 line
Quoted 1 line
+ # statements will return to the calling function (either by
exception or by a reference
Quoted 6 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 6 lines
+ # to the VM).
+ raiseException("VM_LOGIC_ERROR")fence/agents/xenapi/fence_xenapi.py new file +4
-0+ +def main(): + + device_opt = [ "help", "version", "agent", "quiet", "verbose","debug", "action",
Quoted 4 lines
Quoted 4 lines
+ "login", "passwd", "
passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ] +Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
+
atexit.register(atexit_handler)fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServervirtual machines."
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 3 lines
+ docs["longdesc"] = "\
fence/agents/xenapi/fence_xenapi.py new file +2
-0+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \ +It uses the XenAPI, supplied by Citrix, to establish an XML-RPCsesssion \
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+to a XenServer host. Once the session is established, further XML-RPC \ +commands are issued in order to switch on, switch off, restart andquery \
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+the status of virtual machines running on the host." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device + result = fence_action(xenSession, options, set_power_fn,
get_power_fn,get_outlet_list)Quoted 8 lines
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
Sure, no problem.
Is there a way to uncommit the three patches so that I can merge everything into 1 patch, or do I need to add a 4th patch that fixes everything up? I did have a bit of a search, but the only relevant info I could find mentioned altering the most recent commit. Not rolling three commits back or merging multiple commits together. Sorry, I am used to using svn and git seems to be a bit of a different animal.
Quoted 6 lines
Date: Fri, 15 Apr 2011 10:13:11 +0200
From: fdinitto at redhat.com
To: mattjclark0407 at hotmail.com
CC: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP.
Quoted 587 lines
hi Matt,
Marek said the code is fine, can you please resubmit with the build fixes?
Thanks
fabioQuoted 578 lines · Matt Clark
Hi Fabio,
Looks like I had a few problems moving the code over to git. Your comments all sound perfectly valid. Do you want me to fix up and resubmit?
Cheers,
Matt.Quoted 4 lines
Date: Mon, 11 Apr 2011 07:01:29 +0200
From: fdinitto at redhat.com
To: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for CitrixXenServer and XCP.
Quoted 22 lines
Hi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.pybeing a fence library needs to move into the libs/ section. It is not parsed/built/installed in this patch set.You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of
RELEASE_VERSIONandBUILD_DATEat the end of the code forfence_xenapi. Those should be generated dynamically at build time to reflect the build info.Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Quoted 5 lines
Cheers
FabioQuoted 1 line · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status
and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
Quoted 4 lines
Quoted 4 lines
---
fence/agents/xenapi/Makefile.am | 17 +++fence/agents/xenapi/XenAPI.py| 209 ++++++++++++++++++++++++++++++++fence/agents/xenapi/fence_xenapi.py| 227+++++++++++++++++++++++++++++++++++
Quoted 6 lines
Review
Quoted 6 lines
3 files changed, 453 insertions(+), 0 deletions(-)create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755
fence/agents/xenapi/XenAPI.pycreate mode 100644fence/agents/xenapi/fence_xenapi.py(quoted diff) +0
-0b/fence/agents/xenapi/Makefile.am
Quoted 15 lines
Quoted 15 lines
new file mode 100644
index 0000000..781975efence/agents/xenapi/Makefile.am new file +10
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) +Quoted 8 lines
Review
Quoted 8 lines
fence/agents/xenapi/Makefile.am new file +7
-0+man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)(quoted diff) +0
-0
b/fence/agents/xenapi/XenAPI.pyQuoted 6 lines
fence/agents/xenapi/XenAPI.py
Quoted 6 lines
new file mode 100755
index 0000000..4f27ef5fence/agents/xenapi/XenAPI.py new file +0
-0@@ -0,0 +1,209 @@+#============================================================================
Quoted 2 lines
fence/agents/xenapi/XenAPI.py line 2
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU LesserGeneral Public
Quoted 10 lines
fence/agents/xenapi/XenAPI.py
Quoted 10 lines
fence/agents/xenapi/XenAPI.py new file +10
-0+# License as published by the Free Software Foundation. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
Quoted 1 line
Quoted 1 line
+#============================================================================
Quoted 2 lines
Quoted 2 lines
+# Copyright (C) 2006 XenSource Inc.
+#============================================================================
Quoted 63 lines
Quoted 63 lines
fence/agents/xenapi/XenAPI.py new file +63
-0+# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])fence/agents/xenapi/XenAPI.py new file +6
-0+ + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP overUnix domain
Quoted 7 lines
Quoted 7 lines
+ sockets. """
+ def connect(self):
+ path =self.host.replace("_", "/")
+ self.sock =socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+self.sock.connect(path)fence/agents/xenapi/XenAPI.py new file +2
-0+ +class UDSHTTP(httplib.HTTP):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+
_connection_class= UDSHTTPConnectionfence/agents/xenapi/XenAPI.py new file +7
-0+ +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xendusing
Quoted 11 lines
fence/agents/xenapi/XenAPI.py
Quoted 11 lines
+ the Xen-API.
fence/agents/xenapi/XenAPI.py new file +10
-0+ + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support willdisappear
Quoted 1 line
Quoted 1 line
+ once there is a working Xen-API replacement for every call in the
legacy
Quoted 50 lines
Quoted 50 lines
+ API.
+ """fence/agents/xenapi/XenAPI.py new file +48
-0+ + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in') + else: + return result + raise xmlrpclib.Fault( + 500, 'Tried 3 times to get a valid session, but failed') + + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' % method)(*params)) + if result == _RECONNECT_AND_RETRY: + raise xmlrpclib.Fault( + 500, 'Received SESSION_INVALID when logging in') + self._session = result + self.last_login_method = method + self.last_login_params = params + + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else:Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return
xmlrpclib.ServerProxy.__getattr__(self, name)fence/agents/xenapi/XenAPI.py new file +7
-0+ +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response fromserver' + result)
Quoted 30 lines
Quoted 30 lines
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return_RECONNECT_AND_RETRY
+ else:
+ raiseFailure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')fence/agents/xenapi/XenAPI.py new file +15
-0+ + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name):Quoted 8 lines
Review
Quoted 8 lines
+ if self.__name is None:
+ return_Dispatcher(self.__send, name)
+ else:
+ return_Dispatcher(self.__send, "%s.%s" % (self.__name, name))fence/agents/xenapi/XenAPI.py new file +3
-0+ + def __call__(self, *args): + return self.__send(self.__name, args)(quoted diff) +0
-0
b/fence/agents/xenapi/fence_xenapi.pyQuoted 8 lines
fence/agents/xenapi/fence_xenapi.py line 2
Quoted 8 lines
new file mode 100644
index 0000000..0657f4efence/agents/xenapi/fence_xenapi.py new file +2
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +#+#############################################################################
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py new file +4
-0+# Copyright 2011 Matt Clark +# This file is part of fence-xenserver +# +# fence-xenserver is free software: you can redistribute it and/ormodify
Quoted 5 lines
Quoted 5 lines
fence/agents/xenapi/fence_xenapi.py new file +5
-0+# 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,Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+# 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 canwork out
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py line 20
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py new file +1
-0+# whether I should continue support for it. mattjclark0407 athotmail dot com
Quoted 1 line
Quoted 1 line
+#############################################################################
Quoted 2 lines
Quoted 2 lines
+
+#############################################################################
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# It's only just begun... +# Current status: completely usable. This script is now workingwell and,
Quoted 1 line
Quoted 1 line
+# has a lot of functionality as a result of the
fencing.pylibraryand the
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py line 29
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# XenAPI libary. ++#############################################################################
Quoted 1 line
Quoted 1 line
+# Please let me know if you are using this script so that I can
work out
Quoted 1 line
Quoted 1 line
+# whether I should continue support for it. mattjclark0407 at
hotmail dot com
Quoted 8 lines
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+ +import sys +sys.path.append("/usr/lib/fence") +from fencing import * +import XenAPI + +EC_BAD_SESSION = 1 +# Find the status of the port given in the -U flag of options.Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+def get_power_fn(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + try: + # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 8 lines
Quoted 8 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:",record["name_label"], "POWER STATUS:", record["power_state"]
Quoted 1 line
Quoted 1 line
+ # Note that the VM can be in the following states (from the XenAPI
document)
Quoted 2 lines
Quoted 2 lines
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself ispaused and its vCPUs are not running
Quoted 2 lines
Quoted 2 lines
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolongerrunning. Note that disks remain in-Use while
Quoted 1 line
Quoted 1 line
+ # We want to make sure that we only return the status "off" if the
machine is actually halted as the status
Quoted 1 line
Quoted 1 line
+ # is checked before a fencing action. Only when the machine is
Halted is it not consuming resources which
Quoted 1 line
Quoted 1 line
+ # may include whatever you are trying to protect with this fencing
action.
Quoted 8 lines
Quoted 8 lines
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)fence/agents/xenapi/fence_xenapi.py new file +5
-0+ + return "Error" + +# Set the state of the port given in the -U flag of options. +def set_power_fn(session, options):Quoted 8 lines
Quoted 8 lines
+ action = options["-o"].lower()
+ ifoptions.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 32 lines
Quoted 32 lines
+ 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.
+ ifnot(record["is_a_template"])andnot(record["is_control_domain"]):
+if( action == "on" ):
+ # Start the VM
+session.xenapi.VM.start(vm, False, True)
+elif( action == "off" ):
+ # Force shutdown the VM
+session.xenapi.VM.hard_shutdown(vm)
+elif( action == "reboot" ):
+ # Force reboot the VM
+session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +15
-0+ +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + 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);Quoted 8 lines
Quoted 8 lines
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])andnot(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, "POWERSTATUS:", record["power_state"]
Quoted 11 lines
Quoted 11 lines
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +9
-0+ + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] +Quoted 8 lines
Quoted 8 lines
+ 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, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says thatfor no connectivity
Quoted 1 line
Quoted 1 line
+ # the exit value should be 1. It doesn't say anything about failed
logins, so
Quoted 1 line
Quoted 1 line
+ # until I hear otherwise it is best to keep this exit the same to
make sure that
Quoted 1 line
Quoted 1 line
+ # anything calling this script (that uses the same information in
the web page
Quoted 1 line
Quoted 1 line
+ # above) knows that this is an error condition, not a msg
signifying a down port.
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
+
sys.exit(EC_BAD_SESSION);
+ return session;fence/agents/xenapi/fence_xenapi.py new file +2
-0+ +# return a reference to the VM by either using the UUID or thevm_name/port. If the UUID is set then
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# this is tried first as this is the only properly unique identifier. +# Exceptions are not handled in this function, code that calls thismust be ready to handle them.
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py new file +10
-0+def return_vm_reference(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].lower() + # When using the -n parameter for name, we get an error message(in verbose
Quoted 1 line
Quoted 1 line
+ # mode) that tells us that we didn't find a VM. To immitate that
here we
Quoted 12 lines
Quoted 12 lines
+ # need to catch and re-raise the exception produced by
get_by_uuid.
+ try:
+ returnsession.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of \"%s\"" %uuid
+ raisefence/agents/xenapi/fence_xenapi.py new file +6
-0+ + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + vm_arr = session.xenapi.VM.get_by_name_label(vm_name)Quoted 8 lines
Quoted 8 lines
+ # Need to make sure that we only have one result as the
vm_namemay
+ # not be unique. Average case, so do it first.
+ iflen(vm_arr)== 1:
+ return vm_arr[0]
+ else:
+ iflen(vm_arr)== 0:
+ if verbose: print "No VM's found with a name of \"%s\"" %vm_name
+ #NAME_INVALIDused as the XenAPI throws aUUID_INVALIDif itcan't find
Quoted 1 line
Quoted 1 line
+ # a VM with the specified UUID. This should make the output look
fairly
Quoted 4 lines
Quoted 4 lines
+ # consistent.
+ raiseException("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUIDinstead" %vm_name
Quoted 3 lines
Quoted 3 lines
+ raise
Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checksthat either the UUID or
Quoted 1 line
Quoted 1 line
+ # the name parameter is set. Regardless of whether or not a VM is
found the above if
Quoted 1 line
Quoted 1 line
+ # statements will return to the calling function (either by
exception or by a reference
Quoted 6 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 6 lines
+ # to the VM).
+ raiseException("VM_LOGIC_ERROR")fence/agents/xenapi/fence_xenapi.py new file +4
-0+ +def main(): + + device_opt = [ "help", "version", "agent", "quiet", "verbose","debug", "action",
Quoted 4 lines
Quoted 4 lines
+ "login", "passwd", "
passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ] +Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
+
atexit.register(atexit_handler)fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServervirtual machines."
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 3 lines
+ docs["longdesc"] = "\
fence/agents/xenapi/fence_xenapi.py new file +2
-0+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \ +It uses the XenAPI, supplied by Citrix, to establish an XML-RPCsesssion \
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+to a XenServer host. Once the session is established, further XML-RPC \ +commands are issued in order to switch on, switch off, restart andquery \
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+the status of virtual machines running on the host." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device + result = fence_action(xenSession, options, set_power_fn,
get_power_fn,get_outlet_list)Quoted 8 lines
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/cluster-devel/attachments/20110417/b30e13e2/attachment.htm>
PATCH 1/3 New fencing script for Citrix XenServer and XCP.
There are many ways to do it, but honestly, I don't have time to write a full git guideline ;)
Easier way is to do a new clone from upstream and work on that one.
It doesn't matter to me if it comes in 1 or 5 patches. Whatever you prefer is fine.
Fabio
Quoted 636 lines · Matt Clark
Sure, no problem.
Is there a way to uncommit the three patches so that I can merge everything into 1 patch, or do I need to add a 4th patch that fixes everything up? I did have a bit of a search, but the only relevant info I could find mentioned altering the most recent commit. Not rolling three commits back or merging multiple commits together.
Sorry, I am used to using svn and git seems to be a bit of a different animal.
Quoted 5 lines
Date: Fri, 15 Apr 2011 10:13:11 +0200
From: fdinitto at redhat.com
To: mattjclark0407 at hotmail.com
CC: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for CitrixXenServer and XCP.
Quoted 13 lines
hi Matt,
Marek said the code is fine, can you please resubmit with the build fixes?
Thanks
fabioQuoted 4 lines · Matt Clark
Hi Fabio,
Looks like I had a few problems moving the code over to git. Your comments all sound perfectly valid. Do you want me to fix up and
resubmit?
Quoted 8 lines
Quoted 8 lines
Cheers,
Matt.Quoted 4 lines
Date: Mon, 11 Apr 2011 07:01:29 +0200
From: fdinitto at redhat.com
To: cluster-devel at redhat.com; mgrac at redhat.com
Subject: Re: [Cluster-devel] [PATCH 1/3] New fencing script for CitrixQuoted 8 lines
Quoted 8 lines
XenServer and XCP.
Quoted 7 lines
Hi Matt,
I'll leave the comments to the code to Marek, but we will need to do a few adjustments here and there to get this into the tree.
XenAPI.pybeing a fence library needs to move into the libs/section. It
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
is not parsed/built/installed in this patch set.
You sent us pre-built sources and that's not really ok. See for example the hardcoded path to the fence library and the addition of
RELEASE_VERSIONandBUILD_DATEat the end of the code forfence_xenapi. Those should be generated dynamically at build time to reflect thebuild
Quoted 52 lines
Quoted 52 lines
Quoted 9 lines
info.
Because I really like to make sure that COPYRIGHT and licences information are as exact and clear as possible, please also update docs/COPYRIGHT in the "exception" area. While the information in the file are absolutely fine, most people tend to look for a COPYRIGHT/LICENCE file rather than editing the file itself. It's fair that your contribution deserves the right credit and such.
Quoted 5 lines
Cheers
FabioQuoted 1 line · Matt Clark
Fencing script that uses the XenAPI to allow remote switch, status
and list of virtual machines running on Citrix XenServer and Xen Cloud Platform hosts.
Quoted 4 lines
Quoted 4 lines
---
fence/agents/xenapi/Makefile.am | 17 +++fence/agents/xenapi/XenAPI.py| 209 ++++++++++++++++++++++++++++++++fence/agents/xenapi/fence_xenapi.py| 227+++++++++++++++++++++++++++++++++++
Quoted 6 lines
Review
Quoted 6 lines
3 files changed, 453 insertions(+), 0 deletions(-)create mode 100644 fence/agents/xenapi/Makefile.am create mode 100755
fence/agents/xenapi/XenAPI.pycreate mode 100644fence/agents/xenapi/fence_xenapi.py(quoted diff) +0
-0b/fence/agents/xenapi/Makefile.am
Quoted 15 lines
Quoted 15 lines
new file mode 100644
index 0000000..781975efence/agents/xenapi/Makefile.am new file +10
-0@@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_xenapi + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) +Quoted 8 lines
Review
Quoted 8 lines
fence/agents/xenapi/Makefile.am new file +7
-0+man_MANS = $(TARGET).8 + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk + +clean-local: clean-man + rm -f $(TARGET)(quoted diff) +0
-0
b/fence/agents/xenapi/XenAPI.pyQuoted 7 lines
Quoted 7 lines
Quoted 6 lines
fence/agents/xenapi/XenAPI.py
Quoted 6 lines
new file mode 100755
index 0000000..4f27ef5fence/agents/xenapi/XenAPI.py new file +0
-0@@ -0,0 +1,209 @@+#============================================================================
Quoted 12 lines
Quoted 12 lines
Quoted 2 lines
fence/agents/xenapi/XenAPI.py line 2
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU LesserGeneral Public
Quoted 9 lines
fence/agents/xenapi/XenAPI.py
Quoted 9 lines
fence/agents/xenapi/XenAPI.py new file +9
-0+# License as published by the Free Software Foundation. +# +# 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, write to the FreeSoftware
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
fence/agents/xenapi/XenAPI.py line 12
Quoted 1 line
fence/agents/xenapi/XenAPI.py new file +1
-0+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
Quoted 1 line
Quoted 1 line
+#============================================================================
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
Quoted 2 lines
+# Copyright (C) 2006 XenSource Inc.
+#============================================================================
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
fence/agents/xenapi/XenAPI.py
Quoted 9 lines
fence/agents/xenapi/XenAPI.py new file +9
-0+# +# Parts of this file are based upon xmlrpclib.py, the XML-RPC client +# interface included in the Python distribution. +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read,understood,
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
fence/agents/xenapi/XenAPI.py
Quoted 5 lines
fence/agents/xenapi/XenAPI.py new file +5
-0+# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright noticeappears in
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
+# all copies, and that both that copyright notice and this
permission
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
fence/agents/xenapi/XenAPI.py
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising orpublicity
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
+# pertaining to distribution of the software without specific,
written
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
fence/agents/xenapi/XenAPI.py
Quoted 5 lines
fence/agents/xenapi/XenAPI.py new file +5
-0+# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THEAUTHOR
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
OR ANY
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
fence/agents/xenapi/XenAPI.py
Quoted 3 lines
fence/agents/xenapi/XenAPI.py new file +3
-0+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE ORPERFORMANCE
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
fence/agents/xenapi/XenAPI.py
Quoted 2 lines
fence/agents/xenapi/XenAPI.py new file +2
-0+# OF THIS SOFTWARE. +#--------------------------------------------------------------------
Quoted 101 lines
Quoted 101 lines
Quoted 34 lines
Quoted 34 lines
fence/agents/xenapi/XenAPI.py new file +34
-0+ +import gettext +import xmlrpclib +import httplib +import socket + +translation = gettext.translation('xen-xm', fallback = True) + +class Failure(Exception): + def __init__(self, details): + try: + # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we + # correct the return values here, to account for the fact that we + # transparently add the session handle as the first argument. + if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH': + details[2] = str(int(details[2]) - 1) + details[3] = str(int(details[3]) - 1) + + self.details = details + except Exception, exn: + self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)] + + def __str__(self): + try: + return translation.ugettext(self.details[0]) % self._details_map() + except TypeError, exn: + return "Message database broken: %s.\nXen-API failure: %s" % \ + (exn, str(self.details)) + except Exception, exn: + import sys + print >>sys.stderr, exn + return "Xen-API failure: %s" % str(self.details) + + def _details_map(self):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])fence/agents/xenapi/XenAPI.py new file +6
-0+ + +_RECONNECT_AND_RETRY = (lambda _ : ()) + +class UDSHTTPConnection(httplib.HTTPConnection): + """ Stupid hacked up HTTPConnection subclass to allow HTTP overUnix domain
Quoted 7 lines
Quoted 7 lines
+ sockets. """
+ def connect(self):
+ path =self.host.replace("_", "/")
+ self.sock =socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+self.sock.connect(path)fence/agents/xenapi/XenAPI.py new file +2
-0+ +class UDSHTTP(httplib.HTTP):Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+
_connection_class= UDSHTTPConnectionfence/agents/xenapi/XenAPI.py new file +7
-0+ +class UDSTransport(xmlrpclib.Transport): + def make_connection(self, host): + return UDSHTTP(host) + +class Session(xmlrpclib.ServerProxy): + """A server proxy and session manager for communicating with Xendusing
Quoted 11 lines
fence/agents/xenapi/XenAPI.py
Quoted 11 lines
+ the Xen-API.
fence/agents/xenapi/XenAPI.py new file +10
-0+ + Example: + + session = Session('http://localhost:9363/') + session.login_with_password('me', 'mypassword') + session.xenapi.VM.start(vm_uuid) + session.xenapi.session.logout() + + For now, this class also supports the legacy XML-RPC API, using + session.xend.domain('Domain-0') and similar. This support willdisappear
Quoted 1 line
Quoted 1 line
+ once there is a working Xen-API replacement for every call in the
legacy
Quoted 28 lines
Quoted 28 lines
+ API.
+ """fence/agents/xenapi/XenAPI.py new file +26
-0+ + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + self._session = None + self.last_login_method = None + self.last_login_params = None + + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + else: + retry_count = 0 + while retry_count < 3: + full_params = (self._session,) + params + result = _parse_result(getattr(self, methodname)(*full_params)) + if result == _RECONNECT_AND_RETRY: + retry_count += 1 + if self.last_login_method: + self._login(self.last_login_method, + self.last_login_params) + else: + raise xmlrpclib.Fault(401, 'You must log in')Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ else:
+ return result
+ raise xmlrpclib.Fault(
+ 500, 'Tried 3 times to get a valid session, but failed')fence/agents/xenapi/XenAPI.py new file +4
-0+ + + def _login(self, method, params): + result = _parse_result(getattr(self, 'session.%s' %method)(*params))
Quoted 62 lines
Quoted 62 lines
Quoted 14 lines
Quoted 14 lines
+ if result ==
_RECONNECT_AND_RETRY:
+ raise xmlrpclib.Fault(
+ 500, 'ReceivedSESSION_INVALIDwhen logging in')
+ self._session = result
+ self.last_login_method = method
+ self.last_login_params = paramsfence/agents/xenapi/XenAPI.py new file +8
-0+ + + def __getattr__(self, name): + if name == 'xenapi': + return _Dispatcher(self.xenapi_request, None) + elif name.startswith('login'): + return lambda *params: self._login(name, params) + else:Quoted 8 lines
fence/agents/xenapi/XenAPI.py
Quoted 8 lines
+ return
xmlrpclib.ServerProxy.__getattr__(self, name)fence/agents/xenapi/XenAPI.py new file +7
-0+ +def xapi_local(): + return Session("http://_var_xapi_xapi/", transport=UDSTransport()) + +def _parse_result(result): + if type(result) != dict or 'Status' not in result: + raise xmlrpclib.Fault(500, 'Missing Status in response fromserver' + result)
Quoted 30 lines
Quoted 30 lines
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return_RECONNECT_AND_RETRY
+ else:
+ raiseFailure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')fence/agents/xenapi/XenAPI.py new file +15
-0+ + +# Based upon _Method from xmlrpclib. +class _Dispatcher: + def __init__(self, send, name): + self.__send = send + self.__name = name + + def __repr__(self): + if self.__name: + return '<XenAPI._Dispatcher for %s>' % self.__name + else: + return '<XenAPI._Dispatcher>' + + def __getattr__(self, name):Quoted 8 lines
Review
Quoted 8 lines
+ if self.__name is None:
+ return_Dispatcher(self.__send, name)
+ else:
+ return_Dispatcher(self.__send, "%s.%s" % (self.__name, name))fence/agents/xenapi/XenAPI.py new file +3
-0+ + def __call__(self, *args): + return self.__send(self.__name, args)(quoted diff) +0
-0
b/fence/agents/xenapi/fence_xenapi.pyQuoted 9 lines
Quoted 9 lines
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py line 2
Quoted 8 lines
new file mode 100644
index 0000000..0657f4efence/agents/xenapi/fence_xenapi.py new file +2
-0@@ -0,0 +1,227 @@ +#!/usr/bin/python +#+#############################################################################
Quoted 6 lines
Quoted 6 lines
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py new file +4
-0+# Copyright 2011 Matt Clark +# This file is part of fence-xenserver +# +# fence-xenserver is free software: you can redistribute it and/ormodify
Quoted 1 line
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py new file +1
-0+# it under the terms of the GNU General Public License aspublished by
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py new file +10
-0+# 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/>.
Quoted 7 lines
Quoted 7 lines
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py line 19
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+ +# Please let me know if you are using this script so that I canwork out
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py line 20
Quoted 1 line
fence/agents/xenapi/fence_xenapi.py new file +1
-0+# whether I should continue support for it. mattjclark0407 athotmail dot com
Quoted 1 line
Quoted 1 line
+#############################################################################
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
Quoted 2 lines
+
+#############################################################################
Quoted 9 lines
Quoted 9 lines
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 2 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# It's only just begun... +# Current status: completely usable. This script is now workingwell and,
Quoted 1 line
Quoted 1 line
+# has a lot of functionality as a result of the
fencing.pylibraryand the
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py line 29
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py new file +2
-0+# XenAPI libary. ++#############################################################################
Quoted 21 lines
Quoted 21 lines
Quoted 1 line
Quoted 1 line
+# Please let me know if you are using this script so that I can
work out
Quoted 1 line
Quoted 1 line
+# whether I should continue support for it. mattjclark0407 at
hotmail dot com
Quoted 8 lines
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+ +import sys +sys.path.append("/usr/lib/fence") +from fencing import * +import XenAPI + +EC_BAD_SESSION = 1 +# Find the status of the port given in the -U flag of options.Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py new file +8
-0+def get_power_fn(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + try: + # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
+ 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 acontrol
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])and
not(record["is_control_domain"]):Quoted 34 lines
Quoted 34 lines
Quoted 2 lines
Quoted 2 lines
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:",record["name_label"], "POWER STATUS:", record["power_state"]
Quoted 1 line
Quoted 1 line
+ # Note that the VM can be in the following states (from the XenAPI
document)
Quoted 2 lines
Quoted 2 lines
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself ispaused and its vCPUs are not running
Quoted 2 lines
Quoted 2 lines
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolongerrunning. Note that disks remain in-Use while
Quoted 1 line
Quoted 1 line
+ # We want to make sure that we only return the status "off" if the
machine is actually halted as the status
Quoted 1 line
Quoted 1 line
+ # is checked before a fencing action. Only when the machine is
Halted is it not consuming resources which
Quoted 1 line
Quoted 1 line
+ # may include whatever you are trying to protect with this fencing
action.
Quoted 8 lines
Quoted 8 lines
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)fence/agents/xenapi/fence_xenapi.py new file +5
-0+ + return "Error" + +# Set the state of the port given in the -U flag of options. +def set_power_fn(session, options):Quoted 8 lines
Quoted 8 lines
+ action = options["-o"].lower()
+ ifoptions.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/portparameter
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
+ 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 acontrol
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])and
not(record["is_control_domain"]):Quoted 19 lines
Quoted 19 lines
Quoted 19 lines
Quoted 19 lines
+
if( action == "on" ):
+ # Start the VM
+session.xenapi.VM.start(vm, False, True)
+elif( action == "off" ):
+ # Force shutdown the VM
+session.xenapi.VM.hard_shutdown(vm)
+elif( action == "reboot" ):
+ # Force reboot the VM
+session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +8
-0+ +# Function to populate an array of virtual machines and their status +def get_outlet_list(session, options): + result = {} + if options.has_key("-v"): + verbose = True + else: + verbose = FalseQuoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
+
+ 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 acontrol
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ # domain as they show up as VM's with specific properties.
+ ifnot(record["is_a_template"])and
not(record["is_control_domain"]):Quoted 32 lines
Quoted 32 lines
Quoted 5 lines
Quoted 5 lines
+ name = record["name_label"]
+ uuid = record["uuid"]
+ status = record["power_state"]
+ result[uuid] = (name, status)
+ if verbose: print "UUID:", record["uuid"], "NAME:", name, "POWERSTATUS:", record["power_state"]
Quoted 11 lines
Quoted 11 lines
+ except Exception, exn:
+ print str(exn);fence/agents/xenapi/fence_xenapi.py new file +9
-0+ + return result + +# Function to initiate the XenServer session via the XenAPI library. +def connect_and_login(options): + url = options["-s"] + username = options["-l"] + password = options["-p"] +Quoted 8 lines
Quoted 8 lines
+ 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, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says thatfor no connectivity
Quoted 1 line
Quoted 1 line
+ # the exit value should be 1. It doesn't say anything about failed
logins, so
Quoted 1 line
Quoted 1 line
+ # until I hear otherwise it is best to keep this exit the same to
make sure that
Quoted 1 line
Quoted 1 line
+ # anything calling this script (that uses the same information in
the web page
Quoted 8 lines
Quoted 8 lines
Quoted 1 line
Quoted 1 line
+ # above) knows that this is an error condition, not a msg
signifying a down port.
Quoted 4 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 4 lines
+
sys.exit(EC_BAD_SESSION);
+ return session;fence/agents/xenapi/fence_xenapi.py new file +2
-0+ +# return a reference to the VM by either using the UUID or thevm_name/port. If the UUID is set then
Quoted 1 line
Quoted 1 line
+# this is tried first as this is the only properly unique
identifier.
Quoted 71 lines
Quoted 71 lines
Quoted 1 line
Quoted 1 line
+# Exceptions are not handled in this function, code that calls this
must be ready to handle them.
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 10 lines
fence/agents/xenapi/fence_xenapi.py new file +10
-0+def return_vm_reference(session, options): + if options.has_key("-v"): + verbose = True + else: + verbose = False + + # Case where the UUID has been specified + if options.has_key("-U"): + uuid = options["-U"].lower() + # When using the -n parameter for name, we get an error message(in verbose
Quoted 1 line
Quoted 1 line
+ # mode) that tells us that we didn't find a VM. To immitate that
here we
Quoted 12 lines
Quoted 12 lines
+ # need to catch and re-raise the exception produced by
get_by_uuid.
+ try:
+ returnsession.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of \"%s\"" %uuid
+ raisefence/agents/xenapi/fence_xenapi.py new file +6
-0+ + + # Case where the vm_name/port has been specified + if options.has_key("-n"): + vm_name = options["-n"] + vm_arr = session.xenapi.VM.get_by_name_label(vm_name)Quoted 8 lines
Quoted 8 lines
+ # Need to make sure that we only have one result as the
vm_namemay
+ # not be unique. Average case, so do it first.
+ iflen(vm_arr)== 1:
+ return vm_arr[0]
+ else:
+ iflen(vm_arr)== 0:
+ if verbose: print "No VM's found with a name of \"%s\"" %vm_name
+ #NAME_INVALIDused as the XenAPI throws aUUID_INVALIDif itcan't find
Quoted 1 line
Quoted 1 line
+ # a VM with the specified UUID. This should make the output look
fairly
Quoted 4 lines
Quoted 4 lines
+ # consistent.
+ raiseException("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUIDinstead" %vm_name
Quoted 3 lines
Quoted 3 lines
+ raise
Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checksthat either the UUID or
Quoted 1 line
Quoted 1 line
+ # the name parameter is set. Regardless of whether or not a VM is
found the above if
Quoted 1 line
Quoted 1 line
+ # statements will return to the calling function (either by
exception or by a reference
Quoted 6 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 6 lines
+ # to the VM).
+ raiseException("VM_LOGIC_ERROR")fence/agents/xenapi/fence_xenapi.py new file +4
-0+ +def main(): + + device_opt = [ "help", "version", "agent", "quiet", "verbose","debug", "action",
Quoted 4 lines
Quoted 4 lines
+ "login", "passwd", "
passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ] +Quoted 8 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 8 lines
+
atexit.register(atexit_handler)fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + options=process_input(device_opt) + + options = check_input(device_opt, options) + + docs = { } + docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServervirtual machines."
Quoted 5 lines
Quoted 5 lines
Quoted 3 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 3 lines
+ docs["longdesc"] = "\
fence/agents/xenapi/fence_xenapi.py new file +2
-0+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \ +It uses the XenAPI, supplied by Citrix, to establish an XML-RPCsesssion \
Quoted 1 line
Quoted 1 line
+to a XenServer host. Once the session is established, further
XML-RPC \
Quoted 19 lines
Quoted 18 lines
Quoted 1 line
Quoted 1 line
+commands are issued in order to switch on, switch off, restart and
query \
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+the status of virtual machines running on the host." + show_docs(options, docs) + + xenSession = connect_and_login(options) + + # Operate the fencing device + result = fence_action(xenSession, options, set_power_fn,
get_power_fn,get_outlet_list)Quoted 8 lines
Quoted 7 lines
fence/agents/xenapi/fence_xenapi.py new file +7
-0+ + sys.exit(result) + +if __name__ == "__main__": + main() +RELEASE_VERSION="3.1.2.11-2b5b-dirty" +BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"