source: fedd/federation/access.py @ 7ca25b3

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

typo: try to get the cert from a different place if the first one fils!

  • 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
380        print self.auth.attrs
381        for u in user:
382            self.log.debug("[lookup_access] Checking access for %s" % \
383                    ((tb, project, u),))
384            if self.auth.check_attribute((tb, project, u), 'access'):
385                self.log.debug("[lookup_access] Access granted")
386                break
387            else:
388                self.log.debug("[lookup_access] Access Denied")
389        else:
390            raise service_error(service_error.access, "Access denied")
391
392        # This maps a valid user to the Emulab projects and users to use
393        found, user_match = self.find_access((tb, project, user))
394       
395        if found == None:
396            raise service_error(service_error.access,
397                    "Access denied - cannot map access")
398
399        # resolve <dynamic> and <same> in found
400        dyn_proj = False
401        dyn_create_user = False
402        dyn_service_user = False
403
404        if found[0].name == "<same>":
405            if project != None:
406                rp.name = project
407            else : 
408                raise service_error(\
409                        service_error.server_config,
410                        "Project matched <same> when no project given")
411        elif found[0].name == "<dynamic>":
412            rp.name = None
413            dyn_proj = True
414        else:
415            rp.name = found[0].name
416        rp.node_types = found[0].node_types;
417
418        if found[1] == "<same>":
419            if user_match == "<any>":
420                if user != None: rcu = user[0]
421                else: raise service_error(\
422                        service_error.server_config,
423                        "Matched <same> on anonymous request")
424            else:
425                rcu = user_match
426        elif found[1] == "<dynamic>":
427            rcu = None
428            dyn_create_user = True
429       
430        if found[2] == "<same>":
431            if user_match == "<any>":
432                if user != None: rsu = user[0]
433                else: raise service_error(\
434                        service_error.server_config,
435                        "Matched <same> on anonymous request")
436            else:
437                rsu = user_match
438        elif found[2] == "<dynamic>":
439            rsu = None
440            dyn_service_user = True
441
442        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
443                owners
444
445    def build_response(self, alloc_id, ap):
446        """
447        Create the SOAP response.
448
449        Build the dictionary description of the response and use
450        fedd_utils.pack_soap to create the soap message.  ap is the allocate
451        project message returned from a remote project allocation (even if that
452        allocation was done locally).
453        """
454        # Because alloc_id is already a fedd_services_types.IDType_Holder,
455        # there's no need to repack it
456        msg = { 
457                'allocID': alloc_id,
458                'emulab': { 
459                    'domain': self.domain,
460                    'boss': self.boss,
461                    'ops': self.ops,
462                    'fileServer': self.fileserver,
463                    'eventServer': self.eventserver,
464                    'project': ap['project']
465                },
466            }
467        if len(self.attrs) > 0:
468            msg['emulab']['fedAttr'] = \
469                [ { 'attribute': x, 'value' : y } \
470                        for x,y in self.attrs.iteritems()]
471        return msg
472
473    def RequestAccess(self, req, fid):
474        """
475        Handle the access request.  Proxy if not for us.
476
477        Parse out the fields and make the allocations or rejections if for us,
478        otherwise, assuming we're willing to proxy, proxy the request out.
479        """
480
481        def gateway_hardware(h):
482            if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE')
483            else: return h
484
485        # The dance to get into the request body
486        if req.has_key('RequestAccessRequestBody'):
487            req = req['RequestAccessRequestBody']
488        else:
489            raise service_error(service_error.req, "No request!?")
490
491        if req.has_key('destinationTestbed'):
492            dt = unpack_id(req['destinationTestbed'])
493
494        if dt == None or dt in self.testbed:
495            # Request for this fedd
496            found, dyn, owners = self.lookup_access(req, fid)
497            restricted = None
498            ap = None
499
500            # If this is a request to export a project and the access project
501            # is not the project to export, access denied.
502            if req.has_key('exportProject'):
503                ep = unpack_id(req['exportProject'])
504                if ep != found[0].name:
505                    raise service_error(service_error.access,
506                            "Cannot export %s" % ep)
507
508            # Check for access to restricted nodes
509            if req.has_key('resources') and req['resources'].has_key('node'):
510                resources = req['resources']
511                restricted = [ gateway_hardware(t) for n in resources['node'] \
512                                if n.has_key('hardware') \
513                                    for t in n['hardware'] \
514                                        if gateway_hardware(t) \
515                                            in self.restricted ]
516                inaccessible = [ t for t in restricted \
517                                    if t not in found[0].node_types]
518                if len(inaccessible) > 0:
519                    raise service_error(service_error.access,
520                            "Access denied (nodetypes %s)" % \
521                            str(', ').join(inaccessible))
522            # These collect the keys for the two roles into single sets, one
523            # for creation and one for service.  The sets are a simple way to
524            # eliminate duplicates
525            create_ssh = set([ x['sshPubkey'] \
526                    for x in req['createAccess'] \
527                        if x.has_key('sshPubkey')])
528
529            service_ssh = set([ x['sshPubkey'] \
530                    for x in req['serviceAccess'] \
531                        if x.has_key('sshPubkey')])
532
533            if len(create_ssh) > 0 and len(service_ssh) >0: 
534                if dyn[1]: 
535                    # Compose the dynamic project request
536                    # (only dynamic, dynamic currently allowed)
537                    preq = { 'AllocateProjectRequestBody': \
538                                { 'project' : {\
539                                    'user': [ \
540                                    { \
541                                        'access': [ { 'sshPubkey': s } \
542                                            for s in service_ssh ], 
543                                         'role': "serviceAccess",\
544                                    }, \
545                                    { \
546                                        'access': [ { 'sshPubkey': s } \
547                                            for s in create_ssh ], 
548                                         'role': "experimentCreation",\
549                                    }, \
550                                    ], \
551                                    }\
552                                }\
553                            }
554                    if restricted != None and len(restricted) > 0:
555                        preq['AllocateProjectRequestBody']['resources'] = \
556                             {'node': [ { 'hardware' :  [ h ] } \
557                                    for h in restricted ] } 
558                    ap = self.allocate_project.dynamic_project(preq)
559                else:
560                    preq = {'StaticProjectRequestBody' : \
561                            { 'project': \
562                                { 'name' : { 'localname' : found[0].name },\
563                                  'user' : [ \
564                                    {\
565                                        'userID': { 'localname' : found[1] }, \
566                                        'access': [ { 'sshPubkey': s } 
567                                            for s in create_ssh ],
568                                        'role': 'experimentCreation'\
569                                    },\
570                                    {\
571                                        'userID': { 'localname' : found[2] }, \
572                                        'access': [ { 'sshPubkey': s } 
573                                            for s in service_ssh ],
574                                        'role': 'serviceAccess'\
575                                    },\
576                                ]}\
577                            }\
578                    }
579                    if restricted != None and len(restricted) > 0:
580                        preq['StaticProjectRequestBody']['resources'] = \
581                            {'node': [ { 'hardware' :  [ h ] } \
582                                    for h in restricted ] } 
583                    ap = self.allocate_project.static_project(preq)
584            else:
585                raise service_error(service_error.req, 
586                        "SSH access parameters required")
587            # keep track of what's been added
588            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
589            aid = unicode(allocID)
590
591            self.state_lock.acquire()
592            self.allocation[aid] = { }
593            try:
594                pname = ap['project']['name']['localname']
595            except KeyError:
596                pname = None
597
598            if dyn[1]:
599                if not pname:
600                    self.state_lock.release()
601                    raise service_error(service_error.internal,
602                            "Misformed allocation response?")
603                if self.projects.has_key(pname): self.projects[pname] += 1
604                else: self.projects[pname] = 1
605                self.allocation[aid]['project'] = pname
606
607            if ap.has_key('resources'):
608                if not pname:
609                    self.state_lock.release()
610                    raise service_error(service_error.internal,
611                            "Misformed allocation response?")
612                self.allocation[aid]['types'] = set()
613                nodes = ap['resources'].get('node', [])
614                for n in nodes:
615                    for h in n.get('hardware', []):
616                        if self.types.has_key((pname, h)):
617                            self.types[(pname, h)] += 1
618                        else:
619                            self.types[(pname, h)] = 1
620                        self.allocation[aid]['types'].add((pname,h))
621
622
623            self.allocation[aid]['keys'] = [ ]
624
625            try:
626                for u in ap['project']['user']:
627                    uname = u['userID']['localname']
628                    for k in [ k['sshPubkey'] for k in u['access'] \
629                            if k.has_key('sshPubkey') ]:
630                        kv = "%s:%s" % (uname, k)
631                        if self.keys.has_key(kv): self.keys[kv] += 1
632                        else: self.keys[kv] = 1
633                        self.allocation[aid]['keys'].append((uname, k))
634            except KeyError:
635                self.state_lock.release()
636                raise service_error(service_error.internal,
637                        "Misformed allocation response?")
638
639
640            self.allocation[aid]['owners'] = owners
641            self.write_state()
642            self.state_lock.release()
643            for o in owners:
644                self.auth.set_attribute(o, allocID)
645            resp = self.build_response({ 'fedid': allocID } , ap)
646            return resp
647        else:
648            if self.allow_proxy:
649                resp = self.proxy_RequestAccess.call_service(dt, req,
650                            self.cert_file, self.cert_pwd,
651                            self.trusted_certs)
652                if resp.has_key('RequestAccessResponseBody'):
653                    return resp['RequestAccessResponseBody']
654                else:
655                    return None
656            else:
657                raise service_error(service_error.access,
658                        "Access proxying denied")
659
660    def ReleaseAccess(self, req, fid):
661        # The dance to get into the request body
662        if req.has_key('ReleaseAccessRequestBody'):
663            req = req['ReleaseAccessRequestBody']
664        else:
665            raise service_error(service_error.req, "No request!?")
666
667        if req.has_key('destinationTestbed'):
668            dt = unpack_id(req['destinationTestbed'])
669        else:
670            dt = None
671
672        if dt == None or dt in self.testbed:
673            # Local request
674            try:
675                if req['allocID'].has_key('localname'):
676                    auth_attr = aid = req['allocID']['localname']
677                elif req['allocID'].has_key('fedid'):
678                    aid = unicode(req['allocID']['fedid'])
679                    auth_attr = req['allocID']['fedid']
680                else:
681                    raise service_error(service_error.req,
682                            "Only localnames and fedids are understood")
683            except KeyError:
684                raise service_error(service_error.req, "Badly formed request")
685
686            self.log.debug("[access] deallocation requested for %s", aid)
687            if not self.auth.check_attribute(fid, auth_attr):
688                self.log.debug("[access] deallocation denied for %s", aid)
689                raise service_error(service_error.access, "Access Denied")
690
691            # If we know this allocation, reduce the reference counts and
692            # remove the local allocations.  Otherwise report an error.  If
693            # there is an allocation to delete, del_users will be a dictonary
694            # of sets where the key is the user that owns the keys in the set.
695            # We use a set to avoid duplicates.  del_project is just the name
696            # of any dynamic project to delete.  We're somewhat lazy about
697            # deleting authorization attributes.  Having access to something
698            # that doesn't exist isn't harmful.
699            del_users = { }
700            del_project = None
701            del_types = set()
702
703            if self.allocation.has_key(aid):
704                self.log.debug("Found allocation for %s" %aid)
705                self.state_lock.acquire()
706                for k in self.allocation[aid]['keys']:
707                    kk = "%s:%s" % k
708                    self.keys[kk] -= 1
709                    if self.keys[kk] == 0:
710                        if not del_users.has_key(k[0]):
711                            del_users[k[0]] = set()
712                        del_users[k[0]].add(k[1])
713                        del self.keys[kk]
714
715                if self.allocation[aid].has_key('project'):
716                    pname = self.allocation[aid]['project']
717                    self.projects[pname] -= 1
718                    if self.projects[pname] == 0:
719                        del_project = pname
720                        del self.projects[pname]
721
722                if self.allocation[aid].has_key('types'):
723                    for t in self.allocation[aid]['types']:
724                        self.types[t] -= 1
725                        if self.types[t] == 0:
726                            if not del_project: del_project = t[0]
727                            del_types.add(t[1])
728                            del self.types[t]
729
730                del self.allocation[aid]
731                self.write_state()
732                self.state_lock.release()
733                # If we actually have resources to deallocate, prepare the call.
734                if del_project or del_users:
735                    msg = { 'project': { }}
736                    if del_project:
737                        msg['project']['name']= {'localname': del_project}
738                    users = [ ]
739                    for u in del_users.keys():
740                        users.append({ 'userID': { 'localname': u },\
741                            'access' :  \
742                                    [ {'sshPubkey' : s } for s in del_users[u]]\
743                        })
744                    if users: 
745                        msg['project']['user'] = users
746                    if len(del_types) > 0:
747                        msg['resources'] = { 'node': \
748                                [ {'hardware': [ h ] } for h in del_types ]\
749                            }
750                    if self.allocate_project.release_project:
751                        msg = { 'ReleaseProjectRequestBody' : msg}
752                        self.allocate_project.release_project(msg)
753                return { 'allocID': req['allocID'] } 
754            else:
755                raise service_error(service_error.req, "No such allocation")
756
757        else:
758            if self.allow_proxy:
759                resp = self.proxy_ReleaseAccess.call_service(dt, req,
760                            self.cert_file, self.cert_pwd,
761                            self.trusted_certs)
762                if resp.has_key('ReleaseAccessResponseBody'):
763                    return resp['ReleaseAccessResponseBody']
764                else:
765                    return None
766            else:
767                raise service_error(service_error.access,
768                        "Access proxying denied")
769
770
Note: See TracBrowser for help on using the repository browser.