source: fedd/fedd.py @ 11a08b0

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 11a08b0 was 11a08b0, checked in by Ted Faber <faber@…>, 16 years ago

decent logging

  • Property mode set to 100755
File size: 8.5 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6
7from ZSI import Fault, ParseException, FaultFromNotUnderstood, \
8    FaultFromZSIException, FaultFromException, ParsedSoap, SoapWriter
9
10from M2Crypto import SSL
11from M2Crypto.SSL.SSLServer import ThreadingSSLServer
12import xmlrpclib
13
14from optparse import OptionParser
15
16from fedd_util import fedd_ssl_context, fedid
17from fedd_deter_impl import new_feddservice
18from service_error import *
19
20from threading import *
21from signal import signal, pause, SIGINT, SIGTERM
22from select import select
23import logging
24
25# The SSL server here is based on the implementation described at
26# http://www.xml.com/pub/a/ws/2004/01/20/salz.html
27
28# Turn off the matching of hostname to certificate ID
29SSL.Connection.clientPostConnectionCheck = None
30
31class fedd_server(ThreadingSSLServer):
32    def __init__(self, ME, handler, ssl_ctx, impl):
33        ThreadingSSLServer.__init__(self, ME, handler, ssl_ctx)
34        self.impl = impl
35        self.soap_methods = impl.get_soap_services()
36        self.xmlrpc_methods = impl.get_xmlrpc_services()
37
38class fedd_soap_handler(BaseHTTPRequestHandler):
39    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
40
41    def send_xml(self, text, code=200):
42        """Send an XML document as reply"""
43        self.send_response(code)
44        self.send_header('Content-type', 'text/xml; charset="utf-8"')
45        self.send_header('Content-Length', str(len(text)))
46        self.end_headers()
47        self.wfile.write(text)
48        self.wfile.flush()
49
50    def send_fault(self, f, code=500):
51        """Send a SOAP encoded fault as reply"""
52        self.send_xml(f.AsSOAP(processContents="lax"), code)
53
54    def check_headers(self, ps):
55        """Send a fault for any required envelope headers"""
56        for (uri, localname) in ps.WhatMustIUnderstand():
57            self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd'))
58            return False
59        return  True
60
61    def check_method(self, ps):
62        """Confirm that this class implements the namespace and SOAP method"""
63        root = ps.body_root
64        if root.namespaceURI not in self.server.impl.soap_namespaces:
65            self.send_fault(Fault(Fault.Client, 
66                'Unknown namespace "%s"' % root.namespaceURI))
67            return False
68
69        if getattr(root, 'localName', None) == None:
70            self.send_fault(Fault(Fault.Client, 'No method"'))
71            return False
72        return True
73
74    def do_POST(self):
75        """Treat an HTTP POST request as a SOAP service call"""
76        try:
77            cl = int(self.headers['content-length'])
78            data = self.rfile.read(cl)
79            ps = ParsedSoap(data)
80        except ParseException, e:
81            self.send_fault(Fault(Fault.Client, str(e)))
82            return
83        except Exception, e:
84            self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
85            return
86        if not self.check_headers(ps): return
87        if not self.check_method(ps): return
88        try:
89            resp = self.soap_dispatch(ps.body_root.localName, ps,
90                    fedid(cert=self.request.get_peer_cert()))
91        except Fault, f:
92            self.send_fault(f)
93            resp = None
94       
95        if resp != None:
96            sw = SoapWriter()
97            sw.serialize(resp)
98            self.send_xml(str(sw))
99
100    def soap_dispatch(self, method, req, fid):
101        if self.server.soap_methods.has_key(method):
102            try:
103                return self.server.soap_methods[method](req, fid)
104            except service_error, e:
105                de = ns0.faultType_Def(
106                        (ns0.faultType_Def.schema,
107                            "FeddFaultBody")).pyclass()
108                de._code=e.code
109                de._errstr=e.code_string()
110                de._desc=e.desc
111                if  e.is_server_error():
112                    raise Fault(Fault.Server, e.code_string(), detail=de)
113                else:
114                    raise Fault(Fault.Client, e.code_string(), detail=de)
115        else:
116            raise Fault(Fault.Client, "Unknown method: %s" % method)
117
118
119class fedd_xmlrpc_handler(BaseHTTPRequestHandler):
120    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
121
122    def send_xml(self, text, code=200):
123        """Send an XML document as reply"""
124        self.send_response(code)
125        self.send_header('Content-type', 'text/xml; charset="utf-8"')
126        self.send_header('Content-Length', str(len(text)))
127        self.end_headers()
128        self.wfile.write(text)
129        self.wfile.flush()
130
131    def do_POST(self):
132        """Treat an HTTP POST request as an XMLRPC service call"""
133
134        resp = None
135        data = None
136        method = None
137        cl = int(self.headers['content-length'])
138        data = self.rfile.read(cl)
139
140        try:
141            params, method = xmlrpclib.loads(data)
142        except xmlrpclib.ResponseError:
143            data = xmlrpclib.dumps(xmlrpclib.Fault("Client", 
144                "Malformed request"), methodresponse=True)
145       
146        if method != None:
147            try:
148                resp = self.xmlrpc_dispatch(method, params,
149                            fedid(cert=self.request.get_peer_cert()))
150                data = xmlrpclib.dumps((resp,), encoding='UTF-8', 
151                        methodresponse=True)
152            except xmlrpclib.Fault, f:
153                data = xmlrpclib.dumps(f, methodresponse=True)
154                resp = None
155        self.send_xml(data)
156
157
158    def xmlrpc_dispatch(self, method, req, fid):
159        if self.server.xmlrpc_methods.has_key(method):
160            try:
161                return self.server.xmlrpc_methods[method](req, fid)
162            except service_error, e:
163                raise xmlrpclib.Fault(e.code_string(), e.desc)
164        else:
165            raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
166
167class fedd_opts(OptionParser):
168    """Encapsulate option processing in this class, rather than in main"""
169    def __init__(self):
170        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
171                version="0.1")
172
173        self.set_defaults(host="localhost", port=23235, transport="soap",
174                logfile=None, debug=0)
175
176        self.add_option("-d", "--debug", action="count", dest="debug", 
177                help="Set debug.  Repeat for more information")
178        self.add_option("-f", "--configfile", action="store",
179                dest="configfile", help="Configuration file (required)")
180        self.add_option("-H", "--host", action="store", type="string",
181                dest="host", help="Hostname to listen on (default %default)")
182        self.add_option("-l", "--logfile", action="store", dest="logfile", 
183                help="File to send log messages to")
184        self.add_option("-p", "--port", action="store", type="int",
185                dest="port", help="Port to listen on (default %default)")
186        self.add_option("-s", "--service", action="append", type="string",
187                dest="services",
188                help="Service description: host:port:transport")
189        self.add_option("-x","--transport", action="store", type="choice",
190                choices=("xmlrpc", "soap"),
191                help="Transport for request (xmlrpc|soap) (Default: %default)")
192        self.add_option("--trace", action="store_const", dest="tracefile", 
193                const=sys.stderr, help="Print SOAP exchange to stderr")
194
195servers_active = True
196
197log_params = {\
198        'format': "%(asctime)s %(levelname)-8s %(message)s",\
199        'datefmt': '%a, %d %b %Y %H:%M:%S'\
200    }
201
202def shutdown(sig, frame):
203    global servers_active, flog
204    servers_active = False
205    flog.info("Received signal %d, shutting down" % sig);
206
207def run_server(s):
208    global servers_active   # Not strictly needed: servers_active is only read
209
210    if s: 
211        while servers_active:
212            i, o, e = select((s,), (), (), 5.0)
213            if s in i:
214                s.handle_request()
215
216services = [ ]
217servers = [ ]
218
219opts, args = fedd_opts().parse_args()
220
221flog = logging.getLogger("fedd")
222ffmt = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s",
223        '%a, %d %b %Y %H:%M:%S')
224
225if opts.logfile: fh = logging.FileHandler(opts.logfile)
226else: fh = logging.StreamHandler(sys.stdout)
227
228# The handler will print anything, setting the logger level will affect what
229# gets recorded.
230fh.setLevel(logging.DEBUG)
231
232if opts.debug: flog.setLevel(logging.DEBUG)
233else: flog.setLevel(logging.INFO)
234
235fh.setFormatter(ffmt)
236flog.addHandler(fh)
237
238
239if opts.configfile != None: 
240    try:
241        impl = new_feddservice(opts.configfile)
242    except RuntimeError, e:
243        str = getattr(e, 'desc', None) or getattr(e,'message', None) or \
244                "No message"
245        sys.exit("Error configuring fedd: %s" % str)
246else: 
247    sys.exit("--configfile is required")
248
249SOAP_port = (opts.host, opts.port)
250
251if impl.cert_file == None:
252    sys.exit("Must supply certificate file (probably in config)")
253
254ctx = None
255while ctx == None:
256    try:
257        ctx = fedd_ssl_context(impl.cert_file, impl.trusted_certs, 
258                password=impl.cert_pwd)
259    except SSL.SSLError, e:
260        if str(e) != "bad decrypt" or impl.cert_pwd != None:
261            raise
262
263if opts.services:
264    for s in opts.services:
265        h, p, t  = s.split(':')
266
267        if not h: h = opts.host
268        if not p: p = opts.port
269        if not t: h = opts.transport
270
271        p = int(p)
272
273        services.append((t, (h, p)))
274else:
275    services.append((opts.transport, (opts.host, opts.port)))
276
277for s in services:
278    if s[0] == "soap":
279        servers.append(fedd_server(s[1], fedd_soap_handler, ctx, impl))
280    elif s[0] == "xmlrpc":
281        servers.append(fedd_server(s[1], fedd_xmlrpc_handler, ctx, impl))
282    else: flog.warning("Unknown transport: %s" % s[0])
283
284signal(SIGINT, shutdown)
285signal(SIGTERM, shutdown)
286
287for s in servers:
288    if s:
289        t = Thread(target=run_server, args=(s,))
290        t.start()
291
292pause()
Note: See TracBrowser for help on using the repository browser.