source: fedd/fedd_access.py @ d81971a

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

checkpoint of the resource management stuff

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