source: fedd/federation/emulab_access.py @ d8442da

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

This seems more robust in calling GetValue?. This was intermittently hanging, I think because of troubles with the SSL state in the caller object. Reinitializing it seems to help.

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