ia64/xen-unstable

view tools/python/xen/util/xmlrpclib2.py @ 14157:339e477d2548

Fix exception handling to cope with exceptions that do not have a string as
the first argument.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Tue Feb 27 11:19:25 2007 +0000 (2007-02-27)
parents 93d3ff513609
children acdfb333af53
line source
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 #============================================================================
19 """
20 An enhanced XML-RPC client/server interface for Python.
21 """
23 import re
24 import string
25 import fcntl
26 from types import *
29 from httplib import HTTPConnection, HTTP
30 from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
31 import SocketServer
32 import xmlrpclib, socket, os, stat
34 import mkdir
36 from xen.web import connection
37 from xen.xend.XendLogging import log
39 try:
40 import SSHTransport
41 ssh_enabled = True
42 except ImportError:
43 # SSHTransport is disabled on Python <2.4, because it uses the subprocess
44 # package.
45 ssh_enabled = False
47 #
48 # Convert all integers to strings as described in the Xen API
49 #
52 def stringify(value):
53 if isinstance(value, long) or \
54 (isinstance(value, int) and not isinstance(value, bool)):
55 return str(value)
56 elif isinstance(value, dict):
57 for k, v in value.items():
58 value[k] = stringify(v)
59 return value
60 elif isinstance(value, (tuple, list)):
61 return [stringify(v) for v in value]
62 else:
63 return value
66 # A new ServerProxy that also supports httpu urls. An http URL comes in the
67 # form:
68 #
69 # httpu:///absolute/path/to/socket.sock
70 #
71 # It assumes that the RPC handler is /RPC2. This probably needs to be improved
73 # We're forced to subclass the RequestHandler class so that we can work around
74 # some bugs in Keep-Alive handling and also enabled it by default
75 class XMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
76 protocol_version = "HTTP/1.1"
78 def __init__(self, hosts_allowed, request, client_address, server):
79 self.hosts_allowed = hosts_allowed
80 SimpleXMLRPCRequestHandler.__init__(self, request, client_address,
81 server)
83 # this is inspired by SimpleXMLRPCRequestHandler's do_POST but differs
84 # in a few non-trivial ways
85 # 1) we never generate internal server errors. We let the exception
86 # propagate so that it shows up in the Xend debug logs
87 # 2) we don't bother checking for a _dispatch function since we don't
88 # use one
89 def do_POST(self):
90 addrport = self.client_address
91 if not connection.hostAllowed(addrport, self.hosts_allowed):
92 self.connection.shutdown(1)
93 return
95 data = self.rfile.read(int(self.headers["content-length"]))
96 rsp = self.server._marshaled_dispatch(data)
98 self.send_response(200)
99 self.send_header("Content-Type", "text/xml")
100 self.send_header("Content-Length", str(len(rsp)))
101 self.end_headers()
103 self.wfile.write(rsp)
104 self.wfile.flush()
105 if self.close_connection == 1:
106 self.connection.shutdown(1)
108 class HTTPUnixConnection(HTTPConnection):
109 def connect(self):
110 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
111 self.sock.connect(self.host)
113 class HTTPUnix(HTTP):
114 _connection_class = HTTPUnixConnection
116 class UnixTransport(xmlrpclib.Transport):
117 def request(self, host, handler, request_body, verbose=0):
118 self.__handler = handler
119 return xmlrpclib.Transport.request(self, host, '/RPC2',
120 request_body, verbose)
121 def make_connection(self, host):
122 return HTTPUnix(self.__handler)
125 # See _marshalled_dispatch below.
126 def conv_string(x):
127 if isinstance(x, StringTypes):
128 s = string.replace(x, "'", r"\047")
129 exec "s = '" + s + "'"
130 return s
131 else:
132 return x
135 class ServerProxy(xmlrpclib.ServerProxy):
136 def __init__(self, uri, transport=None, encoding=None, verbose=0,
137 allow_none=1):
138 if transport == None:
139 (protocol, rest) = uri.split(':', 1)
140 if protocol == 'httpu':
141 uri = 'http:' + rest
142 transport = UnixTransport()
143 elif protocol == 'ssh':
144 global ssh_enabled
145 if ssh_enabled:
146 (transport, uri) = SSHTransport.getHTTPURI(uri)
147 else:
148 raise ValueError(
149 "SSH transport not supported on Python <2.4.")
150 xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
151 verbose, allow_none)
153 def __request(self, methodname, params):
154 response = xmlrpclib.ServerProxy.__request(self, methodname, params)
156 if isinstance(response, tuple):
157 return tuple([conv_string(x) for x in response])
158 else:
159 return conv_string(response)
162 # This is a base XML-RPC server for TCP. It sets allow_reuse_address to
163 # true, and has an improved marshaller that logs and serializes exceptions.
165 class TCPXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
166 allow_reuse_address = True
168 def __init__(self, addr, allowed, xenapi, requestHandler=None,
169 logRequests = 1):
170 self.xenapi = xenapi
172 if requestHandler is None:
173 requestHandler = XMLRPCRequestHandler
174 SimpleXMLRPCServer.__init__(self, addr,
175 (lambda x, y, z:
176 requestHandler(allowed, x, y, z)),
177 logRequests)
179 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
180 flags |= fcntl.FD_CLOEXEC
181 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
183 def get_request(self):
184 (client, addr) = SimpleXMLRPCServer.get_request(self)
185 flags = fcntl.fcntl(client.fileno(), fcntl.F_GETFD)
186 flags |= fcntl.FD_CLOEXEC
187 fcntl.fcntl(client.fileno(), fcntl.F_SETFD, flags)
188 return (client, addr)
190 def _marshaled_dispatch(self, data, dispatch_method = None):
191 params, method = xmlrpclib.loads(data)
192 if False:
193 # Enable this block of code to exit immediately without sending
194 # a response. This allows you to test client-side crash handling.
195 import sys
196 sys.exit(1)
197 try:
198 if dispatch_method is not None:
199 response = dispatch_method(method, params)
200 else:
201 response = self._dispatch(method, params)
203 # With either Unicode or normal strings, we can only transmit
204 # \t, \n, \r, \u0020-\ud7ff, \ue000-\ufffd, and \u10000-\u10ffff
205 # in an XML document. xmlrpclib does not escape these values
206 # properly, and then breaks when it comes to parse the document.
207 # To hack around this problem, we use repr here and exec above
208 # to transmit the string using Python encoding.
209 # Thanks to David Mertz <mertz@gnosis.cx> for the trick (buried
210 # in xml_pickle.py).
211 if isinstance(response, StringTypes):
212 response = repr(response)[1:-1]
214 response = (response,)
215 response = xmlrpclib.dumps(response,
216 methodresponse=1,
217 allow_none=1)
218 except xmlrpclib.Fault, fault:
219 response = xmlrpclib.dumps(fault)
220 except Exception, exn:
221 if self.xenapi:
222 if _is_not_supported(exn):
223 errdesc = ['MESSAGE_METHOD_UNKNOWN', method]
224 else:
225 log.exception('Internal error handling %s', method)
226 errdesc = ['INTERNAL_ERROR', str(exn)]
227 response = xmlrpclib.dumps(
228 ({ "Status": "Failure",
229 "ErrorDescription": errdesc },),
230 methodresponse = 1)
231 else:
232 log.exception('Internal error handling %s', method)
233 import xen.xend.XendClient
234 response = xmlrpclib.dumps(
235 xmlrpclib.Fault(xen.xend.XendClient.ERROR_INTERNAL, str(exn)))
236 return response
239 notSupportedRE = re.compile(r'method "(.*)" is not supported')
240 def _is_not_supported(exn):
241 try:
242 m = notSupportedRE.search(exn[0])
243 return m is not None
244 except TypeError, e:
245 return False
248 # This is a XML-RPC server that sits on a Unix domain socket.
249 # It implements proper support for allow_reuse_address by
250 # unlink()'ing an existing socket.
252 class UnixXMLRPCRequestHandler(XMLRPCRequestHandler):
253 def address_string(self):
254 try:
255 return XMLRPCRequestHandler.address_string(self)
256 except ValueError, e:
257 return self.client_address[:2]
259 class UnixXMLRPCServer(TCPXMLRPCServer):
260 address_family = socket.AF_UNIX
261 allow_address_reuse = True
263 def __init__(self, addr, allowed, xenapi, logRequests = 1):
264 mkdir.parents(os.path.dirname(addr), stat.S_IRWXU, True)
265 if self.allow_reuse_address and os.path.exists(addr):
266 os.unlink(addr)
268 TCPXMLRPCServer.__init__(self, addr, allowed, xenapi,
269 UnixXMLRPCRequestHandler, logRequests)