source: fedd/federation/emulab_access.py @ 0ac1934

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

Small SEER things. Compute a correct visualization and done tell an exporting tstbed to configure services

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