Package Ganga :: Package GPIDev :: Package Credentials :: Module GridProxy
[hide private]
[frames] | no frames]

Source Code for Module Ganga.GPIDev.Credentials.GridProxy

  1  ################################################################################
 
  2  # Ganga Project. http://cern.ch/ganga
 
  3  #
 
  4  # $Id: GridProxy.py,v 1.5 2009/03/25 15:43:35 karl Exp $
 
  5  ################################################################################
 
  6  #
 
  7  # File: GridProxy.py
 
  8  # Author: K. Harrison
 
  9  # Created: 060519
 
 10  #
 
 11  # 06/07/2006 KH:  Changed to Ganga.Utility.Shell for shell commands
 
 12  #                 Added voms support
 
 13  #
 
 14  # 02/08/2006 KH:  Modified GridProxy class to create one instance of
 
 15  #                 VomsCommand and GridCommand
 
 16  #
 
 17  # 07/08/2006 KH:  Added isValid() method
 
 18  #
 
 19  # 09/08/2006 KH:  Use shell defined via Ganga.Lib.LCG.GridShell.getShell()
 
 20  #
 
 21  # 25/08/2006 KH:  Declare GridProxy class as hidden
 
 22  #
 
 23  # 06/09/2006 KH:  Argument minValidity added to methods create() and renew()
 
 24  #
 
 25  # 25/09/2006 KH:  Changed method isValid(), so that default validity is
 
 26  #                 value of self.minValidity
 
 27  #
 
 28  # 13/11/2006 KH:  Added method info() for obtaining proxy information,
 
 29  #                 and changed location() to use this method
 
 30  #
 
 31  # 23/11/2006 KH:  Added "pipe" keyword to option dictionaries of GridCommand
 
 32  #                 and VomsCommand
 
 33  #                 Added method to determine if credential is available
 
 34  #                 with system/configuration used
 
 35  #                 (requests from CLT)
 
 36  #
 
 37  # 28/02/2007 CLT: Replaced VomsCommand.options and GridCommand.options
 
 38  #                 with dictionaries init_parameters, destroy_parameters,
 
 39  #                 info_parameters, each providing independent options
 
 40  #                 Added VomsCommand.currentOpts and GridCommand.currentOpts
 
 41  #                 dictionaries, to add flexibility and assist in option
 
 42  #                 construction (as opposed to direct string manipulation)
 
 43  #                 Added GridProxy.buildOpts(), to consolidate the option
 
 44  #                 building functionality from create(), destroy() and info()
 
 45  #
 
 46  # 02/03/2007 KH : Added method to determine user's identity
 
 47  #                 (request from DL)
 
 48  #
 
 49  # 25/04/2007 KH : Modified GridProxy.identity method to be able to deal
 
 50  #                 with new-style CERN certificates, with ambiguous CN definition
 
 51  #
 
 52  # 08/06/2007 KH : Added method GridProxy.voname, to allow name of
 
 53  #                 virtual organisation to be determined from proxy
 
 54  #
 
 55  # 25/09/2007 KH:  Changes for compatibility with multi-proxy handling
 
 56  #                 => "middleware" argument introduced
 
 57  #
 
 58  # 08/12/2007 KH:  Changes to take into account ICommandSet being made
 
 59  #                 a component class
 
 60  #
 
 61  # 17/12/2007 KH:  Made changes for handling of GridCommand and VomsCommand as
 
 62  #                 component classes
 
 63  #
 
 64  # 15/01/2008 KH:  Set initial null value for infoCommand in GridProxy.voname()
 
 65  #
 
 66  # 18/01/2008 KH : Modified GridProxy.identity method to disregard
 
 67  #                 values of CN=proxy
 
 68  #
 
 69  # 27/02/2008 KH : Setup shell in GridProxy constructor, if middleware is defined
 
 70  #
 
 71  # 30/01/2009 KH : Added possibility to request that GridProxy.identity()
 
 72  #                 returns string with non-alphanumeric characters stripped out
 
 73  #
 
 74  # 25/03/2009 KH : Correction to GridProxy.voname() to check that one-word
 
 75  #                 VO name is returned 
 
 76  #
 
 77  # 18/03/2009 MWS: Added the 'log' option to isValid
 
 78  #                 Added method to retrieve the full identity as a dictionary
 
 79  #                 Require consistency between VO in proxy and in configuration
 
 80  #
 
 81  # 15/10/2009 MWS: Added cache for proxy information
 
 82  #
 
 83  #
 
 84  # 09/11/2009 MWS: Added check that proxy is valid before updating cache
 
 85  #                 (addToProxyCache() method)
 
 86  #
 
 87  # 12/11/2009 MWS: Added additional checks that cached information
 
 88  #                 is consistent with available proxy
 
 89  
 
 90  """Module defining class for creating, querying and renewing Grid proxy""" 
 91                                                                                  
 
 92  __author__  = "K.Harrison <Harrison@hep.phy.cam.ac.uk>" 
 93  __date__    = "12 November 2009" 
 94  __version__ = "1.21" 
 95  
 
 96  import os 
 97  import re 
 98  import time 
 99  
 
100  from ICredential import ICommandSet 
101  from ICredential import ICredential 
102  from ICredential import registerCommandSet 
103  from Ganga.GPIDev.Schema import SimpleItem 
104  from Ganga.Utility.logging import getLogger 
105  from Ganga.Utility.GridShell import getShell 
106  
 
107  logger = getLogger() 
108  
 
109 -class GridCommand( ICommandSet ):
110 """ 111 Class used to define shell commands and options for working with Grid proxy 112 """ 113 114 _schema = ICommandSet._schema.inherit_copy() 115 _schema['init']._meta['defvalue'] = "grid-proxy-init" 116 _schema['info']._meta['defvalue'] = "grid-proxy-info" 117 _schema['destroy']._meta['defvalue'] = "grid-proxy-destroy" 118 _schema['init_parameters']._meta['defvalue'] = { "pipe" : "-pwstdin", "valid" : "-valid" } 119 _schema['destroy_parameters']._meta['defvalue'] = {} 120 _schema['info_parameters']._meta['defvalue'] = {} 121 122 _name = "GridCommand" 123 _hidden = 1 124 _enable_config = 1 125
126 - def __init__( self ):
127 super( GridCommand, self ).__init__() 128 129 self.currentOpts = {} 130 self.infoOpts = {} 131 self.destroyOpts = {}
132
133 -class VomsCommand( ICommandSet ):
134 """ 135 Class used to define shell commands and options for working with Grid proxy, 136 using VOMS extensions 137 """ 138 139 _schema = ICommandSet._schema.inherit_copy() 140 141 _schema['init']._meta['defvalue'] = "voms-proxy-init" 142 _schema['info']._meta['defvalue'] = "voms-proxy-info" 143 _schema['destroy']._meta['defvalue'] = "voms-proxy-destroy" 144 _schema['init_parameters']._meta['defvalue'] = { "pipe" : "-pwstdin", "valid" : "-valid", \ 145 "voms" : "-voms" } 146 _schema['destroy_parameters']._meta['defvalue'] = {} 147 _schema['info_parameters']._meta['defvalue'] = { "vo" : "-vo" } 148 149 _name = "VomsCommand" 150 _hidden = 1 151 _enable_config = 1 152
153 - def __init__( self ):
154 super( VomsCommand, self ).__init__() 155 156 self.currentOpts = {} 157 self.infoOpts = {} 158 self.destroyOpts = {}
159 160 for commandSet in [ GridCommand, VomsCommand ]: 161 registerCommandSet( commandSet ) 162 163 # global proxy info cache 164 _infoCache = {} 165
166 -class GridProxy ( ICredential ):
167 """ 168 Class for working with Grid proxy 169 """ 170 171 _schema = ICredential._schema.inherit_copy() 172 _schema.datadict[ "voms" ] = SimpleItem( defvalue = "", doc = \ 173 "Virtual organisation managment system information" ) 174 _schema.datadict[ "init_opts" ] = SimpleItem( defvalue = "", doc = \ 175 "String of options to be passed to command for proxy creation" ) 176 _schema.datadict[ "info_refresh_time" ] = SimpleItem( defvalue = "00:15", \ 177 doc = "Refresh time of proxy info cache" ) 178 _name = "GridProxy" 179 _hidden = 1 180 _enable_config = 1 181 _exportmethods = [ "create", "destroy", "identity", "info", "isAvailable", \ 182 "isValid", "location", "renew", "timeleft", "voname", "fullIdentity" ] 183
184 - def __init__( self, middleware = "EDG" ):
185 super( GridProxy, self ).__init__() 186 self.middleware = middleware 187 if self.middleware: 188 self.shell = getShell( self.middleware ) 189 self.gridCommand = GridCommand() 190 self.vomsCommand = VomsCommand() 191 self.chooseCommandSet() 192 return
193
194 - def chooseCommandSet( self ):
195 """ 196 Choose command set to be used for proxy-related commands 197 198 No arguments other than self 199 200 If self.voms has a null value then the GridCommand set of commands 201 is used. Otherwise the VomsCommand set of commands is used. 202 203 Return value: None 204 """ 205 if ( "ICommandSet" == self.command._name ): 206 if self.voms: 207 self.command = self.vomsCommand 208 else: 209 self.command = self.gridCommand 210 return None
211 212 # Populate the self.command.currentOpts dictionary with 213 # GridProxy specific options.
214 - def buildOpts( self, command, clear = True ):
215 if command == self.command.init: 216 if clear: 217 self.command.currentOpts.clear() 218 if self.command.init_parameters.has_key( "voms" ): 219 if self.voms: 220 self.command.currentOpts\ 221 [ self.command.init_parameters[ 'voms' ] ] = self.voms 222 if self.command.init_parameters.has_key( "valid" ): 223 if self.validityAtCreation: 224 self.command.currentOpts\ 225 [ self.command.init_parameters[ 'valid' ] ] \ 226 = self.validityAtCreation 227 if self.init_opts: 228 self.command.currentOpts[ '' ] = self.init_opts 229 elif command == self.command.destroy: 230 if clear: 231 self.command.destroyOpts.clear() 232 elif command == self.command.info: 233 if clear: 234 self.command.infoOpts.clear()
235
236 - def create\ 237 ( self, validity = "", maxTry = 0, minValidity = "", check = False ):
238 self.chooseCommandSet() 239 self.buildOpts( self.command.init ) 240 status = ICredential.create( self, validity, maxTry, minValidity, check ) 241 return status
242
243 - def destroy( self, allowed_exit = [ 0, 1 ] ):
244 self.chooseCommandSet() 245 self.buildOpts( self.command.destroy ) 246 return ICredential.destroy( self, allowed_exit )
247
248 - def isAvailable( self ):
249 if self.shell: 250 return True 251 else: 252 return False
253
254 - def isValid( self, validity = "", log = False, force_check = False ):
255 256 # Do parent check 257 if not ICredential.isValid( self, validity, log, force_check ): 258 return False 259 260 # check vo names 261 if self.voname() != self.voms: 262 if log: 263 logger.warning("Grid Proxy not valid. Certificate VO '%s' does not match requested '%s'" 264 % (self.voname(), self.voms)) 265 return False 266 267 return True
268
269 - def location( self ):
270 271 proxyPath = self.info( "-path" ).strip() 272 273 if not os.path.exists( proxyPath ): 274 proxyPath = "" 275 276 return proxyPath
277
278 - def fullIdentity( self, safe = False ):
279 """ 280 Return the users full identity as a dictionary 281 282 Argument: 283 safe - logical flag 284 => False : return identity exactly as obtained from proxy 285 => True : return identity after stripping out 286 non-alphanumeric characters 287 288 Return value: Dictionary of the various labels in the users DN 289 """ 290 291 ele_dict = {} 292 293 subjectList = self.info( opt = "-identity" ).split( "/" ) 294 295 for subjectElement in subjectList: 296 element = subjectElement.strip() 297 if element.find("=") == -1: 298 continue 299 300 field, val = element.split("=") 301 if safe: 302 val = re.sub( "[^a-zA-Z0-9]", "" ,val ) 303 ele_dict[field] = val 304 305 return ele_dict
306 307
308 - def identity( self, safe = False ):
309 """ 310 Return user's identify 311 312 Argument: 313 safe - logical flag 314 => False : return identity exactly as obtained from proxy 315 => True : return identity after stripping out 316 non-alphanumeric characters 317 318 => The identity is determined from the user proxy if possible, 319 or otherwise from the user's top-level directory 320 321 Return value: String specifying user identity 322 """ 323 324 cn = os.path.basename( os.path.expanduser( "~" ) ) 325 try: 326 subjectList = self.info( opt = "-identity" ).split( "/" ) 327 subjectList.reverse() 328 for subjectElement in subjectList: 329 element = subjectElement.strip() 330 try: 331 cn = element.split( "CN=" )[ 1 ].strip() 332 if cn != "proxy": 333 break 334 except IndexError: 335 pass 336 except: 337 pass 338 339 id = "".join( cn.split() ) 340 if safe: 341 id = re.sub( "[^a-zA-Z0-9]", "" ,id ) 342 343 return id
344
345 - def info( self, opt = "", force_check = False ):
346 """ 347 Obtain proxy information 348 349 Arguments other than self: 350 opt - String of options to be used when querying 351 proxy information 352 => Help on valid options can be obtained using: 353 info( opt = "-help" ) 354 force_check - Force credential check, rather than relying on cache 355 356 Return value: Output from result of querying proxy 357 """ 358 359 # use cached version of this command call if possible 360 output = self.getProxyCacheValue( opt ) 361 362 if ( force_check ) or ( output == "" ): 363 self.chooseCommandSet() 364 infoCommand = " ".join( [ self.command.info, opt ] ) 365 status, output, message = self.shell.cmd1\ 366 ( cmd = infoCommand, allowed_exit = range( 1000 ) ) 367 368 self.addToProxyCache( status, output, opt ) 369 370 if not output: 371 output = "" 372 373 return output
374
375 - def renew( self, validity = "", maxTry = 0, minValidity = "", check = True ):
376 self.chooseCommandSet() 377 if self.voms: 378 if not self.voname(): 379 check = False 380 return ICredential.renew( self, validity, maxTry, minValidity, check )
381
382 - def timeleft( self, units = "hh:mm:ss", force_check = False ):
383 return ICredential.timeleft( self, units, force_check )
384
385 - def timeleftInHMS( self, force_check = False ):
386 global _infoCache 387 388 output = self.getProxyCacheValue( "timeleftInHMS" ) 389 status = 0 390 391 if ( force_check ) or ( output == "" ): 392 # should really use the 'info' method 393 self.chooseCommandSet() 394 infoList = [ self.command.info ] 395 # Append option value pairs 396 for optName, optVal in self.command.infoOpts.iteritems(): 397 infoList.append( "%s %s" % ( optName, optVal ) ) 398 status, output, message = self.shell.cmd1\ 399 ( cmd = " ".join( infoList ), allowed_exit = range( 1000 ) ) 400 401 self.addToProxyCache( status, output, "timeleftInHMS" ) 402 403 timeRemaining = "00:00:00" 404 405 if status: 406 if ( 1 + output.lower().find( "command not found" ) ): 407 logger.warning( "Command '" + self.command.info + "' not found" ) 408 logger.warning( "Unable to obtain information on Grid proxy" ) 409 timeRemaining = "" 410 if _infoCache.has_key( "timeleftInHMS" ): 411 del _infoCache[ "timeleftInHMS" ] 412 413 if timeRemaining: 414 lineList = output.split( "\n" ) 415 for line in lineList: 416 if ( 1 + line.find( "Couldn't find a valid proxy" ) ): 417 timeRemaining = "-1" 418 if _infoCache.has_key('timeleftInHMS'): 419 del _infoCache['timeleftInHMS'] 420 break 421 elif ( 1 + line.find( "timeleft" ) ): 422 elementList = line.split() 423 timeRemaining = elementList[ 2 ] 424 break 425 426 return timeRemaining
427
428 - def voname( self, force_check = False ):
429 """ 430 Obtain name of virtual organisation from proxy 431 432 Argument other than self: 433 force_check - Force credential check, rather than relying on cache 434 435 Return value: Name of virtual organisation where this can be determined 436 (voms proxy), or empty string otherwise (globus proxy) 437 """ 438 global _infoCache 439 output = self.getProxyCacheValue( "voname" ) 440 441 if ( force_check ) or ( output == "" ): 442 self.chooseCommandSet() 443 infoCommand = "" 444 445 if self.command.info_parameters.has_key( "vo" ): 446 if self.command.info: 447 infoCommand = " ".join\ 448 ( [ self.command.info, self.command.info_parameters[ "vo" ] ] ) 449 else: 450 infoCommand = self.command.info 451 452 if infoCommand: 453 status, output, message = self.shell.cmd1( cmd = infoCommand, \ 454 allowed_exit = range( 1000 ), capture_stderr = True ) 455 456 self.addToProxyCache( status, output, "voname" ) 457 458 else: 459 output = "" 460 461 if not output: 462 output = "" 463 if _infoCache.has_key( "voname" ): 464 del _infoCache[ "voname" ] 465 466 output = output.strip() 467 468 for error in [ "VOMS extension not found", "unrecognized option" ]: 469 if output.find( error ) != -1: 470 output = "" 471 if _infoCache.has_key('voname'): 472 del _infoCache['voname'] 473 break 474 475 # Check for reasonable output (single-word VO) 476 if len( output.split() ) != 1: 477 output = self.voms 478 479 return output
480
481 - def getProxyCacheValue( self, opt ):
482 """ 483 Check the proxy cache for the required key. Make sure the proxy 484 file is older than the last check. 485 486 opt - the key to check for 487 """ 488 489 global _infoCache 490 491 info_refresh = self.timeInSeconds( self.info_refresh_time ) 492 output = '' 493 path = '' 494 495 # check when the grid proxy was created 496 if not _infoCache.has_key( '-path' ) or ( _infoCache[ '-path' ][ 1 ] < ( time.time() - info_refresh ) ): 497 self.chooseCommandSet() 498 infoCommand = " ".join( [ self.command.info, '-path' ] ) 499 status, output, message = self.shell.cmd1\ 500 ( cmd = infoCommand, allowed_exit = range( 1000 ) ) 501 502 if not status: 503 path = output 504 self.addToProxyCache( status, output, '-path') 505 506 else: 507 path = _infoCache[ '-path' ][0] 508 509 path = path.strip() 510 if not os.path.exists(path): 511 # blank the cache as the proxy isn't there 512 _infoCache = {} 513 return '' 514 515 # we're OK to use the cache 516 if _infoCache.has_key( opt ) and ( _infoCache[ opt ][ 1 ] > ( time.time() - info_refresh ) ) and ( _infoCache[ opt ][ 1 ] > os.path.getmtime(path) ): 517 output = _infoCache[ opt ][ 0 ] 518 else: 519 output = "" 520 521 return output
522
523 - def addToProxyCache( self, status, output, opt ):
524 """ 525 Test the result of grid proxy call 526 and add to the cache if all OK 527 528 status - the status output 529 output - the output text 530 opt - opt to add 531 """ 532 533 if ( not status ) and ( output ): 534 error = False 535 for line in output.split('\n'): 536 if ( 1 + line.find( "Couldn't find a valid proxy" ) ): 537 error = True 538 539 if not error: 540 _infoCache[ opt ] = [ output, time.time() ] 541 542 return None
543 544 # Add documentation strings from base class 545 for method in [ create, destroy, isAvailable, isValid, location, \ 546 renew, timeleft, timeleftInHMS ]: 547 if hasattr( ICredential, method.__name__ ): 548 baseMethod = getattr( ICredential, method.__name__ ) 549 setattr( method, "__doc__",\ 550 baseMethod.__doc__.replace( "credential", "Grid Proxy" ) )
551