source: fedd/federation/access.py @ c65b7e4

axis_examplecompt_changesinfo-ops
Last change on this file since c65b7e4 was c65b7e4, checked in by Ted Faber <faber@…>, 13 years ago

Access controllers delete (some) unused ABAC attrs.

  • Property mode set to 100644
File size: 22.6 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import stat # for chmod constants
5import re
6import random
7import string
8import copy
9import pickle
10import logging
11import subprocess
12
13from threading import *
14from M2Crypto.SSL import SSLError
15
16from util import *
17from allocate_project import allocate_project_local, allocate_project_remote
18from fedid import fedid, generate_fedid
19from authorizer import authorizer
20from service_error import service_error
21from remote_service import xmlrpc_handler, soap_handler, service_caller
22
23import httplib
24import tempfile
25from urlparse import urlparse
26
27import topdl
28import list_log
29import proxy_emulab_segment
30import local_emulab_segment
31
32
33# Make log messages disappear if noone configures a fedd logger
34class nullHandler(logging.Handler):
35    def emit(self, record): pass
36
37fl = logging.getLogger("fedd.access")
38fl.addHandler(nullHandler())
39
40class access_base:
41    """
42    The implementation of access control based on mapping users to projects.
43
44    Users can be mapped to existing projects or have projects created
45    dynamically.  This implements both direct requests and proxies.
46    """
47
48    class parse_error(RuntimeError): pass
49
50    class access_attribute:
51        def __init__(self, attr, value, pri=1):
52            self.attr = attr
53            self.value = value
54            self.priority = pri
55        def __str__(self):
56            return "%s: %s (%d)" % (self.attr, self.value, self.priority)
57
58    def __init__(self, config=None, auth=None):
59        """
60        Initializer.  Pulls parameters out of the ConfigParser's access section.
61        """
62
63        # Make sure that the configuration is in place
64        if not config: 
65            raise RunTimeError("No config to fedd.access")
66
67        self.project_priority = config.getboolean("access", "project_priority")
68
69        self.certdir = config.get("access","certdir")
70        self.create_debug = config.getboolean("access", "create_debug")
71        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
72        self.access_type = config.get("access", "type")
73        self.log = logging.getLogger("fedd.access")
74        set_log_level(config, "access", self.log)
75        self.state_lock = Lock()
76        self.state = { }
77        # subclasses fill with what and how they export.
78        self.exports = { }
79        # XXX: Configurable
80        self.imports = set(('SMB', 'seer', 'userconfig', 'seer_master',
81            'hide_hosts'))
82
83        if auth: self.auth = auth
84        else:
85            self.log.error(\
86                    "[access]: No authorizer initialized, creating local one.")
87            auth = authorizer()
88
89        self.state_filename = config.get("access", "access_state")
90        self.read_state()
91
92        # Keep cert_file and cert_pwd coming from the same place
93        self.cert_file = config.get("access", "cert_file")
94        if self.cert_file:
95            self.cert_pwd = config.get("access", "cert_pw")
96        else:
97            self.cert_file = config.get("globals", "cert_file")
98            self.sert_pwd = config.get("globals", "cert_pw")
99
100        self.trusted_certs = config.get("access", "trusted_certs") or \
101                config.get("globals", "trusted_certs")
102
103
104    @staticmethod
105    def software_list(v):
106        """
107        From a string containing a sequence of space separated pairs, return a
108        list of tuples with pairs of location and file.
109        """
110        l = [ ]
111        if v:
112            ps = v.split(" ")
113            while len(ps):
114                loc, file = ps[0:2]
115                del ps[0:2]
116                l.append((loc, file))
117        return l
118
119    @staticmethod
120    def add_kit(e, kit):
121        """
122        Add a Software object created from the list of (install, location)
123        tuples passed as kit  to the software attribute of an object e.  We
124        do this enough to break out the code, but it's kind of a hack to
125        avoid changing the old tuple rep.
126        """
127
128        s = [ topdl.Software(install=i, location=l) for i, l in kit]
129
130        if isinstance(e.software, list): e.software.extend(s)
131        else: e.software = s
132
133
134    def read_access(self, fn, access_obj=None, default=[]):
135        """
136        Read an access DB of the form
137            abac.attribute -> local_auth_data
138        The access dict is filled with mappings from the abac attributes (as
139        strings) to the access objects.  The objects are strings by default,
140        but the class constructor is called with the string following the ->
141        and whitespace in the file.
142        """
143
144        map_re = re.compile("(\S+)\s+->\s+(.*)")
145        priority_re = re.compile("([^,]+),\s*(\d+)")
146
147        if access_obj is None:
148            access_obj = lambda(x): "%s" % x
149
150        self.access = []
151        priorities = { }
152
153        f = open(fn, 'r')
154        try:
155            lineno = 0
156            for line in f:
157                lineno += 1
158                line = line.strip();
159                if len(line) == 0 or line.startswith('#'):
160                    continue
161                m = map_re.match(line)
162                if m != None:
163                    self.access.append(access_base.access_attribute(m.group(1),
164                        access_obj(m.group(2))))
165                    continue
166
167                # If a priority is found, collect them
168                m = priority_re.match(line)
169                if m:
170                    try:
171                        priorities[m.group(1)] = int(m.group(2))
172                    except ValueError, e:
173                        if self.log:
174                            self.log.debug("Bad priority in %s line %d" % \
175                                    (fn, lineno))
176                    continue
177
178                # Nothing matched to here: unknown line - raise exception
179                # (finally will close f)
180                raise self.parse_error(
181                        "Unknown statement at line %d of %s" % \
182                        (lineno, fn))
183        finally:
184            if f: f.close()
185
186        # Set priorities
187        for a in self.access:
188            if a.attr in priorities:
189                a.priority = priorities[a.attr]
190
191        # default access mappings
192        for a, v in default:
193            self.access.append(
194                    access_base.access_attribute(attr=a, value=v, pri=0))
195
196
197
198    def write_state(self):
199        if self.state_filename:
200            try:
201                f = open(self.state_filename, 'w')
202                pickle.dump(self.state, f)
203                self.log.debug("Wrote state to %s" % self.state_filename)
204            except EnvironmentError, e:
205                self.log.error("Can't write file %s: %s" % \
206                        (self.state_filename, e))
207            except pickle.PicklingError, e:
208                self.log.error("Pickling problem: %s" % e)
209            except TypeError, e:
210                self.log.error("Pickling problem (TypeError): %s" % e)
211
212
213    def read_state(self):
214        """
215        Read a new copy of access state.  Old state is overwritten.
216
217        State format is a simple pickling of the state dictionary.
218        """
219        if self.state_filename:
220            try:
221                f = open(self.state_filename, "r")
222                self.state = pickle.load(f)
223                self.log.debug("[read_state]: Read state from %s" % \
224                        self.state_filename)
225            except EnvironmentError, e:
226                self.log.warning(("[read_state]: No saved state: " +\
227                        "Can't open %s: %s") % (self.state_filename, e))
228            except EOFError, e:
229                self.log.warning(("[read_state]: " +\
230                        "Empty or damaged state file: %s:") % \
231                        self.state_filename)
232            except pickle.UnpicklingError, e:
233                self.log.warning(("[read_state]: No saved state: " + \
234                        "Unpickling failed: %s") % e)
235
236    def append_allocation_authorization(self, aid, attrs, 
237            need_state_lock=False, write_state_file=False, state_attr='state'):
238        """
239        Append the authorization information to system state.  By default we
240        assume this is called with the state lock and with a write of the state
241        file in the near future, need_state_lock and write_state_file can
242        override this.  The state_attr is the attribute in the access class
243        that holds the per allocation information.  Some complex classes use
244        different names for the dict.
245        """
246
247        for p, a in attrs:
248            self.auth.set_attribute(p, a)
249        self.auth.save()
250
251        if need_state_lock: self.state_lock.acquire()
252        d = getattr(self, state_attr)
253        if aid in d and 'auth' in d[aid]:
254            d[aid]['auth'].update(attrs)
255        if write_state_file: self.write_state()
256        if need_state_lock: self.state_lock.release()
257
258    def clear_allocation_authorization(self, aid, need_state_lock=False,
259            write_state_file=False, state_attr='state'):
260        """
261        Attrs is a set of attribute principal pairs that need to be removed
262        from the authenticator.  Remove them and save the authenticator.  See
263        append_allocation_authorization for the various overrides.
264        """
265
266        if need_state_lock: self.state_lock.acquire()
267        d = getattr(self, state_attr)
268        if aid in d and 'auth' in d[aid]:
269            for p, a in d[aid]['auth']:
270                self.auth.unset_attribute(p, a)
271            d[aid]['auth'] = set()
272        if write_state_file: self.write_state()
273        if need_state_lock: self.state_lock.release()
274        self.auth.save()
275
276    def lookup_access(self, req, fid, filter=None, compare=None): 
277        """
278        Check all the attributes that this controller knows how to map and see
279        if the requester is allowed to use any of them.  If so return one.
280        Filter defined the objects to check - it's a function that returns true
281        for the objects to check - and cmp defines the order to check them in
282        as the cmp field of sorted().  If filter is None, all possibilities are
283        checked.  If cmp is None, the choices are sorted by priority.
284        """
285
286        # Import request credentials into this (clone later??)
287        if self.auth.import_credentials(
288                data_list=req.get('abac_credential', [])):
289            self.auth.save()
290
291        # NB: in the default case (the else), the comparison order is reversed
292        # so numerically larger priorities are checked first.
293        if compare: c = compare
294        else: c = lambda a, b: cmp(b,a)
295
296        if filter: f = filter
297        else: f = lambda(x): True
298
299        check = sorted([ a for a in self.access if f(a)], cmp=c)
300
301        # Check every attribute that we know how to map and take the first
302        # success.
303        for attr in check:
304            if self.auth.check_attribute(fid, attr.attr):
305                self.log.debug("Access succeeded for %s %s" % (attr.attr, fid))
306                # XXX: needs to deal with dynamics
307                return copy.copy(attr.value), (False, False, False), \
308                        [ fid ]
309            else:
310                self.log.debug("Access failed for %s %s" % (attr.attr, fid))
311        else:
312            raise service_error(service_error.access, "Access denied")
313
314
315
316    def get_handler(self, path, fid):
317        """
318        This function is somewhat oddly named.  It doesn't get a handler, it
319        handles GETs.  Specifically, it handls https GETs for retrieving data
320        from the repository exported by the access server.
321        """
322        self.log.info("Get handler %s %s" % (path, fid))
323        if self.auth.check_attribute(fid, path) and self.userconfdir:
324            return ("%s/%s" % (self.userconfdir, path), "application/binary")
325        else:
326            return (None, None)
327
328    def export_userconf(self, project):
329        dev_null = None
330        confid, confcert = generate_fedid("test", dir=self.userconfdir, 
331                log=self.log)
332        conffilename = "%s/%s" % (self.userconfdir, str(confid))
333        cf = None
334        try:
335            cf = open(conffilename, "w")
336            os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
337        except EnvironmentError, e:
338            raise service_error(service_error.internal, 
339                    "Cannot create user configuration data")
340
341        try:
342            dev_null = open("/dev/null", "a")
343        except EnvironmentError, e:
344            self.log.error("export_userconf: can't open /dev/null: %s" % e)
345
346        cmd = "%s %s" % (self.userconfcmd, project)
347        conf = subprocess.call(cmd.split(" "),
348                stdout=cf, stderr=dev_null, close_fds=True)
349
350        self.auth.set_attribute(confid, "/%s" % str(confid))
351
352        return confid, confcert
353
354    def export_SMB(self, id, state, project, user, attrs):
355        if project and user:
356            return [{ 
357                    'id': id,
358                    'name': 'SMB',
359                    'visibility': 'export',
360                    'server': 'http://fs:139',
361                    'fedAttr': [
362                            { 'attribute': 'SMBSHARE', 'value': 'USERS' },
363                            { 'attribute': 'SMBUSER', 'value': user },
364                            { 'attribute': 'SMBPROJ', 'value': project },
365                        ]
366                    }]
367        else:
368            self.log.warn("Cannot export SMB w/o user and project")
369            return [ ]
370
371    def export_seer(self, id, state, project, user, attrs):
372        return [{ 
373                'id': id,
374                'name': 'seer',
375                'visibility': 'export',
376                'server': 'http://control:16606',
377                }]
378
379    def export_local_seer(self, id, state, project, user, attrs):
380        return [{ 
381                'id': id,
382                'name': 'local_seer_control',
383                'visibility': 'export',
384                'server': 'http://control:16606',
385                }]
386
387    def export_seer_master(self, id, state, project, user, attrs):
388        return [{ 
389                'id': id,
390                'name': 'seer_master',
391                'visibility': 'export',
392                'server': 'http://seer-master:17707',
393                }]
394
395    def export_tmcd(self, id, state, project, user, attrs):
396        return [{ 
397                'id': id,
398                'name': 'seer',
399                'visibility': 'export',
400                'server': 'http://boss:7777',
401                }]
402
403    def export_userconfig(self, id, state, project, user, attrs):
404        if self.userconfdir and self.userconfcmd \
405                and self.userconfurl:
406            cid, cert = self.export_userconf(project)
407            state['userconfig'] = unicode(cid)
408            return [{
409                    'id': id,
410                    'name': 'userconfig',
411                    'visibility': 'export',
412                    'server': "%s/%s" % (self.userconfurl, str(cid)),
413                    'fedAttr': [
414                        { 'attribute': 'cert', 'value': cert },
415                    ]
416                    }]
417        else:
418            return [ ]
419
420    def export_hide_hosts(self, id, state, project, user, attrs):
421        return [{
422                'id': id, 
423                'name': 'hide_hosts',
424                'visibility': 'export',
425                'fedAttr': [ x for x in attrs \
426                        if x.get('attribute', "") == 'hosts'],
427                }]
428
429    def export_project_export(self, id, state, project, user, attrs):
430        rv = [ ]
431        rv.extend(self.export_SMB(id, state, project, user, attrs))
432        rv.extend(self.export_userconfig(id, state, project, user, attrs))
433        return rv
434
435    def export_services(self, sreq, project=None, user=None):
436        exp = [ ]
437        state = { }
438        for s in sreq:
439            sname = s.get('name', '')
440            svis = s.get('visibility', '')
441            sattrs = s.get('fedAttr', [])
442            if svis == 'export':
443                if sname in self.exports:
444                    id = s.get('id', 'no_id')
445                    exp.extend(self.exports[sname](id, state, project, user,
446                            sattrs))
447
448        return (exp, state)
449
450    def build_access_response(self, alloc_id, ap, services):
451        """
452        Create the SOAP response.
453
454        Build the dictionary description of the response and use
455        fedd_utils.pack_soap to create the soap message.  ap is the allocate
456        project message returned from a remote project allocation (even if that
457        allocation was done locally).
458        """
459        # Because alloc_id is already a fedd_services_types.IDType_Holder,
460        # there's no need to repack it
461        msg = { 
462                'allocID': alloc_id,
463                'fedAttr': [
464                    { 'attribute': 'domain', 'value': self.domain } , 
465                    { 'attribute': 'project', 'value': 
466                        ap['project'].get('name', {}).get('localname', "???") },
467                ]
468            }
469
470        if self.dragon_endpoint:
471            msg['fedAttr'].append({'attribute': 'dragon',
472                'value': self.dragon_endpoint})
473        if self.deter_internal:
474            msg['fedAttr'].append({'attribute': 'deter_internal',
475                'value': self.deter_internal})
476        #XXX: ??
477        if self.dragon_vlans:
478            msg['fedAttr'].append({'attribute': 'vlans',
479                'value': self.dragon_vlans})
480
481        if services:
482            msg['service'] = services
483        return msg
484
485    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
486            tmpdir, lproj, leid, connInfo, services):
487
488        def conninfo_to_dict(key, info):
489            """
490            Make a cpoy of the connection information about key, and flatten it
491            into a single dict by parsing out any feddAttrs.
492            """
493
494            rv = None
495            for i in info:
496                if key == i.get('portal', "") or \
497                        key in [e.get('element', "") \
498                        for e in i.get('member', [])]:
499                    rv = i.copy()
500                    break
501
502            else:
503                return rv
504
505            if 'fedAttr' in rv:
506                for a in rv['fedAttr']:
507                    attr = a.get('attribute', "")
508                    val = a.get('value', "")
509                    if attr and attr not in rv:
510                        rv[attr] = val
511                del rv['fedAttr']
512            return rv
513
514        # XXX: un hardcode this
515        def client_null(f, s):
516            print >>f, "Service: %s" % s['name']
517
518        def client_seer_master(f, s):
519            print >>f, 'PortalAlias: seer-master'
520
521        def client_smb(f, s):
522            print >>f, "Service: %s" % s['name']
523            smbshare = None
524            smbuser = None
525            smbproj = None
526            for a in s.get('fedAttr', []):
527                if a.get('attribute', '') == 'SMBSHARE':
528                    smbshare = a.get('value', None)
529                elif a.get('attribute', '') == 'SMBUSER':
530                    smbuser = a.get('value', None)
531                elif a.get('attribute', '') == 'SMBPROJ':
532                    smbproj = a.get('value', None)
533
534            if all((smbshare, smbuser, smbproj)):
535                print >>f, "SMBshare: %s" % smbshare
536                print >>f, "ProjectUser: %s" % smbuser
537                print >>f, "ProjectName: %s" % smbproj
538
539        def client_hide_hosts(f, s):
540            for a in s.get('fedAttr', [ ]):
541                if a.get('attribute', "") == 'hosts':
542                    print >>f, "Hide: %s" % a.get('value', "")
543
544        client_service_out = {
545                'SMB': client_smb,
546                'tmcd': client_null,
547                'seer': client_null,
548                'userconfig': client_null,
549                'project_export': client_null,
550                'seer_master': client_seer_master,
551                'hide_hosts': client_hide_hosts,
552            }
553
554        def client_seer_master_export(f, s):
555            print >>f, "AddedNode: seer-master"
556
557        def client_seer_local_export(f, s):
558            print >>f, "AddedNode: control"
559
560        client_export_service_out = {
561                'seer_master': client_seer_master_export,
562                'local_seer_control': client_seer_local_export,
563            }
564
565        def server_port(f, s):
566            p = urlparse(s.get('server', 'http://localhost'))
567            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
568
569        def server_null(f,s): pass
570
571        def server_seer(f, s):
572            print >>f, 'seer: True'
573
574        server_service_out = {
575                'SMB': server_port,
576                'tmcd': server_port,
577                'userconfig': server_null,
578                'project_export': server_null,
579                'seer': server_seer,
580                'seer_master': server_port,
581                'hide_hosts': server_null,
582            }
583        # XXX: end un hardcode this
584
585
586        seer_out = False
587        client_out = False
588        mproj = None
589        mexp = None
590        control_gw = None
591        testbed = ""
592        # Create configuration files for the portals
593        for e in [ e for e in topo.elements \
594                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
595            myname = e.name
596            type = e.get_attribute('portal_type')
597
598            info = conninfo_to_dict(myname, connInfo)
599
600            if not info:
601                raise service_error(service_error.req,
602                        "No connectivity info for %s" % myname)
603
604            peer = info.get('peer', "")
605            ldomain = self.domain
606            ssh_port = info.get('ssh_port', 22)
607
608            # Collect this for the client.conf file
609            if 'masterexperiment' in info:
610                mproj, meid = info['masterexperiment'].split("/", 1)
611
612            if type in ('control', 'both'):
613                testbed = e.get_attribute('testbed')
614                control_gw = myname
615
616            active = info.get('active', 'False')
617
618            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
619            tunnelconfig = self.tunnel_config
620            try:
621                f = open(cfn, "w")
622                if active == 'True':
623                    print >>f, "active: True"
624                    print >>f, "ssh_port: %s" % ssh_port
625                    if type in ('control', 'both'):
626                        for s in [s for s in services \
627                                if s.get('name', "") in self.imports]:
628                            server_service_out[s['name']](f, s)
629
630                if tunnelconfig:
631                    print >>f, "tunnelip: %s" % tunnelconfig
632                print >>f, "peer: %s" % peer.lower()
633                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
634                        (lproj, leid, pubkey_base)
635                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
636                        (lproj, leid, secretkey_base)
637                f.close()
638            except EnvironmentError, e:
639                raise service_error(service_error.internal,
640                        "Can't write protal config %s: %s" % (cfn, e))
641
642        # Done with portals, write the client config file.
643        try:
644            f = open("%s/client.conf" % tmpdir, "w")
645            if control_gw:
646                print >>f, "ControlGateway: %s.%s.%s%s" % \
647                    (myname.lower(), leid.lower(), lproj.lower(),
648                            ldomain.lower())
649            for s in services:
650                if s.get('name',"") in self.imports and \
651                        s.get('visibility','') == 'import':
652                    client_service_out[s['name']](f, s)
653                if s.get('name', '') in self.exports and \
654                        s.get('visibility', '') == 'export' and \
655                        s['name'] in client_export_service_out:
656                    client_export_service_out[s['name']](f, s)
657            # Seer uses this.
658            if mproj and meid:
659                print >>f, "ExperimentID: %s/%s" % (mproj, meid)
660            f.close()
661        except EnvironmentError, e:
662            raise service_error(service_error.internal,
663                    "Cannot write client.conf: %s" %s)
664
665    def configure_userconf(self, services, tmpdir):
666        """
667        If the userconf service was imported, collect the configuration data.
668        """
669        for s in services:
670            s_name = s.get('name', '')
671            s_vis = s.get('visibility','')
672            if s_name  == 'userconfig' and s_vis == 'import':
673                # Collect ther server and certificate info.
674                u = s.get('server', None)
675                for a in s.get('fedAttr', []):
676                    if a.get('attribute',"") == 'cert':
677                        cert = a.get('value', None)
678                        break
679                else:
680                    cert = None
681
682                if cert:
683                    # Make a temporary certificate file for get_url.  The
684                    # finally clause removes it whether something goes
685                    # wrong (including an exception from get_url) or not.
686                    try:
687                        tfos, tn = tempfile.mkstemp(suffix=".pem")
688                        tf = os.fdopen(tfos, 'w')
689                        print >>tf, cert
690                        tf.close()
691                        self.log.debug("Getting userconf info: %s" % u)
692                        get_url(u, tn, tmpdir, "userconf")
693                        self.log.debug("Got userconf info: %s" % u)
694                    except EnvironmentError, e:
695                        raise service_error(service.error.internal, 
696                                "Cannot create temp file for " + 
697                                "userconfig certificates: %s" % e)
698                    except:
699                        t, v, st = sys.exc_info()
700                        raise service_error(service_error.internal,
701                                "Error retrieving %s: %s" % (u, v))
702                    finally:
703                        if tn: os.remove(tn)
704                else:
705                    raise service_error(service_error.req,
706                            "No certificate for retreiving userconfig")
707                break
708
709    def import_store_info(self, cf, connInfo):
710        """
711        Pull any import parameters in connInfo in.  We translate them either
712        into known member names or fedAddrs.
713        """
714
715        for c in connInfo:
716            for p in [ p for p in c.get('parameter', []) \
717                    if p.get('type', '') == 'input']:
718                name = p.get('name', None)
719                key = p.get('key', None)
720                store = p.get('store', None)
721
722                if name and key and store :
723                    req = { 'name': key, 'wait': True }
724                    self.log.debug("Waiting for %s (%s) from %s" % \
725                            (name, key, store))
726                    r = self.call_GetValue(store, req, cf)
727                    r = r.get('GetValueResponseBody', None)
728                    if r :
729                        if r.get('name', '') == key:
730                            v = r.get('value', None)
731                            if v is not None:
732                                if name == 'peer':
733                                    self.log.debug("Got peer %s" % v)
734                                    c['peer'] = v
735                                else:
736                                    self.log.debug("Got %s %s" % (name, v))
737                                    if c.has_key('fedAttr'):
738                                        c['fedAttr'].append({
739                                            'attribute': name, 'value': v})
740                                    else:
741                                        c['fedAttr']= [{
742                                            'attribute': name, 'value': v}]
743                            else:
744                                raise service_error(service_error.internal, 
745                                        'None value exported for %s'  % key)
746                        else:
747                            raise service_error(service_error.internal, 
748                                    'Different name returned for %s: %s' \
749                                            % (key, r.get('name','')))
750                    else:
751                        raise service_error(service_error.internal, 
752                            'Badly formatted response: no GetValueResponseBody')
753                else:
754                    raise service_error(service_error.internal, 
755                        'Bad Services missing info for import %s' % c)
756
757    def remove_dirs(self, dir):
758        """
759        Remove the directory tree and all files rooted at dir.  Log any errors,
760        but continue.
761        """
762        self.log.debug("[removedirs]: removing %s" % dir)
763        try:
764            for path, dirs, files in os.walk(dir, topdown=False):
765                for f in files:
766                    os.remove(os.path.join(path, f))
767                for d in dirs:
768                    os.rmdir(os.path.join(path, d))
769            os.rmdir(dir)
770        except EnvironmentError, e:
771            self.log.error("Error deleting directory tree in %s" % e);
Note: See TracBrowser for help on using the repository browser.