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.

> Date: Mon, 11 Apr 2011 07:01:29 +0200
> From: [email protected]
> To: [email protected]; [email protected]
> 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)"
> 
                                          

Reply via email to