source: fedd/federation/emulab_access.py @ 6280b1f

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

SSH port numbers

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