source: fedd/fedd_proj.py @ 0c0b13c

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

cross transport proxy operations work!

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