source: fedd/federation/emulab_access.py @ 9b3627e

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

SEER support

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