source: fedd/federation/access.py @ 416292f

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

Cleanup

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