source: fedd/federation/desktop_access.py @ 4cf0198

Last change on this file since 4cf0198 was 4cf0198, checked in by Ted Faber <faber@…>, 10 years ago

Cleaner connect/disconnect

  • Property mode set to 100644
File size: 24.0 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9import random
10import subprocess
11
12from util import *
13from deter import fedid, generate_fedid
14from authorizer import authorizer, abac_authorizer
15from service_error import service_error
16from remote_service import xmlrpc_handler, soap_handler, service_caller
17
18from deter import topdl
19
20from access import access_base
21
22# Make log messages disappear if noone configures a fedd logger.  This is
23# something of an incantation, but basically it creates a logger object
24# registered to fedd.access if no other module above us has.  It's an extra
25# belt for the suspenders.
26class nullHandler(logging.Handler):
27    def emit(self, record): pass
28
29fl = logging.getLogger("fedd.access")
30fl.addHandler(nullHandler())
31
32
33# The plug-in itself.
34class access(access_base):
35    """
36    This is a demonstration plug-in for fedd.  It responds to all the
37    experiment_control requests and keeps internal state.  The allocations it
38    makes are simple integers associated with each valid request.  It makes use
39    of the general routines in access.access_base.
40
41    Detailed comments in the code and info at
42    """
43    def __init__(self, config=None, auth=None):
44        """
45        Initializer.  Pulls parameters out of the ConfigParser's access
46        section, and initializes simple internal state.  This version reads a
47        maximum integer to assign from the configuration file, while most other
48        configuration entries  are read by the base class. 
49
50        An access database in the cannonical format is also read as well as a
51        state database that is a hash of internal state.  Routines to
52        manipulate these are in the base class, but specializations appear
53        here.
54
55        The access database maps users to a simple string.
56        """
57
58        # Calling the base initializer, which reads canonical configuration
59        # information and initializes canonical members.
60        access_base.__init__(self, config, auth)
61        # Reading the maximum integer parameter from the configuration file
62
63        self.src_addr = config.get('access', 'interface_address')
64        self.router = config.get('access', 'gateway')
65        self.hostname = config.get('access', 'hostname')
66        # Storage for ephemeral ssh keys and host files
67        self.localdir = config.get('access', 'localdir')
68        # File containing the routing entries for external networks
69        self.external_networks = config.get('access', 'external_networks')
70        # Comma-separated list of interfaces to export (may be empty)
71        self.export_interfaces = config.get('access', 'export_interfaces')
72        if self.export_interfaces is not None:
73            self.export_interfaces = \
74                    [ x.strip() for x in self.export_interfaces.split(',')]
75        else:
76            self.export_interfaces = []
77
78        # Comma separated list of connected nets to export - these won't work
79        # in external_networks
80        self.export_networks = config.get('access', 'export_networks')
81        if self.export_networks is not None:
82            self.export_networks = \
83                    [ x.strip() for x in self.export_networks.split(',')]
84        else:
85            self.export_networks = []
86
87        # values for locations of zebra and ospfd.
88        self.zebra = config.get('access', 'zebra')
89        if self.zebra is None:
90            self.zebra = '/usr/local/sbin/zebra'
91        self.ospfd = config.get('access', 'ospfd')
92        if self.ospfd is None:
93            self.ospfd = '/usr/local/sbin/ospfd'
94
95        # If this is a linux box that will be NATing, the iptables value
96        # must be the path of the iptables command and the nat_interface must
97        # be the nat interface.
98        self.iptables = config.get('access', 'iptables')
99        self.nat_interface = config.get('access', 'nat_interface')
100
101        self.ssh_identity = None
102
103        # hostname is the name of the ssh endpoint for the other side.  That
104        # side needs it to set up routing tables.  If hostname is not
105        # available, but an IP address is, use that.
106        if self.hostname is None:
107            if  self.src_addr is None:
108                raise service_error(service_error.server_config,
109                        'Hostname or interface_address must be set in config')
110            self.hostname = self.src_addr
111       
112        self.ssh_port = config.get('access', 'ssh_port', '22')
113
114        # authorization information
115        self.auth_type = config.get('access', 'auth_type') \
116                or 'abac'
117        self.auth_dir = config.get('access', 'auth_dir')
118        accessdb = config.get("access", "accessdb")
119        # initialize the authorization system.  We make a call to
120        # read the access database that maps from authorization information
121        # into local information.  The local information is parsed by the
122        # translator above.
123        if self.auth_type == 'abac':
124            #  Load the current authorization state
125            self.auth = abac_authorizer(load=self.auth_dir)
126            self.access = [ ]
127            if accessdb:
128                try:
129                    self.read_access(accessdb)
130                except EnvironmentError, e:
131                    self.log.error("Cannot read %s: %s" % \
132                            (config.get("access", "accessdb"), e))
133                    raise e
134        else:
135            raise service_error(service_error.internal, 
136                    "Unknown auth_type: %s" % self.auth_type)
137
138        # The superclass has read the state, but if this is the first run ever,
139        # we must initialise the running flag.  This plugin only supports one
140        # connection, so StartSegment will fail when self.state['running'] is
141        # true.
142        self.state_lock.acquire()
143        if 'running' not in self.state:
144            self.state['running'] = False
145        self.state_lock.release()
146
147        # These dictionaries register the plug-in's local routines for handline
148        # these four messages with the server code above.  There's a version
149        # for SOAP and XMLRPC, depending on which interfaces the plugin
150        # supports.  There's rarely a technical reason not to support one or
151        # the other - the plugin code almost never deals with the transport -
152        # but if a plug-in writer wanted to disable XMLRPC, they could leave
153        # the self.xmlrpc_services dictionary empty.
154        self.soap_services = {\
155            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
156            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
157            'StartSegment': soap_handler("StartSegment", self.StartSegment),
158            'TerminateSegment': soap_handler("TerminateSegment", 
159                self.TerminateSegment),
160            }
161        self.xmlrpc_services =  {\
162            'RequestAccess': xmlrpc_handler('RequestAccess',
163                self.RequestAccess),
164            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
165                self.ReleaseAccess),
166            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
167            'TerminateSegment': xmlrpc_handler('TerminateSegment',
168                self.TerminateSegment),
169            }
170        self.call_SetValue = service_caller('SetValue', log=self.log)
171        self.call_GetValue = service_caller('GetValue', log=self.log)
172
173    # ReleaseAccess come from the base class, this is a slightly modified
174    # RequestAccess from the base that includes a fedAttr to force this side to
175    # be active.
176    def RequestAccess(self, req, fid):
177        """
178        Handle an access request.  Success here maps the requester into the
179        local access control space and establishes state about that user keyed
180        to a fedid.  We also save a copy of the certificate underlying that
181        fedid so this allocation can access configuration information and
182        shared parameters on the experiment controller.
183        """
184
185        self.log.info("RequestAccess called by %s" % fid)
186        # The dance to get into the request body
187        if req.has_key('RequestAccessRequestBody'):
188            req = req['RequestAccessRequestBody']
189        else:
190            raise service_error(service_error.req, "No request!?")
191
192        # Base class lookup routine.  If this fails, it throws a service
193        # exception denying access that triggers a fault response back to the
194        # caller.
195        found,  owners, proof = self.lookup_access(req, fid)
196        self.log.info(
197                "[RequestAccess] Access granted local creds %s" % found)
198        # Make a fedid for this allocation
199        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
200        aid = unicode(allocID)
201
202        # Store the data about this allocation:
203        self.state_lock.acquire()
204        self.state[aid] = { }
205        self.state[aid]['user'] = found
206        self.state[aid]['owners'] = owners
207        self.state[aid]['auth'] = set()
208        # Authorize the creating fedid and the principal representing the
209        # allocation to manipulate it.
210        self.append_allocation_authorization(aid, 
211                ((fid, allocID), (allocID, allocID)))
212        self.write_state()
213        self.state_lock.release()
214
215        # Create a directory to stash the certificate in, ans stash it.
216        try:
217            f = open("%s/%s.pem" % (self.certdir, aid), "w")
218            print >>f, alloc_cert
219            f.close()
220        except EnvironmentError, e:
221            raise service_error(service_error.internal, 
222                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
223        self.log.debug('[RequestAccess] Returning allocation ID: %s' % allocID)
224        msg = { 
225                'allocID': { 'fedid': allocID }, 
226                'fedAttr': [{ 'attribute': 'nat_portals', 'value': 'True' }],
227                'proof': proof.to_dict()
228                }
229        return msg
230
231    def validate_topology(self, top):
232        '''
233        Validate the topology.  Desktops can only be single connections.
234        Though the topology will include a portal and a node, the access
235        controller will implement both on one node.
236
237        As more capabilities are added to the contoller the constraints here
238        will relax.
239        '''
240
241        comps = []
242        for e in top.elements:
243            if isinstance(e, topdl.Computer): comps.append(e)
244        if len(comps) > 2: 
245            raise service_error(service_error.req,
246                    "Desktop only supports 1-node subexperiments")
247
248        portals = 0
249        for c in comps:
250            if c.get_attribute('portal') is not None: 
251                portals += 1
252                continue
253            if len(c.interface) > 1:
254                raise service_error(service_error.req,
255                        "Desktop Node has more than one interface")
256            i  = c.interface[0]
257            if len(i.subs) > 1: 
258                raise service_error(service_error.req,
259                        "Desktop Node has more than one substate on interface")
260            sub = i.subs[0]
261            for i in sub.interfaces:
262                if i.element not in comps:
263                    raise service_error(service_error.req,
264                            "Desktop Node connected to non-portal")
265
266        if portals > 1:
267            raise service_error(service_error.req,
268                    "Desktop segment has more than one portal")
269        return True
270
271    def validate_connInfo(self, connInfo):
272        if len(connInfo) != 1: 
273            raise service_error(service_error.req,
274                    "Desktop segment requests multiple connections")
275        if connInfo[0]['type'] != 'ssh':
276            raise service_error(service_error.req,
277                    "Desktop segment requires ssh connecton")
278        return True
279
280    def export_store_info(self, certfile, connInfo):
281        '''
282        Tell the other portal node where to reach this desktop.  The other side
283        uses this information to set up routing, though the ssh_port is unused
284        as the Desktop always initiates ssh connections.
285        '''
286        values = { 'peer': self.hostname, 'ssh_port': self.ssh_port }
287        for c in connInfo:
288            for p in c.get('parameter', []):
289                if p.get('type','') == 'input': continue
290                pname = p.get('name', '')
291                key = p.get('key', '')
292                surl = p.get('store', '')
293                if pname not in values:
294                    self.log('Unknown export parameter: %s'  % pname)
295                    continue
296                val = values[pname]
297                req = { 'name': key, 'value': val }
298                self.log.debug('Setting %s (%s) to %s on %s' % \
299                        (pname, key,  val, surl))
300                self.call_SetValue(surl, req, certfile)
301
302    def set_route(self, dest, script, gw=None, src=None):
303        if sys.platform.startswith('freebsd'):
304            if src is not None and gw is not None:
305                raise service_error(service_error.internal, 
306                        'FreeBSD will not route based on src address')
307            elif src is not None:
308                raise service_error(service_error.internal, 
309                        'FreeBSD will not route based on src address')
310            elif gw is not None:
311                print >>script, 'route add %s %s' % (dest, gw)
312        elif sys.platform.startswith('linux'):
313            if src is not None and gw is not None:
314                print >>script, 'ip route add %s via %s src %s' % \
315                        (dest, gw, src)
316            elif src is not None:
317                print >>script, 'ip route add %s src %s' % \
318                        (dest, src)
319            elif gw is not None:
320                print >>script, 'ip route add %s via %s' % (dest, gw)
321        else:
322            raise service_error(service_error.internal, 
323                    'Unknown platform %s' % sys.platform)
324
325    def unset_route(self, dest, script):
326        rv = 0
327        if sys.platform.startswith('freebsd'):
328            print >>script, 'route delete %s' % dest
329        elif sys.platform.startswith('linux'):
330            print >>script, 'ip route delete %s' % dest
331
332    def find_a_peer(self, addr): 
333        '''
334        Find another node in the experiment that's on our subnet.  This is a
335        hack to handle the problem that we really cannot require the desktop to
336        dynamically route.  Will be improved by distributing static routes.
337        '''
338
339        peer = None
340        hosts = os.path.join(self.localdir, 'hosts')
341        p = addr.rfind('.')
342        if p == -1:
343            raise service_error(service_error.req, 'bad address in topology')
344        prefix = addr[0:p]
345        addr_re = re.compile('(%s.\\d+)' % prefix)
346        try:
347            f = open(hosts, 'r')
348            for line in f:
349                m = addr_re.search(line)
350                if m is not None and m.group(1) != addr:
351                    peer = m.group(1)
352                    break
353            else:
354                raise service_error(service_error.req, 
355                        'No other nodes in this subnet??')
356        except EnvironmentError, e:
357            raise service_error(service_error.internal, 
358                    'Cannot open %s: %s' % (e.filename, e.strerror))
359        return peer
360
361
362
363
364    def configure_desktop(self, top, connInfo):
365        '''
366        Build the connection.  Establish routing to the peer if using a
367        separate interface, wait until the other end confirms setup, establish
368        the ssh layer-two tunnel (tap), assign the in-experiment IP address to
369        the tunnel and establish routing to the experiment through the tap.
370        '''
371
372
373        # get the peer and ssh port from the portal and our IP from the other
374        peer = None
375        port = None
376        my_addr = None
377        my_name = None
378        for e in top.elements:
379            if not isinstance(e, topdl.Computer): continue
380            if e.get_attribute('portal') is None: 
381                my_name = e.name
382                # there should be one interface with one IPv4 address
383                if len(e.interface) <1 :
384                    raise service_error(service_error.internal,
385                            'No interface on experiment node!?!?')
386                my_addr = e.interface[0].get_attribute('ip4_address')
387            else:
388                for ci in connInfo:
389                    if ci.get('portal', '') != e.name: continue
390                    peer = ci.get('peer')
391                    port = '22'
392                    for a in ci.get('fedAttr', []):
393                        if a['attribute'] == 'ssh_port': port = a['value']
394
395        # XXX scan hosts for IP addresses and compose better routing entry
396       
397        if not all([peer, port, my_addr]):
398            raise service_error(service_error.req, 
399                    'Cannot find all config parameters %s %s %s' % (peer, port, my_addr))
400
401        exp_peer = self.find_a_peer(my_addr)
402
403        cscript = os.path.join(self.localdir, 'connect')
404        dscript = os.path.join(self.localdir, 'disconnect')
405        local_hosts = os.path.join(self.localdir, 'hosts')
406        zebra_conf = os.path.join(self.localdir, 'zebra.conf')
407        ospfd_conf = os.path.join(self.localdir, 'ospfd.conf')
408        try:
409            f = open(cscript, 'w')
410            print >>f, '#!/bin/sh'
411            # This picks the outgoing interface to the experiment using the
412            # routing system.
413            self.set_route(peer, f, self.router, self.src_addr)
414            # Wait until the other end reports that it is configured py placing
415            # a file this end can access into its local file system.  Try once
416            # a minute.
417            print >>f,'while ! /usr/bin/scp -P %s -o "StrictHostKeyChecking no" -i %s %s:/usr/local/federation/etc/prep_done /dev/null; do' % (port, self.ssh_identity, peer)
418            print >>f, 'sleep 60; done'
419            print >>f, ('ssh -w 0:0 -p %s -o "Tunnel ethernet" ' + \
420                    '-o "StrictHostKeyChecking no" -i %s %s perl ' +
421                    '-I/usr/local/federation/lib ' +
422                    '/usr/local/federation/bin/setup_bridge.pl --tapno=0 ' +
423                    '--addr=%s --use_file &') % \
424                    (port, self.ssh_identity, peer, my_addr)
425            # This should give the tap a a chance to come up
426            print >>f,'sleep 10'
427            # Add experiment nodes to hosts
428            print >>f, 'cp /etc/hosts /etc/hosts.DETER.fedd.hold'
429            print >>f, 'echo "#--- BEGIN FEDD ADDITIONS ---" >> /etc/hosts'
430            print >>f, 'cat %s >> /etc/hosts' % local_hosts
431            print >>f, 'echo "#--- END FEDD ADDITIONS ---" >> /etc/hosts'
432            # Assign tap address and route experiment connections through it.
433            print >>f, 'ifconfig tap0 %s netmask 255.255.255.0 up' % \
434                    my_addr
435            print >>f, '%s -d -f %s' % (self.zebra, zebra_conf)
436            print >>f, '%s -d -f %s' % (self.ospfd, ospfd_conf)
437            if self.iptables is not None and self.nat_interface is not None:
438                print >>f, '%s -t nat -A POSTROUTING -o %s -j MASQUERADE' %\
439                        (self.iptables, self.nat_interface)
440                print >>f, ('%s -A FORWARD -i %s -o tap0 -m state ' +
441                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
442                            (self.iptables, self.nat_interface)
443                print >>f, '%s -A FORWARD -i tap0 -o %s -j ACCEPT' % \
444                        (self.iptables, self.nat_interface)
445            f.close()
446            os.chmod(cscript, 0755)
447            f = open(dscript, 'w')
448            print >>f, '#!/bin/sh'
449            if self.iptables is not None and self.nat_interface is not None:
450                print >>f, '%s -t nat -D POSTROUTING -o %s -j MASQUERADE' %\
451                        (self.iptables, self.nat_interface)
452                print >>f, ('%s -D FORWARD -i %s -o tap0 -m state ' +
453                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
454                            (self.iptables, self.nat_interface)
455                print >>f, '%s -D FORWARD -i tap0 -o %s -j ACCEPT' % \
456                        (self.iptables, self.nat_interface)
457            print >>f, 'pkill -f setup_bridge.pl'
458            print >>f, 'mv /etc/hosts.DETER.fedd.hold /etc/hosts'
459            print >>f, ('ssh -p %s -o "StrictHostKeyChecking no" -i %s %s ' +
460                'perl -I/usr/local/federation/lib ' +
461                '/usr/local/federation/bin/unsetup_bridge.pl --tapno=0 ' +
462                '--addr=%s') % \
463                    (port, self.ssh_identity, peer, my_addr)
464            print >>f, 'kill `cat /var/run/quagga/ospfd.pid`'
465            print >>f, 'kill `cat /var/run/quagga/zebra.pid`'
466            if self.iptables is not None and self.nat_interface is not None:
467                print >>f, '%s -t nat -D POSTROUTING -o %s -j MASQUERADE' %\
468                        (self.iptables, self.nat_interface)
469                print >>f, ('%s -D FORWARD -i %s -o tap0 -m state ' +
470                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
471                            (self.iptables, self.nat_interface)
472                print >>f, '%s -D FORWARD -i tap0 -o %s -j ACCEPT' % \
473                        (self.iptables, self.nat_interface)
474            f.close()
475            os.chmod(dscript, 0755)
476            f = open(zebra_conf, 'w')
477            print >>f, 'hostname %s' % my_name
478            print >>f, 'interface tap0'
479            for i in self.export_interfaces:
480                print >>f, 'interface %s' % i
481            if  self.external_networks is not None:
482                try:
483                    extern = open(self.external_networks, 'r')
484                    if extern is not None:
485                        for l in extern:
486                            print >>f, "%s" % l.strip()
487                        extern.close()
488                except EnvironmentError:
489                    # No external_networks or problem reading it, ignore
490                    pass
491            f.close()
492            os.chmod(zebra_conf, 0644)
493            f = open(ospfd_conf, 'w')
494            print >>f, 'hostname %s' % my_name
495            print >>f, 'router ospf'
496            print >>f, ' redistribute static'
497            print >>f, ' network %s/24 area 0.0.0.2' % my_addr
498            for i in self.export_networks:
499                print >>f, ' network %s area 0.0.0.2' % i
500        except EnvironmentError, e:
501            raise service_error(service_error.internal, 
502                    'Cannot create connect %s: %s' % (e.filename, e.strerror))
503        script_log = open('/tmp/connect.log', 'w')
504        subprocess.Popen(['sudo', '/bin/sh', cscript], stdout=script_log,
505                stderr=script_log)
506        return True
507
508    def StartSegment(self, req, fid):
509        """
510        Start a segment.  In this simple skeleton, this means to parse the
511        request and assign an unassigned integer to it.  We store the integer
512        in the persistent state.
513        """
514        try:
515            req = req['StartSegmentRequestBody']
516            # Get the request topology.  If not present, a KeyError is thrown.
517            topref = req['segmentdescription']['topdldescription']
518            # The fedid of the allocation we're attaching resources to
519            auth_attr = req['allocID']['fedid']
520        except KeyError:
521            raise service_error(service_error.req, "Badly formed request")
522
523        # String version of the allocation ID for keying
524        aid = "%s" % auth_attr
525        # Authorization check
526        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
527                with_proof=True)
528        if not access_ok:
529            raise service_error(service_error.access, "Access denied", 
530                    proof=proof)
531        else:
532            # See if this is a replay of an earlier succeeded StartSegment -
533            # sometimes SSL kills 'em.  If so, replay the response rather than
534            # redoing the allocation.
535            self.state_lock.acquire()
536            # Test and set :-)
537            running = self.state['running']
538            self.state['running'] = True
539            retval = self.state[aid].get('started', None)
540            self.state_lock.release()
541            if retval:
542                self.log.warning(
543                        "[StartSegment] Duplicate StartSegment for %s: " \
544                                % aid + \
545                        "replaying response")
546                return retval
547            if running:
548                self.log.debug('[StartSegment] already running')
549                raise service_error(service_error.federant,
550                        'Desktop is already in an experiment')
551
552        certfile = "%s/%s.pem" % (self.certdir, aid)
553
554        # Convert the topology into topdl data structures.  Again, the
555        # skeletion doesn't do anything with it, but this is how one parses a
556        # topology request.
557        if topref: topo = topdl.Topology(**topref)
558        else:
559            raise service_error(service_error.req, 
560                    "Request missing segmentdescription'")
561
562        err = None
563        try:
564            self.validate_topology(topo)
565
566            # The attributes of the request.  The ones we care about are the ssh
567            # keys to operate the tunnel.
568            attrs = req.get('fedAttr', [])
569            for a in attrs:
570                # Save the hosts and ssh_privkeys to our local dir
571                if a['attribute'] in ('hosts', 'ssh_secretkey'):
572                    self.log.debug('Getting %s from %s' % \
573                            (a['attribute'], a['value']))
574                    get_url(a['value'], certfile, self.localdir, log=self.log)
575                    base = os.path.basename(a['value'])
576                    if a['attribute'] == 'ssh_secretkey':
577                        self.ssh_identity = os.path.join(self.localdir, base)
578                    os.chmod(os.path.join(self.localdir, base), 0600)
579                else:
580                    self.log.debug('Ignoring attribute %s' % a['attribute'])
581
582            # Gather connection information and exchange parameters.
583            connInfo = req.get('connection', [])
584            self.validate_connInfo(connInfo)
585            self.export_store_info(certfile, connInfo)
586            self.import_store_info(certfile, connInfo)
587
588            #build it
589            self.configure_desktop(topo, connInfo)
590        except service_error, e:
591            err = e
592
593        # Save the information
594        if err is None:
595            # It's possible that the StartSegment call gets retried (!).  if
596            # the 'started' key is in the allocation, we'll return it rather
597            # than redo the setup.  The integer allocation was saved when we
598            # made it.
599            self.state_lock.acquire()
600            self.state[aid]['started'] = { 
601                    'allocID': req['allocID'],
602                    'allocationLog': "Allocatation complete",
603                    'segmentdescription': { 'topdldescription': topo.to_dict() },
604                    'proof': proof.to_dict(),
605                    }
606            retval = copy.deepcopy(self.state[aid]['started'])
607            self.write_state()
608            self.state_lock.release()
609        else:
610            # Something bad happened - clear the "running" flag so we can try
611            # again
612            self.state_lock.acquire()
613            self.state['running'] = False
614            self.state_lock.release()
615            raise err
616
617        return retval
618
619    def TerminateSegment(self, req, fid):
620        """
621        Remove the resources associated with th eallocation and stop the music.
622        In this example, this simply means removing the integer we allocated.
623        """
624        # Gather the same access information as for Start Segment
625        try:
626            req = req['TerminateSegmentRequestBody']
627        except KeyError:
628            raise service_error(service_error.req, "Badly formed request")
629
630        auth_attr = req['allocID']['fedid']
631        aid = "%s" % auth_attr
632
633        self.log.debug("Terminate request for %s" %aid)
634        # Check authorization
635        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
636                with_proof=True)
637        if not access_ok:
638            raise service_error(service_error.access, "Access denied", 
639                    proof=proof)
640        cscript = os.path.join(self.localdir, 'connect')
641        dscript = os.path.join(self.localdir, 'disconnect')
642        # Do the work of disconnecting
643        if os.path.exists(dscript):
644            self.log.debug('calling %s' % dscript)
645            rv = subprocess.call(['sudo', '/bin/sh', dscript])
646            if rv != 0:
647                self.log.warning('%s had an error: %d' % (dscript, rv))
648        else:
649            self.log.warn('No disconnection script!?')
650
651        try:
652            for bfn in os.listdir(self.localdir):
653                fn = os.path.join(self.localdir, bfn)
654                self.log.debug('Removing %s' % fn)
655                if os.path.exists(fn):
656                    os.remove(fn)
657        except EnvironmentError, e:
658            self.log.warn('Failed to remove %s: %s' % (e.filename, e.strerror))
659
660        self.ssh_identity = None
661
662        self.state_lock.acquire()
663        self.state['running'] = False
664        self.state_lock.release()
665   
666        return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Note: See TracBrowser for help on using the repository browser.