Changeset 4ed10ae
- Timestamp:
- Nov 14, 2008 5:13:10 PM (16 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-1.30, version-2.00, version-3.01, version-3.02
- Children:
- afa43a8
- Parents:
- 2dafa0c
- Location:
- fedd
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/fedd.py
r2dafa0c r4ed10ae 110 110 def send_fault(self, f, code=500): 111 111 """Send a SOAP encoded fault as reply""" 112 print f 112 113 self.send_xml(f.AsSOAP(processContents="lax"), code) 113 114 -
fedd/fedd_access.py
r2dafa0c r4ed10ae 38 38 dynamically. This implements both direct requests and proxies. 39 39 """ 40 41 class parse_error(RuntimeError): pass 40 42 41 43 bool_attrs = ("dynamic_projects", "project_priority") … … 82 84 self.fedid_category[fedid(hexstr=m.string)] = cat 83 85 else: 84 raise parse_error(\86 raise self.parse_error(\ 85 87 "Bad fedid in trust file (%s) line: %d" % \ 86 88 (trust, lineno)) … … 93 95 # Nothing matched - bad line, raise exception 94 96 f.close() 95 raise parse_error(\97 raise self.parse_error(\ 96 98 "Unparsable line in trustfile %s line %d" % (trust, lineno)) 97 99 f.close() … … 121 123 with a # are ignored. 122 124 123 Parsing errors result in a parse_error exception being raised.125 Parsing errors result in a self.parse_error exception being raised. 124 126 """ 125 127 lineno=0 … … 138 140 access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ 139 141 key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 140 access_name + '\s* \)', re.IGNORECASE)142 access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE) 141 143 142 144 def parse_name(n): … … 165 167 continue 166 168 167 # Access line (t, p, u) -> (ap, au) line169 # Access line (t, p, u) -> (ap, cu, su) line 168 170 m = access_re.match(line) 169 171 if m != None: … … 174 176 aps[0] = fedid(hexstr=aps[0]) 175 177 176 au = m.group(5)177 if au.startswith("fedid:"):178 au = fedid(hexstr=aus[len("fedid:"):]) 179 180 access_val = (access_project(aps[0], aps[1:]), au)178 cu = parse_name(m.group(5)) 179 su = parse_name(m.group(6)) 180 181 access_val = (access_project(aps[0], aps[1:]), 182 parse_name(m.group(5)), parse_name(m.group(6))) 181 183 182 184 self.access[access_key] = access_val … … 185 187 # Nothing matched to here: unknown line - raise exception 186 188 f.close() 187 raise parse_error("Unknown statement at line %d of %s" % \189 raise self.parse_error("Unknown statement at line %d of %s" % \ 188 190 (lineno, config)) 189 191 f.close() … … 287 289 if not config.has_option("access", "dynamic_projects_url"): 288 290 self.allocate_project = \ 289 fedd_allocate_project_local(self.dynamic_projects, 290 None, proj_certs) 291 fedd_allocate_project_local(config) 291 292 else: 292 293 self.allocate_project = \ 293 fedd_allocate_project_remote(self.dynamic_projects, 294 config.get("access", "dynamic_projects_url"), 295 proj_certs) 294 fedd_allocate_project_remote(config) 296 295 297 296 # If the project allocator exports services, put them in this object's … … 328 327 return None 329 328 330 def strip_unicode(self, obj):331 """Loosly de-unicode an object"""332 if isinstance(obj, dict):333 for k in obj.keys():334 obj[k] = self.strip_unicode(obj[k])335 return obj336 elif isinstance(obj, basestring):337 return str(obj)338 elif getattr(obj, "__iter__", None):339 return [ self.strip_unicode(x) for x in obj]340 else:341 return obj342 343 329 def proxy_xmlrpc_request(self, dt, req): 344 330 """Send an XMLRPC proxy request. Called if the SOAP RPC fails""" … … 360 346 else: url = str(dt) 361 347 362 r = copy.deepcopy(req) 363 self.strip_unicode(r) 348 r = strip_unicode(copy.deepcopy(req)) 364 349 365 350 transport = SSL_Transport(ctx) … … 517 502 # resolve <dynamic> and <same> in found 518 503 dyn_proj = False 519 dyn_user = False 504 dyn_create_user = False 505 dyn_service_user = False 520 506 521 507 if found[0].name == "<same>": … … 535 521 if found[1] == "<same>": 536 522 if user_match == "<any>": 537 if user != None: r u = user[0]523 if user != None: rcu = user[0] 538 524 else: raise service_error(\ 539 525 service_error.server_config, 540 526 "Matched <same> on anonymous request") 541 527 else: 542 r u = user_match528 rcu = user_match 543 529 elif found[1] == "<dynamic>": 544 r u = None545 dyn_ user = True530 rcu = None 531 dyn_create_user = True 546 532 547 return (rp, ru), (dyn_user, dyn_proj) 533 if found[2] == "<same>": 534 if user_match == "<any>": 535 if user != None: rsu = user[0] 536 else: raise service_error(\ 537 service_error.server_config, 538 "Matched <same> on anonymous request") 539 else: 540 rsu = user_match 541 elif found[2] == "<dynamic>": 542 rsu = None 543 dyn_service_user = True 544 545 return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj) 548 546 549 547 def build_response(self, alloc_id, ap): … … 612 610 "Access denied (nodetypes %s)" % \ 613 611 str(', ').join(inaccessible)) 614 # XXX: This allocates a single user for both service and creation. 615 # This needs to be made more nuanced. 616 tmp_ssh = [ x['sshPubkey'] \ 617 for x in req['serviceAccess'] + req['createAccess'] \ 618 if x.has_key('sshPubkey')] 619 620 # Converting to a set collapses duplicates 621 ssh = set(tmp_ssh) 622 623 if len(ssh) > 0: 612 # These collect the keys for teh two roles into single sets, one 613 # for creation and one for service. The sets are a simple way to 614 # eliminate duplicates 615 create_ssh = set([ x['sshPubkey'] \ 616 for x in req['createAccess'] \ 617 if x.has_key('sshPubkey')]) 618 619 service_ssh = set([ x['sshPubkey'] \ 620 for x in req['serviceAccess'] \ 621 if x.has_key('sshPubkey')]) 622 623 if len(create_ssh) > 0 and len(service_ssh) >0: 624 624 if dyn[1]: 625 625 # Compose the dynamic project request … … 628 628 { 'project' : {\ 629 629 'user': [ \ 630 { 'access': [ { 'sshPubkey': s } ] } \ 631 for s in ssh ] \ 630 { \ 631 'access': [ { 'sshPubkey': s } \ 632 for s in service_ssh ], 633 'role': "serviceAccess",\ 634 }, \ 635 { \ 636 'access': [ { 'sshPubkey': s } \ 637 for s in create_ssh ], 638 'role': "experimentCreation",\ 639 }, \ 640 ], \ 632 641 }\ 633 642 }\ … … 641 650 else: 642 651 # XXX ssh key additions 643 ap = { 'project': \ 644 { 'name' : { 'localname' : found[0].name },\ 645 'user' : [ {\ 646 'userID': { 'localname' : found[1] }, \ 647 'access': [ { 'sshPubkey': s } for s in ssh]}\ 648 ]\ 652 preq = {'StaticProjectRequestBody' : \ 653 { 'project': \ 654 { 'name' : { 'localname' : found[0].name },\ 655 'user' : [ \ 656 {\ 657 'userID': { 'localname' : found[1] }, \ 658 'access': [ { 'sshPubkey': s } 659 for s in create_ssh ], 660 'role': 'experimentCreation'\ 661 },\ 662 {\ 663 'userID': { 'localname' : found[2] }, \ 664 'access': [ { 'sshPubkey': s } 665 for s in service_ssh ], 666 'role': 'serviceAccess'\ 667 },\ 668 ]}\ 649 669 }\ 650 670 } 671 if restricted != None and len(restricted) > 0: 672 preq['StaticProjectRequestBody']['resources'] = \ 673 [ {'node': { 'hardware' : [ h ] } } \ 674 for h in restricted ] 675 ap = self.allocate_project.static_project(preq) 651 676 else: 652 677 raise service_error(service_error.req, -
fedd/fedd_allocate_project.py
r2dafa0c r4ed10ae 35 35 fl.addHandler(nullHandler()) 36 36 37 37 38 class fedd_allocate_project_local: 38 39 """ 39 40 Allocate projects on this machine in response to an access request. 40 41 """ 41 def __init__(self, dp=False, url=None, certs=None):42 def __init__(self, config): 42 43 """ 43 44 Initializer. Parses a configuration if one is given. 44 45 """ 45 46 46 self.dynamic_projects = dp 47 self.wap = '/usr/testbed/sbin/wap' 48 self.newproj = '/usr/testbed/sbin/newproj' 49 self.mkproj = '/usr/testbed/sbin/mkproj' 50 self.grantnodetype = '/usr/testbed/sbin/grantnodetype' 47 self.debug = config.get("access", "debug_project", False) 48 self.wap = config.get('access', 'wap', '/usr/testbed/sbin/wap') 49 self.newproj = config.get('access', 'newproj', 50 '/usr/testbed/sbin/newproj') 51 self.mkproj = config.get('access', 'mkproj', '/usr/testbed/sbin/mkproj') 52 self.addpubkey = config.get('access', 'addpubkey', 53 '/usr/testbed/sbin/taddpubkey') 54 self.grantnodetype = config.get('access', 'grantnodetype', 55 '/usr/testbed/sbin/grantnodetype') 51 56 self.log = logging.getLogger("fedd.allocate.local") 57 set_log_level(config, "access", self.log) 52 58 53 59 # Internal services are SOAP only … … 56 62 AllocateProjectRequestMessage.typecode,\ 57 63 self.dynamic_project, AllocateProjectResponseMessage,\ 58 "AllocateProjectResponseBody")\ 64 "AllocateProjectResponseBody"), 65 "StaticProject": make_soap_handler(\ 66 StaticProjectRequestMessage.typecode,\ 67 self.static_project, StaticProjectResponseMessage,\ 68 "StaticProjectResponseBody")\ 59 69 } 60 70 self.xmlrpc_services = { } … … 179 189 for cmd in cmds: 180 190 self.log.debug("[dynamic_project]: %s" % ' '.join(cmd)) 181 if self.dynamic_projects:191 if not self.debug: 182 192 try: 183 193 rc = subprocess.call(cmd) 184 194 except OSerror, e: 185 raise fedd_proj.service_error(\ 186 fedd_proj.service_error.internal, 195 raise service_error(service_error.internal, 187 196 "Dynamic project subprocess creation error "+ \ 188 197 "[%s] (%s)" % (cmd[1], e.strerror)) 189 198 190 199 if rc != 0: 191 raise fedd_proj.service_error(\ 192 fedd_proj.service_error.internal, 200 raise service_error(service_error.internal, 193 201 "Dynamic project subprocess error " +\ 194 202 "[%s] (%d)" % (cmd[1], rc)) … … 207 215 return rv 208 216 209 210 class fedd_allocate_project_remote: 211 """ 212 Allocate projects on a remote machine using the internal SOAP interface 213 """ 214 def __init__(self, dp=False, url=None, certs=None): 215 """ 216 Initializer. Parses a configuration if one is given. 217 """ 218 219 self.dynamic_projects = dp 220 self.url = url 221 222 if certs != None and isinstance(certs, type(tuple())): 223 self.cert_file, self.trusted_certs, self.cert_pwd = certs 224 else: 225 self.cert_file, self.trusted_certs, self.cert_pwd = \ 226 (None, None, None) 227 self.soap_services = { } 228 self.xmlrpc_services = { } 217 def static_project(self, req, fedid=None): 218 """ 219 Be certain that the local project in the request has access to the 220 proper resources and users have correct keys. Add them if necessary. 221 """ 222 223 cmds = [] 224 225 # While we should be more careful about this, for the short term, add 226 # the keys to the specified users. 227 228 try: 229 users = req['StaticProjectRequestBody']['project']['user'] 230 pname = req['StaticProjectRequestBody']['project']\ 231 ['name']['localname'] 232 resources = req['StaticProjectRequestBody'].get('resources', []) 233 except KeyError: 234 raise service_error(service_error.req, "Badly formed request") 235 236 237 for u in users: 238 try: 239 name = u['userID']['localname'] 240 except KeyError: 241 raise service_error(service_error.req, "Badly formed user") 242 for sk in [ k['sshPubkey'] for k in u.get('access', []) \ 243 if k.has_key('sshPubkey')]: 244 cmds.append((self.addpubkey, '-w', '-u', name, '-k', sk)) 229 245 230 def dynamic_project(self, req, fedid=None): 246 247 # Add commands to grant access to any resources in the request. The 248 # list comprehension pulls out the hardware types in the node entries 249 # in the resources list. 250 for nt in [ h for r in resources \ 251 if r.has_key('node') and r['node'].has_key('hardware')\ 252 for h in r['node']['hardware'] ] : 253 cmds.append((self.wap, self.grantnodetype, '-p', pname, nt)) 254 255 # Run the commands 256 rc = 0 257 for cmd in cmds: 258 self.log.debug("[static_project]: %s" % ' '.join(cmd)) 259 if not self.debug: 260 try: 261 rc = subprocess.call(cmd) 262 except OSError, e: 263 print "Static project subprocess creation error "+ \ 264 "[%s] (%s)" % (cmd[0], e.strerror) 265 raise service_error(service_error.internal, 266 "Static project subprocess creation error "+ \ 267 "[%s] (%s)" % (cmd[0], e.strerror)) 268 269 if rc != 0: 270 raise service_error(service_error.internal, 271 "Static project subprocess error " +\ 272 "[%s] (%d)" % (cmd[0], rc)) 273 274 return { 'project': req['StaticProjectRequestBody']['project']} 275 276 def make_proxy(method, req_name, req_alloc, resp_name): 277 """ 278 Construct the proxy calling function from the given parameters. 279 """ 280 281 # Define the proxy, NB, the parameters to make_proxy are visible to the 282 # definition of proxy. 283 def proxy(self, req, fedid=None): 231 284 """ 232 285 Send req on to a remote project instantiator. 233 286 234 Req is just the projectAllocType object. This function re-wraps it.287 Req is just the message to be sent. This function re-wraps it. 235 288 It also rethrows any faults. 236 289 """ 290 237 291 # No retry loop here. Proxy servers must correctly authenticate 238 292 # themselves without help … … 249 303 transdict={ 'ssl_context' : ctx }) 250 304 251 if req.has_key( 'AllocateProjectRequestBody'):252 req = req[ 'AllocateProjectRequestBody']305 if req.has_key(req_name): 306 req = req[req_name] 253 307 else: 254 308 raise service_error(service_error.req, "Bad formated request"); 255 309 256 310 # Reconstruct the full request message 257 msg = AllocateProjectRequestMessage()258 msg.set_element_AllocateProjectRequestBody(259 pack_soap(msg, "AllocateProjectRequestBody", req))311 msg = req_alloc() 312 set_elem = getattr(msg, "set_element_%s" % req_name) 313 set_elem(pack_soap(msg, req_name, req)) 260 314 try: 261 resp = port.AllocateProject(msg) 315 mattr = getattr(port, method) 316 resp = mattr(msg) 262 317 except ZSI.ParseException, e: 263 318 raise service_error(service_error.proxy, 264 319 "Bad format message (XMLRPC??): %s" % str(e)) 320 except ZSI.FaultException, e: 321 resp = e.fault.detail[0] 322 265 323 r = unpack_soap(resp) 266 324 267 if r.has_key( 'AllocateProjectResponseBody'):268 return r[ 'AllocateProjectResponseBody']325 if r.has_key(resp_name): 326 return r[resp_name] 269 327 else: 270 328 raise service_error(service_error.proxy, "Bad proxy response") 271 329 # NB: end of proxy function definition 330 return proxy 331 332 class fedd_allocate_project_remote: 333 """ 334 Allocate projects on a remote machine using the internal SOAP interface 335 """ 336 dynamic_project = make_proxy("AllocateProject", 337 "AllocateProjectRequestBody", AllocateProjectRequestMessage, 338 "AllocateProjectResponseBody") 339 static_project = make_proxy("StaticProject", 340 "StaticProjectRequestBody", StaticProjectRequestMessage, 341 "StaticProjectResponseBody") 342 343 def __init__(self, config): 344 """ 345 Initializer. Parses a configuration if one is given. 346 """ 347 348 self.debug = config.get("access", "debug_project", False) 349 self.url = config.get("access", "dynamic_projects_url", "") 350 351 self.cert_file = config.get("access", "cert_file", None) 352 self.cert_pwd = config.get("access", "cert_pwd", None) 353 self.trusted_certs = config.get("access", "trusted_certs", None) 354 355 # Certs are promoted from the generic to the specific, so without a if 356 # no dynamic project certificates, then proxy certs are used, and if 357 # none of those the main certs. 358 359 if config.has_option("globals", "proxy_cert_file"): 360 if not self.cert_file: 361 self.cert_file = config.get("globals", "proxy_cert_file") 362 if config.has_option("globals", "porxy_cert_pwd"): 363 self.cert_pwd = config.get("globals", "proxy_cert_pwd") 364 365 if config.has_option("globals", "proxy_trusted_certs") and \ 366 not self.trusted_certs: 367 self.trusted_certs = \ 368 config.get("globals", "proxy_trusted_certs") 369 370 if config.has_option("globals", "cert_file"): 371 has_pwd = config.has_option("globals", "cert_pwd") 372 if not self.cert_file: 373 self.cert_file = config.get("globals", "cert_file") 374 if has_pwd: 375 self.cert_pwd = config.get("globals", "cert_pwd") 376 377 if config.get("globals", "trusted_certs") and not self.trusted_certs: 378 self.trusted_certs = \ 379 config.get("globals", "trusted_certs") 380 381 self.soap_services = { } 382 self.xmlrpc_services = { } 383 self.log = logging.getLogger("fedd.allocate.remote") 384 set_log_level(config, "access", self.log) 385 #self.dynamic_project = fedd_allocate_project_remote.dynamic_project 386 #self.static_project = fedd_allocate_project_remote.static_project -
fedd/fedd_experiment_control.py
r2dafa0c r4ed10ae 320 320 except pickle.PicklingError, e: 321 321 self.log.error("Pickling problem: %s" % e) 322 except TypeError, e: 323 self.log.error("Pickling problem (TypeError): %s" % e) 322 324 323 325 # Call while holding self.state_lock … … 441 443 self.log.error("[start_segment]: can't open /dev/null: %s" %e) 442 444 443 status = Popen(cmd, stdout=PIPE, stderr=dev_null) 444 for line in status.stdout: 445 m = state_re.match(line) 446 if m: state = m.group(1) 447 else: 448 m = no_exp_re.match(line) 449 if m: state = "none" 450 rv = status.wait() 445 if self.debug: 446 state = 'swapped' 447 rv = 0 448 else: 449 status = Popen(cmd, stdout=PIPE, stderr=dev_null) 450 for line in status.stdout: 451 m = state_re.match(line) 452 if m: state = m.group(1) 453 else: 454 m = no_exp_re.match(line) 455 if m: state = "none" 456 rv = status.wait() 451 457 452 458 # If the experiment is not present the subcommand returns a non-zero … … 766 772 else: return None 767 773 768 769 774 def get_access(self, tb, nodes, user, tbparam): 770 775 """ … … 835 840 except ZSI.ParseException, e: 836 841 raise service_error(service_error.req, 837 "Bad format message (XMLRPC??): %s" % 838 str(e)) 839 r = unpack_soap(resp) 842 "Bad format message (XMLRPC??): %s" % str(e)) 843 except ZSI.FaultException, e: 844 resp = e.fault.detail[0] 845 846 # Again, weird incompatibilities rear their head. userRoles, which are 847 # restricted strings, seem to be encoded by ZSI as non-unicode strings 848 # in a way that confuses the pickling and XMLRPC sending systems. 849 # Explicitly unicoding them seems to fix this, though it concerns me 850 # some. It may be that these things are actually a ZSI string 851 # subclass, and the character encoding is not the major issue. In any 852 # case, making all the subclasses of basestring into unicode strings 853 # unifies the response format and solves the problem. 854 r = make_unicode(unpack_soap(resp)) 840 855 841 856 if r.has_key('RequestAccessResponseBody'): … … 844 859 raise service_error(service_error.proxy, 845 860 "Bad proxy response") 846 847 861 848 862 e = r['emulab'] … … 868 882 if key: 869 883 tbparam[tb][key]= a['value'] 870 884 871 885 def remote_splitter(self, uri, desc, master): 872 886 -
fedd/fedd_internal_bindings.wsdl
r2dafa0c r4ed10ae 37 37 </fault> 38 38 </operation> 39 <operation name="StaticProject"> 40 <documentation> 41 The bindings of this operation are straightforward SOAP RPC 1.1. 42 </documentation> 43 <soap:operation soapAction="StaticProject"/> 44 <input> 45 <soap:body use="encoded" parts="tns:StaticProjectRequestBody" 46 namespace="http://www.isi.edu/faber/fedd_internal.wsdl" 47 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> 48 </input> 49 <output> 50 <soap:body use="encoded" parts="tns:StaticProjectResponseBody" 51 namespace="http://www.isi.edu/faber/fedd_internal.wsdl" 52 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> 53 </output> 54 <fault> 55 <soap:fault use="encoded" name="tns:StaticProjectFault" 56 namespace="http://www.isi.edu/faber/fedd_internal.wsdl" 57 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> 58 </fault> 59 </operation> 39 60 <operation name="Ns2Split"> 40 61 <documentation> -
fedd/fedd_internal_messages.wsdl
r2dafa0c r4ed10ae 26 26 </message> 27 27 28 <message name="StaticProjectRequestMessage"> 29 <part name="StaticProjectRequestBody" type="xsd1:projectAllocType"/> 30 </message> 31 32 <message name="StaticProjectResponseMessage"> 33 <part name="StaticProjectResponseBody" 34 type="xsd1:projectAllocResponseType"/> 35 </message> 36 28 37 <message name="Ns2SplitRequestMessage"> 29 38 <part name="Ns2SplitRequestBody" type="xsd1:ns2SplitRequestType"/> … … 43 52 <input message="tns:AllocateProjectRequestMessage"/> 44 53 <output message="tns:AllocateProjectResponseMessage"/> 45 <fault name="AllocateProjectFault" message="tns:FaultMessage"/> 54 <fault name="InternalFault" message="tns:FaultMessage"/> 55 </operation> 56 <operation name="StaticProject"> 57 <documentation> 58 This internal interface allows the part of a fedd running on a machine 59 other than the boss node of the emulab in question to request 60 modification of a static project. For example keys can be added or 61 node access granted. 62 </documentation> 63 <input message="tns:StaticProjectRequestMessage"/> 64 <output message="tns:StaticProjectResponseMessage"/> 65 <fault name="InternalFault" message="tns:FaultMessage"/> 46 66 </operation> 47 67 <operation name="Ns2Split"> … … 53 73 <input message="tns:Ns2SplitRequestMessage"/> 54 74 <output message="tns:Ns2SplitResponseMessage"/> 55 <fault name=" Ns2SplitFault" message="tns:FaultMessage"/>75 <fault name="InternalFault" message="tns:FaultMessage"/> 56 76 </operation> 57 77 </portType> -
fedd/fedd_util.py
r2dafa0c r4ed10ae 287 287 288 288 289 def strip_unicode(obj): 290 """Walk through a message and convert all strings to non-unicode strings""" 291 if isinstance(obj, dict): 292 for k in obj.keys(): 293 obj[k] = strip_unicode(obj[k]) 294 return obj 295 elif isinstance(obj, basestring): 296 return str(obj) 297 elif getattr(obj, "__iter__", None): 298 return [ strip_unicode(x) for x in obj] 299 else: 300 return obj 301 302 def make_unicode(obj): 303 """Walk through a message and convert all strings to unicode""" 304 if isinstance(obj, dict): 305 for k in obj.keys(): 306 obj[k] = make_unicode(obj[k]) 307 return obj 308 elif isinstance(obj, basestring): 309 return unicode(obj) 310 elif getattr(obj, "__iter__", None): 311 return [ make_unicode(x) for x in obj] 312 else: 313 return obj 314 315 289 316 def generate_fedid(subj, bits=2048, log=None, dir=None, trace=sys.stderr): 290 317 """ … … 396 423 return handler 397 424 425 398 426 def set_log_level(config, sect, log): 399 427 """ Set the logging level to the value passed in sect of config."""
Note: See TracChangeset
for help on using the changeset viewer.