source: fedd/fedd_proj.py @ 7a8d667

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

reconfigure sshd_config explicitly

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