sshxmlrpc.py 5.89 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
import sys
import types
import urllib
import popen2
import xmlrpclib

##
# Use SSH to transport XML-RPC requests/responses
#
class SSHTransport:
    
    ##
    # Send a request to the destination.
    #
    # @param host The host name on which to execute the request
    # @param handler The python file that will handle the request.
    # @param request_body The XML-RPC encoded request.
    # @param verbose unused.
    # @return The value returned 
    #
    def request(self, host, handler, request_body, verbose=0):
        # Strip the leading slash in the handler, if there is one.
        if handler.startswith('/'):
            handler = handler[1:]
            pass
        
        # SSH to the host and call python on the handler.
        self.myChild = popen2.Popen3("ssh -x "
                                     + host
                                     + " 'python "
                                     + handler
                                     + "'")

        # Send the request over SSH's stdin,
        self.myChild.tochild.write(request_body)
        # ... close to signal the end of the request,
        self.myChild.tochild.close() # XXX Do something smarter here.

        # ... parse the response, and
        retval = self.parse_response()

        # ... wait for SSH to terminate.
        self.myChild.wait()

        return retval

    ##
    # @return A tuple containing the parser and unmarshaller, in that order
    #
    def getparser(self):
        return xmlrpclib.getparser()

    ##
    # Parse the response from the server.
    #
    # @return The python value returned by the server method.
    #
    def parse_response(self):
        parser, unmarshaller = self.getparser()

        while True:
            response = self.myChild.fromchild.read(1024)
            if not response:
                break
            parser.feed(response)
            pass

        return unmarshaller.close()
    
    pass


##
# A wrapper for objects that should be exported via an XML-RPC interface.  Any
# method calls received via XML-RPC will automatically be translated into real
# python calls.
#
class SSHServerWrapper:

    ##
    # Initialize this object.
    #
    # @param object The object to wrap.
    #
    def __init__(self, object):
        self.myObject = object
        return

    ##
    # Handle a single request from a client.  The method will read the request
    # from the client, dispatch the method, and write the response back to the
    # client.
    #
    # @param streams A pair containing the input and output streams.
    #
    def handle_request(self, streams):
        try:
            # Read the request,
            params, method = xmlrpclib.loads(streams[0].read())
            # ... find the corresponding method in the wrapped object,
            meth = getattr(self.myObject, method)
            # ... dispatch the method, and
            if type(meth) == type(self.handle_request):
                response = apply(meth, params) # It is really a method.
                pass
            else:
                response = str(meth) # Is is just a plain variable.
                pass
            # ... ensure there was a valid response.
            if type(response) != type((  )):
                response = (response,)
                pass
            pass
        except xmlrpclib.Fault, faultobj:
            # An XML-RPC related fault occurred, just encode the response.
            response = xmlrpclib.dumps(faultobj)
            pass
        except:
            # Some other exception happened, convert it to an XML-RPC fault.
            response = xmlrpclib.dumps(
                xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)))
            pass
        else:
            # Everything worked, encode the real response.
            response = xmlrpclib.dumps(response, methodresponse=1)
            pass

        # Finally, send the reply to the client.
        streams[1].write(response)

        return

    pass


##
# A proxy for XML-RPC servers that are accessible via SSH.
#
class SSHServerProxy:

    ##
    # Initialize the object.
    #
    # @param uri The URI for the server.  Must start with 'ssh'.
    # @param transport A python object that implements the Transport interface.
    # The default is to use a new SSHTransport object.
    # @param encoding Content encoding.
    # @param verbose unused.
    #
    def __init__(self, uri, transport=None, encoding=None, verbose=0):
        type, uri = urllib.splittype(uri)
        if type not in ("ssh", ):
            raise IOError, "unsupported XML-RPC protocol"

        self.__host, self.__handler = urllib.splithost(uri)

        if transport is None:
            transport = SSHTransport()
            pass
        
        self.__transport = transport

        self.__encoding = encoding
        self.__verbose = verbose
        return

    ##
    # Send a request to the server.
    #
    # @param methodname The name of the method.
    # @param params The parameters for the method.
    #
    def __request(self, methodname, params):
        # Convert the method name and parameters to a string,
        request = xmlrpclib.dumps(params, methodname, encoding=self.__encoding)

        # ... use the transport to send the request and receive the reply, and
        response = self.__transport.request(
            self.__host,
            self.__handler,
            request,
            verbose=self.__verbose
            )

        # ... ensure there was a valid reply.
        if len(response) == 1:
            response = response[0]
            pass

        return response

    def __repr__(self):
        return (
            "<ServerProxy for %s%s>" %
            (self.__host, self.__handler)
            )

    __str__ = __repr__

    def __getattr__(self, name):
        # magic method dispatcher
        return xmlrpclib._Method(self.__request, name)