source: fedd/federation/emulab_access.py @ 8e6fe4d

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

checkpoint and SLSL error catching

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