source: fedd/fedd_proj.py @ 0a47d52

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

XMLRPC proxy from SOAP and XML

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