source: fedd/federation/emulab_access.py @ c3a3fe3

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

propagate service imports to clients

  • Property mode set to 100644
File size: 50.1 KB
RevLine 
[19cc408]1#!/usr/local/bin/python
2
3import os,sys
[eeb0088]4import stat # for chmod constants
[19cc408]5import re
6import string
7import copy
[d81971a]8import pickle
[c971895]9import logging
[eeb0088]10import subprocess
[19cc408]11
[f8582c9]12from threading import *
13
[ec4fb42]14from util import *
15from allocate_project import allocate_project_local, allocate_project_remote
16from access_project import access_project
[51cc9df]17from fedid import fedid, generate_fedid
[3f6bc5f]18from authorizer import authorizer
[6a0c9f4]19from service_error import service_error
[9460b1e]20from remote_service import xmlrpc_handler, soap_handler, service_caller
[11a08b0]21
[6c57fe9]22import httplib
23import tempfile
24from urlparse import urlparse
25
[11860f52]26import topdl
27import list_log
28import proxy_emulab_segment
29import local_emulab_segment
30
[0ea11af]31
32# Make log messages disappear if noone configures a fedd logger
[11a08b0]33class nullHandler(logging.Handler):
34    def emit(self, record): pass
35
36fl = logging.getLogger("fedd.access")
37fl.addHandler(nullHandler())
[19cc408]38
[ec4fb42]39class access:
[19cc408]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
[4ed10ae]47    class parse_error(RuntimeError): pass
48
[72ed6e4]49
[f069052]50    proxy_RequestAccess= service_caller('RequestAccess')
51    proxy_ReleaseAccess= service_caller('ReleaseAccess')
[c35207d]52
[3f6bc5f]53    def __init__(self, config=None, auth=None):
[866c983]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")
[6c57fe9]70        self.certdir = config.get("access","certdir")
[eeb0088]71        self.userconfdir = config.get("access","userconfdir")
72        self.userconfcmd = config.get("access","userconfcmd")
[fe28bb2]73        self.userconfurl = config.get("access","userconfurl")
[ecca6eb]74        self.ssh_privkey_file = config.get("access","ssh_privkey_file")
[3bddd24]75        self.ssh_pubkey_file = config.get("access","ssh_pubkey_file")
[5ae3857]76        self.create_debug = config.getboolean("access", "create_debug")
[69692a9]77        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
[11860f52]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
[866c983]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()
[e02cd14]107        # XXX: Configurable
[eeb0088]108        self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig'))
[b7f6ccc]109        self.imports = set(('SMB', 'seer', 'userconfig'))
[866c983]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),
[cc8d8e9]141            'StartSegment': soap_handler("StartSegment", self.StartSegment),
[5ae3857]142            'TerminateSegment': soap_handler("TerminateSegment", self.TerminateSegment),
[866c983]143            }
144        self.xmlrpc_services =  {\
145            'RequestAccess': xmlrpc_handler('RequestAccess',
146                self.RequestAccess),
147            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
148                self.ReleaseAccess),
[5ae3857]149            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
150            'TerminateSegment': xmlrpc_handler('TerminateSegment',
151                self.TerminateSegment),
[866c983]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)
[f8582c9]166
[72ed6e4]167
168    def read_access(self, config):
[866c983]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()
[72ed6e4]268
[19cc408]269    def get_users(self, obj):
[866c983]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
[19cc408]278
[d81971a]279    def write_state(self):
[866c983]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)
[d81971a]291
292
293    def read_state(self):
[866c983]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))
[eeb0088]328                if self.allocation[k].has_key('userconfig'):
[fe28bb2]329                    sfid = self.allocation[k]['userconfig']
[eeb0088]330                    fid = fedid(hexstr=sfid)
[fe28bb2]331                    self.auth.set_attribute(fid, "/%s" % sfid)
[3f6bc5f]332
333
[19cc408]334    def permute_wildcards(self, a, p):
[866c983]335        """Return a copy of a with various fields wildcarded.
[19cc408]336
[866c983]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]
[19cc408]346
[866c983]347        return (tb, proj, user)
[19cc408]348
349    def find_access(self, search):
[866c983]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
[19cc408]372
373    def lookup_access(self, req, fid):
[866c983]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        """
[3bddd24]380        user_re = re.compile("user:\s(.*)")
381        project_re = re.compile("project:\s(.*)")
382
[866c983]383        # Search keys
384        tb = None
385        project = None
386        user = None
387        # Return values
388        rp = access_project(None, ())
389        ru = None
390
[3bddd24]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
[866c983]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
[19cc408]498
[eeb0088]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
[fe28bb2]528        self.auth.set_attribute(confid, "/%s" % str(confid))
[eeb0088]529
530        return confid, confcert
531
532
[e02cd14]533    def export_services(self, sreq, project, user):
534        exp = [ ]
[eeb0088]535        state = { }
536        # XXX: Filthy shortcut here using http: so urlparse will give the right
[e02cd14]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"
[eeb0088]556                    elif sname == 'userconfig':
[fe28bb2]557                        if self.userconfdir and self.userconfcmd \
558                                and self.userconfurl:
[eeb0088]559                            cid, cert = self.export_userconf(project)
[fe28bb2]560                            outs['server'] = "%s/%s" % \
561                                    (self.userconfurl, str(cid))
[eeb0088]562                            outs['fedAttr'] = [
563                                    { 'attribute': 'cert', 'value': cert },
564                                ]
565                            state['userconfig'] = unicode(cid)
[e02cd14]566                    exp.append(outs)
[eeb0088]567        return (exp, state)
[e02cd14]568
569    def build_response(self, alloc_id, ap, services):
[866c983]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,
[4afcfc4]582                'fedAttr': [
583                    { 'attribute': 'domain', 'value': self.domain } , 
584                    { 'attribute': 'project', 'value': 
585                        ap['project'].get('name', {}).get('localname', "???") },
586                ]
[866c983]587            }
588        if len(self.attrs) > 0:
[4afcfc4]589            msg['fedAttr'].extend(
[866c983]590                [ { 'attribute': x, 'value' : y } \
[4afcfc4]591                        for x,y in self.attrs.iteritems()])
[e02cd14]592
593        if services:
594            msg['service'] = services
[866c983]595        return msg
[19cc408]596
597    def RequestAccess(self, req, fid):
[866c983]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))
[3bddd24]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 ]
[866c983]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
[ecca6eb]725            else:
726                # sproject is a static project associated with this allocation.
727                self.allocation[aid]['sproject'] = pname
[866c983]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']
[ecca6eb]750                    if u['role'] == 'experimentCreation':
751                        self.allocation[aid]['user'] = uname
[866c983]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
[eeb0088]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
[866c983]769            self.write_state()
770            self.state_lock.release()
771            for o in owners:
772                self.auth.set_attribute(o, allocID)
[6c57fe9]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))
[e02cd14]780            resp = self.build_response({ 'fedid': allocID } , ap, services)
[866c983]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")
[d81971a]794
795    def ReleaseAccess(self, req, fid):
[866c983]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)
[f07fa49]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)
[866c983]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")
[d81971a]908
[f9ef40b]909    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
[e02cd14]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            for i in info:
920                if i.get('portal', "") == key:
921                    rv = i.copy()
922                    break
923            else:
924                return rv
925
926            if 'fedAttr' in rv:
927                for a in rv['fedAttr']:
928                    attr = a.get('attribute', "")
929                    val = a.get('value', "")
930                    if attr and attr not in rv:
931                        rv[attr] = val
932                del rv['fedAttr']
933            return rv
934
935        # XXX: un hardcode this
[b7f6ccc]936        def client_null(f, s):
937            print >>f, "Service: %s" % s['name']
[e02cd14]938
939        def client_smb(f, s):
[b7f6ccc]940            print >>f, "Service: %s" % s['name']
[e02cd14]941            smbshare = None
942            smbuser = None
943            smbproj = None
944            for a in s.get('fedAttr', []):
945                if a.get('attribute', '') == 'SMBSHARE':
946                    smbshare = a.get('value', None)
947                elif a.get('attribute', '') == 'SMBUSER':
948                    smbuser = a.get('value', None)
949                elif a.get('attribute', '') == 'SMBPROJ':
950                    smbproj = a.get('value', None)
951
952            if all((smbshare, smbuser, smbproj)):
953                print >>f, "SMBshare: %s" % smbshare
954                print >>f, "ProjectUser: %s" % smbuser
955                print >>f, "ProjectName: %s" % smbproj
956
957        client_service_out = {
958                'SMB': client_smb,
959                'tmcd': client_null,
960                'seer': client_null,
961                'userconfig': client_null,
962            }
963        # XXX: end un hardcode this
964
[ecca6eb]965
966        seer_out = False
[1da6a23]967        client_out = False
[e2a7a413]968        for e in [ e for e in topo.elements \
[f9ef40b]969                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
970            myname = e.name[0]
[ecca6eb]971            type = e.get_attribute('portal_type')
[f9ef40b]972
[e02cd14]973            info = conninfo_to_dict(myname, connInfo)
974
975            if not info:
976                raise service_error(service_error.req,
977                        "No connectivity info for %s" % myname)
978
979            peer = info.get('peer', "")
980            ldomain = self.domain;
981
982            mexp = info.get('masterexperiment',"")
983            mproj, meid = mexp.split("/", 1)
984            mdomain = info.get('masterdomain',"")
985            muser = info.get('masteruser','root')
986            smbshare = info.get('smbshare', 'USERS')
987
988            active = info.get('active', 'False')
989
[0297248]990            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
[1da6a23]991            tunnelconfig = self.attrs.has_key('TunnelCfg')
[f9ef40b]992            try:
993                f = open(cfn, "w")
[0297248]994                if active == 'True':
[d87778f]995                    print >>f, "active: True"
[0297248]996                    if type in ('control', 'both'):
[e02cd14]997                        for s in [s for s in services \
998                                if s.get('name', "") in self.imports]:
999                            p = urlparse(s.get('server', 'http://localhost'))
1000                            print >>f, 'port: remote:%s:%s:%s' % \
1001                                    (p.port, p.hostname, p.port)
[0297248]1002
1003                if tunnelconfig:
1004                    print >>f, "tunnelip: %s" % tunnelconfig
[e02cd14]1005                # XXX: send this an fedattr
1006                #print >>f, "seercontrol: control.%s.%s%s" % \
1007                        #(meid.lower(), mproj.lower(), mdomain)
1008                print >>f, "peer: %s" % peer.lower()
[0297248]1009                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
[f9ef40b]1010                        (lproj, leid, pubkey_base)
[0297248]1011                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
[f9ef40b]1012                        (lproj, leid, secretkey_base)
1013                f.close()
1014            except IOError, e:
1015                raise service_error(service_error.internal,
1016                        "Can't write protal config %s: %s" % (cfn, e))
[ecca6eb]1017           
1018            # XXX: This little seer config file needs to go away.
1019            if not seer_out:
1020                try:
1021                    seerfn = "%s/seer.conf" % tmpdir
1022                    f = open(seerfn, "w")
1023                    if not master:
1024                        print >>f, "ControlNode: control.%s.%s%s" % \
1025                            (meid.lower(), mproj.lower(), mdomain)
1026                    print >>f, "ExperimentID: %s" % mexp
1027                    f.close()
1028                except IOError, e:
1029                    raise service_error(service_error.internal, 
1030                            "Can't write seer.conf: %s" %e)
1031                seer_out = True
1032
[1da6a23]1033            if not client_out and type in ('control', 'both'):
1034                try:
1035                    f = open("%s/client.conf" % tmpdir, "w")
1036                    print >>f, "ControlGateway: %s.%s.%s%s" % \
1037                        (myname.lower(), leid.lower(), lproj.lower(),
1038                                ldomain.lower())
[e02cd14]1039                    for s in services:
1040                        if s.get('name',"") in self.imports:
1041                            client_service_out[s['name']](f, s)
1042                    # Does seer need this?
1043                    # print >>f, "ExperimentID: %s/%s" % (mproj, meid)
[1da6a23]1044                    f.close()
1045                except IOError, e:
1046                    raise service_error(service_error.internal,
1047                            "Cannot write client.conf: %s" %s)
1048                client_out = True
1049
[ecca6eb]1050
1051    def generate_ns2(self, topo, expfn, softdir, master):
[69692a9]1052        def dragon_commands(e):
1053            s = ""
1054            if isinstance(e, topdl.Computer):
1055                for i in e.interface:
1056                    vlan = i.get_attribute('dragon_vlan')
1057                    if vlan:
1058                        type = i.get_attribute('dragon_type')
1059                        ip = i.get_attribute('ip4_address')
1060                        if type == 'link':
1061                            s = ("tb-allow-external $%s dragonportal " + \
1062                                    "ip %s vlan %s\n") % \
1063                                    (topdl.to_tcl_name(e.name[0]), ip, vlan)
1064                        elif type == 'lan':
1065                            s = ("tb-allow-external $%s dragonportal " + \
1066                                    "ip %s vlan %s usurp %s\n") % \
1067                                    (topdl.to_tcl_name(e.name[0]), ip, vlan,
1068                                        i.substrate[0])
1069                        else:
1070                            raise service_error(service_error_internal,
1071                                    "Unknown DRAGON type %s" % type)
1072            return s
1073
1074        def not_dragon(e):
1075            return all([not i.get_attribute('dragon_vlan') \
1076                    for i in e.interface])
1077
[ecca6eb]1078        t = topo.clone()
1079
[43649f1]1080        # The startcmds for master and slave testbeds
1081        if master: 
1082            gate_cmd = self.attrs.get('MasterConnectorStartCmd', '/bin/true')
1083            node_cmd = self.attrs.get('MasterNodeStartCmd', 'bin/true')
1084        else: 
1085            gate_cmd = self.attrs.get('SlaveConnectorStartCmd', '/bin/true')
1086            node_cmd = self.attrs.get('SlaveNodeStartCmd', 'bin/true')
[0297248]1087
[35aa3ae]1088        # Weed out the things we aren't going to instantiate: Segments, portal
1089        # substrates, and portal interfaces.  (The copy in the for loop allows
1090        # us to delete from e.elements in side the for loop).  While we're
1091        # touching all the elements, we also adjust paths from the original
1092        # testbed to local testbed paths and put the federation commands into
1093        # the start commands
1094        for e in [e for e in t.elements]:
1095            if isinstance(e, topdl.Segment):
1096                t.elements.remove(e)
1097            # Fix software paths
[ecca6eb]1098            for s in getattr(e, 'software', []):
1099                s.location = re.sub("^.*/", softdir, s.location)
[43649f1]1100            if isinstance(e, topdl.Computer):
[35aa3ae]1101                if e.get_attribute('portal') and gate_cmd:
[43649f1]1102                    # Portals never have a user-specified start command
1103                    e.set_attribute('startup', gate_cmd)
[35aa3ae]1104                elif node_cmd:
[43649f1]1105                    if e.get_attribute('startup'):
[d87778f]1106                        e.set_attribute('startup', "%s \\$USER '%s'" % \
[43649f1]1107                                (node_cmd, e.get_attribute('startup')))
1108                    else:
[d87778f]1109                        e.set_attribute('startup', node_cmd)
[0297248]1110
[35aa3ae]1111                # Remove portal interfaces that do not connect to DRAGON
1112                e.interface = [i for i in e.interface \
1113                        if not i.get_attribute('portal') or \
1114                            i.get_attribute('dragon_vlan')]
1115
1116        t.substrates = [ s.clone() for s in t.substrates ]
1117        t.incorporate_elements()
[ecca6eb]1118
1119        # Customize the ns2 output for local portal commands and images
1120        filters = []
1121
[43649f1]1122        # NB: these are extra commands issued for the node, not the startcmds
1123        if master: cmd = self.attrs.get('MasterConnectorCmd', '')
1124        else: cmd = self.attrs.get('SlaveConnectorCmd', '')
[ecca6eb]1125
[69692a9]1126        if self.attrs.has_key('dragon'):
1127            add_filter = not_dragon
1128            filters.append(dragon_commands)
1129        else:
1130            add_filter = None
1131
[43649f1]1132        if cmd:
1133            filters.append(topdl.generate_portal_command_filter(cmd,
1134                add_filter=add_filter))
[ecca6eb]1135
1136        if self.attrs.has_key('connectorImage'):
1137            filters.append(topdl.generate_portal_image_filter(
1138                self.attrs.get('connectorImage')))
1139
1140        if self.attrs.has_key('connectorType'):
1141            filters.append(topdl.generate_portal_hardware_filter(
1142                self.attrs.get('connectorType')))
1143
1144        # Convert to ns and write it out
1145        expfile = topdl.topology_to_ns2(t, filters)
1146        try:
1147            f = open(expfn, "w")
1148            print >>f, expfile
1149            f.close()
1150        except IOError:
1151            raise service_error(service_error.internal,
1152                    "Cannot write experiment file %s: %s" % (expfn,e))
[f9ef40b]1153
[cc8d8e9]1154    def StartSegment(self, req, fid):
[fe28bb2]1155        def get_url(url, cf, destdir, fn=None):
[6c57fe9]1156            po = urlparse(url)
[fe28bb2]1157            if not fn:
1158                fn = po.path.rpartition('/')[2]
[6c57fe9]1159            try:
1160                conn = httplib.HTTPSConnection(po.hostname, port=po.port, 
1161                        cert_file=cf, key_file=cf)
1162                conn.putrequest('GET', po.path)
1163                conn.endheaders()
1164                response = conn.getresponse()
1165
[574055e]1166                lf = open("%s/%s" % (destdir, fn), "w")
[6c57fe9]1167                buf = response.read(4096)
1168                while buf:
1169                    lf.write(buf)
1170                    buf = response.read(4096)
1171                lf.close()
1172            except IOError, e:
[fe28bb2]1173                print e
[6c57fe9]1174                raise service_error(service_error.internal,
[fe28bb2]1175                        "Error writing tempfile: %s" %e)
[6c57fe9]1176            except httplib.HTTPException, e:
[fe28bb2]1177                print e
[6c57fe9]1178                raise service_error(service_error.internal, 
1179                        "Error retrieving data: %s" % e)
1180
1181        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
1182
[b770aa0]1183        err = None  # Any service_error generated after tmpdir is created
1184        rv = None   # Return value from segment creation
1185
[cc8d8e9]1186        try:
1187            req = req['StartSegmentRequestBody']
1188        except KeyError:
1189            raise service_error(server_error.req, "Badly formed request")
[ecca6eb]1190
[e02cd14]1191        connInfo = req.get('connection', [])
1192        services = req.get('service', [])
[cc8d8e9]1193        auth_attr = req['allocID']['fedid']
[ecca6eb]1194        aid = "%s" % auth_attr
[6c57fe9]1195        attrs = req.get('fedAttr', [])
[ecca6eb]1196        if not self.auth.check_attribute(fid, auth_attr):
1197            raise service_error(service_error.access, "Access denied")
[6c57fe9]1198
1199        if req.has_key('segmentdescription') and \
1200                req['segmentdescription'].has_key('topdldescription'):
1201            topo = \
1202                topdl.Topology(**req['segmentdescription']['topdldescription'])
1203        else:
1204            raise service_error(service_error.req, 
1205                    "Request missing segmentdescription'")
1206
[ecca6eb]1207        master = req.get('master', False)
1208
[6c57fe9]1209        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1210        try:
1211            tmpdir = tempfile.mkdtemp(prefix="access-")
[ecca6eb]1212            softdir = "%s/software" % tmpdir
[6c57fe9]1213        except IOError:
1214            raise service_error(service_error.internal, "Cannot create tmp dir")
1215
[b770aa0]1216        # Try block alllows us to clean up temporary files.
1217        try:
1218            sw = set()
1219            for e in topo.elements:
1220                for s in getattr(e, 'software', []):
1221                    sw.add(s.location)
1222            if len(sw) > 0:
1223                os.mkdir(softdir)
1224            for s in sw:
[e02cd14]1225                self.log.debug("Retrieving %s" % s)
[b770aa0]1226                get_url(s, certfile, softdir)
1227
1228            for a in attrs:
1229                if a['attribute'] in configs:
1230                    get_url(a['value'], certfile, tmpdir)
1231                if a['attribute'] == 'ssh_pubkey':
1232                    pubkey_base = a['value'].rpartition('/')[2]
1233                if a['attribute'] == 'ssh_secretkey':
1234                    secretkey_base = a['value'].rpartition('/')[2]
1235                if a['attribute'] == 'experiment_name':
1236                    ename = a['value']
1237
[fe28bb2]1238            # If the userconf service was imported, collect the configuration
1239            # data.
1240            for s in services:
1241                if s.get("name", "") == 'userconfig' \
1242                        and s.get('visibility',"") == 'import':
[94a00cb]1243
1244                    # Collect ther server and certificate info.
[fe28bb2]1245                    u = s.get('server', None)
1246                    for a in s.get('fedAttr', []):
1247                        if a.get('attribute',"") == 'cert':
1248                            cert = a.get('value', None)
1249                            break
1250                    else:
1251                        cert = None
1252
[94a00cb]1253                    if cert:
1254                        # Make a temporary certificate file for get_url.  The
1255                        # finally clause removes it whether something goes
1256                        # wrong (including an exception from get_url) or not.
1257                        try:
1258                            tfos, tn = tempfile.mkstemp(suffix=".pem")
1259                            tf = os.fdopen(tfos, 'w')
1260                            print >>tf, cert
1261                            tf.close()
1262                            get_url(u, tn, tmpdir, "userconf")
1263                        except IOError, e:
1264                            raise service_error(service.error.internal, 
1265                                    "Cannot create temp file for " + 
1266                                    "userconfig certificates: %s e")
1267                        finally:
1268                            if tn: os.remove(tn)
1269                    else:
[fe28bb2]1270                        raise service_error(service_error.req,
1271                                "No certificate for retreiving userconfig")
1272                    break
1273
1274
1275
[b770aa0]1276            proj = None
1277            user = None
1278            self.state_lock.acquire()
1279            if self.allocation.has_key(aid):
1280                proj = self.allocation[aid].get('project', None)
1281                if not proj: 
1282                    proj = self.allocation[aid].get('sproject', None)
1283                user = self.allocation[aid].get('user', None)
1284                self.allocation[aid]['experiment'] = ename
[f07fa49]1285                self.allocation[aid]['log'] = [ ]
1286                # Create a logger that logs to the experiment's state object as
1287                # well as to the main log file.
1288                alloc_log = logging.getLogger('fedd.access.%s' % ename)
1289                h = logging.StreamHandler(
1290                        list_log.list_log(self.allocation[aid]['log']))
1291                # XXX: there should be a global one of these rather than
1292                # repeating the code.
1293                h.setFormatter(logging.Formatter(
1294                    "%(asctime)s %(name)s %(message)s",
1295                            '%d %b %y %H:%M:%S'))
1296                alloc_log.addHandler(h)
[b770aa0]1297                self.write_state()
1298            self.state_lock.release()
1299
1300            if not proj:
1301                raise service_error(service_error.internal, 
1302                        "Can't find project for %s" %aid)
1303
1304            if not user:
1305                raise service_error(service_error.internal, 
1306                        "Can't find creation user for %s" %aid)
1307
1308            expfile = "%s/experiment.tcl" % tmpdir
1309
1310            self.generate_portal_configs(topo, pubkey_base, 
[e02cd14]1311                    secretkey_base, tmpdir, master, proj, ename, connInfo, 
1312                    services)
[b770aa0]1313            self.generate_ns2(topo, expfile, 
1314                    "/proj/%s/software/%s/" % (proj, ename), master)
[f07fa49]1315
[b770aa0]1316            starter = self.start_segment(keyfile=self.ssh_privkey_file, 
[f07fa49]1317                    debug=self.create_debug, log=alloc_log)
[b770aa0]1318            rv = starter(self, ename, proj, user, expfile, tmpdir)
1319        except service_error, e:
1320            err = e
[e02cd14]1321        except e:
1322            err = service_error(service_error.internal, str(e))
[b770aa0]1323
[574055e]1324        # Walk up tmpdir, deleting as we go
[69692a9]1325        if self.cleanup:
1326            self.log.debug("[StartSegment]: removing %s" % tmpdir)
1327            for path, dirs, files in os.walk(tmpdir, topdown=False):
1328                for f in files:
1329                    os.remove(os.path.join(path, f))
1330                for d in dirs:
1331                    os.rmdir(os.path.join(path, d))
1332            os.rmdir(tmpdir)
1333        else:
1334            self.log.debug("[StartSegment]: not removing %s" % tmpdir)
[574055e]1335
[fd556d1]1336        if rv:
[f07fa49]1337            # Grab the log (this is some anal locking, but better safe than
1338            # sorry)
1339            self.state_lock.acquire()
1340            logv = "".join(self.allocation[aid]['log'])
1341            self.state_lock.release()
1342
1343            return { 'allocID': req['allocID'], 'allocationLog': logv }
[b770aa0]1344        elif err:
1345            raise service_error(service_error.federant,
1346                    "Swapin failed: %s" % err)
[fd556d1]1347        else:
1348            raise service_error(service_error.federant, "Swapin failed")
[5ae3857]1349
1350    def TerminateSegment(self, req, fid):
1351        try:
1352            req = req['TerminateSegmentRequestBody']
1353        except KeyError:
1354            raise service_error(server_error.req, "Badly formed request")
1355
1356        auth_attr = req['allocID']['fedid']
1357        aid = "%s" % auth_attr
1358        attrs = req.get('fedAttr', [])
1359        if not self.auth.check_attribute(fid, auth_attr):
1360            raise service_error(service_error.access, "Access denied")
1361
1362        self.state_lock.acquire()
1363        if self.allocation.has_key(aid):
1364            proj = self.allocation[aid].get('project', None)
1365            if not proj: 
1366                proj = self.allocation[aid].get('sproject', None)
1367            user = self.allocation[aid].get('user', None)
1368            ename = self.allocation[aid].get('experiment', None)
[1d913e13]1369        else:
1370            proj = None
1371            user = None
1372            ename = None
[5ae3857]1373        self.state_lock.release()
1374
1375        if not proj:
1376            raise service_error(service_error.internal, 
1377                    "Can't find project for %s" % aid)
1378
1379        if not user:
1380            raise service_error(service_error.internal, 
1381                    "Can't find creation user for %s" % aid)
1382        if not ename:
1383            raise service_error(service_error.internal, 
1384                    "Can't find experiment name for %s" % aid)
[fd556d1]1385        stopper = self.stop_segment(keyfile=self.ssh_privkey_file,
1386                debug=self.create_debug)
[5ae3857]1387        stopper(self, user, proj, ename)
1388        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.