ia64/xen-unstable

view tools/python/xen/util/xmlrpclib2.py @ 13219:bfa793180cb9

Fix xmlrpclib2's stringify to cope with floats and longs, removing the need
for explicit type conversions in XendAPI.

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