panda-server-source 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panda_server_source-0.8.0.dist-info/METADATA +55 -0
- panda_server_source-0.8.0.dist-info/RECORD +375 -0
- panda_server_source-0.8.0.dist-info/WHEEL +4 -0
- panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
- pandajedi/__init__.py +0 -0
- pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
- pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
- pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
- pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
- pandajedi/jedibrokerage/GenJobBroker.py +381 -0
- pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
- pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
- pandajedi/jedibrokerage/__init__.py +0 -0
- pandajedi/jediconfig/__init__.py +0 -0
- pandajedi/jediconfig/jedi_config.py +40 -0
- pandajedi/jedicore/FactoryBase.py +182 -0
- pandajedi/jedicore/Interaction.py +433 -0
- pandajedi/jedicore/JediDBProxy.py +56 -0
- pandajedi/jedicore/JediDBProxyPool.py +37 -0
- pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
- pandajedi/jedicore/JediException.py +13 -0
- pandajedi/jedicore/JediTaskBuffer.py +984 -0
- pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
- pandajedi/jedicore/MsgWrapper.py +124 -0
- pandajedi/jedicore/ProcessUtils.py +34 -0
- pandajedi/jedicore/SiteCandidate.py +90 -0
- pandajedi/jedicore/ThreadUtils.py +205 -0
- pandajedi/jedicore/__init__.py +0 -0
- pandajedi/jediddm/AtlasDDMClient.py +1708 -0
- pandajedi/jediddm/DDMClientBase.py +16 -0
- pandajedi/jediddm/DDMInterface.py +83 -0
- pandajedi/jediddm/GenDDMClient.py +17 -0
- pandajedi/jediddm/__init__.py +0 -0
- pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
- pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
- pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
- pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
- pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
- pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
- pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
- pandajedi/jedidog/GenWatchDog.py +21 -0
- pandajedi/jedidog/JumboWatchDog.py +147 -0
- pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
- pandajedi/jedidog/WatchDogBase.py +53 -0
- pandajedi/jedidog/__init__.py +0 -0
- pandajedi/jediexec/reniceJEDI.py +25 -0
- pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
- pandajedi/jedigen/TaskGeneratorBase.py +15 -0
- pandajedi/jedigen/__init__.py +0 -0
- pandajedi/jedimsgprocessor/__init__.py +0 -0
- pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
- pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
- pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
- pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
- pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
- pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
- pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
- pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
- pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
- pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
- pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
- pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
- pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
- pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
- pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
- pandajedi/jediorder/ContentsFeeder.py +785 -0
- pandajedi/jediorder/JediDaemon.py +27 -0
- pandajedi/jediorder/JediKnight.py +55 -0
- pandajedi/jediorder/JediMaster.py +238 -0
- pandajedi/jediorder/JediMsgProcessor.py +32 -0
- pandajedi/jediorder/JobBroker.py +33 -0
- pandajedi/jediorder/JobGenerator.py +2763 -0
- pandajedi/jediorder/JobSplitter.py +308 -0
- pandajedi/jediorder/JobThrottler.py +28 -0
- pandajedi/jediorder/PostProcessor.py +166 -0
- pandajedi/jediorder/TaskBroker.py +268 -0
- pandajedi/jediorder/TaskCommando.py +334 -0
- pandajedi/jediorder/TaskGenerator.py +17 -0
- pandajedi/jediorder/TaskRefiner.py +611 -0
- pandajedi/jediorder/TaskSetupper.py +17 -0
- pandajedi/jediorder/WatchDog.py +79 -0
- pandajedi/jediorder/__init__.py +0 -0
- pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
- pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
- pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
- pandajedi/jedipprocess/GenPostProcessor.py +35 -0
- pandajedi/jedipprocess/MailTemplates.py +185 -0
- pandajedi/jedipprocess/PostProcessorBase.py +269 -0
- pandajedi/jedipprocess/__init__.py +0 -0
- pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
- pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
- pandajedi/jedirefine/GenTaskRefiner.py +61 -0
- pandajedi/jedirefine/RefinerUtils.py +154 -0
- pandajedi/jedirefine/TaskRefinerBase.py +996 -0
- pandajedi/jedirefine/__init__.py +0 -0
- pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
- pandajedi/jedisetup/GenTaskSetupper.py +16 -0
- pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
- pandajedi/jedisetup/TaskSetupperBase.py +15 -0
- pandajedi/jedisetup/__init__.py +0 -0
- pandajedi/jeditest/SchemaChecker.py +41 -0
- pandajedi/jeditest/addContWoSoTask.py +54 -0
- pandajedi/jeditest/addDataCarouselRequest.py +47 -0
- pandajedi/jeditest/addEventServiceTask.py +101 -0
- pandajedi/jeditest/addHpoTask.py +81 -0
- pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
- pandajedi/jeditest/addNonAtlasTask.py +71 -0
- pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
- pandajedi/jeditest/addTestTask.py +124 -0
- pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
- pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
- pandajedi/jeditest/brokerTest.py +78 -0
- pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
- pandajedi/jeditest/contentsFeederTest.py +28 -0
- pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
- pandajedi/jeditest/ddmTest.py +4 -0
- pandajedi/jeditest/jobGenForOneTask.py +38 -0
- pandajedi/jeditest/jobGeneratorTest.py +43 -0
- pandajedi/jeditest/jobSplitterTest.py +104 -0
- pandajedi/jeditest/jobThrottlerTest.py +22 -0
- pandajedi/jeditest/msgProcessorTest.py +9 -0
- pandajedi/jeditest/postProcessorTest.py +18 -0
- pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
- pandajedi/jeditest/resubmitTaskParam.py +20 -0
- pandajedi/jeditest/taskBrokerTest.py +22 -0
- pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
- pandajedi/jeditest/taskBufferTest.py +5 -0
- pandajedi/jeditest/taskCommandoTest.py +28 -0
- pandajedi/jeditest/taskRefinerTest.py +30 -0
- pandajedi/jeditest/testOneTaskToRefine.py +49 -0
- pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
- pandajedi/jeditest/testPandaDDMMap.py +62 -0
- pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
- pandajedi/jeditest/watchDogTest.py +25 -0
- pandajedi/jeditest/workQueueTest.py +22 -0
- pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
- pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
- pandajedi/jedithrottle/GenJobThrottler.py +26 -0
- pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
- pandajedi/jedithrottle/__init__.py +0 -0
- pandaserver/__init__.py +0 -0
- pandaserver/api/__init__.py +0 -0
- pandaserver/api/v1/__init__.py +0 -0
- pandaserver/api/v1/common.py +339 -0
- pandaserver/api/v1/credential_management_api.py +372 -0
- pandaserver/api/v1/data_carousel_api.py +264 -0
- pandaserver/api/v1/event_api.py +269 -0
- pandaserver/api/v1/file_server_api.py +942 -0
- pandaserver/api/v1/harvester_api.py +445 -0
- pandaserver/api/v1/http_client.py +166 -0
- pandaserver/api/v1/idds_api.py +180 -0
- pandaserver/api/v1/job_api.py +463 -0
- pandaserver/api/v1/metaconfig_api.py +122 -0
- pandaserver/api/v1/pilot_api.py +895 -0
- pandaserver/api/v1/statistics_api.py +207 -0
- pandaserver/api/v1/system_api.py +186 -0
- pandaserver/api/v1/task_api.py +1520 -0
- pandaserver/api/v1/tests/__init__.py +0 -0
- pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
- pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
- pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
- pandaserver/api/v1/tests/idds_api_tests.py +45 -0
- pandaserver/api/v1/tests/job_api_tests.py +112 -0
- pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
- pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
- pandaserver/api/v1/tests/system_api_tests.py +58 -0
- pandaserver/api/v1/tests/task_api_tests.py +590 -0
- pandaserver/api/v1/version.py +2 -0
- pandaserver/api/v1/workflow_api.py +136 -0
- pandaserver/brokerage/SiteMapper.py +406 -0
- pandaserver/brokerage/__init__.py +0 -0
- pandaserver/brokerage/broker.py +272 -0
- pandaserver/config/__init__.py +0 -0
- pandaserver/config/config_utils.py +15 -0
- pandaserver/config/daemon_config.py +29 -0
- pandaserver/config/mcp_config.py +38 -0
- pandaserver/config/panda_config.py +233 -0
- pandaserver/configurator/Carbon.py +65 -0
- pandaserver/configurator/Configurator.py +908 -0
- pandaserver/configurator/__init__.py +0 -0
- pandaserver/configurator/aux.py +73 -0
- pandaserver/daemons/__init__.py +0 -0
- pandaserver/daemons/master.py +110 -0
- pandaserver/daemons/master_systemd.py +79 -0
- pandaserver/daemons/scripts/__init__.py +0 -0
- pandaserver/daemons/scripts/add_main.py +264 -0
- pandaserver/daemons/scripts/add_sub.py +221 -0
- pandaserver/daemons/scripts/cache_pilots.py +47 -0
- pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
- pandaserver/daemons/scripts/carbon.py +50 -0
- pandaserver/daemons/scripts/configurator.py +94 -0
- pandaserver/daemons/scripts/copyArchive.py +1387 -0
- pandaserver/daemons/scripts/datasetManager.py +1154 -0
- pandaserver/daemons/scripts/dummy_test.py +25 -0
- pandaserver/daemons/scripts/evpPD2P.py +144 -0
- pandaserver/daemons/scripts/hs_scrapers.py +264 -0
- pandaserver/daemons/scripts/metric_collector.py +927 -0
- pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
- pandaserver/daemons/scripts/pilotStreaming.py +109 -0
- pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
- pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
- pandaserver/daemons/scripts/task_evaluator.py +405 -0
- pandaserver/daemons/scripts/tmpwatch.py +70 -0
- pandaserver/daemons/scripts/worker_synchronization.py +97 -0
- pandaserver/daemons/utils.py +753 -0
- pandaserver/dataservice/DataServiceUtils.py +323 -0
- pandaserver/dataservice/ErrorCode.py +25 -0
- pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
- pandaserver/dataservice/__init__.py +0 -0
- pandaserver/dataservice/activator.py +79 -0
- pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
- pandaserver/dataservice/adder_dummy_plugin.py +31 -0
- pandaserver/dataservice/adder_gen.py +954 -0
- pandaserver/dataservice/adder_plugin_base.py +27 -0
- pandaserver/dataservice/adder_result.py +75 -0
- pandaserver/dataservice/adder_simple_plugin.py +160 -0
- pandaserver/dataservice/closer.py +247 -0
- pandaserver/dataservice/closer_atlas_plugin.py +90 -0
- pandaserver/dataservice/ddm.py +1381 -0
- pandaserver/dataservice/ddm_handler.py +95 -0
- pandaserver/dataservice/dyn_data_distributer.py +824 -0
- pandaserver/dataservice/event_lookup_client_ei.py +77 -0
- pandaserver/dataservice/event_picker.py +268 -0
- pandaserver/dataservice/finisher.py +194 -0
- pandaserver/dataservice/setupper.py +225 -0
- pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
- pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
- pandaserver/dataservice/setupper_plugin_base.py +67 -0
- pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
- pandaserver/jobdispatcher/ErrorCode.py +19 -0
- pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
- pandaserver/jobdispatcher/Protocol.py +443 -0
- pandaserver/jobdispatcher/Watcher.py +186 -0
- pandaserver/jobdispatcher/__init__.py +0 -0
- pandaserver/pandamcp/__init__.py +0 -0
- pandaserver/pandamcp/mcp_main.py +56 -0
- pandaserver/pandamcp/mcp_test_client.py +95 -0
- pandaserver/pandamcp/mcp_utils.py +74 -0
- pandaserver/proxycache/__init__.py +0 -0
- pandaserver/proxycache/panda_proxy_cache.py +240 -0
- pandaserver/proxycache/token_cache.py +141 -0
- pandaserver/server/.gacl +6 -0
- pandaserver/server/panda.py +539 -0
- pandaserver/srvcore/CoreUtils.py +572 -0
- pandaserver/srvcore/MailUtils.py +106 -0
- pandaserver/srvcore/__init__.py +0 -0
- pandaserver/srvcore/allowed_methods.py +100 -0
- pandaserver/srvcore/oidc_utils.py +152 -0
- pandaserver/srvcore/panda_request.py +196 -0
- pandaserver/srvcore/srv_msg_utils.py +42 -0
- pandaserver/taskbuffer/ConBridge.py +513 -0
- pandaserver/taskbuffer/DBProxyPool.py +95 -0
- pandaserver/taskbuffer/DataCarousel.py +3053 -0
- pandaserver/taskbuffer/DatasetSpec.py +137 -0
- pandaserver/taskbuffer/DdmSpec.py +201 -0
- pandaserver/taskbuffer/ErrorCode.py +87 -0
- pandaserver/taskbuffer/EventServiceUtils.py +396 -0
- pandaserver/taskbuffer/FileSpec.py +290 -0
- pandaserver/taskbuffer/GlobalShares.py +244 -0
- pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
- pandaserver/taskbuffer/Initializer.py +82 -0
- pandaserver/taskbuffer/InputChunk.py +1080 -0
- pandaserver/taskbuffer/JediCacheSpec.py +57 -0
- pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
- pandaserver/taskbuffer/JediFileSpec.py +242 -0
- pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
- pandaserver/taskbuffer/JobSpec.py +1006 -0
- pandaserver/taskbuffer/JobUtils.py +232 -0
- pandaserver/taskbuffer/NucleusSpec.py +116 -0
- pandaserver/taskbuffer/OraDBProxy.py +174 -0
- pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
- pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
- pandaserver/taskbuffer/ParseJobXML.py +350 -0
- pandaserver/taskbuffer/PickleFileSpec.py +18 -0
- pandaserver/taskbuffer/PickleJobSpec.py +25 -0
- pandaserver/taskbuffer/PrioUtil.py +40 -0
- pandaserver/taskbuffer/ProcessGroups.py +111 -0
- pandaserver/taskbuffer/ResourceSpec.py +175 -0
- pandaserver/taskbuffer/SQLDumper.py +37 -0
- pandaserver/taskbuffer/SQLManager.py +60 -0
- pandaserver/taskbuffer/SiteSpec.py +304 -0
- pandaserver/taskbuffer/SupErrors.py +10 -0
- pandaserver/taskbuffer/TaskBuffer.py +2882 -0
- pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
- pandaserver/taskbuffer/Utils.py +848 -0
- pandaserver/taskbuffer/WorkQueue.py +227 -0
- pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
- pandaserver/taskbuffer/WorkerSpec.py +143 -0
- pandaserver/taskbuffer/WrappedCursor.py +487 -0
- pandaserver/taskbuffer/WrappedPickle.py +88 -0
- pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
- pandaserver/taskbuffer/__init__.py +0 -0
- pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
- pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
- pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
- pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
- pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
- pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
- pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
- pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
- pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
- pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
- pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
- pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
- pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
- pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
- pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
- pandaserver/taskbuffer/retryModule.py +552 -0
- pandaserver/taskbuffer/task_split_rules.py +308 -0
- pandaserver/taskbuffer/workflow_processor.py +249 -0
- pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
- pandaserver/test/SchemaChecker.py +40 -0
- pandaserver/test/__init__.py +0 -0
- pandaserver/test/banUser.py +43 -0
- pandaserver/test/boostPrio.py +19 -0
- pandaserver/test/boostUser.py +35 -0
- pandaserver/test/callbackDDM.py +11 -0
- pandaserver/test/daod_on_demand.py +95 -0
- pandaserver/test/finishJob.py +184 -0
- pandaserver/test/finishTaskJEDI.py +11 -0
- pandaserver/test/getJobs.py +53 -0
- pandaserver/test/killJob.py +79 -0
- pandaserver/test/killJobLowPrio.py +99 -0
- pandaserver/test/killJobsInTask.py +56 -0
- pandaserver/test/killTask.py +80 -0
- pandaserver/test/killTaskJEDI.py +11 -0
- pandaserver/test/killUser.py +83 -0
- pandaserver/test/lsst/README.txt +7 -0
- pandaserver/test/lsst/lsstSubmit.py +139 -0
- pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
- pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
- pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
- pandaserver/test/reassignJobs.py +17 -0
- pandaserver/test/reassignSite.py +82 -0
- pandaserver/test/reassignTask.py +117 -0
- pandaserver/test/reassignWaiting.py +39 -0
- pandaserver/test/recoverLostFiles.py +7 -0
- pandaserver/test/reloadInputDS.py +51 -0
- pandaserver/test/sendCommandToJob.py +26 -0
- pandaserver/test/setDebugMode.py +38 -0
- pandaserver/test/setPriority.py +28 -0
- pandaserver/test/testEvgen.py +60 -0
- pandaserver/test/testEvgen17.py +58 -0
- pandaserver/test/testG4sim.py +81 -0
- pandaserver/test/testG4sim17.py +90 -0
- pandaserver/test/testGlobalShares.py +131 -0
- pandaserver/test/testReco.py +103 -0
- pandaserver/test/testSimulReco14.py +98 -0
- pandaserver/test/testSiteMap.py +26 -0
- pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
- pandaserver/userinterface/Client.py +1686 -0
- pandaserver/userinterface/UserIF.py +1799 -0
- pandaserver/userinterface/__init__.py +0 -0
- pandaserver/workflow/__init__.py +0 -0
- pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
- pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
- pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
- pandaserver/workflow/pcwl_test.py +122 -0
- pandaserver/workflow/pcwl_utils.py +384 -0
- pandaserver/workflow/psnakemake_container.json +68 -0
- pandaserver/workflow/psnakemake_task.json +62 -0
- pandaserver/workflow/psnakemake_test.py +60 -0
- pandaserver/workflow/snakeparser/__init__.py +3 -0
- pandaserver/workflow/snakeparser/extensions.py +74 -0
- pandaserver/workflow/snakeparser/log.py +53 -0
- pandaserver/workflow/snakeparser/names.py +3 -0
- pandaserver/workflow/snakeparser/parser.py +332 -0
- pandaserver/workflow/snakeparser/utils.py +29 -0
- pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
- pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
- pandaserver/workflow/workflow_base.py +531 -0
- pandaserver/workflow/workflow_core.py +1974 -0
- pandaserver/workflow/workflow_core_smoketest.py +388 -0
- pandaserver/workflow/workflow_parser.py +222 -0
- pandaserver/workflow/workflow_utils.py +1047 -0
|
@@ -0,0 +1,2763 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import gc
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import re
|
|
7
|
+
import socket
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import traceback
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import unquote
|
|
13
|
+
|
|
14
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
15
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
16
|
+
|
|
17
|
+
from pandajedi.jediconfig import jedi_config
|
|
18
|
+
from pandajedi.jedicore import Interaction
|
|
19
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
20
|
+
from pandajedi.jedicore.ThreadUtils import (
|
|
21
|
+
ListWithLock,
|
|
22
|
+
MapWithLock,
|
|
23
|
+
ThreadPool,
|
|
24
|
+
WorkerThread,
|
|
25
|
+
)
|
|
26
|
+
from pandajedi.jedirefine import RefinerUtils
|
|
27
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
28
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
29
|
+
from pandaserver.srvcore import CoreUtils
|
|
30
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils, ParseJobXML
|
|
31
|
+
from pandaserver.taskbuffer.FileSpec import FileSpec
|
|
32
|
+
from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec
|
|
33
|
+
from pandaserver.taskbuffer.JobSpec import JobSpec
|
|
34
|
+
from pandaserver.userinterface import Client as PandaClient
|
|
35
|
+
|
|
36
|
+
from .JediKnight import JediKnight
|
|
37
|
+
from .JobBroker import JobBroker
|
|
38
|
+
from .JobSplitter import JobSplitter
|
|
39
|
+
from .JobThrottler import JobThrottler
|
|
40
|
+
from .TaskSetupper import TaskSetupper
|
|
41
|
+
|
|
42
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# exceptions
|
|
46
|
+
class UnresolvedParam(Exception):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# time profile level
|
|
51
|
+
TIME_PROFILE_OFF = 0
|
|
52
|
+
TIME_PROFILE_ON = 1
|
|
53
|
+
TIME_PROFILE_DEEP = 2
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# get parameters to get tasks
|
|
57
|
+
def get_params_to_get_tasks(
|
|
58
|
+
taskBufferIF: Any,
|
|
59
|
+
paramsToGetTasks: dict[str, dict[str, dict[str, dict[str, dict[str, int]]]]],
|
|
60
|
+
vo: str,
|
|
61
|
+
prodSourceLabel: str,
|
|
62
|
+
queueName: str,
|
|
63
|
+
cloudName: str,
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
"""Resolve `nFiles` and `nTasks` for a VO/label/queue/cloud combination.
|
|
66
|
+
|
|
67
|
+
The function lazily builds and reuses `paramsToGetTasks`, a caller-owned cache
|
|
68
|
+
parsed from `jedi_config.jobgen.{nFiles,nTasks}PerGroup`, and applies the
|
|
69
|
+
same wildcard precedence as the legacy implementation (`any` fallback at each
|
|
70
|
+
level). Finally, queue-specific overrides from the config table are applied.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
taskBufferIF: TaskBuffer interface used to fetch config-table overrides.
|
|
74
|
+
paramsToGetTasks: Mutable cache map shared by the caller.
|
|
75
|
+
vo: VO name.
|
|
76
|
+
prodSourceLabel: Production source label.
|
|
77
|
+
queueName: Work queue name.
|
|
78
|
+
cloudName: Cloud name.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A map with resolved values for `nFiles` and `nTasks`.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
paramsList = ["nFiles", "nTasks"]
|
|
85
|
+
# get group specified params
|
|
86
|
+
if not paramsToGetTasks:
|
|
87
|
+
# loop over all params
|
|
88
|
+
for paramName in paramsList:
|
|
89
|
+
paramsToGetTasks[paramName] = {}
|
|
90
|
+
configParamName = paramName + "PerGroup"
|
|
91
|
+
# check if param is defined in config
|
|
92
|
+
if hasattr(jedi_config.jobgen, configParamName):
|
|
93
|
+
tmpConfParams = getattr(jedi_config.jobgen, configParamName)
|
|
94
|
+
for item in tmpConfParams.split(","):
|
|
95
|
+
# decompose config params
|
|
96
|
+
try:
|
|
97
|
+
tmpVOs, tmpProdSourceLabels, tmpQueueNames, tmpCloudNames, nXYZ = item.split(":")
|
|
98
|
+
# loop over all VOs
|
|
99
|
+
for tmpVO in tmpVOs.split("|"):
|
|
100
|
+
if tmpVO == "":
|
|
101
|
+
tmpVO = "any"
|
|
102
|
+
paramsToGetTasks[paramName][tmpVO] = {}
|
|
103
|
+
# loop over all labels
|
|
104
|
+
for tmpProdSourceLabel in tmpProdSourceLabels.split("|"):
|
|
105
|
+
if tmpProdSourceLabel == "":
|
|
106
|
+
tmpProdSourceLabel = "any"
|
|
107
|
+
paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel] = {}
|
|
108
|
+
# loop over all queues
|
|
109
|
+
for tmpQueueName in tmpQueueNames.split("|"):
|
|
110
|
+
if tmpQueueName == "":
|
|
111
|
+
tmpQueueName = "any"
|
|
112
|
+
paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel][tmpQueueName] = {}
|
|
113
|
+
for tmpCloudName in tmpCloudNames.split("|"):
|
|
114
|
+
if tmpCloudName == "":
|
|
115
|
+
tmpCloudName = "any"
|
|
116
|
+
# add
|
|
117
|
+
paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel][tmpQueueName][tmpCloudName] = int(nXYZ)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
# make return
|
|
121
|
+
retMap = {}
|
|
122
|
+
for paramName in paramsList:
|
|
123
|
+
# set default
|
|
124
|
+
retMap[paramName] = getattr(jedi_config.jobgen, paramName)
|
|
125
|
+
if paramName in paramsToGetTasks:
|
|
126
|
+
# check VO
|
|
127
|
+
if vo in paramsToGetTasks[paramName]:
|
|
128
|
+
tmpVO = vo
|
|
129
|
+
elif "any" in paramsToGetTasks[paramName]:
|
|
130
|
+
tmpVO = "any"
|
|
131
|
+
else:
|
|
132
|
+
continue
|
|
133
|
+
# check label
|
|
134
|
+
if prodSourceLabel in paramsToGetTasks[paramName][tmpVO]:
|
|
135
|
+
tmpProdSourceLabel = prodSourceLabel
|
|
136
|
+
elif "any" in paramsToGetTasks[paramName][tmpVO]:
|
|
137
|
+
tmpProdSourceLabel = "any"
|
|
138
|
+
else:
|
|
139
|
+
continue
|
|
140
|
+
# check queue
|
|
141
|
+
if queueName in paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel]:
|
|
142
|
+
tmpQueueName = queueName
|
|
143
|
+
elif "any" in paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel]:
|
|
144
|
+
tmpQueueName = "any"
|
|
145
|
+
else:
|
|
146
|
+
continue
|
|
147
|
+
# check cloud
|
|
148
|
+
if cloudName in paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel][tmpQueueName]:
|
|
149
|
+
tmpCloudName = cloudName
|
|
150
|
+
else:
|
|
151
|
+
tmpCloudName = "any"
|
|
152
|
+
# set
|
|
153
|
+
retMap[paramName] = paramsToGetTasks[paramName][tmpVO][tmpProdSourceLabel][tmpQueueName][tmpCloudName]
|
|
154
|
+
# get from config table
|
|
155
|
+
nFiles = taskBufferIF.getConfigValue("jobgen", f"NFILES_{queueName}", "jedi", vo)
|
|
156
|
+
if nFiles is not None:
|
|
157
|
+
retMap["nFiles"] = nFiles
|
|
158
|
+
nTasks = taskBufferIF.getConfigValue("jobgen", f"NTASKS_{queueName}", "jedi", vo)
|
|
159
|
+
if nTasks is not None:
|
|
160
|
+
retMap["nTasks"] = nTasks
|
|
161
|
+
# return
|
|
162
|
+
return retMap
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# worker class to generate jobs
|
|
166
|
+
class JobGenerator(JediKnight):
|
|
167
|
+
# constructor
|
|
168
|
+
def __init__(
|
|
169
|
+
self, commuChannel, taskBufferIF, ddmIF, vos, prodSourceLabels, cloudList, withThrottle=True, execJobs=True, loopCycle_cust=None, test_mode=False
|
|
170
|
+
):
|
|
171
|
+
JediKnight.__init__(self, commuChannel, taskBufferIF, ddmIF, logger)
|
|
172
|
+
self.vos = self.parseInit(vos)
|
|
173
|
+
self.prodSourceLabels = self.parseInit(prodSourceLabels)
|
|
174
|
+
self.pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}_{os.getpgrp()}-gen"
|
|
175
|
+
self.cloudList = cloudList
|
|
176
|
+
self.withThrottle = withThrottle
|
|
177
|
+
self.execJobs = execJobs
|
|
178
|
+
self.loopCycle_cust = loopCycle_cust
|
|
179
|
+
self.paramsToGetTasks = {}
|
|
180
|
+
self.test_mode = test_mode
|
|
181
|
+
|
|
182
|
+
# main
|
|
183
|
+
def start(self):
|
|
184
|
+
# start base class
|
|
185
|
+
# JediKnight.start(self)
|
|
186
|
+
# global thread pool
|
|
187
|
+
globalThreadPool = ThreadPool()
|
|
188
|
+
|
|
189
|
+
# probability of running inactive gshare rtype combinations
|
|
190
|
+
try:
|
|
191
|
+
inactive_poll_probability = jedi_config.jobgen.inactive_poll_probability
|
|
192
|
+
except AttributeError:
|
|
193
|
+
inactive_poll_probability = 0.25
|
|
194
|
+
|
|
195
|
+
# go into main loop
|
|
196
|
+
while True:
|
|
197
|
+
startTime = naive_utcnow()
|
|
198
|
+
tmpLog = MsgWrapper(logger)
|
|
199
|
+
try:
|
|
200
|
+
tmpLog.debug("start")
|
|
201
|
+
# get SiteMapper
|
|
202
|
+
siteMapper = self.taskBufferIF.get_site_mapper()
|
|
203
|
+
tmpLog.debug("got siteMapper")
|
|
204
|
+
# get work queue mapper
|
|
205
|
+
workQueueMapper = self.taskBufferIF.getWorkQueueMap()
|
|
206
|
+
tmpLog.debug("got workQueueMapper")
|
|
207
|
+
# get resource types
|
|
208
|
+
resource_types = self.taskBufferIF.load_resource_types()
|
|
209
|
+
if not resource_types:
|
|
210
|
+
raise RuntimeError("failed to get resource types")
|
|
211
|
+
tmpLog.debug("got resource types")
|
|
212
|
+
# get Throttle
|
|
213
|
+
throttle = JobThrottler(self.vos, self.prodSourceLabels)
|
|
214
|
+
throttle.initializeMods(self.taskBufferIF)
|
|
215
|
+
tmpLog.debug("got Throttle")
|
|
216
|
+
# get TaskSetupper
|
|
217
|
+
taskSetupper = TaskSetupper(self.vos, self.prodSourceLabels)
|
|
218
|
+
taskSetupper.initializeMods(self.taskBufferIF, self.ddmIF)
|
|
219
|
+
# loop over all vos
|
|
220
|
+
tmpLog.debug("go into loop")
|
|
221
|
+
for vo in self.vos:
|
|
222
|
+
# get the active gshare rtypes combinations in order to reduce polling frequency on unused combinations
|
|
223
|
+
active_gshare_rtypes = self.taskBufferIF.get_active_gshare_rtypes(vo)
|
|
224
|
+
|
|
225
|
+
# check if job submission is enabled
|
|
226
|
+
isUP = self.taskBufferIF.getConfigValue("jobgen", "JOB_SUBMISSION", "jedi", vo)
|
|
227
|
+
if isUP is False:
|
|
228
|
+
tmpLog.debug(f"job submission is disabled for VO={vo}")
|
|
229
|
+
continue
|
|
230
|
+
# loop over all sourceLabels
|
|
231
|
+
for prodSourceLabel in self.prodSourceLabels:
|
|
232
|
+
# loop over all clouds
|
|
233
|
+
random.shuffle(self.cloudList)
|
|
234
|
+
workQueueList = workQueueMapper.getAlignedQueueList(vo, prodSourceLabel)
|
|
235
|
+
for cloudName in self.cloudList:
|
|
236
|
+
tmpLog.debug(f"{len(workQueueList)} workqueues for vo:{vo} label:{prodSourceLabel}")
|
|
237
|
+
for workQueue in workQueueList:
|
|
238
|
+
for resource_type in resource_types:
|
|
239
|
+
workqueue_name_nice = "_".join(workQueue.queue_name.split(" "))
|
|
240
|
+
cycleStr = "pid={0} vo={1} cloud={2} queue={3} ( id={4} ) label={5} resource_type={6}".format(
|
|
241
|
+
self.pid, vo, cloudName, workqueue_name_nice, workQueue.queue_id, prodSourceLabel, resource_type.resource_name
|
|
242
|
+
)
|
|
243
|
+
tmpLog_inner = MsgWrapper(logger, cycleStr)
|
|
244
|
+
|
|
245
|
+
# reduce the polling frequency on unused combinations
|
|
246
|
+
active = (
|
|
247
|
+
workQueue.queue_name in active_gshare_rtypes
|
|
248
|
+
and resource_type.resource_name in active_gshare_rtypes[workQueue.queue_name]
|
|
249
|
+
)
|
|
250
|
+
if active_gshare_rtypes and not active:
|
|
251
|
+
if random.uniform(0, 1) > inactive_poll_probability:
|
|
252
|
+
tmpLog_inner.debug(f"skipping {cycleStr} due to inactivity")
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
tmpLog_inner.debug(f"start {cycleStr}")
|
|
256
|
+
# check if to lock
|
|
257
|
+
lockFlag = self.toLockProcess(vo, prodSourceLabel, workQueue.queue_name, cloudName)
|
|
258
|
+
flagLocked = False
|
|
259
|
+
if lockFlag:
|
|
260
|
+
tmpLog_inner.debug("check if to lock")
|
|
261
|
+
# lock
|
|
262
|
+
flagLocked = self.taskBufferIF.lockProcess_JEDI(
|
|
263
|
+
vo=vo,
|
|
264
|
+
prodSourceLabel=prodSourceLabel,
|
|
265
|
+
cloud=cloudName,
|
|
266
|
+
workqueue_id=workQueue.queue_id,
|
|
267
|
+
resource_name=resource_type.resource_name,
|
|
268
|
+
component=None,
|
|
269
|
+
pid=self.pid,
|
|
270
|
+
)
|
|
271
|
+
if not flagLocked:
|
|
272
|
+
tmpLog_inner.debug("skip since locked by another process")
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
# throttle
|
|
276
|
+
tmpLog_inner.debug(f"check throttle with {throttle.getClassName(vo, prodSourceLabel)}")
|
|
277
|
+
try:
|
|
278
|
+
tmpSt, thrFlag = throttle.toBeThrottled(vo, prodSourceLabel, cloudName, workQueue, resource_type.resource_name)
|
|
279
|
+
except Exception:
|
|
280
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
281
|
+
tmpLog_inner.error(f"throttler failed with {errtype} {errvalue}")
|
|
282
|
+
tmpLog_inner.error(f"throttler failed with traceback {traceback.format_exc()}")
|
|
283
|
+
raise RuntimeError("crashed when checking throttle")
|
|
284
|
+
if tmpSt != self.SC_SUCCEEDED:
|
|
285
|
+
raise RuntimeError("failed to check throttle")
|
|
286
|
+
mergeUnThrottled = None
|
|
287
|
+
if thrFlag is True:
|
|
288
|
+
if flagLocked:
|
|
289
|
+
tmpLog_inner.debug("throttled")
|
|
290
|
+
self.taskBufferIF.unlockProcess_JEDI(
|
|
291
|
+
vo=vo,
|
|
292
|
+
prodSourceLabel=prodSourceLabel,
|
|
293
|
+
cloud=cloudName,
|
|
294
|
+
workqueue_id=workQueue.queue_id,
|
|
295
|
+
resource_name=resource_type.resource_name,
|
|
296
|
+
component=None,
|
|
297
|
+
pid=self.pid,
|
|
298
|
+
)
|
|
299
|
+
continue
|
|
300
|
+
elif thrFlag is False:
|
|
301
|
+
pass
|
|
302
|
+
else:
|
|
303
|
+
# leveled flag
|
|
304
|
+
mergeUnThrottled = not throttle.mergeThrottled(vo, workQueue.queue_type, thrFlag)
|
|
305
|
+
if not mergeUnThrottled:
|
|
306
|
+
tmpLog_inner.debug("throttled including merge")
|
|
307
|
+
if flagLocked:
|
|
308
|
+
self.taskBufferIF.unlockProcess_JEDI(
|
|
309
|
+
vo=vo,
|
|
310
|
+
prodSourceLabel=prodSourceLabel,
|
|
311
|
+
cloud=cloudName,
|
|
312
|
+
workqueue_id=workQueue.queue_id,
|
|
313
|
+
resource_name=resource_type.resource_name,
|
|
314
|
+
component=None,
|
|
315
|
+
pid=self.pid,
|
|
316
|
+
)
|
|
317
|
+
continue
|
|
318
|
+
else:
|
|
319
|
+
tmpLog_inner.debug("only merge is unthrottled")
|
|
320
|
+
|
|
321
|
+
tmpLog_inner.debug(f"minPriority={throttle.minPriority} maxNumJobs={throttle.maxNumJobs}")
|
|
322
|
+
# get typical number of files
|
|
323
|
+
typicalNumFilesMap = self.taskBufferIF.getTypicalNumInput_JEDI(vo, prodSourceLabel, workQueue, useResultCache=600)
|
|
324
|
+
if typicalNumFilesMap is None:
|
|
325
|
+
raise RuntimeError("failed to get typical number of files")
|
|
326
|
+
# get params
|
|
327
|
+
tmpParamsToGetTasks = get_params_to_get_tasks(
|
|
328
|
+
self.taskBufferIF, self.paramsToGetTasks, vo, prodSourceLabel, workQueue.queue_name, cloudName
|
|
329
|
+
)
|
|
330
|
+
nTasksToGetTasks = tmpParamsToGetTasks["nTasks"]
|
|
331
|
+
nFilesToGetTasks = tmpParamsToGetTasks["nFiles"]
|
|
332
|
+
tmpLog_inner.debug(f"nTasks={nTasksToGetTasks} nFiles={nFilesToGetTasks} to get tasks")
|
|
333
|
+
# get number of tasks to generate new jumbo jobs
|
|
334
|
+
numTasksWithRunningJumbo = self.taskBufferIF.getNumTasksWithRunningJumbo_JEDI(vo, prodSourceLabel, cloudName, workQueue)
|
|
335
|
+
if not self.withThrottle:
|
|
336
|
+
numTasksWithRunningJumbo = 0
|
|
337
|
+
maxNumTasksWithRunningJumbo = 50
|
|
338
|
+
if numTasksWithRunningJumbo < maxNumTasksWithRunningJumbo:
|
|
339
|
+
numNewTaskWithJumbo = maxNumTasksWithRunningJumbo - numTasksWithRunningJumbo
|
|
340
|
+
if numNewTaskWithJumbo < 0:
|
|
341
|
+
numNewTaskWithJumbo = 0
|
|
342
|
+
else:
|
|
343
|
+
numNewTaskWithJumbo = 0
|
|
344
|
+
# release lock when lack of jobs
|
|
345
|
+
lackOfJobs = False
|
|
346
|
+
if thrFlag is False and flagLocked and throttle.lackOfJobs:
|
|
347
|
+
tmpLog_inner.debug(f"unlock {cycleStr} for multiple processes to quickly fill the queue until nQueueLimit is reached")
|
|
348
|
+
self.taskBufferIF.unlockProcess_JEDI(
|
|
349
|
+
vo=vo,
|
|
350
|
+
prodSourceLabel=prodSourceLabel,
|
|
351
|
+
cloud=cloudName,
|
|
352
|
+
workqueue_id=workQueue.queue_id,
|
|
353
|
+
resource_name=resource_type.resource_name,
|
|
354
|
+
component=None,
|
|
355
|
+
pid=self.pid,
|
|
356
|
+
)
|
|
357
|
+
lackOfJobs = True
|
|
358
|
+
# get the list of input
|
|
359
|
+
tmpList = self.taskBufferIF.getTasksToBeProcessed_JEDI(
|
|
360
|
+
self.pid,
|
|
361
|
+
vo,
|
|
362
|
+
workQueue,
|
|
363
|
+
prodSourceLabel,
|
|
364
|
+
cloudName,
|
|
365
|
+
nTasks=nTasksToGetTasks,
|
|
366
|
+
nFiles=nFilesToGetTasks,
|
|
367
|
+
minPriority=throttle.minPriority,
|
|
368
|
+
maxNumJobs=throttle.maxNumJobs,
|
|
369
|
+
typicalNumFilesMap=typicalNumFilesMap,
|
|
370
|
+
mergeUnThrottled=mergeUnThrottled,
|
|
371
|
+
numNewTaskWithJumbo=numNewTaskWithJumbo,
|
|
372
|
+
resource_name=resource_type.resource_name,
|
|
373
|
+
)
|
|
374
|
+
if tmpList is None:
|
|
375
|
+
# failed
|
|
376
|
+
tmpLog_inner.error("failed to get the list of input chunks to generate jobs")
|
|
377
|
+
else:
|
|
378
|
+
tmpLog_inner.debug(f"got {len(tmpList)} input tasks")
|
|
379
|
+
if len(tmpList) != 0:
|
|
380
|
+
# put to a locked list
|
|
381
|
+
inputList = ListWithLock(tmpList)
|
|
382
|
+
# make thread pool
|
|
383
|
+
threadPool = ThreadPool()
|
|
384
|
+
# make lock if necessary
|
|
385
|
+
if lockFlag:
|
|
386
|
+
liveCounter = MapWithLock()
|
|
387
|
+
else:
|
|
388
|
+
liveCounter = None
|
|
389
|
+
# make list for brokerage lock
|
|
390
|
+
brokerageLockIDs = ListWithLock([])
|
|
391
|
+
# make workers
|
|
392
|
+
nWorker = jedi_config.jobgen.nWorkers
|
|
393
|
+
for iWorker in range(nWorker):
|
|
394
|
+
thr = JobGeneratorThread(
|
|
395
|
+
inputList,
|
|
396
|
+
threadPool,
|
|
397
|
+
self.taskBufferIF,
|
|
398
|
+
self.ddmIF,
|
|
399
|
+
siteMapper,
|
|
400
|
+
self.execJobs,
|
|
401
|
+
taskSetupper,
|
|
402
|
+
self.pid,
|
|
403
|
+
workQueue,
|
|
404
|
+
resource_type.resource_name,
|
|
405
|
+
cloudName,
|
|
406
|
+
liveCounter,
|
|
407
|
+
brokerageLockIDs,
|
|
408
|
+
lackOfJobs,
|
|
409
|
+
resource_types,
|
|
410
|
+
test_mode=self.test_mode,
|
|
411
|
+
)
|
|
412
|
+
globalThreadPool.add(thr)
|
|
413
|
+
thr.start()
|
|
414
|
+
# join
|
|
415
|
+
tmpLog_inner.debug("try to join")
|
|
416
|
+
threadPool.join(60 * 10)
|
|
417
|
+
# unlock locks made by brokerage
|
|
418
|
+
for brokeragelockID in brokerageLockIDs:
|
|
419
|
+
self.taskBufferIF.unlockProcessWithPID_JEDI(
|
|
420
|
+
vo, prodSourceLabel, workQueue.queue_name, resource_type.resource_name, brokeragelockID, True
|
|
421
|
+
)
|
|
422
|
+
tmpLog_inner.debug(f"dump one-time pool : {threadPool.dump()} remTasks={inputList.dump()}")
|
|
423
|
+
# unlock
|
|
424
|
+
self.taskBufferIF.unlockProcess_JEDI(
|
|
425
|
+
vo=vo,
|
|
426
|
+
prodSourceLabel=prodSourceLabel,
|
|
427
|
+
cloud=cloudName,
|
|
428
|
+
workqueue_id=workQueue.queue_id,
|
|
429
|
+
resource_name=resource_type.resource_name,
|
|
430
|
+
component=None,
|
|
431
|
+
pid=self.pid,
|
|
432
|
+
)
|
|
433
|
+
except Exception:
|
|
434
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
435
|
+
tmpLog.error(f"failed in {self.__class__.__name__}.start() with {errtype.__name__}:{errvalue} {traceback.format_exc()}")
|
|
436
|
+
# unlock just in case
|
|
437
|
+
try:
|
|
438
|
+
for vo in self.vos:
|
|
439
|
+
for prodSourceLabel in self.prodSourceLabels:
|
|
440
|
+
for cloudName in self.cloudList:
|
|
441
|
+
for workQueue in workQueueList:
|
|
442
|
+
self.taskBufferIF.unlockProcess_JEDI(
|
|
443
|
+
vo=vo,
|
|
444
|
+
prodSourceLabel=prodSourceLabel,
|
|
445
|
+
cloud=cloudName,
|
|
446
|
+
workqueue_id=workQueue.queue_id,
|
|
447
|
+
resource_name=resource_type.resource_name,
|
|
448
|
+
component=None,
|
|
449
|
+
pid=self.pid,
|
|
450
|
+
)
|
|
451
|
+
except Exception:
|
|
452
|
+
pass
|
|
453
|
+
try:
|
|
454
|
+
# clean up global thread pool
|
|
455
|
+
globalThreadPool.clean()
|
|
456
|
+
# dump
|
|
457
|
+
tmpLog.debug(f"dump global pool : {globalThreadPool.dump()}")
|
|
458
|
+
except Exception:
|
|
459
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
460
|
+
tmpLog.error(f"failed to dump global pool with {errtype.__name__} {errvalue}")
|
|
461
|
+
tmpLog.debug("end")
|
|
462
|
+
# memory check
|
|
463
|
+
try:
|
|
464
|
+
memLimit = 1.5 * 1024
|
|
465
|
+
memNow = CoreUtils.getMemoryUsage()
|
|
466
|
+
tmpLog.debug(f"memUsage now {memNow} MB pid={os.getpid()}")
|
|
467
|
+
if memNow > memLimit:
|
|
468
|
+
tmpLog.warning(f"memory limit exceeds {memNow} > {memLimit} MB pid={os.getpid()}")
|
|
469
|
+
tmpLog.debug("trigger garbage collection")
|
|
470
|
+
gc.collect()
|
|
471
|
+
except Exception as e:
|
|
472
|
+
tmpLog.error(f"failed in garbage collection with {str(e)}")
|
|
473
|
+
# sleep if needed. It can be specified for the particular JobGenerator instance or use the default value
|
|
474
|
+
if self.loopCycle_cust:
|
|
475
|
+
loopCycle = self.loopCycle_cust
|
|
476
|
+
else:
|
|
477
|
+
loopCycle = jedi_config.jobgen.loopCycle
|
|
478
|
+
timeDelta = naive_utcnow() - startTime
|
|
479
|
+
sleepPeriod = loopCycle - timeDelta.seconds
|
|
480
|
+
tmpLog.debug(f"loopCycle {loopCycle}s; sleeping {sleepPeriod}s")
|
|
481
|
+
if sleepPeriod > 0:
|
|
482
|
+
time.sleep(sleepPeriod)
|
|
483
|
+
# randomize cycle
|
|
484
|
+
self.randomSleep(max_val=loopCycle)
|
|
485
|
+
|
|
486
|
+
# check if lock process
|
|
487
|
+
def toLockProcess(self, vo, prodSourceLabel, queueName, cloudName):
|
|
488
|
+
try:
|
|
489
|
+
# check if param is defined in config
|
|
490
|
+
if hasattr(jedi_config.jobgen, "lockProcess"):
|
|
491
|
+
for item in jedi_config.jobgen.lockProcess.split(","):
|
|
492
|
+
tmpVo, tmpProdSourceLabel, tmpQueueName, tmpCloudName = item.split(":")
|
|
493
|
+
if tmpVo not in ["", "any", None] and vo not in tmpVo.split("|"):
|
|
494
|
+
continue
|
|
495
|
+
if tmpProdSourceLabel not in ["", "any", None] and prodSourceLabel not in tmpProdSourceLabel.split("|"):
|
|
496
|
+
continue
|
|
497
|
+
if tmpQueueName not in ["", "any", None] and queueName not in tmpQueueName.split("|"):
|
|
498
|
+
continue
|
|
499
|
+
if tmpCloudName not in ["", "any", None] and cloudName not in tmpCloudName.split("|"):
|
|
500
|
+
continue
|
|
501
|
+
return True
|
|
502
|
+
except Exception:
|
|
503
|
+
pass
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
# thread for real worker
|
|
508
|
+
class JobGeneratorThread(WorkerThread):
|
|
509
|
+
# constructor
|
|
510
|
+
def __init__(
|
|
511
|
+
self,
|
|
512
|
+
inputList,
|
|
513
|
+
threadPool,
|
|
514
|
+
taskbufferIF,
|
|
515
|
+
ddmIF,
|
|
516
|
+
siteMapper,
|
|
517
|
+
execJobs,
|
|
518
|
+
taskSetupper,
|
|
519
|
+
pid,
|
|
520
|
+
workQueue,
|
|
521
|
+
resource_name,
|
|
522
|
+
cloud,
|
|
523
|
+
liveCounter,
|
|
524
|
+
brokerageLockIDs,
|
|
525
|
+
lackOfJobs,
|
|
526
|
+
resource_types,
|
|
527
|
+
test_mode=False,
|
|
528
|
+
):
|
|
529
|
+
# initialize woker with no semaphore
|
|
530
|
+
WorkerThread.__init__(self, None, threadPool, logger)
|
|
531
|
+
# attributes
|
|
532
|
+
self.inputList = inputList
|
|
533
|
+
self.taskBufferIF = taskbufferIF
|
|
534
|
+
self.ddmIF = ddmIF
|
|
535
|
+
self.siteMapper = siteMapper
|
|
536
|
+
self.execJobs = execJobs
|
|
537
|
+
self.numGenJobs = 0
|
|
538
|
+
self.taskSetupper = taskSetupper
|
|
539
|
+
self.msgType = "jobgenerator"
|
|
540
|
+
self.pid = pid
|
|
541
|
+
self.buildSpecMap = {}
|
|
542
|
+
self.finished_lib_specs_map = {}
|
|
543
|
+
self.active_lib_specs_map = {}
|
|
544
|
+
self.workQueue = workQueue
|
|
545
|
+
self.resource_name = resource_name
|
|
546
|
+
self.cloud = cloud
|
|
547
|
+
self.liveCounter = liveCounter
|
|
548
|
+
self.brokerageLockIDs = brokerageLockIDs
|
|
549
|
+
self.lackOfJobs = lackOfJobs
|
|
550
|
+
self.resource_types = resource_types
|
|
551
|
+
self.time_profile_level = TIME_PROFILE_OFF
|
|
552
|
+
self.test_mode = test_mode
|
|
553
|
+
|
|
554
|
+
# main
|
|
555
|
+
def runImpl(self):
|
|
556
|
+
workqueue_name_nice = "_".join(self.workQueue.queue_name.split(" "))
|
|
557
|
+
while True:
|
|
558
|
+
try:
|
|
559
|
+
lastJediTaskID = None
|
|
560
|
+
# get a part of list
|
|
561
|
+
nInput = 1
|
|
562
|
+
taskInputList = self.inputList.get(nInput)
|
|
563
|
+
# no more datasets
|
|
564
|
+
if len(taskInputList) == 0:
|
|
565
|
+
self.logger.debug(f"{self.__class__.__name__} terminating after generating {self.numGenJobs} jobs since no more inputs ")
|
|
566
|
+
if self.numGenJobs > 0:
|
|
567
|
+
prefix = "<VO={0} queue_type={1} cloud={2} queue={3} resource_type={4}>".format(
|
|
568
|
+
self.workQueue.VO, self.workQueue.queue_type, self.cloud, workqueue_name_nice, self.resource_name
|
|
569
|
+
)
|
|
570
|
+
tmpMsg = f": submitted {self.numGenJobs} jobs"
|
|
571
|
+
tmpLog = MsgWrapper(self.logger, monToken=prefix)
|
|
572
|
+
tmpLog.info(prefix + tmpMsg)
|
|
573
|
+
tmpLog.sendMsg(tmpMsg, self.msgType)
|
|
574
|
+
return
|
|
575
|
+
# loop over all tasks
|
|
576
|
+
for tmpJediTaskID, inputList in taskInputList:
|
|
577
|
+
lastJediTaskID = tmpJediTaskID
|
|
578
|
+
# loop over all inputs
|
|
579
|
+
nBrokergeFailed = 0
|
|
580
|
+
nSubmitSucceeded = 0
|
|
581
|
+
task_common_dict = {}
|
|
582
|
+
for idxInputList, tmpInputItem in enumerate(inputList):
|
|
583
|
+
taskSpec, cloudName, inputChunk = tmpInputItem
|
|
584
|
+
# reset error dialog
|
|
585
|
+
taskSpec.errorDialog = None
|
|
586
|
+
# reset map of buildSpec
|
|
587
|
+
self.buildSpecMap = {}
|
|
588
|
+
main_stop_watch = CoreUtils.StopWatch("main")
|
|
589
|
+
loopStart = naive_utcnow()
|
|
590
|
+
# make logger
|
|
591
|
+
tmpLog = MsgWrapper(
|
|
592
|
+
self.logger,
|
|
593
|
+
f"<jediTaskID={taskSpec.jediTaskID} datasetID={inputChunk.masterIndexName}>",
|
|
594
|
+
monToken=f"<jediTaskID={taskSpec.jediTaskID}>",
|
|
595
|
+
)
|
|
596
|
+
tmpLog.info(f"start to generate with VO={taskSpec.vo} cloud={cloudName} queue={workqueue_name_nice} resource_type={self.resource_name}")
|
|
597
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("init"))
|
|
598
|
+
tmpLog.sendMsg("start to generate jobs", self.msgType)
|
|
599
|
+
readyToSubmitJob = False
|
|
600
|
+
jobsSubmitted = False
|
|
601
|
+
goForward = True
|
|
602
|
+
taskParamMap = None
|
|
603
|
+
oldStatus = taskSpec.status
|
|
604
|
+
# extend sandbox lifetime
|
|
605
|
+
if goForward:
|
|
606
|
+
if not inputChunk.isMerging and not (hasattr(jedi_config.jobgen, "touchSandbox") and not jedi_config.jobgen.touchSandbox):
|
|
607
|
+
tmpStat, tmpOut, taskParamMap = self.touchSandoboxFiles(taskSpec, taskParamMap, tmpLog)
|
|
608
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
609
|
+
tmpLog.error("failed to extend lifetime of sandbox file")
|
|
610
|
+
taskSpec.setOnHold()
|
|
611
|
+
taskSpec.setErrDiag(tmpOut)
|
|
612
|
+
goForward = False
|
|
613
|
+
# initialize brokerage
|
|
614
|
+
if goForward:
|
|
615
|
+
jobBroker = JobBroker(taskSpec.vo, taskSpec.prodSourceLabel)
|
|
616
|
+
tmpStat = jobBroker.initializeMods(self.ddmIF.getInterface(taskSpec.vo, taskSpec.cloud), self.taskBufferIF)
|
|
617
|
+
if not tmpStat:
|
|
618
|
+
tmpErrStr = "failed to initialize JobBroker"
|
|
619
|
+
tmpLog.error(tmpErrStr)
|
|
620
|
+
taskSpec.setOnHold()
|
|
621
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
622
|
+
goForward = False
|
|
623
|
+
# set live counter
|
|
624
|
+
jobBrokerCore = jobBroker.getImpl(taskSpec.vo, taskSpec.prodSourceLabel)
|
|
625
|
+
jobBrokerCore.setLiveCounter(self.liveCounter)
|
|
626
|
+
# test mode
|
|
627
|
+
if self.test_mode:
|
|
628
|
+
jobBrokerCore.setTestMode()
|
|
629
|
+
# set lock ID
|
|
630
|
+
jobBrokerCore.setLockID(self.pid, self.ident)
|
|
631
|
+
# set common dict
|
|
632
|
+
jobBrokerCore.set_task_common_dict(task_common_dict)
|
|
633
|
+
# read task params if necessary
|
|
634
|
+
if taskSpec.useLimitedSites():
|
|
635
|
+
tmpStat, taskParamMap = self.readTaskParams(taskSpec, taskParamMap, tmpLog)
|
|
636
|
+
if not tmpStat:
|
|
637
|
+
tmpErrStr = "failed to read task params"
|
|
638
|
+
tmpLog.error(tmpErrStr)
|
|
639
|
+
taskSpec.setOnHold()
|
|
640
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
641
|
+
goForward = False
|
|
642
|
+
# run brokerage
|
|
643
|
+
lockCounter = False
|
|
644
|
+
pendingJumbo = False
|
|
645
|
+
if goForward:
|
|
646
|
+
if self.liveCounter is not None and not inputChunk.isMerging and not self.lackOfJobs:
|
|
647
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock counter"))
|
|
648
|
+
self.liveCounter.acquire()
|
|
649
|
+
lockCounter = True
|
|
650
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("brokerage"))
|
|
651
|
+
tmpLog.debug(f"run brokerage with {jobBroker.getClassName(taskSpec.vo, taskSpec.prodSourceLabel)}")
|
|
652
|
+
try:
|
|
653
|
+
tmpStat, inputChunk = jobBroker.doBrokerage(taskSpec, cloudName, inputChunk, taskParamMap)
|
|
654
|
+
except Exception:
|
|
655
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
656
|
+
tmpLog.error(f"brokerage crashed with {errtype.__name__}:{errvalue} {traceback.format_exc()}")
|
|
657
|
+
tmpStat = Interaction.SC_FAILED
|
|
658
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
659
|
+
nBrokergeFailed += 1
|
|
660
|
+
if inputChunk is not None and inputChunk.hasCandidatesForJumbo():
|
|
661
|
+
pendingJumbo = True
|
|
662
|
+
else:
|
|
663
|
+
tmpErrStr = f"brokerage failed for {nBrokergeFailed} input datasets when trying {len(inputList)} datasets."
|
|
664
|
+
tmpLog.error(f"{tmpErrStr} {taskSpec.get_original_error_dialog()}")
|
|
665
|
+
if nSubmitSucceeded == 0:
|
|
666
|
+
taskSpec.setOnHold()
|
|
667
|
+
taskSpec.setErrDiag(tmpErrStr, True)
|
|
668
|
+
goForward = False
|
|
669
|
+
else:
|
|
670
|
+
# collect brokerage lock ID
|
|
671
|
+
brokerageLockID = jobBroker.getBaseLockID(taskSpec.vo, taskSpec.prodSourceLabel)
|
|
672
|
+
if brokerageLockID is not None:
|
|
673
|
+
self.brokerageLockIDs.append(brokerageLockID)
|
|
674
|
+
# run splitter
|
|
675
|
+
if goForward:
|
|
676
|
+
splitter = JobSplitter()
|
|
677
|
+
try:
|
|
678
|
+
# lock counter
|
|
679
|
+
if self.liveCounter is not None and not inputChunk.isMerging and not lockCounter:
|
|
680
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock counter"))
|
|
681
|
+
self.liveCounter.acquire()
|
|
682
|
+
lockCounter = True
|
|
683
|
+
# update nQueuedJobs since live counter may not have been
|
|
684
|
+
# considered in brokerage
|
|
685
|
+
tmpMsg = inputChunk.update_n_queue(self.liveCounter)
|
|
686
|
+
tmpLog.debug(f"updated nQueue at {tmpMsg}")
|
|
687
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("run splitter"))
|
|
688
|
+
tmpStat, subChunks, isSkipped = splitter.doSplit(taskSpec, inputChunk, self.siteMapper, allow_chunk_size_limit=True)
|
|
689
|
+
if tmpStat == Interaction.SC_SUCCEEDED and isSkipped:
|
|
690
|
+
# run again without chunk size limit to generate jobs for skipped snippet
|
|
691
|
+
tmpStat, tmpChunks, isSkipped = splitter.doSplit(taskSpec, inputChunk, self.siteMapper, allow_chunk_size_limit=False)
|
|
692
|
+
if tmpStat == Interaction.SC_SUCCEEDED:
|
|
693
|
+
subChunks += tmpChunks
|
|
694
|
+
# * remove the last sub-chunk when inputChunk is read in a block
|
|
695
|
+
# since alignment could be broken in the last sub-chunk
|
|
696
|
+
# e.g., inputChunk=10 -> subChunks=4,4,2 and remove 2
|
|
697
|
+
# * don't remove it for the last inputChunk
|
|
698
|
+
# e.g., inputChunks = 10(remove),10(remove),3(not remove)
|
|
699
|
+
if (
|
|
700
|
+
len(subChunks) > 0
|
|
701
|
+
and len(subChunks[-1]["subChunks"]) > 1
|
|
702
|
+
and inputChunk.masterDataset is not None
|
|
703
|
+
and inputChunk.readBlock is True
|
|
704
|
+
):
|
|
705
|
+
subChunks[-1]["subChunks"] = subChunks[-1]["subChunks"][:-1]
|
|
706
|
+
# update counter
|
|
707
|
+
if lockCounter:
|
|
708
|
+
for tmpSubChunk in subChunks:
|
|
709
|
+
self.liveCounter.add(tmpSubChunk["siteName"], len(tmpSubChunk["subChunks"]))
|
|
710
|
+
except Exception:
|
|
711
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
712
|
+
tmpLog.error(f"splitter crashed with {errtype.__name__}:{errvalue} {traceback.format_exc()}")
|
|
713
|
+
tmpStat = Interaction.SC_FAILED
|
|
714
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
715
|
+
tmpErrStr = "splitting failed"
|
|
716
|
+
tmpLog.error(tmpErrStr)
|
|
717
|
+
taskSpec.setOnHold()
|
|
718
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
719
|
+
goForward = False
|
|
720
|
+
# release counter
|
|
721
|
+
if lockCounter:
|
|
722
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("release counter"))
|
|
723
|
+
self.liveCounter.release()
|
|
724
|
+
# lock task
|
|
725
|
+
if goForward:
|
|
726
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock task"))
|
|
727
|
+
tmpStat = self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
728
|
+
if tmpStat is False:
|
|
729
|
+
tmpLog.debug("skip due to lock failure")
|
|
730
|
+
continue
|
|
731
|
+
# generate jobs
|
|
732
|
+
if goForward:
|
|
733
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("job generator"))
|
|
734
|
+
try:
|
|
735
|
+
tmpStat, pandaJobs, datasetToRegister, oldPandaIDs, parallelOutMap, outDsMap = self.doGenerate(
|
|
736
|
+
taskSpec, cloudName, subChunks, inputChunk, tmpLog, taskParamMap=taskParamMap, splitter=splitter
|
|
737
|
+
)
|
|
738
|
+
except Exception as e:
|
|
739
|
+
tmpErrStr = f"job generator crashed with {str(e)}"
|
|
740
|
+
tmpLog.error(tmpErrStr)
|
|
741
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
742
|
+
tmpStat = Interaction.SC_FAILED
|
|
743
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
744
|
+
tmpErrStr = "job generation failed."
|
|
745
|
+
tmpLog.error(tmpErrStr)
|
|
746
|
+
taskSpec.setOnHold()
|
|
747
|
+
taskSpec.setErrDiag(tmpErrStr, append=True, prepend=True)
|
|
748
|
+
goForward = False
|
|
749
|
+
elif not pandaJobs:
|
|
750
|
+
tmpErrStr = "candidates became full after the brokerage decision " "and skipped during the submission cycle"
|
|
751
|
+
tmpLog.error(tmpErrStr)
|
|
752
|
+
taskSpec.setOnHold()
|
|
753
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
754
|
+
goForward = False
|
|
755
|
+
# lock task
|
|
756
|
+
if goForward:
|
|
757
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock task"))
|
|
758
|
+
tmpStat = self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
759
|
+
if tmpStat is False:
|
|
760
|
+
tmpLog.debug("skip due to lock failure")
|
|
761
|
+
continue
|
|
762
|
+
# setup task
|
|
763
|
+
if goForward:
|
|
764
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("task setup"))
|
|
765
|
+
tmpLog.debug(f"run setupper with {self.taskSetupper.getClassName(taskSpec.vo, taskSpec.prodSourceLabel)}")
|
|
766
|
+
tmpStat = self.taskSetupper.doSetup(taskSpec, datasetToRegister, pandaJobs)
|
|
767
|
+
if (
|
|
768
|
+
tmpStat == Interaction.SC_FATAL
|
|
769
|
+
and taskSpec.frozenTime is not None
|
|
770
|
+
and naive_utcnow() - taskSpec.frozenTime > datetime.timedelta(days=7)
|
|
771
|
+
):
|
|
772
|
+
tmpErrStr = "fatal error when setting up task"
|
|
773
|
+
tmpLog.error(tmpErrStr)
|
|
774
|
+
taskSpec.status = "exhausted"
|
|
775
|
+
taskSpec.setErrDiag(tmpErrStr, True)
|
|
776
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
777
|
+
tmpErrStr = "failed to setup task"
|
|
778
|
+
tmpLog.error(tmpErrStr)
|
|
779
|
+
taskSpec.setOnHold()
|
|
780
|
+
taskSpec.setErrDiag(tmpErrStr, True)
|
|
781
|
+
else:
|
|
782
|
+
readyToSubmitJob = True
|
|
783
|
+
if taskSpec.toRegisterDatasets() and (not taskSpec.mergeOutput() or inputChunk.isMerging):
|
|
784
|
+
taskSpec.registeredDatasets()
|
|
785
|
+
# lock task
|
|
786
|
+
if goForward:
|
|
787
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock task"))
|
|
788
|
+
tmpStat = self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
789
|
+
if tmpStat is False:
|
|
790
|
+
tmpLog.debug("skip due to lock failure")
|
|
791
|
+
continue
|
|
792
|
+
# submit
|
|
793
|
+
if readyToSubmitJob:
|
|
794
|
+
# check if first submission
|
|
795
|
+
if oldStatus == "ready" and inputChunk.useScout():
|
|
796
|
+
firstSubmission = True
|
|
797
|
+
else:
|
|
798
|
+
firstSubmission = False
|
|
799
|
+
# type of relation
|
|
800
|
+
if inputChunk.isMerging:
|
|
801
|
+
relationType = "merge"
|
|
802
|
+
else:
|
|
803
|
+
relationType = "retry"
|
|
804
|
+
# submit
|
|
805
|
+
fqans = taskSpec.makeFQANs()
|
|
806
|
+
tmpLog.info(f"submit njobs={len(pandaJobs)} jobs with FQAN={','.join(str(fqan) for fqan in fqans)}")
|
|
807
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time(f"{len(pandaJobs)} job submission"))
|
|
808
|
+
iJobs = 0
|
|
809
|
+
nJobsInBunch = 500
|
|
810
|
+
resSubmit = []
|
|
811
|
+
esJobsetMap = {}
|
|
812
|
+
unprocessedMap = {}
|
|
813
|
+
while iJobs < len(pandaJobs):
|
|
814
|
+
tmpResSubmit, esJobsetMap, unprocessedMap = self.taskBufferIF.storeJobs(
|
|
815
|
+
pandaJobs[iJobs : iJobs + nJobsInBunch],
|
|
816
|
+
taskSpec.userName,
|
|
817
|
+
fqans=fqans,
|
|
818
|
+
toPending=True,
|
|
819
|
+
oldPandaIDs=oldPandaIDs[iJobs : iJobs + nJobsInBunch],
|
|
820
|
+
relationType=relationType,
|
|
821
|
+
esJobsetMap=esJobsetMap,
|
|
822
|
+
getEsJobsetMap=True,
|
|
823
|
+
unprocessedMap=unprocessedMap,
|
|
824
|
+
bulk_job_insert=True,
|
|
825
|
+
trust_user=True,
|
|
826
|
+
)
|
|
827
|
+
if isinstance(tmpResSubmit, str):
|
|
828
|
+
tmpLog.error(f"failed to store jobs with {tmpResSubmit}")
|
|
829
|
+
break
|
|
830
|
+
resSubmit += tmpResSubmit
|
|
831
|
+
self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
832
|
+
iJobs += nJobsInBunch
|
|
833
|
+
pandaIDs = []
|
|
834
|
+
nSkipJumbo = 0
|
|
835
|
+
for pandaJob, items in zip(pandaJobs, resSubmit):
|
|
836
|
+
if items[0] != "NULL":
|
|
837
|
+
pandaIDs.append(items[0])
|
|
838
|
+
elif EventServiceUtils.isJumboJob(pandaJob):
|
|
839
|
+
nSkipJumbo += 1
|
|
840
|
+
pandaIDs.append(items[0])
|
|
841
|
+
if nSkipJumbo > 0:
|
|
842
|
+
tmpLog.debug(f"{nSkipJumbo} jumbo jobs were skipped")
|
|
843
|
+
# check if submission was successful
|
|
844
|
+
if len(pandaIDs) == len(pandaJobs) and pandaJobs:
|
|
845
|
+
tmpMsg = (
|
|
846
|
+
f"successfully submitted jobs_submitted={len(pandaIDs)} / jobs_possible={len(pandaJobs)} "
|
|
847
|
+
f"for VO={taskSpec.vo} cloud={cloudName} queue={workqueue_name_nice} "
|
|
848
|
+
f"resource_type={self.resource_name} status={oldStatus} nucleus={taskSpec.nucleus} "
|
|
849
|
+
f"pmerge={'Y' if inputChunk.isMerging else 'N'}"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
tmpLog.info(tmpMsg)
|
|
853
|
+
tmpLog.sendMsg(tmpMsg, self.msgType)
|
|
854
|
+
if self.execJobs:
|
|
855
|
+
# skip fake co-jumbo and unsubmitted jumbo
|
|
856
|
+
pandaIDsForExec = []
|
|
857
|
+
for pandaID, pandaJob in zip(pandaIDs, pandaJobs):
|
|
858
|
+
if pandaJob.computingSite == EventServiceUtils.siteIdForWaitingCoJumboJobs:
|
|
859
|
+
continue
|
|
860
|
+
if pandaID == "NULL":
|
|
861
|
+
continue
|
|
862
|
+
pandaIDsForExec.append(pandaID)
|
|
863
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time(f"{len(pandaIDsForExec)} job execution"))
|
|
864
|
+
statExe, retExe = PandaClient.reassign_jobs(pandaIDsForExec)
|
|
865
|
+
tmpLog.info(f"exec {len(pandaIDsForExec)} jobs with status={retExe}")
|
|
866
|
+
jobsSubmitted = True
|
|
867
|
+
nSubmitSucceeded += 1
|
|
868
|
+
if inputChunk.isMerging:
|
|
869
|
+
# don't change task status by merging
|
|
870
|
+
pass
|
|
871
|
+
elif taskSpec.usePrePro():
|
|
872
|
+
taskSpec.status = "preprocessing"
|
|
873
|
+
elif inputChunk.useScout():
|
|
874
|
+
taskSpec.status = "scouting"
|
|
875
|
+
else:
|
|
876
|
+
taskSpec.status = "running"
|
|
877
|
+
# scout was skipped
|
|
878
|
+
if taskSpec.useScout():
|
|
879
|
+
taskSpec.setUseScout(False)
|
|
880
|
+
else:
|
|
881
|
+
tmpErrStr = f"submitted only {len(pandaIDs)}/{len(pandaJobs)}"
|
|
882
|
+
tmpLog.error(tmpErrStr)
|
|
883
|
+
taskSpec.setOnHold()
|
|
884
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
885
|
+
# the number of generated jobs
|
|
886
|
+
self.numGenJobs += len(pandaIDs)
|
|
887
|
+
# lock task
|
|
888
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time("lock task"))
|
|
889
|
+
tmpStat = self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
890
|
+
if tmpStat is False:
|
|
891
|
+
tmpLog.debug("skip due to lock failure")
|
|
892
|
+
continue
|
|
893
|
+
# reset unused files
|
|
894
|
+
nFileReset = self.taskBufferIF.resetUnusedFiles_JEDI(taskSpec.jediTaskID, inputChunk)
|
|
895
|
+
# set jumbo flag
|
|
896
|
+
if pendingJumbo:
|
|
897
|
+
tmpFlagStat = self.taskBufferIF.setUseJumboFlag_JEDI(taskSpec.jediTaskID, "pending")
|
|
898
|
+
tmpErrStr = "going to generate jumbo or real co-jumbo jobs when needed"
|
|
899
|
+
tmpLog.debug(tmpErrStr)
|
|
900
|
+
if tmpFlagStat:
|
|
901
|
+
taskSpec.setErrDiag(None)
|
|
902
|
+
else:
|
|
903
|
+
taskSpec.setOnHold()
|
|
904
|
+
taskSpec.setErrDiag(tmpErrStr)
|
|
905
|
+
elif jobsSubmitted and taskSpec.getNumJumboJobs() is not None and inputChunk.useJumbo is not None:
|
|
906
|
+
self.taskBufferIF.setUseJumboFlag_JEDI(taskSpec.jediTaskID, "running")
|
|
907
|
+
# unset lockedBy when all inputs are done for a task
|
|
908
|
+
setOldModTime = False
|
|
909
|
+
if idxInputList + 1 == len(inputList):
|
|
910
|
+
taskSpec.lockedBy = None
|
|
911
|
+
taskSpec.lockedTime = None
|
|
912
|
+
if taskSpec.status in ["running", "scouting"]:
|
|
913
|
+
setOldModTime = True
|
|
914
|
+
else:
|
|
915
|
+
taskSpec.lockedBy = self.pid
|
|
916
|
+
taskSpec.lockedTime = naive_utcnow()
|
|
917
|
+
# update task
|
|
918
|
+
retDB = self.taskBufferIF.updateTask_JEDI(
|
|
919
|
+
taskSpec,
|
|
920
|
+
{"jediTaskID": taskSpec.jediTaskID},
|
|
921
|
+
oldStatus=JediTaskSpec.statusForJobGenerator() + ["pending"],
|
|
922
|
+
setOldModTime=setOldModTime,
|
|
923
|
+
)
|
|
924
|
+
tmpMsg = f"set task_status={taskSpec.status} oldTask={setOldModTime} with {str(retDB)}"
|
|
925
|
+
if taskSpec.errorDialog not in ["", None]:
|
|
926
|
+
tmpMsg += " " + taskSpec.errorDialog
|
|
927
|
+
tmpLog.sendMsg(tmpMsg, self.msgType)
|
|
928
|
+
tmpLog.info(tmpMsg)
|
|
929
|
+
regTime = naive_utcnow() - loopStart
|
|
930
|
+
tmpLog.debug(main_stop_watch.get_elapsed_time(""))
|
|
931
|
+
tmpLog.info(f"done. took cycle_t={regTime.seconds} sec")
|
|
932
|
+
except Exception as e:
|
|
933
|
+
logger.error("%s.runImpl() failed with {} lastJediTaskID={} {}".format(self.__class__.__name__, str(e), lastJediTaskID, traceback.format_exc()))
|
|
934
|
+
|
|
935
|
+
# read task parameters
|
|
936
|
+
def readTaskParams(self, taskSpec, taskParamMap, tmpLog):
|
|
937
|
+
# already read
|
|
938
|
+
if taskParamMap is not None:
|
|
939
|
+
return True, taskParamMap
|
|
940
|
+
try:
|
|
941
|
+
# read task parameters
|
|
942
|
+
taskParam = self.taskBufferIF.getTaskParamsWithID_JEDI(taskSpec.jediTaskID)
|
|
943
|
+
taskParamMap = RefinerUtils.decodeJSON(taskParam)
|
|
944
|
+
return True, taskParamMap
|
|
945
|
+
except Exception:
|
|
946
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
947
|
+
tmpLog.error(f"task param conversion from json failed with {errtype.__name__}:{errvalue}")
|
|
948
|
+
return False, None
|
|
949
|
+
|
|
950
|
+
# generate jobs
|
|
951
|
+
def doGenerate(self, taskSpec, cloudName, inSubChunkList, inputChunk, tmpLog, simul=False, taskParamMap=None, splitter=None):
|
|
952
|
+
# return for failure
|
|
953
|
+
failedRet = Interaction.SC_FAILED, None, None, None, None, None
|
|
954
|
+
# read task parameters
|
|
955
|
+
tmpStat, taskParamMap = self.readTaskParams(taskSpec, taskParamMap, tmpLog)
|
|
956
|
+
if not tmpStat:
|
|
957
|
+
return failedRet
|
|
958
|
+
# special priorities
|
|
959
|
+
scoutPriority = 901
|
|
960
|
+
mergePriority = 5000
|
|
961
|
+
# register datasets
|
|
962
|
+
registerDatasets = taskSpec.toRegisterDatasets()
|
|
963
|
+
try:
|
|
964
|
+
# load XML
|
|
965
|
+
xmlConfig = None
|
|
966
|
+
if taskSpec.useLoadXML():
|
|
967
|
+
loadXML = taskParamMap["loadXML"]
|
|
968
|
+
xmlConfig = ParseJobXML.dom_parser(xmlStr=loadXML)
|
|
969
|
+
# using grouping with boundaryID
|
|
970
|
+
useBoundary = taskSpec.useGroupWithBoundaryID()
|
|
971
|
+
# loop over all sub chunks
|
|
972
|
+
jobSpecList = []
|
|
973
|
+
outDsMap = {}
|
|
974
|
+
datasetToRegister = []
|
|
975
|
+
oldPandaIDs = []
|
|
976
|
+
siteDsMap = {}
|
|
977
|
+
esIndex = 0
|
|
978
|
+
parallelOutMap = {}
|
|
979
|
+
dddMap = {}
|
|
980
|
+
fileIDPool = []
|
|
981
|
+
if inputChunk.isMerging:
|
|
982
|
+
confKey = f"MERGE_JOB_MAX_WALLTIME_{taskSpec.prodSourceLabel}"
|
|
983
|
+
merge_max_walltime = self.taskBufferIF.getConfigValue("jobgen", confKey, "jedi", taskSpec.vo)
|
|
984
|
+
else:
|
|
985
|
+
merge_max_walltime = None
|
|
986
|
+
# count expected number of jobs
|
|
987
|
+
totalNormalJobs = 0
|
|
988
|
+
n_jobs_per_site = {}
|
|
989
|
+
for tmpInChunk in inSubChunkList:
|
|
990
|
+
totalNormalJobs += len(tmpInChunk["subChunks"])
|
|
991
|
+
site_name = tmpInChunk["siteName"]
|
|
992
|
+
n_jobs_per_site.setdefault(site_name, 0)
|
|
993
|
+
n_jobs_per_site[site_name] += len(tmpInChunk["subChunks"])
|
|
994
|
+
# get number of outputs per job and bulk-fetch file IDs
|
|
995
|
+
if totalNormalJobs > 0:
|
|
996
|
+
if taskSpec.instantiateTmpl():
|
|
997
|
+
output_dataset_types = ["tmpl_output", "tmpl_log"]
|
|
998
|
+
else:
|
|
999
|
+
output_dataset_types = ["output", "log"]
|
|
1000
|
+
tmp_stat, tmp_dataset_specs = self.taskBufferIF.getDatasetsWithJediTaskID_JEDI(taskSpec.jediTaskID, output_dataset_types)
|
|
1001
|
+
if not tmp_stat:
|
|
1002
|
+
tmpLog.error("cannot get output/log datasets")
|
|
1003
|
+
return failedRet
|
|
1004
|
+
num_outputs_per_job = len(tmp_dataset_specs)
|
|
1005
|
+
if not simul and num_outputs_per_job > 0:
|
|
1006
|
+
fileIDPool = self.taskBufferIF.bulkFetchFileIDs_JEDI(taskSpec.jediTaskID, num_outputs_per_job * totalNormalJobs)
|
|
1007
|
+
else:
|
|
1008
|
+
fileIDPool = range(num_outputs_per_job * totalNormalJobs)
|
|
1009
|
+
else:
|
|
1010
|
+
num_outputs_per_job = None
|
|
1011
|
+
|
|
1012
|
+
# get random seeds
|
|
1013
|
+
random_seed_list = None
|
|
1014
|
+
random_seed_dataset = None
|
|
1015
|
+
if taskSpec.useRandomSeed() and not inputChunk.isMerging:
|
|
1016
|
+
tmp_stat, (random_seed_list, random_seed_dataset) = self.taskBufferIF.getRandomSeed_JEDI(taskSpec.jediTaskID, simul, totalNormalJobs)
|
|
1017
|
+
if not tmp_stat:
|
|
1018
|
+
tmpLog.error("failed to get random seeds")
|
|
1019
|
+
return failedRet
|
|
1020
|
+
|
|
1021
|
+
# check if the chunks produce outputs that need to be merged later
|
|
1022
|
+
if taskSpec.mergeOutput() and not inputChunk.isMerging:
|
|
1023
|
+
to_produce_outputs_merged_later = True
|
|
1024
|
+
else:
|
|
1025
|
+
to_produce_outputs_merged_later = False
|
|
1026
|
+
|
|
1027
|
+
# check if the chunks instantiate template datasets
|
|
1028
|
+
if to_produce_outputs_merged_later or taskSpec.instantiateTmpl():
|
|
1029
|
+
instantiate_template_dataset = True
|
|
1030
|
+
else:
|
|
1031
|
+
instantiate_template_dataset = False
|
|
1032
|
+
|
|
1033
|
+
# bulk fetch output files unless XML config is used to describe relationship between input and output files, or the task is configured
|
|
1034
|
+
# to produce multiple output files for each output stream per job, or output filenames inherit from input filenames, since in these cases
|
|
1035
|
+
# the output files cannot be fetched in one-go
|
|
1036
|
+
fetched_out_sub_chunks = {}
|
|
1037
|
+
fetched_serial_numbers = {}
|
|
1038
|
+
fetched_parallel_out_map = {}
|
|
1039
|
+
if (
|
|
1040
|
+
xmlConfig is None
|
|
1041
|
+
and (useBoundary is None or not useBoundary["outMap"])
|
|
1042
|
+
and not taskSpec.on_site_merging()
|
|
1043
|
+
and taskSpec.getFieldNumToLFN() is None
|
|
1044
|
+
):
|
|
1045
|
+
for site_name in n_jobs_per_site:
|
|
1046
|
+
# set site name if the chunks instantiate template datasets
|
|
1047
|
+
if to_produce_outputs_merged_later or (taskSpec.instantiateTmpl() and taskSpec.instantiateTmplSite()):
|
|
1048
|
+
site_to_instantiate = site_name
|
|
1049
|
+
else:
|
|
1050
|
+
site_to_instantiate = None
|
|
1051
|
+
# bulk fetch output files
|
|
1052
|
+
(
|
|
1053
|
+
fetched_out_sub_chunks[site_name],
|
|
1054
|
+
fetched_serial_numbers[site_name],
|
|
1055
|
+
tmp_datasets_to_register,
|
|
1056
|
+
siteDsMap,
|
|
1057
|
+
fetched_parallel_out_map[site_name],
|
|
1058
|
+
) = self.taskBufferIF.getOutputFiles_JEDI(
|
|
1059
|
+
taskSpec.jediTaskID,
|
|
1060
|
+
None,
|
|
1061
|
+
simul,
|
|
1062
|
+
instantiate_template_dataset,
|
|
1063
|
+
site_to_instantiate,
|
|
1064
|
+
to_produce_outputs_merged_later,
|
|
1065
|
+
False,
|
|
1066
|
+
None,
|
|
1067
|
+
siteDsMap,
|
|
1068
|
+
"",
|
|
1069
|
+
registerDatasets,
|
|
1070
|
+
None,
|
|
1071
|
+
fileIDPool,
|
|
1072
|
+
n_jobs_per_site[site_name],
|
|
1073
|
+
bulk_fetch_for_multiple_jobs=True,
|
|
1074
|
+
master_dataset_id=inputChunk.masterIndexName,
|
|
1075
|
+
)
|
|
1076
|
+
for tmp_dataset_spec in tmp_datasets_to_register:
|
|
1077
|
+
if tmp_dataset_spec not in datasetToRegister:
|
|
1078
|
+
datasetToRegister.append(tmp_dataset_spec)
|
|
1079
|
+
if not simul:
|
|
1080
|
+
try:
|
|
1081
|
+
fileIDPool = fileIDPool[num_outputs_per_job * n_jobs_per_site[site_name] :]
|
|
1082
|
+
except Exception:
|
|
1083
|
+
fileIDPool = []
|
|
1084
|
+
|
|
1085
|
+
# task queued time
|
|
1086
|
+
task_queued_time = taskSpec.get_queued_time()
|
|
1087
|
+
|
|
1088
|
+
# loop over all sub chunks
|
|
1089
|
+
for tmpInChunk in inSubChunkList:
|
|
1090
|
+
site_name_list = tmpInChunk["siteName"].split(",")
|
|
1091
|
+
siteName = tmpInChunk["siteName"]
|
|
1092
|
+
inSubChunks = tmpInChunk["subChunks"]
|
|
1093
|
+
siteCandidate = tmpInChunk["siteCandidate"]
|
|
1094
|
+
siteSpec = self.siteMapper.getSite(site_name_list[0])
|
|
1095
|
+
scope_input, scope_output = select_scope(siteSpec, taskSpec.prodSourceLabel, JobUtils.translate_tasktype_to_jobtype(taskSpec.taskType))
|
|
1096
|
+
buildFileSpec = None
|
|
1097
|
+
build_file_spec_map = {}
|
|
1098
|
+
# make preprocessing job
|
|
1099
|
+
if taskSpec.usePrePro():
|
|
1100
|
+
tmpStat, preproJobSpec, tmpToRegister = self.doGeneratePrePro(
|
|
1101
|
+
taskSpec, cloudName, siteName, siteSpec, taskParamMap, inSubChunks, tmpLog, simul
|
|
1102
|
+
)
|
|
1103
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
1104
|
+
tmpLog.error("failed to generate prepro job")
|
|
1105
|
+
return failedRet
|
|
1106
|
+
# append
|
|
1107
|
+
jobSpecList.append(preproJobSpec)
|
|
1108
|
+
oldPandaIDs.append([])
|
|
1109
|
+
# append datasets
|
|
1110
|
+
for tmpToRegisterItem in tmpToRegister:
|
|
1111
|
+
if tmpToRegisterItem not in datasetToRegister:
|
|
1112
|
+
datasetToRegister.append(tmpToRegisterItem)
|
|
1113
|
+
break
|
|
1114
|
+
# make build job
|
|
1115
|
+
elif taskSpec.useBuild():
|
|
1116
|
+
for idx, tmp_site_name in enumerate(site_name_list):
|
|
1117
|
+
tmp_site_spec = self.siteMapper.getSite(tmp_site_name)
|
|
1118
|
+
tmpStat, tmp_buildJobSpec, tmp_buildFileSpec, tmpToRegister = self.doGenerateBuild(
|
|
1119
|
+
taskSpec, cloudName, tmp_site_name, tmp_site_spec, taskParamMap, tmpLog, siteCandidate, simul
|
|
1120
|
+
)
|
|
1121
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
1122
|
+
tmpLog.error("failed to generate build job")
|
|
1123
|
+
return failedRet
|
|
1124
|
+
if idx == 0:
|
|
1125
|
+
buildJobSpec, buildFileSpec = tmp_buildJobSpec, tmp_buildFileSpec
|
|
1126
|
+
# append
|
|
1127
|
+
if tmp_buildJobSpec is not None:
|
|
1128
|
+
jobSpecList.append(tmp_buildJobSpec)
|
|
1129
|
+
oldPandaIDs.append([])
|
|
1130
|
+
build_file_spec_map[tmp_site_spec.get_unified_name()] = tmp_buildFileSpec
|
|
1131
|
+
# append datasets
|
|
1132
|
+
for tmpToRegisterItem in tmpToRegister:
|
|
1133
|
+
if tmpToRegisterItem not in datasetToRegister:
|
|
1134
|
+
datasetToRegister.append(tmpToRegisterItem)
|
|
1135
|
+
# make normal jobs
|
|
1136
|
+
tmpJobSpecList = []
|
|
1137
|
+
tmpMasterEventsList = []
|
|
1138
|
+
stop_watch = CoreUtils.StopWatch("gen_cycle")
|
|
1139
|
+
i_cycle = 0
|
|
1140
|
+
for inSubChunk in inSubChunks:
|
|
1141
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1142
|
+
stop_watch.reset()
|
|
1143
|
+
tmpLog.debug(stop_watch.get_elapsed_time(f"init {i_cycle}"))
|
|
1144
|
+
i_cycle += 1
|
|
1145
|
+
subOldPandaIDs = []
|
|
1146
|
+
jobSpec = JobSpec()
|
|
1147
|
+
jobSpec.jobDefinitionID = 0
|
|
1148
|
+
jobSpec.jobExecutionID = 0
|
|
1149
|
+
jobSpec.attemptNr = self.getLargestAttemptNr(inSubChunk)
|
|
1150
|
+
if taskSpec.disableAutoRetry():
|
|
1151
|
+
# disable server/pilot retry
|
|
1152
|
+
jobSpec.maxAttempt = -1
|
|
1153
|
+
elif taskSpec.useEventService(siteSpec) and not inputChunk.isMerging:
|
|
1154
|
+
# set max attempt for event service
|
|
1155
|
+
if taskSpec.getMaxAttemptEsJob() is None:
|
|
1156
|
+
jobSpec.maxAttempt = jobSpec.attemptNr + EventServiceUtils.defMaxAttemptEsJob
|
|
1157
|
+
else:
|
|
1158
|
+
jobSpec.maxAttempt = jobSpec.attemptNr + taskSpec.getMaxAttemptEsJob()
|
|
1159
|
+
else:
|
|
1160
|
+
jobSpec.maxAttempt = jobSpec.attemptNr
|
|
1161
|
+
jobSpec.jobName = taskSpec.taskName + ".$ORIGINPANDAID"
|
|
1162
|
+
if inputChunk.isMerging:
|
|
1163
|
+
# use merge trf
|
|
1164
|
+
jobSpec.transformation = taskParamMap["mergeSpec"]["transPath"]
|
|
1165
|
+
else:
|
|
1166
|
+
jobSpec.transformation = taskSpec.transPath
|
|
1167
|
+
platforms = siteCandidate.get_overridden_attribute("platforms")
|
|
1168
|
+
if platforms:
|
|
1169
|
+
jobSpec.cmtConfig = platforms
|
|
1170
|
+
else:
|
|
1171
|
+
jobSpec.cmtConfig = taskSpec.get_platforms()
|
|
1172
|
+
if taskSpec.transHome is not None:
|
|
1173
|
+
jobSpec.homepackage = re.sub("-(?P<dig>\d+\.)", "/\g<dig>", taskSpec.transHome)
|
|
1174
|
+
jobSpec.homepackage = re.sub("\r", "", jobSpec.homepackage)
|
|
1175
|
+
jobSpec.prodSourceLabel = taskSpec.prodSourceLabel
|
|
1176
|
+
if inputChunk.isMerging:
|
|
1177
|
+
# set merging type
|
|
1178
|
+
jobSpec.processingType = "pmerge"
|
|
1179
|
+
else:
|
|
1180
|
+
jobSpec.processingType = taskSpec.processingType
|
|
1181
|
+
jobSpec.jediTaskID = taskSpec.jediTaskID
|
|
1182
|
+
jobSpec.taskID = taskSpec.jediTaskID
|
|
1183
|
+
jobSpec.jobsetID = taskSpec.reqID
|
|
1184
|
+
jobSpec.reqID = taskSpec.reqID
|
|
1185
|
+
jobSpec.workingGroup = taskSpec.workingGroup
|
|
1186
|
+
jobSpec.countryGroup = taskSpec.countryGroup
|
|
1187
|
+
if inputChunk.useJumbo in ["fake", "only"]:
|
|
1188
|
+
jobSpec.computingSite = EventServiceUtils.siteIdForWaitingCoJumboJobs
|
|
1189
|
+
else:
|
|
1190
|
+
if taskSpec.useEventService(siteSpec) and not inputChunk.isMerging and taskSpec.getNumEventServiceConsumer() is not None:
|
|
1191
|
+
# keep concatenated site names which are converted when increasing consumers
|
|
1192
|
+
jobSpec.computingSite = siteName
|
|
1193
|
+
else:
|
|
1194
|
+
jobSpec.computingSite = siteSpec.get_unified_name()
|
|
1195
|
+
if taskSpec.cloud_as_vo():
|
|
1196
|
+
jobSpec.cloud = taskSpec.cloud
|
|
1197
|
+
else:
|
|
1198
|
+
jobSpec.cloud = cloudName
|
|
1199
|
+
jobSpec.nucleus = taskSpec.nucleus
|
|
1200
|
+
jobSpec.VO = taskSpec.vo
|
|
1201
|
+
jobSpec.prodSeriesLabel = "pandatest"
|
|
1202
|
+
jobSpec.AtlasRelease = taskSpec.transUses
|
|
1203
|
+
jobSpec.AtlasRelease = re.sub("\r", "", jobSpec.AtlasRelease)
|
|
1204
|
+
jobSpec.maxCpuCount = taskSpec.walltime
|
|
1205
|
+
jobSpec.maxCpuUnit = taskSpec.walltimeUnit
|
|
1206
|
+
if inputChunk.isMerging and splitter is not None:
|
|
1207
|
+
jobSpec.maxDiskCount = splitter.sizeGradientsPerInSizeForMerge
|
|
1208
|
+
else:
|
|
1209
|
+
jobSpec.maxDiskCount = taskSpec.getOutDiskSize()
|
|
1210
|
+
jobSpec.maxDiskUnit = "MB"
|
|
1211
|
+
if inputChunk.isMerging and taskSpec.mergeCoreCount is not None:
|
|
1212
|
+
jobSpec.coreCount = taskSpec.mergeCoreCount
|
|
1213
|
+
elif inputChunk.isMerging and siteSpec.sitename != siteSpec.get_unified_name():
|
|
1214
|
+
jobSpec.coreCount = 1
|
|
1215
|
+
else:
|
|
1216
|
+
if taskSpec.coreCount == 1 or siteSpec.coreCount in [None, 0]:
|
|
1217
|
+
jobSpec.coreCount = 1
|
|
1218
|
+
elif siteSpec.coreCount == -1:
|
|
1219
|
+
jobSpec.coreCount = taskSpec.coreCount
|
|
1220
|
+
else:
|
|
1221
|
+
jobSpec.coreCount = siteSpec.coreCount
|
|
1222
|
+
jobSpec.minRamCount, jobSpec.minRamUnit = JobUtils.getJobMinRamCount(taskSpec, inputChunk, siteSpec, jobSpec.coreCount)
|
|
1223
|
+
# calculate the hs06 occupied by the job
|
|
1224
|
+
if siteSpec.corepower:
|
|
1225
|
+
jobSpec.hs06 = (jobSpec.coreCount or 1) * siteSpec.corepower # default 0 and None corecount to 1
|
|
1226
|
+
jobSpec.diskIO = taskSpec.diskIO
|
|
1227
|
+
jobSpec.ipConnectivity = "yes"
|
|
1228
|
+
jobSpec.metadata = ""
|
|
1229
|
+
if inputChunk.isMerging:
|
|
1230
|
+
# give higher priority to merge jobs
|
|
1231
|
+
jobSpec.assignedPriority = mergePriority
|
|
1232
|
+
elif inputChunk.useScout():
|
|
1233
|
+
# give higher priority to scouts max(scoutPriority,taskPrio+1)
|
|
1234
|
+
if taskSpec.currentPriority < scoutPriority:
|
|
1235
|
+
jobSpec.assignedPriority = scoutPriority
|
|
1236
|
+
else:
|
|
1237
|
+
jobSpec.assignedPriority = taskSpec.currentPriority + 1
|
|
1238
|
+
else:
|
|
1239
|
+
jobSpec.assignedPriority = taskSpec.currentPriority
|
|
1240
|
+
jobSpec.currentPriority = jobSpec.assignedPriority
|
|
1241
|
+
jobSpec.lockedby = "jedi"
|
|
1242
|
+
jobSpec.workQueue_ID = taskSpec.workQueue_ID
|
|
1243
|
+
jobSpec.gshare = taskSpec.gshare
|
|
1244
|
+
jobSpec.container_name = taskSpec.container_name
|
|
1245
|
+
jobSpec.job_label = JobUtils.translate_tasktype_to_jobtype(taskSpec.taskType)
|
|
1246
|
+
# disable reassign
|
|
1247
|
+
if taskSpec.disableReassign():
|
|
1248
|
+
jobSpec.relocationFlag = 2
|
|
1249
|
+
# using grouping with boundaryID
|
|
1250
|
+
boundaryID = None
|
|
1251
|
+
# flag for merging
|
|
1252
|
+
isUnMerging = False
|
|
1253
|
+
# special handling
|
|
1254
|
+
specialHandling = ""
|
|
1255
|
+
# DDM backend
|
|
1256
|
+
tmpDdmBackEnd = taskSpec.getDdmBackEnd()
|
|
1257
|
+
if tmpDdmBackEnd is not None:
|
|
1258
|
+
if specialHandling == "":
|
|
1259
|
+
specialHandling = f"ddm:{tmpDdmBackEnd},"
|
|
1260
|
+
else:
|
|
1261
|
+
specialHandling += f",ddm:{tmpDdmBackEnd},"
|
|
1262
|
+
# set specialHandling for Event Service
|
|
1263
|
+
if (taskSpec.useEventService(siteSpec) or taskSpec.is_fine_grained_process()) and not inputChunk.isMerging:
|
|
1264
|
+
specialHandling += EventServiceUtils.getHeaderForES(esIndex)
|
|
1265
|
+
if taskSpec.useJumbo is None:
|
|
1266
|
+
# normal ES job
|
|
1267
|
+
jobSpec.eventService = EventServiceUtils.esJobFlagNumber
|
|
1268
|
+
else:
|
|
1269
|
+
# co-jumbo job
|
|
1270
|
+
jobSpec.eventService = EventServiceUtils.coJumboJobFlagNumber
|
|
1271
|
+
# inputs
|
|
1272
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1273
|
+
tmpLog.debug(stop_watch.get_elapsed_time("inputs"))
|
|
1274
|
+
prodDBlock = None
|
|
1275
|
+
setProdDBlock = False
|
|
1276
|
+
totalMasterSize = 0
|
|
1277
|
+
totalMasterEvents = 0
|
|
1278
|
+
totalFileSize = 0
|
|
1279
|
+
lumiBlockNr = None
|
|
1280
|
+
setSpecialHandlingForJC = False
|
|
1281
|
+
setInputPrestaging = False
|
|
1282
|
+
segmentName = None
|
|
1283
|
+
segmentID = None
|
|
1284
|
+
for tmpDatasetSpec, tmpFileSpecList in inSubChunk:
|
|
1285
|
+
# get boundaryID if grouping is done with boundaryID
|
|
1286
|
+
if useBoundary is not None and boundaryID is None and tmpDatasetSpec.isMaster():
|
|
1287
|
+
boundaryID = tmpFileSpecList[0].boundaryID
|
|
1288
|
+
# get prodDBlock
|
|
1289
|
+
if not tmpDatasetSpec.isPseudo():
|
|
1290
|
+
if tmpDatasetSpec.isMaster():
|
|
1291
|
+
jobSpec.prodDBlock = tmpDatasetSpec.datasetName
|
|
1292
|
+
setProdDBlock = True
|
|
1293
|
+
else:
|
|
1294
|
+
prodDBlock = tmpDatasetSpec.datasetName
|
|
1295
|
+
# get segment information
|
|
1296
|
+
if taskSpec.is_work_segmented() and tmpDatasetSpec.isMaster() and tmpDatasetSpec.isPseudo():
|
|
1297
|
+
segmentID = tmpDatasetSpec.datasetID
|
|
1298
|
+
segmentName = tmpDatasetSpec.containerName.split("/")[0]
|
|
1299
|
+
# making files
|
|
1300
|
+
for tmpFileSpec in tmpFileSpecList:
|
|
1301
|
+
if inputChunk.isMerging:
|
|
1302
|
+
tmpInFileSpec = tmpFileSpec.convertToJobFileSpec(tmpDatasetSpec, setType="input")
|
|
1303
|
+
else:
|
|
1304
|
+
tmpInFileSpec = tmpFileSpec.convertToJobFileSpec(tmpDatasetSpec)
|
|
1305
|
+
# set status
|
|
1306
|
+
if tmpFileSpec.locality in ["localdisk", "remote"]:
|
|
1307
|
+
tmpInFileSpec.status = "ready"
|
|
1308
|
+
elif tmpFileSpec.locality == "cache":
|
|
1309
|
+
tmpInFileSpec.status = "cached"
|
|
1310
|
+
elif tmpFileSpec.locality == "localtape":
|
|
1311
|
+
setInputPrestaging = True
|
|
1312
|
+
# local IO
|
|
1313
|
+
if taskSpec.useLocalIO() or (inputChunk.isMerging and "useLocalIO" in taskParamMap["mergeSpec"]):
|
|
1314
|
+
tmpInFileSpec.prodDBlockToken = "local"
|
|
1315
|
+
jobSpec.addFile(tmpInFileSpec)
|
|
1316
|
+
# use remote access
|
|
1317
|
+
if tmpFileSpec.locality == "remote":
|
|
1318
|
+
jobSpec.transferType = siteCandidate.remoteProtocol
|
|
1319
|
+
jobSpec.sourceSite = siteCandidate.remoteSource
|
|
1320
|
+
elif not inputChunk.isMerging and not taskSpec.useLocalIO() and (taskSpec.allowInputLAN() is not None and siteSpec.isDirectIO()):
|
|
1321
|
+
jobSpec.transferType = "direct"
|
|
1322
|
+
# collect old PandaIDs
|
|
1323
|
+
if tmpFileSpec.PandaID is not None and tmpFileSpec.PandaID not in subOldPandaIDs:
|
|
1324
|
+
subOldPandaIDs.append(tmpFileSpec.PandaID)
|
|
1325
|
+
"""
|
|
1326
|
+
subOldMergePandaIDs = self.taskBufferIF.getOldMergeJobPandaIDs_JEDI(taskSpec.jediTaskID,
|
|
1327
|
+
tmpFileSpec.PandaID)
|
|
1328
|
+
subOldPandaIDs += subOldMergePandaIDs
|
|
1329
|
+
"""
|
|
1330
|
+
# set specialHandling for normal Event Service
|
|
1331
|
+
if (
|
|
1332
|
+
(taskSpec.useEventService(siteSpec) or taskSpec.is_fine_grained_process())
|
|
1333
|
+
and not inputChunk.isMerging
|
|
1334
|
+
and tmpDatasetSpec.isMaster()
|
|
1335
|
+
):
|
|
1336
|
+
if not taskSpec.useJobCloning() or not setSpecialHandlingForJC:
|
|
1337
|
+
if taskSpec.useJobCloning():
|
|
1338
|
+
# single event range for job cloning
|
|
1339
|
+
if tmpFileSpec.startEvent is not None:
|
|
1340
|
+
nEventsPerWorker = tmpFileSpec.endEvent - tmpFileSpec.startEvent + 1
|
|
1341
|
+
else:
|
|
1342
|
+
nEventsPerWorker = tmpFileSpec.nEvents
|
|
1343
|
+
setSpecialHandlingForJC = True
|
|
1344
|
+
elif taskSpec.is_fine_grained_process():
|
|
1345
|
+
nEventsPerWorker = 1
|
|
1346
|
+
else:
|
|
1347
|
+
nEventsPerWorker = taskSpec.getNumEventsPerWorker()
|
|
1348
|
+
# set start and end events
|
|
1349
|
+
tmpStartEvent = tmpFileSpec.startEvent
|
|
1350
|
+
if tmpStartEvent is None:
|
|
1351
|
+
tmpStartEvent = 0
|
|
1352
|
+
tmpEndEvent = tmpFileSpec.endEvent
|
|
1353
|
+
if tmpEndEvent is None:
|
|
1354
|
+
tmpEndEvent = tmpFileSpec.nEvents - 1
|
|
1355
|
+
if tmpEndEvent is None:
|
|
1356
|
+
tmpEndEvent = 0
|
|
1357
|
+
if tmpEndEvent < tmpStartEvent:
|
|
1358
|
+
tmpEndEvent = tmpStartEvent
|
|
1359
|
+
# first event number and offset without/with in-file positional event numbers
|
|
1360
|
+
if not taskSpec.inFilePosEvtNum():
|
|
1361
|
+
tmpFirstEventOffset = taskSpec.getFirstEventOffset()
|
|
1362
|
+
tmpFirstEvent = tmpFileSpec.firstEvent
|
|
1363
|
+
if tmpFirstEvent is None:
|
|
1364
|
+
tmpFirstEvent = tmpFirstEventOffset
|
|
1365
|
+
else:
|
|
1366
|
+
tmpFirstEventOffset = None
|
|
1367
|
+
tmpFirstEvent = None
|
|
1368
|
+
specialHandling += EventServiceUtils.encodeFileInfo(
|
|
1369
|
+
tmpFileSpec.lfn,
|
|
1370
|
+
tmpStartEvent,
|
|
1371
|
+
tmpEndEvent,
|
|
1372
|
+
nEventsPerWorker,
|
|
1373
|
+
taskSpec.getMaxAttemptES(),
|
|
1374
|
+
tmpFirstEventOffset,
|
|
1375
|
+
tmpFirstEvent,
|
|
1376
|
+
)
|
|
1377
|
+
# calculate total master size
|
|
1378
|
+
if tmpDatasetSpec.isMaster():
|
|
1379
|
+
totalMasterSize += CoreUtils.getEffectiveFileSize(
|
|
1380
|
+
tmpFileSpec.fsize, tmpFileSpec.startEvent, tmpFileSpec.endEvent, tmpFileSpec.nEvents
|
|
1381
|
+
)
|
|
1382
|
+
totalMasterEvents += tmpFileSpec.getEffectiveNumEvents()
|
|
1383
|
+
# set failure count
|
|
1384
|
+
if tmpFileSpec.failedAttempt is not None:
|
|
1385
|
+
if jobSpec.failedAttempt in [None, "NULL"] or jobSpec.failedAttempt < tmpFileSpec.failedAttempt:
|
|
1386
|
+
jobSpec.failedAttempt = tmpFileSpec.failedAttempt
|
|
1387
|
+
# total file size
|
|
1388
|
+
if tmpInFileSpec.status != "cached":
|
|
1389
|
+
totalFileSize += tmpFileSpec.fsize
|
|
1390
|
+
# lumi block number
|
|
1391
|
+
if tmpDatasetSpec.isMaster() and lumiBlockNr is None:
|
|
1392
|
+
lumiBlockNr = tmpFileSpec.lumiBlockNr
|
|
1393
|
+
# check if merging
|
|
1394
|
+
if taskSpec.mergeOutput() and tmpDatasetSpec.isMaster() and not tmpDatasetSpec.toMerge():
|
|
1395
|
+
isUnMerging = True
|
|
1396
|
+
# tarball is downloaded by pilot
|
|
1397
|
+
tarball_via_pilot = taskParamMap.get("tarBallViaDDM")
|
|
1398
|
+
if tarball_via_pilot:
|
|
1399
|
+
tmp_file_spec = FileSpec()
|
|
1400
|
+
tmp_file_spec.lfn = tarball_via_pilot.split(":")[-1]
|
|
1401
|
+
tmp_file_spec.type = "input"
|
|
1402
|
+
tmp_file_spec.attemptNr = 0
|
|
1403
|
+
tmp_file_spec.jediTaskID = taskSpec.jediTaskID
|
|
1404
|
+
tmp_file_spec.dataset = tarball_via_pilot.split(":")[0]
|
|
1405
|
+
tmp_file_spec.dispatchDBlock = tmp_file_spec.dataset
|
|
1406
|
+
tmp_file_spec.prodDBlockToken = "local"
|
|
1407
|
+
tmp_file_spec.status = "ready"
|
|
1408
|
+
jobSpec.addFile(tmp_file_spec)
|
|
1409
|
+
specialHandling = specialHandling[:-1]
|
|
1410
|
+
# using job cloning
|
|
1411
|
+
if setSpecialHandlingForJC:
|
|
1412
|
+
specialHandling = EventServiceUtils.setHeaderForJobCloning(specialHandling, taskSpec.getJobCloningType())
|
|
1413
|
+
# dynamic number of events
|
|
1414
|
+
if not inputChunk.isMerging and taskSpec.dynamicNumEvents():
|
|
1415
|
+
specialHandling = EventServiceUtils.setHeaderForDynNumEvents(specialHandling)
|
|
1416
|
+
# merge ES on ObjectStore
|
|
1417
|
+
if taskSpec.mergeEsOnOS():
|
|
1418
|
+
specialHandling = EventServiceUtils.setHeaderForMergeAtOS(specialHandling)
|
|
1419
|
+
# resurrect consumers
|
|
1420
|
+
if taskSpec.resurrectConsumers():
|
|
1421
|
+
specialHandling = EventServiceUtils.setHeaderToResurrectConsumers(specialHandling)
|
|
1422
|
+
# set specialHandling
|
|
1423
|
+
if specialHandling != "":
|
|
1424
|
+
jobSpec.specialHandling = specialHandling
|
|
1425
|
+
# set home cloud
|
|
1426
|
+
if taskSpec.useWorldCloud():
|
|
1427
|
+
jobSpec.setHomeCloud(siteSpec.cloud)
|
|
1428
|
+
# allow partial finish
|
|
1429
|
+
if taskSpec.allowPartialFinish():
|
|
1430
|
+
jobSpec.setToAcceptPartialFinish()
|
|
1431
|
+
# alternative stage-out
|
|
1432
|
+
if taskSpec.mergeOutput() and not inputChunk.isMerging:
|
|
1433
|
+
# disable alternative stage-out for pre-merge jobs
|
|
1434
|
+
jobSpec.setAltStgOut("off")
|
|
1435
|
+
elif taskSpec.getAltStageOut() is not None:
|
|
1436
|
+
jobSpec.setAltStgOut(taskSpec.getAltStageOut())
|
|
1437
|
+
# log to OS
|
|
1438
|
+
if taskSpec.putLogToOS():
|
|
1439
|
+
jobSpec.setToPutLogToOS()
|
|
1440
|
+
# suppress execute string conversion
|
|
1441
|
+
if taskSpec.noExecStrCnv():
|
|
1442
|
+
jobSpec.setNoExecStrCnv()
|
|
1443
|
+
# in-file positional event number
|
|
1444
|
+
if taskSpec.inFilePosEvtNum():
|
|
1445
|
+
jobSpec.setInFilePosEvtNum()
|
|
1446
|
+
# register event service files
|
|
1447
|
+
if taskSpec.registerEsFiles():
|
|
1448
|
+
jobSpec.setRegisterEsFiles()
|
|
1449
|
+
# use prefetcher
|
|
1450
|
+
if taskSpec.usePrefetcher():
|
|
1451
|
+
jobSpec.setUsePrefetcher()
|
|
1452
|
+
# not discard events
|
|
1453
|
+
if taskSpec.notDiscardEvents():
|
|
1454
|
+
jobSpec.setNotDiscardEvents()
|
|
1455
|
+
# decrement attemptNr of events only when failed
|
|
1456
|
+
if taskSpec.decAttOnFailedES():
|
|
1457
|
+
jobSpec.setDecAttOnFailedES()
|
|
1458
|
+
# use zip to pin input files
|
|
1459
|
+
if taskSpec.useZipToPin():
|
|
1460
|
+
jobSpec.setUseZipToPin()
|
|
1461
|
+
# write input to file
|
|
1462
|
+
if taskSpec.writeInputToFile():
|
|
1463
|
+
jobSpec.setToWriteInputToFile()
|
|
1464
|
+
# set lumi block number
|
|
1465
|
+
if lumiBlockNr is not None:
|
|
1466
|
+
jobSpec.setLumiBlockNr(lumiBlockNr)
|
|
1467
|
+
# looping check
|
|
1468
|
+
if taskSpec.no_looping_check():
|
|
1469
|
+
jobSpec.disable_looping_check()
|
|
1470
|
+
# fake job
|
|
1471
|
+
if jobSpec.computingSite == EventServiceUtils.siteIdForWaitingCoJumboJobs:
|
|
1472
|
+
jobSpec.setFakeJobToIgnore()
|
|
1473
|
+
# use secondary dataset name as prodDBlock
|
|
1474
|
+
if setProdDBlock is False and prodDBlock is not None:
|
|
1475
|
+
jobSpec.prodDBlock = prodDBlock
|
|
1476
|
+
# scout
|
|
1477
|
+
if inputChunk.useScout():
|
|
1478
|
+
jobSpec.setScoutJobFlag()
|
|
1479
|
+
# prestaging
|
|
1480
|
+
if setInputPrestaging:
|
|
1481
|
+
jobSpec.setInputPrestaging()
|
|
1482
|
+
# HPO
|
|
1483
|
+
if taskSpec.is_hpo_workflow():
|
|
1484
|
+
jobSpec.set_hpo_workflow()
|
|
1485
|
+
# encode job parameters
|
|
1486
|
+
if taskSpec.encode_job_params():
|
|
1487
|
+
jobSpec.set_encode_job_params()
|
|
1488
|
+
# use secrets
|
|
1489
|
+
if taskSpec.use_secrets():
|
|
1490
|
+
jobSpec.set_use_secrets()
|
|
1491
|
+
# debug mode
|
|
1492
|
+
if taskSpec.is_debug_mode():
|
|
1493
|
+
jobSpec.set_debug_mode()
|
|
1494
|
+
# publish status changes
|
|
1495
|
+
if taskSpec.push_status_changes():
|
|
1496
|
+
jobSpec.set_push_status_changes()
|
|
1497
|
+
# push job
|
|
1498
|
+
if taskSpec.push_job():
|
|
1499
|
+
jobSpec.set_push_job()
|
|
1500
|
+
# fine-grained process
|
|
1501
|
+
if taskSpec.is_fine_grained_process():
|
|
1502
|
+
EventServiceUtils.set_fine_grained(jobSpec)
|
|
1503
|
+
# on-site merging
|
|
1504
|
+
if taskSpec.on_site_merging():
|
|
1505
|
+
jobSpec.set_on_site_merging()
|
|
1506
|
+
# set task queued time
|
|
1507
|
+
jobSpec.set_task_queued_time(task_queued_time)
|
|
1508
|
+
# extract middle name
|
|
1509
|
+
middleName = ""
|
|
1510
|
+
if taskSpec.getFieldNumToLFN() is not None and jobSpec.prodDBlock not in [None, "NULL", ""]:
|
|
1511
|
+
if inputChunk.isMerging:
|
|
1512
|
+
# extract from LFN of unmerged files
|
|
1513
|
+
for tmpDatasetSpec, tmpFileSpecList in inSubChunk:
|
|
1514
|
+
if not tmpDatasetSpec.isMaster():
|
|
1515
|
+
try:
|
|
1516
|
+
middleName = "." + ".".join(tmpFileSpecList[0].lfn.split(".")[4 : 4 + len(taskSpec.getFieldNumToLFN())])
|
|
1517
|
+
except Exception:
|
|
1518
|
+
pass
|
|
1519
|
+
break
|
|
1520
|
+
else:
|
|
1521
|
+
# extract from file or dataset name
|
|
1522
|
+
if taskSpec.useFileAsSourceLFN():
|
|
1523
|
+
for tmpDatasetSpec, tmpFileSpecList in inSubChunk:
|
|
1524
|
+
if tmpDatasetSpec.isMaster():
|
|
1525
|
+
middleName = tmpFileSpecList[0].extractFieldsStr(taskSpec.getFieldNumToLFN())
|
|
1526
|
+
break
|
|
1527
|
+
else:
|
|
1528
|
+
tmpMidStr = jobSpec.prodDBlock.split(":")[-1]
|
|
1529
|
+
tmpMidStrList = re.split("\.|_tid\d+", tmpMidStr)
|
|
1530
|
+
if len(tmpMidStrList) >= max(taskSpec.getFieldNumToLFN()):
|
|
1531
|
+
middleName = ""
|
|
1532
|
+
for tmpFieldNum in taskSpec.getFieldNumToLFN():
|
|
1533
|
+
middleName += "." + tmpMidStrList[tmpFieldNum - 1]
|
|
1534
|
+
# append segment name to middle name
|
|
1535
|
+
if segmentName is not None:
|
|
1536
|
+
if middleName:
|
|
1537
|
+
middleName += "_"
|
|
1538
|
+
middleName += segmentName
|
|
1539
|
+
# set provenanceID
|
|
1540
|
+
provenanceID = None
|
|
1541
|
+
if useBoundary is not None and useBoundary["outMap"] is True:
|
|
1542
|
+
provenanceID = boundaryID
|
|
1543
|
+
# instantiate template datasets
|
|
1544
|
+
instantiateTmpl = False
|
|
1545
|
+
instantiatedSite = None
|
|
1546
|
+
if isUnMerging:
|
|
1547
|
+
instantiateTmpl = True
|
|
1548
|
+
instantiatedSite = siteName
|
|
1549
|
+
elif taskSpec.instantiateTmpl():
|
|
1550
|
+
instantiateTmpl = True
|
|
1551
|
+
if taskSpec.instantiateTmplSite():
|
|
1552
|
+
instantiatedSite = siteName
|
|
1553
|
+
else:
|
|
1554
|
+
instantiateTmpl = False
|
|
1555
|
+
# multiply maxCpuCount by total master size
|
|
1556
|
+
try:
|
|
1557
|
+
if jobSpec.maxCpuCount > 0:
|
|
1558
|
+
jobSpec.maxCpuCount *= totalMasterSize
|
|
1559
|
+
jobSpec.maxCpuCount = int(jobSpec.maxCpuCount)
|
|
1560
|
+
else:
|
|
1561
|
+
# negative cpu count to suppress looping job detection
|
|
1562
|
+
jobSpec.maxCpuCount *= -1
|
|
1563
|
+
except Exception:
|
|
1564
|
+
pass
|
|
1565
|
+
# maxWalltime
|
|
1566
|
+
tmpMasterEventsList.append(totalMasterEvents)
|
|
1567
|
+
CoreUtils.getJobMaxWalltime(taskSpec, inputChunk, totalMasterEvents, jobSpec, siteSpec)
|
|
1568
|
+
# multiply maxDiskCount by total master size or # of events
|
|
1569
|
+
try:
|
|
1570
|
+
if inputChunk.isMerging:
|
|
1571
|
+
jobSpec.maxDiskCount *= totalFileSize
|
|
1572
|
+
elif not taskSpec.outputScaleWithEvents():
|
|
1573
|
+
jobSpec.maxDiskCount *= totalMasterSize
|
|
1574
|
+
else:
|
|
1575
|
+
jobSpec.maxDiskCount *= totalMasterEvents
|
|
1576
|
+
except Exception:
|
|
1577
|
+
pass
|
|
1578
|
+
# add offset to maxDiskCount
|
|
1579
|
+
try:
|
|
1580
|
+
if inputChunk.isMerging and splitter is not None:
|
|
1581
|
+
jobSpec.maxDiskCount += max(taskSpec.getWorkDiskSize(), splitter.interceptsMerginForMerge)
|
|
1582
|
+
else:
|
|
1583
|
+
jobSpec.maxDiskCount += taskSpec.getWorkDiskSize()
|
|
1584
|
+
except Exception:
|
|
1585
|
+
pass
|
|
1586
|
+
# add input size
|
|
1587
|
+
if not CoreUtils.use_direct_io_for_job(taskSpec, siteSpec, inputChunk):
|
|
1588
|
+
jobSpec.maxDiskCount += totalFileSize
|
|
1589
|
+
# maxDiskCount in MB
|
|
1590
|
+
jobSpec.maxDiskCount /= 1024 * 1024
|
|
1591
|
+
jobSpec.maxDiskCount = int(jobSpec.maxDiskCount)
|
|
1592
|
+
# cap not to go over site limit
|
|
1593
|
+
if siteSpec.maxwdir and jobSpec.maxDiskCount and siteSpec.maxwdir < jobSpec.maxDiskCount:
|
|
1594
|
+
jobSpec.maxDiskCount = siteSpec.maxwdir
|
|
1595
|
+
# unset maxCpuCount and minRamCount for merge jobs
|
|
1596
|
+
if inputChunk.isMerging:
|
|
1597
|
+
if merge_max_walltime is None:
|
|
1598
|
+
jobSpec.maxCpuCount = 0
|
|
1599
|
+
jobSpec.maxWalltime = siteSpec.maxtime
|
|
1600
|
+
else:
|
|
1601
|
+
jobSpec.maxCpuCount = merge_max_walltime * 60 * 60
|
|
1602
|
+
jobSpec.maxWalltime = jobSpec.maxCpuCount
|
|
1603
|
+
if taskSpec.baseWalltime is not None:
|
|
1604
|
+
jobSpec.maxWalltime += taskSpec.baseWalltime
|
|
1605
|
+
|
|
1606
|
+
if jobSpec.minRamCount in [0, None, "NULL"]:
|
|
1607
|
+
# set 2GB RAM for merge jobs by default
|
|
1608
|
+
jobSpec.minRamCount = 2000
|
|
1609
|
+
|
|
1610
|
+
# set retry RAM count
|
|
1611
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1612
|
+
tmpLog.debug(stop_watch.get_elapsed_time("resource_type"))
|
|
1613
|
+
retry_ram = taskSpec.get_ram_for_retry(jobSpec.minRamCount)
|
|
1614
|
+
if retry_ram:
|
|
1615
|
+
jobSpec.set_ram_for_retry(retry_ram)
|
|
1616
|
+
try:
|
|
1617
|
+
jobSpec.resource_type = JobUtils.get_resource_type_job(self.resource_types, jobSpec)
|
|
1618
|
+
except Exception:
|
|
1619
|
+
jobSpec.resource_type = "Undefined"
|
|
1620
|
+
tmpLog.error(f"set resource_type excepted with {traceback.format_exc()}")
|
|
1621
|
+
# XML config
|
|
1622
|
+
xmlConfigJob = None
|
|
1623
|
+
if xmlConfig is not None:
|
|
1624
|
+
try:
|
|
1625
|
+
xmlConfigJob = xmlConfig.jobs[boundaryID]
|
|
1626
|
+
except Exception:
|
|
1627
|
+
tmpLog.error(f"failed to get XML config for N={boundaryID}")
|
|
1628
|
+
return failedRet
|
|
1629
|
+
# num of output files per job
|
|
1630
|
+
if taskSpec.on_site_merging():
|
|
1631
|
+
tmpStat, taskParamMap = self.readTaskParams(taskSpec, taskParamMap, tmpLog)
|
|
1632
|
+
if not tmpStat:
|
|
1633
|
+
return failedRet
|
|
1634
|
+
if "nEventsPerOutputFile" in taskParamMap and totalMasterEvents:
|
|
1635
|
+
n_files_per_chunk = int(totalMasterEvents / taskParamMap["nEventsPerOutputFile"])
|
|
1636
|
+
else:
|
|
1637
|
+
n_files_per_chunk = 1
|
|
1638
|
+
else:
|
|
1639
|
+
n_files_per_chunk = 1
|
|
1640
|
+
# outputs
|
|
1641
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1642
|
+
tmpLog.debug(stop_watch.get_elapsed_time("outputs"))
|
|
1643
|
+
if siteName in fetched_out_sub_chunks and fetched_out_sub_chunks[siteName]:
|
|
1644
|
+
outSubChunk = fetched_out_sub_chunks[siteName].pop(0)
|
|
1645
|
+
serialNr = fetched_serial_numbers[siteName].pop(0)
|
|
1646
|
+
tmpToRegister = []
|
|
1647
|
+
tmpParOutMap = fetched_parallel_out_map[siteName].pop(0)
|
|
1648
|
+
else:
|
|
1649
|
+
outSubChunk, serialNr, tmpToRegister, siteDsMap, tmpParOutMap = self.taskBufferIF.getOutputFiles_JEDI(
|
|
1650
|
+
taskSpec.jediTaskID,
|
|
1651
|
+
provenanceID,
|
|
1652
|
+
simul,
|
|
1653
|
+
instantiateTmpl,
|
|
1654
|
+
instantiatedSite,
|
|
1655
|
+
isUnMerging,
|
|
1656
|
+
False,
|
|
1657
|
+
xmlConfigJob,
|
|
1658
|
+
siteDsMap,
|
|
1659
|
+
middleName,
|
|
1660
|
+
registerDatasets,
|
|
1661
|
+
None,
|
|
1662
|
+
fileIDPool,
|
|
1663
|
+
n_files_per_chunk,
|
|
1664
|
+
master_dataset_id=inputChunk.masterIndexName,
|
|
1665
|
+
)
|
|
1666
|
+
if outSubChunk is None:
|
|
1667
|
+
# failed
|
|
1668
|
+
tmpLog.error("failed to get OutputFiles")
|
|
1669
|
+
return failedRet
|
|
1670
|
+
# number of outputs per job
|
|
1671
|
+
if not simul:
|
|
1672
|
+
try:
|
|
1673
|
+
fileIDPool = fileIDPool[num_outputs_per_job * n_files_per_chunk :]
|
|
1674
|
+
except Exception:
|
|
1675
|
+
fileIDPool = []
|
|
1676
|
+
# update parallel output mapping
|
|
1677
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1678
|
+
tmpLog.debug(stop_watch.get_elapsed_time("parallel output"))
|
|
1679
|
+
for tmpParFileID, tmpParFileList in tmpParOutMap.items():
|
|
1680
|
+
if tmpParFileID not in parallelOutMap:
|
|
1681
|
+
parallelOutMap[tmpParFileID] = []
|
|
1682
|
+
parallelOutMap[tmpParFileID] += tmpParFileList
|
|
1683
|
+
for tmpToRegisterItem in tmpToRegister:
|
|
1684
|
+
if tmpToRegisterItem not in datasetToRegister:
|
|
1685
|
+
datasetToRegister.append(tmpToRegisterItem)
|
|
1686
|
+
destinationDBlock = None
|
|
1687
|
+
destination_data_block_type = None
|
|
1688
|
+
for tmpFileSpec in outSubChunk.values():
|
|
1689
|
+
# get dataset
|
|
1690
|
+
if tmpFileSpec.datasetID not in outDsMap:
|
|
1691
|
+
tmpStat, tmpDataset = self.taskBufferIF.getDatasetWithID_JEDI(taskSpec.jediTaskID, tmpFileSpec.datasetID)
|
|
1692
|
+
# not found
|
|
1693
|
+
if not tmpStat:
|
|
1694
|
+
tmpLog.error(f"failed to get DS with datasetID={tmpFileSpec.datasetID}")
|
|
1695
|
+
return failedRet
|
|
1696
|
+
outDsMap[tmpFileSpec.datasetID] = tmpDataset
|
|
1697
|
+
# convert to job's FileSpec
|
|
1698
|
+
tmpDatasetSpec = outDsMap[tmpFileSpec.datasetID]
|
|
1699
|
+
tmpOutFileSpec = tmpFileSpec.convertToJobFileSpec(tmpDatasetSpec, useEventService=taskSpec.useEventService(siteSpec))
|
|
1700
|
+
# stay output on site
|
|
1701
|
+
if taskSpec.stayOutputOnSite():
|
|
1702
|
+
tmpOutFileSpec.destinationSE = siteName
|
|
1703
|
+
tmpOutFileSpec.destinationDBlockToken = f"dst:{siteSpec.ddm_output[scope_output]}"
|
|
1704
|
+
# distributed dataset
|
|
1705
|
+
tmpDistributedDestination = DataServiceUtils.getDistributedDestination(tmpOutFileSpec.destinationDBlockToken)
|
|
1706
|
+
if tmpDistributedDestination is not None:
|
|
1707
|
+
tmpDddKey = (siteName, tmpDistributedDestination)
|
|
1708
|
+
if tmpDddKey not in dddMap:
|
|
1709
|
+
dddMap[tmpDddKey] = siteSpec.ddm_endpoints_output[scope_output].getAssociatedEndpoint(tmpDistributedDestination)
|
|
1710
|
+
if dddMap[tmpDddKey] is not None:
|
|
1711
|
+
tmpOutFileSpec.destinationSE = siteName
|
|
1712
|
+
tmpOutFileSpec.destinationDBlockToken = f"ddd:{dddMap[tmpDddKey]['ddm_endpoint_name']}"
|
|
1713
|
+
else:
|
|
1714
|
+
tmpOutFileSpec.destinationDBlockToken = "ddd:"
|
|
1715
|
+
jobSpec.addFile(tmpOutFileSpec)
|
|
1716
|
+
# use the first (preferably non-log) dataset as destinationDBlock
|
|
1717
|
+
if destinationDBlock is None or destination_data_block_type == "log":
|
|
1718
|
+
destinationDBlock = tmpDatasetSpec.datasetName
|
|
1719
|
+
destination_data_block_type = tmpDatasetSpec.type
|
|
1720
|
+
if destinationDBlock is not None:
|
|
1721
|
+
jobSpec.destinationDBlock = destinationDBlock
|
|
1722
|
+
# get datasetSpec for parallel jobs
|
|
1723
|
+
for tmpFileSpecList in parallelOutMap.values():
|
|
1724
|
+
for tmpFileSpec in tmpFileSpecList:
|
|
1725
|
+
if tmpFileSpec.datasetID not in outDsMap:
|
|
1726
|
+
tmpStat, tmpDataset = self.taskBufferIF.getDatasetWithID_JEDI(taskSpec.jediTaskID, tmpFileSpec.datasetID)
|
|
1727
|
+
# not found
|
|
1728
|
+
if not tmpStat:
|
|
1729
|
+
tmpLog.error(f"failed to get DS with datasetID={tmpFileSpec.datasetID}")
|
|
1730
|
+
return failedRet
|
|
1731
|
+
outDsMap[tmpFileSpec.datasetID] = tmpDataset
|
|
1732
|
+
# lib.tgz. cloning jobs will set it later in increaseEventServiceConsumers
|
|
1733
|
+
paramList = []
|
|
1734
|
+
if buildFileSpec is not None and not taskSpec.useEventService(siteSpec):
|
|
1735
|
+
tmpBuildFileSpec = copy.copy(buildFileSpec)
|
|
1736
|
+
jobSpec.addFile(tmpBuildFileSpec)
|
|
1737
|
+
paramList.append(("LIB", buildFileSpec.lfn))
|
|
1738
|
+
# placeholders for XML config
|
|
1739
|
+
if xmlConfigJob is not None:
|
|
1740
|
+
paramList.append(("XML_OUTMAP", xmlConfigJob.get_outmap_str(outSubChunk)))
|
|
1741
|
+
paramList.append(("XML_EXESTR", xmlConfigJob.exec_string_enc()))
|
|
1742
|
+
# middle name
|
|
1743
|
+
paramList.append(("MIDDLENAME", middleName))
|
|
1744
|
+
# segment ID
|
|
1745
|
+
if segmentID is not None:
|
|
1746
|
+
paramList.append(("SEGMENT_ID", segmentID))
|
|
1747
|
+
paramList.append(("SEGMENT_NAME", segmentName))
|
|
1748
|
+
# job parameter
|
|
1749
|
+
if taskSpec.useEventService(siteSpec) and not taskSpec.useJobCloning():
|
|
1750
|
+
useEStoMakeJP = True
|
|
1751
|
+
else:
|
|
1752
|
+
useEStoMakeJP = False
|
|
1753
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1754
|
+
tmpLog.debug(stop_watch.get_elapsed_time("making job params"))
|
|
1755
|
+
jobSpec.jobParameters, multiExecStep = self.makeJobParameters(
|
|
1756
|
+
taskSpec,
|
|
1757
|
+
inSubChunk,
|
|
1758
|
+
outSubChunk,
|
|
1759
|
+
serialNr,
|
|
1760
|
+
paramList,
|
|
1761
|
+
jobSpec,
|
|
1762
|
+
simul,
|
|
1763
|
+
taskParamMap,
|
|
1764
|
+
inputChunk.isMerging,
|
|
1765
|
+
jobSpec.Files,
|
|
1766
|
+
useEStoMakeJP,
|
|
1767
|
+
random_seed_list,
|
|
1768
|
+
random_seed_dataset,
|
|
1769
|
+
tmpLog,
|
|
1770
|
+
)
|
|
1771
|
+
if multiExecStep is not None:
|
|
1772
|
+
jobSpec.addMultiStepExec(multiExecStep)
|
|
1773
|
+
elif taskSpec.on_site_merging():
|
|
1774
|
+
# add merge spec for on-site-merging
|
|
1775
|
+
tmp_data = {"esmergeSpec": copy.deepcopy(taskParamMap["esmergeSpec"])}
|
|
1776
|
+
tmp_data["esmergeSpec"]["nEventsPerOutputFile"] = taskParamMap["nEventsPerOutputFile"]
|
|
1777
|
+
if "nEventsPerInputFile" in taskParamMap:
|
|
1778
|
+
tmp_data["nEventsPerInputFile"] = taskParamMap["nEventsPerInputFile"]
|
|
1779
|
+
jobSpec.addMultiStepExec(tmp_data)
|
|
1780
|
+
# set destinationSE for fake co-jumbo
|
|
1781
|
+
if inputChunk.useJumbo in ["fake", "only"]:
|
|
1782
|
+
jobSpec.destinationSE = DataServiceUtils.checkJobDestinationSE(jobSpec)
|
|
1783
|
+
# add
|
|
1784
|
+
tmpJobSpecList.append(jobSpec)
|
|
1785
|
+
oldPandaIDs.append(subOldPandaIDs)
|
|
1786
|
+
# increment index of event service job
|
|
1787
|
+
if (taskSpec.useEventService(siteSpec) or taskSpec.is_fine_grained_process()) and not inputChunk.isMerging:
|
|
1788
|
+
esIndex += 1
|
|
1789
|
+
# lock task
|
|
1790
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1791
|
+
tmpLog.debug(stop_watch.get_elapsed_time("lock task"))
|
|
1792
|
+
if not simul and len(jobSpecList + tmpJobSpecList) % 50 == 0:
|
|
1793
|
+
self.taskBufferIF.lockTask_JEDI(taskSpec.jediTaskID, self.pid)
|
|
1794
|
+
if self.time_profile_level >= TIME_PROFILE_ON:
|
|
1795
|
+
tmpLog.debug(stop_watch.get_elapsed_time(""))
|
|
1796
|
+
# increase event service consumers
|
|
1797
|
+
if taskSpec.useEventService(siteSpec) and not inputChunk.isMerging and inputChunk.useJumbo not in ["fake", "only"]:
|
|
1798
|
+
nConsumers = taskSpec.getNumEventServiceConsumer()
|
|
1799
|
+
if nConsumers is not None:
|
|
1800
|
+
tmpJobSpecList, incOldPandaIDs = self.increaseEventServiceConsumers(
|
|
1801
|
+
tmpJobSpecList,
|
|
1802
|
+
nConsumers,
|
|
1803
|
+
taskSpec.getNumSitesPerJob(),
|
|
1804
|
+
parallelOutMap,
|
|
1805
|
+
outDsMap,
|
|
1806
|
+
oldPandaIDs[len(jobSpecList) :],
|
|
1807
|
+
taskSpec,
|
|
1808
|
+
inputChunk,
|
|
1809
|
+
tmpMasterEventsList,
|
|
1810
|
+
build_file_spec_map,
|
|
1811
|
+
)
|
|
1812
|
+
oldPandaIDs = oldPandaIDs[: len(jobSpecList)] + incOldPandaIDs
|
|
1813
|
+
# add to all list
|
|
1814
|
+
jobSpecList += tmpJobSpecList
|
|
1815
|
+
# sort
|
|
1816
|
+
if taskSpec.useEventService() and taskSpec.getNumSitesPerJob():
|
|
1817
|
+
jobSpecList, oldPandaIDs = self.sortParallelJobsBySite(jobSpecList, oldPandaIDs)
|
|
1818
|
+
# make jumbo jobs
|
|
1819
|
+
if taskSpec.getNumJumboJobs() is not None and inputChunk.useJumbo is not None and taskSpec.status == "running":
|
|
1820
|
+
if inputChunk.useJumbo == "only":
|
|
1821
|
+
jobSpecList = self.makeJumboJobs(jobSpecList, taskSpec, inputChunk, simul, outDsMap, tmpLog)
|
|
1822
|
+
else:
|
|
1823
|
+
jobSpecList += self.makeJumboJobs(jobSpecList, taskSpec, inputChunk, simul, outDsMap, tmpLog)
|
|
1824
|
+
# return
|
|
1825
|
+
return Interaction.SC_SUCCEEDED, jobSpecList, datasetToRegister, oldPandaIDs, parallelOutMap, outDsMap
|
|
1826
|
+
except UnresolvedParam:
|
|
1827
|
+
raise
|
|
1828
|
+
except Exception:
|
|
1829
|
+
tmpLog.error(f"{self.__class__.__name__}.doGenerate() failed with {traceback.format_exc()}")
|
|
1830
|
+
return failedRet
|
|
1831
|
+
|
|
1832
|
+
# generate build jobs
|
|
1833
|
+
def doGenerateBuild(self, taskSpec, cloudName, siteName, siteSpec, taskParamMap, tmpLog, siteCandidate, simul=False):
|
|
1834
|
+
# return for failure
|
|
1835
|
+
failedRet = Interaction.SC_FAILED, None, None, None
|
|
1836
|
+
periodToUselibTgz = 7
|
|
1837
|
+
try:
|
|
1838
|
+
datasetToRegister = []
|
|
1839
|
+
# get sites which share DDM endpoint
|
|
1840
|
+
associated_sites = DataServiceUtils.getSitesShareDDM(
|
|
1841
|
+
self.siteMapper, siteName, taskSpec.prodSourceLabel, JobUtils.translate_tasktype_to_jobtype(taskSpec.taskType), True
|
|
1842
|
+
)
|
|
1843
|
+
# convert to sorted unified names and remove duplicates
|
|
1844
|
+
associated_sites = [self.siteMapper.getSite(s).get_unified_name() for s in associated_sites]
|
|
1845
|
+
associated_sites = sorted(list(set(associated_sites)))
|
|
1846
|
+
if siteSpec.get_unified_name() in associated_sites:
|
|
1847
|
+
associated_sites.remove(siteSpec.get_unified_name())
|
|
1848
|
+
# key for map of buildSpec
|
|
1849
|
+
secondKey = [siteSpec.get_unified_name()] + associated_sites
|
|
1850
|
+
secondKey.sort()
|
|
1851
|
+
buildSpecMapKey = (taskSpec.jediTaskID, tuple(secondKey))
|
|
1852
|
+
tmpStat = True
|
|
1853
|
+
if buildSpecMapKey in self.buildSpecMap:
|
|
1854
|
+
# reuse active lib.tgz
|
|
1855
|
+
if self.buildSpecMap[buildSpecMapKey] in self.active_lib_specs_map:
|
|
1856
|
+
fileSpec, datasetSpec = self.active_lib_specs_map[self.buildSpecMap[buildSpecMapKey]]
|
|
1857
|
+
else:
|
|
1858
|
+
reuseDatasetID, reuseFileID = self.buildSpecMap[buildSpecMapKey]
|
|
1859
|
+
tmpStat, fileSpec, datasetSpec = self.taskBufferIF.getOldBuildFileSpec_JEDI(taskSpec.jediTaskID, reuseDatasetID, reuseFileID)
|
|
1860
|
+
if fileSpec is not None:
|
|
1861
|
+
self.active_lib_specs_map[self.buildSpecMap[buildSpecMapKey]] = (fileSpec, datasetSpec)
|
|
1862
|
+
else:
|
|
1863
|
+
# get finished lib.tgz file
|
|
1864
|
+
if buildSpecMapKey in self.finished_lib_specs_map:
|
|
1865
|
+
fileSpec, datasetSpec = self.finished_lib_specs_map[buildSpecMapKey]
|
|
1866
|
+
else:
|
|
1867
|
+
tmpStat, fileSpec, datasetSpec = self.taskBufferIF.get_previous_build_file_spec(
|
|
1868
|
+
taskSpec.jediTaskID, siteSpec.get_unified_name(), associated_sites
|
|
1869
|
+
)
|
|
1870
|
+
if fileSpec is not None:
|
|
1871
|
+
self.finished_lib_specs_map[buildSpecMapKey] = (fileSpec, datasetSpec)
|
|
1872
|
+
# failed
|
|
1873
|
+
if not tmpStat:
|
|
1874
|
+
tmpLog.error(f"failed to get lib.tgz for jediTaskID={taskSpec.jediTaskID} siteName={siteName}")
|
|
1875
|
+
return failedRet
|
|
1876
|
+
# lib.tgz is already available
|
|
1877
|
+
if fileSpec is not None:
|
|
1878
|
+
if fileSpec.creationDate > naive_utcnow() - datetime.timedelta(days=periodToUselibTgz):
|
|
1879
|
+
pandaFileSpec = fileSpec.convertToJobFileSpec(datasetSpec, setType="input")
|
|
1880
|
+
pandaFileSpec.dispatchDBlock = pandaFileSpec.dataset
|
|
1881
|
+
pandaFileSpec.prodDBlockToken = "local"
|
|
1882
|
+
if fileSpec.status == "finished":
|
|
1883
|
+
pandaFileSpec.status = "ready"
|
|
1884
|
+
else:
|
|
1885
|
+
pandaFileSpec.status = None
|
|
1886
|
+
# make dummy jobSpec
|
|
1887
|
+
jobSpec = JobSpec()
|
|
1888
|
+
jobSpec.addFile(pandaFileSpec)
|
|
1889
|
+
return Interaction.SC_SUCCEEDED, None, pandaFileSpec, datasetToRegister
|
|
1890
|
+
else:
|
|
1891
|
+
# not to use very old lib.tgz
|
|
1892
|
+
fileSpec = None
|
|
1893
|
+
# make job spec for build
|
|
1894
|
+
jobSpec = JobSpec()
|
|
1895
|
+
jobSpec.jobDefinitionID = 0
|
|
1896
|
+
jobSpec.jobExecutionID = 0
|
|
1897
|
+
jobSpec.attemptNr = 1
|
|
1898
|
+
jobSpec.maxAttempt = jobSpec.attemptNr
|
|
1899
|
+
jobSpec.jobName = taskSpec.taskName
|
|
1900
|
+
jobSpec.transformation = taskParamMap["buildSpec"]["transPath"]
|
|
1901
|
+
platforms = siteCandidate.get_overridden_attribute("platforms")
|
|
1902
|
+
if platforms:
|
|
1903
|
+
jobSpec.cmtConfig = platforms
|
|
1904
|
+
else:
|
|
1905
|
+
jobSpec.cmtConfig = taskSpec.get_platforms()
|
|
1906
|
+
if taskSpec.transHome is not None:
|
|
1907
|
+
jobSpec.homepackage = re.sub("-(?P<dig>\d+\.)", "/\g<dig>", taskSpec.transHome)
|
|
1908
|
+
jobSpec.homepackage = re.sub("\r", "", jobSpec.homepackage)
|
|
1909
|
+
jobSpec.prodSourceLabel = taskParamMap["buildSpec"]["prodSourceLabel"]
|
|
1910
|
+
jobSpec.processingType = taskSpec.processingType
|
|
1911
|
+
jobSpec.jediTaskID = taskSpec.jediTaskID
|
|
1912
|
+
jobSpec.jobsetID = taskSpec.reqID
|
|
1913
|
+
jobSpec.reqID = taskSpec.reqID
|
|
1914
|
+
jobSpec.workingGroup = taskSpec.workingGroup
|
|
1915
|
+
jobSpec.countryGroup = taskSpec.countryGroup
|
|
1916
|
+
jobSpec.computingSite = siteSpec.get_unified_name()
|
|
1917
|
+
jobSpec.nucleus = taskSpec.nucleus
|
|
1918
|
+
jobSpec.cloud = cloudName
|
|
1919
|
+
jobSpec.VO = taskSpec.vo
|
|
1920
|
+
jobSpec.prodSeriesLabel = "pandatest"
|
|
1921
|
+
jobSpec.AtlasRelease = taskSpec.transUses
|
|
1922
|
+
jobSpec.AtlasRelease = re.sub("\r", "", jobSpec.AtlasRelease)
|
|
1923
|
+
jobSpec.lockedby = "jedi"
|
|
1924
|
+
jobSpec.workQueue_ID = taskSpec.workQueue_ID
|
|
1925
|
+
jobSpec.gshare = taskSpec.gshare
|
|
1926
|
+
jobSpec.container_name = taskSpec.container_name
|
|
1927
|
+
jobSpec.metadata = ""
|
|
1928
|
+
|
|
1929
|
+
if taskSpec.coreCount == 1 or siteSpec.coreCount in [None, 0] or siteSpec.sitename != siteSpec.get_unified_name():
|
|
1930
|
+
jobSpec.coreCount = 1
|
|
1931
|
+
else:
|
|
1932
|
+
jobSpec.coreCount = siteSpec.coreCount
|
|
1933
|
+
|
|
1934
|
+
# for the memory, we are going to use 2GB as a default value, as this was the usual ATLAS slot memory and it has always fit the build jobs.
|
|
1935
|
+
# There is no other major reason and it could be over-dimensioned.
|
|
1936
|
+
if siteSpec.maxrss:
|
|
1937
|
+
jobSpec.minRamCount = min(2000 * jobSpec.coreCount, siteSpec.maxrss)
|
|
1938
|
+
else:
|
|
1939
|
+
jobSpec.minRamCount = 2000 * jobSpec.coreCount
|
|
1940
|
+
|
|
1941
|
+
try:
|
|
1942
|
+
jobSpec.resource_type = JobUtils.get_resource_type_job(self.resource_types, jobSpec)
|
|
1943
|
+
except Exception:
|
|
1944
|
+
jobSpec.resource_type = "Undefined"
|
|
1945
|
+
tmpLog.error(f"set resource_type excepted with {traceback.format_exc()}")
|
|
1946
|
+
# calculate the hs06 occupied by the job
|
|
1947
|
+
if siteSpec.corepower:
|
|
1948
|
+
jobSpec.hs06 = (jobSpec.coreCount or 1) * siteSpec.corepower # default 0 and None corecount to 1
|
|
1949
|
+
# set CPU count
|
|
1950
|
+
buildJobMaxWalltime = self.taskBufferIF.getConfigValue("jobgen", "BUILD_JOB_MAX_WALLTIME", "jedi", taskSpec.vo)
|
|
1951
|
+
if buildJobMaxWalltime is None:
|
|
1952
|
+
jobSpec.maxCpuCount = 0
|
|
1953
|
+
jobSpec.maxWalltime = siteSpec.maxtime
|
|
1954
|
+
else:
|
|
1955
|
+
jobSpec.maxCpuCount = buildJobMaxWalltime * 60 * 60
|
|
1956
|
+
jobSpec.maxWalltime = jobSpec.maxCpuCount
|
|
1957
|
+
if taskSpec.baseWalltime is not None:
|
|
1958
|
+
jobSpec.maxWalltime += taskSpec.baseWalltime
|
|
1959
|
+
jobSpec.maxCpuUnit = taskSpec.walltimeUnit
|
|
1960
|
+
# make libDS name
|
|
1961
|
+
if (
|
|
1962
|
+
datasetSpec is None
|
|
1963
|
+
or fileSpec is None
|
|
1964
|
+
or datasetSpec.state == "closed"
|
|
1965
|
+
or datasetSpec.creationTime < naive_utcnow() - datetime.timedelta(days=periodToUselibTgz)
|
|
1966
|
+
):
|
|
1967
|
+
# make new libDS
|
|
1968
|
+
reusedDatasetID = None
|
|
1969
|
+
libDsName = f"panda.{time.strftime('%m%d%H%M%S', time.gmtime())}.{naive_utcnow().microsecond}.lib._{jobSpec.jediTaskID:06d}"
|
|
1970
|
+
else:
|
|
1971
|
+
# reuse existing DS
|
|
1972
|
+
reusedDatasetID = datasetSpec.datasetID
|
|
1973
|
+
libDsName = datasetSpec.datasetName
|
|
1974
|
+
jobSpec.destinationDBlock = libDsName
|
|
1975
|
+
jobSpec.destinationSE = siteName
|
|
1976
|
+
# special handling
|
|
1977
|
+
specialHandling = ""
|
|
1978
|
+
# DDM backend
|
|
1979
|
+
tmpDdmBackEnd = taskSpec.getDdmBackEnd()
|
|
1980
|
+
if tmpDdmBackEnd is not None:
|
|
1981
|
+
if specialHandling == "":
|
|
1982
|
+
specialHandling = f"ddm:{tmpDdmBackEnd},"
|
|
1983
|
+
else:
|
|
1984
|
+
specialHandling += f"ddm:{tmpDdmBackEnd},"
|
|
1985
|
+
specialHandling = specialHandling[:-1]
|
|
1986
|
+
if specialHandling != "":
|
|
1987
|
+
jobSpec.specialHandling = specialHandling
|
|
1988
|
+
jobSpec.set_task_queued_time(taskSpec.get_queued_time())
|
|
1989
|
+
libDsFileNameBase = libDsName + ".$JEDIFILEID"
|
|
1990
|
+
# make lib.tgz
|
|
1991
|
+
libTgzName = libDsFileNameBase + ".lib.tgz"
|
|
1992
|
+
lib_file_spec = FileSpec()
|
|
1993
|
+
lib_file_spec.lfn = libTgzName
|
|
1994
|
+
lib_file_spec.type = "output"
|
|
1995
|
+
lib_file_spec.attemptNr = 0
|
|
1996
|
+
lib_file_spec.jediTaskID = taskSpec.jediTaskID
|
|
1997
|
+
lib_file_spec.dataset = jobSpec.destinationDBlock
|
|
1998
|
+
lib_file_spec.destinationDBlock = jobSpec.destinationDBlock
|
|
1999
|
+
lib_file_spec.destinationSE = jobSpec.destinationSE
|
|
2000
|
+
jobSpec.addFile(lib_file_spec)
|
|
2001
|
+
# make log
|
|
2002
|
+
logFileSpec = FileSpec()
|
|
2003
|
+
logFileSpec.lfn = libDsFileNameBase + ".log.tgz"
|
|
2004
|
+
logFileSpec.type = "log"
|
|
2005
|
+
logFileSpec.attemptNr = 0
|
|
2006
|
+
logFileSpec.jediTaskID = taskSpec.jediTaskID
|
|
2007
|
+
logFileSpec.dataset = jobSpec.destinationDBlock
|
|
2008
|
+
logFileSpec.destinationDBlock = jobSpec.destinationDBlock
|
|
2009
|
+
logFileSpec.destinationSE = jobSpec.destinationSE
|
|
2010
|
+
jobSpec.addFile(logFileSpec)
|
|
2011
|
+
# insert lib.tgz file
|
|
2012
|
+
tmpStat, fileIdMap = self.taskBufferIF.insertBuildFileSpec_JEDI(jobSpec, reusedDatasetID, simul)
|
|
2013
|
+
# failed
|
|
2014
|
+
if not tmpStat:
|
|
2015
|
+
tmpLog.error("failed to insert libDS for jediTaskID={0} siteName={0}".format(taskSpec.jediTaskID, siteName))
|
|
2016
|
+
return failedRet
|
|
2017
|
+
# set attributes
|
|
2018
|
+
for tmpFile in jobSpec.Files:
|
|
2019
|
+
tmpFile.fileID = fileIdMap[tmpFile.lfn]["fileID"]
|
|
2020
|
+
tmpFile.datasetID = fileIdMap[tmpFile.lfn]["datasetID"]
|
|
2021
|
+
tmpFile.scope = fileIdMap[tmpFile.lfn]["scope"]
|
|
2022
|
+
# set new LFN where place holder is replaced
|
|
2023
|
+
tmpFile.lfn = fileIdMap[tmpFile.lfn]["newLFN"]
|
|
2024
|
+
if tmpFile.datasetID not in datasetToRegister:
|
|
2025
|
+
datasetToRegister.append(tmpFile.datasetID)
|
|
2026
|
+
# append to map of buildSpec
|
|
2027
|
+
self.buildSpecMap[buildSpecMapKey] = (fileIdMap[libTgzName]["datasetID"], fileIdMap[libTgzName]["fileID"])
|
|
2028
|
+
# parameter map
|
|
2029
|
+
paramMap = {}
|
|
2030
|
+
paramMap["OUT"] = lib_file_spec.lfn
|
|
2031
|
+
paramMap["IN"] = taskParamMap["buildSpec"]["archiveName"]
|
|
2032
|
+
paramMap["SURL"] = taskParamMap["sourceURL"]
|
|
2033
|
+
# job parameter
|
|
2034
|
+
jobSpec.jobParameters = self.makeBuildJobParameters(taskParamMap["buildSpec"]["jobParameters"], paramMap)
|
|
2035
|
+
# tarball is downloaded by pilot
|
|
2036
|
+
tarball_via_pilot = taskParamMap["buildSpec"].get("tarBallViaDDM")
|
|
2037
|
+
if tarball_via_pilot:
|
|
2038
|
+
tmp_file_spec = FileSpec()
|
|
2039
|
+
tmp_file_spec.lfn = tarball_via_pilot.split(":")[-1]
|
|
2040
|
+
tmp_file_spec.type = "input"
|
|
2041
|
+
tmp_file_spec.attemptNr = 0
|
|
2042
|
+
tmp_file_spec.jediTaskID = taskSpec.jediTaskID
|
|
2043
|
+
tmp_file_spec.dataset = tarball_via_pilot.split(":")[0]
|
|
2044
|
+
tmp_file_spec.dispatchDBlock = tmp_file_spec.dataset
|
|
2045
|
+
tmp_file_spec.prodDBlockToken = "local"
|
|
2046
|
+
tmp_file_spec.status = "ready"
|
|
2047
|
+
jobSpec.addFile(tmp_file_spec)
|
|
2048
|
+
# make file spec which will be used by runJobs
|
|
2049
|
+
runFileSpec = copy.copy(lib_file_spec)
|
|
2050
|
+
runFileSpec.dispatchDBlock = lib_file_spec.dataset
|
|
2051
|
+
runFileSpec.destinationDBlock = None
|
|
2052
|
+
runFileSpec.type = "input"
|
|
2053
|
+
runFileSpec.prodDBlockToken = "local"
|
|
2054
|
+
# return
|
|
2055
|
+
return Interaction.SC_SUCCEEDED, jobSpec, runFileSpec, datasetToRegister
|
|
2056
|
+
except Exception:
|
|
2057
|
+
tmpLog.error(f"{self.__class__.__name__}.doGenerateBuild() failed with {traceback.format_exc()}")
|
|
2058
|
+
return failedRet
|
|
2059
|
+
|
|
2060
|
+
# generate preprocessing jobs
|
|
2061
|
+
def doGeneratePrePro(self, taskSpec, cloudName, siteName, siteSpec, taskParamMap, inSubChunks, tmpLog, simul=False):
|
|
2062
|
+
# return for failure
|
|
2063
|
+
failedRet = Interaction.SC_FAILED, None, None
|
|
2064
|
+
try:
|
|
2065
|
+
# make job spec for preprocessing
|
|
2066
|
+
jobSpec = JobSpec()
|
|
2067
|
+
jobSpec.jobDefinitionID = 0
|
|
2068
|
+
jobSpec.jobExecutionID = 0
|
|
2069
|
+
jobSpec.attemptNr = 1
|
|
2070
|
+
jobSpec.maxAttempt = jobSpec.attemptNr
|
|
2071
|
+
jobSpec.jobName = taskSpec.taskName
|
|
2072
|
+
jobSpec.transformation = taskParamMap["preproSpec"]["transPath"]
|
|
2073
|
+
jobSpec.prodSourceLabel = taskParamMap["preproSpec"]["prodSourceLabel"]
|
|
2074
|
+
jobSpec.processingType = taskSpec.processingType
|
|
2075
|
+
jobSpec.jediTaskID = taskSpec.jediTaskID
|
|
2076
|
+
jobSpec.jobsetID = taskSpec.reqID
|
|
2077
|
+
jobSpec.reqID = taskSpec.reqID
|
|
2078
|
+
jobSpec.workingGroup = taskSpec.workingGroup
|
|
2079
|
+
jobSpec.countryGroup = taskSpec.countryGroup
|
|
2080
|
+
jobSpec.computingSite = siteSpec.get_unified_name()
|
|
2081
|
+
jobSpec.nucleus = taskSpec.nucleus
|
|
2082
|
+
jobSpec.cloud = cloudName
|
|
2083
|
+
jobSpec.VO = taskSpec.vo
|
|
2084
|
+
jobSpec.prodSeriesLabel = "pandatest"
|
|
2085
|
+
"""
|
|
2086
|
+
jobSpec.AtlasRelease = taskSpec.transUses
|
|
2087
|
+
jobSpec.AtlasRelease = re.sub('\r','',jobSpec.AtlasRelease)
|
|
2088
|
+
"""
|
|
2089
|
+
jobSpec.lockedby = "jedi"
|
|
2090
|
+
jobSpec.workQueue_ID = taskSpec.workQueue_ID
|
|
2091
|
+
jobSpec.gshare = taskSpec.gshare
|
|
2092
|
+
jobSpec.destinationSE = siteName
|
|
2093
|
+
jobSpec.metadata = ""
|
|
2094
|
+
jobSpec.coreCount = 1
|
|
2095
|
+
if siteSpec.corepower:
|
|
2096
|
+
jobSpec.hs06 = (jobSpec.coreCount or 1) * siteSpec.corepower
|
|
2097
|
+
# get log file
|
|
2098
|
+
outSubChunk, serialNr, datasetToRegister, siteDsMap, parallelOutMap = self.taskBufferIF.getOutputFiles_JEDI(
|
|
2099
|
+
taskSpec.jediTaskID, None, simul, True, siteName, False, True
|
|
2100
|
+
)
|
|
2101
|
+
if outSubChunk is None:
|
|
2102
|
+
# failed
|
|
2103
|
+
tmpLog.error("doGeneratePrePro failed to get OutputFiles")
|
|
2104
|
+
return failedRet
|
|
2105
|
+
outDsMap = {}
|
|
2106
|
+
for tmpFileSpec in outSubChunk.values():
|
|
2107
|
+
# get dataset
|
|
2108
|
+
if tmpFileSpec.datasetID not in outDsMap:
|
|
2109
|
+
tmpStat, tmpDataset = self.taskBufferIF.getDatasetWithID_JEDI(taskSpec.jediTaskID, tmpFileSpec.datasetID)
|
|
2110
|
+
# not found
|
|
2111
|
+
if not tmpStat:
|
|
2112
|
+
tmpLog.error(f"doGeneratePrePro failed to get logDS with datasetID={tmpFileSpec.datasetID}")
|
|
2113
|
+
return failedRet
|
|
2114
|
+
outDsMap[tmpFileSpec.datasetID] = tmpDataset
|
|
2115
|
+
# convert to job's FileSpec
|
|
2116
|
+
tmpDatasetSpec = outDsMap[tmpFileSpec.datasetID]
|
|
2117
|
+
tmpOutFileSpec = tmpFileSpec.convertToJobFileSpec(tmpDatasetSpec, setType="log")
|
|
2118
|
+
jobSpec.addFile(tmpOutFileSpec)
|
|
2119
|
+
jobSpec.destinationDBlock = tmpDatasetSpec.datasetName
|
|
2120
|
+
# make pseudo input
|
|
2121
|
+
tmpDatasetSpec, tmpFileSpecList = inSubChunks[0][0]
|
|
2122
|
+
tmpFileSpec = tmpFileSpecList[0]
|
|
2123
|
+
tmpInFileSpec = tmpFileSpec.convertToJobFileSpec(tmpDatasetSpec, setType="pseudo_input")
|
|
2124
|
+
jobSpec.addFile(tmpInFileSpec)
|
|
2125
|
+
# parameter map
|
|
2126
|
+
paramMap = {}
|
|
2127
|
+
paramMap["SURL"] = taskParamMap["sourceURL"]
|
|
2128
|
+
# job parameter
|
|
2129
|
+
jobSpec.jobParameters = self.makeBuildJobParameters(taskParamMap["preproSpec"]["jobParameters"], paramMap)
|
|
2130
|
+
# return
|
|
2131
|
+
return Interaction.SC_SUCCEEDED, jobSpec, datasetToRegister
|
|
2132
|
+
except Exception:
|
|
2133
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
2134
|
+
tmpLog.error(f"{self.__class__.__name__}.doGeneratePrePro() failed with {traceback.format_exc()}")
|
|
2135
|
+
return failedRet
|
|
2136
|
+
|
|
2137
|
+
# make job parameters
|
|
2138
|
+
def makeJobParameters(
|
|
2139
|
+
self,
|
|
2140
|
+
taskSpec,
|
|
2141
|
+
inSubChunk,
|
|
2142
|
+
outSubChunk,
|
|
2143
|
+
serialNr,
|
|
2144
|
+
paramList,
|
|
2145
|
+
jobSpec,
|
|
2146
|
+
simul,
|
|
2147
|
+
taskParamMap,
|
|
2148
|
+
isMerging,
|
|
2149
|
+
jobFileList,
|
|
2150
|
+
useEventService,
|
|
2151
|
+
random_seed_list,
|
|
2152
|
+
random_seed_dataset,
|
|
2153
|
+
tmp_log,
|
|
2154
|
+
):
|
|
2155
|
+
stop_watch = CoreUtils.StopWatch("make_params")
|
|
2156
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2157
|
+
tmp_log.debug(stop_watch.get_elapsed_time("init"))
|
|
2158
|
+
if not isMerging:
|
|
2159
|
+
parTemplate = taskSpec.jobParamsTemplate
|
|
2160
|
+
else:
|
|
2161
|
+
# use params template for merging
|
|
2162
|
+
parTemplate = taskParamMap["mergeSpec"]["jobParameters"]
|
|
2163
|
+
# make the list of stream/LFNs
|
|
2164
|
+
streamLFNsMap = {}
|
|
2165
|
+
# mapping between stream and dataset
|
|
2166
|
+
streamDsMap = {}
|
|
2167
|
+
# parameters for placeholders
|
|
2168
|
+
skipEvents = None
|
|
2169
|
+
maxEvents = 0
|
|
2170
|
+
firstEvent = None
|
|
2171
|
+
rndmSeed = None
|
|
2172
|
+
sourceURL = None
|
|
2173
|
+
# source URL
|
|
2174
|
+
if taskParamMap is not None and "sourceURL" in taskParamMap:
|
|
2175
|
+
sourceURL = taskParamMap["sourceURL"]
|
|
2176
|
+
# get random seed
|
|
2177
|
+
if taskSpec.useRandomSeed() and not isMerging:
|
|
2178
|
+
tmpRandomFileSpec = None
|
|
2179
|
+
if random_seed_list:
|
|
2180
|
+
tmpRandomFileSpec = random_seed_list.pop(0)
|
|
2181
|
+
tmpRandomDatasetSpec = random_seed_dataset
|
|
2182
|
+
else:
|
|
2183
|
+
tmpStat, randomSpecList = self.taskBufferIF.getRandomSeed_JEDI(taskSpec.jediTaskID, simul, 1)
|
|
2184
|
+
if tmpStat is True:
|
|
2185
|
+
tmp_file_spec_list, tmpRandomDatasetSpec = randomSpecList
|
|
2186
|
+
if tmp_file_spec_list:
|
|
2187
|
+
tmpRandomFileSpec = tmp_file_spec_list[0]
|
|
2188
|
+
if tmpRandomFileSpec is not None:
|
|
2189
|
+
tmpJobFileSpec = tmpRandomFileSpec.convertToJobFileSpec(tmpRandomDatasetSpec, setType="pseudo_input")
|
|
2190
|
+
rndmSeed = tmpRandomFileSpec.firstEvent + taskSpec.getRndmSeedOffset()
|
|
2191
|
+
jobSpec.addFile(tmpJobFileSpec)
|
|
2192
|
+
# input
|
|
2193
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2194
|
+
tmp_log.debug(stop_watch.get_elapsed_time("input"))
|
|
2195
|
+
for tmpDatasetSpec, tmpFileSpecList in inSubChunk:
|
|
2196
|
+
# stream name
|
|
2197
|
+
streamName = tmpDatasetSpec.streamName
|
|
2198
|
+
# LFNs
|
|
2199
|
+
tmpLFNs = []
|
|
2200
|
+
size_map = {}
|
|
2201
|
+
for tmpFileSpec in tmpFileSpecList:
|
|
2202
|
+
tmpLFNs.append(tmpFileSpec.lfn)
|
|
2203
|
+
size_map[tmpFileSpec.lfn] = tmpFileSpec.fsize
|
|
2204
|
+
# tweak file list if necessary
|
|
2205
|
+
if isMerging:
|
|
2206
|
+
# sort by descending size not to process empty files first
|
|
2207
|
+
tmpLFNs.sort(key=lambda kkk: size_map[kkk], reverse=True)
|
|
2208
|
+
elif taskSpec.dynamicNumEvents():
|
|
2209
|
+
# remove duplication for dynamic number of events while preserving the file order
|
|
2210
|
+
tmpLFNs = list(dict.fromkeys(tmpLFNs))
|
|
2211
|
+
elif not taskSpec.order_input_by():
|
|
2212
|
+
# remove duplication and sort by LFN unless the order is specified
|
|
2213
|
+
tmpLFNs = list(dict.fromkeys(tmpLFNs))
|
|
2214
|
+
tmpLFNs.sort()
|
|
2215
|
+
# change stream name and LFNs for PFN list
|
|
2216
|
+
if taskSpec.useListPFN() and tmpDatasetSpec.isMaster() and tmpDatasetSpec.isPseudo():
|
|
2217
|
+
streamName = "IN"
|
|
2218
|
+
tmpPFNs = []
|
|
2219
|
+
for tmpLFN in tmpLFNs:
|
|
2220
|
+
# remove serial number and num events
|
|
2221
|
+
tmpPFN = taskParamMap["pfnList"][int(tmpLFN.split(":")[0])].split("^")[0]
|
|
2222
|
+
tmpPFNs.append(tmpPFN)
|
|
2223
|
+
tmpLFNs = tmpPFNs
|
|
2224
|
+
# add to map
|
|
2225
|
+
streamLFNsMap[streamName] = tmpLFNs
|
|
2226
|
+
# collect dataset or container name to be used as tmp file name
|
|
2227
|
+
if tmpDatasetSpec.containerName not in [None, ""]:
|
|
2228
|
+
streamDsMap[streamName] = tmpDatasetSpec.containerName
|
|
2229
|
+
else:
|
|
2230
|
+
streamDsMap[streamName] = tmpDatasetSpec.datasetName
|
|
2231
|
+
streamDsMap[streamName] = re.sub("/$", "", streamDsMap[streamName])
|
|
2232
|
+
streamDsMap[streamName] = streamDsMap[streamName].split(":")[-1]
|
|
2233
|
+
# collect parameters for event-level split
|
|
2234
|
+
if tmpDatasetSpec.isMaster():
|
|
2235
|
+
# skipEvents and firstEvent
|
|
2236
|
+
for tmpFileSpec in tmpFileSpecList:
|
|
2237
|
+
if firstEvent is None and tmpFileSpec.firstEvent is not None:
|
|
2238
|
+
firstEvent = tmpFileSpec.firstEvent
|
|
2239
|
+
if skipEvents is None and tmpFileSpec.startEvent is not None:
|
|
2240
|
+
skipEvents = tmpFileSpec.startEvent
|
|
2241
|
+
if tmpFileSpec.startEvent is not None and tmpFileSpec.endEvent is not None:
|
|
2242
|
+
maxEvents += tmpFileSpec.endEvent - tmpFileSpec.startEvent + 1
|
|
2243
|
+
# set zero if undefined
|
|
2244
|
+
if skipEvents is None:
|
|
2245
|
+
skipEvents = 0
|
|
2246
|
+
# set -1 if maxEvents is zero since it breaks some apps
|
|
2247
|
+
if maxEvents == 0:
|
|
2248
|
+
maxEvents = -1
|
|
2249
|
+
# output
|
|
2250
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2251
|
+
tmp_log.debug(stop_watch.get_elapsed_time("output"))
|
|
2252
|
+
for streamName, tmpFileSpec in outSubChunk.items():
|
|
2253
|
+
streamName = streamName.split("|")[0]
|
|
2254
|
+
streamLFNsMap.setdefault(streamName, [])
|
|
2255
|
+
streamLFNsMap[streamName].append(tmpFileSpec.lfn)
|
|
2256
|
+
# extract placeholders with range expression, e.g., IN[0:2]
|
|
2257
|
+
for tmpMatch in re.finditer("\$\{([^\}]+)\}", parTemplate):
|
|
2258
|
+
tmpPatt = tmpMatch.group(1)
|
|
2259
|
+
# split to stream name and range expression
|
|
2260
|
+
tmpStRaMatch = re.search("([^\[]+)(.*)", tmpPatt)
|
|
2261
|
+
if tmpStRaMatch is not None:
|
|
2262
|
+
tmpStream = tmpStRaMatch.group(1)
|
|
2263
|
+
tmpRange = tmpStRaMatch.group(2)
|
|
2264
|
+
if tmpPatt != tmpStream and tmpStream in streamLFNsMap:
|
|
2265
|
+
try:
|
|
2266
|
+
exec(f"streamLFNsMap['{tmpPatt}']=streamLFNsMap['{tmpStream}']{tmpRange}", globals())
|
|
2267
|
+
except Exception:
|
|
2268
|
+
pass
|
|
2269
|
+
# loop over all streams to collect transient and final steams
|
|
2270
|
+
transientStreamCombo = {}
|
|
2271
|
+
streamToDelete = {}
|
|
2272
|
+
if isMerging:
|
|
2273
|
+
for streamName in streamLFNsMap.keys():
|
|
2274
|
+
# collect transient and final steams
|
|
2275
|
+
if streamName is not None and not streamName.startswith("TRN_"):
|
|
2276
|
+
counterStreamName = "TRN_" + streamName
|
|
2277
|
+
if streamName == "LOG_MERGE" and "TRN_LOG0" in streamLFNsMap:
|
|
2278
|
+
transientStreamCombo[streamName] = {
|
|
2279
|
+
"out": streamName,
|
|
2280
|
+
"in": "TRN_LOG0",
|
|
2281
|
+
}
|
|
2282
|
+
elif counterStreamName not in streamLFNsMap:
|
|
2283
|
+
# streams to be deleted
|
|
2284
|
+
streamToDelete[streamName] = streamLFNsMap[streamName]
|
|
2285
|
+
streamToDelete[counterStreamName] = []
|
|
2286
|
+
else:
|
|
2287
|
+
transientStreamCombo[streamName] = {
|
|
2288
|
+
"out": streamName,
|
|
2289
|
+
"in": counterStreamName,
|
|
2290
|
+
}
|
|
2291
|
+
# delete empty streams
|
|
2292
|
+
for streamName in streamToDelete.keys():
|
|
2293
|
+
try:
|
|
2294
|
+
del streamLFNsMap[streamName]
|
|
2295
|
+
except Exception:
|
|
2296
|
+
pass
|
|
2297
|
+
# loop over all placeholders
|
|
2298
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2299
|
+
tmp_log.debug(stop_watch.get_elapsed_time("placeholders"))
|
|
2300
|
+
for tmpMatch in re.finditer("\$\{([^\}]+)\}", parTemplate):
|
|
2301
|
+
placeHolder = tmpMatch.group(1)
|
|
2302
|
+
# remove decorators
|
|
2303
|
+
streamNames = placeHolder.split("/")[0]
|
|
2304
|
+
streamNameList = streamNames.split(",")
|
|
2305
|
+
listLFN = []
|
|
2306
|
+
for streamName in streamNameList:
|
|
2307
|
+
if streamName in streamLFNsMap:
|
|
2308
|
+
listLFN += streamLFNsMap[streamName]
|
|
2309
|
+
if listLFN != []:
|
|
2310
|
+
decorators = re.sub("^" + streamNames, "", placeHolder)
|
|
2311
|
+
# long format
|
|
2312
|
+
if "/L" in decorators:
|
|
2313
|
+
longLFNs = ""
|
|
2314
|
+
for tmpLFN in listLFN:
|
|
2315
|
+
if "/A" in decorators:
|
|
2316
|
+
longLFNs += "'"
|
|
2317
|
+
longLFNs += tmpLFN
|
|
2318
|
+
if "/A" in decorators:
|
|
2319
|
+
longLFNs += "'"
|
|
2320
|
+
if "/S" in decorators:
|
|
2321
|
+
# use white-space as separator
|
|
2322
|
+
longLFNs += " "
|
|
2323
|
+
else:
|
|
2324
|
+
longLFNs += ","
|
|
2325
|
+
if taskSpec.usingJumboJobs():
|
|
2326
|
+
parTemplate = parTemplate.replace("${" + placeHolder + "}", "tmpin__cnt_" + streamDsMap[streamName])
|
|
2327
|
+
else:
|
|
2328
|
+
longLFNs = longLFNs[:-1]
|
|
2329
|
+
parTemplate = parTemplate.replace("${" + placeHolder + "}", longLFNs)
|
|
2330
|
+
continue
|
|
2331
|
+
# list to string
|
|
2332
|
+
if "/T" in decorators:
|
|
2333
|
+
parTemplate = parTemplate.replace("${" + placeHolder + "}", str(listLFN))
|
|
2334
|
+
continue
|
|
2335
|
+
# write to file
|
|
2336
|
+
if "/F" in decorators:
|
|
2337
|
+
parTemplate = parTemplate.replace("${" + placeHolder + "}", "tmpin_" + streamDsMap[streamName])
|
|
2338
|
+
# single file
|
|
2339
|
+
if len(listLFN) == 1:
|
|
2340
|
+
# just replace with the original file name
|
|
2341
|
+
replaceStr = listLFN[0]
|
|
2342
|
+
parTemplate = parTemplate.replace("${" + streamNames + "}", replaceStr)
|
|
2343
|
+
# encoded
|
|
2344
|
+
encStreamName = streamNames + "/E"
|
|
2345
|
+
replaceStr = unquote(replaceStr)
|
|
2346
|
+
parTemplate = parTemplate.replace("${" + encStreamName + "}", replaceStr)
|
|
2347
|
+
# mathematics
|
|
2348
|
+
if "/M" in decorators:
|
|
2349
|
+
tmp_m = re.search(r"/M\[([^\]]+)\]", decorators)
|
|
2350
|
+
if tmp_m:
|
|
2351
|
+
tmp_formula = tmp_m.group(1).replace("#", listLFN[0])
|
|
2352
|
+
replaceStr = str(eval(tmp_formula))
|
|
2353
|
+
parTemplate = parTemplate.replace("${" + placeHolder + "}", replaceStr)
|
|
2354
|
+
else:
|
|
2355
|
+
# compact format
|
|
2356
|
+
compactLFNs = []
|
|
2357
|
+
# remove attempt numbers
|
|
2358
|
+
fullLFNList = ""
|
|
2359
|
+
for tmpLFN in listLFN:
|
|
2360
|
+
# keep full LFNs
|
|
2361
|
+
fullLFNList += f"{tmpLFN},"
|
|
2362
|
+
compactLFNs.append(re.sub("\.\d+$", "", tmpLFN))
|
|
2363
|
+
fullLFNList = fullLFNList[:-1]
|
|
2364
|
+
# find head and tail to convert file.1.pool,file.2.pool,file.4.pool to file.[1,2,4].pool
|
|
2365
|
+
tmpHead = ""
|
|
2366
|
+
tmpTail = ""
|
|
2367
|
+
tmpLFN0 = compactLFNs[0]
|
|
2368
|
+
tmpLFN1 = compactLFNs[1]
|
|
2369
|
+
i = 0
|
|
2370
|
+
for s1, s2 in zip(tmpLFN0, tmpLFN1):
|
|
2371
|
+
if s1 != s2:
|
|
2372
|
+
break
|
|
2373
|
+
i += 1
|
|
2374
|
+
tmpHead = tmpLFN0[:i]
|
|
2375
|
+
i = 0
|
|
2376
|
+
for s1, s2 in zip(tmpLFN0[::-1], tmpLFN1[::-1]):
|
|
2377
|
+
if s1 != s2:
|
|
2378
|
+
break
|
|
2379
|
+
i += 1
|
|
2380
|
+
tmpTail = tmpLFN0[-i:]
|
|
2381
|
+
# remove numbers : ABC_00,00_XYZ -> ABC_,_XYZ
|
|
2382
|
+
tmpHead = re.sub("\d*$", "", tmpHead)
|
|
2383
|
+
tmpTail = re.sub("^\d*", "", tmpTail)
|
|
2384
|
+
# create compact paramter
|
|
2385
|
+
compactPar = f"{tmpHead}["
|
|
2386
|
+
for tmpLFN in compactLFNs:
|
|
2387
|
+
# extract number
|
|
2388
|
+
tmpLFN = re.sub(f"^{tmpHead}", "", tmpLFN)
|
|
2389
|
+
tmpLFN = re.sub(f"{tmpTail}$", "", tmpLFN)
|
|
2390
|
+
compactPar += f"{tmpLFN},"
|
|
2391
|
+
compactPar = compactPar[:-1]
|
|
2392
|
+
compactPar += f"]{tmpTail}"
|
|
2393
|
+
# check contents in []
|
|
2394
|
+
conMatch = re.search("\[([^\]]+)\]", compactPar)
|
|
2395
|
+
if conMatch is not None and re.search("^[\d,]+$", conMatch.group(1)) is not None:
|
|
2396
|
+
# replace with compact format
|
|
2397
|
+
replaceStr = compactPar
|
|
2398
|
+
else:
|
|
2399
|
+
# replace with full format since [] contains non digits
|
|
2400
|
+
replaceStr = fullLFNList
|
|
2401
|
+
parTemplate = parTemplate.replace("${" + streamNames + "}", replaceStr)
|
|
2402
|
+
# encoded
|
|
2403
|
+
encStreamName = streamNames + "/E"
|
|
2404
|
+
replaceStr = unquote(fullLFNList)
|
|
2405
|
+
parTemplate = parTemplate.replace("${" + encStreamName + "}", replaceStr)
|
|
2406
|
+
# replace params related to transient files
|
|
2407
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2408
|
+
tmp_log.debug(stop_watch.get_elapsed_time("transient files"))
|
|
2409
|
+
replaceStrMap = {}
|
|
2410
|
+
emptyStreamMap = {}
|
|
2411
|
+
for streamName, transientStreamMap in transientStreamCombo.items():
|
|
2412
|
+
# remove serial number
|
|
2413
|
+
streamNameBase = re.sub("\d+$", "", streamName)
|
|
2414
|
+
# empty streams
|
|
2415
|
+
if streamNameBase not in emptyStreamMap:
|
|
2416
|
+
emptyStreamMap[streamNameBase] = []
|
|
2417
|
+
# make param
|
|
2418
|
+
replaceStr = ""
|
|
2419
|
+
if streamLFNsMap[transientStreamMap["in"]] == []:
|
|
2420
|
+
emptyStreamMap[streamNameBase].append(streamName)
|
|
2421
|
+
else:
|
|
2422
|
+
for tmpLFN in streamLFNsMap[transientStreamMap["in"]]:
|
|
2423
|
+
replaceStr += f"{tmpLFN},"
|
|
2424
|
+
replaceStr = replaceStr[:-1]
|
|
2425
|
+
replaceStr += ":"
|
|
2426
|
+
for tmpLFN in streamLFNsMap[transientStreamMap["out"]]:
|
|
2427
|
+
replaceStr += f"{tmpLFN},"
|
|
2428
|
+
replaceStr = replaceStr[:-1]
|
|
2429
|
+
# concatenate per base stream name
|
|
2430
|
+
if streamNameBase not in replaceStrMap:
|
|
2431
|
+
replaceStrMap[streamNameBase] = ""
|
|
2432
|
+
replaceStrMap[streamNameBase] += f"{replaceStr} "
|
|
2433
|
+
for streamNameBase, replaceStr in replaceStrMap.items():
|
|
2434
|
+
targetName = "${TRN_" + streamNameBase + ":" + streamNameBase + "}"
|
|
2435
|
+
if targetName in parTemplate:
|
|
2436
|
+
parTemplate = parTemplate.replace(targetName, replaceStr)
|
|
2437
|
+
# remove outputs with empty input files
|
|
2438
|
+
for emptyStream in emptyStreamMap[streamNameBase]:
|
|
2439
|
+
tmpFileIdx = 0
|
|
2440
|
+
for tmpJobFileSpec in jobFileList:
|
|
2441
|
+
if tmpJobFileSpec.lfn in streamLFNsMap[emptyStream]:
|
|
2442
|
+
jobFileList.pop(tmpFileIdx)
|
|
2443
|
+
break
|
|
2444
|
+
tmpFileIdx += 1
|
|
2445
|
+
# remove outputs and params for deleted streams
|
|
2446
|
+
for streamName, deletedLFNs in streamToDelete.items():
|
|
2447
|
+
# remove params
|
|
2448
|
+
parTemplate = re.sub("--[^=]+=\$\{" + streamName + "\}", "", parTemplate)
|
|
2449
|
+
# remove output files
|
|
2450
|
+
if deletedLFNs == []:
|
|
2451
|
+
continue
|
|
2452
|
+
tmpFileIdx = 0
|
|
2453
|
+
for tmpJobFileSpec in jobFileList:
|
|
2454
|
+
if tmpJobFileSpec.lfn in deletedLFNs:
|
|
2455
|
+
jobFileList.pop(tmpFileIdx)
|
|
2456
|
+
break
|
|
2457
|
+
tmpFileIdx += 1
|
|
2458
|
+
# replace placeholders for numbers
|
|
2459
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2460
|
+
tmp_log.debug(stop_watch.get_elapsed_time("numbers"))
|
|
2461
|
+
if serialNr is None:
|
|
2462
|
+
serialNr = 0
|
|
2463
|
+
for streamName, parVal in [
|
|
2464
|
+
("SN", serialNr),
|
|
2465
|
+
("SN/P", f"{serialNr:06d}"),
|
|
2466
|
+
("RNDMSEED", rndmSeed),
|
|
2467
|
+
("MAXEVENTS", maxEvents),
|
|
2468
|
+
("SKIPEVENTS", skipEvents),
|
|
2469
|
+
("FIRSTEVENT", firstEvent),
|
|
2470
|
+
("SURL", sourceURL),
|
|
2471
|
+
("ATTEMPTNR", jobSpec.attemptNr),
|
|
2472
|
+
] + paramList:
|
|
2473
|
+
# ignore undefined
|
|
2474
|
+
if parVal is None:
|
|
2475
|
+
continue
|
|
2476
|
+
# replace
|
|
2477
|
+
parTemplate = parTemplate.replace("${" + streamName + "}", str(parVal))
|
|
2478
|
+
# replace unmerge files
|
|
2479
|
+
for jobFileSpec in jobFileList:
|
|
2480
|
+
if jobFileSpec.isUnMergedOutput():
|
|
2481
|
+
mergedFileName = re.sub("^panda\.um\.", "", jobFileSpec.lfn)
|
|
2482
|
+
parTemplate = parTemplate.replace(mergedFileName, jobFileSpec.lfn)
|
|
2483
|
+
# remove duplicated panda.um
|
|
2484
|
+
parTemplate = parTemplate.replace("panda.um.panda.um.", "panda.um.")
|
|
2485
|
+
# remove ES parameters if necessary
|
|
2486
|
+
if useEventService:
|
|
2487
|
+
parTemplate = parTemplate.replace("<PANDA_ES_ONLY>", "")
|
|
2488
|
+
parTemplate = parTemplate.replace("</PANDA_ES_ONLY>", "")
|
|
2489
|
+
else:
|
|
2490
|
+
parTemplate = re.sub("<PANDA_ES_ONLY>[^<]*</PANDA_ES_ONLY>", "", parTemplate)
|
|
2491
|
+
parTemplate = re.sub("<PANDA_ESMERGE.*>[^<]*</PANDA_ESMERGE.*>", "", parTemplate)
|
|
2492
|
+
# multi-step execution
|
|
2493
|
+
if not taskSpec.is_multi_step_exec():
|
|
2494
|
+
multiExecSpec = None
|
|
2495
|
+
else:
|
|
2496
|
+
multiExecSpec = copy.deepcopy(taskParamMap["multiStepExec"])
|
|
2497
|
+
for k, v in multiExecSpec.items():
|
|
2498
|
+
for kk, vv in v.items():
|
|
2499
|
+
# resolve placeholders
|
|
2500
|
+
new_vv = vv.replace("${TRF_ARGS}", parTemplate)
|
|
2501
|
+
new_vv = new_vv.replace("${TRF}", jobSpec.transformation)
|
|
2502
|
+
v[kk] = new_vv
|
|
2503
|
+
# check unresolved placeholders
|
|
2504
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2505
|
+
tmp_log.debug(stop_watch.get_elapsed_time("unresolved placeholders"))
|
|
2506
|
+
matchI = re.search("\${IN.*}", parTemplate)
|
|
2507
|
+
if matchI is None:
|
|
2508
|
+
matchI = re.search("\${SEQNUMBER}", parTemplate)
|
|
2509
|
+
if matchI is not None:
|
|
2510
|
+
raise UnresolvedParam(f"unresolved {matchI.group(0)} when making job parameters")
|
|
2511
|
+
# return
|
|
2512
|
+
if self.time_profile_level >= TIME_PROFILE_DEEP:
|
|
2513
|
+
tmp_log.debug(stop_watch.get_elapsed_time(""))
|
|
2514
|
+
return parTemplate, multiExecSpec
|
|
2515
|
+
|
|
2516
|
+
# make build/prepro job parameters
|
|
2517
|
+
def makeBuildJobParameters(self, jobParameters, paramMap):
|
|
2518
|
+
parTemplate = jobParameters
|
|
2519
|
+
# replace placeholders
|
|
2520
|
+
for streamName, parVal in paramMap.items():
|
|
2521
|
+
# ignore undefined
|
|
2522
|
+
if parVal is None:
|
|
2523
|
+
continue
|
|
2524
|
+
# replace
|
|
2525
|
+
parTemplate = parTemplate.replace("${" + streamName + "}", str(parVal))
|
|
2526
|
+
# return
|
|
2527
|
+
return parTemplate
|
|
2528
|
+
|
|
2529
|
+
# increase event service consumers
|
|
2530
|
+
def increaseEventServiceConsumers(
|
|
2531
|
+
self, pandaJobs, nConsumers, nSitesPerJob, parallelOutMap, outDsMap, oldPandaIDs, taskSpec, inputChunk, masterEventsList, build_spec_map
|
|
2532
|
+
):
|
|
2533
|
+
newPandaJobs = []
|
|
2534
|
+
newOldPandaIDs = []
|
|
2535
|
+
for pandaJob, oldPandaID, masterEvents in zip(pandaJobs, oldPandaIDs, masterEventsList):
|
|
2536
|
+
for iConsumers in range(nConsumers):
|
|
2537
|
+
newPandaJob = self.clonePandaJob(
|
|
2538
|
+
pandaJob,
|
|
2539
|
+
iConsumers,
|
|
2540
|
+
parallelOutMap,
|
|
2541
|
+
outDsMap,
|
|
2542
|
+
taskSpec=taskSpec,
|
|
2543
|
+
inputChunk=inputChunk,
|
|
2544
|
+
totalMasterEvents=masterEvents,
|
|
2545
|
+
build_spec_map=build_spec_map,
|
|
2546
|
+
)
|
|
2547
|
+
newPandaJobs.append(newPandaJob)
|
|
2548
|
+
newOldPandaIDs.append(oldPandaID)
|
|
2549
|
+
# return
|
|
2550
|
+
return newPandaJobs, newOldPandaIDs
|
|
2551
|
+
|
|
2552
|
+
# close panda job with new specialHandling
|
|
2553
|
+
def clonePandaJob(
|
|
2554
|
+
self, pandaJob, index, parallelOutMap, outDsMap, sites=None, forJumbo=False, taskSpec=None, inputChunk=None, totalMasterEvents=None, build_spec_map=None
|
|
2555
|
+
):
|
|
2556
|
+
newPandaJob = copy.copy(pandaJob)
|
|
2557
|
+
if sites is None:
|
|
2558
|
+
sites = newPandaJob.computingSite.split(",")
|
|
2559
|
+
nSites = len(sites)
|
|
2560
|
+
# set site for parallel jobs
|
|
2561
|
+
newPandaJob.computingSite = sites[index % nSites]
|
|
2562
|
+
siteSpec = self.siteMapper.getSite(newPandaJob.computingSite)
|
|
2563
|
+
scope_input, scope_output = select_scope(siteSpec, pandaJob.prodSourceLabel, pandaJob.job_label)
|
|
2564
|
+
siteCandidate = inputChunk.getSiteCandidate(newPandaJob.computingSite)
|
|
2565
|
+
newPandaJob.computingSite = siteSpec.get_unified_name()
|
|
2566
|
+
if taskSpec.coreCount == 1 or siteSpec.coreCount in [None, 0]:
|
|
2567
|
+
newPandaJob.coreCount = 1
|
|
2568
|
+
else:
|
|
2569
|
+
newPandaJob.coreCount = siteSpec.coreCount
|
|
2570
|
+
if taskSpec is not None and inputChunk is not None:
|
|
2571
|
+
newPandaJob.minRamCount, newPandaJob.minRamUnit = JobUtils.getJobMinRamCount(taskSpec, inputChunk, siteSpec, newPandaJob.coreCount)
|
|
2572
|
+
newPandaJob.hs06 = (newPandaJob.coreCount or 1) * siteSpec.corepower
|
|
2573
|
+
if totalMasterEvents is not None:
|
|
2574
|
+
CoreUtils.getJobMaxWalltime(taskSpec, inputChunk, totalMasterEvents, newPandaJob, siteSpec)
|
|
2575
|
+
try:
|
|
2576
|
+
newPandaJob.resource_type = JobUtils.get_resource_type_job(self.resource_types, newPandaJob)
|
|
2577
|
+
except Exception:
|
|
2578
|
+
newPandaJob.resource_type = "Undefined"
|
|
2579
|
+
platforms = siteCandidate.get_overridden_attribute("platforms")
|
|
2580
|
+
if platforms:
|
|
2581
|
+
newPandaJob.cmtConfig = platforms
|
|
2582
|
+
datasetList = set()
|
|
2583
|
+
# reset SH for jumbo
|
|
2584
|
+
if forJumbo:
|
|
2585
|
+
EventServiceUtils.removeHeaderForES(newPandaJob)
|
|
2586
|
+
# clone files
|
|
2587
|
+
newPandaJob.Files = []
|
|
2588
|
+
for fileSpec in pandaJob.Files:
|
|
2589
|
+
# copy files
|
|
2590
|
+
if (
|
|
2591
|
+
forJumbo
|
|
2592
|
+
or nSites == 1
|
|
2593
|
+
or fileSpec.type not in ["log", "output"]
|
|
2594
|
+
or (fileSpec.fileID in parallelOutMap and len(parallelOutMap[fileSpec.fileID]) == 1)
|
|
2595
|
+
):
|
|
2596
|
+
newFileSpec = copy.copy(fileSpec)
|
|
2597
|
+
else:
|
|
2598
|
+
newFileSpec = parallelOutMap[fileSpec.fileID][index % nSites]
|
|
2599
|
+
datasetSpec = outDsMap[newFileSpec.datasetID]
|
|
2600
|
+
newFileSpec = newFileSpec.convertToJobFileSpec(datasetSpec, useEventService=True)
|
|
2601
|
+
# set locality
|
|
2602
|
+
if inputChunk is not None and newFileSpec.type == "input":
|
|
2603
|
+
if siteCandidate is not None:
|
|
2604
|
+
locality = siteCandidate.getFileLocality(newFileSpec)
|
|
2605
|
+
if locality in ["localdisk", "remote"]:
|
|
2606
|
+
newFileSpec.status = "ready"
|
|
2607
|
+
elif locality == "cache":
|
|
2608
|
+
newFileSpec.status = "cached"
|
|
2609
|
+
else:
|
|
2610
|
+
newFileSpec.status = None
|
|
2611
|
+
# use log/output files and only one input file per dataset for jumbo jobs
|
|
2612
|
+
if forJumbo:
|
|
2613
|
+
# reset fileID to avoid updating JEDI tables
|
|
2614
|
+
newFileSpec.fileID = None
|
|
2615
|
+
if newFileSpec.type in ["log", "output"]:
|
|
2616
|
+
pass
|
|
2617
|
+
elif newFileSpec.type == "input" and newFileSpec.datasetID not in datasetList:
|
|
2618
|
+
datasetList.add(newFileSpec.datasetID)
|
|
2619
|
+
# reset LFN etc since jumbo job runs on any file
|
|
2620
|
+
newFileSpec.lfn = "any"
|
|
2621
|
+
newFileSpec.GUID = None
|
|
2622
|
+
# reset status to trigger input data transfer
|
|
2623
|
+
newFileSpec.status = None
|
|
2624
|
+
else:
|
|
2625
|
+
continue
|
|
2626
|
+
# append PandaID as suffix for log files to avoid LFN duplication
|
|
2627
|
+
if newFileSpec.type == "log":
|
|
2628
|
+
newFileSpec.lfn += ".$PANDAID"
|
|
2629
|
+
if (nSites > 1 or forJumbo) and fileSpec.type in ["log", "output"]:
|
|
2630
|
+
# distributed dataset
|
|
2631
|
+
datasetSpec = outDsMap[newFileSpec.datasetID]
|
|
2632
|
+
tmpDistributedDestination = DataServiceUtils.getDistributedDestination(datasetSpec.storageToken)
|
|
2633
|
+
if tmpDistributedDestination is not None:
|
|
2634
|
+
tmpDestination = siteSpec.ddm_endpoints_output[scope_output].getAssociatedEndpoint(tmpDistributedDestination)
|
|
2635
|
+
# change destination
|
|
2636
|
+
newFileSpec.destinationSE = newPandaJob.computingSite
|
|
2637
|
+
if tmpDestination is not None:
|
|
2638
|
+
newFileSpec.destinationDBlockToken = f"ddd:{tmpDestination['ddm_endpoint_name']}"
|
|
2639
|
+
newPandaJob.addFile(newFileSpec)
|
|
2640
|
+
if build_spec_map and newPandaJob.computingSite in build_spec_map:
|
|
2641
|
+
tmp_build_file = copy.copy(build_spec_map[newPandaJob.computingSite])
|
|
2642
|
+
newPandaJob.addFile(tmp_build_file)
|
|
2643
|
+
newPandaJob.jobParameters = newPandaJob.jobParameters.replace("${LIB}", tmp_build_file.lfn)
|
|
2644
|
+
return newPandaJob
|
|
2645
|
+
|
|
2646
|
+
# make jumbo jobs
|
|
2647
|
+
def makeJumboJobs(self, pandaJobs, taskSpec, inputChunk, simul, outDsMap, tmpLog):
|
|
2648
|
+
jumboJobs = []
|
|
2649
|
+
# no original
|
|
2650
|
+
if len(pandaJobs) == 0:
|
|
2651
|
+
return jumboJobs
|
|
2652
|
+
# get active jumbo jobs
|
|
2653
|
+
if not simul:
|
|
2654
|
+
activeJumboJobs = self.taskBufferIF.getActiveJumboJobs_JEDI(taskSpec.jediTaskID)
|
|
2655
|
+
else:
|
|
2656
|
+
activeJumboJobs = {}
|
|
2657
|
+
# enough jobs
|
|
2658
|
+
numNewJumboJobs = taskSpec.getNumJumboJobs() - len(activeJumboJobs)
|
|
2659
|
+
if numNewJumboJobs <= 0:
|
|
2660
|
+
return jumboJobs
|
|
2661
|
+
# sites which already have jumbo jobs
|
|
2662
|
+
sitesWithJumbo = dict()
|
|
2663
|
+
for tmpPandaID, activeJumboJob in activeJumboJobs.items():
|
|
2664
|
+
sitesWithJumbo.setdefault(activeJumboJob["site"], [])
|
|
2665
|
+
if activeJumboJob["status"] not in ["transferring", "holding"]:
|
|
2666
|
+
sitesWithJumbo[activeJumboJob["site"]].append(tmpPandaID)
|
|
2667
|
+
# sites with enough jumbo
|
|
2668
|
+
maxJumboPerSite = taskSpec.getMaxJumboPerSite()
|
|
2669
|
+
ngSites = []
|
|
2670
|
+
for tmpSite, tmpPandaIDs in sitesWithJumbo.items():
|
|
2671
|
+
if len(tmpPandaIDs) >= maxJumboPerSite:
|
|
2672
|
+
ngSites.append(tmpSite)
|
|
2673
|
+
# get sites
|
|
2674
|
+
newSites = []
|
|
2675
|
+
for i in range(numNewJumboJobs):
|
|
2676
|
+
siteCandidate = inputChunk.getOneSiteCandidateForJumbo(ngSites)
|
|
2677
|
+
if siteCandidate is None:
|
|
2678
|
+
break
|
|
2679
|
+
newSites.append(siteCandidate.siteName)
|
|
2680
|
+
# check if enough
|
|
2681
|
+
sitesWithJumbo.setdefault(siteCandidate.siteName, [])
|
|
2682
|
+
sitesWithJumbo[siteCandidate.siteName].append(None)
|
|
2683
|
+
if len(sitesWithJumbo[siteCandidate.siteName]) >= maxJumboPerSite:
|
|
2684
|
+
ngSites.append(siteCandidate.siteName)
|
|
2685
|
+
nJumbo = len(newSites)
|
|
2686
|
+
newSites.sort()
|
|
2687
|
+
# get job parameter of the first job
|
|
2688
|
+
if nJumbo > 0:
|
|
2689
|
+
jobParams, outFileMap = self.taskBufferIF.getJobParamsOfFirstJob_JEDI(taskSpec.jediTaskID)
|
|
2690
|
+
if jobParams is None:
|
|
2691
|
+
tmpLog.error("cannot get first job for jumbo")
|
|
2692
|
+
return jumboJobs
|
|
2693
|
+
# make jumbo jobs
|
|
2694
|
+
for iJumbo in range(nJumbo):
|
|
2695
|
+
newJumboJob = self.clonePandaJob(pandaJobs[0], iJumbo, {}, outDsMap, newSites, True, taskSpec=taskSpec, inputChunk=inputChunk)
|
|
2696
|
+
newJumboJob.eventService = EventServiceUtils.jumboJobFlagNumber
|
|
2697
|
+
# job params inherit from the first job since first_event etc must be the first value
|
|
2698
|
+
if jobParams != "":
|
|
2699
|
+
newJumboJob.jobParameters = jobParams
|
|
2700
|
+
# change output file name
|
|
2701
|
+
for outDatasetID, outLFN in outFileMap.items():
|
|
2702
|
+
for fileSpec in newJumboJob.Files:
|
|
2703
|
+
if fileSpec.type == "output" and fileSpec.datasetID == outDatasetID:
|
|
2704
|
+
newJumboJob.jobParameters = newJumboJob.jobParameters.replace(outLFN, fileSpec.lfn)
|
|
2705
|
+
break
|
|
2706
|
+
jumboJobs.append(newJumboJob)
|
|
2707
|
+
# return
|
|
2708
|
+
return jumboJobs
|
|
2709
|
+
|
|
2710
|
+
# sort parallel jobs by site
|
|
2711
|
+
def sortParallelJobsBySite(self, pandaJobs, oldPandaIDs):
|
|
2712
|
+
tmpMap = {}
|
|
2713
|
+
oldMap = {}
|
|
2714
|
+
for pandaJob, oldPandaID in zip(pandaJobs, oldPandaIDs):
|
|
2715
|
+
if pandaJob.computingSite not in tmpMap:
|
|
2716
|
+
tmpMap[pandaJob.computingSite] = []
|
|
2717
|
+
if pandaJob.computingSite not in oldMap:
|
|
2718
|
+
oldMap[pandaJob.computingSite] = []
|
|
2719
|
+
tmpMap[pandaJob.computingSite].append(pandaJob)
|
|
2720
|
+
oldMap[pandaJob.computingSite].append(oldPandaID)
|
|
2721
|
+
newPandaJobs = []
|
|
2722
|
+
newOldPandaIds = []
|
|
2723
|
+
for computingSite in tmpMap.keys():
|
|
2724
|
+
newPandaJobs += tmpMap[computingSite]
|
|
2725
|
+
newOldPandaIds += oldMap[computingSite]
|
|
2726
|
+
# return
|
|
2727
|
+
return newPandaJobs, newOldPandaIds
|
|
2728
|
+
|
|
2729
|
+
# get the largest attempt number
|
|
2730
|
+
def getLargestAttemptNr(self, inSubChunk):
|
|
2731
|
+
largestAttemptNr = 0
|
|
2732
|
+
for tmpDatasetSpec, tmpFileSpecList in inSubChunk:
|
|
2733
|
+
if tmpDatasetSpec.isMaster():
|
|
2734
|
+
for tmpFileSpec in tmpFileSpecList:
|
|
2735
|
+
if tmpFileSpec.attemptNr is not None and tmpFileSpec.attemptNr > largestAttemptNr:
|
|
2736
|
+
largestAttemptNr = tmpFileSpec.attemptNr
|
|
2737
|
+
return largestAttemptNr + 1
|
|
2738
|
+
|
|
2739
|
+
# touch sandbox files
|
|
2740
|
+
def touchSandoboxFiles(self, task_spec, task_param_map, tmp_log):
|
|
2741
|
+
# get task parameter map
|
|
2742
|
+
tmpStat, taskParamMap = self.readTaskParams(task_spec, task_param_map, tmp_log)
|
|
2743
|
+
if not tmpStat:
|
|
2744
|
+
return Interaction.SC_FAILED, "failed to get task parameter dict", taskParamMap
|
|
2745
|
+
# look for sandbox
|
|
2746
|
+
sandboxName = RefinerUtils.get_sandbox_name(taskParamMap)
|
|
2747
|
+
# tarball is downloaded by pilot
|
|
2748
|
+
tarball_via_pilot = "tarBallViaDDM" in taskParamMap or ("buildSpec" in taskParamMap and "tarBallViaDDM" in taskParamMap["buildSpec"])
|
|
2749
|
+
if sandboxName is not None and not tarball_via_pilot:
|
|
2750
|
+
tmpRes = self.taskBufferIF.extendSandboxLifetime_JEDI(task_spec.jediTaskID, sandboxName)
|
|
2751
|
+
tmp_log.debug(f"extend lifetime for {sandboxName} with {tmpRes}")
|
|
2752
|
+
if not tmpRes:
|
|
2753
|
+
errMsg = "user sandbox file unavailable. resubmit the task with --useNewCode"
|
|
2754
|
+
return Interaction.SC_FAILED, errMsg, taskParamMap
|
|
2755
|
+
return Interaction.SC_SUCCEEDED, None, taskParamMap
|
|
2756
|
+
|
|
2757
|
+
|
|
2758
|
+
# launch
|
|
2759
|
+
|
|
2760
|
+
|
|
2761
|
+
def launcher(commuChannel, taskBufferIF, ddmIF, vos, prodSourceLabels, cloudList, withThrottle=True, execJobs=True, loopCycle_cust=None, test_mode=False):
|
|
2762
|
+
p = JobGenerator(commuChannel, taskBufferIF, ddmIF, vos, prodSourceLabels, cloudList, withThrottle, execJobs, loopCycle_cust, test_mode)
|
|
2763
|
+
p.start()
|