source: fedd/federation/emulab_access.py @ 8e54792

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since 8e54792 was ab847bc, checked in by Ted Faber <faber@…>, 15 years ago

New syntax for testbeds that includes a /instance rider. This allows users to
request multiple segments from a single testbed.

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