source: fedd/fedd_proj.py @ e5a8b44

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

split out internal interfaces

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