source: fedd/fedd_proj.py @ c52c48d

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

add info and work with SEER attach

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