source: fedd/federation/allocate_project.py @ 7a011e9

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since 7a011e9 was 03b9b14, checked in by Ted Faber <faber@…>, 16 years ago

2 changes:

Pass an interface parameter to fed-tun.pl to use different interfaces on the
differnet testbeds. This needs to be replaced with a node_attribute-based
solution.

Open /dev/null for writing when redirecting to it.

  • Property mode set to 100644
File size: 23.9 KB
RevLine 
[7da9da6]1#!/usr/local/bin/python
2
3import os,sys
4import re
5import random
6import string
7import subprocess
[c3dcf48]8import threading
9import pickle
[7da9da6]10import tempfile
11
[ec4fb42]12from util import *
[51cc9df]13from fedid import fedid
[9460b1e]14from remote_service import xmlrpc_handler, soap_handler, service_caller
[ec4fb42]15from service_error import service_error
[11a08b0]16import logging
17
18
[0ea11af]19# Configure loggers to dump to /dev/null which avoids errors if calling classes
20# don't configure them.
[11a08b0]21class nullHandler(logging.Handler):
22    def emit(self, record): pass
23
24fl = logging.getLogger("fedd.allocate.local")
25fl.addHandler(nullHandler())
26fl = logging.getLogger("fedd.allocate.remote")
27fl.addHandler(nullHandler())
[7da9da6]28
[4ed10ae]29
[ec4fb42]30class allocate_project_local:
[0ea11af]31    """
32    Allocate projects on this machine in response to an access request.
33    """
[93a06fb]34    dynamic_projects = 4
35    dynamic_keys= 2
36    confirm_keys = 1
37    none = 0
38
39    levels = {
40            'dynamic_projects': dynamic_projects,
41            'dynamic_keys': dynamic_keys,
42            'confirm_keys': confirm_keys,
43            'none': none,
44    }
45
[3f6bc5f]46    def __init__(self, config, auth=None):
[7da9da6]47        """
48        Initializer.  Parses a configuration if one is given.
49        """
50
[b312431b]51        self.debug = config.getboolean("allocate", "debug", False)
[05191a6]52        self.wap = config.get('allocate', 'wap', '/usr/testbed/sbin/wap')
53        self.newproj = config.get('allocate', 'newproj',
[4ed10ae]54                '/usr/testbed/sbin/newproj')
[05191a6]55        self.mkproj = config.get('allocate', 'mkproj', 
56                '/usr/testbed/sbin/mkproj')
57        self.rmproj = config.get('allocate', 'rmproj',
58                '/usr/testbed/sbin/rmproj')
[08329f4]59        self.rmuser = config.get('allocate', 'rmuser',
60                '/usr/testbed/sbin/rmuser')
61        self.newuser = config.get('allocate', 'newuser',
62                '/usr/testbed/sbin/newuser')
[05191a6]63        self.addpubkey = config.get('allocate', 'addpubkey', 
[b312431b]64                '/usr/testbed/sbin/addpubkey')
[05191a6]65        self.grantnodetype = config.get('allocate', 'grantnodetype', 
[4ed10ae]66                '/usr/testbed/sbin/grantnodetype')
[05191a6]67        self.confirmkey = config.get('allocate', 'confirmkey', 
[93a06fb]68                '/usr/testbed/sbin/taddpubkey')
[08329f4]69        self.user_to_project=config.get("allocate", 'user_to_project',
70                '/usr/local/bin/user_to_project.py')
[05191a6]71        self.allocation_level = config.get("allocate", "allocation_level", 
72                "none")
[11a08b0]73        self.log = logging.getLogger("fedd.allocate.local")
[05191a6]74        set_log_level(config, "allocate", self.log)
75
76        if auth:
77            self.auth = auth
78        else:
79            auth = authorizer()
80            log.warn("[allocate] No authorizer passed in, using local one")
[93a06fb]81
82        try:
83            self.allocation_level = \
84                    self.levels[self.allocation_level.strip().lower()]
85        except KeyError:
86            self.log.error("Bad allocation_level %s.  Defaulting to none" % \
87                    self.allocation_error)
88            self.allocation_level = self.none
89
[c3dcf48]90        self.state = { 
91                'keys': set(), 
92                'types': set(),
93                'projects': set(),
94                'users': set(),
95                }
96        self.state_filename = config.get('allocate', 'allocation_state')
97        self.state_lock = threading.Lock()
98        self.read_state()
99
[05191a6]100        access_db = config.get("allocate", "accessdb")
101        if access_db:
102            try:
103                read_simple_accessdb(access_db, self.auth, 'allocate')
104            except IOError, e:
105                raise service_error(service_error.internal,
106                        "Error reading accessDB %s: %s" % (access_db, e))
107            except ValueError:
108                raise service_error(service_error.internal, "%s" % e)
109
[93a06fb]110
[0ea11af]111        # Internal services are SOAP only
112        self.soap_services = {\
[f069052]113                "AllocateProject": soap_handler("AllocateProject", 
114                    self.dynamic_project),
115                "StaticProject": soap_handler("StaticProject", 
116                    self.static_project),
117                "ReleaseProject": soap_handler("ReleaseProject", 
118                    self.release_project),
[0ea11af]119                }
120        self.xmlrpc_services = { }
121
[c3dcf48]122    def read_state(self):
123        """
124        Read a new copy of access state.  Old state is overwritten.
125
126        State format is a simple pickling of the state dictionary.
127        """
128        if self.state_filename:
129            try:
130                f = open(self.state_filename, "r")
131                self.state = pickle.load(f)
132                self.log.debug("[allocation]: Read state from %s" % \
133                        self.state_filename)
134            except IOError, e:
135                self.log.warning(("[allocation]: No saved state: " +\
136                        "Can't open %s: %s") % (self.state_filename, e))
137            except EOFError, e:
138                self.log.warning(("[allocation]: " +\
139                        "Empty or damaged state file: %s:") % \
140                        self.state_filename)
141            except pickle.UnpicklingError, e:
142                self.log.warning(("[allocation]: No saved state: " + \
143                        "Unpickling failed: %s") % e)
144            # These should all be in the picked representation, but make sure
145            if not self.state.has_key('keys'): self.state['keys'] = set()
146            if not self.state.has_key('types'): self.state['types'] = set()
147            if not self.state.has_key('projects'):
148                self.state['projects'] = set()
149            if not self.state.has_key('users'): self.state['users'] = set()
150
151    def write_state(self):
152        if self.state_filename:
153            try:
154                f = open(self.state_filename, 'w')
155                pickle.dump(self.state, f)
156            except IOError, e:
157                self.log.error("Can't write file %s: %s" % \
158                        (self.state_filename, e))
159            except pickle.PicklingError, e:
160                self.log.error("Pickling problem: %s" % e)
161            except TypeError, e:
162                self.log.error("Pickling problem (TypeError): %s" % e)
163
164
[7da9da6]165    def random_string(self, s, n=3):
166        """Append n random ASCII characters to s and return the string"""
167        rv = s
168        for i in range(0,n):
169            rv += random.choice(string.ascii_letters)
170        return rv
171
172    def write_attr_xml(self, file, root, lines):
173        """
174        Write an emulab config file for a dynamic project.
175
176        Format is <root><attribute name=lines[0]>lines[1]</attribute></root>
177        """
178        # Convert a pair to an attribute line
179        out_attr = lambda a,v : \
180                '<attribute name="%s"><value>%s</value></attribute>' % (a, v)
181
182        f = os.fdopen(file, "w")
183        f.write("<%s>\n" % root)
184        f.write("\n".join([out_attr(*l) for l in lines]))
185        f.write("</%s>\n" % root)
186        f.close()
187
[c3dcf48]188    def run_cmd(self, cmd, log_prefix='allocate'):
189        """
190        Run the command passed in.  Cmd is a list containing the words of the
191        command.  Return the exit value from the subprocess - that is 0 on
192        success.  On an error running the command -  python or OS error, raise
193        a service exception.
194        """
[03b9b14]195        try:
196            dnull = open("/dev/null", "w")
197        except IOError:
198            self.log.warn("[run_cmd]: failed to open /dev/null for redirect")
199            dnull = None
200
[c3dcf48]201        self.log.debug("[%s]: %s" % (log_prefix, ' '.join(cmd)))
202        if not self.debug:
203            try:
[03b9b14]204                return subprocess.call(cmd, stdout=dnull, stderr=dnull)
[c3dcf48]205            except OSError, e:
206                raise service_error(service_error.internal,
207                        "Static project subprocess creation error "+ \
208                                "[%s] (%s)" %  (cmd[0], e.strerror))
209        else:
210            return 0
211
212    def confirm_key(self, user, key):
213        """
214        Call run_cmd to comfirm the key.  Return a boolean rather
215        than the subprocess code.
216        """
217        return self.run_cmd((self.wap, self.confirmkey, '-C', 
218            '-u', user, '-k', key)) ==0
219
220    def add_key(self, user, key):
221        """
222        Call run_cmd to add the key.  Return a boolean rather
223        than the subprocess code.
224        """
225        return self.run_cmd((self.wap, self.addpubkey, '-u', user,
226            '-k', key)) == 0
227
228    def remove_key(self, user, key):
229        """
230        Call run_cmd to remove the key.  Return a boolean rather
231        than the subprocess code.
232        """
233        return self.run_cmd((self.wap, self.addpubkey, '-R', '-u', user, 
234            '-k', key)) == 0
235
236    def confirm_access(self, project, type):
237        """
238        Call run_cmd to comfirm the key.  Return a boolean rather
239        than the subprocess code.
240        """
241        return self.run_cmd((self.wap, self.grantnodetype, '-C', 
242            '-p', project, type)) ==0
243
244    def add_access(self, project, type):
245        """
246        Call run_cmd to add the key.  Return a boolean rather
247        than the subprocess code.
248        """
249        return self.run_cmd((self.wap, self.grantnodetype, 
250            '-p', project, type)) == 0
251
252    def remove_access(self, project, type):
253        """
254        Call run_cmd to remove the key.  Return a boolean rather
255        than the subprocess code.
256        """
257
258        return self.run_cmd((self.wap, self.grantnodetype, '-R', 
259            '-p', project, type)) == 0
260
261    def add_project(self, project, projfile):
262        """
263        Create a project using run_cmd.  This is two steps, and assumes that
264        the relevant XML files are in place and correct.  Make the return value
265        boolean.  Note that if a new user is specified in the XML, that user is
266        created on success.
267        """
268
269        if self.run_cmd((self.wap, self.newproj, projfile)) == 0:
270            return self.run_cmd((self.wap, self.mkproj, project)) ==0
271        else:
272            return False
273
274    def remove_project(self, project):
275        """
276        Call run_cmd to remove the project.  Make the return value boolean.
277        """
278
279        return self.run_cmd(self.wap, self.rmproj, project) == 0
280
281   
282    def add_user(self, name, param_file, project):
283        """
284        Create a user and link them to the given project.  Similar to
285        add_project, this requires a two step approach.  Returns True on success
286        False on failure.
287        """
288
289        if self.run_cmd((self.wap, self.newuser, param_file)) == 0:
290           return self.run_cmd((self.wap, self.user_to_project,
291               user, project)) == 0
292        else:
293           return False
294
295    def remove_user(self, user):
296        """
297        Call run_cmd to remove the user.  Make the return value boolean.
298        """
299
300        return self.run_cmd(self.wap, self.rmuser, user) == 0
301
302
[7da9da6]303
[ef36c1e]304    def dynamic_project(self, req, fedid=None):
[7da9da6]305        """
306        Create a dynamic project with ssh access
307
308        Req includes the project and resources as a dictionary
309        """
[93a06fb]310
[05191a6]311        # Internal calls do not have a fedid parameter (i.e., local calls on
312        # behalf of already vetted fedids)
313        if fedid and not self.auth.check_attribute(fedid, "allocate"):
314            self.log.debug("[allocate] Access denied (%s)" % fedid)
315            raise service_error(service_error.access, "Access Denied")
316
[93a06fb]317        if self.allocation_level < self.dynamic_projects:
318            raise service_error(service_error.access, 
319                    "[dynamic_project] dynamic project allocation not " + \
320                            "permitted: check allocation level")
[7da9da6]321        # tempfiles for the parameter files
[08329f4]322        cuf, create_userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
323                dir="/tmp")
324        suf, service_userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
[7da9da6]325                dir="/tmp")
326        pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
327                dir="/tmp")
328
[1b376ca]329        if req.has_key('AllocateProjectRequestBody'):
330            proj = req['AllocateProjectRequestBody'].get('project', None)
331            if not proj:
332                raise service_error(service_error.req, 
333                        "Badly formed allocation request")
334            resources = req['AllocateProjectRequestBody'].get('resources', { })
[ef36c1e]335        else:
336            raise service_error(service_error.req, 
337                    "Badly formed allocation request")
[7da9da6]338        # Take the first user and ssh key
339        name = proj.get('name', None) or self.random_string("proj",4)
[08329f4]340        user = proj.get('user', [])
341
342        uname = { }
343        ssh = { }
344        for u in user:
345            role = u.get('role', None)
346            if not role: continue
347            if u.has_key('userID'):
348                uid = u['userID']
349                uname[role] = uid.get('localname', None) or \
[7da9da6]350                        uid.get('kerberosUsername', None) or \
351                        uid.get('uri', None)
[08329f4]352                if uname[role] == None:
353                    raise service_error(service_error.req, "No ID for user")
354            else:
355                uname[role] = self.random_string("user", 3)
356
357            access = u.get('access', None)
358            if access:
359                # XXX collect and call addpubkey later, for now use first one.
360                for a in access:
361                    ssh[role] = a.get('sshPubkey', None)
362                    if ssh: break
363                else:
364                    raise service_error(service_error.req,
365                            "No SSH key for user %s" % uname[role])
366            else:
367                raise service_error(service_error.req,
368                        "No access mechanisms for for user %s" % uname[role])
369
370        if not (uname.has_key('experimentCreation') and \
371                uname.has_key('serviceAccess')):
372            raise service_error(service_error.req,
373                    "Must specify both user roles")
374       
375
376        create_user_fields = [
377                ("name", "Federation User %s" % uname['experimentCreation']),
378                ("email", "%s-fed@isi.deterlab.net" % \
379                        uname['experimentCreation']),
380                ("password", self.random_string("", 8)),
381                ("login", uname['experimentCreation']),
382                ("address", "4676 Admiralty"),
383                ("city", "Marina del Rey"),
384                ("state", "CA"),
385                ("zip", "90292"),
386                ("country", "USA"),
387                ("phone", "310-448-9190"),
388                ("title", "None"),
389                ("affiliation", "USC/ISI"),
390                ("pubkey", ssh['experimentCreation'])
391        ]
[7da9da6]392
[08329f4]393        service_user_fields = [
394                ("name", "Federation User %s" % uname['serviceAccess']),
395                ("email", "%s-fed@isi.deterlab.net" % uname['serviceAccess']),
[7da9da6]396                ("password", self.random_string("", 8)),
[08329f4]397                ("login", uname['serviceAccess']),
[7da9da6]398                ("address", "4676 Admiralty"),
399                ("city", "Marina del Rey"),
400                ("state", "CA"),
401                ("zip", "90292"),
402                ("country", "USA"),
403                ("phone", "310-448-9190"),
404                ("title", "None"),
405                ("affiliation", "USC/ISI"),
[08329f4]406                ("pubkey", ssh['serviceAccess'])
[7da9da6]407        ]
408
409        proj_fields = [
410                ("name", name),
411                ("short description", "dynamic federated project"),
412                ("URL", "http://www.isi.edu/~faber"),
413                ("funders", "USC/USU"),
414                ("long description", "Federation access control"),
415                ("public", "1"),
416                ("num_pcs", "100"),
417                ("linkedtous", "1"),
[08329f4]418                ("newuser_xml", create_userfile)
[7da9da6]419        ]
420       
421
[1b376ca]422
[c3dcf48]423        added_projects = [ ]
424        added_users = [ ]
425        added_types = [ ]
[7da9da6]426
[c3dcf48]427        self.state_lock.acquire()
428        try:
429            # Write out the files
430            self.write_attr_xml(cuf, "user", create_user_fields)
431            self.write_attr_xml(suf, "user", service_user_fields)
432            self.write_attr_xml(pf, "project", proj_fields)
433            try:
434                if self.add_project(name, projfile):
435                    # add_project adds a user as well in this case
436                    added_projects.append(name)
437                    added_users.append(uname['createExperiment'])
438                    self.state['projects'].add(name)
439                    self.state['users'].add(uname['createExperiment'])
440
441                    if self.add_user(uname['serviceAccess'], 
442                            service_userfile, name):
443                        added_users.append(uname['serviceAccess'])
444                        self.state['users'].add(uname['serviceAccess'])
445                    else:
446                        raise service_error("Unable to create user %s" % \
447                                uname['serviceAccess'])
448                else:
449                    raise service_error("Unable to create project/user %s/%s" % \
450                            (name, uname['experimentCreation']))
451
452                nodes = resources.get('node', [])
453                # Grant access to restricted resources.  This is simpler than
454                # the corresponding loop from static_project because this is a
455                # clean slate.
456                for nt in [ h for n in nodes\
457                        if n.has_key('hardware')\
458                            for h in n['hardware'] ] :
459                    if self.add_access(name, nt):
460                        self.state['types'].add((name, nt))
461                        added_types.append((name, nt))
462                    else:
463                        raise service_error(service_error.internal,
464                                "Failed to add access for %s to %s"\
465                                        % (name, nt))
466            except service_error, e:
467                # Something failed.  Back out the partial allocation as
468                # completely as possible and re-raise the error.
469                for p, t in added_types:
470                    self.state['types'].discard((p, t))
471                    try:
472                        self.remove_access(p, t)
473                    except service_error:
474                        pass
475                for u in added_users:
476                    self.state['users'].discard(u)
477                    try:
478                        self.remove_user(u)
479                    except service_error:
480                        pass
481
482                for p in added_projects:
483                    self.state['projects'].discard(p)
484                    try:
485                        self.remove_project(p)
486                    except service_error:
487                        pass
488                self.state_lock.release()
489                raise e
490        finally:
491            # Clean up tempfiles
492            os.unlink(create_userfile)
493            os.unlink(service_userfile)
494            os.unlink(projfile)
[7da9da6]495
496        rv = {\
497            'project': {\
[e40c7ee]498                'name': { 'localname': name }, 
[08329f4]499                'user' : [\
500                    {\
501                        'userID': { 'localname' : uname['experimentCreation'] },
502                        'access': [ {'sshPubkey': ssh['experimentCreation'] } ],
503                        'role': 'experimentCreation',
504                    }, \
505                    {\
506                        'userID': { 'localname' : uname['serviceAccess'] },
507                        'access': [ { 'sshPubkey' : ssh['serviceAccess'] } ], 
508                        'role': 'serviceAccess',
509                    } \
510                ]\
[7da9da6]511            }\
512        }
513        return rv
[ef36c1e]514
[4ed10ae]515    def static_project(self, req, fedid=None):
[ef36c1e]516        """
[4ed10ae]517        Be certain that the local project in the request has access to the
518        proper resources and users have correct keys.  Add them if necessary.
[ef36c1e]519        """
[05191a6]520        # Internal calls do not have a fedid parameter (i.e., local calls on
521        # behalf of already vetted fedids)
522        if fedid and not self.auth.check_attribute(fedid, "allocate"):
523            self.log.debug("[allocate] Access denied (%s)" % fedid)
524            raise service_error(service_error.access, "Access Denied")
[4ed10ae]525        # While we should be more careful about this, for the short term, add
526        # the keys to the specified users.
527
528        try:
529            users = req['StaticProjectRequestBody']['project']['user']
530            pname = req['StaticProjectRequestBody']['project']\
531                    ['name']['localname']
[1b376ca]532            resources = req['StaticProjectRequestBody'].get('resources', { })
[4ed10ae]533        except KeyError:
534            raise service_error(service_error.req, "Badly formed request")
535
[c3dcf48]536        added_keys = [ ]
537        added_types = [ ]
538        # Keep track of changes made to the system
539        self.state_lock.acquire()
[4ed10ae]540
[c3dcf48]541        try:
542            for u in users:
543                try:
544                    name = u['userID']['localname']
545                except KeyError:
546                    raise service_error(service_error.req, "Badly formed user")
547                for sk in [ k['sshPubkey'] for k in u.get('access', []) \
548                        if k.has_key('sshPubkey')]:
549                    if self.allocation_level >=self.confirm_keys:
550                        key_ok = self.confirm_key(name, sk)
551                        if not key_ok:
552                            if self.allocation_level >= self.dynamic_keys:
553                                if self.add_key(name, sk):
554                                    self.state['keys'].add((name, sk))
555                                    added_keys.append((name, sk))
556                                else:
557                                    raise service_error(service_error.internal,
558                                            "Failed to add key for %s" % name)
559                            else:
560                                raise service_error(service_error.internal,
561                                        "Failed to confirm key for %s" % name)
562                    else:
563                        self.log.warning("[static_project] no checking of " + \
[93a06fb]564                            "static keys")
[4ed10ae]565
[c3dcf48]566            # Grant access to any resources in the request.  The
567            # list comprehension pulls out the hardware types in the node
568            # entries in the resources list.  The access module knows to
569            # only send resources that are restricted and needed by the
570            # project.
571            nodes = resources.get('node', [])
572            for nt in [ h for n in nodes\
573                    if n.has_key('hardware')\
574                        for h in n['hardware'] ] :
[1b376ca]575                if self.allocation_level >= self.confirm_keys:
[c3dcf48]576                    access_ok = self.confirm_access(pname, nt)
577                    if not access_ok:
578                        if self.allocation_level >= self.dynamic_keys:
579                            if self.add_access(pname, nt):
580                                self.state['types'].add((pname, nt))
581                                added_types.append((pname, nt))
582                            else:
583                                raise service_error(service_error.internal,
584                                        "Failed to add access for %s to %s"\
585                                                % (pname, nt))
586                        else:
587                            raise service_error(service_error.internal,
588                                    "Failed to confirm access for %s to %s"\
589                                            % (pname, nt))
590                else:
591                    self.log.warning("[static_project] no checking of " + \
592                        "node access")
593        except service_error, e:
594            # Do our best to clean up partial allocation and reraise the
595            # error.  Do our best to make sure that both allocation state and
596            # testbed state is restored.
597            for u, k in added_keys:
598                self.state['keys'].discard((u, k))
[4ed10ae]599                try:
[c3dcf48]600                    self.remove_key(u, k)
601                except service_error:
602                    pass
603            for p, t in added_types:
604                self.state['types'].discard((p, t))
605                try:
606                    self.remove_access(p, t)
607                except service_error:
608                    pass
609            self.state_lock.release()
610            raise e
611        # All is well, save state and release the lock
612        self.write_state()
613        self.state_lock.release()
614        # return { 'project': req['StaticProjectRequestBody']['project']}
615        return req['StaticProjectRequestBody']
[4ed10ae]616
[7583a62]617    def release_project(self, req, fedid=None):
618        """
619        Remove user keys from users and delete dynamic projects.
620
[c3d5d53]621        Only keys this service created are deleted and there are
[7583a62]622        similar protections for projects.
623        """
[05191a6]624        # Internal calls do not have a fedid parameter (i.e., local calls on
625        # behalf of already vetted fedids)
626        if fedid and not self.auth.check_attribute(fedid, "allocate"):
627            self.log.debug("[allocate] Access denied (%s)" % fedid)
628            raise service_error(service_error.access, "Access Denied")
[7583a62]629
630        pname = None
631        users = []
[c3dcf48]632        nodes = [ ]
633
[7583a62]634        try:
635            if req['ReleaseProjectRequestBody']['project'].has_key('name'):
636                pname = req['ReleaseProjectRequestBody']['project']\
637                        ['name']['localname']
638            if req['ReleaseProjectRequestBody']['project'].has_key('user'):
639                users = req['ReleaseProjectRequestBody']['project']['user']
[c3dcf48]640            if req['ReleaseProjectRequestBody'].has_key('resources'):
641                nodes = req['ReleaseProjectRequestBody']\
642                        ['resources'].get('node', [])
[7583a62]643        except KeyError:
644            raise service_error(service_error.req, "Badly formed request")
645
[c3dcf48]646        if nodes and not pname:
647            raise service_error(service_error.req, 
648                    "Badly formed request (nodes without project)")
[08329f4]649
[c3dcf48]650        self.state_lock.acquire()
651        try:
652            for nt in [ h for n in nodes if n.has_key('hardware')\
653                    for h in n['hardware'] ] :
654                if (pname, nt ) in self.state['types']:
655                    self.remove_access(pname, nt)
656                    self.state['types'].discard((pname, nt))
[7583a62]657
[c3dcf48]658            for u in users:
659                try:
660                    name = u['userID']['localname']
661                except KeyError:
662                    raise service_error(service_error.req, "Badly formed user")
663                if name in self.state['users']:
664                    # If we created this user, discard the user, keys and all
665                    self.remove_user(name)
666                    self.state['users'].discard(name)
667                else:
668                    # If not, just strip any keys we added
669                    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
670                            if k.has_key('sshPubkey')]:
671                        if (name, sk) in self.state['keys']:
672                            self.remove_key(name, sk)
673                            self.state['keys'].discard((name, sk))
674            if pname in self.state['projects']:
675                self.remove_project(pname)
676                self.state['projects'].discard(pname)
677
678        except service_error, e:
679            self.write_state()
680            self.state_lock.release()
681            raise e
682        self.write_state()
683        self.state_lock.release()
[7583a62]684
685        return { 'project': req['ReleaseProjectRequestBody']['project']}
686
[ec4fb42]687class allocate_project_remote:
[4ed10ae]688    """
[058f58e]689    Allocate projects on a remote machine using the internal SOAP interface
[4ed10ae]690    """
[058f58e]691    class proxy(service_caller):
[ef36c1e]692        """
[058f58e]693        This class is a proxy functor (callable) that has the same signature as
694        a function called by soap_handler or xmlrpc_handler, but that used the
695        service_caller class to call the function remotely.
[ef36c1e]696        """
[4ed10ae]697
[05191a6]698        def __init__(self, url, cert_file, cert_pwd, trusted_certs, auth, 
699                method):
[f069052]700            service_caller.__init__(self, method)
[058f58e]701            self.url = url
702            self.cert_file = cert_file
703            self.cert_pwd = cert_pwd
704            self.trusted_certs = trusted_certs
[05191a6]705            self.request_body__name = "%sRequestBody" % method
706            self.resp_name = "%sResponseBody" % method
707            self.auth = auth
[058f58e]708            # Calling the proxy object directly invokes the proxy_call method,
709            # not the service_call method.
710            self.__call__ = self.proxy_call
711           
712
713        # Define the proxy, NB, the parameters to make_proxy are visible to the
714        # definition of proxy.
[05191a6]715        def proxy_call(self, req, fid=None):
[058f58e]716            """
717            Send req on to a remote project instantiator.
718
719            Req is just the message to be sent.  This function re-wraps it.
720            It also rethrows any faults.
721            """
722
723            if req.has_key(self.request_body_name):
724                req = req[self.request_body_name]
725            else:
726                raise service_error(service_error.req, "Bad formated request");
[ef36c1e]727
[9d3e646]728            try:
729                r = self.call_service(self.url, req, self.cert_file,
730                        self.cert_pwd, self.trusted_certs)
731            except service_error, e:
732                if e.code == service_error.connect:
733                    raise service_error(service_error.internal, 
734                            "Cannot connect to internal service: (%d) %s" % \
735                                    (e.code, e.desc))
736                else: raise
[058f58e]737            if r.has_key(self.resp_name):
738                return r[self.resp_name]
739            else:
740                raise service_error(service_error.protocol, 
741                        "Bad proxy response")
[4ed10ae]742
[ec4fb42]743    # back to defining the allocate_project_remote class
[3f6bc5f]744    def __init__(self, config, auth=None):
[4ed10ae]745        """
746        Initializer.  Parses a configuration if one is given.
747        """
748
[05191a6]749        self.debug = config.get("allocate", "debug", False)
750        self.url = config.get("allocate", "uri", "")
[4ed10ae]751
[c3d5d53]752        # Keep cert file and password coming from the same source
[05191a6]753        self.cert_file = config.get("allocate", "cert_file", None)
[c3d5d53]754        if self.cert_file:
755            self.cert_pwd = config.get("allocate", "cert_pwd", None)
756        else:
757            self.cert_file = config.get("globals", "cert_file", None)
758            self.cert_pwd = config.get("globals", "cert_pwd", None)
759
760        self.trusted_certs = config.get("allocate", "trusted_certs", None) or \
761                config.get("globals", "trusted_certs")
[4ed10ae]762
763        self.soap_services = { }
764        self.xmlrpc_services = { }
765        self.log = logging.getLogger("fedd.allocate.remote")
[05191a6]766        set_log_level(config, "allocate", self.log)
767
768        if auth:
769            self.auth = auth
770        else:
771            auth = authorizer()
772            log.warn("[allocate] No authorizer passed in, using local one")
[93a06fb]773
[058f58e]774        # The specializations of the proxy functions
775        self.dynamic_project = self.proxy(self.url, self.cert_file, 
[05191a6]776                self.cert_pwd, self.trusted_certs, self.auth, 
777                "AllocateProject")
[058f58e]778        self.static_project = self.proxy(self.url, self.cert_file, 
[05191a6]779                self.cert_pwd, self.trusted_certs, self.auth, 
780                "StaticProject")
[058f58e]781        self.release_project = self.proxy(self.url, self.cert_file,
[05191a6]782                self.cert_pwd, self.trusted_certs, self.auth, 
783                "ReleaseProject")
[058f58e]784
Note: See TracBrowser for help on using the repository browser.