source: fedd/federation/desktop_access.py @ 968b84b

Last change on this file since 968b84b was ba07149, checked in by Ted Faber <faber@…>, 11 years ago

Make a couple error messages clearer

  • Property mode set to 100644
File size: 24.0 KB
RevLine 
[1819839]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')
[ae714e4]68        # File containing the routing entries for external networks
69        self.external_networks = config.get('access', 'external_networks')
[ea0e8cb]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
[f1f9aec]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
[0608d96]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
[1819839]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
[2dc99e3]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
[1819839]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,
[ba07149]255                        "Topology: Desktop Node has more than one interface")
[1819839]256            i  = c.interface[0]
257            if len(i.subs) > 1: 
258                raise service_error(service_error.req,
[ba07149]259                        "Topology: Desktop Node has more than " +\
260                        "one substate on interface")
[1819839]261            sub = i.subs[0]
262            for i in sub.interfaces:
263                if i.element not in comps:
264                    raise service_error(service_error.req,
[ba07149]265                            "Topology: Desktop Node connected to non-portal")
[1819839]266
267        if portals > 1:
268            raise service_error(service_error.req,
269                    "Desktop segment has more than one portal")
270        return True
271
272    def validate_connInfo(self, connInfo):
273        if len(connInfo) != 1: 
274            raise service_error(service_error.req,
275                    "Desktop segment requests multiple connections")
276        if connInfo[0]['type'] != 'ssh':
277            raise service_error(service_error.req,
278                    "Desktop segment requires ssh connecton")
279        return True
280
281    def export_store_info(self, certfile, connInfo):
282        '''
283        Tell the other portal node where to reach this desktop.  The other side
284        uses this information to set up routing, though the ssh_port is unused
285        as the Desktop always initiates ssh connections.
286        '''
287        values = { 'peer': self.hostname, 'ssh_port': self.ssh_port }
288        for c in connInfo:
289            for p in c.get('parameter', []):
290                if p.get('type','') == 'input': continue
291                pname = p.get('name', '')
292                key = p.get('key', '')
293                surl = p.get('store', '')
294                if pname not in values:
295                    self.log('Unknown export parameter: %s'  % pname)
296                    continue
297                val = values[pname]
298                req = { 'name': key, 'value': val }
299                self.log.debug('Setting %s (%s) to %s on %s' % \
300                        (pname, key,  val, surl))
301                self.call_SetValue(surl, req, certfile)
302
303    def set_route(self, dest, script, gw=None, src=None):
304        if sys.platform.startswith('freebsd'):
305            if src is not None and gw is not None:
306                raise service_error(service_error.internal, 
307                        'FreeBSD will not route based on src address')
308            elif src is not None:
309                raise service_error(service_error.internal, 
310                        'FreeBSD will not route based on src address')
311            elif gw is not None:
[5dbcc93]312                print >>script, 'route add %s %s' % (dest, gw)
[1819839]313        elif sys.platform.startswith('linux'):
314            if src is not None and gw is not None:
[5dbcc93]315                print >>script, 'ip route add %s via %s src %s' % \
[1819839]316                        (dest, gw, src)
317            elif src is not None:
[5dbcc93]318                print >>script, 'ip route add %s src %s' % \
[1819839]319                        (dest, src)
320            elif gw is not None:
[5dbcc93]321                print >>script, 'ip route add %s via %s' % (dest, gw)
[1819839]322        else:
323            raise service_error(service_error.internal, 
324                    'Unknown platform %s' % sys.platform)
325
326    def unset_route(self, dest, script):
327        rv = 0
328        if sys.platform.startswith('freebsd'):
[5dbcc93]329            print >>script, 'route delete %s' % dest
[1819839]330        elif sys.platform.startswith('linux'):
[5dbcc93]331            print >>script, 'ip route delete %s' % dest
[1819839]332
[2dc99e3]333    def find_a_peer(self, addr): 
334        '''
335        Find another node in the experiment that's on our subnet.  This is a
336        hack to handle the problem that we really cannot require the desktop to
337        dynamically route.  Will be improved by distributing static routes.
338        '''
339
340        peer = None
341        hosts = os.path.join(self.localdir, 'hosts')
342        p = addr.rfind('.')
343        if p == -1:
344            raise service_error(service_error.req, 'bad address in topology')
345        prefix = addr[0:p]
346        addr_re = re.compile('(%s.\\d+)' % prefix)
347        try:
348            f = open(hosts, 'r')
349            for line in f:
350                m = addr_re.search(line)
351                if m is not None and m.group(1) != addr:
352                    peer = m.group(1)
353                    break
354            else:
355                raise service_error(service_error.req, 
356                        'No other nodes in this subnet??')
357        except EnvironmentError, e:
358            raise service_error(service_error.internal, 
359                    'Cannot open %s: %s' % (e.filename, e.strerror))
360        return peer
361
362
[1819839]363
364
365    def configure_desktop(self, top, connInfo):
366        '''
367        Build the connection.  Establish routing to the peer if using a
368        separate interface, wait until the other end confirms setup, establish
369        the ssh layer-two tunnel (tap), assign the in-experiment IP address to
370        the tunnel and establish routing to the experiment through the tap.
371        '''
372
373
374        # get the peer and ssh port from the portal and our IP from the other
375        peer = None
376        port = None
377        my_addr = None
[b06744b]378        my_name = None
[1819839]379        for e in top.elements:
380            if not isinstance(e, topdl.Computer): continue
381            if e.get_attribute('portal') is None: 
[b06744b]382                my_name = e.name
[1819839]383                # there should be one interface with one IPv4 address
384                if len(e.interface) <1 :
385                    raise service_error(service_error.internal,
386                            'No interface on experiment node!?!?')
387                my_addr = e.interface[0].get_attribute('ip4_address')
388            else:
389                for ci in connInfo:
390                    if ci.get('portal', '') != e.name: continue
391                    peer = ci.get('peer')
392                    port = '22'
393                    for a in ci.get('fedAttr', []):
394                        if a['attribute'] == 'ssh_port': port = a['value']
395
396        # XXX scan hosts for IP addresses and compose better routing entry
397       
398        if not all([peer, port, my_addr]):
399            raise service_error(service_error.req, 
400                    'Cannot find all config parameters %s %s %s' % (peer, port, my_addr))
401
[2dc99e3]402        exp_peer = self.find_a_peer(my_addr)
403
[1819839]404        cscript = os.path.join(self.localdir, 'connect')
405        dscript = os.path.join(self.localdir, 'disconnect')
[5dbcc93]406        local_hosts = os.path.join(self.localdir, 'hosts')
[b06744b]407        zebra_conf = os.path.join(self.localdir, 'zebra.conf')
408        ospfd_conf = os.path.join(self.localdir, 'ospfd.conf')
[1819839]409        try:
410            f = open(cscript, 'w')
411            print >>f, '#!/bin/sh'
412            # This picks the outgoing interface to the experiment using the
413            # routing system.
414            self.set_route(peer, f, self.router, self.src_addr)
415            # Wait until the other end reports that it is configured py placing
416            # a file this end can access into its local file system.  Try once
417            # a minute.
[7862660]418            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)
[1819839]419            print >>f, 'sleep 60; done'
[5dbcc93]420            print >>f, ('ssh -w 0:0 -p %s -o "Tunnel ethernet" ' + \
[4cf0198]421                    '-o "StrictHostKeyChecking no" -i %s %s perl ' +
422                    '-I/usr/local/federation/lib ' +
423                    '/usr/local/federation/bin/setup_bridge.pl --tapno=0 ' +
424                    '--addr=%s --use_file &') % \
[2dc99e3]425                    (port, self.ssh_identity, peer, my_addr)
[1819839]426            # This should give the tap a a chance to come up
427            print >>f,'sleep 10'
[5dbcc93]428            # Add experiment nodes to hosts
429            print >>f, 'cp /etc/hosts /etc/hosts.DETER.fedd.hold'
430            print >>f, 'echo "#--- BEGIN FEDD ADDITIONS ---" >> /etc/hosts'
431            print >>f, 'cat %s >> /etc/hosts' % local_hosts
432            print >>f, 'echo "#--- END FEDD ADDITIONS ---" >> /etc/hosts'
433            # Assign tap address and route experiment connections through it.
434            print >>f, 'ifconfig tap0 %s netmask 255.255.255.0 up' % \
[1819839]435                    my_addr
[f1f9aec]436            print >>f, '%s -d -f %s' % (self.zebra, zebra_conf)
437            print >>f, '%s -d -f %s' % (self.ospfd, ospfd_conf)
[0608d96]438            if self.iptables is not None and self.nat_interface is not None:
439                print >>f, '%s -t nat -A POSTROUTING -o %s -j MASQUERADE' %\
440                        (self.iptables, self.nat_interface)
441                print >>f, ('%s -A FORWARD -i %s -o tap0 -m state ' +
442                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
443                            (self.iptables, self.nat_interface)
444                print >>f, '%s -A FORWARD -i tap0 -o %s -j ACCEPT' % \
445                        (self.iptables, self.nat_interface)
[1819839]446            f.close()
447            os.chmod(cscript, 0755)
448            f = open(dscript, 'w')
449            print >>f, '#!/bin/sh'
[0608d96]450            if self.iptables is not None and self.nat_interface is not None:
451                print >>f, '%s -t nat -D POSTROUTING -o %s -j MASQUERADE' %\
452                        (self.iptables, self.nat_interface)
453                print >>f, ('%s -D FORWARD -i %s -o tap0 -m state ' +
454                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
455                            (self.iptables, self.nat_interface)
456                print >>f, '%s -D FORWARD -i tap0 -o %s -j ACCEPT' % \
457                        (self.iptables, self.nat_interface)
[4cf0198]458            print >>f, 'pkill -f setup_bridge.pl'
[5dbcc93]459            print >>f, 'mv /etc/hosts.DETER.fedd.hold /etc/hosts'
[4cf0198]460            print >>f, ('ssh -p %s -o "StrictHostKeyChecking no" -i %s %s ' +
461                'perl -I/usr/local/federation/lib ' +
462                '/usr/local/federation/bin/unsetup_bridge.pl --tapno=0 ' +
463                '--addr=%s') % \
464                    (port, self.ssh_identity, peer, my_addr)
[b3125fa1]465            print >>f, 'kill `cat /var/run/quagga/ospfd.pid`'
466            print >>f, 'kill `cat /var/run/quagga/zebra.pid`'
[0608d96]467            if self.iptables is not None and self.nat_interface is not None:
468                print >>f, '%s -t nat -D POSTROUTING -o %s -j MASQUERADE' %\
469                        (self.iptables, self.nat_interface)
470                print >>f, ('%s -D FORWARD -i %s -o tap0 -m state ' +
471                    '--state RELATED,ESTABLISHED -j ACCEPT') % \
472                            (self.iptables, self.nat_interface)
473                print >>f, '%s -D FORWARD -i tap0 -o %s -j ACCEPT' % \
474                        (self.iptables, self.nat_interface)
[1819839]475            f.close()
476            os.chmod(dscript, 0755)
[b06744b]477            f = open(zebra_conf, 'w')
478            print >>f, 'hostname %s' % my_name
479            print >>f, 'interface tap0'
[ea0e8cb]480            for i in self.export_interfaces:
481                print >>f, 'interface %s' % i
[972993c]482            if  self.external_networks is not None:
483                try:
484                    extern = open(self.external_networks, 'r')
485                    if extern is not None:
486                        for l in extern:
487                            print >>f, "%s" % l.strip()
488                        extern.close()
489                except EnvironmentError:
490                    # No external_networks or problem reading it, ignore
491                    pass
[b06744b]492            f.close()
493            os.chmod(zebra_conf, 0644)
494            f = open(ospfd_conf, 'w')
495            print >>f, 'hostname %s' % my_name
496            print >>f, 'router ospf'
497            print >>f, ' redistribute static'
498            print >>f, ' network %s/24 area 0.0.0.2' % my_addr
[ea0e8cb]499            for i in self.export_networks:
500                print >>f, ' network %s area 0.0.0.2' % i
[1819839]501        except EnvironmentError, e:
502            raise service_error(service_error.internal, 
503                    'Cannot create connect %s: %s' % (e.filename, e.strerror))
[5dbcc93]504        script_log = open('/tmp/connect.log', 'w')
[ea0e8cb]505        subprocess.Popen(['sudo', '/bin/sh', cscript], stdout=script_log,
506                stderr=script_log)
[1819839]507        return True
508
509    def StartSegment(self, req, fid):
510        """
511        Start a segment.  In this simple skeleton, this means to parse the
512        request and assign an unassigned integer to it.  We store the integer
513        in the persistent state.
514        """
515        try:
516            req = req['StartSegmentRequestBody']
517            # Get the request topology.  If not present, a KeyError is thrown.
518            topref = req['segmentdescription']['topdldescription']
519            # The fedid of the allocation we're attaching resources to
520            auth_attr = req['allocID']['fedid']
521        except KeyError:
522            raise service_error(service_error.req, "Badly formed request")
523
524        # String version of the allocation ID for keying
525        aid = "%s" % auth_attr
526        # Authorization check
527        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
528                with_proof=True)
529        if not access_ok:
530            raise service_error(service_error.access, "Access denied", 
531                    proof=proof)
532        else:
533            # See if this is a replay of an earlier succeeded StartSegment -
534            # sometimes SSL kills 'em.  If so, replay the response rather than
535            # redoing the allocation.
536            self.state_lock.acquire()
537            # Test and set :-)
538            running = self.state['running']
539            self.state['running'] = True
540            retval = self.state[aid].get('started', None)
541            self.state_lock.release()
542            if retval:
543                self.log.warning(
544                        "[StartSegment] Duplicate StartSegment for %s: " \
545                                % aid + \
546                        "replaying response")
547                return retval
548            if running:
549                self.log.debug('[StartSegment] already running')
550                raise service_error(service_error.federant,
551                        'Desktop is already in an experiment')
552
553        certfile = "%s/%s.pem" % (self.certdir, aid)
554
555        # Convert the topology into topdl data structures.  Again, the
556        # skeletion doesn't do anything with it, but this is how one parses a
557        # topology request.
558        if topref: topo = topdl.Topology(**topref)
559        else:
560            raise service_error(service_error.req, 
561                    "Request missing segmentdescription'")
562
563        err = None
564        try:
565            self.validate_topology(topo)
566
567            # The attributes of the request.  The ones we care about are the ssh
568            # keys to operate the tunnel.
569            attrs = req.get('fedAttr', [])
570            for a in attrs:
571                # Save the hosts and ssh_privkeys to our local dir
572                if a['attribute'] in ('hosts', 'ssh_secretkey'):
573                    self.log.debug('Getting %s from %s' % \
574                            (a['attribute'], a['value']))
575                    get_url(a['value'], certfile, self.localdir, log=self.log)
576                    base = os.path.basename(a['value'])
577                    if a['attribute'] == 'ssh_secretkey':
578                        self.ssh_identity = os.path.join(self.localdir, base)
579                    os.chmod(os.path.join(self.localdir, base), 0600)
580                else:
581                    self.log.debug('Ignoring attribute %s' % a['attribute'])
582
583            # Gather connection information and exchange parameters.
584            connInfo = req.get('connection', [])
585            self.validate_connInfo(connInfo)
586            self.export_store_info(certfile, connInfo)
587            self.import_store_info(certfile, connInfo)
588
589            #build it
590            self.configure_desktop(topo, connInfo)
591        except service_error, e:
592            err = e
593
594        # Save the information
595        if err is None:
596            # It's possible that the StartSegment call gets retried (!).  if
597            # the 'started' key is in the allocation, we'll return it rather
598            # than redo the setup.  The integer allocation was saved when we
599            # made it.
600            self.state_lock.acquire()
601            self.state[aid]['started'] = { 
602                    'allocID': req['allocID'],
603                    'allocationLog': "Allocatation complete",
604                    'segmentdescription': { 'topdldescription': topo.to_dict() },
605                    'proof': proof.to_dict(),
606                    }
607            retval = copy.deepcopy(self.state[aid]['started'])
608            self.write_state()
609            self.state_lock.release()
610        else:
611            # Something bad happened - clear the "running" flag so we can try
612            # again
613            self.state_lock.acquire()
614            self.state['running'] = False
615            self.state_lock.release()
616            raise err
617
618        return retval
619
620    def TerminateSegment(self, req, fid):
621        """
622        Remove the resources associated with th eallocation and stop the music.
623        In this example, this simply means removing the integer we allocated.
624        """
625        # Gather the same access information as for Start Segment
626        try:
627            req = req['TerminateSegmentRequestBody']
628        except KeyError:
629            raise service_error(service_error.req, "Badly formed request")
630
631        auth_attr = req['allocID']['fedid']
632        aid = "%s" % auth_attr
633
634        self.log.debug("Terminate request for %s" %aid)
635        # Check authorization
636        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
637                with_proof=True)
638        if not access_ok:
639            raise service_error(service_error.access, "Access denied", 
640                    proof=proof)
641        cscript = os.path.join(self.localdir, 'connect')
642        dscript = os.path.join(self.localdir, 'disconnect')
[5dbcc93]643        # Do the work of disconnecting
644        if os.path.exists(dscript):
645            self.log.debug('calling %s' % dscript)
646            rv = subprocess.call(['sudo', '/bin/sh', dscript])
647            if rv != 0:
648                self.log.warning('%s had an error: %d' % (dscript, rv))
649        else:
650            self.log.warn('No disconnection script!?')
[1819839]651
652        try:
653            for bfn in os.listdir(self.localdir):
654                fn = os.path.join(self.localdir, bfn)
655                self.log.debug('Removing %s' % fn)
656                if os.path.exists(fn):
657                    os.remove(fn)
658        except EnvironmentError, e:
659            self.log.warn('Failed to remove %s: %s' % (e.filename, e.strerror))
660
661        self.ssh_identity = None
662
663        self.state_lock.acquire()
664        self.state['running'] = False
665        self.state_lock.release()
666   
667        return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Note: See TracBrowser for help on using the repository browser.