source: fedd/fedd_proj.py @ 987aaa1

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

get topo and vis data, persistent state

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