source: fedd/federation/allocate_project.py @ c3d5d53

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

Cleanup. Remove fixed stuff from allocation.

  • Property mode set to 100644
File size: 23.7 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        """
195        self.log.debug("[%s]: %s" % (log_prefix, ' '.join(cmd)))
196        if not self.debug:
197            try:
198                return subprocess.call(cmd)
199            except OSError, e:
200                raise service_error(service_error.internal,
201                        "Static project subprocess creation error "+ \
202                                "[%s] (%s)" %  (cmd[0], e.strerror))
203        else:
204            return 0
205
206    def confirm_key(self, user, key):
207        """
208        Call run_cmd to comfirm the key.  Return a boolean rather
209        than the subprocess code.
210        """
211        return self.run_cmd((self.wap, self.confirmkey, '-C', 
212            '-u', user, '-k', key)) ==0
213
214    def add_key(self, user, key):
215        """
216        Call run_cmd to add the key.  Return a boolean rather
217        than the subprocess code.
218        """
219        return self.run_cmd((self.wap, self.addpubkey, '-u', user,
220            '-k', key)) == 0
221
222    def remove_key(self, user, key):
223        """
224        Call run_cmd to remove the key.  Return a boolean rather
225        than the subprocess code.
226        """
227        return self.run_cmd((self.wap, self.addpubkey, '-R', '-u', user, 
228            '-k', key)) == 0
229
230    def confirm_access(self, project, type):
231        """
232        Call run_cmd to comfirm the key.  Return a boolean rather
233        than the subprocess code.
234        """
235        return self.run_cmd((self.wap, self.grantnodetype, '-C', 
236            '-p', project, type)) ==0
237
238    def add_access(self, project, type):
239        """
240        Call run_cmd to add the key.  Return a boolean rather
241        than the subprocess code.
242        """
243        return self.run_cmd((self.wap, self.grantnodetype, 
244            '-p', project, type)) == 0
245
246    def remove_access(self, project, type):
247        """
248        Call run_cmd to remove the key.  Return a boolean rather
249        than the subprocess code.
250        """
251
252        return self.run_cmd((self.wap, self.grantnodetype, '-R', 
253            '-p', project, type)) == 0
254
255    def add_project(self, project, projfile):
256        """
257        Create a project using run_cmd.  This is two steps, and assumes that
258        the relevant XML files are in place and correct.  Make the return value
259        boolean.  Note that if a new user is specified in the XML, that user is
260        created on success.
261        """
262
263        if self.run_cmd((self.wap, self.newproj, projfile)) == 0:
264            return self.run_cmd((self.wap, self.mkproj, project)) ==0
265        else:
266            return False
267
268    def remove_project(self, project):
269        """
270        Call run_cmd to remove the project.  Make the return value boolean.
271        """
272
273        return self.run_cmd(self.wap, self.rmproj, project) == 0
274
275   
276    def add_user(self, name, param_file, project):
277        """
278        Create a user and link them to the given project.  Similar to
279        add_project, this requires a two step approach.  Returns True on success
280        False on failure.
281        """
282
283        if self.run_cmd((self.wap, self.newuser, param_file)) == 0:
284           return self.run_cmd((self.wap, self.user_to_project,
285               user, project)) == 0
286        else:
287           return False
288
289    def remove_user(self, user):
290        """
291        Call run_cmd to remove the user.  Make the return value boolean.
292        """
293
294        return self.run_cmd(self.wap, self.rmuser, user) == 0
295
296
[7da9da6]297
[ef36c1e]298    def dynamic_project(self, req, fedid=None):
[7da9da6]299        """
300        Create a dynamic project with ssh access
301
302        Req includes the project and resources as a dictionary
303        """
[93a06fb]304
[05191a6]305        # Internal calls do not have a fedid parameter (i.e., local calls on
306        # behalf of already vetted fedids)
307        if fedid and not self.auth.check_attribute(fedid, "allocate"):
308            self.log.debug("[allocate] Access denied (%s)" % fedid)
309            raise service_error(service_error.access, "Access Denied")
310
[93a06fb]311        if self.allocation_level < self.dynamic_projects:
312            raise service_error(service_error.access, 
313                    "[dynamic_project] dynamic project allocation not " + \
314                            "permitted: check allocation level")
[7da9da6]315        # tempfiles for the parameter files
[08329f4]316        cuf, create_userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
317                dir="/tmp")
318        suf, service_userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
[7da9da6]319                dir="/tmp")
320        pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
321                dir="/tmp")
322
[1b376ca]323        if req.has_key('AllocateProjectRequestBody'):
324            proj = req['AllocateProjectRequestBody'].get('project', None)
325            if not proj:
326                raise service_error(service_error.req, 
327                        "Badly formed allocation request")
328            resources = req['AllocateProjectRequestBody'].get('resources', { })
[ef36c1e]329        else:
330            raise service_error(service_error.req, 
331                    "Badly formed allocation request")
[7da9da6]332        # Take the first user and ssh key
333        name = proj.get('name', None) or self.random_string("proj",4)
[08329f4]334        user = proj.get('user', [])
335
336        uname = { }
337        ssh = { }
338        for u in user:
339            role = u.get('role', None)
340            if not role: continue
341            if u.has_key('userID'):
342                uid = u['userID']
343                uname[role] = uid.get('localname', None) or \
[7da9da6]344                        uid.get('kerberosUsername', None) or \
345                        uid.get('uri', None)
[08329f4]346                if uname[role] == None:
347                    raise service_error(service_error.req, "No ID for user")
348            else:
349                uname[role] = self.random_string("user", 3)
350
351            access = u.get('access', None)
352            if access:
353                # XXX collect and call addpubkey later, for now use first one.
354                for a in access:
355                    ssh[role] = a.get('sshPubkey', None)
356                    if ssh: break
357                else:
358                    raise service_error(service_error.req,
359                            "No SSH key for user %s" % uname[role])
360            else:
361                raise service_error(service_error.req,
362                        "No access mechanisms for for user %s" % uname[role])
363
364        if not (uname.has_key('experimentCreation') and \
365                uname.has_key('serviceAccess')):
366            raise service_error(service_error.req,
367                    "Must specify both user roles")
368       
369
370        create_user_fields = [
371                ("name", "Federation User %s" % uname['experimentCreation']),
372                ("email", "%s-fed@isi.deterlab.net" % \
373                        uname['experimentCreation']),
374                ("password", self.random_string("", 8)),
375                ("login", uname['experimentCreation']),
376                ("address", "4676 Admiralty"),
377                ("city", "Marina del Rey"),
378                ("state", "CA"),
379                ("zip", "90292"),
380                ("country", "USA"),
381                ("phone", "310-448-9190"),
382                ("title", "None"),
383                ("affiliation", "USC/ISI"),
384                ("pubkey", ssh['experimentCreation'])
385        ]
[7da9da6]386
[08329f4]387        service_user_fields = [
388                ("name", "Federation User %s" % uname['serviceAccess']),
389                ("email", "%s-fed@isi.deterlab.net" % uname['serviceAccess']),
[7da9da6]390                ("password", self.random_string("", 8)),
[08329f4]391                ("login", uname['serviceAccess']),
[7da9da6]392                ("address", "4676 Admiralty"),
393                ("city", "Marina del Rey"),
394                ("state", "CA"),
395                ("zip", "90292"),
396                ("country", "USA"),
397                ("phone", "310-448-9190"),
398                ("title", "None"),
399                ("affiliation", "USC/ISI"),
[08329f4]400                ("pubkey", ssh['serviceAccess'])
[7da9da6]401        ]
402
403        proj_fields = [
404                ("name", name),
405                ("short description", "dynamic federated project"),
406                ("URL", "http://www.isi.edu/~faber"),
407                ("funders", "USC/USU"),
408                ("long description", "Federation access control"),
409                ("public", "1"),
410                ("num_pcs", "100"),
411                ("linkedtous", "1"),
[08329f4]412                ("newuser_xml", create_userfile)
[7da9da6]413        ]
414       
415
[1b376ca]416
[c3dcf48]417        added_projects = [ ]
418        added_users = [ ]
419        added_types = [ ]
[7da9da6]420
[c3dcf48]421        self.state_lock.acquire()
422        try:
423            # Write out the files
424            self.write_attr_xml(cuf, "user", create_user_fields)
425            self.write_attr_xml(suf, "user", service_user_fields)
426            self.write_attr_xml(pf, "project", proj_fields)
427            try:
428                if self.add_project(name, projfile):
429                    # add_project adds a user as well in this case
430                    added_projects.append(name)
431                    added_users.append(uname['createExperiment'])
432                    self.state['projects'].add(name)
433                    self.state['users'].add(uname['createExperiment'])
434
435                    if self.add_user(uname['serviceAccess'], 
436                            service_userfile, name):
437                        added_users.append(uname['serviceAccess'])
438                        self.state['users'].add(uname['serviceAccess'])
439                    else:
440                        raise service_error("Unable to create user %s" % \
441                                uname['serviceAccess'])
442                else:
443                    raise service_error("Unable to create project/user %s/%s" % \
444                            (name, uname['experimentCreation']))
445
446                nodes = resources.get('node', [])
447                # Grant access to restricted resources.  This is simpler than
448                # the corresponding loop from static_project because this is a
449                # clean slate.
450                for nt in [ h for n in nodes\
451                        if n.has_key('hardware')\
452                            for h in n['hardware'] ] :
453                    if self.add_access(name, nt):
454                        self.state['types'].add((name, nt))
455                        added_types.append((name, nt))
456                    else:
457                        raise service_error(service_error.internal,
458                                "Failed to add access for %s to %s"\
459                                        % (name, nt))
460            except service_error, e:
461                # Something failed.  Back out the partial allocation as
462                # completely as possible and re-raise the error.
463                for p, t in added_types:
464                    self.state['types'].discard((p, t))
465                    try:
466                        self.remove_access(p, t)
467                    except service_error:
468                        pass
469                for u in added_users:
470                    self.state['users'].discard(u)
471                    try:
472                        self.remove_user(u)
473                    except service_error:
474                        pass
475
476                for p in added_projects:
477                    self.state['projects'].discard(p)
478                    try:
479                        self.remove_project(p)
480                    except service_error:
481                        pass
482                self.state_lock.release()
483                raise e
484        finally:
485            # Clean up tempfiles
486            os.unlink(create_userfile)
487            os.unlink(service_userfile)
488            os.unlink(projfile)
[7da9da6]489
490        rv = {\
491            'project': {\
[e40c7ee]492                'name': { 'localname': name }, 
[08329f4]493                'user' : [\
494                    {\
495                        'userID': { 'localname' : uname['experimentCreation'] },
496                        'access': [ {'sshPubkey': ssh['experimentCreation'] } ],
497                        'role': 'experimentCreation',
498                    }, \
499                    {\
500                        'userID': { 'localname' : uname['serviceAccess'] },
501                        'access': [ { 'sshPubkey' : ssh['serviceAccess'] } ], 
502                        'role': 'serviceAccess',
503                    } \
504                ]\
[7da9da6]505            }\
506        }
507        return rv
[ef36c1e]508
[4ed10ae]509    def static_project(self, req, fedid=None):
[ef36c1e]510        """
[4ed10ae]511        Be certain that the local project in the request has access to the
512        proper resources and users have correct keys.  Add them if necessary.
[ef36c1e]513        """
[05191a6]514        # Internal calls do not have a fedid parameter (i.e., local calls on
515        # behalf of already vetted fedids)
516        if fedid and not self.auth.check_attribute(fedid, "allocate"):
517            self.log.debug("[allocate] Access denied (%s)" % fedid)
518            raise service_error(service_error.access, "Access Denied")
[4ed10ae]519        # While we should be more careful about this, for the short term, add
520        # the keys to the specified users.
521
522        try:
523            users = req['StaticProjectRequestBody']['project']['user']
524            pname = req['StaticProjectRequestBody']['project']\
525                    ['name']['localname']
[1b376ca]526            resources = req['StaticProjectRequestBody'].get('resources', { })
[4ed10ae]527        except KeyError:
528            raise service_error(service_error.req, "Badly formed request")
529
[c3dcf48]530        added_keys = [ ]
531        added_types = [ ]
532        # Keep track of changes made to the system
533        self.state_lock.acquire()
[4ed10ae]534
[c3dcf48]535        try:
536            for u in users:
537                try:
538                    name = u['userID']['localname']
539                except KeyError:
540                    raise service_error(service_error.req, "Badly formed user")
541                for sk in [ k['sshPubkey'] for k in u.get('access', []) \
542                        if k.has_key('sshPubkey')]:
543                    if self.allocation_level >=self.confirm_keys:
544                        key_ok = self.confirm_key(name, sk)
545                        if not key_ok:
546                            if self.allocation_level >= self.dynamic_keys:
547                                if self.add_key(name, sk):
548                                    self.state['keys'].add((name, sk))
549                                    added_keys.append((name, sk))
550                                else:
551                                    raise service_error(service_error.internal,
552                                            "Failed to add key for %s" % name)
553                            else:
554                                raise service_error(service_error.internal,
555                                        "Failed to confirm key for %s" % name)
556                    else:
557                        self.log.warning("[static_project] no checking of " + \
[93a06fb]558                            "static keys")
[4ed10ae]559
[c3dcf48]560            # Grant access to any resources in the request.  The
561            # list comprehension pulls out the hardware types in the node
562            # entries in the resources list.  The access module knows to
563            # only send resources that are restricted and needed by the
564            # project.
565            nodes = resources.get('node', [])
566            for nt in [ h for n in nodes\
567                    if n.has_key('hardware')\
568                        for h in n['hardware'] ] :
[1b376ca]569                if self.allocation_level >= self.confirm_keys:
[c3dcf48]570                    access_ok = self.confirm_access(pname, nt)
571                    if not access_ok:
572                        if self.allocation_level >= self.dynamic_keys:
573                            if self.add_access(pname, nt):
574                                self.state['types'].add((pname, nt))
575                                added_types.append((pname, nt))
576                            else:
577                                raise service_error(service_error.internal,
578                                        "Failed to add access for %s to %s"\
579                                                % (pname, nt))
580                        else:
581                            raise service_error(service_error.internal,
582                                    "Failed to confirm access for %s to %s"\
583                                            % (pname, nt))
584                else:
585                    self.log.warning("[static_project] no checking of " + \
586                        "node access")
587        except service_error, e:
588            # Do our best to clean up partial allocation and reraise the
589            # error.  Do our best to make sure that both allocation state and
590            # testbed state is restored.
591            for u, k in added_keys:
592                self.state['keys'].discard((u, k))
[4ed10ae]593                try:
[c3dcf48]594                    self.remove_key(u, k)
595                except service_error:
596                    pass
597            for p, t in added_types:
598                self.state['types'].discard((p, t))
599                try:
600                    self.remove_access(p, t)
601                except service_error:
602                    pass
603            self.state_lock.release()
604            raise e
605        # All is well, save state and release the lock
606        self.write_state()
607        self.state_lock.release()
608        # return { 'project': req['StaticProjectRequestBody']['project']}
609        return req['StaticProjectRequestBody']
[4ed10ae]610
[7583a62]611    def release_project(self, req, fedid=None):
612        """
613        Remove user keys from users and delete dynamic projects.
614
[c3d5d53]615        Only keys this service created are deleted and there are
[7583a62]616        similar protections for projects.
617        """
[05191a6]618        # Internal calls do not have a fedid parameter (i.e., local calls on
619        # behalf of already vetted fedids)
620        if fedid and not self.auth.check_attribute(fedid, "allocate"):
621            self.log.debug("[allocate] Access denied (%s)" % fedid)
622            raise service_error(service_error.access, "Access Denied")
[7583a62]623
624        pname = None
625        users = []
[c3dcf48]626        nodes = [ ]
627
628        print req
[7583a62]629
630        try:
631            if req['ReleaseProjectRequestBody']['project'].has_key('name'):
632                pname = req['ReleaseProjectRequestBody']['project']\
633                        ['name']['localname']
634            if req['ReleaseProjectRequestBody']['project'].has_key('user'):
635                users = req['ReleaseProjectRequestBody']['project']['user']
[c3dcf48]636            if req['ReleaseProjectRequestBody'].has_key('resources'):
637                nodes = req['ReleaseProjectRequestBody']\
638                        ['resources'].get('node', [])
[7583a62]639        except KeyError:
640            raise service_error(service_error.req, "Badly formed request")
641
[c3dcf48]642        if nodes and not pname:
643            raise service_error(service_error.req, 
644                    "Badly formed request (nodes without project)")
[08329f4]645
[c3dcf48]646        self.state_lock.acquire()
647        try:
648            for nt in [ h for n in nodes if n.has_key('hardware')\
649                    for h in n['hardware'] ] :
650                if (pname, nt ) in self.state['types']:
651                    self.remove_access(pname, nt)
652                    self.state['types'].discard((pname, nt))
[7583a62]653
[c3dcf48]654            for u in users:
655                try:
656                    name = u['userID']['localname']
657                except KeyError:
658                    raise service_error(service_error.req, "Badly formed user")
659                if name in self.state['users']:
660                    # If we created this user, discard the user, keys and all
661                    self.remove_user(name)
662                    self.state['users'].discard(name)
663                else:
664                    # If not, just strip any keys we added
665                    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
666                            if k.has_key('sshPubkey')]:
667                        if (name, sk) in self.state['keys']:
668                            self.remove_key(name, sk)
669                            self.state['keys'].discard((name, sk))
670            if pname in self.state['projects']:
671                self.remove_project(pname)
672                self.state['projects'].discard(pname)
673
674        except service_error, e:
675            self.write_state()
676            self.state_lock.release()
677            raise e
678        self.write_state()
679        self.state_lock.release()
[7583a62]680
681        return { 'project': req['ReleaseProjectRequestBody']['project']}
682
[ec4fb42]683class allocate_project_remote:
[4ed10ae]684    """
[058f58e]685    Allocate projects on a remote machine using the internal SOAP interface
[4ed10ae]686    """
[058f58e]687    class proxy(service_caller):
[ef36c1e]688        """
[058f58e]689        This class is a proxy functor (callable) that has the same signature as
690        a function called by soap_handler or xmlrpc_handler, but that used the
691        service_caller class to call the function remotely.
[ef36c1e]692        """
[4ed10ae]693
[05191a6]694        def __init__(self, url, cert_file, cert_pwd, trusted_certs, auth, 
695                method):
[f069052]696            service_caller.__init__(self, method)
[058f58e]697            self.url = url
698            self.cert_file = cert_file
699            self.cert_pwd = cert_pwd
700            self.trusted_certs = trusted_certs
[05191a6]701            self.request_body__name = "%sRequestBody" % method
702            self.resp_name = "%sResponseBody" % method
703            self.auth = auth
[058f58e]704            # Calling the proxy object directly invokes the proxy_call method,
705            # not the service_call method.
706            self.__call__ = self.proxy_call
707           
708
709        # Define the proxy, NB, the parameters to make_proxy are visible to the
710        # definition of proxy.
[05191a6]711        def proxy_call(self, req, fid=None):
[058f58e]712            """
713            Send req on to a remote project instantiator.
714
715            Req is just the message to be sent.  This function re-wraps it.
716            It also rethrows any faults.
717            """
718
719            if req.has_key(self.request_body_name):
720                req = req[self.request_body_name]
721            else:
722                raise service_error(service_error.req, "Bad formated request");
[ef36c1e]723
[9d3e646]724            try:
725                r = self.call_service(self.url, req, self.cert_file,
726                        self.cert_pwd, self.trusted_certs)
727            except service_error, e:
728                if e.code == service_error.connect:
729                    raise service_error(service_error.internal, 
730                            "Cannot connect to internal service: (%d) %s" % \
731                                    (e.code, e.desc))
732                else: raise
[058f58e]733            if r.has_key(self.resp_name):
734                return r[self.resp_name]
735            else:
736                raise service_error(service_error.protocol, 
737                        "Bad proxy response")
[4ed10ae]738
[ec4fb42]739    # back to defining the allocate_project_remote class
[3f6bc5f]740    def __init__(self, config, auth=None):
[4ed10ae]741        """
742        Initializer.  Parses a configuration if one is given.
743        """
744
[05191a6]745        self.debug = config.get("allocate", "debug", False)
746        self.url = config.get("allocate", "uri", "")
[4ed10ae]747
[c3d5d53]748        # Keep cert file and password coming from the same source
[05191a6]749        self.cert_file = config.get("allocate", "cert_file", None)
[c3d5d53]750        if self.cert_file:
751            self.cert_pwd = config.get("allocate", "cert_pwd", None)
752        else:
753            self.cert_file = config.get("globals", "cert_file", None)
754            self.cert_pwd = config.get("globals", "cert_pwd", None)
755
756        self.trusted_certs = config.get("allocate", "trusted_certs", None) or \
757                config.get("globals", "trusted_certs")
[4ed10ae]758
759        self.soap_services = { }
760        self.xmlrpc_services = { }
761        self.log = logging.getLogger("fedd.allocate.remote")
[05191a6]762        set_log_level(config, "allocate", self.log)
763
764        if auth:
765            self.auth = auth
766        else:
767            auth = authorizer()
768            log.warn("[allocate] No authorizer passed in, using local one")
[93a06fb]769
[058f58e]770        # The specializations of the proxy functions
771        self.dynamic_project = self.proxy(self.url, self.cert_file, 
[05191a6]772                self.cert_pwd, self.trusted_certs, self.auth, 
773                "AllocateProject")
[058f58e]774        self.static_project = self.proxy(self.url, self.cert_file, 
[05191a6]775                self.cert_pwd, self.trusted_certs, self.auth, 
776                "StaticProject")
[058f58e]777        self.release_project = self.proxy(self.url, self.cert_file,
[05191a6]778                self.cert_pwd, self.trusted_certs, self.auth, 
779                "ReleaseProject")
[058f58e]780
Note: See TracBrowser for help on using the repository browser.