From: Fabio M. Di Nitto <fdinitto@redhat.com>
To: cluster-devel.redhat.com
Subject: [Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP.
Date: Mon, 11 Apr 2011 07:01:29 +0200 [thread overview]
Message-ID: <4DA28B29.2030303@redhat.com> (raw)
In-Reply-To: <1302324875-31166-1-git-send-email-mattjclark0407@hotmail.com>
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)"
next prev parent reply other threads:[~2011-04-11 5:01 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-04-09 4:54 [Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP Matt Clark
2011-04-09 4:54 ` [Cluster-devel] [PATCH 2/3] Updated to include xenapi script Matt Clark
2011-04-09 4:54 ` [Cluster-devel] [PATCH 3/3] Updated to include xenapi script in Makefile.am Matt Clark
2011-04-11 5:01 ` Fabio M. Di Nitto [this message]
2011-04-12 2:46 ` [Cluster-devel] [PATCH 1/3] New fencing script for Citrix XenServer and XCP Matt Clark
2011-04-12 4:04 ` Fabio M. Di Nitto
2011-04-15 8:02 ` Marek Grac
2011-04-15 8:13 ` Fabio M. Di Nitto
2011-04-17 7:44 ` Matt Clark
2011-04-17 12:09 ` Fabio M. Di Nitto
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4DA28B29.2030303@redhat.com \
--to=fdinitto@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).