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