source: fedd/federation/protogeni_access.py @ 42cd8a7

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

Remove the proxy split from protogeni. It gets in the way of code inheritance
and generally makes a mess.

  • Property mode set to 100644
File size: 44.7 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import stat # for chmod constants
5import re
6import time
7import string
8import copy
9import pickle
10import logging
11import subprocess
12import random
13import traceback
14
15from threading import Thread, Timer, Lock
16from M2Crypto.SSL import SSLError
17
18from util import *
19from access_project import access_project
20from fedid import fedid, generate_fedid
21from authorizer import authorizer
22from service_error import service_error
23from remote_service import xmlrpc_handler, soap_handler, service_caller
24
25import httplib
26import tempfile
27from urlparse import urlparse
28
29from access import access_base
30from proxy_segment import proxy_segment
31
32import topdl
33import list_log
34
35
36# Make log messages disappear if noone configures a fedd logger
37class nullHandler(logging.Handler):
38    def emit(self, record): pass
39
40fl = logging.getLogger("fedd.access")
41fl.addHandler(nullHandler())
42
43class protogeni_proxy(proxy_segment):
44    class ProtoGENIError(Exception): 
45        def __init__(self, op, code, output):
46            Exception.__init__(self, output)
47            self.op = op
48            self.code = code
49            self.output = output
50
51    def __init__(self, log=None, keyfile=None, debug=False, 
52            ch_url=None, sa_url=None, cm_url=None):
53        proxy_segment.__init__(self, log=log, keyfile=keyfile, debug=debug)
54
55        self.ProtoGENIError = protogeni_proxy.ProtoGENIError
56        self.ch_url = ch_url
57        self.sa_url = sa_url
58        self.cm_url = cm_url
59
60        self.call_SetValue = service_caller('SetValue')
61
62        self.debug_fail = ['Resolve']
63        self.debug_response = {
64                'RedeemTicket': ("XML blob1", "XML blob2"),
65                'SliceStatus': { 'status': 'ready' },
66            }
67
68
69    def pg_call(self, url, method, params, context):
70        max_retries = 5
71        retries = 0
72
73        s = service_caller(method, request_body_name="", strict=False)
74        self.log.debug("Calling %s %s" % (url, method))
75        if not self.debug:
76            while retries < max_retries:
77                r = s.call_xmlrpc_service(url, params, context=context)
78                code = r.get('code', -1)
79                if code == 0:
80                    # Success leaves the loop here
81                    return r.get('value', None)
82                elif code == 14 and retries +1 < max_retries:
83                    # Busy resource
84                    retries+= 1
85                    self.log.info("Resource busy, retrying in 30 secs")
86                    time.sleep(30)
87                else:
88                    # NB: out of retries falls through to here
89                    raise self.ProtoGENIError(op=method, 
90                            code=r.get('code', 'unknown'), 
91                            output=r.get('output', 'No output'))
92        else:
93            if method in self.debug_fail:
94                raise self.ProtoGENIError(op=method, code='unknown',
95                        output='No output')
96            elif self.debug_response.has_key(method):
97                return self.debug_response[method]
98            else:
99                return "%s XML blob" % method
100
101
102
103class access(access_base):
104    """
105    The implementation of access control based on mapping users to projects.
106
107    Users can be mapped to existing projects or have projects created
108    dynamically.  This implements both direct requests and proxies.
109    """
110
111    def __init__(self, config=None, auth=None):
112        """
113        Initializer.  Pulls parameters out of the ConfigParser's access section.
114        """
115
116        access_base.__init__(self, config, auth)
117
118        self.domain = config.get("access", "domain")
119        self.userconfdir = config.get("access","userconfdir")
120        self.userconfcmd = config.get("access","userconfcmd")
121        self.userconfurl = config.get("access","userconfurl")
122        self.federation_software = config.get("access", "federation_software")
123        self.portal_software = config.get("access", "portal_software")
124        self.ssh_port = config.get("access","ssh_port") or "22"
125        self.sshd = config.get("access","sshd")
126        self.sshd_config = config.get("access", "sshd_config")
127        self.access_type = config.get("access", "type")
128        self.staging_dir = config.get("access", "staging_dir") or "/tmp"
129        self.staging_host = config.get("access", "staging_host") \
130                or "ops.emulab.net"
131        self.local_seer_software = config.get("access", "local_seer_software")
132        self.local_seer_image = config.get("access", "local_seer_image")
133        self.local_seer_start = config.get("access", "local_seer_start")
134   
135        self.dragon_endpoint = config.get("access", "dragon")
136        self.dragon_vlans = config.get("access", "dragon_vlans")
137        self.deter_internal = config.get("access", "deter_internal")
138
139        self.tunnel_config = config.getboolean("access", "tunnel_config")
140        self.portal_command = config.get("access", "portal_command")
141        self.portal_image = config.get("access", "portal_image")
142        self.portal_type = config.get("access", "portal_type") or "pc"
143        self.portal_startcommand = config.get("access", "portal_startcommand")
144        self.node_startcommand = config.get("access", "node_startcommand")
145
146        self.federation_software = self.software_list(self.federation_software)
147        self.portal_software = self.software_list(self.portal_software)
148        self.local_seer_software = self.software_list(self.local_seer_software)
149
150        self.renewal_interval = config.get("access", "renewal") or (3 * 60 )
151        self.renewal_interval = int(self.renewal_interval) * 60
152
153        self.ch_url = config.get("access", "ch_url")
154        self.sa_url = config.get("access", "sa_url")
155        self.cm_url = config.get("access", "cm_url")
156
157        self.restricted = [ ]
158
159        # read_state in the base_class
160        self.state_lock.acquire()
161        for a  in ('allocation', 'projects', 'keys', 'types'):
162            if a not in self.state:
163                self.state[a] = { }
164        self.allocation = self.state['allocation']
165        self.projects = self.state['projects']
166        self.keys = self.state['keys']
167        self.types = self.state['types']
168        # Add the ownership attributes to the authorizer.  Note that the
169        # indices of the allocation dict are strings, but the attributes are
170        # fedids, so there is a conversion.
171        for k in self.state.get('allocation', {}).keys():
172            for o in self.state['allocation'][k].get('owners', []):
173                self.auth.set_attribute(o, fedid(hexstr=k))
174            self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
175
176        self.state_lock.release()
177
178
179        self.log = logging.getLogger("fedd.access")
180        set_log_level(config, "access", self.log)
181
182        self.access = { }
183        if config.has_option("access", "accessdb"):
184            self.read_access(config.get("access", "accessdb"), 
185                    access_obj=self.make_access_info)
186
187        self.lookup_access = self.lookup_access_base
188
189        self.call_SetValue = service_caller('SetValue')
190        self.call_GetValue = service_caller('GetValue')
191        self.exports = {
192                'local_seer_control': self.export_local_seer,
193                'seer_master': self.export_seer_master,
194                'hide_hosts': self.export_hide_hosts,
195                }
196
197        if not self.local_seer_image or not self.local_seer_software or \
198                not self.local_seer_start:
199            if 'local_seer_control' in self.exports:
200                del self.exports['local_seer_control']
201
202        if not self.local_seer_image or not self.local_seer_software or \
203                not self.seer_master_start:
204            if 'seer_master' in self.exports:
205                del self.exports['seer_master']
206
207        self.RenewSlices()
208
209        self.soap_services = {\
210            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
211            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
212            'StartSegment': soap_handler("StartSegment", self.StartSegment),
213            'TerminateSegment': soap_handler("TerminateSegment", 
214                self.TerminateSegment),
215            }
216        self.xmlrpc_services =  {\
217            'RequestAccess': xmlrpc_handler('RequestAccess',
218                self.RequestAccess),
219            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
220                self.ReleaseAccess),
221            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
222            'TerminateSegment': xmlrpc_handler('TerminateSegment',
223                self.TerminateSegment),
224            }
225
226    @staticmethod
227    def make_access_info(s):
228        """
229        Split a string of the form (id, id, id, id) ito its constituent tuples
230        and return them as a tuple.  Use to import access info from the
231        access_db.
232        """
233
234        ss = s.strip()
235        if ss.startswith('(') and ss.endswith(')'):
236            l = [ s.strip() for s  in ss[1:-1].split(",")]
237            if len(l) == 4:
238                return tuple(l)
239            else:
240                raise self.parse_error(
241                        "Exactly 4 elements in access info required")
242        else:
243            raise self.parse_error("Expecting parenthezied values")
244
245
246    def get_handler(self, path, fid):
247        self.log.info("Get handler %s %s" % (path, fid))
248        if self.auth.check_attribute(fid, path) and self.userconfdir:
249            return ("%s/%s" % (self.userconfdir, path), "application/binary")
250        else:
251            return (None, None)
252
253    def build_access_response(self, alloc_id, services):
254        """
255        Create the SOAP response.
256
257        Build the dictionary description of the response and use
258        fedd_utils.pack_soap to create the soap message.  ap is the allocate
259        project message returned from a remote project allocation (even if that
260        allocation was done locally).
261        """
262        # Because alloc_id is already a fedd_services_types.IDType_Holder,
263        # there's no need to repack it
264        msg = { 
265                'allocID': alloc_id,
266                'fedAttr': [
267                    { 'attribute': 'domain', 'value': self.domain } , 
268                ]
269            }
270        if self.dragon_endpoint:
271            msg['fedAttr'].append({'attribute': 'dragon',
272                'value': self.dragon_endpoint})
273        if self.deter_internal:
274            msg['fedAttr'].append({'attribute': 'deter_internal',
275                'value': self.deter_internal})
276        #XXX: ??
277        if self.dragon_vlans:
278            msg['fedAttr'].append({'attribute': 'vlans',
279                'value': self.dragon_vlans})
280
281        if services:
282            msg['service'] = services
283        return msg
284
285    def RequestAccess(self, req, fid):
286        """
287        Handle the access request.
288        """
289
290        # The dance to get into the request body
291        if req.has_key('RequestAccessRequestBody'):
292            req = req['RequestAccessRequestBody']
293        else:
294            raise service_error(service_error.req, "No request!?")
295
296        if req.has_key('destinationTestbed'):
297            dt = unpack_id(req['destinationTestbed'])
298
299        # Request for this fedd
300        found, match = self.lookup_access(req, fid)
301        services, svc_state = self.export_services(req.get('service',[]),
302                None, None)
303        # keep track of what's been added
304        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
305        aid = unicode(allocID)
306
307        self.state_lock.acquire()
308        self.allocation[aid] = { }
309        # The protoGENI certificate
310        self.allocation[aid]['credentials'] = found
311        # The list of owner FIDs
312        self.allocation[aid]['owners'] = [ fid ]
313        self.write_state()
314        self.state_lock.release()
315        self.auth.set_attribute(fid, allocID)
316        self.auth.set_attribute(allocID, allocID)
317
318        try:
319            f = open("%s/%s.pem" % (self.certdir, aid), "w")
320            print >>f, alloc_cert
321            f.close()
322        except EnvironmentError, e:
323            raise service_error(service_error.internal, 
324                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
325        return self.build_access_response({ 'fedid': allocID }, None)
326
327
328    def ReleaseAccess(self, req, fid):
329        # The dance to get into the request body
330        if req.has_key('ReleaseAccessRequestBody'):
331            req = req['ReleaseAccessRequestBody']
332        else:
333            raise service_error(service_error.req, "No request!?")
334
335        # Local request
336        try:
337            if req['allocID'].has_key('localname'):
338                auth_attr = aid = req['allocID']['localname']
339            elif req['allocID'].has_key('fedid'):
340                aid = unicode(req['allocID']['fedid'])
341                auth_attr = req['allocID']['fedid']
342            else:
343                raise service_error(service_error.req,
344                        "Only localnames and fedids are understood")
345        except KeyError:
346            raise service_error(service_error.req, "Badly formed request")
347
348        self.log.debug("[access] deallocation requested for %s", aid)
349        if not self.auth.check_attribute(fid, auth_attr):
350            self.log.debug("[access] deallocation denied for %s", aid)
351            raise service_error(service_error.access, "Access Denied")
352
353        self.state_lock.acquire()
354        if self.allocation.has_key(aid):
355            self.log.debug("Found allocation for %s" %aid)
356            del self.allocation[aid]
357            self.write_state()
358            self.state_lock.release()
359            # And remove the access cert
360            cf = "%s/%s.pem" % (self.certdir, aid)
361            self.log.debug("Removing %s" % cf)
362            os.remove(cf)
363            return { 'allocID': req['allocID'] } 
364        else:
365            self.state_lock.release()
366            raise service_error(service_error.req, "No such allocation")
367
368    # Turn the manifest into a dict were each virtual nodename (i.e. the topdl
369    # name) has an entry with the allocated machine in hostname and the
370    # interfaces in 'interfaces'.  I love having XML parser code lying around.
371    def manifest_to_dict(self, manifest, ignore_debug=False):
372        if self.create_debug and not ignore_debug: 
373            self.log.debug("Returning null manifest dict")
374            return { }
375
376        # The class allows us to keep a little state - the dict under
377        # consteruction and the current entry in that dict for the interface
378        # element code.
379        class manifest_parser:
380            def __init__(self):
381                self.d = { }
382                self.current_key=None
383
384            # If the element is a node, create a dict entry for it.  If it's an
385            # interface inside a node, add an entry in the interfaces list with
386            # the virtual name and component id.
387            def start_element(self, name, attrs):
388                if name == 'node':
389                    self.current_key = attrs.get('virtual_id',"")
390                    if self.current_key:
391                        self.d[self.current_key] = {
392                                'hostname': attrs.get('hostname', None),
393                                'interfaces': { }
394                                }
395                elif name == 'interface' and self.current_key:
396                    self.d[self.current_key]['interfaces']\
397                            [attrs.get('virtual_id','')] = \
398                            attrs.get('component_id', None)
399            #  When a node is finished, clear current_key
400            def end_element(self, name):
401                if name == 'node': self.current_key = None
402
403        node = { }
404
405        mp = manifest_parser()
406        p = xml.parsers.expat.ParserCreate()
407        # These are bound to the class we just created
408        p.StartElementHandler = mp.start_element
409        p.EndElementHandler = mp.end_element
410
411        p.Parse(manifest)
412        # Make the node dict that the callers expect
413        for k in mp.d:
414            node[k] = mp.d.get('hostname', '')
415        return mp.d
416
417    def fake_manifest(self, topo):
418        node = { }
419        for i, e in enumerate([ e for e in topo.elements \
420                if isinstance(e, topdl.Computer)]):
421            node[e.name] = { 
422                    'hostname': "node%03d" % i,
423                    'interfaces': { }
424                    }
425            for j, inf in enumerate(e.interface):
426                node[e.name]['interfaces'][inf.name] = 'eth%d' % j
427
428        return node
429
430
431    def generate_portal_configs(self, topo, pubkey_base, 
432            secretkey_base, tmpdir, leid, connInfo, services, nodes):
433
434        def conninfo_to_dict(key, info):
435            """
436            Make a cpoy of the connection information about key, and flatten it
437            into a single dict by parsing out any feddAttrs.
438            """
439
440            rv = None
441            for i in info:
442                if key == i.get('portal', "") or \
443                        key in [e.get('element', "") \
444                        for e in i.get('member', [])]:
445                    rv = i.copy()
446                    break
447
448            else:
449                return rv
450
451            if 'fedAttr' in rv:
452                for a in rv['fedAttr']:
453                    attr = a.get('attribute', "")
454                    val = a.get('value', "")
455                    if attr and attr not in rv:
456                        rv[attr] = val
457                del rv['fedAttr']
458            return rv
459
460        # XXX: un hardcode this
461        def client_null(f, s):
462            print >>f, "Service: %s" % s['name']
463       
464        def client_seer_master(f, s):
465            print >>f, 'PortalAlias: seer-master'
466
467        def client_smb(f, s):
468            print >>f, "Service: %s" % s['name']
469            smbshare = None
470            smbuser = None
471            smbproj = None
472            for a in s.get('fedAttr', []):
473                if a.get('attribute', '') == 'SMBSHARE':
474                    smbshare = a.get('value', None)
475                elif a.get('attribute', '') == 'SMBUSER':
476                    smbuser = a.get('value', None)
477                elif a.get('attribute', '') == 'SMBPROJ':
478                    smbproj = a.get('value', None)
479
480            if all((smbshare, smbuser, smbproj)):
481                print >>f, "SMBshare: %s" % smbshare
482                print >>f, "ProjectUser: %s" % smbuser
483                print >>f, "ProjectName: %s" % smbproj
484
485        def client_hide_hosts(f, s):
486            for a in s.get('fedAttr', [ ]):
487                if a.get('attribute', "") == 'hosts':
488                    print >>f, 'Hide: %s' % a.get('value', "")
489
490        client_service_out = {
491                'SMB': client_smb,
492                'tmcd': client_null,
493                'seer': client_null,
494                'userconfig': client_null,
495                'project_export': client_null,
496                'seer_master': client_seer_master,
497                'hide_hosts': client_hide_hosts,
498            }
499
500        def client_seer_master_export(f, s):
501            print >>f, "AddedNode: seer-master"
502
503        def client_seer_local_export(f, s):
504            print >>f, "AddedNode: control"
505
506        client_export_service_out = {
507                'seer_master': client_seer_master_export,
508                'local_seer_control': client_seer_local_export,
509            }
510
511        def server_port(f, s):
512            p = urlparse(s.get('server', 'http://localhost'))
513            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
514
515        def server_null(f,s): pass
516
517        def server_seer(f, s):
518            print >>f, 'seer: true'
519
520        server_service_out = {
521                'SMB': server_port,
522                'tmcd': server_port,
523                'userconfig': server_null,
524                'project_export': server_null,
525                'seer': server_seer,
526                'seer_master': server_port,
527                'hide_hosts': server_null,
528            }
529        # XXX: end un hardcode this
530
531
532        seer_out = False
533        client_out = False
534        for e in [ e for e in topo.elements \
535                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
536            myname = e.name
537            type = e.get_attribute('portal_type')
538
539            info = conninfo_to_dict(myname, connInfo)
540
541            if not info:
542                raise service_error(service_error.req,
543                        "No connectivity info for %s" % myname)
544
545            # Translate to physical name (ProtoGENI doesn't have DNS)
546            physname = nodes.get(myname, { }).get('hostname', None)
547            peer = info.get('peer', "")
548            ldomain = self.domain
549            ssh_port = info.get('ssh_port', 22)
550
551            # Collect this for the client.conf file
552            if 'masterexperiment' in info:
553                mproj, meid = info['masterexperiment'].split("/", 1)
554
555            active = info.get('active', 'False')
556
557            if type in ('control', 'both'):
558                testbed = e.get_attribute('testbed')
559                control_gw = myname
560
561            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
562            tunnelconfig = self.tunnel_config
563            try:
564                f = open(cfn, "w")
565                if active == 'True':
566                    print >>f, "active: True"
567                    print >>f, "ssh_port: %s" % ssh_port
568                    if type in ('control', 'both'):
569                        for s in [s for s in services \
570                                if s.get('name', "") in self.imports]:
571                            server_service_out[s['name']](f, s)
572
573                if tunnelconfig:
574                    print >>f, "tunnelip: %s" % tunnelconfig
575                print >>f, "peer: %s" % peer.lower()
576                print >>f, "ssh_pubkey: /usr/local/federation/etc/%s" % \
577                        pubkey_base
578                print >>f, "ssh_privkey: /usr/local/federation/etc/%s" % \
579                        secretkey_base
580                f.close()
581            except EnvironmentError, e:
582                raise service_error(service_error.internal,
583                        "Can't write protal config %s: %s" % (cfn, e))
584
585        # Done with portals, write the client config file.
586        try:
587            f = open("%s/client.conf" % tmpdir, "w")
588            if control_gw:
589                print >>f, "ControlGateway: %s" % physname.lower()
590            for s in services:
591                if s.get('name',"") in self.imports and \
592                        s.get('visibility','') == 'import':
593                    client_service_out[s['name']](f, s)
594                if s.get('name', '') in self.exports and \
595                        s.get('visibility', '') == 'export' and \
596                        s['name'] in client_export_service_out:
597                    client_export_service_out[s['name']](f, s)
598            # Seer uses this.
599            if mproj and meid:
600                print >>f, "ExperimentID: %s/%s" % (mproj, meid)
601            f.close()
602        except EnvironmentError, e:
603            raise service_error(service_error.internal,
604                    "Cannot write client.conf: %s" %s)
605
606
607
608    def export_store_info(self, cf, nodes, ssh_port, connInfo):
609        """
610        For the export requests in the connection info, install the peer names
611        at the experiment controller via SetValue calls.
612        """
613
614        for c in connInfo:
615            for p in [ p for p in c.get('parameter', []) \
616                    if p.get('type', '') == 'output']:
617
618                if p.get('name', '') == 'peer':
619                    k = p.get('key', None)
620                    surl = p.get('store', None)
621                    if surl and k and k.index('/') != -1:
622                        if self.create_debug:
623                            req = { 'name': k, 'value': 'debug' }
624                            self.call_SetValue(surl, req, cf)
625                        else:
626                            n = nodes.get(k[k.index('/')+1:], { })
627                            value = n.get('hostname', None)
628                            if value:
629                                req = { 'name': k, 'value': value }
630                                self.call_SetValue(surl, req, cf)
631                            else:
632                                self.log.error("No hostname for %s" % \
633                                        k[k.index('/'):])
634                    else:
635                        self.log.error("Bad export request: %s" % p)
636                elif p.get('name', '') == 'ssh_port':
637                    k = p.get('key', None)
638                    surl = p.get('store', None)
639                    if surl and k:
640                        req = { 'name': k, 'value': ssh_port }
641                        self.call_SetValue(surl, req, cf)
642                    else:
643                        self.log.error("Bad export request: %s" % p)
644                else:
645
646                    self.log.error("Unknown export parameter: %s" % \
647                            p.get('name'))
648                    continue
649
650    def configure_nodes(self, segment_commands, topo, nodes, user, pubkey, secretkey, 
651            stagingdir, tmpdir):
652
653        # These little functions/functors just make things more readable
654        class stage_file_type:
655            def __init__(self, user, host, stagingdir):
656                self.user = user
657                self.host = host
658                self.stagingdir = stagingdir
659                self.scp = "/usr/bin/scp -i .ssh/id_rsa -o " + \
660                        "'ForwardX11 no' -o 'StrictHostKeyChecking no' "
661
662            def __call__(self, script, file, dest="."):
663                # If the file is a full pathname, do not use stagingdir
664                if file.find('/') == -1:
665                    file = "%s/%s" % (self.stagingdir, file)
666                print >>script, "%s %s@%s:%s %s" % \
667                        (self.scp, self.user, self.host, file, dest)
668
669        def install_tar(script, loc, base):
670            tar = "/bin/tar"
671            mkdir="/bin/mkdir"
672
673            print >>script, "%s -p %s" % (mkdir, loc)
674            print >>script, "%s -C %s -xzf %s" % (tar, loc, base)
675
676        def install_rpm(script, base):
677            rpm = "/bin/rpm"
678            print >>script, "%s --install %s" % (rpm, base)
679
680        fed_dir = "/usr/local/federation"
681        fed_etc_dir = "%s/etc" % fed_dir
682        fed_bin_dir = "%s/bin" % fed_dir
683        fed_lib_dir = "%s/lib" % fed_dir
684
685        ifconfig = "/sbin/ifconfig"
686
687        stage_file = stage_file_type(user, self.staging_host, stagingdir)
688
689        for e in [ e for e in topo.elements if isinstance(e, topdl.Computer)]:
690            vname = e.name
691            node = nodes.get(vname, { })
692            pname = node.get('hostname', None)
693            if pname:
694                script = open("%s/%s.startup" %(tmpdir, pname), "w")
695                # Reset the interfaces to the ones in the topo file
696                for i in [ i for i in e.interface \
697                        if not i.get_attribute('portal')]:
698                    pinf = node['interfaces'].get(i.name, None)
699                    addr = i.get_attribute('ip4_address') 
700                    netmask = i.get_attribute('ip4_netmask') or '255.255.255.0'
701                    if pinf and addr:
702                        print >>script, \
703                                "%s %s %s netmask %s"  % \
704                                (ifconfig, pinf, addr, netmask)
705                    else:
706                        self.log.error("Missing interface or address for %s" \
707                                % i.name)
708                   
709                for l, f in self.federation_software:
710                    base = os.path.basename(f)
711                    stage_file(script, base)
712                    if l: install_tar(script, l, base)
713                    else: install_rpm(script, base)
714
715                for s in e.software:
716                    s_base = s.location.rpartition('/')[2]
717                    stage_file(script, s_base)
718                    if s.install: install_tar(script, s.install, s_base)
719                    else: install_rpm(script, s_base)
720
721                for f in ('hosts', pubkey, secretkey, 'client.conf', 
722                        'userconf'):
723                    stage_file(script, f, fed_etc_dir)
724                if self.sshd:
725                    stage_file(script, self.sshd, fed_bin_dir)
726                if self.sshd_config:
727                    stage_file(script, self.sshd_config, fed_etc_dir)
728
729                # Look in tmpdir to get the names.  They've all been copied
730                # into the (remote) staging dir
731                if os.access("%s/%s.gw.conf" % (tmpdir, vname), os.R_OK):
732                    stage_file(script, "%s.gw.conf" % vname, fed_etc_dir)
733
734                # Hackery dackery dock: the ProtoGENI python is really ancient.
735                # A modern version (though packaged for Mandrake (remember
736                # Mandrake?  good times, good times)) should be in the
737                # federation_software list, but we need to move rename is for
738                # SEER.
739                print >>script, "rm /usr/bin/python"
740                print >>script, "ln /usr/bin/python2.4 /usr/bin/python"
741                # Back to less hacky stuff
742
743                # Start commands
744                if e.get_attribute('portal') and self.portal_startcommand:
745                    # Install portal software
746                    for l, f in self.portal_software:
747                        base = os.path.basename(f)
748                        stage_file(script, base)
749                        if l: install_tar(script, l, base)
750                        else: install_rpm(script, base)
751
752                    # Portals never have a user-specified start command
753                    print >>script, self.portal_startcommand
754                elif self.node_startcommand:
755                    # XXX: debug
756                    print >>script, "sudo perl -I%s %simport_key.pl /users/%s/.ssh/authorized_keys /root/.ssh/authorized_keys" % (fed_lib_dir, fed_bin_dir, user)
757                    # XXX: debug
758                    if e.get_attribute('startup'):
759                        print >>script, "%s \\$USER '%s'" % \
760                                (self.node_startcommand, e.get_attribute('startup'))
761                    else:
762                        print >>script, self.node_startcommand
763                script.close()
764                if not segment_commands.scp_file("%s/%s.startup" % (tmpdir, pname), 
765                        user, pname):
766                    self.log.error("Could not copy script to %s" % pname)
767            else:
768                self.log.error("Unmapped node: %s" % vname)
769
770    def start_node(self, user, host, node, segment_commands):
771        # Place an identity on the node so that the copying can succeed
772        segment_commands.ssh_cmd(user, host, "scp .ssh/id_rsa %s:.ssh" % node)
773        segment_commands.ssh_cmd(user, node, 
774                "sudo /bin/sh ./%s.startup &" % node)
775
776    def start_nodes(self, user, host, nodes, segment_commands):
777        threads = [ ]
778        for n in nodes:
779            t = Thread(target=self.start_node, args=(user, host, n, 
780                segment_commands))
781            t.start()
782            threads.append(t)
783
784        done = [not t.isAlive() for t in threads]
785        while not all(done):
786            self.log.info("Waiting for threads %s" % done)
787            time.sleep(10)
788            done = [not t.isAlive() for t in threads]
789
790
791
792
793    def start_segment(self, segment_commands, aid, user, rspec, pubkey,
794            secretkey, ename, stagingdir, tmpdir, certfile, certpw,
795            export_certfile, topo, connInfo, services, timeout=0):
796        """
797        Start a sub-experiment on a federant.
798
799        Get the current state, modify or create as appropriate, ship data
800        and configs and start the experiment.  There are small ordering
801        differences based on the initial state of the sub-experiment.
802        """
803
804        def random_slicename(user):
805            slicename = user
806            for i in range(0,5):
807                slicename += random.choice(string.ascii_letters)
808            return slicename
809
810        host = self.staging_host
811        if not os.access(certfile, os.R_OK):
812            self.log.error("[start_segment]: Cannot read certfile: %s" % \
813                    certfile)
814            return False
815        ctxt = fedd_ssl_context(my_cert=certfile, password=certpw)
816        # Local software dir
817        lsoftdir = "%s/software" % tmpdir
818
819        # Open up a temporary file to contain a script for setting up the
820        # filespace for the new experiment.
821        self.log.info("[start_segment]: creating script file")
822        try:
823            sf, scriptname = tempfile.mkstemp()
824            scriptfile = os.fdopen(sf, 'w')
825        except EnvironmentError:
826            return False
827
828        scriptbase = os.path.basename(scriptname)
829
830        # Script the filesystem changes
831        print >>scriptfile, "/bin/rm -rf %s" % stagingdir
832        print >>scriptfile, 'mkdir -p %s' % stagingdir
833        print >>scriptfile, "rm -f %s" % scriptbase
834        scriptfile.close()
835
836        # Move the script to the remote machine
837        # XXX: could collide tempfile names on the remote host
838        if segment_commands.scp_file(scriptname, user, host, scriptbase):
839            os.remove(scriptname)
840        else:
841            return False
842
843        # Execute the script (and the script's last line deletes it)
844        if not segment_commands.ssh_cmd(user, host, "sh -x %s" % scriptbase):
845            return False
846
847        try:
848            gcred = segment_commands.pg_call(self.sa_url, 
849                    'GetCredential', {}, ctxt)
850        except self.ProtoGENIError, e:
851            raise service_error(service_error.federant,
852                    "ProtoGENI: %s" % e)
853        # Find a slicename not in use
854        slicename = "fabereGpgL"
855        while True:
856            slicename = random_slicename(user)
857            try:
858                param = {
859                        'credential': gcred, 
860                        'hrn': slicename,
861                        'type': 'Slice'
862                        }
863                segment_commands.pg_call(self.sa_url, 'Resolve', param, ctxt)
864            except segment_commands.ProtoGENIError, e:
865                print e
866                break
867
868        self.log.info("Creating %s" % slicename)
869        f = open("./rspec", "w")
870        print >>f, "%s" % rspec
871        f.close()
872        # Create the slice and allocate resources.  If any of this stuff fails,
873        # the allocations will time out on PG in short order, so we just raise
874        # the service_error.
875        try:
876            param = {
877                    'credential': gcred, 
878                    'hrn': slicename,
879                    'type': 'Slice'
880                    }
881            slice_cred = segment_commands.pg_call(self.sa_url, 'Register', param, ctxt)
882            f = open("./slice_cred", "w")
883            print >>f, slice_cred
884            f.close()
885            # Populate the ssh keys (let PG format them)
886            param = {
887                    'credential': gcred, 
888                    }
889            keys =  segment_commands.pg_call(self.sa_url, 'GetKeys', param, ctxt)
890            # Grab and redeem a ticket
891            param = {
892                    'credential': slice_cred, 
893                    'rspec': rspec,
894                    }
895            ticket = segment_commands.pg_call(self.cm_url, 'GetTicket', param, ctxt)
896            f = open("./ticket", "w")
897            print >>f, ticket
898            f.close()
899            param = { 
900                    'credential': slice_cred, 
901                    'keys': keys,
902                    'ticket': ticket,
903                    }
904            sliver_cred, manifest = segment_commands.pg_call(self.cm_url, 
905                    'RedeemTicket', param, ctxt)
906            f = open("./sliver_cred", "w")
907            print >>f, sliver_cred
908            f.close()
909            f = open("./manifest", "w")
910            print >>f, manifest
911            f.close()
912            # start 'em up
913            param = { 
914                    'credential': sliver_cred,
915                    }
916            segment_commands.pg_call(self.cm_url, 'StartSliver', param, ctxt)
917        except segment_commands.ProtoGENIError, e:
918            raise service_error(service_error.federant,
919                    "ProtoGENI: %s %s" % (e.code, e))
920
921        # With manifest in hand, we can export the portal node names.
922        if self.create_debug: nodes = self.fake_manifest(topo)
923        else: nodes = self.manifest_to_dict(manifest)
924
925        self.export_store_info(export_certfile, nodes, self.ssh_port,
926                connInfo)
927        self.generate_portal_configs(topo, pubkey, secretkey, tmpdir, 
928                ename, connInfo, services, nodes)
929
930        # Copy software to the staging machine (done after generation to copy
931        # those, too)
932        for d in (tmpdir, lsoftdir):
933            if os.path.isdir(d):
934                for f in os.listdir(d):
935                    if not os.path.isdir("%s/%s" % (d, f)):
936                        if not segment_commands.scp_file("%s/%s" % (d, f), 
937                                user, host, "%s/%s" % (stagingdir, f)):
938                            self.log.error("Scp failed")
939                            return False
940
941
942        # Now we wait for the nodes to start on PG
943        status = 'notready'
944        try:
945            while status == 'notready':
946                param = { 
947                        'credential': slice_cred
948                        }
949                r = segment_commands.pg_call(self.cm_url, 'SliceStatus', param, ctxt)
950                print r
951                status = r.get('status', 'notready')
952                if status == 'notready':
953                    time.sleep(30)
954        except segment_commands.ProtoGENIError, e:
955            raise service_error(service_error.federant,
956                    "ProtoGENI: %s %s" % (e.code, e))
957
958        if status == 'failed':
959            self.log.error('Sliver failed to start on ProtoGENI')
960            try:
961                param = { 
962                        'credential': slice_cred
963                        }
964                segment_commands.pg_call(self.cm_url, 'DeleteSliver', param, ctxt)
965            except segment_commands.ProtoGENIError, e:
966                raise service_error(service_error.federant,
967                    "ProtoGENI: %s" % e)
968            return False
969        else:
970            self.state_lock.acquire()
971            self.allocation[aid]['slice_name'] = slicename
972            self.allocation[aid]['slice_credential'] = slice_cred
973            self.allocation[aid]['sliver_credential'] = sliver_cred
974            self.allocation[aid]['manifest'] = manifest
975            self.allocation[aid]['certfile'] = certfile
976            self.allocation[aid]['certpw'] = certpw
977            self.write_state()
978            self.state_lock.release()
979
980        # Now we have configuration to do for ProtoGENI
981        self.configure_nodes(segment_commands, topo, nodes, user, pubkey, secretkey, 
982                stagingdir, tmpdir)
983
984        self.start_nodes(user, self.staging_host, 
985                [ n.get('hostname', None) for n in nodes.values()],
986                segment_commands)
987
988        # Everything has gone OK.
989        return True, dict([(k, n.get('hostname', None)) \
990                for k, n in nodes.items()])
991
992    def generate_rspec(self, topo, softdir, connInfo):
993        t = topo.clone()
994
995        starts = { }
996        # Weed out the things we aren't going to instantiate: Segments, portal
997        # substrates, and portal interfaces.  (The copy in the for loop allows
998        # us to delete from e.elements in side the for loop).  While we're
999        # touching all the elements, we also adjust paths from the original
1000        # testbed to local testbed paths and put the federation commands and
1001        # startcommands into a dict so we can start them manually later.
1002        # ProtoGENI requires setup before the federation commands run, so we
1003        # run them by hand after we've seeded configurations.
1004        for e in [e for e in t.elements]:
1005            if isinstance(e, topdl.Segment):
1006                t.elements.remove(e)
1007            # Fix software paths
1008            for s in getattr(e, 'software', []):
1009                s.location = re.sub("^.*/", softdir, s.location)
1010            if isinstance(e, topdl.Computer):
1011                if e.get_attribute('portal') and self.portal_startcommand:
1012                    # Portals never have a user-specified start command
1013                    starts[e.name] = self.portal_startcommand
1014                elif self.node_startcommand:
1015                    if e.get_attribute('startup'):
1016                        starts[e.name] = "%s \\$USER '%s'" % \
1017                                (self.node_startcommand, 
1018                                        e.get_attribute('startup'))
1019                        e.remove_attribute('startup')
1020                    else:
1021                        starts[e.name] = self.node_startcommand
1022
1023                # Remove portal interfaces
1024                e.interface = [i for i in e.interface \
1025                        if not i.get_attribute('portal')]
1026
1027        t.substrates = [ s.clone() for s in t.substrates ]
1028        t.incorporate_elements()
1029
1030        # Customize the ns2 output for local portal commands and images
1031        filters = []
1032
1033        # NB: these are extra commands issued for the node, not the startcmds
1034        if self.portal_command:
1035            filters.append(topdl.generate_portal_command_filter(
1036                self.portal_command))
1037
1038        # Convert to rspec and return it
1039        exp_rspec = topdl.topology_to_rspec(t, filters)
1040
1041        return exp_rspec
1042
1043    def retrieve_software(self, topo, certfile, softdir):
1044        """
1045        Collect the software that nodes in the topology need loaded and stage
1046        it locally.  This implies retrieving it from the experiment_controller
1047        and placing it into softdir.  Certfile is used to prove that this node
1048        has access to that data (it's the allocation/segment fedid).  Finally
1049        local portal and federation software is also copied to the same staging
1050        directory for simplicity - all software needed for experiment creation
1051        is in softdir.
1052        """
1053        sw = set()
1054        for e in topo.elements:
1055            for s in getattr(e, 'software', []):
1056                sw.add(s.location)
1057        os.mkdir(softdir)
1058        for s in sw:
1059            self.log.debug("Retrieving %s" % s)
1060            try:
1061                get_url(s, certfile, softdir)
1062            except:
1063                t, v, st = sys.exc_info()
1064                raise service_error(service_error.internal,
1065                        "Error retrieving %s: %s" % (s, v))
1066
1067        # Copy local portal node software to the tempdir
1068        for s in (self.portal_software, self.federation_software):
1069            for l, f in s:
1070                base = os.path.basename(f)
1071                copy_file(f, "%s/%s" % (softdir, base))
1072
1073        # Ick.  Put this python rpm in a place that it will get moved into
1074        # the staging area.  It's a hack to install a modern (in a Roman
1075        # sense of modern) python on ProtoGENI
1076        python_rpm ="python2.4-2.4-1pydotorg.i586.rpm"
1077        if os.access("./%s" % python_rpm, os.R_OK):
1078            copy_file("./%s" % python_rpm, "%s/%s" % (softdir, python_rpm))
1079
1080
1081    def initialize_experiment_info(self, attrs, aid, certfile, tmpdir):
1082        """
1083        Gather common configuration files, retrieve or create an experiment
1084        name and project name, and return the ssh_key filenames.  Create an
1085        allocation log bound to the state log variable as well.
1086        """
1087        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
1088        ename = None
1089        pubkey_base = None
1090        secretkey_base = None
1091        alloc_log = None
1092
1093        for a in attrs:
1094            if a['attribute'] in configs:
1095                try:
1096                    self.log.debug("Retrieving %s" % a['value'])
1097                    get_url(a['value'], certfile, tmpdir)
1098                except:
1099                    t, v, st = sys.exc_info()
1100                    raise service_error(service_error.internal,
1101                            "Error retrieving %s: %s" % (a.get('value', ""), v))
1102            if a['attribute'] == 'ssh_pubkey':
1103                pubkey_base = a['value'].rpartition('/')[2]
1104            if a['attribute'] == 'ssh_secretkey':
1105                secretkey_base = a['value'].rpartition('/')[2]
1106            if a['attribute'] == 'experiment_name':
1107                ename = a['value']
1108
1109        if not ename:
1110            ename = ""
1111            for i in range(0,5):
1112                ename += random.choice(string.ascii_letters)
1113            self.log.warn("No experiment name: picked one randomly: %s" \
1114                    % ename)
1115
1116        self.state_lock.acquire()
1117        if self.allocation.has_key(aid):
1118            cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
1119            self.allocation[aid]['experiment'] = ename
1120            self.allocation[aid]['log'] = [ ]
1121            # Create a logger that logs to the experiment's state object as
1122            # well as to the main log file.
1123            alloc_log = logging.getLogger('fedd.access.%s' % ename)
1124            h = logging.StreamHandler(
1125                    list_log.list_log(self.allocation[aid]['log']))
1126            # XXX: there should be a global one of these rather than
1127            # repeating the code.
1128            h.setFormatter(logging.Formatter(
1129                "%(asctime)s %(name)s %(message)s",
1130                        '%d %b %y %H:%M:%S'))
1131            alloc_log.addHandler(h)
1132            self.write_state()
1133        else:
1134            self.log.error("No allocation for %s!?" % aid)
1135        self.state_lock.release()
1136
1137        return (ename, pubkey_base, secretkey_base, cf, user, ssh_key, 
1138                cpw, alloc_log)
1139
1140    def finalize_experiment(self, topo, nodes, aid, alloc_id):
1141        # Copy the assigned names into the return topology
1142        rvtopo = topo.clone()
1143        embedding = [ ]
1144        for k, n in nodes.items():
1145            embedding.append({ 
1146                'toponame': k,
1147                'physname': ["%s%s" %  (n, self.domain)],
1148                })
1149        # Grab the log (this is some anal locking, but better safe than
1150        # sorry)
1151        self.state_lock.acquire()
1152        logv = "".join(self.allocation[aid]['log'])
1153        # It's possible that the StartSegment call gets retried (!).
1154        # if the 'started' key is in the allocation, we'll return it rather
1155        # than redo the setup.
1156        self.allocation[aid]['started'] = { 
1157                'allocID': alloc_id,
1158                'allocationLog': logv,
1159                'segmentdescription': { 
1160                    'topdldescription': rvtopo.to_dict() },
1161                'embedding': embedding,
1162                }
1163        retval = copy.deepcopy(self.allocation[aid]['started'])
1164        self.write_state()
1165        self.state_lock.release()
1166
1167        return retval
1168
1169    def StartSegment(self, req, fid):
1170        err = None  # Any service_error generated after tmpdir is created
1171        rv = None   # Return value from segment creation
1172
1173        try:
1174            req = req['StartSegmentRequestBody']
1175            topref = req['segmentdescription']['topdldescription']
1176        except KeyError:
1177            raise service_error(service_error.req, "Badly formed request")
1178
1179        connInfo = req.get('connection', [])
1180        services = req.get('service', [])
1181        auth_attr = req['allocID']['fedid']
1182        aid = "%s" % auth_attr
1183        attrs = req.get('fedAttr', [])
1184        if not self.auth.check_attribute(fid, auth_attr):
1185            raise service_error(service_error.access, "Access denied")
1186        else:
1187            # See if this is a replay of an earlier succeeded StartSegment -
1188            # sometimes SSL kills 'em.  If so, replay the response rather than
1189            # redoing the allocation.
1190            self.state_lock.acquire()
1191            retval = self.allocation[aid].get('started', None)
1192            self.state_lock.release()
1193            if retval:
1194                self.log.warning("Duplicate StartSegment for %s: " % aid + \
1195                        "replaying response")
1196                return retval
1197
1198        if topref:
1199            topo = topdl.Topology(**topref)
1200        else:
1201            raise service_error(service_error.req, 
1202                    "Request missing segmentdescription'")
1203
1204        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1205        try:
1206            tmpdir = tempfile.mkdtemp(prefix="access-")
1207            softdir = "%s/software" % tmpdir
1208        except EnvironmentError:
1209            raise service_error(service_error.internal, "Cannot create tmp dir")
1210
1211        # Try block alllows us to clean up temporary files.
1212        try:
1213            self.retrieve_software(topo, certfile, softdir)
1214            self.configure_userconf(services, tmpdir)
1215            ename, pubkey_base, secretkey_base, cf, user, ssh_key, \
1216                cpw, alloc_log = self.initialize_experiment_info(attrs,
1217                        aid, certfile, tmpdir)
1218            self.import_store_info(certfile, connInfo)
1219            rspec = self.generate_rspec(topo, "%s/%s/" \
1220                    % (self.staging_dir, ename), connInfo)
1221
1222            segment_commands = protogeni_proxy(keyfile=ssh_key,
1223                    debug=self.create_debug, log=alloc_log,
1224                    ch_url = self.ch_url, sa_url=self.sa_url,
1225                    cm_url=self.cm_url)
1226            rv, nodes = self.start_segment(segment_commands, aid, user, rspec,
1227                    pubkey_base, 
1228                    secretkey_base, ename,
1229                    "%s/%s" % (self.staging_dir, ename), tmpdir, cf, cpw,
1230                    certfile, topo, connInfo, services)
1231        except EnvironmentError:
1232            err = service_error(service_error.internal, "%s" % e)
1233        except service_error, e:
1234            err = e
1235        except:
1236            t, v, st = sys.exc_info()
1237            err = service_error(service_error.internal, "%s: %s" % \
1238                    (v, traceback.extract_tb(st)))
1239
1240        # Walk up tmpdir, deleting as we go
1241        if self.cleanup: self.remove_dirs(tmpdir)
1242        else: self.log.debug("[StartSegment]: not removing %s" % tmpdir)
1243
1244        if rv:
1245            return self.finalize_experiment(topo, nodes, aid, req['allocID'])
1246        elif err:
1247            raise service_error(service_error.federant,
1248                    "Swapin failed: %s" % err)
1249        else:
1250            raise service_error(service_error.federant, "Swapin failed")
1251
1252    def stop_segment(self, segment_commands, user, stagingdir, slice_cred,
1253            certfile, certpw):
1254        """
1255        Stop a sub experiment by calling swapexp on the federant
1256        """
1257        host = self.staging_host
1258        rv = False
1259        try:
1260            # Clean out tar files: we've gone over quota in the past
1261            if stagingdir:
1262                segment_commands.ssh_cmd(user, host, "rm -rf %s" % stagingdir)
1263            if slice_cred:
1264                self.log.error('Removing Sliver on ProtoGENI')
1265                ctxt = fedd_ssl_context(my_cert=certfile, password=certpw)
1266                try:
1267                    param = { 
1268                            'credential': slice_cred
1269                            }
1270                    segment_commands.pg_call(self.cm_url, 'DeleteSlice',
1271                            param, ctxt)
1272                except segment_commands.ProtoGENIError, e:
1273                    raise service_error(service_error.federant,
1274                        "ProtoGENI: %s" % e)
1275            return True
1276        except self.ssh_cmd_timeout:
1277            rv = False
1278        return rv
1279
1280    def TerminateSegment(self, req, fid):
1281        try:
1282            req = req['TerminateSegmentRequestBody']
1283        except KeyError:
1284            raise service_error(service_error.req, "Badly formed request")
1285
1286        auth_attr = req['allocID']['fedid']
1287        aid = "%s" % auth_attr
1288        attrs = req.get('fedAttr', [])
1289        if not self.auth.check_attribute(fid, auth_attr):
1290            raise service_error(service_error.access, "Access denied")
1291
1292        self.state_lock.acquire()
1293        if self.allocation.has_key(aid):
1294            cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
1295            slice_cred = self.allocation[aid].get('slice_credential', None)
1296            ename = self.allocation[aid].get('experiment', None)
1297        else:
1298            cf, user, ssh_key, cpw = (None, None, None, None)
1299            slice_cred = None
1300            ename = None
1301        self.state_lock.release()
1302
1303        if ename:
1304            staging = "%s/%s" % ( self.staging_dir, ename)
1305        else:
1306            self.log.warn("Can't find experiment name for %s" % aid)
1307            staging = None
1308
1309        segment_commands = protogeni_proxy(keyfile=ssh_key,
1310                debug=self.create_debug, ch_url = self.ch_url,
1311                sa_url=self.sa_url, cm_url=self.cm_url)
1312        self.stop_segment(segment_commands, user, staging, slice_cred, cf, cpw)
1313        return { 'allocID': req['allocID'] }
1314
1315    def renew_segment(self, segment_commands, name, scred, interval, 
1316            certfile, certpw):
1317        ctxt = fedd_ssl_context(my_cert=certfile, password=certpw)
1318        try:
1319            expiration = time.strftime("%Y%m%dT%H:%M:%S",
1320                    time.gmtime(time.time() + interval))
1321            cred = segment_commands.pg_call(self.sa_url, 'GetCredential', {}, ctxt)
1322
1323            param = {
1324                    'credential': scred,
1325                    'expiration': expiration
1326                    }
1327            r = segment_commands.pg_call(self.sa_url, 'RenewSlice', param, ctxt)
1328            param = {
1329                    'credential': cred,
1330                    'hrn': name,
1331                    'type': 'Slice',
1332                    }
1333            slice = segment_commands.pg_call(self.sa_url, 'Resolve', param, ctxt)
1334            uuid = slice.get('uuid', None)
1335            if uuid == None:
1336                sys.exit('No uuid for %s' % slicename)
1337
1338            print 'Calling GetCredential (uuid)'
1339            param = {
1340                    'credential': cred,
1341                    'uuid': uuid,
1342                    'type': 'Slice',
1343                    }
1344            new_scred = segment_commands.pg_call(self.sa_url, 'GetCredential', param, ctxt)
1345            f = open('./new_slice_cred', 'w')
1346            print >>f, new_scred
1347            f.close()
1348
1349        except segment_commands.ProtoGENIError, e:
1350            self.log.error("Failed to extend slice %s: %s" % (name, e))
1351            return None
1352        try:
1353            print 'Calling RenewSlice (CM)'
1354            param = {
1355                    'credential': new_scred,
1356                    }
1357            r = segment_commands.pg_call(self.cm_url, 'RenewSlice', param, ctxt)
1358        except segment_commands.ProtoGENIError, e:
1359            self.log.warn("Failed to renew sliver for %s: %s" % (name, e))
1360
1361        return new_scred
1362   
1363
1364    def RenewSlices(self):
1365        self.log.info("Scanning for slices to renew")
1366        self.state_lock.acquire()
1367        aids = self.allocation.keys()
1368        self.state_lock.release()
1369
1370        for aid in aids:
1371            self.state_lock.acquire()
1372            if self.allocation.has_key(aid):
1373                name = self.allocation[aid].get('slice_name', None)
1374                scred = self.allocation[aid].get('slice_credential', None)
1375                cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
1376            else:
1377                name = None
1378                scred = None
1379            self.state_lock.release()
1380
1381            if not os.access(cf, os.R_OK):
1382                self.log.error(
1383                        "[RenewSlices] cred.file %s unreadable, ignoring" % cf)
1384                continue
1385
1386            # There's a ProtoGENI slice associated with the segment; renew it.
1387            if name and scred:
1388                segment_commands = protogeni_proxy(log=self.log, 
1389                        debug=self.create_debug, keyfile=ssh_key,
1390                        cm_url = self.cm_url, sa_url = self.sa_url,
1391                        ch_url = self.ch_url)
1392                new_scred = self.renew_segment(segment_commands, name, scred, 
1393                        self.renewal_interval, cf, cpw)
1394                if new_scred:
1395                    self.log.info("Slice %s renewed until %s GMT" % \
1396                            (name, time.asctime(time.gmtime(\
1397                                time.time()+self.renewal_interval))))
1398                    self.state_lock.acquire()
1399                    if self.allocation.has_key(aid):
1400                        self.allocation[aid]['slice_credential'] = new_scred
1401                    self.state_lock.release()
1402                else:
1403                    self.log.info("Failed to renew slice %s " % name)
1404
1405        # Let's do this all again soon.  (4 tries before the slices time out)   
1406        t = Timer(self.renewal_interval/4, self.RenewSlices)
1407        t.start()
Note: See TracBrowser for help on using the repository browser.