source: fedd/fedd_access.py @ c971895

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

clean up imports

  • Property mode set to 100644
File size: 23.3 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9
10from threading import *
11
12from fedd_services import *
13from fedd_util import *
14from fedd_allocate_project import *
15from fedd_access_project import access_project
16from fedid import fedid, generate_fedid
17import parse_detail
18from service_error import *
19from remote_service import xmlrpc_handler, soap_handler, service_caller
20
21
22# Make log messages disappear if noone configures a fedd logger
23class nullHandler(logging.Handler):
24    def emit(self, record): pass
25
26fl = logging.getLogger("fedd.access")
27fl.addHandler(nullHandler())
28
29class fedd_access:
30    """
31    The implementation of access control based on mapping users to projects.
32
33    Users can be mapped to existing projects or have projects created
34    dynamically.  This implements both direct requests and proxies.
35    """
36
37    class parse_error(RuntimeError): pass
38
39    bool_attrs = ("dynamic_projects", "project_priority")
40    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
41    id_attrs = ("testbed", "proxy",
42            "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
43            "dynamic_projects_url", "dynamic_projects_cert_file", 
44            "dynamic_projects_cert_pwd", "dynamic_projects_trusted_certs")
45    id_list_attrs = ("restricted",)
46
47    proxy_RequestAccess= \
48            service_caller('RequestAccess', 'getfeddPortType', 
49                    feddServiceLocator, RequestAccessRequestMessage,
50                    'RequestAccessRequestBody')
51
52    def __init__(self, config=None):
53        """
54        Initializer.  Pulls parameters out of the ConfigParser's access section.
55        """
56
57        # Make sure that the configuration is in place
58        if config: 
59            if not config.has_section("access"):
60                config.add_section("access")
61            if not config.has_section("globals"):
62                config.add_section("globals")
63        else:
64            raise RunTimeError("No config to fedd_access")
65
66
67        # Create instance attributes from the static lists
68        for a in fedd_access.bool_attrs:
69            if config.has_option("access", a):
70                setattr(self, a, config.get("access", a))
71            else:
72                setattr(self, a, False)
73
74        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
75            if config.has_option("access", a):
76                setattr(self, a, config.get("access",a))
77            else:
78                setattr(self, a, None)
79
80        self.attrs = { }
81        self.access = { }
82        self.restricted = [ ]
83        self.fedid_category = { }
84        self.projects = { }
85        self.keys = { }
86        self.allocation = { }
87        self.state = { 
88            'projects': self.projects,
89            'allocation' : self.allocation,
90            'keys' : self.keys
91        }
92        self.state_lock = Lock()
93        self.fedid_default = "testbed"
94        if config.has_option("access", "accessdb"):
95            self.read_access(config.get("access", "accessdb"))
96        if config.has_option("access", "trustdb"):
97            self.read_trust(config.get("access", "trustdb"))
98
99        self.state_filename = config.get("access", "access_state", "")
100        self.log = logging.getLogger("fedd.access")
101        set_log_level(config, "access", self.log)
102        self.read_state()
103
104
105        # Certs are promoted from the generic to the specific, so without a
106        # specific proxy certificate, the main certificates are used for
107        # proxy interactions. If no dynamic project certificates, then
108        # proxy certs are used, and if none of those the main certs.
109
110        if config.has_option("globals", "proxy_cert_file"):
111            if not self.dynamic_projects_cert_file:
112                self.dynamic_projects_cert_file = \
113                        config.get("globals", "proxy_cert_file")
114                if config.has_option("globals", "porxy_cert_pwd"):
115                    self.dynamic_projects_cert_pwd = \
116                            config.get("globals", "proxy_cert_pwd")
117
118        if config.has_option("globals", "proxy_trusted_certs"):
119            if not self.dynamic_projects_trusted_certs:
120                self.dynamic_projects_trusted_certs =\
121                        config.get("globals", proxy_trusted_certs)
122
123        if config.has_option("globals", "cert_file"):
124            has_pwd = config.has_option("globals", "cert_pwd")
125            if not self.dynamic_projects_cert_file:
126                self.dynamic_projects_cert_file = \
127                        config.get("globals", "cert_file")
128                if has_pwd: 
129                    self.dynamic_projects_cert_pwd = \
130                            config.get("globals", "cert_pwd")
131            if not self.proxy_cert_file:
132                self.proxy_cert_file = config.get("globals", "cert_file")
133                if has_pwd:
134                    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
135
136        if config.get("globals", "trusted_certs"):
137            if not self.proxy_trusted_certs:
138                self.proxy_trusted_certs = \
139                        config.get("globals", "trusted_certs")
140            if not self.dynamic_projects_trusted_certs:
141                self.dynamic_projects_trusted_certs = \
142                        config.get("globals", "trusted_certs")
143
144        proj_certs = (self.dynamic_projects_cert_file, 
145                self.dynamic_projects_trusted_certs,
146                self.dynamic_projects_cert_pwd)
147
148        self.soap_services = {\
149            'RequestAccess': soap_handler(\
150                RequestAccessRequestMessage.typecode,\
151                self.RequestAccess, RequestAccessResponseMessage,\
152                "RequestAccessResponseBody"), \
153            'ReleaseAccess': soap_handler(\
154                ReleaseAccessRequestMessage.typecode,\
155                self.ReleaseAccess, ReleaseAccessResponseMessage,\
156                "ReleaseAccessResponseBody")\
157            }
158        self.xmlrpc_services =  {\
159            'RequestAccess': xmlrpc_handler(\
160                self.RequestAccess, "RequestAccessResponseBody"),\
161            'ReleaseAccess': xmlrpc_handler(\
162                self.ReleaseAccess, "ReleaseAccessResponseBody")\
163            }
164
165
166        if not config.has_option("access", "dynamic_projects_url"):
167            self.allocate_project = \
168                fedd_allocate_project_local(config)
169        else:
170            self.allocate_project = \
171                fedd_allocate_project_remote(config)
172
173        # If the project allocator exports services, put them in this object's
174        # maps so that classes that instantiate this can call the services.
175        self.soap_services.update(self.allocate_project.soap_services)
176        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
177
178    def read_trust(self, trust):
179        """
180        Read a trust file that splits fedids into testbeds, users or projects
181
182        Format is:
183
184        [type]
185        fedid
186        fedid
187        default: type
188        """
189        lineno = 0;
190        cat = None
191        cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE)
192        fedid_re = re.compile("[" + string.hexdigits + "]+$")
193        default_re = re.compile("default:\s*(user|testbed|project)$", 
194                re.IGNORECASE)
195
196        f = open(trust, "r")
197        for line in f:
198            lineno += 1
199            line = line.strip()
200            if len(line) == 0 or line.startswith("#"):
201                continue
202            # Category line
203            m = cat_re.match(line)
204            if m != None:
205                cat = m.group(1).lower()
206                continue
207            # Fedid line
208            m = fedid_re.match(line)
209            if m != None:
210                if cat != None:
211                    self.fedid_category[fedid(hexstr=m.string)] = cat
212                else:
213                    raise self.parse_error(\
214                            "Bad fedid in trust file (%s) line: %d" % \
215                            (trust, lineno))
216                continue
217            # default line
218            m = default_re.match(line)
219            if m != None:
220                self.fedid_default = m.group(1).lower()
221                continue
222            # Nothing matched - bad line, raise exception
223            f.close()
224            raise self.parse_error(\
225                    "Unparsable line in trustfile %s line %d" % (trust, lineno))
226        f.close()
227
228    def read_access(self, config):
229        """
230        Read a configuration file and set internal parameters.
231
232        The format is more complex than one might hope.  The basic format is
233        attribute value pairs separated by colons(:) on a signle line.  The
234        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
235        directly using the name: value syntax.  E.g.
236        boss: hostname
237        sets self.boss to hostname.  In addition, there are access lines of the
238        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
239        names to the second for access purposes.  Names in the key (left side)
240        can include "<NONE> or <ANY>" to act as wildcards or to require the
241        fields to be empty.  Similarly aproj or auser can be <SAME> or
242        <DYNAMIC> indicating that either the matching key is to be used or a
243        dynamic user or project will be created.  These names can also be
244        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
245        can be followed with a colon-separated list of node types to which that
246        project has access (or will have access if dynamic).
247        Testbed attributes outside the forms above can be given using the
248        format attribute: name value: value.  The name is a single word and the
249        value continues to the end of the line.  Empty lines and lines startin
250        with a # are ignored.
251
252        Parsing errors result in a self.parse_error exception being raised.
253        """
254        lineno=0
255        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
256        fedid_expr = "fedid:[" + string.hexdigits + "]+"
257        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
258        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
259                "<SAME>" + "(?::" + name_expr + ")*|" + \
260                fedid_expr + "(?::" + name_expr + ")*|" + \
261                name_expr + "(?::" + name_expr + ")*)"
262        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
263
264        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
265        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
266                re.IGNORECASE)
267        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
268                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
269                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
270
271        def parse_name(n):
272            if n.startswith('fedid:'): return fedid(n[len('fedid:'):])
273            else: return n
274
275        f = open(config, "r");
276        for line in f:
277            lineno += 1
278            line = line.strip();
279            if len(line) == 0 or line.startswith('#'):
280                continue
281
282            # Extended (attribute: x value: y) attribute line
283            m = attr_re.match(line)
284            if m != None:
285                attr, val = m.group(1,2)
286                self.attrs[attr] = val
287                continue
288
289            # Restricted entry
290            m = restricted_re.match(line)
291            if m != None:
292                val = m.group(1)
293                self.restricted.append(val)
294                continue
295
296            # Access line (t, p, u) -> (ap, cu, su) line
297            m = access_re.match(line)
298            if m != None:
299                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
300                aps = m.group(4).split(":");
301                if aps[0] == 'fedid:':
302                    del aps[0]
303                    aps[0] = fedid(hexstr=aps[0])
304
305                cu = parse_name(m.group(5))
306                su = parse_name(m.group(6))
307
308                access_val = (access_project(aps[0], aps[1:]),
309                        parse_name(m.group(5)), parse_name(m.group(6)))
310
311                self.access[access_key] = access_val
312                continue
313
314            # Nothing matched to here: unknown line - raise exception
315            f.close()
316            raise self.parse_error("Unknown statement at line %d of %s" % \
317                    (lineno, config))
318        f.close()
319
320
321    def dump_state(self):
322        """
323        Dump the state read from a configuration file.  Mostly for debugging.
324        """
325        for a in fedd_access.bool_attrs:
326            print "%s: %s" % (a, getattr(self, a ))
327        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
328            print "%s: %s" % (a, getattr(self, a))
329        for k, v in self.attrs.iteritems():
330            print "%s %s" % (k, v)
331        print "Access DB:"
332        for k, v in self.access.iteritems():
333            print "%s %s" % (k, v)
334        print "Trust DB:"
335        for k, v in self.fedid_category.iteritems():
336            print "%s %s" % (k, v)
337        print "Restricted: %s" % str(',').join(sorted(self.restricted))
338
339    def get_users(self, obj):
340        """
341        Return a list of the IDs of the users in dict
342        """
343        if obj.has_key('user'):
344            return [ unpack_id(u['userID']) \
345                    for u in obj['user'] if u.has_key('userID') ]
346        else:
347            return None
348
349    def write_state(self):
350        try:
351            f = open(self.state_filename, 'w')
352            pickle.dump(self.state, f)
353        except IOError, e:
354            self.log.error("Can't write file %s: %s" % \
355                    (self.state_filename, e))
356        except pickle.PicklingError, e:
357            self.log.error("Pickling problem: %s" % e)
358        except TypeError, e:
359            self.log.error("Pickling problem (TypeError): %s" % e)
360
361
362    def read_state(self):
363        """
364        Read a new copy of access state.  Old state is overwritten.
365
366        State format is a simple pickling of the state dictionary.
367        """
368        try:
369            f = open(self.state_filename, "r")
370            self.state = pickle.load(f)
371
372            self.allocation = self.state['allocation']
373            self.projects = self.state['projects']
374            self.keys = self.state['keys']
375
376            self.log.debug("[read_state]: Read state from %s" % \
377                    self.state_filename)
378        except IOError, e:
379            self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
380                    % (self.state_filename, e))
381        except EOFError, e:
382            self.log.warning("[read_state]: Empty or damaged state file: %s:"\
383                    % self.state_filename)
384        except pickle.UnpicklingError, e:
385            self.log.warning(("[read_state]: No saved state: " + \
386                    "Unpickling failed: %s") % e)
387
388    def permute_wildcards(self, a, p):
389        """Return a copy of a with various fields wildcarded.
390
391        The bits of p control the wildcards.  A set bit is a wildcard
392        replacement with the lowest bit being user then project then testbed.
393        """
394        if p & 1: user = ["<any>"]
395        else: user = a[2]
396        if p & 2: proj = "<any>"
397        else: proj = a[1]
398        if p & 4: tb = "<any>"
399        else: tb = a[0]
400
401        return (tb, proj, user)
402
403    def find_access(self, search):
404        """
405        Search the access DB for a match on this tuple.  Return the matching
406        access tuple and the user that matched.
407       
408        NB, if the initial tuple fails to match we start inserting wildcards in
409        an order determined by self.project_priority.  Try the list of users in
410        order (when wildcarded, there's only one user in the list).
411        """
412        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
413        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
414
415        for p in perm: 
416            s = self.permute_wildcards(search, p)
417            # s[2] is None on an anonymous, unwildcarded request
418            if s[2] != None:
419                for u in s[2]:
420                    if self.access.has_key((s[0], s[1], u)):
421                        return (self.access[(s[0], s[1], u)], u)
422            else:
423                if self.access.has_key(s):
424                    return (self.access[s], None)
425        return None, None
426
427    def lookup_access(self, req, fid):
428        """
429        Determine the allowed access for this request.  Return the access and
430        which fields are dynamic.
431
432        The fedid is needed to construct the request
433        """
434        # Search keys
435        tb = None
436        project = None
437        user = None
438        # Return values
439        rp = access_project(None, ())
440        ru = None
441
442
443        principal_type = self.fedid_category.get(fid, self.fedid_default)
444
445        if principal_type == "testbed": tb = fid
446
447        if req.has_key('project'):
448            p = req['project']
449            if p.has_key('name'):
450                project = unpack_id(p['name'])
451            user = self.get_users(p)
452        else:
453            user = self.get_users(req)
454
455        # Now filter by prinicpal type
456        if principal_type == "user":
457            if user != None:
458                fedids = [ u for u in user if isinstance(u, type(fid))]
459                if len(fedids) > 1:
460                    raise service_error(service_error.req,
461                            "User asserting multiple fedids")
462                elif len(fedids) == 1 and fedids[0] != fid:
463                    raise service_error(service_error.req,
464                            "User asserting different fedid")
465            project = None
466            tb = None
467        elif principal_type == "project":
468            if isinstance(project, type(fid)) and fid != project:
469                raise service_error(service_error.req,
470                        "Project asserting different fedid")
471            tb = None
472
473        # Ready to look up access
474        found, user_match = self.find_access((tb, project, user))
475       
476        if found == None:
477            raise service_error(service_error.access,
478                    "Access denied")
479
480        # resolve <dynamic> and <same> in found
481        dyn_proj = False
482        dyn_create_user = False
483        dyn_service_user = False
484
485        if found[0].name == "<same>":
486            if project != None:
487                rp.name = project
488            else : 
489                raise service_error(\
490                        service_error.server_config,
491                        "Project matched <same> when no project given")
492        elif found[0].name == "<dynamic>":
493            rp.name = None
494            dyn_proj = True
495        else:
496            rp.name = found[0].name
497        rp.node_types = found[0].node_types;
498
499        if found[1] == "<same>":
500            if user_match == "<any>":
501                if user != None: rcu = user[0]
502                else: raise service_error(\
503                        service_error.server_config,
504                        "Matched <same> on anonymous request")
505            else:
506                rcu = user_match
507        elif found[1] == "<dynamic>":
508            rcu = None
509            dyn_create_user = True
510       
511        if found[2] == "<same>":
512            if user_match == "<any>":
513                if user != None: rsu = user[0]
514                else: raise service_error(\
515                        service_error.server_config,
516                        "Matched <same> on anonymous request")
517            else:
518                rsu = user_match
519        elif found[2] == "<dynamic>":
520            rsu = None
521            dyn_service_user = True
522
523        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj)
524
525    def build_response(self, alloc_id, ap):
526        """
527        Create the SOAP response.
528
529        Build the dictionary description of the response and use
530        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
531        a fedd_services_types.IDType_Holder pulled from the incoming message.
532        ap is the allocate project message returned from a remote project
533        allocation (even if that allocation was done locally).
534        """
535        # Because alloc_id is already a fedd_services_types.IDType_Holder,
536        # there's no need to repack it
537        msg = { 
538                'allocID': alloc_id,
539                'emulab': { 
540                    'domain': self.domain,
541                    'boss': self.boss,
542                    'ops': self.ops,
543                    'fileServer': self.fileserver,
544                    'eventServer': self.eventserver,
545                    'project': ap['project']
546                },
547            }
548        if len(self.attrs) > 0:
549            msg['emulab']['fedAttr'] = \
550                [ { 'attribute': x, 'value' : y } \
551                        for x,y in self.attrs.iteritems()]
552        return msg
553
554    def RequestAccess(self, req, fid):
555        """
556        Handle the access request.  Proxy if not for us.
557
558        Parse out the fields and make the allocations or rejections if for us,
559        otherwise, assuming we're willing to proxy, proxy the request out.
560        """
561
562        # The dance to get into the request body
563        if req.has_key('RequestAccessRequestBody'):
564            req = req['RequestAccessRequestBody']
565        else:
566            raise service_error(service_error.req, "No request!?")
567
568        if req.has_key('destinationTestbed'):
569            dt = unpack_id(req['destinationTestbed'])
570
571        if dt == None or dt == self.testbed:
572            # Request for this fedd
573            found, dyn = self.lookup_access(req, fid)
574            restricted = None
575            ap = None
576
577            # Check for access to restricted nodes
578            if req.has_key('resources') and req['resources'].has_key('node'):
579                resources = req['resources']
580                restricted = [ t for n in resources['node'] \
581                                if n.has_key('hardware') \
582                                    for t in n['hardware'] \
583                                        if t in self.restricted ]
584                inaccessible = [ t for t in restricted \
585                                    if t not in found[0].node_types]
586                if len(inaccessible) > 0:
587                    raise service_error(service_error.access,
588                            "Access denied (nodetypes %s)" % \
589                            str(', ').join(inaccessible))
590            # These collect the keys for teh two roles into single sets, one
591            # for creation and one for service.  The sets are a simple way to
592            # eliminate duplicates
593            create_ssh = set([ x['sshPubkey'] \
594                    for x in req['createAccess'] \
595                        if x.has_key('sshPubkey')])
596
597            service_ssh = set([ x['sshPubkey'] \
598                    for x in req['serviceAccess'] \
599                        if x.has_key('sshPubkey')])
600
601            if len(create_ssh) > 0 and len(service_ssh) >0: 
602                if dyn[1]: 
603                    # Compose the dynamic project request
604                    # (only dynamic, dynamic currently allowed)
605                    preq = { 'AllocateProjectRequestBody': \
606                                { 'project' : {\
607                                    'user': [ \
608                                    { \
609                                        'access': [ { 'sshPubkey': s } \
610                                            for s in service_ssh ], 
611                                         'role': "serviceAccess",\
612                                    }, \
613                                    { \
614                                        'access': [ { 'sshPubkey': s } \
615                                            for s in create_ssh ], 
616                                         'role': "experimentCreation",\
617                                    }, \
618                                    ], \
619                                    }\
620                                }\
621                            }
622                    if restricted != None and len(restricted) > 0:
623                        preq['AllocateProjectRequestBody']['resources'] = \
624                            [ {'node': { 'hardware' :  [ h ] } } \
625                                    for h in restricted ]
626                               
627                    ap = self.allocate_project.dynamic_project(preq)
628                else:
629                    preq = {'StaticProjectRequestBody' : \
630                            { 'project': \
631                                { 'name' : { 'localname' : found[0].name },\
632                                  'user' : [ \
633                                    {\
634                                        'userID': { 'localname' : found[1] }, \
635                                        'access': [ { 'sshPubkey': s } 
636                                            for s in create_ssh ],
637                                        'role': 'experimentCreation'\
638                                    },\
639                                    {\
640                                        'userID': { 'localname' : found[2] }, \
641                                        'access': [ { 'sshPubkey': s } 
642                                            for s in service_ssh ],
643                                        'role': 'serviceAccess'\
644                                    },\
645                                ]}\
646                            }\
647                    }
648                    if restricted != None and len(restricted) > 0:
649                        preq['StaticProjectRequestBody']['resources'] = \
650                            [ {'node': { 'hardware' :  [ h ] } } \
651                                    for h in restricted ]
652                    ap = self.allocate_project.static_project(preq)
653            else:
654                raise service_error(service_error.req, 
655                        "SSH access parameters required")
656            # keep track of what's been added
657            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
658            aid = unicode(allocID)
659
660            self.state_lock.acquire()
661            self.allocation[aid] = { }
662            if dyn[1]:
663                try:
664                    pname = ap['project']['name']['localname']
665                except KeyError:
666                    self.state_lock.release()
667                    raise service_error(service_error.internal,
668                            "Misformed allocation response?")
669                if self.projects.has_key(pname): self.projects[pname] += 1
670                else: self.projects[pname] = 1
671                self.allocation[aid]['project'] = pname
672
673            self.allocation[aid]['keys'] = [ ]
674
675            try:
676                for u in ap['project']['user']:
677                    uname = u['userID']['localname']
678                    for k in [ k['sshPubkey'] for k in u['access'] \
679                            if k.has_key('sshPubkey') ]:
680                        kv = "%s:%s" % (uname, k)
681                        if self.keys.has_key(kv): self.keys[kv] += 1
682                        else: self.keys[kv] = 1
683                        self.allocation[aid]['keys'].append((uname, k))
684            except KeyError:
685                self.state_lock.release()
686                raise service_error(service_error.internal,
687                        "Misformed allocation response?")
688
689            self.write_state()
690            self.state_lock.release()
691            resp = self.build_response({ 'fedid': allocID } , ap)
692            return resp
693        else:
694            resp = self.proxy_RequestAccess.call_service(dt, req,
695                        self.proxy_cert_file, self.proxy_cert_pwd,
696                        self.proxy_trusted_certs)
697            if resp.has_key('RequestAccessResponseBody'):
698                return resp['RequestAccessResponseBody']
699            else:
700                return None
701
702    def ReleaseAccess(self, req, fid):
703        # The dance to get into the request body
704        if req.has_key('ReleaseAccessRequestBody'):
705            req = req['ReleaseAccessRequestBody']
706        else:
707            raise service_error(service_error.req, "No request!?")
708
709        try:
710            if req['allocID'].has_key('localname'):
711                aid = req['allocID']['localname']
712            elif req['allocID'].has_key('fedid'):
713                aid = unicode(req['allocID']['fedid'])
714            else:
715                raise service_error(service_error.req,
716                        "Only localnames and fedids are understood")
717        except KeyError:
718            raise service_error(service_error.req, "Badly formed request")
719
720        # If we know this allocation, reduce the reference counts and remove
721        # the local allocations.  Otherwise report an error.  If there is an
722        # allocation to delete, del_users will be a dictonary of sets where the
723        # key is the user that owns the keys in the set.  We use a set to avoid
724        # duplicates.  del_project is just the name of any dynamic project to
725        # delete.
726        del_users = { }
727        del_project = None
728        if self.allocation.has_key(aid):
729            self.state_lock.acquire()
730            for k in self.allocation[aid]['keys']:
731                kk = "%s:%s" % k
732                self.keys[kk] -= 1
733                if self.keys[kk] == 0:
734                    if not del_users.has_key(k[0]):
735                        del_users[k[0]] = set()
736                    del_users[k[0]].add(k[1])
737                    del self.keys[kk]
738
739            if self.allocation[aid].has_key('project'):
740                pname = self.allocation[aid]['project']
741                self.projects[pname] -= 1
742                if self.projects[pname] == 0:
743                    del_project = pname
744                    del self.projects[pname]
745
746            del self.allocation[aid]
747            self.write_state()
748            self.state_lock.release()
749            # If we actually have resources to deallocate, prepare the call.
750            if del_project or del_users:
751                msg = { 'project': { }}
752                if del_project:
753                    msg['project']['name']= {'localname': del_project}
754                users = [ ]
755                for u in del_users.keys():
756                    users.append({ 'userID': { 'localname': u },\
757                        'access' :  \
758                                [ {'sshPubkey' : s } for s in del_users[u]]\
759                    })
760                if users: 
761                    msg['project']['user'] = users
762                if self.allocate_project.release_project:
763                    msg = { 'ReleaseProjectRequestBody' : msg}
764                    self.allocate_project.release_project(msg)
765            return { 'allocID': req['allocID'] } 
766        else:
767            raise service_error(service_error.req, "No such allocation")
768
769
770
Note: See TracBrowser for help on using the repository browser.