source: fedd/federation/emulab_access.py @ 5f96438

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since 5f96438 was 1627c3d, checked in by Ted Faber <faber@…>, 15 years ago

Protect dragon from StartSegment? replays. Also writeout the replay protection in the other access controllers.

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