source: fedd/federation/emulab_access.py @ 8353ac6

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

Typo

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