source: fedd/federation/emulab_access.py @ 5334044

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

Add service to selectively hide hosts from other testbeds

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