Skip to contentSkip to conversation

New fencing script for Citrix XenServer and XCP.

PATCH New Matt Clark 3 participants 10 messages Apr 9, 2011 – Apr 17, 2011

lore
Share & tools
lore

Matt Clark

author
#0 lore raw

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
new file mode 100644
index 0000000..781975e
@@ -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
author
#1 lore raw

PATCH 2/3 Updated to include xenapi script.

---
 configure.ac |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
configure.ac +1 -0
index d8994d2..d71f6e4 100644
@@ -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
author
#2 lore raw

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
index e2f445f..ff3dbf7 100644
@@ -40,4 +40,5 @@ SUBDIRS 		= lib \
 			  vmware_soap \
 			  wti \
 			  xcat \
+			  xenapi \
 			  zvm
fence/agents/xenapi/fence_xenapi.py +0 -0
old mode 100644
new mode 100755
Signature · 1 line
1.7.0.4
#3 lore raw

PATCH 1/3 New fencing script for Citrix XenServer and XCP.

In reply to Matt Clark

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

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.py
fence/agents/xenapi/Makefile.am new file +17 -0
new file mode 100644
index 0000000..781975e
@@ -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
new file mode 100755
index 0000000..4f27ef5
@@ -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
new file mode 100644
index 0000000..0657f4e
@@ -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)"
author
#4 lore raw

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.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 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.py
fence/agents/xenapi/Makefile.am new file +17 -0
new file mode 100644
index 0000000..781975e
@@ -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
new file mode 100755
index 0000000..4f27ef5
@@ -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
new file mode 100644
index 0000000..0657f4e
@@ -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>

#5 lore raw

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 Citrix

XenServer 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.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.

Quoted 5 lines

Cheers
Fabio

Quoted 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.py create mode 100644 fence/agents/xenapi/fence_xenapi.py

(quoted diff) +0 -0

b/fence/agents/xenapi/Makefile.am

Quoted 15 lines
Quoted 15 lines

new file mode 100644
index 0000000..781975e

fence/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.py

Quoted 6 lines

fence/agents/xenapi/XenAPI.py

Quoted 6 lines

new file mode 100755
index 0000000..4f27ef5

fence/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 Lesser

General 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, MA

02111-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 over

Unix 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 = UDSHTTPConnection

fence/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 Xend

using

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 will

disappear

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 from

server' + 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:
+ raise Failure(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.py

Quoted 8 lines

fence/agents/xenapi/fence_xenapi.py line 2

Quoted 8 lines

new file mode 100644
index 0000000..0657f4e

fence/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/or

modify

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 can

work 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 at

hotmail 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 working

well and,

Quoted 1 line
Quoted 1 line

+# has a lot of functionality as a result of the fencing.py library

and 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/port

parameter

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.
+ 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"]

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 is

paused 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 nolonger

running. 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()
+ 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

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.
+ 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);

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.
+ 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"]

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 that

for 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 the

vm_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 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:
+ 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

fence/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_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

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.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUID

instead" %vm_name

Quoted 3 lines
Quoted 3 lines

+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks

that 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).
+ raise Exception("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 XenServer

virtual 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-RPC

sesssion \

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 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)"
#6 lore raw

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,

#7 lore raw

PATCH 1/3 New fencing script for Citrix XenServer and XCP.

In reply to Matt Clark

hi 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 Citrix

XenServer 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.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.

Quoted 5 lines

Cheers
Fabio

Quoted 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.py create mode 100644 fence/agents/xenapi/fence_xenapi.py

(quoted diff) +0 -0

b/fence/agents/xenapi/Makefile.am

Quoted 15 lines
Quoted 15 lines

new file mode 100644
index 0000000..781975e

fence/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.py

Quoted 6 lines

fence/agents/xenapi/XenAPI.py

Quoted 6 lines

new file mode 100755
index 0000000..4f27ef5

fence/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 Lesser

General 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, MA

02111-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 over

Unix 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 = UDSHTTPConnection

fence/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 Xend

using

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 will

disappear

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 from

server' + 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:
+ raise Failure(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.py

Quoted 8 lines

fence/agents/xenapi/fence_xenapi.py line 2

Quoted 8 lines

new file mode 100644
index 0000000..0657f4e

fence/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/or

modify

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 can

work 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 at

hotmail 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 working

well and,

Quoted 1 line
Quoted 1 line

+# has a lot of functionality as a result of the fencing.py library

and 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/port

parameter

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.
+ 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"]

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 is

paused 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 nolonger

running. 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()
+ 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

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.
+ 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);

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.
+ 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"]

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 that

for 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 the

vm_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 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:
+ 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

fence/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_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

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.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUID

instead" %vm_name

Quoted 3 lines
Quoted 3 lines

+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks

that 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).
+ raise Exception("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 XenServer

virtual 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-RPC

sesssion \

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 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)"
author
#8 lore raw

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
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 Citrix

XenServer 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.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.

Quoted 5 lines

Cheers
Fabio

Quoted 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.py create mode 100644 fence/agents/xenapi/fence_xenapi.py

(quoted diff) +0 -0

b/fence/agents/xenapi/Makefile.am

Quoted 15 lines
Quoted 15 lines

new file mode 100644
index 0000000..781975e

fence/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.py

Quoted 6 lines

fence/agents/xenapi/XenAPI.py

Quoted 6 lines

new file mode 100755
index 0000000..4f27ef5

fence/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 Lesser

General 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, MA

02111-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 over

Unix 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 = UDSHTTPConnection

fence/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 Xend

using

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 will

disappear

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 from

server' + 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:
+ raise Failure(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.py

Quoted 8 lines

fence/agents/xenapi/fence_xenapi.py line 2

Quoted 8 lines

new file mode 100644
index 0000000..0657f4e

fence/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/or

modify

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 can

work 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 at

hotmail 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 working

well and,

Quoted 1 line
Quoted 1 line

+# has a lot of functionality as a result of the fencing.py library

and 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/port

parameter

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.
+ 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"]

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 is

paused 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 nolonger

running. 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()
+ 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

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.
+ 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);

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.
+ 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"]

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 that

for 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 the

vm_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 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:
+ 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

fence/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_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

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.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUID

instead" %vm_name

Quoted 3 lines
Quoted 3 lines

+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks

that 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).
+ raise Exception("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 XenServer

virtual 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-RPC

sesssion \

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 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)"

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/cluster-devel/attachments/20110417/b30e13e2/attachment.htm>

#9 lore raw

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 Citrix

XenServer and XCP.

Quoted 13 lines

hi Matt,

Marek said the code is fine, can you please resubmit with the build fixes?

Thanks
fabio

Quoted 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 Citrix

Quoted 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.py being 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_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

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
Fabio

Quoted 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.py create mode 100644 fence/agents/xenapi/fence_xenapi.py

(quoted diff) +0 -0

b/fence/agents/xenapi/Makefile.am

Quoted 15 lines
Quoted 15 lines

new file mode 100644
index 0000000..781975e

fence/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.py

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

fence/agents/xenapi/XenAPI.py

Quoted 6 lines

new file mode 100755
index 0000000..4f27ef5

fence/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 Lesser

General 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 Free

Software

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, MA

02111-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 notice

appears 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 or

publicity

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 THE

AUTHOR

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 OR

PERFORMANCE

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 over

Unix 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 = UDSHTTPConnection

fence/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 Xend

using

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 will

disappear

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, 'Received SESSION_INVALID when logging in')
+ self._session = result
+ self.last_login_method = method
+ self.last_login_params = params

fence/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 from

server' + 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:
+ raise Failure(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.py

Quoted 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..0657f4e

fence/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/or

modify

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 as

published 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 can

work 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 at

hotmail 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 working

well and,

Quoted 1 line
Quoted 1 line

+# has a lot of functionality as a result of the fencing.py library

and 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/port

parameter

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 a

control

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+ # domain as they show up as VM's with specific properties.
+ if not(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 is

paused 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 nolonger

running. 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()
+ 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

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 a

control

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+ # domain as they show up as VM's with specific properties.
+ if not(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 = False
Quoted 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 a

control

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+ # domain as they show up as VM's with specific properties.
+ if not(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, "POWER

STATUS:", 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 that

for 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 the

vm_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:
+ 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

fence/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_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

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.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name \"%s\", use UUID

instead" %vm_name

Quoted 3 lines
Quoted 3 lines

+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks

that 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).
+ raise Exception("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 XenServer

virtual 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-RPC

sesssion \

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)"