source: fedd/federation/emulab_access.py @ 1d913e13

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

Potentially uninitialized variables.

  • Property mode set to 100644
File size: 43.4 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9
10from threading import *
11
12from util import *
13from allocate_project import allocate_project_local, allocate_project_remote
14from access_project import access_project
15from fedid import fedid, generate_fedid
16from authorizer import authorizer
17from service_error import service_error
18from remote_service import xmlrpc_handler, soap_handler, service_caller
19
20import httplib
21import tempfile
22from urlparse import urlparse
23
24import topdl
25import list_log
26import proxy_emulab_segment
27import local_emulab_segment
28
29
30# Make log messages disappear if noone configures a fedd logger
31class nullHandler(logging.Handler):
32    def emit(self, record): pass
33
34fl = logging.getLogger("fedd.access")
35fl.addHandler(nullHandler())
36
37class access:
38    """
39    The implementation of access control based on mapping users to projects.
40
41    Users can be mapped to existing projects or have projects created
42    dynamically.  This implements both direct requests and proxies.
43    """
44
45    class parse_error(RuntimeError): pass
46
47
48    proxy_RequestAccess= service_caller('RequestAccess')
49    proxy_ReleaseAccess= service_caller('ReleaseAccess')
50
51    def __init__(self, config=None, auth=None):
52        """
53        Initializer.  Pulls parameters out of the ConfigParser's access section.
54        """
55
56        # Make sure that the configuration is in place
57        if not config: 
58            raise RunTimeError("No config to fedd.access")
59
60        self.project_priority = config.getboolean("access", "project_priority")
61        self.allow_proxy = config.getboolean("access", "allow_proxy")
62
63        self.boss = config.get("access", "boss")
64        self.ops = config.get("access", "ops")
65        self.domain = config.get("access", "domain")
66        self.fileserver = config.get("access", "fileserver")
67        self.eventserver = config.get("access", "eventserver")
68        self.certdir = config.get("access","certdir")
69        self.ssh_privkey_file = config.get("access","ssh_privkey_file")
70        self.create_debug = config.getboolean("access", "create_debug")
71        self.access_type = config.get("access", "type")
72
73        self.access_type = self.access_type.lower()
74        if self.access_type == 'remote_emulab':
75            self.start_segment = proxy_emulab_segment.start_segment
76            self.stop_segment = proxy_emulab_segment.stop_segment
77        elif self.access_type == 'local_emulab':
78            self.start_segment = local_emulab_segment.start_segment
79            self.stop_segment = local_emulab_segment.stop_segment
80        else:
81            self.start_segment = None
82            self.stop_segment = None
83
84        self.attrs = { }
85        self.access = { }
86        self.restricted = [ ]
87        self.projects = { }
88        self.keys = { }
89        self.types = { }
90        self.allocation = { }
91        self.state = { 
92            'projects': self.projects,
93            'allocation' : self.allocation,
94            'keys' : self.keys,
95            'types': self.types
96        }
97        self.log = logging.getLogger("fedd.access")
98        set_log_level(config, "access", self.log)
99        self.state_lock = Lock()
100
101        if auth: self.auth = auth
102        else:
103            self.log.error(\
104                    "[access]: No authorizer initialized, creating local one.")
105            auth = authorizer()
106
107        tb = config.get('access', 'testbed')
108        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
109        else: self.testbed = [ ]
110
111        if config.has_option("access", "accessdb"):
112            self.read_access(config.get("access", "accessdb"))
113
114        self.state_filename = config.get("access", "access_state")
115        self.read_state()
116
117        # Keep cert_file and cert_pwd coming from the same place
118        self.cert_file = config.get("access", "cert_file")
119        if self.cert_file:
120            self.sert_pwd = config.get("access", "cert_pw")
121        else:
122            self.cert_file = config.get("globals", "cert_file")
123            self.sert_pwd = config.get("globals", "cert_pw")
124
125        self.trusted_certs = config.get("access", "trusted_certs") or \
126                config.get("globals", "trusted_certs")
127
128        self.soap_services = {\
129            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
130            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
131            'StartSegment': soap_handler("StartSegment", self.StartSegment),
132            'TerminateSegment': soap_handler("TerminateSegment", self.TerminateSegment),
133            }
134        self.xmlrpc_services =  {\
135            'RequestAccess': xmlrpc_handler('RequestAccess',
136                self.RequestAccess),
137            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
138                self.ReleaseAccess),
139            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
140            'TerminateSegment': xmlrpc_handler('TerminateSegment',
141                self.TerminateSegment),
142            }
143
144
145        if not config.has_option("allocate", "uri"):
146            self.allocate_project = \
147                allocate_project_local(config, auth)
148        else:
149            self.allocate_project = \
150                allocate_project_remote(config, auth)
151
152        # If the project allocator exports services, put them in this object's
153        # maps so that classes that instantiate this can call the services.
154        self.soap_services.update(self.allocate_project.soap_services)
155        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
156
157
158    def read_access(self, config):
159        """
160        Read a configuration file and set internal parameters.
161
162        The format is more complex than one might hope.  The basic format is
163        attribute value pairs separated by colons(:) on a signle line.  The
164        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
165        directly using the name: value syntax.  E.g.
166        boss: hostname
167        sets self.boss to hostname.  In addition, there are access lines of the
168        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
169        names to the second for access purposes.  Names in the key (left side)
170        can include "<NONE> or <ANY>" to act as wildcards or to require the
171        fields to be empty.  Similarly aproj or auser can be <SAME> or
172        <DYNAMIC> indicating that either the matching key is to be used or a
173        dynamic user or project will be created.  These names can also be
174        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
175        can be followed with a colon-separated list of node types to which that
176        project has access (or will have access if dynamic).
177        Testbed attributes outside the forms above can be given using the
178        format attribute: name value: value.  The name is a single word and the
179        value continues to the end of the line.  Empty lines and lines startin
180        with a # are ignored.
181
182        Parsing errors result in a self.parse_error exception being raised.
183        """
184        lineno=0
185        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
186        fedid_expr = "fedid:[" + string.hexdigits + "]+"
187        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
188        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
189                "<SAME>" + "(?::" + name_expr + ")*|" + \
190                fedid_expr + "(?::" + name_expr + ")*|" + \
191                name_expr + "(?::" + name_expr + ")*)"
192        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
193
194        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
195        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
196                re.IGNORECASE)
197        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
198                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
199                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
200
201        def parse_name(n):
202            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
203            else: return n
204       
205        def auth_name(n):
206            if isinstance(n, basestring):
207                if n =='<any>' or n =='<none>': return None
208                else: return unicode(n)
209            else:
210                return n
211
212        f = open(config, "r");
213        for line in f:
214            lineno += 1
215            line = line.strip();
216            if len(line) == 0 or line.startswith('#'):
217                continue
218
219            # Extended (attribute: x value: y) attribute line
220            m = attr_re.match(line)
221            if m != None:
222                attr, val = m.group(1,2)
223                self.attrs[attr] = val
224                continue
225
226            # Restricted entry
227            m = restricted_re.match(line)
228            if m != None:
229                val = m.group(1)
230                self.restricted.append(val)
231                continue
232
233            # Access line (t, p, u) -> (ap, cu, su) line
234            m = access_re.match(line)
235            if m != None:
236                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
237                auth_key = tuple([ auth_name(x) for x in access_key])
238                aps = m.group(4).split(":");
239                if aps[0] == 'fedid:':
240                    del aps[0]
241                    aps[0] = fedid(hexstr=aps[0])
242
243                cu = parse_name(m.group(5))
244                su = parse_name(m.group(6))
245
246                access_val = (access_project(aps[0], aps[1:]),
247                        parse_name(m.group(5)), parse_name(m.group(6)))
248
249                self.access[access_key] = access_val
250                self.auth.set_attribute(auth_key, "access")
251                continue
252
253            # Nothing matched to here: unknown line - raise exception
254            f.close()
255            raise self.parse_error("Unknown statement at line %d of %s" % \
256                    (lineno, config))
257        f.close()
258
259    def get_users(self, obj):
260        """
261        Return a list of the IDs of the users in dict
262        """
263        if obj.has_key('user'):
264            return [ unpack_id(u['userID']) \
265                    for u in obj['user'] if u.has_key('userID') ]
266        else:
267            return None
268
269    def write_state(self):
270        if self.state_filename:
271            try:
272                f = open(self.state_filename, 'w')
273                pickle.dump(self.state, f)
274            except IOError, e:
275                self.log.error("Can't write file %s: %s" % \
276                        (self.state_filename, e))
277            except pickle.PicklingError, e:
278                self.log.error("Pickling problem: %s" % e)
279            except TypeError, e:
280                self.log.error("Pickling problem (TypeError): %s" % e)
281
282
283    def read_state(self):
284        """
285        Read a new copy of access state.  Old state is overwritten.
286
287        State format is a simple pickling of the state dictionary.
288        """
289        if self.state_filename:
290            try:
291                f = open(self.state_filename, "r")
292                self.state = pickle.load(f)
293
294                self.allocation = self.state['allocation']
295                self.projects = self.state['projects']
296                self.keys = self.state['keys']
297                self.types = self.state['types']
298
299                self.log.debug("[read_state]: Read state from %s" % \
300                        self.state_filename)
301            except IOError, e:
302                self.log.warning(("[read_state]: No saved state: " +\
303                        "Can't open %s: %s") % (self.state_filename, e))
304            except EOFError, e:
305                self.log.warning(("[read_state]: " +\
306                        "Empty or damaged state file: %s:") % \
307                        self.state_filename)
308            except pickle.UnpicklingError, e:
309                self.log.warning(("[read_state]: No saved state: " + \
310                        "Unpickling failed: %s") % e)
311
312            # Add the ownership attributes to the authorizer.  Note that the
313            # indices of the allocation dict are strings, but the attributes are
314            # fedids, so there is a conversion.
315            for k in self.allocation.keys():
316                for o in self.allocation[k].get('owners', []):
317                    self.auth.set_attribute(o, fedid(hexstr=k))
318
319
320    def permute_wildcards(self, a, p):
321        """Return a copy of a with various fields wildcarded.
322
323        The bits of p control the wildcards.  A set bit is a wildcard
324        replacement with the lowest bit being user then project then testbed.
325        """
326        if p & 1: user = ["<any>"]
327        else: user = a[2]
328        if p & 2: proj = "<any>"
329        else: proj = a[1]
330        if p & 4: tb = "<any>"
331        else: tb = a[0]
332
333        return (tb, proj, user)
334
335    def find_access(self, search):
336        """
337        Search the access DB for a match on this tuple.  Return the matching
338        access tuple and the user that matched.
339       
340        NB, if the initial tuple fails to match we start inserting wildcards in
341        an order determined by self.project_priority.  Try the list of users in
342        order (when wildcarded, there's only one user in the list).
343        """
344        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
345        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
346
347        for p in perm: 
348            s = self.permute_wildcards(search, p)
349            # s[2] is None on an anonymous, unwildcarded request
350            if s[2] != None:
351                for u in s[2]:
352                    if self.access.has_key((s[0], s[1], u)):
353                        return (self.access[(s[0], s[1], u)], u)
354            else:
355                if self.access.has_key(s):
356                    return (self.access[s], None)
357        return None, None
358
359    def lookup_access(self, req, fid):
360        """
361        Determine the allowed access for this request.  Return the access and
362        which fields are dynamic.
363
364        The fedid is needed to construct the request
365        """
366        # Search keys
367        tb = None
368        project = None
369        user = None
370        # Return values
371        rp = access_project(None, ())
372        ru = None
373
374        if req.has_key('project'):
375            p = req['project']
376            if p.has_key('name'):
377                project = unpack_id(p['name'])
378            user = self.get_users(p)
379        else:
380            user = self.get_users(req)
381
382        user_fedids = [ u for u in user if isinstance(u, fedid)]
383        # Determine how the caller is representing itself.  If its fedid shows
384        # up as a project or a singleton user, let that stand.  If neither the
385        # usernames nor the project name is a fedid, the caller is a testbed.
386        if project and isinstance(project, fedid):
387            if project == fid:
388                # The caller is the project (which is already in the tuple
389                # passed in to the authorizer)
390                owners = user_fedids
391                owners.append(project)
392            else:
393                raise service_error(service_error.req,
394                        "Project asserting different fedid")
395        else:
396            if fid not in user_fedids:
397                tb = fid
398                owners = user_fedids
399                owners.append(fid)
400            else:
401                if len(fedids) > 1:
402                    raise service_error(service_error.req,
403                            "User asserting different fedid")
404                else:
405                    # Which is a singleton
406                    owners = user_fedids
407        # Confirm authorization
408
409        for u in user:
410            self.log.debug("[lookup_access] Checking access for %s" % \
411                    ((tb, project, u),))
412            if self.auth.check_attribute((tb, project, u), 'access'):
413                self.log.debug("[lookup_access] Access granted")
414                break
415            else:
416                self.log.debug("[lookup_access] Access Denied")
417        else:
418            raise service_error(service_error.access, "Access denied")
419
420        # This maps a valid user to the Emulab projects and users to use
421        found, user_match = self.find_access((tb, project, user))
422       
423        if found == None:
424            raise service_error(service_error.access,
425                    "Access denied - cannot map access")
426
427        # resolve <dynamic> and <same> in found
428        dyn_proj = False
429        dyn_create_user = False
430        dyn_service_user = False
431
432        if found[0].name == "<same>":
433            if project != None:
434                rp.name = project
435            else : 
436                raise service_error(\
437                        service_error.server_config,
438                        "Project matched <same> when no project given")
439        elif found[0].name == "<dynamic>":
440            rp.name = None
441            dyn_proj = True
442        else:
443            rp.name = found[0].name
444        rp.node_types = found[0].node_types;
445
446        if found[1] == "<same>":
447            if user_match == "<any>":
448                if user != None: rcu = user[0]
449                else: raise service_error(\
450                        service_error.server_config,
451                        "Matched <same> on anonymous request")
452            else:
453                rcu = user_match
454        elif found[1] == "<dynamic>":
455            rcu = None
456            dyn_create_user = True
457        else:
458            rcu = found[1]
459       
460        if found[2] == "<same>":
461            if user_match == "<any>":
462                if user != None: rsu = user[0]
463                else: raise service_error(\
464                        service_error.server_config,
465                        "Matched <same> on anonymous request")
466            else:
467                rsu = user_match
468        elif found[2] == "<dynamic>":
469            rsu = None
470            dyn_service_user = True
471        else:
472            rsu = found[2]
473
474        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
475                owners
476
477    def build_response(self, alloc_id, ap):
478        """
479        Create the SOAP response.
480
481        Build the dictionary description of the response and use
482        fedd_utils.pack_soap to create the soap message.  ap is the allocate
483        project message returned from a remote project allocation (even if that
484        allocation was done locally).
485        """
486        # Because alloc_id is already a fedd_services_types.IDType_Holder,
487        # there's no need to repack it
488        msg = { 
489                'allocID': alloc_id,
490                'emulab': { 
491                    'domain': self.domain,
492                    'boss': self.boss,
493                    'ops': self.ops,
494                    'fileServer': self.fileserver,
495                    'eventServer': self.eventserver,
496                    'project': ap['project']
497                },
498            }
499        if len(self.attrs) > 0:
500            msg['emulab']['fedAttr'] = \
501                [ { 'attribute': x, 'value' : y } \
502                        for x,y in self.attrs.iteritems()]
503        return msg
504
505    def RequestAccess(self, req, fid):
506        """
507        Handle the access request.  Proxy if not for us.
508
509        Parse out the fields and make the allocations or rejections if for us,
510        otherwise, assuming we're willing to proxy, proxy the request out.
511        """
512
513        def gateway_hardware(h):
514            if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE')
515            else: return h
516
517        # The dance to get into the request body
518        if req.has_key('RequestAccessRequestBody'):
519            req = req['RequestAccessRequestBody']
520        else:
521            raise service_error(service_error.req, "No request!?")
522
523        if req.has_key('destinationTestbed'):
524            dt = unpack_id(req['destinationTestbed'])
525
526        if dt == None or dt in self.testbed:
527            # Request for this fedd
528            found, dyn, owners = self.lookup_access(req, fid)
529            restricted = None
530            ap = None
531
532            # If this is a request to export a project and the access project
533            # is not the project to export, access denied.
534            if req.has_key('exportProject'):
535                ep = unpack_id(req['exportProject'])
536                if ep != found[0].name:
537                    raise service_error(service_error.access,
538                            "Cannot export %s" % ep)
539
540            # Check for access to restricted nodes
541            if req.has_key('resources') and req['resources'].has_key('node'):
542                resources = req['resources']
543                restricted = [ gateway_hardware(t) for n in resources['node'] \
544                                if n.has_key('hardware') \
545                                    for t in n['hardware'] \
546                                        if gateway_hardware(t) \
547                                            in self.restricted ]
548                inaccessible = [ t for t in restricted \
549                                    if t not in found[0].node_types]
550                if len(inaccessible) > 0:
551                    raise service_error(service_error.access,
552                            "Access denied (nodetypes %s)" % \
553                            str(', ').join(inaccessible))
554            # These collect the keys for the two roles into single sets, one
555            # for creation and one for service.  The sets are a simple way to
556            # eliminate duplicates
557            create_ssh = set([ x['sshPubkey'] \
558                    for x in req['createAccess'] \
559                        if x.has_key('sshPubkey')])
560
561            service_ssh = set([ x['sshPubkey'] \
562                    for x in req['serviceAccess'] \
563                        if x.has_key('sshPubkey')])
564
565            if len(create_ssh) > 0 and len(service_ssh) >0: 
566                if dyn[1]: 
567                    # Compose the dynamic project request
568                    # (only dynamic, dynamic currently allowed)
569                    preq = { 'AllocateProjectRequestBody': \
570                                { 'project' : {\
571                                    'user': [ \
572                                    { \
573                                        'access': [ { 'sshPubkey': s } \
574                                            for s in service_ssh ], 
575                                         'role': "serviceAccess",\
576                                    }, \
577                                    { \
578                                        'access': [ { 'sshPubkey': s } \
579                                            for s in create_ssh ], 
580                                         'role': "experimentCreation",\
581                                    }, \
582                                    ], \
583                                    }\
584                                }\
585                            }
586                    if restricted != None and len(restricted) > 0:
587                        preq['AllocateProjectRequestBody']['resources'] = \
588                             {'node': [ { 'hardware' :  [ h ] } \
589                                    for h in restricted ] } 
590                    ap = self.allocate_project.dynamic_project(preq)
591                else:
592                    preq = {'StaticProjectRequestBody' : \
593                            { 'project': \
594                                { 'name' : { 'localname' : found[0].name },\
595                                  'user' : [ \
596                                    {\
597                                        'userID': { 'localname' : found[1] }, \
598                                        'access': [ { 'sshPubkey': s } 
599                                            for s in create_ssh ],
600                                        'role': 'experimentCreation'\
601                                    },\
602                                    {\
603                                        'userID': { 'localname' : found[2] }, \
604                                        'access': [ { 'sshPubkey': s } 
605                                            for s in service_ssh ],
606                                        'role': 'serviceAccess'\
607                                    },\
608                                ]}\
609                            }\
610                    }
611                    if restricted != None and len(restricted) > 0:
612                        preq['StaticProjectRequestBody']['resources'] = \
613                            {'node': [ { 'hardware' :  [ h ] } \
614                                    for h in restricted ] } 
615                    ap = self.allocate_project.static_project(preq)
616            else:
617                raise service_error(service_error.req, 
618                        "SSH access parameters required")
619            # keep track of what's been added
620            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
621            aid = unicode(allocID)
622
623            self.state_lock.acquire()
624            self.allocation[aid] = { }
625            try:
626                pname = ap['project']['name']['localname']
627            except KeyError:
628                pname = None
629
630            if dyn[1]:
631                if not pname:
632                    self.state_lock.release()
633                    raise service_error(service_error.internal,
634                            "Misformed allocation response?")
635                if self.projects.has_key(pname): self.projects[pname] += 1
636                else: self.projects[pname] = 1
637                self.allocation[aid]['project'] = pname
638            else:
639                # sproject is a static project associated with this allocation.
640                self.allocation[aid]['sproject'] = pname
641
642            if ap.has_key('resources'):
643                if not pname:
644                    self.state_lock.release()
645                    raise service_error(service_error.internal,
646                            "Misformed allocation response?")
647                self.allocation[aid]['types'] = set()
648                nodes = ap['resources'].get('node', [])
649                for n in nodes:
650                    for h in n.get('hardware', []):
651                        if self.types.has_key((pname, h)):
652                            self.types[(pname, h)] += 1
653                        else:
654                            self.types[(pname, h)] = 1
655                        self.allocation[aid]['types'].add((pname,h))
656
657
658            self.allocation[aid]['keys'] = [ ]
659
660            try:
661                for u in ap['project']['user']:
662                    uname = u['userID']['localname']
663                    if u['role'] == 'experimentCreation':
664                        self.allocation[aid]['user'] = uname
665                    for k in [ k['sshPubkey'] for k in u['access'] \
666                            if k.has_key('sshPubkey') ]:
667                        kv = "%s:%s" % (uname, k)
668                        if self.keys.has_key(kv): self.keys[kv] += 1
669                        else: self.keys[kv] = 1
670                        self.allocation[aid]['keys'].append((uname, k))
671            except KeyError:
672                self.state_lock.release()
673                raise service_error(service_error.internal,
674                        "Misformed allocation response?")
675
676
677            self.allocation[aid]['owners'] = owners
678            self.write_state()
679            self.state_lock.release()
680            for o in owners:
681                self.auth.set_attribute(o, allocID)
682            try:
683                f = open("%s/%s.pem" % (self.certdir, aid), "w")
684                print >>f, alloc_cert
685                f.close()
686            except IOError, e:
687                raise service_error(service_error.internal, 
688                        "Can't open %s/%s : %s" % (self.certdir, aid, e))
689            resp = self.build_response({ 'fedid': allocID } , ap)
690            return resp
691        else:
692            if self.allow_proxy:
693                resp = self.proxy_RequestAccess.call_service(dt, req,
694                            self.cert_file, self.cert_pwd,
695                            self.trusted_certs)
696                if resp.has_key('RequestAccessResponseBody'):
697                    return resp['RequestAccessResponseBody']
698                else:
699                    return None
700            else:
701                raise service_error(service_error.access,
702                        "Access proxying denied")
703
704    def ReleaseAccess(self, req, fid):
705        # The dance to get into the request body
706        if req.has_key('ReleaseAccessRequestBody'):
707            req = req['ReleaseAccessRequestBody']
708        else:
709            raise service_error(service_error.req, "No request!?")
710
711        if req.has_key('destinationTestbed'):
712            dt = unpack_id(req['destinationTestbed'])
713        else:
714            dt = None
715
716        if dt == None or dt in self.testbed:
717            # Local request
718            try:
719                if req['allocID'].has_key('localname'):
720                    auth_attr = aid = req['allocID']['localname']
721                elif req['allocID'].has_key('fedid'):
722                    aid = unicode(req['allocID']['fedid'])
723                    auth_attr = req['allocID']['fedid']
724                else:
725                    raise service_error(service_error.req,
726                            "Only localnames and fedids are understood")
727            except KeyError:
728                raise service_error(service_error.req, "Badly formed request")
729
730            self.log.debug("[access] deallocation requested for %s", aid)
731            if not self.auth.check_attribute(fid, auth_attr):
732                self.log.debug("[access] deallocation denied for %s", aid)
733                raise service_error(service_error.access, "Access Denied")
734
735            # If we know this allocation, reduce the reference counts and
736            # remove the local allocations.  Otherwise report an error.  If
737            # there is an allocation to delete, del_users will be a dictonary
738            # of sets where the key is the user that owns the keys in the set.
739            # We use a set to avoid duplicates.  del_project is just the name
740            # of any dynamic project to delete.  We're somewhat lazy about
741            # deleting authorization attributes.  Having access to something
742            # that doesn't exist isn't harmful.
743            del_users = { }
744            del_project = None
745            del_types = set()
746
747            if self.allocation.has_key(aid):
748                self.log.debug("Found allocation for %s" %aid)
749                self.state_lock.acquire()
750                for k in self.allocation[aid]['keys']:
751                    kk = "%s:%s" % k
752                    self.keys[kk] -= 1
753                    if self.keys[kk] == 0:
754                        if not del_users.has_key(k[0]):
755                            del_users[k[0]] = set()
756                        del_users[k[0]].add(k[1])
757                        del self.keys[kk]
758
759                if self.allocation[aid].has_key('project'):
760                    pname = self.allocation[aid]['project']
761                    self.projects[pname] -= 1
762                    if self.projects[pname] == 0:
763                        del_project = pname
764                        del self.projects[pname]
765
766                if self.allocation[aid].has_key('types'):
767                    for t in self.allocation[aid]['types']:
768                        self.types[t] -= 1
769                        if self.types[t] == 0:
770                            if not del_project: del_project = t[0]
771                            del_types.add(t[1])
772                            del self.types[t]
773
774                del self.allocation[aid]
775                self.write_state()
776                self.state_lock.release()
777                # If we actually have resources to deallocate, prepare the call.
778                if del_project or del_users:
779                    msg = { 'project': { }}
780                    if del_project:
781                        msg['project']['name']= {'localname': del_project}
782                    users = [ ]
783                    for u in del_users.keys():
784                        users.append({ 'userID': { 'localname': u },\
785                            'access' :  \
786                                    [ {'sshPubkey' : s } for s in del_users[u]]\
787                        })
788                    if users: 
789                        msg['project']['user'] = users
790                    if len(del_types) > 0:
791                        msg['resources'] = { 'node': \
792                                [ {'hardware': [ h ] } for h in del_types ]\
793                            }
794                    if self.allocate_project.release_project:
795                        msg = { 'ReleaseProjectRequestBody' : msg}
796                        self.allocate_project.release_project(msg)
797                # And remove the access cert
798                cf = "%s/%s.pem" % (self.certdir, aid)
799                self.log.debug("Removing %s" % cf)
800                os.remove(cf)
801                return { 'allocID': req['allocID'] } 
802            else:
803                raise service_error(service_error.req, "No such allocation")
804
805        else:
806            if self.allow_proxy:
807                resp = self.proxy_ReleaseAccess.call_service(dt, req,
808                            self.cert_file, self.cert_pwd,
809                            self.trusted_certs)
810                if resp.has_key('ReleaseAccessResponseBody'):
811                    return resp['ReleaseAccessResponseBody']
812                else:
813                    return None
814            else:
815                raise service_error(service_error.access,
816                        "Access proxying denied")
817
818    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
819            tmpdir, master):
820
821        seer_out = False
822        client_out = False
823        for e in [ e for e in topo.elements \
824                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
825            myname = e.name[0]
826            peer = e.get_attribute('peer')
827            lexp = e.get_attribute('experiment')
828            lproj, leid = lexp.split('/', 1)
829            ldomain = e.get_attribute('domain')
830            mexp = e.get_attribute('masterexperiment')
831            mproj, meid = mexp.split("/", 1)
832            mdomain = e.get_attribute('masterdomain')
833            muser = e.get_attribute('masteruser') or 'root'
834            smbshare = e.get_attribute('smbshare') or 'USERS'
835            scriptdir = e.get_attribute('scriptdir')
836            active = e.get_attribute('active')
837            type = e.get_attribute('portal_type')
838            segid = fedid(hexstr=e.get_attribute('peer_segment'))
839            for e in topo.elements:
840                if isinstance(e, topdl.Segment) and e.id.fedid == segid:
841                    seg = e
842                    break
843            else:
844                raise service_error(service_error.req, 
845                        "Can't find segment for portal %s" % myname)
846
847            rexp = seg.get_attribute('experiment')
848            rproj, reid = rexp.split("/", 1)
849            rdomain = seg.get_attribute('domain')
850            cfn = "%s/%s.%s.%s%s.gw.conf" % \
851                    (tmpdir, myname.lower(), leid.lower(),
852                            lproj.lower(), ldomain.lower())
853            tunnelconfig = self.attrs.has_key('TunnelCfg')
854            try:
855                f = open(cfn, "w")
856                print >>f, "Active: %s" % active
857                print >>f, "TunnelCfg: %s" % tunnelconfig
858                print >>f, "BossName: boss"
859                print >>f, "FsName: fs"
860                print >>f, "EventServerName: event-server%s" % ldomain
861                print >>f, "RemoteEventServerName: event-server%s" % rdomain
862                print >>f, "SeerControl: control.%s.%s%s" % \
863                        (meid.lower(), mproj.lower(), mdomain)
864                print >>f, "Type: %s" % type
865                print >>f, "RemoteExperiment: %s" % rexp
866                print >>f, "LocalExperiment: %s" % lexp
867                print >>f, "RemoteConfigFile: " + \
868                        "/proj/%s/exp/%s/tmp/%s.%s.%s%s.gw.conf" \
869                        % (rproj, reid, peer.lower(), reid.lower(),
870                                rproj.lower(), rdomain)
871                print >>f, "Peer: %s.%s.%s%s" % \
872                        (peer.lower(), reid.lower(), rproj.lower(), rdomain)
873                print >>f, "RemoteScriptDir: %s" % scriptdir
874                print >>f, "Pubkeys: /proj/%s/exp/%s/tmp/%s" % \
875                        (lproj, leid, pubkey_base)
876                print >>f, "Privkeys: /proj/%s/exp/%s/tmp/%s" % \
877                        (lproj, leid, secretkey_base)
878                f.close()
879            except IOError, e:
880                raise service_error(service_error.internal,
881                        "Can't write protal config %s: %s" % (cfn, e))
882           
883            # XXX: This little seer config file needs to go away.
884            if not seer_out:
885                try:
886                    seerfn = "%s/seer.conf" % tmpdir
887                    f = open(seerfn, "w")
888                    if not master:
889                        print >>f, "ControlNode: control.%s.%s%s" % \
890                            (meid.lower(), mproj.lower(), mdomain)
891                    print >>f, "ExperimentID: %s" % mexp
892                    f.close()
893                except IOError, e:
894                    raise service_error(service_error.internal, 
895                            "Can't write seer.conf: %s" %e)
896                seer_out = True
897
898            if not client_out and type in ('control', 'both'):
899                try:
900                    f = open("%s/client.conf" % tmpdir, "w")
901                    print >>f, "ControlGateway: %s.%s.%s%s" % \
902                        (myname.lower(), leid.lower(), lproj.lower(),
903                                ldomain.lower())
904                    print >>f, "SMBshare: %s" % smbshare
905                    print >>f, "ProjectUser: %s" % muser
906                    print >>f, "ProjectName: %s" % mproj
907                    print >>f, "ExperimentID: %s/%s" % (mproj, meid)
908                    f.close()
909                except IOError, e:
910                    raise service_error(service_error.internal,
911                            "Cannot write client.conf: %s" %s)
912                client_out = True
913
914
915
916    def generate_ns2(self, topo, expfn, softdir, master):
917        t = topo.clone()
918
919        # Weed out the things we aren't going to instantiate: Segments, portal
920        # substrates, and portal interfaces.  (The copy in the for loop allows
921        # us to delete from e.elements in side the for loop).
922        for e in [e for e in t.elements]:
923            if isinstance(e, topdl.Segment):
924                t.elements.remove(e)
925            if isinstance(e, topdl.Computer):
926                e.interface = [i for i in e.interface \
927                        if not i.get_attribute('portal')]
928        t.substrates = [ s for s in t.substrates \
929                if not s.get_attribute('portal')]
930        t.incorporate_elements()
931
932        # Localize the software locations
933        for e in t.elements:
934            for s in getattr(e, 'software', []):
935                s.location = re.sub("^.*/", softdir, s.location)
936
937        # Customize the ns2 output for local portal commands and images
938        filters = []
939
940        if master: cmdname = 'MasterConnectorCmd'
941        else:cmdname = 'SlaveConnectorCmd'
942
943        if self.attrs.has_key(cmdname):
944            filters.append(topdl.generate_portal_command_filter(
945                self.attrs.get(cmdname)))
946
947        if self.attrs.has_key('connectorImage'):
948            filters.append(topdl.generate_portal_image_filter(
949                self.attrs.get('connectorImage')))
950
951        if self.attrs.has_key('connectorType'):
952            filters.append(topdl.generate_portal_hardware_filter(
953                self.attrs.get('connectorType')))
954
955        # Convert to ns and write it out
956        expfile = topdl.topology_to_ns2(t, filters)
957        try:
958            f = open(expfn, "w")
959            print >>f, expfile
960            f.close()
961        except IOError:
962            raise service_error(service_error.internal,
963                    "Cannot write experiment file %s: %s" % (expfn,e))
964
965    def StartSegment(self, req, fid):
966        def get_url(url, cf, destdir):
967            po = urlparse(url)
968            fn = po.path.rpartition('/')[2]
969            try:
970                conn = httplib.HTTPSConnection(po.hostname, port=po.port, 
971                        cert_file=cf, key_file=cf)
972                conn.putrequest('GET', po.path)
973                conn.endheaders()
974                response = conn.getresponse()
975
976                lf = open("%s/%s" % (destdir, fn), "w")
977                buf = response.read(4096)
978                while buf:
979                    lf.write(buf)
980                    buf = response.read(4096)
981                lf.close()
982            except IOError, e:
983                raise service_error(service_error.internal,
984                        "Erro writing tempfile: %s" %e)
985            except httplib.HTTPException, e:
986                raise service_error(service_error.internal, 
987                        "Error retrieving data: %s" % e)
988
989        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
990
991        err = None  # Any service_error generated after tmpdir is created
992        rv = None   # Return value from segment creation
993
994        try:
995            req = req['StartSegmentRequestBody']
996        except KeyError:
997            raise service_error(server_error.req, "Badly formed request")
998
999        auth_attr = req['allocID']['fedid']
1000        aid = "%s" % auth_attr
1001        attrs = req.get('fedAttr', [])
1002        if not self.auth.check_attribute(fid, auth_attr):
1003            raise service_error(service_error.access, "Access denied")
1004
1005        if req.has_key('segmentdescription') and \
1006                req['segmentdescription'].has_key('topdldescription'):
1007            topo = \
1008                topdl.Topology(**req['segmentdescription']['topdldescription'])
1009        else:
1010            raise service_error(service_error.req, 
1011                    "Request missing segmentdescription'")
1012
1013        master = req.get('master', False)
1014
1015        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1016        try:
1017            tmpdir = tempfile.mkdtemp(prefix="access-")
1018            softdir = "%s/software" % tmpdir
1019        except IOError:
1020            raise service_error(service_error.internal, "Cannot create tmp dir")
1021
1022        # Try block alllows us to clean up temporary files.
1023        try:
1024            sw = set()
1025            for e in topo.elements:
1026                for s in getattr(e, 'software', []):
1027                    sw.add(s.location)
1028            if len(sw) > 0:
1029                os.mkdir(softdir)
1030            for s in sw:
1031                get_url(s, certfile, softdir)
1032
1033            for a in attrs:
1034                if a['attribute'] in configs:
1035                    get_url(a['value'], certfile, tmpdir)
1036                if a['attribute'] == 'ssh_pubkey':
1037                    pubkey_base = a['value'].rpartition('/')[2]
1038                if a['attribute'] == 'ssh_secretkey':
1039                    secretkey_base = a['value'].rpartition('/')[2]
1040                if a['attribute'] == 'experiment_name':
1041                    ename = a['value']
1042
1043            proj = None
1044            user = None
1045            self.state_lock.acquire()
1046            if self.allocation.has_key(aid):
1047                proj = self.allocation[aid].get('project', None)
1048                if not proj: 
1049                    proj = self.allocation[aid].get('sproject', None)
1050                user = self.allocation[aid].get('user', None)
1051                self.allocation[aid]['experiment'] = ename
1052                self.allocation[aid]['log'] = [ ]
1053                # Create a logger that logs to the experiment's state object as
1054                # well as to the main log file.
1055                alloc_log = logging.getLogger('fedd.access.%s' % ename)
1056                h = logging.StreamHandler(
1057                        list_log.list_log(self.allocation[aid]['log']))
1058                # XXX: there should be a global one of these rather than
1059                # repeating the code.
1060                h.setFormatter(logging.Formatter(
1061                    "%(asctime)s %(name)s %(message)s",
1062                            '%d %b %y %H:%M:%S'))
1063                alloc_log.addHandler(h)
1064                self.write_state()
1065            self.state_lock.release()
1066
1067            if not proj:
1068                raise service_error(service_error.internal, 
1069                        "Can't find project for %s" %aid)
1070
1071            if not user:
1072                raise service_error(service_error.internal, 
1073                        "Can't find creation user for %s" %aid)
1074
1075            expfile = "%s/experiment.tcl" % tmpdir
1076
1077            self.generate_portal_configs(topo, pubkey_base, 
1078                    secretkey_base, tmpdir, master)
1079            self.generate_ns2(topo, expfile, 
1080                    "/proj/%s/software/%s/" % (proj, ename), master)
1081
1082            starter = self.start_segment(keyfile=self.ssh_privkey_file, 
1083                    debug=self.create_debug, log=alloc_log)
1084            rv = starter(self, ename, proj, user, expfile, tmpdir)
1085        except service_error, e:
1086            err = e
1087
1088        self.log.debug("[StartSegment]: removing %s" % tmpdir)
1089        # Walk up tmpdir, deleting as we go
1090        for path, dirs, files in os.walk(tmpdir, topdown=False):
1091            for f in files:
1092                os.remove(os.path.join(path, f))
1093            for d in dirs:
1094                os.rmdir(os.path.join(path, d))
1095        os.rmdir(tmpdir)
1096
1097        if rv:
1098            # Grab the log (this is some anal locking, but better safe than
1099            # sorry)
1100            self.state_lock.acquire()
1101            logv = "".join(self.allocation[aid]['log'])
1102            self.state_lock.release()
1103
1104            return { 'allocID': req['allocID'], 'allocationLog': logv }
1105        elif err:
1106            raise service_error(service_error.federant,
1107                    "Swapin failed: %s" % err)
1108        else:
1109            raise service_error(service_error.federant, "Swapin failed")
1110
1111    def TerminateSegment(self, req, fid):
1112        try:
1113            req = req['TerminateSegmentRequestBody']
1114        except KeyError:
1115            raise service_error(server_error.req, "Badly formed request")
1116
1117        auth_attr = req['allocID']['fedid']
1118        aid = "%s" % auth_attr
1119        attrs = req.get('fedAttr', [])
1120        if not self.auth.check_attribute(fid, auth_attr):
1121            raise service_error(service_error.access, "Access denied")
1122
1123        self.state_lock.acquire()
1124        if self.allocation.has_key(aid):
1125            proj = self.allocation[aid].get('project', None)
1126            if not proj: 
1127                proj = self.allocation[aid].get('sproject', None)
1128            user = self.allocation[aid].get('user', None)
1129            ename = self.allocation[aid].get('experiment', None)
1130        else:
1131            proj = None
1132            user = None
1133            ename = None
1134        self.state_lock.release()
1135
1136        if not proj:
1137            raise service_error(service_error.internal, 
1138                    "Can't find project for %s" % aid)
1139
1140        if not user:
1141            raise service_error(service_error.internal, 
1142                    "Can't find creation user for %s" % aid)
1143        if not ename:
1144            raise service_error(service_error.internal, 
1145                    "Can't find experiment name for %s" % aid)
1146        stopper = self.stop_segment(keyfile=self.ssh_privkey_file,
1147                debug=self.create_debug)
1148        stopper(self, user, proj, ename)
1149        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.