[Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP.

Fabio M. Di Nitto fdinitto at redhat.com
Tue Apr 12 04:04:50 UTC 2011


Hi Matt,

On 04/12/2011 04:46 AM, Matt Clark wrote:
> Hi Fabio,
> 
> Looks like I had a few problems moving the code over to git.

git mv <file> <tofile>

> 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

> 
> Cheers,
> Matt.
> 
>> 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.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
>>
>> On 04/09/2011 06:54 AM, Matt Clark wrote:
>> > 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
>> >
>> > diff --git a/fence/agents/xenapi/Makefile.am
> b/fence/agents/xenapi/Makefile.am
>> > new file mode 100644
>> > index 0000000..781975e
>> > --- /dev/null
>> > +++ b/fence/agents/xenapi/Makefile.am
>> > @@ -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)
>> > diff --git a/fence/agents/xenapi/XenAPI.py
> b/fence/agents/xenapi/XenAPI.py
>> > new file mode 100755
>> > index 0000000..4f27ef5
>> > --- /dev/null
>> > +++ b/fence/agents/xenapi/XenAPI.py
>> > @@ -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)
>> > diff --git a/fence/agents/xenapi/fence_xenapi.py
> b/fence/agents/xenapi/fence_xenapi.py
>> > new file mode 100644
>> > index 0000000..0657f4e
>> > --- /dev/null
>> > +++ b/fence/agents/xenapi/fence_xenapi.py
>> > @@ -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)"
>>




More information about the Cluster-devel mailing list