FAQ Search Today's Posts Mark Forums Read
» Video Reviews

» Linux Archive

Linux-archive is a website aiming to archive linux email lists and to make them easily accessible for linux users/developers.


» Sponsor

» Partners

» Sponsor

Go Back   Linux Archive > Redhat > Cluster Development

 
 
LinkBack Thread Tools
 
Old 04-09-2011, 04:54 AM
Matt Clark
 
Default 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

diff --git a/fence/agents/xenapi/Makefile.am b/fence/agents/xenapi/Makefile.am
new file mode 100644
index 0000000..781975e
--- /dev/null
+++ b/fence/agents/xenapi/Makefile.am
@@ -0,0 +1,17 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+TARGET = fence_xenapi
+
+SRC = $(TARGET).py
+
+EXTRA_DIST = $(SRC)
+
+sbin_SCRIPTS = $(TARGET)
+
+man_MANS = $(TARGET).8
+
+include $(top_srcdir)/make/fencebuild.mk
+include $(top_srcdir)/make/fenceman.mk
+
+clean-local: clean-man
+ rm -f $(TARGET)
diff --git a/fence/agents/xenapi/XenAPI.py b/fence/agents/xenapi/XenAPI.py
new file mode 100755
index 0000000..4f27ef5
--- /dev/null
+++ b/fence/agents/xenapi/XenAPI.py
@@ -0,0 +1,209 @@
+#================================================ ============================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#================================================ ============================
+# Copyright (C) 2006 XenSource Inc.
+#================================================ ============================
+#
+# Parts of this file are based upon xmlrpclib.py, the XML-RPC client
+# interface included in the Python distribution.
+#
+# Copyright (c) 1999-2002 by Secret Labs AB
+# Copyright (c) 1999-2002 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Secret Labs AB or the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+import gettext
+import xmlrpclib
+import httplib
+import socket
+
+translation = gettext.translation('xen-xm', fallback = True)
+
+class Failure(Exception):
+ def __init__(self, details):
+ try:
+ # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we
+ # correct the return values here, to account for the fact that we
+ # transparently add the session handle as the first argument.
+ if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH':
+ details[2] = str(int(details[2]) - 1)
+ details[3] = str(int(details[3]) - 1)
+
+ self.details = details
+ except Exception, exn:
+ self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)]
+
+ def __str__(self):
+ try:
+ return translation.ugettext(self.details[0]) % self._details_map()
+ except TypeError, exn:
+ return "Message database broken: %s.
Xen-API failure: %s" %
+ (exn, str(self.details))
+ except Exception, exn:
+ import sys
+ print >>sys.stderr, exn
+ return "Xen-API failure: %s" % str(self.details)
+
+ def _details_map(self):
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])
+
+
+_RECONNECT_AND_RETRY = (lambda _ : ())
+
+class UDSHTTPConnection(httplib.HTTPConnection):
+ """ Stupid hacked up HTTPConnection subclass to allow HTTP over Unix domain
+ sockets. """
+ def connect(self):
+ path = self.host.replace("_", "/")
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(path)
+
+class UDSHTTP(httplib.HTTP):
+ _connection_class = UDSHTTPConnection
+
+class UDSTransport(xmlrpclib.Transport):
+ def make_connection(self, host):
+ return UDSHTTP(host)
+
+class Session(xmlrpclib.ServerProxy):
+ """A server proxy and session manager for communicating with Xend using
+ the Xen-API.
+
+ Example:
+
+ session = Session('http://localhost:9363/')
+ session.login_with_password('me', 'mypassword')
+ session.xenapi.VM.start(vm_uuid)
+ session.xenapi.session.logout()
+
+ For now, this class also supports the legacy XML-RPC API, using
+ session.xend.domain('Domain-0') and similar. This support will disappear
+ once there is a working Xen-API replacement for every call in the legacy
+ API.
+ """
+
+ def __init__(self, uri, transport=None, encoding=None, verbose=0,
+ allow_none=1):
+ xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
+ verbose, allow_none)
+ self._session = None
+ self.last_login_method = None
+ self.last_login_params = None
+
+
+ def xenapi_request(self, methodname, params):
+ if methodname.startswith('login'):
+ self._login(methodname, params)
+ return None
+ else:
+ retry_count = 0
+ while retry_count < 3:
+ full_params = (self._session,) + params
+ result = _parse_result(getattr(self, methodname)(*full_params))
+ if result == _RECONNECT_AND_RETRY:
+ retry_count += 1
+ if self.last_login_method:
+ self._login(self.last_login_method,
+ self.last_login_params)
+ else:
+ raise xmlrpclib.Fault(401, 'You must log in')
+ else:
+ return result
+ raise xmlrpclib.Fault(
+ 500, 'Tried 3 times to get a valid session, but failed')
+
+
+ def _login(self, method, params):
+ result = _parse_result(getattr(self, 'session.%s' % method)(*params))
+ if result == _RECONNECT_AND_RETRY:
+ raise xmlrpclib.Fault(
+ 500, 'Received SESSION_INVALID when logging in')
+ self._session = result
+ self.last_login_method = method
+ self.last_login_params = params
+
+
+ def __getattr__(self, name):
+ if name == 'xenapi':
+ return _Dispatcher(self.xenapi_request, None)
+ elif name.startswith('login'):
+ return lambda *params: self._login(name, params)
+ else:
+ return xmlrpclib.ServerProxy.__getattr__(self, name)
+
+def xapi_local():
+ return Session("http://_var_xapi_xapi/", transport=UDSTransport())
+
+def _parse_result(result):
+ if type(result) != dict or 'Status' not in result:
+ raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result)
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return _RECONNECT_AND_RETRY
+ else:
+ raise Failure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')
+
+
+# Based upon _Method from xmlrpclib.
+class _Dispatcher:
+ def __init__(self, send, name):
+ self.__send = send
+ self.__name = name
+
+ def __repr__(self):
+ if self.__name:
+ return '<XenAPI._Dispatcher for %s>' % self.__name
+ else:
+ return '<XenAPI._Dispatcher>'
+
+ def __getattr__(self, name):
+ if self.__name is None:
+ return _Dispatcher(self.__send, name)
+ else:
+ return _Dispatcher(self.__send, "%s.%s" % (self.__name, name))
+
+ def __call__(self, *args):
+ return self.__send(self.__name, args)
diff --git a/fence/agents/xenapi/fence_xenapi.py b/fence/agents/xenapi/fence_xenapi.py
new file mode 100644
index 0000000..0657f4e
--- /dev/null
+++ b/fence/agents/xenapi/fence_xenapi.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+#
+################################################# ############################
+# Copyright 2011 Matt Clark
+# This file is part of fence-xenserver
+#
+# fence-xenserver is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# fence-xenserver is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Please let me know if you are using this script so that I can work out
+# whether I should continue support for it. mattjclark0407 at hotmail dot com
+################################################# ############################
+
+################################################# ############################
+# It's only just begun...
+# Current status: completely usable. This script is now working well and,
+# has a lot of functionality as a result of the fencing.py library and the
+# XenAPI libary.
+
+################################################# ############################
+# Please let me know if you are using this script so that I can work out
+# whether I should continue support for it. mattjclark0407 at hotmail dot com
+
+import sys
+sys.path.append("/usr/lib/fence")
+from fencing import *
+import XenAPI
+
+EC_BAD_SESSION = 1
+# Find the status of the port given in the -U flag of options.
+def get_power_fn(session, options):
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/port parameter
+ vm = return_vm_reference(session, options)
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm);
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:", record["name_label"], "POWER STATUS:", record["power_state"]
+ # Note that the VM can be in the following states (from the XenAPI document)
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself is paused and its vCPUs are not running
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolonger running. Note that disks remain in-Use while
+ # We want to make sure that we only return the status "off" if the machine is actually halted as the status
+ # is checked before a fencing action. Only when the machine is Halted is it not consuming resources which
+ # may include whatever you are trying to protect with this fencing action.
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)
+
+ return "Error"
+
+# Set the state of the port given in the -U flag of options.
+def set_power_fn(session, options):
+ action = options["-o"].lower()
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/port parameter
+ vm = return_vm_reference(session, options)
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm)
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ if( action == "on" ):
+ # Start the VM
+ session.xenapi.VM.start(vm, False, True)
+ elif( action == "off" ):
+ # Force shutdown the VM
+ session.xenapi.VM.hard_shutdown(vm)
+ elif( action == "reboot" ):
+ # Force reboot the VM
+ session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);
+
+# Function to populate an array of virtual machines and their status
+def get_outlet_list(session, options):
+ result = {}
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Return an array of all the VM's on the host
+ vms = session.xenapi.VM.get_all()
+ for vm in vms:
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm);
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ name = record["name_label"]
+ uuid = record["uuid"]
+ status = record["power_state"]
+ result[uuid] = (name, status)
+ if verbose: print "UUID:", record["uuid"], "NAME:", name, "POWER STATUS:", record["power_state"]
+ except Exception, exn:
+ print str(exn);
+
+ return result
+
+# Function to initiate the XenServer session via the XenAPI library.
+def connect_and_login(options):
+ url = options["-s"]
+ username = options["-l"]
+ password = options["-p"]
+
+ try:
+ # Create the XML RPC session to the specified URL.
+ session = XenAPI.Session(url);
+ # Login using the supplied credentials.
+ session.xenapi.login_with_password(username, password);
+ except Exception, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says that for no connectivity
+ # the exit value should be 1. It doesn't say anything about failed logins, so
+ # until I hear otherwise it is best to keep this exit the same to make sure that
+ # anything calling this script (that uses the same information in the web page
+ # above) knows that this is an error condition, not a msg signifying a down port.
+ sys.exit(EC_BAD_SESSION);
+ return session;
+
+# return a reference to the VM by either using the UUID or the vm_name/port. If the UUID is set then
+# this is tried first as this is the only properly unique identifier.
+# Exceptions are not handled in this function, code that calls this must be ready to handle them.
+def return_vm_reference(session, options):
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ # Case where the UUID has been specified
+ if options.has_key("-U"):
+ uuid = options["-U"].lower()
+ # When using the -n parameter for name, we get an error message (in verbose
+ # mode) that tells us that we didn't find a VM. To immitate that here we
+ # need to catch and re-raise the exception produced by get_by_uuid.
+ try:
+ return session.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of "%s"" %uuid
+ raise
+
+
+ # Case where the vm_name/port has been specified
+ if options.has_key("-n"):
+ vm_name = options["-n"]
+ vm_arr = session.xenapi.VM.get_by_name_label(vm_name)
+ # Need to make sure that we only have one result as the vm_name may
+ # not be unique. Average case, so do it first.
+ if len(vm_arr) == 1:
+ return vm_arr[0]
+ else:
+ if len(vm_arr) == 0:
+ if verbose: print "No VM's found with a name of "%s"" %vm_name
+ # NAME_INVALID used as the XenAPI throws a UUID_INVALID if it can't find
+ # a VM with the specified UUID. This should make the output look fairly
+ # consistent.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name "%s", use UUID instead" %vm_name
+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks that either the UUID or
+ # the name parameter is set. Regardless of whether or not a VM is found the above if
+ # statements will return to the calling function (either by exception or by a reference
+ # to the VM).
+ raise Exception("VM_LOGIC_ERROR")
+
+def main():
+
+ device_opt = [ "help", "version", "agent", "quiet", "verbose", "debug", "action",
+ "login", "passwd", "passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ]
+
+ atexit.register(atexit_handler)
+
+ options=process_input(device_opt)
+
+ options = check_input(device_opt, options)
+
+ docs = { }
+ docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServer virtual machines."
+ docs["longdesc"] = "
+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts.
+It uses the XenAPI, supplied by Citrix, to establish an XML-RPC sesssion
+to a XenServer host. Once the session is established, further XML-RPC
+commands are issued in order to switch on, switch off, restart and query
+the status of virtual machines running on the host."
+ show_docs(options, docs)
+
+ xenSession = connect_and_login(options)
+
+ # Operate the fencing device
+ result = fence_action(xenSession, options, set_power_fn, get_power_fn, get_outlet_list)
+
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()
+RELEASE_VERSION="3.1.2.11-2b5b-dirty"
+BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
--
1.7.0.4
 
Old 04-20-2011, 11:51 AM
Matt Clark
 
Default 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

diff --git a/fence/agents/xenapi/Makefile.am b/fence/agents/xenapi/Makefile.am
new file mode 100644
index 0000000..781975e
--- /dev/null
+++ b/fence/agents/xenapi/Makefile.am
@@ -0,0 +1,17 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+TARGET = fence_xenapi
+
+SRC = $(TARGET).py
+
+EXTRA_DIST = $(SRC)
+
+sbin_SCRIPTS = $(TARGET)
+
+man_MANS = $(TARGET).8
+
+include $(top_srcdir)/make/fencebuild.mk
+include $(top_srcdir)/make/fenceman.mk
+
+clean-local: clean-man
+ rm -f $(TARGET)
diff --git a/fence/agents/xenapi/XenAPI.py b/fence/agents/xenapi/XenAPI.py
new file mode 100755
index 0000000..4f27ef5
--- /dev/null
+++ b/fence/agents/xenapi/XenAPI.py
@@ -0,0 +1,209 @@
+#================================================ ============================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#================================================ ============================
+# Copyright (C) 2006 XenSource Inc.
+#================================================ ============================
+#
+# Parts of this file are based upon xmlrpclib.py, the XML-RPC client
+# interface included in the Python distribution.
+#
+# Copyright (c) 1999-2002 by Secret Labs AB
+# Copyright (c) 1999-2002 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Secret Labs AB or the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+import gettext
+import xmlrpclib
+import httplib
+import socket
+
+translation = gettext.translation('xen-xm', fallback = True)
+
+class Failure(Exception):
+ def __init__(self, details):
+ try:
+ # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we
+ # correct the return values here, to account for the fact that we
+ # transparently add the session handle as the first argument.
+ if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH':
+ details[2] = str(int(details[2]) - 1)
+ details[3] = str(int(details[3]) - 1)
+
+ self.details = details
+ except Exception, exn:
+ self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)]
+
+ def __str__(self):
+ try:
+ return translation.ugettext(self.details[0]) % self._details_map()
+ except TypeError, exn:
+ return "Message database broken: %s.
Xen-API failure: %s" %
+ (exn, str(self.details))
+ except Exception, exn:
+ import sys
+ print >>sys.stderr, exn
+ return "Xen-API failure: %s" % str(self.details)
+
+ def _details_map(self):
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])
+
+
+_RECONNECT_AND_RETRY = (lambda _ : ())
+
+class UDSHTTPConnection(httplib.HTTPConnection):
+ """ Stupid hacked up HTTPConnection subclass to allow HTTP over Unix domain
+ sockets. """
+ def connect(self):
+ path = self.host.replace("_", "/")
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(path)
+
+class UDSHTTP(httplib.HTTP):
+ _connection_class = UDSHTTPConnection
+
+class UDSTransport(xmlrpclib.Transport):
+ def make_connection(self, host):
+ return UDSHTTP(host)
+
+class Session(xmlrpclib.ServerProxy):
+ """A server proxy and session manager for communicating with Xend using
+ the Xen-API.
+
+ Example:
+
+ session = Session('http://localhost:9363/')
+ session.login_with_password('me', 'mypassword')
+ session.xenapi.VM.start(vm_uuid)
+ session.xenapi.session.logout()
+
+ For now, this class also supports the legacy XML-RPC API, using
+ session.xend.domain('Domain-0') and similar. This support will disappear
+ once there is a working Xen-API replacement for every call in the legacy
+ API.
+ """
+
+ def __init__(self, uri, transport=None, encoding=None, verbose=0,
+ allow_none=1):
+ xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
+ verbose, allow_none)
+ self._session = None
+ self.last_login_method = None
+ self.last_login_params = None
+
+
+ def xenapi_request(self, methodname, params):
+ if methodname.startswith('login'):
+ self._login(methodname, params)
+ return None
+ else:
+ retry_count = 0
+ while retry_count < 3:
+ full_params = (self._session,) + params
+ result = _parse_result(getattr(self, methodname)(*full_params))
+ if result == _RECONNECT_AND_RETRY:
+ retry_count += 1
+ if self.last_login_method:
+ self._login(self.last_login_method,
+ self.last_login_params)
+ else:
+ raise xmlrpclib.Fault(401, 'You must log in')
+ else:
+ return result
+ raise xmlrpclib.Fault(
+ 500, 'Tried 3 times to get a valid session, but failed')
+
+
+ def _login(self, method, params):
+ result = _parse_result(getattr(self, 'session.%s' % method)(*params))
+ if result == _RECONNECT_AND_RETRY:
+ raise xmlrpclib.Fault(
+ 500, 'Received SESSION_INVALID when logging in')
+ self._session = result
+ self.last_login_method = method
+ self.last_login_params = params
+
+
+ def __getattr__(self, name):
+ if name == 'xenapi':
+ return _Dispatcher(self.xenapi_request, None)
+ elif name.startswith('login'):
+ return lambda *params: self._login(name, params)
+ else:
+ return xmlrpclib.ServerProxy.__getattr__(self, name)
+
+def xapi_local():
+ return Session("http://_var_xapi_xapi/", transport=UDSTransport())
+
+def _parse_result(result):
+ if type(result) != dict or 'Status' not in result:
+ raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result)
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return _RECONNECT_AND_RETRY
+ else:
+ raise Failure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')
+
+
+# Based upon _Method from xmlrpclib.
+class _Dispatcher:
+ def __init__(self, send, name):
+ self.__send = send
+ self.__name = name
+
+ def __repr__(self):
+ if self.__name:
+ return '<XenAPI._Dispatcher for %s>' % self.__name
+ else:
+ return '<XenAPI._Dispatcher>'
+
+ def __getattr__(self, name):
+ if self.__name is None:
+ return _Dispatcher(self.__send, name)
+ else:
+ return _Dispatcher(self.__send, "%s.%s" % (self.__name, name))
+
+ def __call__(self, *args):
+ return self.__send(self.__name, args)
diff --git a/fence/agents/xenapi/fence_xenapi.py b/fence/agents/xenapi/fence_xenapi.py
new file mode 100644
index 0000000..0657f4e
--- /dev/null
+++ b/fence/agents/xenapi/fence_xenapi.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+#
+################################################# ############################
+# Copyright 2011 Matt Clark
+# This file is part of fence-xenserver
+#
+# fence-xenserver is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# fence-xenserver is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Please let me know if you are using this script so that I can work out
+# whether I should continue support for it. mattjclark0407 at hotmail dot com
+################################################# ############################
+
+################################################# ############################
+# It's only just begun...
+# Current status: completely usable. This script is now working well and,
+# has a lot of functionality as a result of the fencing.py library and the
+# XenAPI libary.
+
+################################################# ############################
+# Please let me know if you are using this script so that I can work out
+# whether I should continue support for it. mattjclark0407 at hotmail dot com
+
+import sys
+sys.path.append("/usr/lib/fence")
+from fencing import *
+import XenAPI
+
+EC_BAD_SESSION = 1
+# Find the status of the port given in the -U flag of options.
+def get_power_fn(session, options):
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/port parameter
+ vm = return_vm_reference(session, options)
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm);
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ status = record["power_state"]
+ if verbose: print "UUID:", record["uuid"], "NAME:", record["name_label"], "POWER STATUS:", record["power_state"]
+ # Note that the VM can be in the following states (from the XenAPI document)
+ # Halted: VM is offline and not using any resources.
+ # Paused: All resources have been allocated but the VM itself is paused and its vCPUs are not running
+ # Running: Running
+ # Paused: VM state has been saved to disk and it is nolonger running. Note that disks remain in-Use while
+ # We want to make sure that we only return the status "off" if the machine is actually halted as the status
+ # is checked before a fencing action. Only when the machine is Halted is it not consuming resources which
+ # may include whatever you are trying to protect with this fencing action.
+ return (status=="Halted" and "off" or "on")
+ except Exception, exn:
+ print str(exn)
+
+ return "Error"
+
+# Set the state of the port given in the -U flag of options.
+def set_power_fn(session, options):
+ action = options["-o"].lower()
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Get a reference to the vm specified in the UUID or vm_name/port parameter
+ vm = return_vm_reference(session, options)
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm)
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ if( action == "on" ):
+ # Start the VM
+ session.xenapi.VM.start(vm, False, True)
+ elif( action == "off" ):
+ # Force shutdown the VM
+ session.xenapi.VM.hard_shutdown(vm)
+ elif( action == "reboot" ):
+ # Force reboot the VM
+ session.xenapi.VM.hard_reboot(vm)
+ except Exception, exn:
+ print str(exn);
+
+# Function to populate an array of virtual machines and their status
+def get_outlet_list(session, options):
+ result = {}
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ try:
+ # Return an array of all the VM's on the host
+ vms = session.xenapi.VM.get_all()
+ for vm in vms:
+ # Query the VM for its' associated parameters
+ record = session.xenapi.VM.get_record(vm);
+ # Check that we are not trying to manipulate a template or a control
+ # domain as they show up as VM's with specific properties.
+ if not(record["is_a_template"]) and not(record["is_control_domain"]):
+ name = record["name_label"]
+ uuid = record["uuid"]
+ status = record["power_state"]
+ result[uuid] = (name, status)
+ if verbose: print "UUID:", record["uuid"], "NAME:", name, "POWER STATUS:", record["power_state"]
+ except Exception, exn:
+ print str(exn);
+
+ return result
+
+# Function to initiate the XenServer session via the XenAPI library.
+def connect_and_login(options):
+ url = options["-s"]
+ username = options["-l"]
+ password = options["-p"]
+
+ try:
+ # Create the XML RPC session to the specified URL.
+ session = XenAPI.Session(url);
+ # Login using the supplied credentials.
+ session.xenapi.login_with_password(username, password);
+ except Exception, exn:
+ print str(exn);
+ # http://sources.redhat.com/cluster/wiki/FenceAgentAPI says that for no connectivity
+ # the exit value should be 1. It doesn't say anything about failed logins, so
+ # until I hear otherwise it is best to keep this exit the same to make sure that
+ # anything calling this script (that uses the same information in the web page
+ # above) knows that this is an error condition, not a msg signifying a down port.
+ sys.exit(EC_BAD_SESSION);
+ return session;
+
+# return a reference to the VM by either using the UUID or the vm_name/port. If the UUID is set then
+# this is tried first as this is the only properly unique identifier.
+# Exceptions are not handled in this function, code that calls this must be ready to handle them.
+def return_vm_reference(session, options):
+ if options.has_key("-v"):
+ verbose = True
+ else:
+ verbose = False
+
+ # Case where the UUID has been specified
+ if options.has_key("-U"):
+ uuid = options["-U"].lower()
+ # When using the -n parameter for name, we get an error message (in verbose
+ # mode) that tells us that we didn't find a VM. To immitate that here we
+ # need to catch and re-raise the exception produced by get_by_uuid.
+ try:
+ return session.xenapi.VM.get_by_uuid(uuid)
+ except Exception,exn:
+ if verbose: print "No VM's found with a UUID of "%s"" %uuid
+ raise
+
+
+ # Case where the vm_name/port has been specified
+ if options.has_key("-n"):
+ vm_name = options["-n"]
+ vm_arr = session.xenapi.VM.get_by_name_label(vm_name)
+ # Need to make sure that we only have one result as the vm_name may
+ # not be unique. Average case, so do it first.
+ if len(vm_arr) == 1:
+ return vm_arr[0]
+ else:
+ if len(vm_arr) == 0:
+ if verbose: print "No VM's found with a name of "%s"" %vm_name
+ # NAME_INVALID used as the XenAPI throws a UUID_INVALID if it can't find
+ # a VM with the specified UUID. This should make the output look fairly
+ # consistent.
+ raise Exception("NAME_INVALID")
+ else:
+ if verbose: print "Multiple VM's have the name "%s", use UUID instead" %vm_name
+ raise Exception("MULTIPLE_VMS_FOUND")
+
+ # We should never get to this case as the input processing checks that either the UUID or
+ # the name parameter is set. Regardless of whether or not a VM is found the above if
+ # statements will return to the calling function (either by exception or by a reference
+ # to the VM).
+ raise Exception("VM_LOGIC_ERROR")
+
+def main():
+
+ device_opt = [ "help", "version", "agent", "quiet", "verbose", "debug", "action",
+ "login", "passwd", "passwd_script", "port", "test", "separator",
+ "no_login", "no_password", "power_timeout", "shell_timeout",
+ "login_timeout", "power_wait", "session_url", "uuid" ]
+
+ atexit.register(atexit_handler)
+
+ options=process_input(device_opt)
+
+ options = check_input(device_opt, options)
+
+ docs = { }
+ docs["shortdesc"] = "XenAPI based fencing for the Citrix XenServer virtual machines."
+ docs["longdesc"] = "
+fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts.
+It uses the XenAPI, supplied by Citrix, to establish an XML-RPC sesssion
+to a XenServer host. Once the session is established, further XML-RPC
+commands are issued in order to switch on, switch off, restart and query
+the status of virtual machines running on the host."
+ show_docs(options, docs)
+
+ xenSession = connect_and_login(options)
+
+ # Operate the fencing device
+ result = fence_action(xenSession, options, set_power_fn, get_power_fn, get_outlet_list)
+
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()
+RELEASE_VERSION="3.1.2.11-2b5b-dirty"
+BUILD_DATE="(built Fri Mar 25 22:57:28 EST 2011)"
--
1.7.0.4
 

Thread Tools




All times are GMT. The time now is 07:59 AM.

VBulletin, Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.
Content Relevant URLs by vBSEO ©2007, Crawlability, Inc.
Copyright 2007 - 2008, www.linux-archive.org