source: fedd/federation/access.py @ 0b4e272

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

whitespace conversion

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