source: fedd/federation/emulab_access.py @ 2ee4226

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

More attempts to make the SSL more reliable on users. Not completely
successful.

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