source: fedd/federation/emulab_access.py @ 617592b

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

More dragon cleanup. Config files look right, but need to be tested.

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