source: fedd/federation/emulab_access.py @ 43197eb

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since 43197eb was 43197eb, checked in by Ted Faber <faber@…>, 14 years ago

better service handling including project_export psuedo service done more or less right- tested on dry runs

  • Property mode set to 100644
File size: 57.2 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import stat # for chmod constants
5import re
6import string
7import copy
8import pickle
9import logging
10import subprocess
11
12from threading import *
13from M2Crypto.SSL import SSLError
14
15from util import *
16from allocate_project import allocate_project_local, allocate_project_remote
17from access_project import access_project
18from fedid import fedid, generate_fedid
19from authorizer import authorizer
20from service_error import service_error
21from remote_service import xmlrpc_handler, soap_handler, service_caller
22
23import httplib
24import tempfile
25from urlparse import urlparse
26
27import topdl
28import list_log
29import proxy_emulab_segment
30import local_emulab_segment
31
32
33# Make log messages disappear if noone configures a fedd logger
34class nullHandler(logging.Handler):
35    def emit(self, record): pass
36
37fl = logging.getLogger("fedd.access")
38fl.addHandler(nullHandler())
39
40class access:
41    """
42    The implementation of access control based on mapping users to projects.
43
44    Users can be mapped to existing projects or have projects created
45    dynamically.  This implements both direct requests and proxies.
46    """
47
48    class parse_error(RuntimeError): pass
49
50
51    proxy_RequestAccess= service_caller('RequestAccess')
52    proxy_ReleaseAccess= service_caller('ReleaseAccess')
53
54    def __init__(self, config=None, auth=None):
55        """
56        Initializer.  Pulls parameters out of the ConfigParser's access section.
57        """
58
59        def software_list(v):
60            l = [ ]
61            if v:
62                ps = v.split(" ")
63                while len(ps):
64                    loc, file = ps[0:2]
65                    del ps[0:2]
66                    l.append((loc, file))
67            return l
68
69
70        # Make sure that the configuration is in place
71        if not config: 
72            raise RunTimeError("No config to fedd.access")
73
74        self.project_priority = config.getboolean("access", "project_priority")
75        self.allow_proxy = config.getboolean("access", "allow_proxy")
76
77        self.boss = config.get("access", "boss")
78        self.ops = config.get("access", "ops")
79        self.domain = config.get("access", "domain")
80        self.fileserver = config.get("access", "fileserver")
81        self.eventserver = config.get("access", "eventserver")
82        self.certdir = config.get("access","certdir")
83        self.userconfdir = config.get("access","userconfdir")
84        self.userconfcmd = config.get("access","userconfcmd")
85        self.userconfurl = config.get("access","userconfurl")
86        self.federation_software = config.get("access", "federation_software")
87        self.portal_software = config.get("access", "portal_software")
88        self.ssh_privkey_file = config.get("access","ssh_privkey_file")
89        self.ssh_pubkey_file = config.get("access","ssh_pubkey_file")
90        self.ssh_port = config.get("access","ssh_port") or "22"
91        self.create_debug = config.getboolean("access", "create_debug")
92        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
93        self.access_type = config.get("access", "type")
94   
95        self.federation_software = software_list(self.federation_software)
96        self.portal_software = software_list(self.portal_software)
97
98        self.access_type = self.access_type.lower()
99        if self.access_type == 'remote_emulab':
100            self.start_segment = proxy_emulab_segment.start_segment
101            self.stop_segment = proxy_emulab_segment.stop_segment
102        elif self.access_type == 'local_emulab':
103            self.start_segment = local_emulab_segment.start_segment
104            self.stop_segment = local_emulab_segment.stop_segment
105        else:
106            self.start_segment = None
107            self.stop_segment = None
108
109        self.attrs = { }
110        self.access = { }
111        self.restricted = [ ]
112        self.projects = { }
113        self.keys = { }
114        self.types = { }
115        self.allocation = { }
116        self.state = { 
117            'projects': self.projects,
118            'allocation' : self.allocation,
119            'keys' : self.keys,
120            'types': self.types
121        }
122        self.log = logging.getLogger("fedd.access")
123        set_log_level(config, "access", self.log)
124        self.state_lock = Lock()
125        # XXX: Configurable
126        self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig', 
127            'project_export'))
128        self.imports = set(('SMB', 'seer', 'userconfig'))
129
130        if auth: self.auth = auth
131        else:
132            self.log.error(\
133                    "[access]: No authorizer initialized, creating local one.")
134            auth = authorizer()
135
136        tb = config.get('access', 'testbed')
137        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
138        else: self.testbed = [ ]
139
140        if config.has_option("access", "accessdb"):
141            self.read_access(config.get("access", "accessdb"))
142
143        self.state_filename = config.get("access", "access_state")
144        self.read_state()
145
146        # Keep cert_file and cert_pwd coming from the same place
147        self.cert_file = config.get("access", "cert_file")
148        if self.cert_file:
149            self.sert_pwd = config.get("access", "cert_pw")
150        else:
151            self.cert_file = config.get("globals", "cert_file")
152            self.sert_pwd = config.get("globals", "cert_pw")
153
154        self.trusted_certs = config.get("access", "trusted_certs") or \
155                config.get("globals", "trusted_certs")
156
157        self.soap_services = {\
158            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
159            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
160            'StartSegment': soap_handler("StartSegment", self.StartSegment),
161            'TerminateSegment': soap_handler("TerminateSegment", self.TerminateSegment),
162            }
163        self.xmlrpc_services =  {\
164            'RequestAccess': xmlrpc_handler('RequestAccess',
165                self.RequestAccess),
166            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
167                self.ReleaseAccess),
168            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
169            'TerminateSegment': xmlrpc_handler('TerminateSegment',
170                self.TerminateSegment),
171            }
172
173        self.call_SetValue = service_caller('SetValue')
174        self.call_GetValue = service_caller('GetValue')
175
176        if not config.has_option("allocate", "uri"):
177            self.allocate_project = \
178                allocate_project_local(config, auth)
179        else:
180            self.allocate_project = \
181                allocate_project_remote(config, auth)
182
183        # If the project allocator exports services, put them in this object's
184        # maps so that classes that instantiate this can call the services.
185        self.soap_services.update(self.allocate_project.soap_services)
186        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
187
188
189    def read_access(self, config):
190        """
191        Read a configuration file and set internal parameters.
192
193        The format is more complex than one might hope.  The basic format is
194        attribute value pairs separated by colons(:) on a signle line.  The
195        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
196        directly using the name: value syntax.  E.g.
197        boss: hostname
198        sets self.boss to hostname.  In addition, there are access lines of the
199        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
200        names to the second for access purposes.  Names in the key (left side)
201        can include "<NONE> or <ANY>" to act as wildcards or to require the
202        fields to be empty.  Similarly aproj or auser can be <SAME> or
203        <DYNAMIC> indicating that either the matching key is to be used or a
204        dynamic user or project will be created.  These names can also be
205        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
206        can be followed with a colon-separated list of node types to which that
207        project has access (or will have access if dynamic).
208        Testbed attributes outside the forms above can be given using the
209        format attribute: name value: value.  The name is a single word and the
210        value continues to the end of the line.  Empty lines and lines startin
211        with a # are ignored.
212
213        Parsing errors result in a self.parse_error exception being raised.
214        """
215        lineno=0
216        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
217        fedid_expr = "fedid:[" + string.hexdigits + "]+"
218        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
219        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
220                "<SAME>" + "(?::" + name_expr + ")*|" + \
221                fedid_expr + "(?::" + name_expr + ")*|" + \
222                name_expr + "(?::" + name_expr + ")*)"
223        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
224
225        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
226        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
227                re.IGNORECASE)
228        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
229                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
230                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
231
232        def parse_name(n):
233            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
234            else: return n
235       
236        def auth_name(n):
237            if isinstance(n, basestring):
238                if n =='<any>' or n =='<none>': return None
239                else: return unicode(n)
240            else:
241                return n
242
243        f = open(config, "r");
244        for line in f:
245            lineno += 1
246            line = line.strip();
247            if len(line) == 0 or line.startswith('#'):
248                continue
249
250            # Extended (attribute: x value: y) attribute line
251            m = attr_re.match(line)
252            if m != None:
253                attr, val = m.group(1,2)
254                self.attrs[attr] = val
255                continue
256
257            # Restricted entry
258            m = restricted_re.match(line)
259            if m != None:
260                val = m.group(1)
261                self.restricted.append(val)
262                continue
263
264            # Access line (t, p, u) -> (ap, cu, su) line
265            m = access_re.match(line)
266            if m != None:
267                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
268                auth_key = tuple([ auth_name(x) for x in access_key])
269                aps = m.group(4).split(":");
270                if aps[0] == 'fedid:':
271                    del aps[0]
272                    aps[0] = fedid(hexstr=aps[0])
273
274                cu = parse_name(m.group(5))
275                su = parse_name(m.group(6))
276
277                access_val = (access_project(aps[0], aps[1:]),
278                        parse_name(m.group(5)), parse_name(m.group(6)))
279
280                self.access[access_key] = access_val
281                self.auth.set_attribute(auth_key, "access")
282                continue
283
284            # Nothing matched to here: unknown line - raise exception
285            f.close()
286            raise self.parse_error("Unknown statement at line %d of %s" % \
287                    (lineno, config))
288        f.close()
289
290    def get_users(self, obj):
291        """
292        Return a list of the IDs of the users in dict
293        """
294        if obj.has_key('user'):
295            return [ unpack_id(u['userID']) \
296                    for u in obj['user'] if u.has_key('userID') ]
297        else:
298            return None
299
300    def write_state(self):
301        if self.state_filename:
302            try:
303                f = open(self.state_filename, 'w')
304                pickle.dump(self.state, f)
305            except IOError, e:
306                self.log.error("Can't write file %s: %s" % \
307                        (self.state_filename, e))
308            except pickle.PicklingError, e:
309                self.log.error("Pickling problem: %s" % e)
310            except TypeError, e:
311                self.log.error("Pickling problem (TypeError): %s" % e)
312
313
314    def read_state(self):
315        """
316        Read a new copy of access state.  Old state is overwritten.
317
318        State format is a simple pickling of the state dictionary.
319        """
320        if self.state_filename:
321            try:
322                f = open(self.state_filename, "r")
323                self.state = pickle.load(f)
324
325                self.allocation = self.state['allocation']
326                self.projects = self.state['projects']
327                self.keys = self.state['keys']
328                self.types = self.state['types']
329
330                self.log.debug("[read_state]: Read state from %s" % \
331                        self.state_filename)
332            except IOError, e:
333                self.log.warning(("[read_state]: No saved state: " +\
334                        "Can't open %s: %s") % (self.state_filename, e))
335            except EOFError, e:
336                self.log.warning(("[read_state]: " +\
337                        "Empty or damaged state file: %s:") % \
338                        self.state_filename)
339            except pickle.UnpicklingError, e:
340                self.log.warning(("[read_state]: No saved state: " + \
341                        "Unpickling failed: %s") % e)
342
343            # Add the ownership attributes to the authorizer.  Note that the
344            # indices of the allocation dict are strings, but the attributes are
345            # fedids, so there is a conversion.
346            for k in self.allocation.keys():
347                for o in self.allocation[k].get('owners', []):
348                    self.auth.set_attribute(o, fedid(hexstr=k))
349                if self.allocation[k].has_key('userconfig'):
350                    sfid = self.allocation[k]['userconfig']
351                    fid = fedid(hexstr=sfid)
352                    self.auth.set_attribute(fid, "/%s" % sfid)
353
354
355    def permute_wildcards(self, a, p):
356        """Return a copy of a with various fields wildcarded.
357
358        The bits of p control the wildcards.  A set bit is a wildcard
359        replacement with the lowest bit being user then project then testbed.
360        """
361        if p & 1: user = ["<any>"]
362        else: user = a[2]
363        if p & 2: proj = "<any>"
364        else: proj = a[1]
365        if p & 4: tb = "<any>"
366        else: tb = a[0]
367
368        return (tb, proj, user)
369
370    def find_access(self, search):
371        """
372        Search the access DB for a match on this tuple.  Return the matching
373        access tuple and the user that matched.
374       
375        NB, if the initial tuple fails to match we start inserting wildcards in
376        an order determined by self.project_priority.  Try the list of users in
377        order (when wildcarded, there's only one user in the list).
378        """
379        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
380        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
381
382        for p in perm: 
383            s = self.permute_wildcards(search, p)
384            # s[2] is None on an anonymous, unwildcarded request
385            if s[2] != None:
386                for u in s[2]:
387                    if self.access.has_key((s[0], s[1], u)):
388                        return (self.access[(s[0], s[1], u)], u)
389            else:
390                if self.access.has_key(s):
391                    return (self.access[s], None)
392        return None, None
393
394    def lookup_access(self, req, fid):
395        """
396        Determine the allowed access for this request.  Return the access and
397        which fields are dynamic.
398
399        The fedid is needed to construct the request
400        """
401        user_re = re.compile("user:\s(.*)")
402        project_re = re.compile("project:\s(.*)")
403
404        # Search keys
405        tb = None
406        project = None
407        user = None
408        # Return values
409        rp = access_project(None, ())
410        ru = None
411
412        user = [ user_re.findall(x)[0] for x in req.get('credential', []) \
413                if user_re.match(x)]
414        project = [ project_re.findall(x)[0] \
415                for x in req.get('credential', []) \
416                    if project_re.match(x)]
417
418        if len(project) == 1: project = project[0]
419        elif len(project) == 0: project = None
420        else: 
421            raise service_error(service_error.req, 
422                    "More than one project credential")
423
424
425        user_fedids = [ u for u in user if isinstance(u, fedid)]
426        # Determine how the caller is representing itself.  If its fedid shows
427        # up as a project or a singleton user, let that stand.  If neither the
428        # usernames nor the project name is a fedid, the caller is a testbed.
429        if project and isinstance(project, fedid):
430            if project == fid:
431                # The caller is the project (which is already in the tuple
432                # passed in to the authorizer)
433                owners = user_fedids
434                owners.append(project)
435            else:
436                raise service_error(service_error.req,
437                        "Project asserting different fedid")
438        else:
439            if fid not in user_fedids:
440                tb = fid
441                owners = user_fedids
442                owners.append(fid)
443            else:
444                if len(fedids) > 1:
445                    raise service_error(service_error.req,
446                            "User asserting different fedid")
447                else:
448                    # Which is a singleton
449                    owners = user_fedids
450        # Confirm authorization
451
452        for u in user:
453            self.log.debug("[lookup_access] Checking access for %s" % \
454                    ((tb, project, u),))
455            if self.auth.check_attribute((tb, project, u), 'access'):
456                self.log.debug("[lookup_access] Access granted")
457                break
458            else:
459                self.log.debug("[lookup_access] Access Denied")
460        else:
461            raise service_error(service_error.access, "Access denied")
462
463        # This maps a valid user to the Emulab projects and users to use
464        found, user_match = self.find_access((tb, project, user))
465       
466        if found == None:
467            raise service_error(service_error.access,
468                    "Access denied - cannot map access")
469
470        # resolve <dynamic> and <same> in found
471        dyn_proj = False
472        dyn_create_user = False
473        dyn_service_user = False
474
475        if found[0].name == "<same>":
476            if project != None:
477                rp.name = project
478            else : 
479                raise service_error(\
480                        service_error.server_config,
481                        "Project matched <same> when no project given")
482        elif found[0].name == "<dynamic>":
483            rp.name = None
484            dyn_proj = True
485        else:
486            rp.name = found[0].name
487        rp.node_types = found[0].node_types;
488
489        if found[1] == "<same>":
490            if user_match == "<any>":
491                if user != None: rcu = user[0]
492                else: raise service_error(\
493                        service_error.server_config,
494                        "Matched <same> on anonymous request")
495            else:
496                rcu = user_match
497        elif found[1] == "<dynamic>":
498            rcu = None
499            dyn_create_user = True
500        else:
501            rcu = found[1]
502       
503        if found[2] == "<same>":
504            if user_match == "<any>":
505                if user != None: rsu = user[0]
506                else: raise service_error(\
507                        service_error.server_config,
508                        "Matched <same> on anonymous request")
509            else:
510                rsu = user_match
511        elif found[2] == "<dynamic>":
512            rsu = None
513            dyn_service_user = True
514        else:
515            rsu = found[2]
516
517        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
518                owners
519
520    def get_handler(self, path, fid):
521        self.log.info("Get handler %s %s" % (path, fid))
522        if self.auth.check_attribute(fid, path) and self.userconfdir:
523            return ("%s/%s" % (self.userconfdir, path), "application/binary")
524        else:
525            return (None, None)
526
527    def export_userconf(self, project):
528        dev_null = None
529        confid, confcert = generate_fedid("test", dir=self.userconfdir, 
530                log=self.log)
531        conffilename = "%s/%s" % (self.userconfdir, str(confid))
532        cf = None
533        try:
534            cf = open(conffilename, "w")
535            os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
536        except IOError, e:
537            raise service_error(service_error.internal, 
538                    "Cannot create user configuration data")
539
540        try:
541            dev_null = open("/dev/null", "a")
542        except IOError, e:
543            self.log.error("export_userconf: can't open /dev/null: %s" % e)
544
545        cmd = "%s %s" % (self.userconfcmd, project)
546        conf = subprocess.call(cmd.split(" "),
547                stdout=cf, stderr=dev_null, close_fds=True)
548
549        self.auth.set_attribute(confid, "/%s" % str(confid))
550
551        return confid, confcert
552
553    def export_SMB(self, id, state, project, user):
554        return { 
555                'id': id,
556                'name': 'SMB',
557                'visibility': 'export',
558                'server': 'http://fs:139',
559                'fedAttr': [
560                        { 'attribute': 'SMBSHARE', 'value': 'USERS' },
561                        { 'attribute': 'SMBUSER', 'value': user },
562                        { 'attribute': 'SMBPROJ', 'value': project },
563                    ]
564                }
565
566    def export_seer(self, id, state, project, user):
567        return { 
568                'id': id,
569                'name': 'seer',
570                'visibility': 'export',
571                'server': 'http://control:16606',
572                }
573
574    def export_tmcd(self, id, state, project, user):
575        return { 
576                'id': id,
577                'name': 'seer',
578                'visibility': 'export',
579                'server': 'http://boss:7777',
580                }
581
582    def export_userconfig(self, id, state, project, user):
583        if self.userconfdir and self.userconfcmd \
584                and self.userconfurl:
585            cid, cert = self.export_userconf(project)
586            state['userconfig'] = unicode(cid)
587            return {
588                    'id': id,
589                    'name': 'userconfig',
590                    'visibility': 'export',
591                    'server': "%s/%s" % (self.userconfurl, str(cid)),
592                    'fedAttr': [
593                        { 'attribute': 'cert', 'value': cert },
594                    ]
595                    }
596        else:
597            return None
598
599    def export_services(self, sreq, project, user):
600        exp = [ ]
601        state = { }
602        # XXX: Filthy shortcut here using http: so urlparse will give the right
603        # answers.
604        for s in sreq:
605            sname = s.get('name', '')
606            svis = s.get('visibility', '')
607            if svis == 'export':
608                if sname in self.exports:
609                    id = s.get('id', 'no_id')
610                    if sname == 'SMB':
611                        exp.append(self.export_SMB(id, state, project, user))
612                    elif sname == 'seer':
613                        exp.append(self.export_seer(id, state, project, user))
614                    elif sname == 'tmcd':
615                        exp.append(self.export_tmcd(id, state, project, user))
616                    elif sname == 'userconfig':
617                        exp.append(self.export_userconfig(id, state,
618                            project, user))
619                    elif sname == 'project_export':
620                        exp.append(self.export_SMB(id, state, project, user))
621                        exp.append(self.export_seer(id, state, project, user))
622                        exp.append(self.export_userconfig(id, state,
623                            project, user))
624        return (exp, state)
625
626    def build_response(self, alloc_id, ap, services):
627        """
628        Create the SOAP response.
629
630        Build the dictionary description of the response and use
631        fedd_utils.pack_soap to create the soap message.  ap is the allocate
632        project message returned from a remote project allocation (even if that
633        allocation was done locally).
634        """
635        # Because alloc_id is already a fedd_services_types.IDType_Holder,
636        # there's no need to repack it
637        msg = { 
638                'allocID': alloc_id,
639                'fedAttr': [
640                    { 'attribute': 'domain', 'value': self.domain } , 
641                    { 'attribute': 'project', 'value': 
642                        ap['project'].get('name', {}).get('localname', "???") },
643                ]
644            }
645        if len(self.attrs) > 0:
646            msg['fedAttr'].extend(
647                [ { 'attribute': x, 'value' : y } \
648                        for x,y in self.attrs.iteritems()])
649
650        if services:
651            msg['service'] = services
652        return msg
653
654    def RequestAccess(self, req, fid):
655        """
656        Handle the access request.  Proxy if not for us.
657
658        Parse out the fields and make the allocations or rejections if for us,
659        otherwise, assuming we're willing to proxy, proxy the request out.
660        """
661
662        def gateway_hardware(h):
663            if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE')
664            else: return h
665
666        def get_export_project(svcs):
667            """
668            if the service requests includes one to export a project, return
669            that project.
670            """
671            rv = None
672            for s in svcs:
673                if s.get('name', '') == 'project_export' and \
674                        s.get('visibility', '') == 'export':
675                    if not rv: 
676                        for a in s.get('feddAttr', []):
677                            if a.get('attribute', '') == 'project' \
678                                    and 'value' in a:
679                                rv = a['value']
680                    else:
681                        raise service_error(service_error, access, 
682                                'Requesting multiple project exports is ' + \
683                                        'not supported');
684            return rv
685
686        # The dance to get into the request body
687        if req.has_key('RequestAccessRequestBody'):
688            req = req['RequestAccessRequestBody']
689        else:
690            raise service_error(service_error.req, "No request!?")
691
692        if req.has_key('destinationTestbed'):
693            dt = unpack_id(req['destinationTestbed'])
694
695        if dt == None or dt in self.testbed:
696            # Request for this fedd
697            found, dyn, owners = self.lookup_access(req, fid)
698            restricted = None
699            ap = None
700
701            # if this includes a project export request and the exported
702            # project is not the access project, access denied.
703            if 'service' in req:
704                ep = get_export_project(req['service'])
705                if ep and ep != found[0].name:
706                    raise service_error(service_error.access,
707                            "Cannot export %s" % ep)
708
709            # Check for access to restricted nodes
710            if req.has_key('resources') and req['resources'].has_key('node'):
711                resources = req['resources']
712                restricted = [ gateway_hardware(t) for n in resources['node'] \
713                                if n.has_key('hardware') \
714                                    for t in n['hardware'] \
715                                        if gateway_hardware(t) \
716                                            in self.restricted ]
717                inaccessible = [ t for t in restricted \
718                                    if t not in found[0].node_types]
719                if len(inaccessible) > 0:
720                    raise service_error(service_error.access,
721                            "Access denied (nodetypes %s)" % \
722                            str(', ').join(inaccessible))
723
724            # These were passed around before, but now are hidden from users
725            # and configurators alike, beyond a configuration file entry.
726            create_ssh = [ self.ssh_pubkey_file ]
727            service_ssh = [ self.ssh_pubkey_file ]
728
729            if len(create_ssh) > 0 and len(service_ssh) >0: 
730                if dyn[1]: 
731                    # Compose the dynamic project request
732                    # (only dynamic, dynamic currently allowed)
733                    preq = { 'AllocateProjectRequestBody': \
734                                { 'project' : {\
735                                    'user': [ \
736                                    { \
737                                        'access': [ { 'sshPubkey': s } \
738                                            for s in service_ssh ], 
739                                         'role': "serviceAccess",\
740                                    }, \
741                                    { \
742                                        'access': [ { 'sshPubkey': s } \
743                                            for s in create_ssh ], 
744                                         'role': "experimentCreation",\
745                                    }, \
746                                    ], \
747                                    }\
748                                }\
749                            }
750                    if restricted != None and len(restricted) > 0:
751                        preq['AllocateProjectRequestBody']['resources'] = \
752                             {'node': [ { 'hardware' :  [ h ] } \
753                                    for h in restricted ] } 
754                    ap = self.allocate_project.dynamic_project(preq)
755                else:
756                    preq = {'StaticProjectRequestBody' : \
757                            { 'project': \
758                                { 'name' : { 'localname' : found[0].name },\
759                                  'user' : [ \
760                                    {\
761                                        'userID': { 'localname' : found[1] }, \
762                                        'access': [ { 'sshPubkey': s } 
763                                            for s in create_ssh ],
764                                        'role': 'experimentCreation'\
765                                    },\
766                                    {\
767                                        'userID': { 'localname' : found[2] }, \
768                                        'access': [ { 'sshPubkey': s } 
769                                            for s in service_ssh ],
770                                        'role': 'serviceAccess'\
771                                    },\
772                                ]}\
773                            }\
774                    }
775                    if restricted != None and len(restricted) > 0:
776                        preq['StaticProjectRequestBody']['resources'] = \
777                            {'node': [ { 'hardware' :  [ h ] } \
778                                    for h in restricted ] } 
779                    ap = self.allocate_project.static_project(preq)
780            else:
781                raise service_error(service_error.req, 
782                        "SSH access parameters required")
783            # keep track of what's been added
784            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
785            aid = unicode(allocID)
786
787            self.state_lock.acquire()
788            self.allocation[aid] = { }
789            try:
790                pname = ap['project']['name']['localname']
791            except KeyError:
792                pname = None
793
794            if dyn[1]:
795                if not pname:
796                    self.state_lock.release()
797                    raise service_error(service_error.internal,
798                            "Misformed allocation response?")
799                if self.projects.has_key(pname): self.projects[pname] += 1
800                else: self.projects[pname] = 1
801                self.allocation[aid]['project'] = pname
802            else:
803                # sproject is a static project associated with this allocation.
804                self.allocation[aid]['sproject'] = pname
805
806            if ap.has_key('resources'):
807                if not pname:
808                    self.state_lock.release()
809                    raise service_error(service_error.internal,
810                            "Misformed allocation response?")
811                self.allocation[aid]['types'] = set()
812                nodes = ap['resources'].get('node', [])
813                for n in nodes:
814                    for h in n.get('hardware', []):
815                        if self.types.has_key((pname, h)):
816                            self.types[(pname, h)] += 1
817                        else:
818                            self.types[(pname, h)] = 1
819                        self.allocation[aid]['types'].add((pname,h))
820
821
822            self.allocation[aid]['keys'] = [ ]
823
824            try:
825                for u in ap['project']['user']:
826                    uname = u['userID']['localname']
827                    if u['role'] == 'experimentCreation':
828                        self.allocation[aid]['user'] = uname
829                    for k in [ k['sshPubkey'] for k in u['access'] \
830                            if k.has_key('sshPubkey') ]:
831                        kv = "%s:%s" % (uname, k)
832                        if self.keys.has_key(kv): self.keys[kv] += 1
833                        else: self.keys[kv] = 1
834                        self.allocation[aid]['keys'].append((uname, k))
835            except KeyError:
836                self.state_lock.release()
837                raise service_error(service_error.internal,
838                        "Misformed allocation response?")
839
840            self.allocation[aid]['owners'] = owners
841            services, svc_state = self.export_services(req.get('service',[]),
842                    pname, uname)
843            # Store services state in global state
844            for k, v in svc_state.items():
845                self.allocation[aid][k] = v
846            self.write_state()
847            self.state_lock.release()
848            for o in owners:
849                self.auth.set_attribute(o, allocID)
850            try:
851                f = open("%s/%s.pem" % (self.certdir, aid), "w")
852                print >>f, alloc_cert
853                f.close()
854            except IOError, e:
855                raise service_error(service_error.internal, 
856                        "Can't open %s/%s : %s" % (self.certdir, aid, e))
857            resp = self.build_response({ 'fedid': allocID } , ap, services)
858            return resp
859        else:
860            if self.allow_proxy:
861                resp = self.proxy_RequestAccess.call_service(dt, req,
862                            self.cert_file, self.cert_pwd,
863                            self.trusted_certs)
864                if resp.has_key('RequestAccessResponseBody'):
865                    return resp['RequestAccessResponseBody']
866                else:
867                    return None
868            else:
869                raise service_error(service_error.access,
870                        "Access proxying denied")
871
872    def ReleaseAccess(self, req, fid):
873        # The dance to get into the request body
874        if req.has_key('ReleaseAccessRequestBody'):
875            req = req['ReleaseAccessRequestBody']
876        else:
877            raise service_error(service_error.req, "No request!?")
878
879        if req.has_key('destinationTestbed'):
880            dt = unpack_id(req['destinationTestbed'])
881        else:
882            dt = None
883
884        if dt == None or dt in self.testbed:
885            # Local request
886            try:
887                if req['allocID'].has_key('localname'):
888                    auth_attr = aid = req['allocID']['localname']
889                elif req['allocID'].has_key('fedid'):
890                    aid = unicode(req['allocID']['fedid'])
891                    auth_attr = req['allocID']['fedid']
892                else:
893                    raise service_error(service_error.req,
894                            "Only localnames and fedids are understood")
895            except KeyError:
896                raise service_error(service_error.req, "Badly formed request")
897
898            self.log.debug("[access] deallocation requested for %s", aid)
899            if not self.auth.check_attribute(fid, auth_attr):
900                self.log.debug("[access] deallocation denied for %s", aid)
901                raise service_error(service_error.access, "Access Denied")
902
903            # If we know this allocation, reduce the reference counts and
904            # remove the local allocations.  Otherwise report an error.  If
905            # there is an allocation to delete, del_users will be a dictonary
906            # of sets where the key is the user that owns the keys in the set.
907            # We use a set to avoid duplicates.  del_project is just the name
908            # of any dynamic project to delete.  We're somewhat lazy about
909            # deleting authorization attributes.  Having access to something
910            # that doesn't exist isn't harmful.
911            del_users = { }
912            del_project = None
913            del_types = set()
914
915            if self.allocation.has_key(aid):
916                self.log.debug("Found allocation for %s" %aid)
917                self.state_lock.acquire()
918                for k in self.allocation[aid]['keys']:
919                    kk = "%s:%s" % k
920                    self.keys[kk] -= 1
921                    if self.keys[kk] == 0:
922                        if not del_users.has_key(k[0]):
923                            del_users[k[0]] = set()
924                        del_users[k[0]].add(k[1])
925                        del self.keys[kk]
926
927                if self.allocation[aid].has_key('project'):
928                    pname = self.allocation[aid]['project']
929                    self.projects[pname] -= 1
930                    if self.projects[pname] == 0:
931                        del_project = pname
932                        del self.projects[pname]
933
934                if self.allocation[aid].has_key('types'):
935                    for t in self.allocation[aid]['types']:
936                        self.types[t] -= 1
937                        if self.types[t] == 0:
938                            if not del_project: del_project = t[0]
939                            del_types.add(t[1])
940                            del self.types[t]
941
942                del self.allocation[aid]
943                self.write_state()
944                self.state_lock.release()
945                # If we actually have resources to deallocate, prepare the call.
946                if del_project or del_users:
947                    msg = { 'project': { }}
948                    if del_project:
949                        msg['project']['name']= {'localname': del_project}
950                    users = [ ]
951                    for u in del_users.keys():
952                        users.append({ 'userID': { 'localname': u },\
953                            'access' :  \
954                                    [ {'sshPubkey' : s } for s in del_users[u]]\
955                        })
956                    if users: 
957                        msg['project']['user'] = users
958                    if len(del_types) > 0:
959                        msg['resources'] = { 'node': \
960                                [ {'hardware': [ h ] } for h in del_types ]\
961                            }
962                    if self.allocate_project.release_project:
963                        msg = { 'ReleaseProjectRequestBody' : msg}
964                        self.allocate_project.release_project(msg)
965                # And remove the access cert
966                cf = "%s/%s.pem" % (self.certdir, aid)
967                self.log.debug("Removing %s" % cf)
968                os.remove(cf)
969                return { 'allocID': req['allocID'] } 
970            else:
971                raise service_error(service_error.req, "No such allocation")
972
973        else:
974            if self.allow_proxy:
975                resp = self.proxy_ReleaseAccess.call_service(dt, req,
976                            self.cert_file, self.cert_pwd,
977                            self.trusted_certs)
978                if resp.has_key('ReleaseAccessResponseBody'):
979                    return resp['ReleaseAccessResponseBody']
980                else:
981                    return None
982            else:
983                raise service_error(service_error.access,
984                        "Access proxying denied")
985
986    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
987            tmpdir, master, lproj, leid, connInfo, services):
988
989        def conninfo_to_dict(key, info):
990            """
991            Make a cpoy of the connection information about key, and flatten it
992            into a single dict by parsing out any feddAttrs.
993            """
994
995            rv = None
996            for i in info:
997                if key == i.get('portal', "") or \
998                        key in [e.get('element', "") \
999                        for e in i.get('member', [])]:
1000                    rv = i.copy()
1001                    break
1002
1003            else:
1004                return rv
1005
1006            if 'fedAttr' in rv:
1007                for a in rv['fedAttr']:
1008                    attr = a.get('attribute', "")
1009                    val = a.get('value', "")
1010                    if attr and attr not in rv:
1011                        rv[attr] = val
1012                del rv['fedAttr']
1013            return rv
1014
1015        # XXX: un hardcode this
1016        def client_null(f, s):
1017            print >>f, "Service: %s" % s['name']
1018
1019        def client_smb(f, s):
1020            print >>f, "Service: %s" % s['name']
1021            smbshare = None
1022            smbuser = None
1023            smbproj = None
1024            for a in s.get('fedAttr', []):
1025                if a.get('attribute', '') == 'SMBSHARE':
1026                    smbshare = a.get('value', None)
1027                elif a.get('attribute', '') == 'SMBUSER':
1028                    smbuser = a.get('value', None)
1029                elif a.get('attribute', '') == 'SMBPROJ':
1030                    smbproj = a.get('value', None)
1031
1032            if all((smbshare, smbuser, smbproj)):
1033                print >>f, "SMBshare: %s" % smbshare
1034                print >>f, "ProjectUser: %s" % smbuser
1035                print >>f, "ProjectName: %s" % smbproj
1036
1037        client_service_out = {
1038                'SMB': client_smb,
1039                'tmcd': client_null,
1040                'seer': client_null,
1041                'userconfig': client_null,
1042                'project_export': client_null,
1043            }
1044
1045        def server_port(f, s):
1046            p = urlparse(s.get('server', 'http://localhost'))
1047            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
1048
1049        def server_null(f,s): pass
1050
1051        def server_seer(f, s):
1052            print >>f, 'seer: True'
1053
1054        server_service_out = {
1055                'SMB': server_port,
1056                'tmcd': server_port,
1057                'userconfig': server_null,
1058                'project_export': server_null,
1059                'seer': server_seer,
1060            }
1061        # XXX: end un hardcode this
1062
1063
1064        seer_out = False
1065        client_out = False
1066        for e in [ e for e in topo.elements \
1067                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
1068            myname = e.name[0]
1069            type = e.get_attribute('portal_type')
1070            testbed = e.get_attribute('testbed')
1071
1072            info = conninfo_to_dict(myname, connInfo)
1073
1074            if not info:
1075                raise service_error(service_error.req,
1076                        "No connectivity info for %s" % myname)
1077
1078            peer = info.get('peer', "")
1079            ldomain = self.domain;
1080            ssh_port = info.get('ssh_port', 22)
1081
1082            mexp = info.get('masterexperiment',"")
1083            mproj, meid = mexp.split("/", 1)
1084            mdomain = info.get('masterdomain',"")
1085            muser = info.get('masteruser','root')
1086            smbshare = info.get('smbshare', 'USERS')
1087
1088            active = info.get('active', 'False')
1089
1090            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
1091            tunnelconfig = self.attrs.has_key('TunnelCfg')
1092            try:
1093                f = open(cfn, "w")
1094                if active == 'True':
1095                    print >>f, "active: True"
1096                    print >>f, "ssh_port: %s" % ssh_port
1097                    if type in ('control', 'both'):
1098                        for s in [s for s in services \
1099                                if s.get('name', "") in self.imports]:
1100                            server_service_out[s['name']](f, s)
1101
1102                if tunnelconfig:
1103                    print >>f, "tunnelip: %s" % tunnelconfig
1104                print >>f, "peer: %s" % peer.lower()
1105                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
1106                        (lproj, leid, pubkey_base)
1107                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
1108                        (lproj, leid, secretkey_base)
1109                f.close()
1110            except IOError, e:
1111                raise service_error(service_error.internal,
1112                        "Can't write protal config %s: %s" % (cfn, e))
1113           
1114            if not client_out and type in ('control', 'both'):
1115                try:
1116                    f = open("%s/client.conf" % tmpdir, "w")
1117                    print >>f, "ControlGateway: %s.%s.%s%s" % \
1118                        (myname.lower(), leid.lower(), lproj.lower(),
1119                                ldomain.lower())
1120                    for s in services:
1121                        if s.get('name',"") in self.imports and \
1122                                s.get('visibility','') == 'import':
1123                            client_service_out[s['name']](f, s)
1124                    # Seer uses this.
1125                    print >>f, "ExperimentID: %s/%s" % (mproj, meid)
1126                    # Better way...
1127                    if testbed == master:
1128                        print >>f, "SEERBase: True"
1129                    f.close()
1130                except IOError, e:
1131                    raise service_error(service_error.internal,
1132                            "Cannot write client.conf: %s" %s)
1133                client_out = True
1134
1135
1136    def generate_ns2(self, topo, expfn, softdir, master, connInfo):
1137        class dragon_commands:
1138            """
1139            Functor to spit out approrpiate dragon commands for nodes listed in
1140            the connectivity description.  The constructor makes a dict mapping
1141            dragon nodes to their parameters and the __call__ checks each
1142            element in turn for membership.
1143            """
1144            def __init__(self, map):
1145                self.node_info = map
1146
1147            def __call__(self, e):
1148                s = ""
1149                if isinstance(e, topdl.Computer):
1150                    if self.node_info.has_key(e.name[0]):
1151                        i = self.node_info[e.name[0]]
1152                        for ifname, vlan, type in i:
1153                            for i in e.interface:
1154                                if i.name == ifname:
1155                                    addr = i.get_attribute('ip4_address')
1156                                    subs = i.substrate[0]
1157                                    break
1158                            else:
1159                                raise service_error(service_error.internal,
1160                                        "No interface %s on element %s" % \
1161                                                (ifname, e.name[0]))
1162                            # XXX: do netmask right
1163                            if type =='link':
1164                                s = ("tb-allow-external $%s dragonportal " + \
1165                                        "ip %s vlan %s netmask 255.255.255.0\n") % \
1166                                        (e.name[0], addr, vlan)
1167                            elif type =='lan':
1168                                s = ("tb-allow-external $%s dragonportal " + \
1169                                        "ip %s vlan %s usurp %s\n") % \
1170                                        (e.name[0], addr, vlan, subs)
1171                            else:
1172                                raise service_error(service_error_internal,
1173                                        "Unknown DRAGON type %s" % type)
1174                return s
1175
1176        class not_dragon:
1177            def __init__(self, map):
1178                self.nodes = set(map.keys())
1179
1180            def __call__(self, e):
1181                return e.name[0] not in self.nodes
1182
1183        def add_kit(e, kit):
1184            """
1185            Add a Software object created from the list of (install, location)
1186            tuples passed as kit  to the software attribute of an object e.  We
1187            do this enough to break out the code, but it's kind of a hack to
1188            avoid changing the old tuple rep.
1189            """
1190
1191            s = [ topdl.Software(install=i, location=l) for i, l in kit]
1192
1193            if isinstance(e.software, list): e.software.extend(s)
1194            else: e.software = s
1195
1196
1197        t = topo.clone()
1198
1199        dragon_map = { }
1200        for i in [ i for i in connInfo if i['type'] == 'transit']:
1201            for a in i.get('fedAttr', []):
1202                if a['attribute'] == 'vlan_id':
1203                    vlan = a['value']
1204                    break
1205            else:
1206                raise service_error(service_error.internal, 
1207                        "No vlan tag")
1208            members = i.get('member', [])
1209            if len(members) > 1: type = 'lan'
1210            else: type = 'link'
1211
1212            try:
1213                for m in members:
1214                    if dragon_map.has_key(m['element']):
1215                        dragon_map[m['element']].append(( m['interface'], 
1216                            vlan, type))
1217                    else:
1218                        dragon_map[m['element']] = [( m['interface'], 
1219                            vlan, type),]
1220            except KeyError:
1221                raise service_error(service_error.req,
1222                        "Missing connectivity info")
1223
1224        # The startcmds for master and slave testbeds
1225        if master: 
1226            gate_cmd = self.attrs.get('MasterConnectorStartCmd', '/bin/true')
1227            node_cmd = self.attrs.get('MasterNodeStartCmd', 'bin/true')
1228        else: 
1229            gate_cmd = self.attrs.get('SlaveConnectorStartCmd', '/bin/true')
1230            node_cmd = self.attrs.get('SlaveNodeStartCmd', 'bin/true')
1231
1232        # Weed out the things we aren't going to instantiate: Segments, portal
1233        # substrates, and portal interfaces.  (The copy in the for loop allows
1234        # us to delete from e.elements in side the for loop).  While we're
1235        # touching all the elements, we also adjust paths from the original
1236        # testbed to local testbed paths and put the federation commands into
1237        # the start commands
1238        for e in [e for e in t.elements]:
1239            if isinstance(e, topdl.Segment):
1240                t.elements.remove(e)
1241            if isinstance(e, topdl.Computer):
1242                add_kit(e, self.federation_software)
1243                if e.get_attribute('portal') and gate_cmd:
1244                    # Add local portal support software
1245                    add_kit(e, self.portal_software)
1246                    # Portals never have a user-specified start command
1247                    e.set_attribute('startup', gate_cmd)
1248                elif node_cmd:
1249                    if e.get_attribute('startup'):
1250                        e.set_attribute('startup', "%s \\$USER '%s'" % \
1251                                (node_cmd, e.get_attribute('startup')))
1252                    else:
1253                        e.set_attribute('startup', node_cmd)
1254
1255                dinf = [i[0] for i in dragon_map.get(e.name[0], []) ]
1256                # Remove portal interfaces that do not connect to DRAGON
1257                e.interface = [i for i in e.interface \
1258                        if not i.get_attribute('portal') or i.name in dinf ]
1259            # Fix software paths
1260            for s in getattr(e, 'software', []):
1261                s.location = re.sub("^.*/", softdir, s.location)
1262
1263        t.substrates = [ s.clone() for s in t.substrates ]
1264        t.incorporate_elements()
1265
1266        # Customize the ns2 output for local portal commands and images
1267        filters = []
1268
1269        # NB: these are extra commands issued for the node, not the startcmds
1270        if master: cmd = self.attrs.get('MasterConnectorCmd', '')
1271        else: cmd = self.attrs.get('SlaveConnectorCmd', '')
1272
1273        if self.attrs.has_key('dragon'):
1274            add_filter = not_dragon(dragon_map)
1275            filters.append(dragon_commands(dragon_map))
1276        else:
1277            add_filter = None
1278
1279        if cmd:
1280            filters.append(topdl.generate_portal_command_filter(cmd,
1281                add_filter=add_filter))
1282
1283        if self.attrs.has_key('connectorImage'):
1284            filters.append(topdl.generate_portal_image_filter(
1285                self.attrs.get('connectorImage')))
1286
1287        if self.attrs.has_key('connectorType'):
1288            filters.append(topdl.generate_portal_hardware_filter(
1289                self.attrs.get('connectorType')))
1290
1291        # Convert to ns and write it out
1292        expfile = topdl.topology_to_ns2(t, filters)
1293        try:
1294            f = open(expfn, "w")
1295            print >>f, expfile
1296            f.close()
1297        except IOError:
1298            raise service_error(service_error.internal,
1299                    "Cannot write experiment file %s: %s" % (expfn,e))
1300
1301    def export_store_info(self, cf, proj, ename, connInfo):
1302        """
1303        For the export requests in the connection info, install the peer names
1304        at the experiment controller via SetValue calls.
1305        """
1306
1307        call_SetValue = service_caller('SetValue', log=self.log)
1308        for c in connInfo:
1309            for p in [ p for p in c.get('parameter', []) \
1310                    if p.get('type', '') == 'output']:
1311
1312                if p.get('name', '') == 'peer':
1313                    k = p.get('key', None)
1314                    surl = p.get('store', None)
1315                    if surl and k and k.index('/') != -1:
1316                        value = "%s.%s.%s%s" % \
1317                                (k[k.index('/')+1:], ename, proj, self.domain)
1318                        req = { 'name': k, 'value': value }
1319                        call_SetValue(surl, req, cf)
1320                    else:
1321                        self.log.error("Bad export request: %s" % p)
1322                elif p.get('name', '') == 'ssh_port':
1323                    k = p.get('key', None)
1324                    surl = p.get('store', None)
1325                    if surl and k:
1326                        req = { 'name': k, 'value': self.ssh_port }
1327                        call_SetValue(surl, req, cf)
1328                    else:
1329                        self.log.error("Bad export request: %s" % p)
1330                else:
1331                    self.log.error("Unknown export parameter: %s" % \
1332                            p.get('name'))
1333                    continue
1334
1335    def import_store_info(self, cf, connInfo):
1336        """
1337        Pull any import parameters in connInfo in.  We translate them either
1338        into known member names or fedAddrs.
1339        """
1340
1341        for c in connInfo:
1342            for p in [ p for p in c.get('parameter', []) \
1343                    if p.get('type', '') == 'input']:
1344                name = p.get('name', None)
1345                key = p.get('key', None)
1346                store = p.get('store', None)
1347
1348                if name and key and store :
1349                    req = { 'name': key, 'wait': True }
1350                    r = self.call_GetValue(store, req, cf)
1351                    r = r.get('GetValueResponseBody', None)
1352                    if r :
1353                        if r.get('name', '') == key:
1354                            v = r.get('value', None)
1355                            if v is not None:
1356                                if name == 'peer':
1357                                    c['peer'] = v
1358                                else:
1359                                    if c.has_key('fedAttr'):
1360                                        c['fedAttr'].append({
1361                                            'attribute': name, 'value': v})
1362                                    else:
1363                                        c['fedAttr']= [{
1364                                            'attribute': name, 'value': v}]
1365                            else:
1366                                raise service_error(service_error.internal, 
1367                                        'None value exported for %s'  % key)
1368                        else:
1369                            raise service_error(service_error.internal, 
1370                                    'Different name returned for %s: %s' \
1371                                            % (key, r.get('name','')))
1372                    else:
1373                        raise service_error(service_error.internal, 
1374                            'Badly formatted response: no GetValueResponseBody')
1375                else:
1376                    raise service_error(service_error.internal, 
1377                        'Bad Services missing info for import %s' % c)
1378
1379    def StartSegment(self, req, fid):
1380
1381        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
1382
1383        err = None  # Any service_error generated after tmpdir is created
1384        rv = None   # Return value from segment creation
1385
1386        try:
1387            req = req['StartSegmentRequestBody']
1388        except KeyError:
1389            raise service_error(server_error.req, "Badly formed request")
1390
1391        connInfo = req.get('connection', [])
1392        services = req.get('service', [])
1393        auth_attr = req['allocID']['fedid']
1394        aid = "%s" % auth_attr
1395        attrs = req.get('fedAttr', [])
1396        if not self.auth.check_attribute(fid, auth_attr):
1397            raise service_error(service_error.access, "Access denied")
1398        else:
1399            # See if this is a replay of an earlier succeeded StartSegment -
1400            # sometimes SSL kills 'em.  If so, replay the response rather than
1401            # redoing the allocation.
1402            self.state_lock.acquire()
1403            retval = self.allocation[aid].get('started', None)
1404            self.state_lock.release()
1405            if retval:
1406                self.log.warning("Duplicate StartSegment for %s: " % aid + \
1407                        "replaying response")
1408                return retval
1409
1410        # A new request.  Do it.
1411
1412        if req.has_key('segmentdescription') and \
1413                req['segmentdescription'].has_key('topdldescription'):
1414            topo = \
1415                topdl.Topology(**req['segmentdescription']['topdldescription'])
1416        else:
1417            raise service_error(service_error.req, 
1418                    "Request missing segmentdescription'")
1419       
1420        master = req.get('master', False)
1421
1422        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1423        try:
1424            tmpdir = tempfile.mkdtemp(prefix="access-")
1425            softdir = "%s/software" % tmpdir
1426            os.mkdir(softdir)
1427        except IOError:
1428            raise service_error(service_error.internal, "Cannot create tmp dir")
1429
1430        # Try block alllows us to clean up temporary files.
1431        try:
1432            sw = set()
1433            for e in topo.elements:
1434                for s in getattr(e, 'software', []):
1435                    sw.add(s.location)
1436            for s in sw:
1437                self.log.debug("Retrieving %s" % s)
1438                try:
1439                    get_url(s, certfile, softdir)
1440                except:
1441                    t, v, st = sys.exc_info()
1442                    raise service_error(service_error.internal,
1443                            "Error retrieving %s: %s" % (s, v))
1444
1445            # Copy local federation and portal node software to the tempdir
1446            for s in (self.federation_software, self.portal_software):
1447                for l, f in s:
1448                    base = os.path.basename(f)
1449                    copy_file(f, "%s/%s" % (softdir, base))
1450
1451            for a in attrs:
1452                if a['attribute'] in configs:
1453                    try:
1454                        get_url(a['value'], certfile, tmpdir)
1455                    except:
1456                        t, v, st = sys.exc_info()
1457                        raise service_error(service_error.internal,
1458                                "Error retrieving %s: %s" % (s, v))
1459                if a['attribute'] == 'ssh_pubkey':
1460                    pubkey_base = a['value'].rpartition('/')[2]
1461                if a['attribute'] == 'ssh_secretkey':
1462                    secretkey_base = a['value'].rpartition('/')[2]
1463                if a['attribute'] == 'experiment_name':
1464                    ename = a['value']
1465
1466            # If the userconf service was imported, collect the configuration
1467            # data.
1468            for s in services:
1469                if s.get("name", "") == 'userconfig' \
1470                        and s.get('visibility',"") == 'import':
1471
1472                    # Collect ther server and certificate info.
1473                    u = s.get('server', None)
1474                    for a in s.get('fedAttr', []):
1475                        if a.get('attribute',"") == 'cert':
1476                            cert = a.get('value', None)
1477                            break
1478                    else:
1479                        cert = None
1480
1481                    if cert:
1482                        # Make a temporary certificate file for get_url.  The
1483                        # finally clause removes it whether something goes
1484                        # wrong (including an exception from get_url) or not.
1485                        try:
1486                            tfos, tn = tempfile.mkstemp(suffix=".pem")
1487                            tf = os.fdopen(tfos, 'w')
1488                            print >>tf, cert
1489                            tf.close()
1490                            get_url(u, tn, tmpdir, "userconf")
1491                        except IOError, e:
1492                            raise service_error(service.error.internal, 
1493                                    "Cannot create temp file for " + 
1494                                    "userconfig certificates: %s e")
1495                        except:
1496                            t, v, st = sys.exc_info()
1497                            raise service_error(service_error.internal,
1498                                    "Error retrieving %s: %s" % (s, v))
1499                        finally:
1500                            if tn: os.remove(tn)
1501                    else:
1502                        raise service_error(service_error.req,
1503                                "No certificate for retreiving userconfig")
1504                    break
1505
1506
1507
1508            proj = None
1509            user = None
1510            self.state_lock.acquire()
1511            if self.allocation.has_key(aid):
1512                proj = self.allocation[aid].get('project', None)
1513                if not proj: 
1514                    proj = self.allocation[aid].get('sproject', None)
1515                user = self.allocation[aid].get('user', None)
1516                self.allocation[aid]['experiment'] = ename
1517                self.allocation[aid]['log'] = [ ]
1518                # Create a logger that logs to the experiment's state object as
1519                # well as to the main log file.
1520                alloc_log = logging.getLogger('fedd.access.%s' % ename)
1521                h = logging.StreamHandler(
1522                        list_log.list_log(self.allocation[aid]['log']))
1523                # XXX: there should be a global one of these rather than
1524                # repeating the code.
1525                h.setFormatter(logging.Formatter(
1526                    "%(asctime)s %(name)s %(message)s",
1527                            '%d %b %y %H:%M:%S'))
1528                alloc_log.addHandler(h)
1529                self.write_state()
1530            self.state_lock.release()
1531
1532            if not proj:
1533                raise service_error(service_error.internal, 
1534                        "Can't find project for %s" %aid)
1535
1536            if not user:
1537                raise service_error(service_error.internal, 
1538                        "Can't find creation user for %s" %aid)
1539
1540            self.export_store_info(certfile, proj, ename, connInfo)
1541            self.import_store_info(certfile, connInfo)
1542
1543            expfile = "%s/experiment.tcl" % tmpdir
1544
1545            self.generate_portal_configs(topo, pubkey_base, 
1546                    secretkey_base, tmpdir, master, proj, ename, connInfo, 
1547                    services)
1548            self.generate_ns2(topo, expfile, 
1549                    "/proj/%s/software/%s/" % (proj, ename), master, connInfo)
1550
1551            starter = self.start_segment(keyfile=self.ssh_privkey_file, 
1552                    debug=self.create_debug, log=alloc_log)
1553            rv = starter(self, ename, proj, user, expfile, tmpdir)
1554        except service_error, e:
1555            err = e
1556        except e:
1557            err = service_error(service_error.internal, str(e))
1558
1559        # Walk up tmpdir, deleting as we go
1560        if self.cleanup:
1561            self.log.debug("[StartSegment]: removing %s" % tmpdir)
1562            for path, dirs, files in os.walk(tmpdir, topdown=False):
1563                for f in files:
1564                    os.remove(os.path.join(path, f))
1565                for d in dirs:
1566                    os.rmdir(os.path.join(path, d))
1567            os.rmdir(tmpdir)
1568        else:
1569            self.log.debug("[StartSegment]: not removing %s" % tmpdir)
1570
1571        if rv:
1572            # Grab the log (this is some anal locking, but better safe than
1573            # sorry)
1574            self.state_lock.acquire()
1575            logv = "".join(self.allocation[aid]['log'])
1576            # It's possible that the StartSegment call gets retried (!).
1577            # if the 'started' key is in the allocation, we'll return it rather
1578            # than redo the setup.
1579            self.allocation[aid]['started'] = { 
1580                    'allocID': req['allocID'],
1581                    'allocationLog': logv,
1582                    }
1583            retval = self.allocation[aid]['started']
1584            self.write_state()
1585            self.state_lock.release()
1586
1587            return retval
1588        elif err:
1589            raise service_error(service_error.federant,
1590                    "Swapin failed: %s" % err)
1591        else:
1592            raise service_error(service_error.federant, "Swapin failed")
1593
1594    def TerminateSegment(self, req, fid):
1595        try:
1596            req = req['TerminateSegmentRequestBody']
1597        except KeyError:
1598            raise service_error(server_error.req, "Badly formed request")
1599
1600        auth_attr = req['allocID']['fedid']
1601        aid = "%s" % auth_attr
1602        attrs = req.get('fedAttr', [])
1603        if not self.auth.check_attribute(fid, auth_attr):
1604            raise service_error(service_error.access, "Access denied")
1605
1606        self.state_lock.acquire()
1607        if self.allocation.has_key(aid):
1608            proj = self.allocation[aid].get('project', None)
1609            if not proj: 
1610                proj = self.allocation[aid].get('sproject', None)
1611            user = self.allocation[aid].get('user', None)
1612            ename = self.allocation[aid].get('experiment', None)
1613        else:
1614            proj = None
1615            user = None
1616            ename = None
1617        self.state_lock.release()
1618
1619        if not proj:
1620            raise service_error(service_error.internal, 
1621                    "Can't find project for %s" % aid)
1622
1623        if not user:
1624            raise service_error(service_error.internal, 
1625                    "Can't find creation user for %s" % aid)
1626        if not ename:
1627            raise service_error(service_error.internal, 
1628                    "Can't find experiment name for %s" % aid)
1629        stopper = self.stop_segment(keyfile=self.ssh_privkey_file,
1630                debug=self.create_debug)
1631        stopper(self, user, proj, ename)
1632        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.