source: fedd/federation/emulab_access.py @ 4e9719b

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

SEER stuff

  • Property mode set to 100644
File size: 55.4 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                            if type =='link':
1104                                s = ("tb-allow-external $%s dragonportal " + \
1105                                        "ip %s vlan %s\n") % \
1106                                        (e.name[0], addr, vlan)
1107                            elif type =='lan':
1108                                s = ("tb-allow-external $%s dragonportal " + \
1109                                        "ip %s vlan %s usurp %s\n") % \
1110                                        (e.name[0], addr, vlan, subs)
1111                            else:
1112                                raise service_error(service_error_internal,
1113                                        "Unknown DRAGON type %s" % type)
1114                return s
1115
1116        class not_dragon:
1117            def __init__(self, map):
1118                self.nodes = set(map.keys())
1119
1120            def __call__(self, e):
1121                return e.name[0] not in self.nodes
1122
1123        def add_kit(e, kit):
1124            """
1125            Add a Software object created from the list of (install, location)
1126            tuples passed as kit  to the software attribute of an object e.  We
1127            do this enough to break out the code, but it's kind of a hack to
1128            avoid changing the old tuple rep.
1129            """
1130
1131            s = [ topdl.Software(install=i, location=l) for i, l in kit]
1132
1133            if isinstance(e.software, list): e.software.extend(s)
1134            else: e.software = s
1135
1136
1137        t = topo.clone()
1138
1139        dragon_map = { }
1140        for i in [ i for i in connInfo if i['type'] == 'transit']:
1141            for a in i.get('fedAttr', []):
1142                if a['attribute'] == 'vlan_id':
1143                    vlan = a['value']
1144                    break
1145            else:
1146                raise service_error(service_error.internal, 
1147                        "No vlan tag")
1148            members = i.get('member', [])
1149            if len(members) > 1: type = 'lan'
1150            else: type = 'link'
1151
1152            try:
1153                for m in members:
1154                    if dragon_map.has_key(m['element']):
1155                        dragon_map[m['element']].append(( m['interface'], 
1156                            vlan, type))
1157                    else:
1158                        dragon_map[m['element']] = [( m['interface'], 
1159                            vlan, type),]
1160            except KeyError:
1161                raise service_error(service_error.req,
1162                        "Missing connectivity info")
1163
1164        # The startcmds for master and slave testbeds
1165        if master: 
1166            gate_cmd = self.attrs.get('MasterConnectorStartCmd', '/bin/true')
1167            node_cmd = self.attrs.get('MasterNodeStartCmd', 'bin/true')
1168        else: 
1169            gate_cmd = self.attrs.get('SlaveConnectorStartCmd', '/bin/true')
1170            node_cmd = self.attrs.get('SlaveNodeStartCmd', 'bin/true')
1171
1172        # Weed out the things we aren't going to instantiate: Segments, portal
1173        # substrates, and portal interfaces.  (The copy in the for loop allows
1174        # us to delete from e.elements in side the for loop).  While we're
1175        # touching all the elements, we also adjust paths from the original
1176        # testbed to local testbed paths and put the federation commands into
1177        # the start commands
1178        for e in [e for e in t.elements]:
1179            if isinstance(e, topdl.Segment):
1180                t.elements.remove(e)
1181            if isinstance(e, topdl.Computer):
1182                add_kit(e, self.federation_software)
1183                if e.get_attribute('portal') and gate_cmd:
1184                    # Add local portal support software
1185                    add_kit(e, self.portal_software)
1186                    # Portals never have a user-specified start command
1187                    e.set_attribute('startup', gate_cmd)
1188                elif node_cmd:
1189                    if e.get_attribute('startup'):
1190                        e.set_attribute('startup', "%s \\$USER '%s'" % \
1191                                (node_cmd, e.get_attribute('startup')))
1192                    else:
1193                        e.set_attribute('startup', node_cmd)
1194
1195                dinf = [i[0] for i in dragon_map.get(e.name[0], []) ]
1196                # Remove portal interfaces that do not connect to DRAGON
1197                e.interface = [i for i in e.interface \
1198                        if not i.get_attribute('portal') or i.name in dinf ]
1199            # Fix software paths
1200            for s in getattr(e, 'software', []):
1201                s.location = re.sub("^.*/", softdir, s.location)
1202
1203        t.substrates = [ s.clone() for s in t.substrates ]
1204        t.incorporate_elements()
1205
1206        # Customize the ns2 output for local portal commands and images
1207        filters = []
1208
1209        # NB: these are extra commands issued for the node, not the startcmds
1210        if master: cmd = self.attrs.get('MasterConnectorCmd', '')
1211        else: cmd = self.attrs.get('SlaveConnectorCmd', '')
1212
1213        if self.attrs.has_key('dragon'):
1214            add_filter = not_dragon(dragon_map)
1215            filters.append(dragon_commands(dragon_map))
1216        else:
1217            add_filter = None
1218
1219        if cmd:
1220            filters.append(topdl.generate_portal_command_filter(cmd,
1221                add_filter=add_filter))
1222
1223        if self.attrs.has_key('connectorImage'):
1224            filters.append(topdl.generate_portal_image_filter(
1225                self.attrs.get('connectorImage')))
1226
1227        if self.attrs.has_key('connectorType'):
1228            filters.append(topdl.generate_portal_hardware_filter(
1229                self.attrs.get('connectorType')))
1230
1231        # Convert to ns and write it out
1232        expfile = topdl.topology_to_ns2(t, filters)
1233        try:
1234            f = open(expfn, "w")
1235            print >>f, expfile
1236            f.close()
1237        except IOError:
1238            raise service_error(service_error.internal,
1239                    "Cannot write experiment file %s: %s" % (expfn,e))
1240
1241    def export_store_info(self, cf, proj, ename, connInfo):
1242        """
1243        For the export requests in the connection info, install the peer names
1244        at the experiment controller via SetValue calls.
1245        """
1246
1247        for c in connInfo:
1248            for p in [ p for p in c.get('parameter', []) \
1249                    if p.get('type', '') == 'output']:
1250
1251                if p.get('name', '') == 'peer':
1252                    k = p.get('key', None)
1253                    surl = p.get('store', None)
1254                    if surl and k and k.index('/') != -1:
1255                        value = "%s.%s.%s%s" % \
1256                                (k[k.index('/')+1:], ename, proj, self.domain)
1257                        req = { 'name': k, 'value': value }
1258                        self.call_SetValue(surl, req, cf)
1259                    else:
1260                        self.log.error("Bad export request: %s" % p)
1261                elif p.get('name', '') == 'ssh_port':
1262                    k = p.get('key', None)
1263                    surl = p.get('store', None)
1264                    if surl and k:
1265                        req = { 'name': k, 'value': self.ssh_port }
1266                        self.call_SetValue(surl, req, cf)
1267                    else:
1268                        self.log.error("Bad export request: %s" % p)
1269                else:
1270                    self.log.error("Unknown export parameter: %s" % \
1271                            p.get('name'))
1272                    continue
1273
1274    def import_store_info(self, cf, connInfo):
1275        """
1276        Pull any import parameters in connInfo in.  We translate them either
1277        into known member names or fedAddrs.
1278        """
1279
1280        for c in connInfo:
1281            for p in [ p for p in c.get('parameter', []) \
1282                    if p.get('type', '') == 'input']:
1283                name = p.get('name', None)
1284                key = p.get('key', None)
1285                store = p.get('store', None)
1286
1287                if name and key and store :
1288                    req = { 'name': key, 'wait': True }
1289                    r = self.call_GetValue(store, req, cf)
1290                    r = r.get('GetValueResponseBody', None)
1291                    if r :
1292                        if r.get('name', '') == key:
1293                            v = r.get('value', None)
1294                            if v is not None:
1295                                if name == 'peer':
1296                                    c['peer'] = v
1297                                else:
1298                                    if c.has_key('fedAttr'):
1299                                        c['fedAttr'].append({
1300                                            'attribute': name, 'value': v})
1301                                    else:
1302                                        c['fedAttr']= [{
1303                                            'attribute': name, 'value': v}]
1304                            else:
1305                                raise service_error(service_error.internal, 
1306                                        'None value exported for %s'  % key)
1307                        else:
1308                            raise service_error(service_error.internal, 
1309                                    'Different name returned for %s: %s' \
1310                                            % (key, r.get('name','')))
1311                    else:
1312                        raise service_error(service_error.internal, 
1313                            'Badly formatted response: no GetValueResponseBody')
1314                else:
1315                    raise service_error(service_error.internal, 
1316                        'Bad Services missing info for import %s' % c)
1317
1318    def StartSegment(self, req, fid):
1319        def get_url(url, cf, destdir, fn=None):
1320            po = urlparse(url)
1321            if not fn:
1322                fn = po.path.rpartition('/')[2]
1323            ok = False
1324            retries = 0
1325            while not ok and retries < 5:
1326                try:
1327                    conn = httplib.HTTPSConnection(po.hostname, port=po.port, 
1328                            cert_file=cf, key_file=cf)
1329                    conn.putrequest('GET', po.path)
1330                    conn.endheaders()
1331                    response = conn.getresponse()
1332
1333                    lf = open("%s/%s" % (destdir, fn), "w")
1334                    buf = response.read(4096)
1335                    while buf:
1336                        lf.write(buf)
1337                        buf = response.read(4096)
1338                    lf.close()
1339                    ok = True
1340                except IOError, e:
1341                    print e
1342                    raise service_error(service_error.internal,
1343                            "Error writing tempfile: %s" %e)
1344                except httplib.HTTPException, e:
1345                    print e
1346                    raise service_error(service_error.internal, 
1347                            "Error retrieving data: %s" % e)
1348                except SSLError, e:
1349                    print "SSL error %s" %e
1350                    retries += 1
1351
1352            if retries > 5:
1353                raise service_error(service_error.internal,
1354                        "Repeated SSL failures")
1355
1356        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
1357
1358        err = None  # Any service_error generated after tmpdir is created
1359        rv = None   # Return value from segment creation
1360
1361        try:
1362            req = req['StartSegmentRequestBody']
1363        except KeyError:
1364            raise service_error(server_error.req, "Badly formed request")
1365
1366        connInfo = req.get('connection', [])
1367        services = req.get('service', [])
1368        auth_attr = req['allocID']['fedid']
1369        aid = "%s" % auth_attr
1370        attrs = req.get('fedAttr', [])
1371        if not self.auth.check_attribute(fid, auth_attr):
1372            raise service_error(service_error.access, "Access denied")
1373
1374        if req.has_key('segmentdescription') and \
1375                req['segmentdescription'].has_key('topdldescription'):
1376            topo = \
1377                topdl.Topology(**req['segmentdescription']['topdldescription'])
1378        else:
1379            raise service_error(service_error.req, 
1380                    "Request missing segmentdescription'")
1381       
1382        master = req.get('master', False)
1383
1384        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1385        try:
1386            tmpdir = tempfile.mkdtemp(prefix="access-")
1387            softdir = "%s/software" % tmpdir
1388        except IOError:
1389            raise service_error(service_error.internal, "Cannot create tmp dir")
1390
1391        # Try block alllows us to clean up temporary files.
1392        try:
1393            sw = set()
1394            for e in topo.elements:
1395                for s in getattr(e, 'software', []):
1396                    sw.add(s.location)
1397            if len(sw) > 0:
1398                os.mkdir(softdir)
1399            for s in sw:
1400                self.log.debug("Retrieving %s" % s)
1401                get_url(s, certfile, softdir)
1402
1403            # Copy local portal node software to the tempdir
1404            for l, f in self.portal_software:
1405                base = os.path.basename(f)
1406                copy_file(f, "%s/%s" % (softdir, base))
1407
1408            for a in attrs:
1409                if a['attribute'] in configs:
1410                    get_url(a['value'], certfile, tmpdir)
1411                if a['attribute'] == 'ssh_pubkey':
1412                    pubkey_base = a['value'].rpartition('/')[2]
1413                if a['attribute'] == 'ssh_secretkey':
1414                    secretkey_base = a['value'].rpartition('/')[2]
1415                if a['attribute'] == 'experiment_name':
1416                    ename = a['value']
1417
1418            # If the userconf service was imported, collect the configuration
1419            # data.
1420            for s in services:
1421                if s.get("name", "") == 'userconfig' \
1422                        and s.get('visibility',"") == 'import':
1423
1424                    # Collect ther server and certificate info.
1425                    u = s.get('server', None)
1426                    for a in s.get('fedAttr', []):
1427                        if a.get('attribute',"") == 'cert':
1428                            cert = a.get('value', None)
1429                            break
1430                    else:
1431                        cert = None
1432
1433                    if cert:
1434                        # Make a temporary certificate file for get_url.  The
1435                        # finally clause removes it whether something goes
1436                        # wrong (including an exception from get_url) or not.
1437                        try:
1438                            tfos, tn = tempfile.mkstemp(suffix=".pem")
1439                            tf = os.fdopen(tfos, 'w')
1440                            print >>tf, cert
1441                            tf.close()
1442                            get_url(u, tn, tmpdir, "userconf")
1443                        except IOError, e:
1444                            raise service_error(service.error.internal, 
1445                                    "Cannot create temp file for " + 
1446                                    "userconfig certificates: %s e")
1447                        finally:
1448                            if tn: os.remove(tn)
1449                    else:
1450                        raise service_error(service_error.req,
1451                                "No certificate for retreiving userconfig")
1452                    break
1453
1454
1455
1456            proj = None
1457            user = None
1458            self.state_lock.acquire()
1459            if self.allocation.has_key(aid):
1460                proj = self.allocation[aid].get('project', None)
1461                if not proj: 
1462                    proj = self.allocation[aid].get('sproject', None)
1463                user = self.allocation[aid].get('user', None)
1464                self.allocation[aid]['experiment'] = ename
1465                self.allocation[aid]['log'] = [ ]
1466                # Create a logger that logs to the experiment's state object as
1467                # well as to the main log file.
1468                alloc_log = logging.getLogger('fedd.access.%s' % ename)
1469                h = logging.StreamHandler(
1470                        list_log.list_log(self.allocation[aid]['log']))
1471                # XXX: there should be a global one of these rather than
1472                # repeating the code.
1473                h.setFormatter(logging.Formatter(
1474                    "%(asctime)s %(name)s %(message)s",
1475                            '%d %b %y %H:%M:%S'))
1476                alloc_log.addHandler(h)
1477                self.write_state()
1478            self.state_lock.release()
1479
1480            if not proj:
1481                raise service_error(service_error.internal, 
1482                        "Can't find project for %s" %aid)
1483
1484            if not user:
1485                raise service_error(service_error.internal, 
1486                        "Can't find creation user for %s" %aid)
1487
1488            self.export_store_info(certfile, proj, ename, connInfo)
1489            self.import_store_info(certfile, connInfo)
1490
1491            expfile = "%s/experiment.tcl" % tmpdir
1492
1493            self.generate_portal_configs(topo, pubkey_base, 
1494                    secretkey_base, tmpdir, master, proj, ename, connInfo, 
1495                    services)
1496            self.generate_ns2(topo, expfile, 
1497                    "/proj/%s/software/%s/" % (proj, ename), master, connInfo)
1498
1499            starter = self.start_segment(keyfile=self.ssh_privkey_file, 
1500                    debug=self.create_debug, log=alloc_log)
1501            rv = starter(self, ename, proj, user, expfile, tmpdir)
1502        except service_error, e:
1503            err = e
1504        except e:
1505            err = service_error(service_error.internal, str(e))
1506
1507        # Walk up tmpdir, deleting as we go
1508        if self.cleanup:
1509            self.log.debug("[StartSegment]: removing %s" % tmpdir)
1510            for path, dirs, files in os.walk(tmpdir, topdown=False):
1511                for f in files:
1512                    os.remove(os.path.join(path, f))
1513                for d in dirs:
1514                    os.rmdir(os.path.join(path, d))
1515            os.rmdir(tmpdir)
1516        else:
1517            self.log.debug("[StartSegment]: not removing %s" % tmpdir)
1518
1519        if rv:
1520            # Grab the log (this is some anal locking, but better safe than
1521            # sorry)
1522            self.state_lock.acquire()
1523            logv = "".join(self.allocation[aid]['log'])
1524            self.state_lock.release()
1525
1526            return { 'allocID': req['allocID'], 'allocationLog': logv }
1527        elif err:
1528            raise service_error(service_error.federant,
1529                    "Swapin failed: %s" % err)
1530        else:
1531            raise service_error(service_error.federant, "Swapin failed")
1532
1533    def TerminateSegment(self, req, fid):
1534        try:
1535            req = req['TerminateSegmentRequestBody']
1536        except KeyError:
1537            raise service_error(server_error.req, "Badly formed request")
1538
1539        auth_attr = req['allocID']['fedid']
1540        aid = "%s" % auth_attr
1541        attrs = req.get('fedAttr', [])
1542        if not self.auth.check_attribute(fid, auth_attr):
1543            raise service_error(service_error.access, "Access denied")
1544
1545        self.state_lock.acquire()
1546        if self.allocation.has_key(aid):
1547            proj = self.allocation[aid].get('project', None)
1548            if not proj: 
1549                proj = self.allocation[aid].get('sproject', None)
1550            user = self.allocation[aid].get('user', None)
1551            ename = self.allocation[aid].get('experiment', None)
1552        else:
1553            proj = None
1554            user = None
1555            ename = None
1556        self.state_lock.release()
1557
1558        if not proj:
1559            raise service_error(service_error.internal, 
1560                    "Can't find project for %s" % aid)
1561
1562        if not user:
1563            raise service_error(service_error.internal, 
1564                    "Can't find creation user for %s" % aid)
1565        if not ename:
1566            raise service_error(service_error.internal, 
1567                    "Can't find experiment name for %s" % aid)
1568        stopper = self.stop_segment(keyfile=self.ssh_privkey_file,
1569                debug=self.create_debug)
1570        stopper(self, user, proj, ename)
1571        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.