source: fedd/fedd_proj.py @ 808889e

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

split out project creation

  • Property mode set to 100644
File size: 21.3 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6from ZSI import *
7from M2Crypto import SSL
8from M2Crypto.m2xmlrpclib import SSL_Transport
9from M2Crypto.SSL.SSLServer import SSLServer
10import M2Crypto.httpslib
11import xmlrpclib
12
13import re
14import string
15import subprocess
16import tempfile
17import copy
18
19from fedd_services import *
20from fedd_util import *
21from fedd_allocate_project import *
22import parse_detail
23from service_error import *
24
25class fedd_proj:
26    """
27    The implementation of access control based on mapping users to projects.
28
29    Users can be mapped to existing projects or have projects created
30    dynamically.  This implements both direct requests and proxies.
31    """
32    # Attributes that can be parsed from the configuration file
33    bool_attrs = ("dynamic_projects", "project_priority")
34    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
35    id_attrs = ("testbed", "cert_file", "cert_pwd", "trusted_certs", "proxy",
36            "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
37            "dynamic_projects_url", "dynamic_projects_cert_file", 
38            "dynamic_projects_cert_pwd", "dynamic_projects_trusted_certs")
39
40    # Used by the SOAP caller
41    soap_namespace = 'http://www.isi.edu/faber/fedd.wsdl'
42    soap_methods = { 'RequestAccess': 'soap_RequestAccess' }
43    xmlrpc_methods = { 'RequestAccess': 'xmlrpc_RequestAccess' }
44
45    class access_project:
46        """
47        A project description used to grant access to this testbed.
48
49        The description includes a name and a list of node types to which the
50        project will be granted access.
51        """
52        def __init__(self, name, nt):
53            self.name = name
54            self.node_types = list(nt)
55
56        def __repr__(self):
57            if len(self.node_types) > 0:
58                return "access_proj('%s', ['%s'])" % \
59                        (self.name, str("','").join(self.node_types))
60            else:
61                return "access_proj('%s', [])" % self.name
62
63    # Used to report errors parsing the configuration files, not in providing
64    # service
65    class parse_error(RuntimeError): pass
66
67
68    def __init__(self, config=None):
69        """
70        Initializer.  Parses a configuration if one is given.
71        """
72
73        # Create instance attributes from the static lists
74        for a in fedd_proj.bool_attrs:
75            setattr(self, a, False)
76
77        for a in fedd_proj.emulab_attrs + fedd_proj.id_attrs:
78            setattr(self, a, None)
79
80        # Other attributes
81        self.attrs = {}
82        self.access = {}
83        self.fedid_category = {}
84        self.fedid_default = "user"
85        self.restricted = []
86
87        # Delete these
88        self.wap = '/usr/testbed/sbin/wap'
89        self.newproj = '/usr/testbed/sbin/newproj'
90        self.mkproj = '/usr/testbed/sbin/mkproj'
91        self.grantnodetype = '/usr/testbed/sbin/grantnodetype'
92
93        # Read the configuration
94        if config != None: 
95            self.read_config(config)
96
97        # Certs are promoted from the generic to the specific, so without a
98        # specific proxy certificate, the main certificates are used for proxy
99        # interactions. If no dynamic project certificates, then proxy certs
100        # are used, and if none of those the main certs.
101
102        # init proxy certs
103        if self.proxy_cert_file == None:
104            self.proxy_cert_file = self.cert_file
105            self.proxy_cert_pwd = self.cert_pwd
106
107        if self.proxy_trusted_certs == None:
108            self.proxy_trusted_certs = self.trusted_certs
109
110        # init dynamic project certs
111        if self.dynamic_projects_cert_file == None:
112            self.dynamic_projects_cert_file = self.proxy_cert_file
113            self.dynamic_projects_cert_pwd = self.proxy_cert_pwd
114
115        if self.dynamic_projects_trusted_certs == None:
116            self.dynamic_projects_trusted_certs = self.proxy_trusted_certs
117
118        proj_certs = (self.dynamic_projects_cert_file, 
119                self.dynamic_projects_trusted_certs,
120                self.dynamic_projects_cert_pwd)
121
122        if self.dynamic_projects_url == None:
123            self.allocate_project = \
124                fedd_allocate_project_local(self.dynamic_projects, 
125                        self.dynamic_projects_url, proj_certs)
126            fedd_proj.soap_methods['AllocateProject'] = 'soap_AllocateProject'
127        else:
128            self.allocate_project = \
129                fedd_allocate_project_remote(self.dynamic_projects, 
130                        self.dynamic_projects_url, proj_certs)
131
132    def dump_state(self):
133        """
134        Dump the state read from a configuration file.  Mostly for debugging.
135        """
136        for a in fedd_proj.bool_attrs:
137            print "%s: %s" % (a, getattr(self, a ))
138        for a in fedd_proj.emulab_attrs + fedd_proj.id_attrs:
139            print "%s: %s" % (a, getattr(self, a))
140        for k, v in self.attrs.iteritems():
141            print "%s %s" % (k, v)
142        print "Access DB:"
143        for k, v in self.access.iteritems():
144            print "%s %s" % (k, v)
145        print "Trust DB:"
146        for k, v in self.fedid_category.iteritems():
147            print "%s %s" % (k, v)
148        print "Restricted: %s" % str(',').join(sorted(self.restricted))
149
150    def get_users(self, obj):
151        """
152        Return a list of the IDs of the users in dict
153        """
154        if obj.has_key('user'):
155            return [ unpack_id(u['userID']) \
156                    for u in obj['user'] if u.has_key('userID') ]
157        else:
158            return None
159
160    def strip_unicode(self, obj):
161        """Loosly de-unicode an object"""
162        if isinstance(obj, dict):
163            for k in obj.keys():
164                obj[k] = self.strip_unicode(obj[k])
165            return obj
166        elif isinstance(obj, basestring):
167            return str(obj)
168        elif getattr(obj, "__iter__", None):
169            return [ self.strip_unicode(x) for x in obj]
170        else:
171            return obj
172
173    def proxy_xmlrpc_request(self, dt, req):
174        """Send an XMLRPC proxy request.  Called if the SOAP RPC fails"""
175
176        # No retry loop here.  Proxy servers must correctly authenticate
177        # themselves without help
178        try:
179            ctx = fedd_ssl_context(self.proxy_cert_file, 
180                    self.proxy_trusted_certs, password=self.proxy_cert_pwd)
181        except SSL.SSLError:
182            raise service_error(service_error.server_config,
183                    "Server certificates misconfigured")
184
185        # Of all the dumbass things.  The XMLRPC library in use here won't
186        # properly encode unicode strings, so we make a copy of req with the
187        # unicode objects converted.  We also convert the destination testbed
188        # to a basic string if it isn't one already.
189        if isinstance(dt, str): url = dt
190        else: url = str(dt)
191
192        r = copy.deepcopy(req)
193        self.strip_unicode(r)
194       
195        transport = SSL_Transport(ctx)
196        port = xmlrpclib.ServerProxy(url, transport=transport)
197
198        # Reconstruct the full request message
199        try:
200            resp = port.RequestAccess(
201                    { "RequestAccessRequestBody": r})
202            resp, method = xmlrpclib.loads(resp)
203        except xmlrpclib.Fault, f:
204            se = service_error(None, f.faultString, f.faultCode)
205            raise se
206        except xmlrpclib.Error, e:
207            raise service_error(service_error.proxy, 
208                    "Remote XMLRPC Fault: %s" % e)
209       
210        if resp[0].has_key('RequestAccessResponseBody'):
211            return resp[0]['RequestAccessResponseBody']
212        else:
213            raise service_error(service_error.proxy, 
214                    "Bad proxy response")
215
216    def proxy_request(self, dt, req):
217        """
218        Send req on to the real destination in dt and return the response
219
220        Req is just the requestType object.  This function re-wraps it.  It
221        also rethrows any faults.
222        """
223        # No retry loop here.  Proxy servers must correctly authenticate
224        # themselves without help
225        try:
226            ctx = fedd_ssl_context(self.proxy_cert_file, 
227                    self.proxy_trusted_certs, password=self.proxy_cert_pwd)
228        except SSL.SSLError:
229            raise service_error(service_error.server_config, 
230                    "Server certificates misconfigured")
231
232        loc = feddServiceLocator();
233        port = loc.getfeddPortType(dt,
234                transport=M2Crypto.httpslib.HTTPSConnection, 
235                transdict={ 'ssl_context' : ctx })
236
237        # Reconstruct the full request message
238        msg = RequestAccessRequestMessage()
239        msg.set_element_RequestAccessRequestBody(
240                pack_soap(msg, "RequestAccessRequestBody", req))
241        try:
242            resp = port.RequestAccess(msg)
243        except ZSI.ParseException, e:
244            raise service_error(service_error.proxy,
245                    "Bad format message (XMLRPC??): %s" %
246                    str(e))
247        r = unpack_soap(resp)
248
249        if r.has_key('RequestAccessResponseBody'):
250            return r['RequestAccessResponseBody']
251        else:
252            raise service_error(service_error.proxy,
253                    "Bad proxy response")
254
255    def permute_wildcards(self, a, p):
256        """Return a copy of a with various fields wildcarded.
257
258        The bits of p control the wildcards.  A set bit is a wildcard
259        replacement with the lowest bit being user then project then testbed.
260        """
261        if p & 1: user = ["<any>"]
262        else: user = a[2]
263        if p & 2: proj = "<any>"
264        else: proj = a[1]
265        if p & 4: tb = "<any>"
266        else: tb = a[0]
267
268        return (tb, proj, user)
269
270    def find_access(self, search):
271        """
272        Search the access DB for a match on this tuple.  Return the matching
273        access tuple and the user that matched.
274       
275        NB, if the initial tuple fails to match we start inserting wildcards in
276        an order determined by self.project_priority.  Try the list of users in
277        order (when wildcarded, there's only one user in the list).
278        """
279        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
280        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
281
282        for p in perm: 
283            s = self.permute_wildcards(search, p)
284            # s[2] is None on an anonymous, unwildcarded request
285            if s[2] != None:
286                for u in s[2]:
287                    if self.access.has_key((s[0], s[1], u)):
288                        return (self.access[(s[0], s[1], u)], u)
289            else:
290                if self.access.has_key(s):
291                    return (self.access[s], None)
292        return None
293
294    def lookup_access(self, req, fid):
295        """
296        Determine the allowed access for this request.  Return the access and
297        which fields are dynamic.
298
299        The fedid is needed to construct the request
300        """
301        tb = None
302        project = None
303        user = None
304
305        principal_type = self.fedid_category.get(fid, self.fedid_default)
306
307        if principal_type == "testbed": tb = fid
308
309        if req.has_key('project'):
310            p = req['project']
311            if p.has_key('name'):
312                project = unpack_id(p['name'])
313            user = self.get_users(p)
314        else:
315            user = self.get_users(req)
316
317        # Now filter by prinicpal type
318        if principal_type == "user":
319            if user != None:
320                fedids = [ u for u in user if isinstance(u, type(fid))]
321                if len(fedids) > 1:
322                    raise service_error(service_error.req,
323                            "User asserting multiple fedids")
324                elif len(fedids) == 1 and fedids[0] != fid:
325                    raise service_error(service_error.req,
326                            "User asserting different fedid")
327            project = None
328            tb = None
329        elif principal_type == "project":
330            if isinstance(project, type(fid)) and fid != project:
331                raise service_error(service_error.req,
332                        "Project asserting different fedid")
333            tb = None
334
335        # Ready to look up access
336        found, user_match = self.find_access((tb, project, user))
337       
338        if found == None:
339            raise service_error(service_error.access,
340                    "Access denied")
341
342        # resolve <dynamic> and <same> in found
343        dyn_proj = False
344        dyn_user = False
345
346        if found[0].name == "<same>":
347            if project != None:
348                found[0].name = project
349            else : 
350                raise service_error(\
351                        service_error.server_config,
352                        "Project matched <same> when no project given")
353        elif found[0].name == "<dynamic>":
354            found[0].name = None
355            dyn_proj = True
356
357        if found[1] == "<same>":
358            if user_match == "<any>":
359                if user != None: found = (found[0], user[0])
360                else: raise service_error(\
361                        service_error.server_config,
362                        "Matched <same> on anonymous request")
363            else:
364                found = (found[0], user_match)
365        elif found[1] == "<dynamic>":
366            found = (found[0], None)
367            dyn_user = True
368       
369        return found, (dyn_user, dyn_proj)
370
371    def build_response(self, alloc_id, ap):
372        """
373        Create the SOAP response.
374
375        Build the dictionary description of the response and use
376        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
377        a fedd_services_types.IDType_Holder pulled from the incoming message.
378        ap is the allocate project message returned from a remote project
379        allocation (even if that allocation was done locally).
380        """
381        # Because alloc_id is already a fedd_services_types.IDType_Holder,
382        # there's no need to repack it
383        msg = { 
384                'allocID': alloc_id,
385                'emulab': { 
386                    'domain': self.domain,
387                    'boss': self.boss,
388                    'ops': self.ops,
389                    'fileServer': self.fileserver,
390                    'eventServer': self.eventserver,
391                    'project': ap['project']
392                },
393            }
394        if len(self.attrs) > 0:
395            msg['emulab']['fedAttr'] = \
396                [ { 'attribute': x, 'value' : y } \
397                        for x,y in self.attrs.iteritems()]
398        return msg
399
400    def RequestAccess(self, req, fid):
401
402        if req.has_key('RequestAccessRequestBody'):
403            req = req['RequestAccessRequestBody']
404        else:
405            raise service_error(service_error.req, "No request!?")
406
407        if req.has_key('destinationTestbed'):
408            dt = unpack_id(req['destinationTestbed'])
409       
410        if dt == None or dt == self.testbed:
411            # Request for this fedd
412            found, dyn = self.lookup_access(req, fid)
413            restricted = None
414            ap = None
415
416            # Check for access to restricted nodes
417            if req.has_key('resources') and req['resources'].has_key('node'):
418                resources = req['resources']
419                restricted = [ t for n in resources['node'] \
420                                if n.has_key('hardware') \
421                                    for t in n['hardware'] \
422                                        if t in self.restricted ]
423                inaccessible = [ t for t in restricted \
424                                    if t not in found[0].node_types]
425                if len(inaccessible) > 0:
426                    raise service_error(service_error.access,
427                            "Access denied (nodetypes %s)" % \
428                            str(', ').join(inaccessible))
429
430            ssh = [ x['sshPubkey'] \
431                    for x in req['access'] if x.has_key('sshPubkey')]
432
433            if len(ssh) > 0: 
434                if dyn[1]: 
435                    # Compose the dynamic project request
436                    # (only dynamic, dynamic currently allowed)
437                    preq = { 'AllocateProjectRequestBody': \
438                                { 'project' : {\
439                                    'user': [ \
440                                    { 'access': [ { 'sshPubkey': s } ] } \
441                                        for s in ssh ] \
442                                    }\
443                                }\
444                            }
445                    if restricted != None and len(restricted) > 0:
446                        preq['AllocateProjectRequestBody']['resources'] = \
447                            [ {'node': { 'hardware' :  [ h ] } } \
448                                    for h in restricted ]
449                               
450                    ap = self.allocate_project.dynamic_project(preq)
451                else: pass    # SSH key additions
452            else:
453                raise service_error(service_error.req, 
454                        "SSH access parameters required")
455
456            resp = self.build_response(req['allocID'], ap)
457            return resp
458        else:
459            p_fault = None      # Any SOAP failure (sent unless XMLRPC works)
460            try:
461                # Proxy the request using SOAP
462                return self.proxy_request(dt, req)
463            except service_error, e:
464                if e.code == service_error.proxy: p_fault = None
465                else: raise
466            except ZSI.FaultException, f:
467                p_fault = f.fault.detail[0]
468                   
469
470            # If we could not get a valid SOAP response to the request above,
471            # try the same address using XMLRPC and let any faults flow back
472            # out.
473            if p_fault == None:
474                return self.proxy_xmlrpc_request(dt, req)
475            else:
476                # Build the fault
477                body = p_fault.get_element_RequestAccessFaultBody()
478                if body != None:
479                    raise service_error(body.get_element_code(),
480                                body.get_element_desc());
481                else:
482                    raise service_error(\
483                            service_error.proxy,
484                            "Undefined fault from proxy??");
485
486
487    def soap_AllocateProject(self, ps, fid):
488        req = ps.Parse(AllocateProjectRequestMessage.typecode)
489
490        msg = self.allocate_project.dynamic_project(unpack_soap(req), fedid)
491
492        resp = AllocateProjectResponseMessage()
493        resp.set_element_AllocateProjectResponseBody(
494                pack_soap(resp, "AllocateProjectResponseBody", msg))
495
496        return resp
497
498    def soap_RequestAccess(self, ps, fid):
499        req = ps.Parse(RequestAccessRequestMessage.typecode)
500
501        msg = self.RequestAccess(unpack_soap(req), fedid)
502
503        resp = RequestAccessResponseMessage()
504        resp.set_element_RequestAccessResponseBody(
505                pack_soap(resp, "RequestAccessResponseBody", msg))
506
507        return resp
508
509    def xmlrpc_RequestAccess(self, params, fid):
510        msg = self.RequestAccess(params[0], fedid)
511
512        if msg != None:
513            return xmlrpclib.dumps(({ "RequestAccessResponseBody": msg },))
514        else:
515            raise service_error(service_error.internal,
516                    "No response generated?!");
517
518    def read_trust(self, trust):
519        """
520        Read a trust file that splits fedids into testbeds, users or projects
521
522        Format is:
523
524        [type]
525        fedid
526        fedid
527        default: type
528        """
529        lineno = 0;
530        cat = None
531        cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE)
532        fedid_re = re.compile("[" + string.hexdigits + "]+$")
533        default_re = re.compile("default:\s*(user|testbed|project)$", 
534                re.IGNORECASE)
535
536        f = open(trust, "r")
537        for line in f:
538            lineno += 1
539            line = line.strip()
540            if len(line) == 0 or line.startswith("#"):
541                continue
542            # Category line
543            m = cat_re.match(line)
544            if m != None:
545                cat = m.group(1).lower()
546                continue
547            # Fedid line
548            m = fedid_re.match(line)
549            if m != None:
550                if cat != None:
551                    self.fedid_category[fedid(hexstr=m.string)] = cat
552                else:
553                    raise fedd_proj.parse_error(\
554                            "Bad fedid in trust file (%s) line: %d" % \
555                            (trust, lineno))
556                continue
557            # default line
558            m = default_re.match(line)
559            if m != None:
560                self.fedid_default = m.group(1).lower()
561                continue
562            # Nothing matched - bad line, raise exception
563            f.close()
564            raise fedd_proj.parse_error(\
565                    "Unparsable line in trustfile %s line %d" % (trust, lineno))
566        f.close()
567
568    def read_config(self, config):
569        """
570        Read a configuration file and set internal parameters.
571
572        The format is more complex than one might hope.  The basic format is
573        attribute value pairs separated by colons(:) on a signle line.  The
574        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
575        directly using the name: value syntax.  E.g.
576        boss: hostname
577        sets self.boss to hostname.  In addition, there are access lines of the
578        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
579        names to the second for access purposes.  Names in the key (left side)
580        can include "<NONE> or <ANY>" to act as wildcards or to require the
581        fields to be empty.  Similarly aproj or auser can be <SAME> or
582        <DYNAMIC> indicating that either the matching key is to be used or a
583        dynamic user or project will be created.  These names can also be
584        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
585        can be followed with a colon-separated list of node types to which that
586        project has access (or will have access if dynamic).
587        Testbed attributes outside the forms above can be given using the
588        format attribute: name value: value.  The name is a single word and the
589        value continues to the end of the line.  Empty lines and lines startin
590        with a # are ignored.
591
592        Parsing errors result in a parse_error exception being raised.
593        """
594        lineno=0
595        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
596        fedid_expr = "fedid:[" + string.hexdigits + "]+"
597        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
598        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
599                "<SAME>" + "(?::" + name_expr + ")*|" + \
600                fedid_expr + "(?::" + name_expr + ")*|" + \
601                name_expr + "(?::" + name_expr + ")*)"
602        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
603
604        bool_re = re.compile('(' + '|'.join(fedd_proj.bool_attrs) + 
605                '):\s+(true|false)', re.IGNORECASE)
606        string_re = re.compile( "(" + \
607                '|'.join(fedd_proj.emulab_attrs + fedd_proj.id_attrs) + \
608                '):\s*(.*)', re.IGNORECASE)
609        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
610                re.IGNORECASE)
611        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
612                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
613                access_name + '\s*\)', re.IGNORECASE)
614        trustfile_re = re.compile("trustfile:\s*(.*)", re.IGNORECASE)
615        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
616
617        def parse_name(n):
618            if n.startswith('fedid:'): return fedid(n[len('fedid:'):])
619            else: return n
620
621        f = open(config, "r");
622        for line in f:
623            lineno += 1
624            line = line.strip();
625            if len(line) == 0 or line.startswith('#'):
626                continue
627
628            # Boolean attribute line
629            m = bool_re.match(line);
630            if m != None:
631                attr, val = m.group(1,2)
632                setattr(self, attr.lower(), bool(val.lower() == "true"))
633                continue
634
635            # String attribute line
636            m = string_re.match(line)
637            if m != None:
638                attr, val = m.group(1,2)
639                setattr(self, attr.lower(), val)
640                continue
641
642            # Extended (attribute: x value: y) attribute line
643            m = attr_re.match(line)
644            if m != None:
645                attr, val = m.group(1,2)
646                self.attrs[attr] = val
647                continue
648
649            # Access line (t, p, u) -> (ap, au) line
650            m = access_re.match(line)
651            if m != None:
652                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
653                aps = m.group(4).split(":");
654                if aps[0] == 'fedid:':
655                    del aps[0]
656                    aps[0] = fedid(hexstr=aps[0])
657
658                au = m.group(5)
659                if au.startswith("fedid:"):
660                    au = fedid(hexstr=aus[len("fedid:"):])
661
662                access_val = (fedd_proj.access_project(aps[0], aps[1:]), au)
663
664                self.access[access_key] = access_val
665                continue
666
667            # Trustfile inclusion
668            m = trustfile_re.match(line)
669            if m != None:
670                self.read_trust(m.group(1))
671                continue
672            # Restricted node types
673
674            m = restricted_re.match(line)
675            if m != None:
676                self.restricted.append(m.group(1))
677                continue
678
679            # Nothing matched to here: unknown line - raise exception
680            f.close()
681            raise fedd_proj.parse_error("Unknown statement at line %d of %s" % \
682                    (lineno, config))
683        f.close()
684
685    def soap_dispatch(self, method, req, fid):
686        if fedd_proj.soap_methods.has_key(method):
687            try:
688                return getattr(self, fedd_proj.soap_methods[method])(req, fid)
689            except service_error, e:
690                de = ns0.faultType_Def(
691                        (ns0.faultType_Def.schema,
692                            "RequestAccessFaultBody")).pyclass()
693                de._code=e.code
694                de._errstr=e.code_string()
695                de._desc=e.desc
696                if  e.is_server_error():
697                    raise Fault(Fault.Server, e.code_string(), detail=de)
698                else:
699                    raise Fault(Fault.Client, e.code_string(), detail=de)
700        else:
701            raise Fault(Fault.Client, "Unknown method: %s" % method)
702
703    def xmlrpc_dispatch(self, method, req, fid):
704        if fedd_proj.xmlrpc_methods.has_key(method):
705            try:
706                return getattr(self, fedd_proj.xmlrpc_methods[method])(req, fid)
707            except service_error, e:
708                raise xmlrpclib.Fault(e.code_string(), e.desc)
709        else:
710            raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
711
712def new_feddservice(configfile):
713    return fedd_proj(configfile)
Note: See TracBrowser for help on using the repository browser.