source: fedd/fedd_proj.py @ 4d48e01

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

xmlrpc access to Create

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