source: trunk/packages/xen-common/xen-common/tools/python/xen/util/xmlrpclib2.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 17 years ago

Add xen and xen-common

File size: 8.3 KB
Line 
1#============================================================================
2# This library is free software; you can redistribute it and/or
3# modify it under the terms of version 2.1 of the GNU Lesser General Public
4# License as published by the Free Software Foundation.
5#
6# This library is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9# Lesser General Public License for more details.
10#
11# You should have received a copy of the GNU Lesser General Public
12# License along with this library; if not, write to the Free Software
13# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14#============================================================================
15# Copyright (C) 2006 Anthony Liguori <aliguori@us.ibm.com>
16# Copyright (C) 2006 XenSource Inc.
17#============================================================================
18
19"""
20An enhanced XML-RPC client/server interface for Python.
21"""
22
23import re
24import fcntl
25from types import *
26   
27
28from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
29import SocketServer
30import xmlrpclib, socket, os, stat
31
32import mkdir
33
34from xen.web import connection
35from xen.xend.XendLogging import log
36
37#
38# Convert all integers to strings as described in the Xen API
39#
40
41
42def stringify(value):
43    if isinstance(value, long) or \
44       (isinstance(value, int) and not isinstance(value, bool)):
45        return str(value)
46    elif isinstance(value, dict):
47        new_value = {}
48        for k, v in value.items():
49            new_value[stringify(k)] = stringify(v)
50        return new_value
51    elif isinstance(value, (tuple, list)):
52        return [stringify(v) for v in value]
53    else:
54        return value
55
56
57# We're forced to subclass the RequestHandler class so that we can work around
58# some bugs in Keep-Alive handling and also enabled it by default
59class XMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
60    protocol_version = "HTTP/1.1"
61
62    def __init__(self, hosts_allowed, request, client_address, server):
63        self.hosts_allowed = hosts_allowed
64        SimpleXMLRPCRequestHandler.__init__(self, request, client_address,
65                                            server)
66
67    # this is inspired by SimpleXMLRPCRequestHandler's do_POST but differs
68    # in a few non-trivial ways
69    # 1) we never generate internal server errors.  We let the exception
70    #    propagate so that it shows up in the Xend debug logs
71    # 2) we don't bother checking for a _dispatch function since we don't
72    #    use one
73    def do_POST(self):
74        addrport = self.client_address
75        if not connection.hostAllowed(addrport, self.hosts_allowed):
76            self.connection.shutdown(1)
77            return
78
79        data = self.rfile.read(int(self.headers["content-length"]))
80        rsp = self.server._marshaled_dispatch(data)
81
82        self.send_response(200)
83        self.send_header("Content-Type", "text/xml")
84        self.send_header("Content-Length", str(len(rsp)))
85        self.end_headers()
86
87        self.wfile.write(rsp)
88        self.wfile.flush()
89        if self.close_connection == 1:
90            self.connection.shutdown(1)
91
92# This is a base XML-RPC server for TCP.  It sets allow_reuse_address to
93# true, and has an improved marshaller that logs and serializes exceptions.
94
95class TCPXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
96    allow_reuse_address = True
97
98    def __init__(self, addr, allowed, xenapi, requestHandler=None,
99                 logRequests = 1):
100        self.xenapi = xenapi
101       
102        if requestHandler is None:
103            requestHandler = XMLRPCRequestHandler
104        SimpleXMLRPCServer.__init__(self, addr,
105                                    (lambda x, y, z:
106                                     requestHandler(allowed, x, y, z)),
107                                    logRequests)
108
109        flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
110        flags |= fcntl.FD_CLOEXEC
111        fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
112
113    def get_request(self):
114        (client, addr) = SimpleXMLRPCServer.get_request(self)
115        flags = fcntl.fcntl(client.fileno(), fcntl.F_GETFD)
116        flags |= fcntl.FD_CLOEXEC
117        fcntl.fcntl(client.fileno(), fcntl.F_SETFD, flags)
118        return (client, addr)
119
120    def _marshaled_dispatch(self, data, dispatch_method = None):
121        params, method = xmlrpclib.loads(data)
122        if False:
123            # Enable this block of code to exit immediately without sending
124            # a response.  This allows you to test client-side crash handling.
125            import sys
126            sys.exit(1)
127        try:
128            if dispatch_method is not None:
129                response = dispatch_method(method, params)
130            else:
131                response = self._dispatch(method, params)
132
133            if self.xenapi and \
134               (response is None or
135                not isinstance(response, dict) or
136                'Status' not in response):
137                log.exception('Internal error handling %s: Invalid result %s',
138                              method, response)
139                response = { "Status": "Failure",
140                             "ErrorDescription":
141                             ['INTERNAL_ERROR',
142                              'Invalid result %s handling %s' %
143                              (response, method)]}
144
145            # With either Unicode or normal strings, we can only transmit
146            # \t, \n, \r, \u0020-\ud7ff, \ue000-\ufffd, and \u10000-\u10ffff
147            # in an XML document.  xmlrpclib does not escape these values
148            # properly, and then breaks when it comes to parse the document.
149            # To hack around this problem, we use repr here and exec above
150            # to transmit the string using Python encoding.
151            # Thanks to David Mertz <mertz@gnosis.cx> for the trick (buried
152            # in xml_pickle.py).
153            if isinstance(response, StringTypes):
154                response = repr(response)[1:-1]
155
156            response = (response,)
157            response = xmlrpclib.dumps(response,
158                                       methodresponse=1,
159                                       allow_none=1)
160        except Exception, exn:
161            try:
162                if self.xenapi:
163                    if _is_not_supported(exn):
164                         errdesc = ['MESSAGE_METHOD_UNKNOWN', method]
165                    else:
166                         log.exception('Internal error handling %s', method)
167                         errdesc = ['INTERNAL_ERROR', str(exn)]
168
169                    response = xmlrpclib.dumps(
170                          ({ "Status": "Failure",
171                             "ErrorDescription": errdesc },),
172                          methodresponse = 1)
173                else:
174                    import xen.xend.XendClient
175                    if isinstance(exn, xmlrpclib.Fault):
176                        response = xmlrpclib.dumps(exn)
177                    else:
178                        log.exception('Internal error handling %s', method)
179                        response = xmlrpclib.dumps(
180                            xmlrpclib.Fault(xen.xend.XendClient.ERROR_INTERNAL, str(exn)))
181            except:
182                log.exception('Internal error handling error')
183
184        return response
185
186
187notSupportedRE = re.compile(r'method "(.*)" is not supported')
188def _is_not_supported(exn):
189    try:
190        m = notSupportedRE.search(exn[0])
191        return m is not None
192    except:
193        return False
194
195
196# This is a XML-RPC server that sits on a Unix domain socket.
197# It implements proper support for allow_reuse_address by
198# unlink()'ing an existing socket.
199
200class UnixXMLRPCRequestHandler(XMLRPCRequestHandler):
201    def address_string(self):
202        try:
203            return XMLRPCRequestHandler.address_string(self)
204        except ValueError, e:
205            return self.client_address[:2]
206
207class UnixXMLRPCServer(TCPXMLRPCServer):
208    address_family = socket.AF_UNIX
209    allow_address_reuse = True
210
211    def __init__(self, addr, allowed, xenapi, logRequests = 1):
212        mkdir.parents(os.path.dirname(addr), stat.S_IRWXU, True)
213        if self.allow_reuse_address and os.path.exists(addr):
214            os.unlink(addr)
215
216        TCPXMLRPCServer.__init__(self, addr, allowed, xenapi,
217                                 UnixXMLRPCRequestHandler, logRequests)
Note: See TracBrowser for help on using the repository browser.