source: fedd/federation/access.py @ 22defdb

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

typeo

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