File indexing completed on 2023-03-17 10:46:31
0001 from __future__ import print_function
0002 __author__ = 'Giacomo Govi'
0003
0004 import sqlalchemy
0005 import sqlalchemy.ext.declarative
0006 import subprocess
0007 from datetime import datetime
0008 import os
0009 import sys
0010 import logging
0011 import string
0012 import json
0013
0014 import CondCore.Utilities.credentials as auth
0015
0016 prod_db_service = 'cms_orcon_prod'
0017 dev_db_service = 'cms_orcoff_prep'
0018 schema_name = 'CMS_CONDITIONS'
0019 sqlalchemy_tpl = 'oracle://%s:%s@%s'
0020 coral_tpl = 'oracle://%s/%s'
0021 private_db = 'sqlite:///o2o_jobs.db'
0022 startStatus = -1
0023 messageLevelEnvVar = 'O2O_LOG_LEVEL'
0024 logFolderEnvVar = 'O2O_LOG_FOLDER'
0025 logger = logging.getLogger(__name__)
0026
0027 _Base = sqlalchemy.ext.declarative.declarative_base()
0028
0029 class O2OJob(_Base):
0030 __tablename__ = 'O2O_JOB'
0031 __table_args__ = {'schema' : schema_name}
0032 name = sqlalchemy.Column(sqlalchemy.String(100), primary_key=True)
0033 enabled = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
0034 frequent = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
0035 tag_name = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
0036 interval = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
0037
0038 class O2OJobConf(_Base):
0039 __tablename__ = 'O2O_JOB_CONF'
0040 __table_args__ = {'schema' : schema_name}
0041 job_name = sqlalchemy.Column(sqlalchemy.ForeignKey(O2OJob.name), primary_key=True)
0042 insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, primary_key=True)
0043 configuration = sqlalchemy.Column(sqlalchemy.String(4000), nullable=False)
0044
0045 job = sqlalchemy.orm.relationship('O2OJob', primaryjoin="O2OJob.name==O2OJobConf.job_name")
0046
0047 class O2ORun(_Base):
0048 __tablename__ = 'O2O_RUN'
0049 __table_args__ = {'schema' : schema_name}
0050 job_name = sqlalchemy.Column(sqlalchemy.ForeignKey(O2OJob.name), primary_key=True)
0051 start_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, primary_key=True)
0052 end_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=True)
0053 status_code = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
0054 log = sqlalchemy.Column(sqlalchemy.CLOB, nullable=True)
0055
0056 job = sqlalchemy.orm.relationship('O2OJob', primaryjoin="O2OJob.name==O2ORun.job_name")
0057
0058 def print_table( headers, table ):
0059 ws = []
0060 for h in headers:
0061 ws.append(len(h))
0062 for row in table:
0063 ind = 0
0064 for c in row:
0065 if ind<len(ws):
0066 if len(c)> ws[ind]:
0067 ws[ind] = len(c)
0068 ind += 1
0069
0070 def printf( row ):
0071 line = ''
0072 ind = 0
0073 for w in ws:
0074 fmt = '{:<%s}' %w
0075 if ind<len(ws):
0076 line += (fmt.format( row[ind] )+' ')
0077 ind += 1
0078 print(line)
0079 printf( headers )
0080 hsep = ''
0081 for w in ws:
0082 fmt = '{:-<%s}' %w
0083 hsep += (fmt.format('')+' ')
0084 print(hsep)
0085 for row in table:
0086 printf( row )
0087
0088
0089 class O2OJobMgr(object):
0090
0091 def __init__( self , logLevel):
0092 self.db_connection = None
0093 self.conf_dict = {}
0094 fmt_str = "[%(asctime)s] %(levelname)s: %(message)s"
0095 if messageLevelEnvVar in os.environ:
0096 levStr = os.environ[messageLevelEnvVar]
0097 if levStr == 'DEBUG':
0098 logLevel = logging.DEBUG
0099 logFormatter = logging.Formatter(fmt_str)
0100
0101 self.logger = logging.getLogger()
0102 self.logger.setLevel(logLevel)
0103 consoleHandler = logging.StreamHandler(sys.stdout)
0104 consoleHandler.setFormatter(logFormatter)
0105 self.logger.addHandler(consoleHandler)
0106 self.eng = None
0107
0108 def getSession( self, db_service, role, authPath ):
0109 url = None
0110 if db_service is None:
0111 url = private_db
0112 else:
0113 self.logger.info('Getting credentials')
0114 if authPath is not None:
0115 if not os.path.exists(authPath):
0116 self.logger.error('Authentication path %s is invalid.' %authPath)
0117 return None
0118 try:
0119 (username, account, pwd) = auth.get_credentials_for_schema( db_service, schema_name, role, authPath )
0120 except Exception as e:
0121 self.logger.debug(str(e))
0122 username = None
0123 pwd = None
0124 if username is None:
0125 self.logger.error('Credentials for service %s are not available' %db_service)
0126 raise Exception("Cannot connect to db %s" %db_service )
0127 url = sqlalchemy_tpl %(username,pwd,db_service)
0128 session = None
0129 try:
0130 self.eng = sqlalchemy.create_engine( url, max_identifier_length=30)
0131 session = sqlalchemy.orm.scoped_session( sqlalchemy.orm.sessionmaker(bind=self.eng))
0132 except sqlalchemy.exc.SQLAlchemyError as dberror:
0133 self.logger.error( str(dberror) )
0134 return session
0135
0136 def connect( self, service, args ):
0137 self.session = self.getSession( service, args.role, args.auth )
0138 self.verbose = args.verbose
0139 if self.session is None:
0140 return False
0141 else:
0142 self.db_connection = coral_tpl %(service,schema_name)
0143 self.conf_dict['db']=self.db_connection
0144 return True
0145 def runManager( self ):
0146 return O2ORunMgr( self.db_connection, self.session, self.logger )
0147
0148 def add( self, job_name, configJson, int_val, freq_flag, en_flag ):
0149 if configJson == '':
0150 return False
0151 res = self.session.query(O2OJob.enabled).filter_by(name=job_name)
0152 enabled = None
0153 for r in res:
0154 enabled = r
0155 if enabled:
0156 self.logger.error( "A job called '%s' exists already.", job_name )
0157 return False
0158 freq_val = 0
0159 if freq_flag:
0160 freq_val = 1
0161 job = O2OJob(name=job_name,tag_name='-',enabled=en_flag,frequent=freq_val,interval=int_val)
0162 config = O2OJobConf( job_name=job_name, insertion_time = datetime.utcnow(), configuration = configJson )
0163 self.session.add(job)
0164 self.session.add(config)
0165 self.session.commit()
0166 self.logger.info( "New o2o job '%s' created.", job_name )
0167 return True
0168
0169 def set( self, job_name, en_flag, fr_val=None ):
0170 res = self.session.query(O2OJob.enabled).filter_by(name=job_name)
0171 enabled = None
0172 for r in res:
0173 enabled = r
0174 if enabled is None:
0175 self.logger.error( "A job called '%s' does not exist.", job_name )
0176 return
0177 if en_flag is not None and enabled != en_flag:
0178 job = O2OJob(name=job_name,enabled=en_flag)
0179 self.session.merge(job)
0180 self.session.commit()
0181 action = 'enabled'
0182 if not en_flag:
0183 action = 'disabled'
0184 self.logger.info( "Job '%s' %s." %(job_name,action) )
0185 if fr_val is not None:
0186 job = O2OJob(name=job_name,frequent=fr_val)
0187 self.session.merge(job)
0188 self.session.commit()
0189 if fr_val==1:
0190 self.logger.info( "Job '%s' set 'frequent'" %job_name)
0191 else:
0192 self.logger.info( "Job '%s' unset 'frequent'" %job_name)
0193
0194 def setConfig( self, job_name, configJson ):
0195 if configJson == '':
0196 return False
0197 res = self.session.query(O2OJob.enabled).filter_by(name=job_name)
0198 enabled = None
0199 for r in res:
0200 enabled = r
0201 if enabled is None:
0202 self.logger.error( "A job called '%s' does not exist.", job_name )
0203 return
0204 config = O2OJobConf( job_name=job_name, insertion_time = datetime.utcnow(), configuration = configJson )
0205 self.session.add(config)
0206 self.session.commit()
0207 self.logger.info( "New configuration inserted for job '%s'", job_name )
0208 return True
0209
0210 def setInterval( self, job_name, int_val ):
0211 res = self.session.query(O2OJob.enabled).filter_by(name=job_name)
0212 enabled = None
0213 for r in res:
0214 enabled = r
0215 if enabled is None:
0216 self.logger.error( "A job called '%s' does not exist.", job_name )
0217 return
0218 job = O2OJob(name=job_name,interval=int_val)
0219 self.session.merge(job)
0220 self.session.commit()
0221 self.logger.info( "The execution interval for job '%s' has been updated.", job_name )
0222
0223 def listJobs( self ):
0224 runs = {}
0225 res = self.session.query(O2ORun.job_name,sqlalchemy.func.max(O2ORun.start_time)).group_by(O2ORun.job_name).order_by(O2ORun.job_name)
0226 for r in res:
0227 runs[r[0]] = str(r[1])
0228 res = self.session.query(O2OJob.name, O2OJob.interval, O2OJob.enabled, O2OJob.frequent).order_by(O2OJob.name).all()
0229 table = []
0230 for r in res:
0231 row = []
0232 row.append(r[0]),
0233 row.append('%5d' %r[1] )
0234 frequent = 'Y' if (r[3]==1) else 'N'
0235 row.append('%4s' %frequent )
0236 enabled = 'Y' if (r[2]==1) else 'N'
0237 row.append('%4s' %enabled )
0238 lastRun = '-'
0239 if r[0] in runs.keys():
0240 lastRun = runs[r[0]]
0241 row.append( lastRun )
0242 table.append(row)
0243 headers = ['Job name','Interval','Frequent','Enabled','Last run start']
0244 print_table( headers, table )
0245
0246 def listConfig( self, jname ):
0247 res = self.session.query(O2OJob.enabled).filter_by(name=jname)
0248 enabled = None
0249 for r in res:
0250 enabled = r
0251 if enabled is None:
0252 self.logger.error( "A job called '%s' does not exist.", jname )
0253 return
0254 res = self.session.query( O2OJobConf.configuration, O2OJobConf.insertion_time ).filter_by(job_name=jname).order_by(O2OJobConf.insertion_time)
0255 configs = []
0256 for r in res:
0257 configs.append((str(r[0]),r[1]))
0258 ind = len(configs)
0259 if ind:
0260 print("Configurations for job '%s'" %jname)
0261 for cf in reversed(configs):
0262 print('#%2d since: %s' %(ind,cf[1]))
0263 print(cf[0])
0264 ind -= 1
0265 else:
0266 self.logger.info("No configuration found for job '%s'" %jname )
0267
0268 def dumpConfig( self, jname, versionIndex, configFile ):
0269 versionIndex = int(versionIndex)
0270 res = self.session.query(O2OJob.enabled).filter_by(name=jname)
0271 enabled = None
0272 for r in res:
0273 enabled = r
0274 if enabled is None:
0275 self.logger.error( "A job called '%s' does not exist.", jname )
0276 return
0277 res = self.session.query( O2OJobConf.configuration, O2OJobConf.insertion_time ).filter_by(job_name=jname).order_by(O2OJobConf.insertion_time)
0278 configs = []
0279 for r in res:
0280 configs.append((str(r[0]),r[1]))
0281 ind = len(configs)
0282 if versionIndex>ind or versionIndex==0:
0283 self.logger.error("Configuration for job %s with index %s has not been found." %(jname,versionIndex))
0284 return
0285 print("Configuration #%2d for job '%s'" %(versionIndex,jname))
0286 config = configs[versionIndex-1]
0287 print('#%2d since %s' %(versionIndex,config[1]))
0288 print(config[0])
0289 if configFile is None or configFile == '':
0290 configFile = '%s_%s.json' %(jname,versionIndex)
0291 with open(configFile,'w') as json_file:
0292 json_file.write(config[0])
0293
0294
0295 class O2ORunMgr(object):
0296
0297 def __init__( self, db_connection, session, logger ):
0298 self.job_name = None
0299 self.start = None
0300 self.end = None
0301 self.conf_dict = {}
0302 self.conf_dict['db'] = db_connection
0303 self.session = session
0304 self.logger = logger
0305
0306 def startJob( self, job_name ):
0307 self.logger.info('Checking job %s', job_name)
0308 exists = None
0309 enabled = None
0310 try:
0311 res = self.session.query(O2OJob.enabled,O2OJob.tag_name).filter_by(name=job_name)
0312 for r in res:
0313 exists = True
0314 enabled = int(r[0])
0315 self.tag_name = str(r[1])
0316 if exists is None:
0317 self.logger.error( 'The job %s is unknown.', job_name )
0318 return 2
0319 if enabled:
0320 res = self.session.query(O2OJobConf.configuration).filter_by(job_name=job_name).order_by(sqlalchemy.desc(O2OJobConf.insertion_time)).first()
0321 conf = None
0322 for r in res:
0323 conf = str(r)
0324 if conf is None:
0325 self.logger.warning("No configuration found for job '%s'" %job_name )
0326 else:
0327 try:
0328 self.conf_dict.update( json.loads(conf) )
0329 self.logger.info('Using configuration: %s ' %conf)
0330 except Exception as e:
0331 self.logger.error( str(e) )
0332 return 6
0333 self.job_name = job_name
0334 self.start = datetime.utcnow()
0335 run = O2ORun(job_name=self.job_name,start_time=self.start,status_code=startStatus)
0336 self.session.add(run)
0337 self.session.commit()
0338 return 0
0339 else:
0340 self.logger.info( 'The job %s has been disabled.', job_name )
0341 return 5
0342 except sqlalchemy.exc.SQLAlchemyError as dberror:
0343 self.logger.error( str(dberror) )
0344 return 7
0345 return -1
0346
0347
0348 def endJob( self, status, log ):
0349 self.end = datetime.utcnow()
0350 try:
0351 run = O2ORun(job_name=self.job_name,start_time=self.start,end_time=self.end,status_code=status,log=log)
0352 self.session.merge(run)
0353 self.session.commit()
0354 self.logger.info( 'Job %s ended.', self.job_name )
0355 return 0
0356 except sqlalchemy.exc.SQLAlchemyError as dberror:
0357 self.logger.error( str(dberror) )
0358 return 8
0359
0360 def executeJob( self, args ):
0361 job_name = args.name
0362 command = args.executable
0363 logFolder = os.getcwd()
0364 if logFolderEnvVar in os.environ:
0365 logFolder = os.environ[logFolderEnvVar]
0366 datelabel = datetime.utcnow().strftime("%y-%m-%d-%H-%M-%S")
0367 logFileName = '%s-%s.log' %(job_name,datelabel)
0368 logFile = os.path.join(logFolder,logFileName)
0369 started = self.startJob( job_name )
0370 if started !=0:
0371 return started
0372 ret = -1
0373 try:
0374
0375 command = command %(self.conf_dict)
0376
0377 command = command.format(**self.conf_dict )
0378 except KeyError as exc:
0379 self.logger.error( "Unresolved template key %s in the command." %str(exc) )
0380 return 3
0381 self.logger.info('Command: "%s"', command )
0382 out = ''
0383 try:
0384 self.logger.info('Executing command...' )
0385 pipe = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
0386 for line in pipe.stdout:
0387 if args.verbose is not None and args.verbose>=1:
0388 sys.stdout.write(line.decode())
0389 sys.stdout.flush()
0390 out += line.decode()
0391 pipe.communicate()
0392 self.logger.info( 'Command returned code: %s' %pipe.returncode )
0393 ret = pipe.returncode
0394 except Exception as e:
0395 self.logger.error( str(e) )
0396 return 4
0397 ended = self.endJob( pipe.returncode, out )
0398 if ended != 0:
0399 ret = ended
0400 with open(logFile,'a') as logF:
0401 logF.write(out)
0402 return ret
0403
0404 def readConfiguration( config_filename ):
0405 config = ''
0406 try:
0407 with open( config_filename, 'r' ) as config_file:
0408 config = config_file.read().strip('\n')
0409 if config == '':
0410 logging.error( 'The file %s contains an empty string.', config_filename )
0411 else:
0412 json.loads(config)
0413 except IOError as e:
0414 logging.error( 'The file %s cannot be open.', config_filename )
0415 except ValueError as e:
0416 config = ''
0417 logging.error( 'The file "%s" contains an invalid json string.', config_filename )
0418 return config
0419
0420 def checkConfiguration( config_string ):
0421 config = config_string
0422 try:
0423 json.loads(config)
0424 except ValueError as e:
0425 config = ''
0426 logging.error( 'The string "%s" is an invalid json format.', config_string )
0427 return config
0428
0429
0430 import optparse
0431 import argparse
0432
0433 class O2OTool():
0434
0435 def execute(self):
0436 parser = argparse.ArgumentParser(description='CMS o2o command-line tool. For general help (manual page), use the help subcommand.')
0437 parser.add_argument('--db', type=str, help='The target database: pro ( for prod ) or dev ( for prep ). default=pro')
0438 parser.add_argument("--auth","-a", type=str, help="The path of the authentication file")
0439 parser.add_argument('--verbose', '-v', action='count', help='The verbosity level')
0440 parser_subparsers = parser.add_subparsers(title='Available subcommands')
0441 parser_create = parser_subparsers.add_parser('create', description='Create a new O2O job')
0442 parser_create.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0443 parser_create.add_argument('--configFile', '-c', type=str, help='the JSON configuration file path')
0444 parser_create.add_argument('--configString', '-s', type=str, help='the JSON configuration string')
0445 parser_create.add_argument('--interval', '-i', type=int, help='the chron job interval',default=0)
0446 parser_create.add_argument('--frequent', '-f',action='store_true',help='set the "frequent" flag for this job ("false" by default)')
0447 parser_create.set_defaults(func=self.create,role=auth.admin_role)
0448 parser_setConfig = parser_subparsers.add_parser('setConfig', description='Set a new configuration for the specified job. The configuration is expected as a list of entries "param": "value" (dictionary). The "param" labels will be used to inject the values in the command to execute. The dictionary is stored in JSON format.')
0449 parser_setConfig.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0450 parser_setConfig.add_argument('--configFile', '-c', type=str, help='the JSON configuration file path')
0451 parser_setConfig.add_argument('--configString', '-s', type=str, help='the JSON configuration string')
0452 parser_setConfig.set_defaults(func=self.setConfig,role=auth.admin_role)
0453 parser_setFrequent = parser_subparsers.add_parser('setFrequent',description='Set the "frequent" flag for the specified job')
0454 parser_setFrequent.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0455 parser_setFrequent.add_argument('--flag', '-f', choices=['0','1'], help='the flag value to set',required=True)
0456 parser_setFrequent.set_defaults(func=self.setFrequent,role=auth.admin_role)
0457 parser_setInterval = parser_subparsers.add_parser('setInterval',description='Set a new execution interval for the specified job')
0458 parser_setInterval.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0459 parser_setInterval.add_argument('--interval', '-i', type=int, help='the chron job interval',required=True)
0460 parser_setInterval.set_defaults(func=self.setInterval,role=auth.admin_role)
0461 parser_enable = parser_subparsers.add_parser('enable',description='enable the O2O job')
0462 parser_enable.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0463 parser_enable.set_defaults(func=self.enable,role=auth.admin_role)
0464 parser_disable = parser_subparsers.add_parser('disable',description='disable the O2O job')
0465 parser_disable.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0466 parser_disable.set_defaults(func=self.disable,role=auth.admin_role)
0467 parser_listJobs = parser_subparsers.add_parser('listJobs', description='list the registered jobs')
0468 parser_listJobs.set_defaults(func=self.listJobs,role=auth.reader_role)
0469 parser_listConf = parser_subparsers.add_parser('listConfig', description='shows the configurations for the specified job')
0470 parser_listConf.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0471 parser_listConf.add_argument('--dump', type=int, help='Dump the specified config.',default=0)
0472 parser_listConf.set_defaults(func=self.listConf,role=auth.reader_role)
0473 parser_dumpConf = parser_subparsers.add_parser('dumpConfig', description='dumps a specific job configuration version')
0474 parser_dumpConf.add_argument('versionIndex', type=str,help='the version to dump')
0475 parser_dumpConf.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0476 parser_dumpConf.add_argument('--configFile', '-c', type=str, help='the JSON configuration file name - default:[jobname]_[version].json')
0477 parser_dumpConf.set_defaults(func=self.dumpConf,role=auth.reader_role)
0478 parser_run = parser_subparsers.add_parser('run', description='Wrapper for O2O jobs execution. Supports input parameter injection from the configuration file associated to the job. The formatting syntax supported are the python ones: "command -paramName {paramLabel}" or "command -paramName %(paramLabel)s". where [paramName] is the name of the parameter required for the command, and [paramLabel] is the key of the parameter entry in the config dictionary (recommended to be equal for clarity!"')
0479 parser_run.add_argument('executable', type=str,help='command to execute')
0480 parser_run.add_argument('--name', '-n', type=str, help='The o2o job name',required=True)
0481 parser_run.set_defaults(func=self.run,role=auth.writer_role)
0482
0483 args = parser.parse_args()
0484
0485 if args.verbose is not None and args.verbose >=1:
0486 self.setup(args)
0487 return args.func()
0488 else:
0489 try:
0490 self.setup(args)
0491 sys.exit( args.func())
0492 except Exception as e:
0493 logging.error(e)
0494 sys.exit(1)
0495
0496 def setup(self, args):
0497 self.args = args
0498 db_service = prod_db_service
0499 if args.db is not None:
0500 if args.db == 'dev' or args.db == 'oradev' :
0501 db_service = dev_db_service
0502 elif args.db != 'orapro' and args.db != 'onlineorapro' and args.db != 'pro':
0503 raise Exception("Database '%s' is not known." %args.db )
0504
0505 logLevel = logging.DEBUG if args.verbose is not None and args.verbose >= 1 else logging.INFO
0506 self.mgr = O2OJobMgr( logLevel )
0507 return self.mgr.connect( db_service, args )
0508
0509 def create(self):
0510 configJson = None
0511 if self.args.configFile is not None:
0512 if self.args.configString is not None:
0513 logging.error('Ambigouous input provided: please specify a configFile OR a configString')
0514 return False
0515 else:
0516 configJson = readConfiguration( self.args.configFile )
0517 else:
0518 if self.args.configString is None:
0519 logging.error('No configuration has been provided: please specify "configFile" or "configString" param.')
0520 return False
0521 else:
0522 configJson = checkConfiguration( self.args.configString )
0523 self.mgr.add( self.args.name, configJson, self.args.interval, self.args.frequent, True )
0524
0525 def setConfig(self):
0526 configJson = None
0527 if self.args.configFile is not None:
0528 if self.args.configString is not None:
0529 logging.error('Ambigouous input provided: please specify a configFile OR a configString')
0530 return False
0531 else:
0532 configJson = readConfiguration( self.args.configFile )
0533 else:
0534 if self.args.configString is None:
0535 logging.error('No configuration has been provided: please specify "configFile" or "configString" param.')
0536 return False
0537 else:
0538 configJson = checkConfiguration( self.args.configString )
0539 self.mgr.setConfig( self.args.name, configJson )
0540
0541 def setInterval(self):
0542 self.mgr.setInterval( self.args.name, self.args.interval )
0543
0544 def enable(self):
0545 self.mgr.set( self.args.name, True )
0546
0547 def disable(self):
0548 self.mgr.set( self.args.name, False )
0549
0550 def setFrequent(self):
0551 self.mgr.set( self.args.name, None, int(self.args.flag) )
0552
0553 def listJobs(self):
0554 self.mgr.listJobs()
0555
0556 def listConf(self):
0557 self.mgr.listConfig( self.args.name )
0558
0559 def dumpConf(self):
0560 self.mgr.dumpConfig( self.args.name, self.args.versionIndex, self.args.configFile )
0561
0562 def run(self):
0563 rmgr = self.mgr.runManager()
0564 return rmgr.executeJob( self.args )