source: fedd/federation/emulab_access.py @ e777dab

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

Checkpoint working federation w/PG (w/o routing yet...)

  • Property mode set to 100644
File size: 54.5 KB
RevLine 
[19cc408]1#!/usr/local/bin/python
2
3import os,sys
[eeb0088]4import stat # for chmod constants
[19cc408]5import re
6import string
7import copy
[d81971a]8import pickle
[c971895]9import logging
[eeb0088]10import subprocess
[19cc408]11
[f8582c9]12from threading import *
[8e6fe4d]13from M2Crypto.SSL import SSLError
[f8582c9]14
[ec4fb42]15from util import *
16from allocate_project import allocate_project_local, allocate_project_remote
17from access_project import access_project
[51cc9df]18from fedid import fedid, generate_fedid
[3f6bc5f]19from authorizer import authorizer
[6a0c9f4]20from service_error import service_error
[9460b1e]21from remote_service import xmlrpc_handler, soap_handler, service_caller
[11a08b0]22
[6c57fe9]23import httplib
24import tempfile
25from urlparse import urlparse
26
[11860f52]27import topdl
28import list_log
29import proxy_emulab_segment
30import local_emulab_segment
31
[0ea11af]32
33# Make log messages disappear if noone configures a fedd logger
[11a08b0]34class nullHandler(logging.Handler):
35    def emit(self, record): pass
36
37fl = logging.getLogger("fedd.access")
38fl.addHandler(nullHandler())
[19cc408]39
[ec4fb42]40class access:
[19cc408]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
[4ed10ae]48    class parse_error(RuntimeError): pass
49
[72ed6e4]50
[f069052]51    proxy_RequestAccess= service_caller('RequestAccess')
52    proxy_ReleaseAccess= service_caller('ReleaseAccess')
[c35207d]53
[3f6bc5f]54    def __init__(self, config=None, auth=None):
[866c983]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")
[6c57fe9]71        self.certdir = config.get("access","certdir")
[eeb0088]72        self.userconfdir = config.get("access","userconfdir")
73        self.userconfcmd = config.get("access","userconfcmd")
[fe28bb2]74        self.userconfurl = config.get("access","userconfurl")
[ecca6eb]75        self.ssh_privkey_file = config.get("access","ssh_privkey_file")
[3bddd24]76        self.ssh_pubkey_file = config.get("access","ssh_pubkey_file")
[6280b1f]77        self.ssh_port = config.get("access","ssh_port") or "22"
[5ae3857]78        self.create_debug = config.getboolean("access", "create_debug")
[69692a9]79        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
[11860f52]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
[866c983]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()
[e02cd14]109        # XXX: Configurable
[eeb0088]110        self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig'))
[b7f6ccc]111        self.imports = set(('SMB', 'seer', 'userconfig'))
[866c983]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),
[cc8d8e9]143            'StartSegment': soap_handler("StartSegment", self.StartSegment),
[5ae3857]144            'TerminateSegment': soap_handler("TerminateSegment", self.TerminateSegment),
[866c983]145            }
146        self.xmlrpc_services =  {\
147            'RequestAccess': xmlrpc_handler('RequestAccess',
148                self.RequestAccess),
149            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
150                self.ReleaseAccess),
[5ae3857]151            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
152            'TerminateSegment': xmlrpc_handler('TerminateSegment',
153                self.TerminateSegment),
[866c983]154            }
155
[2761484]156        self.call_SetValue = service_caller('SetValue')
157        self.call_GetValue = service_caller('GetValue')
[866c983]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)
[f8582c9]170
[72ed6e4]171
172    def read_access(self, config):
[866c983]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()
[72ed6e4]272
[19cc408]273    def get_users(self, obj):
[866c983]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
[19cc408]282
[d81971a]283    def write_state(self):
[866c983]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)
[d81971a]295
296
297    def read_state(self):
[866c983]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))
[eeb0088]332                if self.allocation[k].has_key('userconfig'):
[fe28bb2]333                    sfid = self.allocation[k]['userconfig']
[eeb0088]334                    fid = fedid(hexstr=sfid)
[fe28bb2]335                    self.auth.set_attribute(fid, "/%s" % sfid)
[3f6bc5f]336
337
[19cc408]338    def permute_wildcards(self, a, p):
[866c983]339        """Return a copy of a with various fields wildcarded.
[19cc408]340
[866c983]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]
[19cc408]350
[866c983]351        return (tb, proj, user)
[19cc408]352
353    def find_access(self, search):
[866c983]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
[19cc408]376
377    def lookup_access(self, req, fid):
[866c983]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        """
[3bddd24]384        user_re = re.compile("user:\s(.*)")
385        project_re = re.compile("project:\s(.*)")
386
[866c983]387        # Search keys
388        tb = None
389        project = None
390        user = None
391        # Return values
392        rp = access_project(None, ())
393        ru = None
394
[3bddd24]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
[866c983]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
[19cc408]502
[eeb0088]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
[fe28bb2]532        self.auth.set_attribute(confid, "/%s" % str(confid))
[eeb0088]533
534        return confid, confcert
535
536
[e02cd14]537    def export_services(self, sreq, project, user):
538        exp = [ ]
[eeb0088]539        state = { }
540        # XXX: Filthy shortcut here using http: so urlparse will give the right
[e02cd14]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"
[eeb0088]560                    elif sname == 'userconfig':
[fe28bb2]561                        if self.userconfdir and self.userconfcmd \
562                                and self.userconfurl:
[eeb0088]563                            cid, cert = self.export_userconf(project)
[fe28bb2]564                            outs['server'] = "%s/%s" % \
565                                    (self.userconfurl, str(cid))
[eeb0088]566                            outs['fedAttr'] = [
567                                    { 'attribute': 'cert', 'value': cert },
568                                ]
569                            state['userconfig'] = unicode(cid)
[e02cd14]570                    exp.append(outs)
[eeb0088]571        return (exp, state)
[e02cd14]572
573    def build_response(self, alloc_id, ap, services):
[866c983]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,
[4afcfc4]586                'fedAttr': [
587                    { 'attribute': 'domain', 'value': self.domain } , 
588                    { 'attribute': 'project', 'value': 
589                        ap['project'].get('name', {}).get('localname', "???") },
590                ]
[866c983]591            }
592        if len(self.attrs) > 0:
[4afcfc4]593            msg['fedAttr'].extend(
[866c983]594                [ { 'attribute': x, 'value' : y } \
[4afcfc4]595                        for x,y in self.attrs.iteritems()])
[e02cd14]596
597        if services:
598            msg['service'] = services
[866c983]599        return msg
[19cc408]600
601    def RequestAccess(self, req, fid):
[866c983]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))
[3bddd24]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 ]
[866c983]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
[ecca6eb]729            else:
730                # sproject is a static project associated with this allocation.
731                self.allocation[aid]['sproject'] = pname
[866c983]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']
[ecca6eb]754                    if u['role'] == 'experimentCreation':
755                        self.allocation[aid]['user'] = uname
[866c983]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
[eeb0088]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
[866c983]773            self.write_state()
774            self.state_lock.release()
775            for o in owners:
776                self.auth.set_attribute(o, allocID)
[6c57fe9]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))
[e02cd14]784            resp = self.build_response({ 'fedid': allocID } , ap, services)
[866c983]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")
[d81971a]798
799    def ReleaseAccess(self, req, fid):
[866c983]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)
[f07fa49]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)
[866c983]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")
[d81971a]912
[f9ef40b]913    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
[e02cd14]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:
[617592b]924                if key == i.get('portal', "") or \
925                        key in [e.get('element', "") \
926                        for e in i.get('member', [])]:
[e02cd14]927                    rv = i.copy()
928                    break
[617592b]929
[e02cd14]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
[b7f6ccc]943        def client_null(f, s):
944            print >>f, "Service: %s" % s['name']
[e02cd14]945
946        def client_smb(f, s):
[b7f6ccc]947            print >>f, "Service: %s" % s['name']
[e02cd14]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
[ecca6eb]972
973        seer_out = False
[1da6a23]974        client_out = False
[e2a7a413]975        for e in [ e for e in topo.elements \
[f9ef40b]976                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
977            myname = e.name[0]
[ecca6eb]978            type = e.get_attribute('portal_type')
[f9ef40b]979
[e02cd14]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;
[593e901]988            ssh_port = info.get('ssh_port', 22)
[e02cd14]989
990            mexp = info.get('masterexperiment',"")
991            mproj, meid = mexp.split("/", 1)
992            mdomain = info.get('masterdomain',"")
993            muser = info.get('masteruser','root')
994            smbshare = info.get('smbshare', 'USERS')
995
996            active = info.get('active', 'False')
997
[0297248]998            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
[1da6a23]999            tunnelconfig = self.attrs.has_key('TunnelCfg')
[f9ef40b]1000            try:
1001                f = open(cfn, "w")
[0297248]1002                if active == 'True':
[d87778f]1003                    print >>f, "active: True"
[593e901]1004                    print >>f, "ssh_port: %s" % ssh_port
[0297248]1005                    if type in ('control', 'both'):
[e02cd14]1006                        for s in [s for s in services \
1007                                if s.get('name', "") in self.imports]:
1008                            p = urlparse(s.get('server', 'http://localhost'))
1009                            print >>f, 'port: remote:%s:%s:%s' % \
1010                                    (p.port, p.hostname, p.port)
[0297248]1011
1012                if tunnelconfig:
1013                    print >>f, "tunnelip: %s" % tunnelconfig
[e02cd14]1014                # XXX: send this an fedattr
1015                #print >>f, "seercontrol: control.%s.%s%s" % \
1016                        #(meid.lower(), mproj.lower(), mdomain)
1017                print >>f, "peer: %s" % peer.lower()
[0297248]1018                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
[f9ef40b]1019                        (lproj, leid, pubkey_base)
[0297248]1020                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
[f9ef40b]1021                        (lproj, leid, secretkey_base)
1022                f.close()
1023            except IOError, e:
1024                raise service_error(service_error.internal,
1025                        "Can't write protal config %s: %s" % (cfn, e))
[ecca6eb]1026           
1027            # XXX: This little seer config file needs to go away.
1028            if not seer_out:
1029                try:
1030                    seerfn = "%s/seer.conf" % tmpdir
1031                    f = open(seerfn, "w")
1032                    if not master:
1033                        print >>f, "ControlNode: control.%s.%s%s" % \
1034                            (meid.lower(), mproj.lower(), mdomain)
1035                    print >>f, "ExperimentID: %s" % mexp
1036                    f.close()
1037                except IOError, e:
1038                    raise service_error(service_error.internal, 
1039                            "Can't write seer.conf: %s" %e)
1040                seer_out = True
1041
[1da6a23]1042            if not client_out and type in ('control', 'both'):
1043                try:
1044                    f = open("%s/client.conf" % tmpdir, "w")
1045                    print >>f, "ControlGateway: %s.%s.%s%s" % \
1046                        (myname.lower(), leid.lower(), lproj.lower(),
1047                                ldomain.lower())
[e02cd14]1048                    for s in services:
[0ac1934]1049                        if s.get('name',"") in self.imports and \
1050                                s.get('visibility','') == 'import':
[e02cd14]1051                            client_service_out[s['name']](f, s)
[6280b1f]1052                    # Does seer need this? (evidently so)
1053                    print >>f, "ExperimentID: %s/%s" % (mproj, meid)
[1da6a23]1054                    f.close()
1055                except IOError, e:
1056                    raise service_error(service_error.internal,
1057                            "Cannot write client.conf: %s" %s)
1058                client_out = True
1059
[ecca6eb]1060
[617592b]1061    def generate_ns2(self, topo, expfn, softdir, master, connInfo):
1062        class dragon_commands:
1063            """
1064            Functor to spit out approrpiate dragon commands for nodes listed in
1065            the connectivity description.  The constructor makes a dict mapping
1066            dragon nodes to their parameters and the __call__ checks each
1067            element in turn for membership.
1068            """
1069            def __init__(self, map):
1070                self.node_info = map
1071
1072            def __call__(self, e):
1073                s = ""
1074                if isinstance(e, topdl.Computer):
1075                    if self.node_info.has_key(e.name[0]):
1076                        i = self.node_info[e.name[0]]
1077                        for ifname, vlan, type in i:
1078                            for i in e.interface:
1079                                if i.name == ifname:
1080                                    addr = i.get_attribute('ip4_address')
1081                                    subs = i.substrate[0]
1082                                    break
1083                            else:
1084                                raise service_error(service_error.internal,
1085                                        "No interface %s on element %s" % \
1086                                                (ifname, e.name[0]))
1087                            if type =='link':
1088                                s = ("tb-allow-external $%s dragonportal " + \
1089                                        "ip %s vlan %s\n") % \
1090                                        (e.name[0], addr, vlan)
1091                            elif type =='lan':
1092                                s = ("tb-allow-external $%s dragonportal " + \
1093                                        "ip %s vlan %s usurp %s\n") % \
1094                                        (e.name[0], addr, vlan, subs)
1095                            else:
1096                                raise service_error(service_error_internal,
1097                                        "Unknown DRAGON type %s" % type)
1098                return s
1099
1100        class not_dragon:
1101            def __init__(self, map):
1102                self.nodes = set(map.keys())
1103
1104            def __call__(self, e):
1105                return e.name[0] not in self.nodes
[69692a9]1106
[ecca6eb]1107        t = topo.clone()
1108
[617592b]1109        dragon_map = { }
1110        for i in [ i for i in connInfo if i['type'] == 'transit']:
1111            for a in i.get('fedAttr', []):
1112                if a['attribute'] == 'vlan_id':
1113                    vlan = a['value']
1114                    break
1115            else:
1116                raise service_error(service_error.internal, 
1117                        "No vlan tag")
1118            members = i.get('member', [])
1119            if len(members) > 1: type = 'lan'
1120            else: type = 'link'
1121
1122            try:
1123                for m in members:
1124                    if dragon_map.has_key(m['element']):
1125                        dragon_map[m['element']].append(( m['interface'], 
1126                            vlan, type))
1127                    else:
1128                        dragon_map[m['element']] = [( m['interface'], 
1129                            vlan, type),]
1130            except KeyError:
1131                raise service_error(service_error.req,
1132                        "Missing connectivity info")
1133
[43649f1]1134        # The startcmds for master and slave testbeds
1135        if master: 
1136            gate_cmd = self.attrs.get('MasterConnectorStartCmd', '/bin/true')
1137            node_cmd = self.attrs.get('MasterNodeStartCmd', 'bin/true')
1138        else: 
1139            gate_cmd = self.attrs.get('SlaveConnectorStartCmd', '/bin/true')
1140            node_cmd = self.attrs.get('SlaveNodeStartCmd', 'bin/true')
[0297248]1141
[35aa3ae]1142        # Weed out the things we aren't going to instantiate: Segments, portal
1143        # substrates, and portal interfaces.  (The copy in the for loop allows
1144        # us to delete from e.elements in side the for loop).  While we're
1145        # touching all the elements, we also adjust paths from the original
1146        # testbed to local testbed paths and put the federation commands into
1147        # the start commands
1148        for e in [e for e in t.elements]:
1149            if isinstance(e, topdl.Segment):
1150                t.elements.remove(e)
1151            # Fix software paths
[ecca6eb]1152            for s in getattr(e, 'software', []):
1153                s.location = re.sub("^.*/", softdir, s.location)
[43649f1]1154            if isinstance(e, topdl.Computer):
[35aa3ae]1155                if e.get_attribute('portal') and gate_cmd:
[43649f1]1156                    # Portals never have a user-specified start command
1157                    e.set_attribute('startup', gate_cmd)
[35aa3ae]1158                elif node_cmd:
[43649f1]1159                    if e.get_attribute('startup'):
[d87778f]1160                        e.set_attribute('startup', "%s \\$USER '%s'" % \
[43649f1]1161                                (node_cmd, e.get_attribute('startup')))
1162                    else:
[d87778f]1163                        e.set_attribute('startup', node_cmd)
[0297248]1164
[617592b]1165                dinf = [i[0] for i in dragon_map.get(e.name[0], []) ]
[35aa3ae]1166                # Remove portal interfaces that do not connect to DRAGON
1167                e.interface = [i for i in e.interface \
[617592b]1168                        if not i.get_attribute('portal') or i.name in dinf ]
[35aa3ae]1169
1170        t.substrates = [ s.clone() for s in t.substrates ]
1171        t.incorporate_elements()
[ecca6eb]1172
1173        # Customize the ns2 output for local portal commands and images
1174        filters = []
1175
[43649f1]1176        # NB: these are extra commands issued for the node, not the startcmds
1177        if master: cmd = self.attrs.get('MasterConnectorCmd', '')
1178        else: cmd = self.attrs.get('SlaveConnectorCmd', '')
[ecca6eb]1179
[69692a9]1180        if self.attrs.has_key('dragon'):
[617592b]1181            add_filter = not_dragon(dragon_map)
1182            filters.append(dragon_commands(dragon_map))
[69692a9]1183        else:
1184            add_filter = None
1185
[43649f1]1186        if cmd:
1187            filters.append(topdl.generate_portal_command_filter(cmd,
1188                add_filter=add_filter))
[ecca6eb]1189
1190        if self.attrs.has_key('connectorImage'):
1191            filters.append(topdl.generate_portal_image_filter(
1192                self.attrs.get('connectorImage')))
1193
1194        if self.attrs.has_key('connectorType'):
1195            filters.append(topdl.generate_portal_hardware_filter(
1196                self.attrs.get('connectorType')))
1197
1198        # Convert to ns and write it out
1199        expfile = topdl.topology_to_ns2(t, filters)
1200        try:
1201            f = open(expfn, "w")
1202            print >>f, expfile
1203            f.close()
1204        except IOError:
1205            raise service_error(service_error.internal,
1206                    "Cannot write experiment file %s: %s" % (expfn,e))
[f9ef40b]1207
[2761484]1208    def export_store_info(self, cf, proj, ename, connInfo):
1209        """
1210        For the export requests in the connection info, install the peer names
1211        at the experiment controller via SetValue calls.
1212        """
1213
1214        for c in connInfo:
1215            for p in [ p for p in c.get('parameter', []) \
1216                    if p.get('type', '') == 'output']:
1217
[6280b1f]1218                if p.get('name', '') == 'peer':
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                elif p.get('name', '') == 'ssh_port':
1229                    k = p.get('key', None)
1230                    surl = p.get('store', None)
1231                    if surl and k:
1232                        req = { 'name': k, 'value': self.ssh_port }
1233                        self.call_SetValue(surl, req, cf)
1234                    else:
1235                        self.log.error("Bad export request: %s" % p)
1236                else:
[2761484]1237                    self.log.error("Unknown export parameter: %s" % \
1238                            p.get('name'))
1239                    continue
1240
1241    def import_store_info(self, cf, connInfo):
1242        """
1243        Pull any import parameters in connInfo in.  We translate them either
1244        into known member names or fedAddrs.
1245        """
1246
1247        for c in connInfo:
1248            for p in [ p for p in c.get('parameter', []) \
1249                    if p.get('type', '') == 'input']:
1250                name = p.get('name', None)
1251                key = p.get('key', None)
1252                store = p.get('store', None)
1253
1254                if name and key and store :
1255                    req = { 'name': key, 'wait': True }
1256                    r = self.call_GetValue(store, req, cf)
1257                    r = r.get('GetValueResponseBody', None)
1258                    if r :
1259                        if r.get('name', '') == key:
1260                            v = r.get('value', None)
1261                            if v is not None:
1262                                if name == 'peer':
1263                                    c['peer'] = v
1264                                else:
1265                                    if c.has_key('fedAttr'):
1266                                        c['fedAttr'].append({
[8353ac6]1267                                            'attribute': name, 'value': v})
[2761484]1268                                    else:
1269                                        c['fedAttr']= [{
[8353ac6]1270                                            'attribute': name, 'value': v}]
[2761484]1271                            else:
1272                                raise service_error(service_error.internal, 
1273                                        'None value exported for %s'  % key)
1274                        else:
1275                            raise service_error(service_error.internal, 
1276                                    'Different name returned for %s: %s' \
1277                                            % (key, r.get('name','')))
1278                    else:
1279                        raise service_error(service_error.internal, 
1280                            'Badly formatted response: no GetValueResponseBody')
1281                else:
1282                    raise service_error(service_error.internal, 
1283                        'Bad Services missing info for import %s' % c)
1284
[cc8d8e9]1285    def StartSegment(self, req, fid):
[fe28bb2]1286        def get_url(url, cf, destdir, fn=None):
[6c57fe9]1287            po = urlparse(url)
[fe28bb2]1288            if not fn:
1289                fn = po.path.rpartition('/')[2]
[8e6fe4d]1290            ok = False
1291            retries = 0
1292            while not ok and retries < 5:
1293                try:
1294                    conn = httplib.HTTPSConnection(po.hostname, port=po.port, 
1295                            cert_file=cf, key_file=cf)
1296                    conn.putrequest('GET', po.path)
1297                    conn.endheaders()
1298                    response = conn.getresponse()
1299
1300                    lf = open("%s/%s" % (destdir, fn), "w")
[6c57fe9]1301                    buf = response.read(4096)
[8e6fe4d]1302                    while buf:
1303                        lf.write(buf)
1304                        buf = response.read(4096)
1305                    lf.close()
1306                    ok = True
1307                except IOError, e:
1308                    print e
1309                    raise service_error(service_error.internal,
1310                            "Error writing tempfile: %s" %e)
1311                except httplib.HTTPException, e:
1312                    print e
1313                    raise service_error(service_error.internal, 
1314                            "Error retrieving data: %s" % e)
1315                except SSLError, e:
1316                    print "SSL error %s" %e
1317                    retries += 1
1318
1319            if retries > 5:
[6c57fe9]1320                raise service_error(service_error.internal,
[8e6fe4d]1321                        "Repeated SSL failures")
[6c57fe9]1322
1323        configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
1324
[b770aa0]1325        err = None  # Any service_error generated after tmpdir is created
1326        rv = None   # Return value from segment creation
1327
[cc8d8e9]1328        try:
1329            req = req['StartSegmentRequestBody']
1330        except KeyError:
1331            raise service_error(server_error.req, "Badly formed request")
[ecca6eb]1332
[e02cd14]1333        connInfo = req.get('connection', [])
1334        services = req.get('service', [])
[cc8d8e9]1335        auth_attr = req['allocID']['fedid']
[ecca6eb]1336        aid = "%s" % auth_attr
[6c57fe9]1337        attrs = req.get('fedAttr', [])
[ecca6eb]1338        if not self.auth.check_attribute(fid, auth_attr):
1339            raise service_error(service_error.access, "Access denied")
[6c57fe9]1340
1341        if req.has_key('segmentdescription') and \
1342                req['segmentdescription'].has_key('topdldescription'):
1343            topo = \
1344                topdl.Topology(**req['segmentdescription']['topdldescription'])
1345        else:
1346            raise service_error(service_error.req, 
1347                    "Request missing segmentdescription'")
[2761484]1348       
[ecca6eb]1349        master = req.get('master', False)
1350
[6c57fe9]1351        certfile = "%s/%s.pem" % (self.certdir, auth_attr)
1352        try:
1353            tmpdir = tempfile.mkdtemp(prefix="access-")
[ecca6eb]1354            softdir = "%s/software" % tmpdir
[6c57fe9]1355        except IOError:
1356            raise service_error(service_error.internal, "Cannot create tmp dir")
1357
[b770aa0]1358        # Try block alllows us to clean up temporary files.
1359        try:
1360            sw = set()
1361            for e in topo.elements:
1362                for s in getattr(e, 'software', []):
1363                    sw.add(s.location)
1364            if len(sw) > 0:
1365                os.mkdir(softdir)
1366            for s in sw:
[e02cd14]1367                self.log.debug("Retrieving %s" % s)
[b770aa0]1368                get_url(s, certfile, softdir)
1369
1370            for a in attrs:
1371                if a['attribute'] in configs:
1372                    get_url(a['value'], certfile, tmpdir)
1373                if a['attribute'] == 'ssh_pubkey':
1374                    pubkey_base = a['value'].rpartition('/')[2]
1375                if a['attribute'] == 'ssh_secretkey':
1376                    secretkey_base = a['value'].rpartition('/')[2]
1377                if a['attribute'] == 'experiment_name':
1378                    ename = a['value']
1379
[fe28bb2]1380            # If the userconf service was imported, collect the configuration
1381            # data.
1382            for s in services:
1383                if s.get("name", "") == 'userconfig' \
1384                        and s.get('visibility',"") == 'import':
[94a00cb]1385
1386                    # Collect ther server and certificate info.
[fe28bb2]1387                    u = s.get('server', None)
1388                    for a in s.get('fedAttr', []):
1389                        if a.get('attribute',"") == 'cert':
1390                            cert = a.get('value', None)
1391                            break
1392                    else:
1393                        cert = None
1394
[94a00cb]1395                    if cert:
1396                        # Make a temporary certificate file for get_url.  The
1397                        # finally clause removes it whether something goes
1398                        # wrong (including an exception from get_url) or not.
1399                        try:
1400                            tfos, tn = tempfile.mkstemp(suffix=".pem")
1401                            tf = os.fdopen(tfos, 'w')
1402                            print >>tf, cert
1403                            tf.close()
1404                            get_url(u, tn, tmpdir, "userconf")
1405                        except IOError, e:
1406                            raise service_error(service.error.internal, 
1407                                    "Cannot create temp file for " + 
1408                                    "userconfig certificates: %s e")
1409                        finally:
1410                            if tn: os.remove(tn)
1411                    else:
[fe28bb2]1412                        raise service_error(service_error.req,
1413                                "No certificate for retreiving userconfig")
1414                    break
1415
1416
1417
[b770aa0]1418            proj = None
1419            user = None
1420            self.state_lock.acquire()
1421            if self.allocation.has_key(aid):
1422                proj = self.allocation[aid].get('project', None)
1423                if not proj: 
1424                    proj = self.allocation[aid].get('sproject', None)
1425                user = self.allocation[aid].get('user', None)
1426                self.allocation[aid]['experiment'] = ename
[f07fa49]1427                self.allocation[aid]['log'] = [ ]
1428                # Create a logger that logs to the experiment's state object as
1429                # well as to the main log file.
1430                alloc_log = logging.getLogger('fedd.access.%s' % ename)
1431                h = logging.StreamHandler(
1432                        list_log.list_log(self.allocation[aid]['log']))
1433                # XXX: there should be a global one of these rather than
1434                # repeating the code.
1435                h.setFormatter(logging.Formatter(
1436                    "%(asctime)s %(name)s %(message)s",
1437                            '%d %b %y %H:%M:%S'))
1438                alloc_log.addHandler(h)
[b770aa0]1439                self.write_state()
1440            self.state_lock.release()
1441
1442            if not proj:
1443                raise service_error(service_error.internal, 
1444                        "Can't find project for %s" %aid)
1445
1446            if not user:
1447                raise service_error(service_error.internal, 
1448                        "Can't find creation user for %s" %aid)
1449
[2761484]1450            self.export_store_info(certfile, proj, ename, connInfo)
1451            self.import_store_info(certfile, connInfo)
1452
[b770aa0]1453            expfile = "%s/experiment.tcl" % tmpdir
1454
1455            self.generate_portal_configs(topo, pubkey_base, 
[e02cd14]1456                    secretkey_base, tmpdir, master, proj, ename, connInfo, 
1457                    services)
[b770aa0]1458            self.generate_ns2(topo, expfile, 
[617592b]1459                    "/proj/%s/software/%s/" % (proj, ename), master, connInfo)
[f07fa49]1460
[b770aa0]1461            starter = self.start_segment(keyfile=self.ssh_privkey_file, 
[f07fa49]1462                    debug=self.create_debug, log=alloc_log)
[b770aa0]1463            rv = starter(self, ename, proj, user, expfile, tmpdir)
1464        except service_error, e:
1465            err = e
[e02cd14]1466        except e:
1467            err = service_error(service_error.internal, str(e))
[b770aa0]1468
[574055e]1469        # Walk up tmpdir, deleting as we go
[69692a9]1470        if self.cleanup:
1471            self.log.debug("[StartSegment]: removing %s" % tmpdir)
1472            for path, dirs, files in os.walk(tmpdir, topdown=False):
1473                for f in files:
1474                    os.remove(os.path.join(path, f))
1475                for d in dirs:
1476                    os.rmdir(os.path.join(path, d))
1477            os.rmdir(tmpdir)
1478        else:
1479            self.log.debug("[StartSegment]: not removing %s" % tmpdir)
[574055e]1480
[fd556d1]1481        if rv:
[f07fa49]1482            # Grab the log (this is some anal locking, but better safe than
1483            # sorry)
1484            self.state_lock.acquire()
1485            logv = "".join(self.allocation[aid]['log'])
1486            self.state_lock.release()
1487
1488            return { 'allocID': req['allocID'], 'allocationLog': logv }
[b770aa0]1489        elif err:
1490            raise service_error(service_error.federant,
1491                    "Swapin failed: %s" % err)
[fd556d1]1492        else:
1493            raise service_error(service_error.federant, "Swapin failed")
[5ae3857]1494
1495    def TerminateSegment(self, req, fid):
1496        try:
1497            req = req['TerminateSegmentRequestBody']
1498        except KeyError:
1499            raise service_error(server_error.req, "Badly formed request")
1500
1501        auth_attr = req['allocID']['fedid']
1502        aid = "%s" % auth_attr
1503        attrs = req.get('fedAttr', [])
1504        if not self.auth.check_attribute(fid, auth_attr):
1505            raise service_error(service_error.access, "Access denied")
1506
1507        self.state_lock.acquire()
1508        if self.allocation.has_key(aid):
1509            proj = self.allocation[aid].get('project', None)
1510            if not proj: 
1511                proj = self.allocation[aid].get('sproject', None)
1512            user = self.allocation[aid].get('user', None)
1513            ename = self.allocation[aid].get('experiment', None)
[1d913e13]1514        else:
1515            proj = None
1516            user = None
1517            ename = None
[5ae3857]1518        self.state_lock.release()
1519
1520        if not proj:
1521            raise service_error(service_error.internal, 
1522                    "Can't find project for %s" % aid)
1523
1524        if not user:
1525            raise service_error(service_error.internal, 
1526                    "Can't find creation user for %s" % aid)
1527        if not ename:
1528            raise service_error(service_error.internal, 
1529                    "Can't find experiment name for %s" % aid)
[fd556d1]1530        stopper = self.stop_segment(keyfile=self.ssh_privkey_file,
1531                debug=self.create_debug)
[5ae3857]1532        stopper(self, user, proj, ename)
1533        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.