source: fedd/federation/emulab_access.py @ 5e1fb7b

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

Get all those attributes out of the access DB. Bad design.

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