source: fedd/federation/emulab_access.py @ dbc9144

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

remove some debugging

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