source: fedd/fedd_access.py @ dab4d56

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

Resource allocation and deallocation really working
Access handler selects allocation ID
Fedid allocation IDs work
Revamp of util code for maodifying messages (e.g. binaries)
Handlers now see fedids as objects in messages
Fedid bug in handlers in fedd_util

This should have been multiple commits

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