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