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,954 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General Adder plugin. Add data to dataset
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import traceback
|
|
12
|
+
|
|
13
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
14
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
15
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
16
|
+
|
|
17
|
+
import pandaserver.dataservice.ErrorCode
|
|
18
|
+
import pandaserver.taskbuffer.ErrorCode
|
|
19
|
+
from pandaserver.config import panda_config
|
|
20
|
+
from pandaserver.dataservice import closer
|
|
21
|
+
from pandaserver.srvcore.CoreUtils import normalize_cpu_model
|
|
22
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils, retryModule
|
|
23
|
+
|
|
24
|
+
_logger = PandaLogger().getLogger("adder")
|
|
25
|
+
|
|
26
|
+
panda_config.setupPlugin()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AdderGen:
|
|
30
|
+
"""
|
|
31
|
+
General Adder plugin.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# constructor
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
taskBuffer,
|
|
38
|
+
job_id,
|
|
39
|
+
job_status,
|
|
40
|
+
attempt_nr,
|
|
41
|
+
ignore_tmp_error=True,
|
|
42
|
+
siteMapper=None,
|
|
43
|
+
pid=None,
|
|
44
|
+
prelock_pid=None,
|
|
45
|
+
lock_offset=10,
|
|
46
|
+
lock_pool=None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Initialize the AdderGen.
|
|
50
|
+
|
|
51
|
+
:param job: The job object.
|
|
52
|
+
:param params: Additional parameters.
|
|
53
|
+
"""
|
|
54
|
+
self.job = None
|
|
55
|
+
self.job_id = job_id
|
|
56
|
+
self.job_status = job_status
|
|
57
|
+
self.taskBuffer = taskBuffer
|
|
58
|
+
self.ignore_tmp_error = ignore_tmp_error
|
|
59
|
+
self.lock_offset = lock_offset
|
|
60
|
+
self.siteMapper = siteMapper
|
|
61
|
+
self.dataset_map = {}
|
|
62
|
+
self.extra_info = {
|
|
63
|
+
"surl": {},
|
|
64
|
+
"nevents": {},
|
|
65
|
+
"lbnr": {},
|
|
66
|
+
"endpoint": {},
|
|
67
|
+
"guid": {},
|
|
68
|
+
}
|
|
69
|
+
self.attempt_nr = attempt_nr
|
|
70
|
+
self.pid = pid
|
|
71
|
+
self.prelock_pid = prelock_pid
|
|
72
|
+
self.data = None
|
|
73
|
+
self.lock_pool = lock_pool
|
|
74
|
+
self.report_dict = None
|
|
75
|
+
self.adder_plugin = None
|
|
76
|
+
self.add_result = None
|
|
77
|
+
self.adder_plugin_class = None
|
|
78
|
+
# logger
|
|
79
|
+
self.logger = LogWrapper(_logger, str(self.job_id))
|
|
80
|
+
|
|
81
|
+
# main
|
|
82
|
+
def run(self):
|
|
83
|
+
"""
|
|
84
|
+
Run the AdderGen plugin.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
start_time = naive_utcnow()
|
|
88
|
+
self.logger.debug(f"new start: {self.job_status} attemptNr={self.attempt_nr}")
|
|
89
|
+
|
|
90
|
+
# got lock, get the report
|
|
91
|
+
self.get_report()
|
|
92
|
+
|
|
93
|
+
# query job
|
|
94
|
+
self.job = self.taskBuffer.peekJobs([self.job_id], fromDefined=False, fromWaiting=False, fromArchived=False, forAnal=True)[0]
|
|
95
|
+
|
|
96
|
+
# execute plugin to process job report and update the job
|
|
97
|
+
processed = self.process_job_report()
|
|
98
|
+
self.logger.debug(f"job report was successfully processed and can be deleted now: {processed}")
|
|
99
|
+
|
|
100
|
+
duration = naive_utcnow() - start_time
|
|
101
|
+
self.logger.debug("end: took %s.%03d sec in total" % (duration.seconds, duration.microseconds / 1000))
|
|
102
|
+
|
|
103
|
+
# remove Catalog
|
|
104
|
+
if processed:
|
|
105
|
+
self.taskBuffer.deleteJobOutputReport(panda_id=self.job_id, attempt_nr=self.attempt_nr)
|
|
106
|
+
|
|
107
|
+
del self.data
|
|
108
|
+
del self.report_dict
|
|
109
|
+
except Exception as e:
|
|
110
|
+
err_str = f"{str(e)} {traceback.format_exc()}"
|
|
111
|
+
self.logger.error(err_str)
|
|
112
|
+
duration = naive_utcnow() - start_time
|
|
113
|
+
self.logger.error("except: took %s.%03d sec in total" % (duration.seconds, duration.microseconds / 1000))
|
|
114
|
+
# unlock job output report
|
|
115
|
+
self.taskBuffer.unlockJobOutputReport(
|
|
116
|
+
panda_id=self.job_id,
|
|
117
|
+
attempt_nr=self.attempt_nr,
|
|
118
|
+
pid=self.pid,
|
|
119
|
+
lock_offset=self.lock_offset,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# dump file report
|
|
123
|
+
def dump_file_report(self, file_catalog, attempt_nr):
|
|
124
|
+
"""
|
|
125
|
+
Dump the file report.
|
|
126
|
+
|
|
127
|
+
:param file_catalog: The file catalog.
|
|
128
|
+
:param attempt_nr: The attempt number.
|
|
129
|
+
"""
|
|
130
|
+
self.logger.debug("dump file report")
|
|
131
|
+
|
|
132
|
+
attempt_nr = 0 if attempt_nr is None else attempt_nr
|
|
133
|
+
if self.job is None:
|
|
134
|
+
self.job = self.taskBuffer.peekJobs([self.job_id], fromDefined=False, fromWaiting=False, forAnal=True)[0]
|
|
135
|
+
if self.job:
|
|
136
|
+
self.taskBuffer.insertJobOutputReport(
|
|
137
|
+
panda_id=self.job_id,
|
|
138
|
+
prod_source_label=self.job.prodSourceLabel,
|
|
139
|
+
job_status=self.job_status,
|
|
140
|
+
attempt_nr=attempt_nr,
|
|
141
|
+
data=file_catalog,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# get plugin class
|
|
145
|
+
def get_plugin_class(self, tmp_vo, tmp_group):
|
|
146
|
+
"""
|
|
147
|
+
Get the plugin class for the given VO and group.
|
|
148
|
+
|
|
149
|
+
This method attempts to retrieve a specific plugin class based on the provided
|
|
150
|
+
VO (Virtual Organization) and group. If no specific plugin is found, it defaults
|
|
151
|
+
to using the ATLAS plugin.
|
|
152
|
+
|
|
153
|
+
:param tmp_vo: The Virtual Organization identifier.
|
|
154
|
+
:param tmp_group: The group identifier.
|
|
155
|
+
:return: The plugin class.
|
|
156
|
+
"""
|
|
157
|
+
# instantiate concrete plugin
|
|
158
|
+
self.adder_plugin_class = panda_config.getPlugin("adder_plugins", tmp_vo, tmp_group)
|
|
159
|
+
if self.adder_plugin_class is None:
|
|
160
|
+
# use ATLAS plugin by default
|
|
161
|
+
from pandaserver.dataservice.adder_atlas_plugin import AdderAtlasPlugin
|
|
162
|
+
|
|
163
|
+
self.adder_plugin_class = AdderAtlasPlugin
|
|
164
|
+
self.logger.debug(f"plugin name {self.adder_plugin_class.__name__}")
|
|
165
|
+
return self.adder_plugin_class
|
|
166
|
+
|
|
167
|
+
def get_report(self) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Get the job output report.
|
|
170
|
+
"""
|
|
171
|
+
self.report_dict = self.taskBuffer.getJobOutputReport(panda_id=self.job_id, attempt_nr=self.attempt_nr)
|
|
172
|
+
self.data = self.report_dict.get("data")
|
|
173
|
+
|
|
174
|
+
def register_event_service_files(self) -> None:
|
|
175
|
+
"""
|
|
176
|
+
Register Event Service (ES) files.
|
|
177
|
+
"""
|
|
178
|
+
# instantiate concrete plugin
|
|
179
|
+
self.adder_plugin_class = self.get_plugin_class(self.job.VO, self.job.cloud)
|
|
180
|
+
self.adder_plugin = self.adder_plugin_class(
|
|
181
|
+
self.job,
|
|
182
|
+
taskBuffer=self.taskBuffer,
|
|
183
|
+
siteMapper=self.siteMapper,
|
|
184
|
+
logger=self.logger,
|
|
185
|
+
)
|
|
186
|
+
self.logger.debug("plugin is ready for ES file registration")
|
|
187
|
+
self.adder_plugin.register_event_service_files()
|
|
188
|
+
|
|
189
|
+
def check_file_status_in_jedi(self) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Check the file status in JEDI.
|
|
192
|
+
"""
|
|
193
|
+
if not self.job.isCancelled() and self.job.taskBufferErrorCode not in [pandaserver.taskbuffer.ErrorCode.EC_PilotRetried]:
|
|
194
|
+
file_check_in_jedi = self.taskBuffer.checkInputFileStatusInJEDI(self.job)
|
|
195
|
+
self.logger.debug(f"check file status in JEDI : {file_check_in_jedi}")
|
|
196
|
+
if file_check_in_jedi is None:
|
|
197
|
+
raise RuntimeError("failed to check file status in JEDI")
|
|
198
|
+
if file_check_in_jedi is False:
|
|
199
|
+
# set job status to failed since some file status is wrong in JEDI
|
|
200
|
+
self.job_status = "failed"
|
|
201
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
202
|
+
err_str = "inconsistent file status between Panda and JEDI. "
|
|
203
|
+
err_str += "failed to avoid duplicated processing caused by synchronization failure"
|
|
204
|
+
self.job.ddmErrorDiag = err_str
|
|
205
|
+
self.logger.debug(f"set jobStatus={self.job_status} since input is inconsistent between Panda and JEDI")
|
|
206
|
+
elif self.job.jobSubStatus in ["pilot_closed"]:
|
|
207
|
+
# terminated by the pilot
|
|
208
|
+
self.logger.debug("going to closed since terminated by the pilot")
|
|
209
|
+
ret_closed = self.taskBuffer.killJobs([self.job_id], "pilot", "60", True)
|
|
210
|
+
if ret_closed[0] is True:
|
|
211
|
+
self.logger.debug("end")
|
|
212
|
+
# remove Catalog
|
|
213
|
+
self.taskBuffer.deleteJobOutputReport(panda_id=self.job_id, attempt_nr=self.attempt_nr)
|
|
214
|
+
return
|
|
215
|
+
# check for cloned jobs
|
|
216
|
+
if EventServiceUtils.isJobCloningJob(self.job) and self.job_status == "finished":
|
|
217
|
+
# get semaphore for storeonce
|
|
218
|
+
if EventServiceUtils.getJobCloningType(self.job) == "storeonce":
|
|
219
|
+
self.taskBuffer.getEventRanges(self.job.PandaID, self.job.jobsetID, self.job.jediTaskID, 1, False, False, None)
|
|
220
|
+
# check semaphore
|
|
221
|
+
check_jc = self.taskBuffer.checkClonedJob(self.job)
|
|
222
|
+
if check_jc is None:
|
|
223
|
+
raise RuntimeError("failed to check the cloned job")
|
|
224
|
+
# failed to lock semaphore
|
|
225
|
+
if check_jc["lock"] is False:
|
|
226
|
+
self.job_status = "failed"
|
|
227
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
228
|
+
self.job.ddmErrorDiag = "failed to lock semaphore for job cloning"
|
|
229
|
+
self.logger.debug(f"set jobStatus={self.job_status} since did not get semaphore for job cloning")
|
|
230
|
+
|
|
231
|
+
def execute_plugin(self) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Execute the Adder plugin.
|
|
234
|
+
"""
|
|
235
|
+
# interaction with DDM
|
|
236
|
+
try:
|
|
237
|
+
# instantiate concrete plugin
|
|
238
|
+
self.adder_plugin_class = self.get_plugin_class(self.job.VO, self.job.cloud)
|
|
239
|
+
self.adder_plugin = self.adder_plugin_class(
|
|
240
|
+
self.job,
|
|
241
|
+
taskBuffer=self.taskBuffer,
|
|
242
|
+
siteMapper=self.siteMapper,
|
|
243
|
+
extra_info=self.extra_info,
|
|
244
|
+
logger=self.logger,
|
|
245
|
+
)
|
|
246
|
+
self.logger.debug("plugin is ready")
|
|
247
|
+
self.adder_plugin.execute()
|
|
248
|
+
self.add_result = self.adder_plugin.result
|
|
249
|
+
self.logger.debug(f"plugin done with {self.add_result.status_code}")
|
|
250
|
+
except Exception:
|
|
251
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
252
|
+
self.logger.error(f"failed to execute AdderPlugin for VO={self.job.VO} with {err_type}:{err_value}")
|
|
253
|
+
self.logger.error(f"failed to execute AdderPlugin for VO={self.job.VO} with {traceback.format_exc()}")
|
|
254
|
+
self.add_result = None
|
|
255
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
256
|
+
self.job.ddmErrorDiag = "AdderPlugin failure"
|
|
257
|
+
|
|
258
|
+
def finalize_job_status(self) -> None:
|
|
259
|
+
"""
|
|
260
|
+
Finalize the job status.
|
|
261
|
+
"""
|
|
262
|
+
if self.job.jobStatus == "failed" or self.job_status == "failed":
|
|
263
|
+
self.handle_failed_job()
|
|
264
|
+
else:
|
|
265
|
+
# reset errors
|
|
266
|
+
self.job.jobDispatcherErrorCode = 0
|
|
267
|
+
self.job.jobDispatcherErrorDiag = "NULL"
|
|
268
|
+
# set status
|
|
269
|
+
if self.add_result is not None and self.add_result.merging_files != []:
|
|
270
|
+
# set status for merging:
|
|
271
|
+
for file in self.job.Files:
|
|
272
|
+
if file.lfn in self.add_result.merging_files:
|
|
273
|
+
file.status = "merging"
|
|
274
|
+
self.job.jobStatus = "merging"
|
|
275
|
+
# propagate transition to prodDB
|
|
276
|
+
self.job.stateChangeTime = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
|
277
|
+
elif self.add_result is not None and self.add_result.transferring_files != []:
|
|
278
|
+
# set status for transferring
|
|
279
|
+
for file in self.job.Files:
|
|
280
|
+
if file.lfn in self.add_result.transferring_files:
|
|
281
|
+
file.status = "transferring"
|
|
282
|
+
self.job.jobStatus = "transferring"
|
|
283
|
+
self.job.jobSubStatus = None
|
|
284
|
+
# propagate transition to prodDB
|
|
285
|
+
self.job.stateChangeTime = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
|
286
|
+
else:
|
|
287
|
+
self.job.jobStatus = "finished"
|
|
288
|
+
|
|
289
|
+
def handle_failed_job(self) -> None:
|
|
290
|
+
"""
|
|
291
|
+
Handle failed job status.
|
|
292
|
+
"""
|
|
293
|
+
# First of all: check if job failed and in this case take first actions according to error table
|
|
294
|
+
source, error_code, error_diag = None, None, None
|
|
295
|
+
errors = []
|
|
296
|
+
if self.job.pilotErrorCode:
|
|
297
|
+
source = "pilotErrorCode"
|
|
298
|
+
error_code = self.job.pilotErrorCode
|
|
299
|
+
error_diag = self.job.pilotErrorDiag
|
|
300
|
+
errors.append(
|
|
301
|
+
{
|
|
302
|
+
"source": source,
|
|
303
|
+
"error_code": error_code,
|
|
304
|
+
"error_diag": error_diag,
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
if self.job.exeErrorCode:
|
|
308
|
+
source = "exeErrorCode"
|
|
309
|
+
error_code = self.job.exeErrorCode
|
|
310
|
+
error_diag = self.job.exeErrorDiag
|
|
311
|
+
errors.append(
|
|
312
|
+
{
|
|
313
|
+
"source": source,
|
|
314
|
+
"error_code": error_code,
|
|
315
|
+
"error_diag": error_diag,
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
if self.job.ddmErrorCode:
|
|
319
|
+
source = "ddmErrorCode"
|
|
320
|
+
error_code = self.job.ddmErrorCode
|
|
321
|
+
error_diag = self.job.ddmErrorDiag
|
|
322
|
+
errors.append(
|
|
323
|
+
{
|
|
324
|
+
"source": source,
|
|
325
|
+
"error_code": error_code,
|
|
326
|
+
"error_diag": error_diag,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
if self.job.transExitCode:
|
|
330
|
+
source = "transExitCode"
|
|
331
|
+
error_code = self.job.transExitCode
|
|
332
|
+
error_diag = ""
|
|
333
|
+
errors.append(
|
|
334
|
+
{
|
|
335
|
+
"source": source,
|
|
336
|
+
"error_code": error_code,
|
|
337
|
+
"error_diag": error_diag,
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if source and error_code:
|
|
342
|
+
try:
|
|
343
|
+
self.logger.debug("AdderGen.run will call job_failure_postprocessing")
|
|
344
|
+
retryModule.job_failure_postprocessing(
|
|
345
|
+
self.taskBuffer,
|
|
346
|
+
self.job.PandaID,
|
|
347
|
+
errors,
|
|
348
|
+
self.job.attemptNr,
|
|
349
|
+
)
|
|
350
|
+
self.logger.debug("job_failure_postprocessing is back")
|
|
351
|
+
except Exception as e:
|
|
352
|
+
self.logger.error(f"job_failure_postprocessing excepted and needs to be investigated ({e}): {traceback.format_exc()}")
|
|
353
|
+
|
|
354
|
+
self.job.jobStatus = "failed"
|
|
355
|
+
for file in self.job.Files:
|
|
356
|
+
if file.type in ["output", "log"]:
|
|
357
|
+
if self.add_result is not None and file.lfn in self.add_result.merging_files:
|
|
358
|
+
file.status = "merging"
|
|
359
|
+
else:
|
|
360
|
+
file.status = "failed"
|
|
361
|
+
|
|
362
|
+
def process_job_report(self) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Check the job status, execute the plugin if job status is applicable, and update job in the database.
|
|
365
|
+
return: True if successful or unnecessary, False otherwise to retry.
|
|
366
|
+
"""
|
|
367
|
+
if self.job is None:
|
|
368
|
+
self.logger.debug("job not found in DB")
|
|
369
|
+
return True
|
|
370
|
+
if self.job.jobStatus in ["finished", "failed", "unknown", "merging"]:
|
|
371
|
+
self.logger.error(f"invalid state -> {self.job.jobStatus}")
|
|
372
|
+
return True
|
|
373
|
+
if self.attempt_nr is not None and self.job.attemptNr != self.attempt_nr:
|
|
374
|
+
self.logger.error(f"wrong attemptNr -> job={self.job.attemptNr} <> {self.attempt_nr}")
|
|
375
|
+
return True
|
|
376
|
+
if self.job_status == EventServiceUtils.esRegStatus:
|
|
377
|
+
self.register_event_service_files()
|
|
378
|
+
return True
|
|
379
|
+
|
|
380
|
+
# check file status in JEDI
|
|
381
|
+
self.check_file_status_in_jedi()
|
|
382
|
+
|
|
383
|
+
# use failed for cancelled/closed jobs
|
|
384
|
+
if self.job.isCancelled():
|
|
385
|
+
self.job_status = "failed"
|
|
386
|
+
# reset error codes to skip retrial module
|
|
387
|
+
self.job.pilotErrorCode = 0
|
|
388
|
+
self.job.exeErrorCode = 0
|
|
389
|
+
self.job.ddmErrorCode = 0
|
|
390
|
+
|
|
391
|
+
# keep old status
|
|
392
|
+
old_job_status = self.job.jobStatus
|
|
393
|
+
|
|
394
|
+
# set job status
|
|
395
|
+
if self.job.jobStatus not in ["transferring"]:
|
|
396
|
+
self.job.jobStatus = self.job_status
|
|
397
|
+
self.add_result = None
|
|
398
|
+
self.adder_plugin = None
|
|
399
|
+
|
|
400
|
+
# parse Job Output Report JSON
|
|
401
|
+
parse_result = self.parse_job_output_report()
|
|
402
|
+
|
|
403
|
+
# If the parse_result is less than 2, it means that the parsing either succeeded or encountered a harmless error
|
|
404
|
+
if parse_result < 2:
|
|
405
|
+
self.execute_plugin()
|
|
406
|
+
|
|
407
|
+
# ignore temporary errors
|
|
408
|
+
if self.ignore_tmp_error and self.add_result is not None and self.add_result.is_temporary():
|
|
409
|
+
self.logger.debug(f"ignore {self.job.ddmErrorDiag} ")
|
|
410
|
+
self.logger.debug("escape")
|
|
411
|
+
# unlock job output report
|
|
412
|
+
self.taskBuffer.unlockJobOutputReport(
|
|
413
|
+
panda_id=self.job_id,
|
|
414
|
+
attempt_nr=self.attempt_nr,
|
|
415
|
+
pid=self.pid,
|
|
416
|
+
lock_offset=self.lock_offset,
|
|
417
|
+
)
|
|
418
|
+
return False
|
|
419
|
+
# failed
|
|
420
|
+
if self.add_result is None or not self.add_result.is_succeeded():
|
|
421
|
+
self.job.jobStatus = "failed"
|
|
422
|
+
|
|
423
|
+
# set file status for failed jobs or failed transferring jobs
|
|
424
|
+
self.logger.debug(f"status after plugin call :job.jobStatus={self.job.jobStatus} jobStatus={self.job_status}")
|
|
425
|
+
|
|
426
|
+
self.finalize_job_status()
|
|
427
|
+
|
|
428
|
+
# endtime
|
|
429
|
+
if self.job.endTime == "NULL":
|
|
430
|
+
self.job.endTime = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
|
431
|
+
# output size and # of outputs
|
|
432
|
+
self.job.nOutputDataFiles = 0
|
|
433
|
+
self.job.outputFileBytes = 0
|
|
434
|
+
for tmp_file in self.job.Files:
|
|
435
|
+
if tmp_file.type == "output":
|
|
436
|
+
self.job.nOutputDataFiles += 1
|
|
437
|
+
try:
|
|
438
|
+
self.job.outputFileBytes += tmp_file.fsize
|
|
439
|
+
except Exception:
|
|
440
|
+
pass
|
|
441
|
+
# protection
|
|
442
|
+
max_output_file_bytes = 99999999999
|
|
443
|
+
self.job.outputFileBytes = min(self.job.outputFileBytes, max_output_file_bytes)
|
|
444
|
+
# set cancelled state
|
|
445
|
+
if self.job.commandToPilot == "tobekilled" and self.job.jobStatus == "failed":
|
|
446
|
+
self.job.jobStatus = "cancelled"
|
|
447
|
+
# update job
|
|
448
|
+
if old_job_status in ["cancelled", "closed"]:
|
|
449
|
+
pass
|
|
450
|
+
else:
|
|
451
|
+
self.logger.debug("updating DB")
|
|
452
|
+
update_result = self.taskBuffer.updateJobs(
|
|
453
|
+
[self.job],
|
|
454
|
+
False,
|
|
455
|
+
oldJobStatusList=[old_job_status],
|
|
456
|
+
extraInfo=self.extra_info,
|
|
457
|
+
async_dataset_update=True,
|
|
458
|
+
)
|
|
459
|
+
self.logger.debug(f"retU: {update_result}")
|
|
460
|
+
|
|
461
|
+
# failed
|
|
462
|
+
if not update_result[0]:
|
|
463
|
+
self.logger.error(f"failed to update DB for pandaid={self.job.PandaID}")
|
|
464
|
+
# unlock job output report
|
|
465
|
+
self.taskBuffer.unlockJobOutputReport(
|
|
466
|
+
panda_id=self.job_id,
|
|
467
|
+
attempt_nr=self.attempt_nr,
|
|
468
|
+
pid=self.pid,
|
|
469
|
+
lock_offset=self.lock_offset,
|
|
470
|
+
)
|
|
471
|
+
return False
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
# updateJobs was successful and it failed a job with taskBufferErrorCode
|
|
475
|
+
self.logger.debug("AdderGen.run will peek the job")
|
|
476
|
+
job_tmp = self.taskBuffer.peekJobs(
|
|
477
|
+
[self.job.PandaID],
|
|
478
|
+
fromDefined=False,
|
|
479
|
+
fromArchived=True,
|
|
480
|
+
fromWaiting=False,
|
|
481
|
+
)[0]
|
|
482
|
+
self.logger.debug(
|
|
483
|
+
f"status {job_tmp.jobStatus}, taskBufferErrorCode {job_tmp.taskBufferErrorCode}, taskBufferErrorDiag {job_tmp.taskBufferErrorDiag}"
|
|
484
|
+
)
|
|
485
|
+
if job_tmp.jobStatus == "failed" and job_tmp.taskBufferErrorCode:
|
|
486
|
+
source = "taskBufferErrorCode"
|
|
487
|
+
error_code = job_tmp.taskBufferErrorCode
|
|
488
|
+
error_diag = job_tmp.taskBufferErrorDiag
|
|
489
|
+
errors = [
|
|
490
|
+
{
|
|
491
|
+
"source": source,
|
|
492
|
+
"error_code": error_code,
|
|
493
|
+
"error_diag": error_diag,
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
self.logger.debug("AdderGen.run 2 will call job_failure_postprocessing")
|
|
497
|
+
retryModule.job_failure_postprocessing(
|
|
498
|
+
self.taskBuffer,
|
|
499
|
+
job_tmp.PandaID,
|
|
500
|
+
errors,
|
|
501
|
+
job_tmp.attemptNr,
|
|
502
|
+
)
|
|
503
|
+
self.logger.debug("job_failure_postprocessing 2 is back")
|
|
504
|
+
except IndexError:
|
|
505
|
+
pass
|
|
506
|
+
except Exception as e:
|
|
507
|
+
self.logger.error(f"job_failure_postprocessing 2 excepted and needs to be investigated ({e}): {traceback.format_exc()}")
|
|
508
|
+
|
|
509
|
+
self.setup_closer()
|
|
510
|
+
return True
|
|
511
|
+
|
|
512
|
+
def setup_closer(self) -> None:
|
|
513
|
+
"""
|
|
514
|
+
Setup closer for the job.
|
|
515
|
+
"""
|
|
516
|
+
# setup for closer
|
|
517
|
+
if not (EventServiceUtils.isEventServiceJob(self.job) and self.job.isCancelled()):
|
|
518
|
+
destination_dispatch_block_list = []
|
|
519
|
+
guid_list = []
|
|
520
|
+
for file in self.job.Files:
|
|
521
|
+
# ignore inputs
|
|
522
|
+
if file.type == "input":
|
|
523
|
+
continue
|
|
524
|
+
# skip pseudo datasets
|
|
525
|
+
if file.destinationDBlock in ["", None, "NULL"]:
|
|
526
|
+
continue
|
|
527
|
+
# start closer for output/log datasets
|
|
528
|
+
if file.destinationDBlock not in destination_dispatch_block_list:
|
|
529
|
+
destination_dispatch_block_list.append(file.destinationDBlock)
|
|
530
|
+
# collect GUIDs
|
|
531
|
+
if (
|
|
532
|
+
self.job.prodSourceLabel == "panda"
|
|
533
|
+
or (
|
|
534
|
+
self.job.prodSourceLabel in ["rucio_test"] + JobUtils.list_ptest_prod_sources
|
|
535
|
+
and self.job.processingType
|
|
536
|
+
in [
|
|
537
|
+
"pathena",
|
|
538
|
+
"prun",
|
|
539
|
+
"gangarobot-rctest",
|
|
540
|
+
"hammercloud",
|
|
541
|
+
]
|
|
542
|
+
)
|
|
543
|
+
) and file.type == "output":
|
|
544
|
+
# extract base LFN since LFN was changed to full LFN for CMS
|
|
545
|
+
base_lfn = file.lfn.split("/")[-1]
|
|
546
|
+
guid_list.append(
|
|
547
|
+
{
|
|
548
|
+
"lfn": base_lfn,
|
|
549
|
+
"guid": file.GUID,
|
|
550
|
+
"type": file.type,
|
|
551
|
+
"checksum": file.checksum,
|
|
552
|
+
"md5sum": file.md5sum,
|
|
553
|
+
"fsize": file.fsize,
|
|
554
|
+
"scope": file.scope,
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
if guid_list:
|
|
558
|
+
self.taskBuffer.setGUIDs(guid_list)
|
|
559
|
+
if destination_dispatch_block_list:
|
|
560
|
+
# start Closer
|
|
561
|
+
if self.adder_plugin is not None and hasattr(self.adder_plugin, "dataset_map") and self.adder_plugin.dataset_map != {}:
|
|
562
|
+
closer_thread = closer.Closer(
|
|
563
|
+
self.taskBuffer,
|
|
564
|
+
destination_dispatch_block_list,
|
|
565
|
+
self.job,
|
|
566
|
+
dataset_map=self.adder_plugin.dataset_map,
|
|
567
|
+
)
|
|
568
|
+
else:
|
|
569
|
+
closer_thread = closer.Closer(self.taskBuffer, destination_dispatch_block_list, self.job)
|
|
570
|
+
self.logger.debug("start Closer")
|
|
571
|
+
closer_thread.run()
|
|
572
|
+
del closer_thread
|
|
573
|
+
self.logger.debug("end Closer")
|
|
574
|
+
# run closer for associate parallel jobs
|
|
575
|
+
if EventServiceUtils.isJobCloningJob(self.job):
|
|
576
|
+
associate_dispatch_block_map = self.taskBuffer.getDestDBlocksWithSingleConsumer(
|
|
577
|
+
self.job.jediTaskID, self.job.PandaID, destination_dispatch_block_list
|
|
578
|
+
)
|
|
579
|
+
for associate_job_id in associate_dispatch_block_map:
|
|
580
|
+
associate_dispatch_blocks = associate_dispatch_block_map[associate_job_id]
|
|
581
|
+
associate_job = self.taskBuffer.peekJobs(
|
|
582
|
+
[associate_job_id],
|
|
583
|
+
fromDefined=False,
|
|
584
|
+
fromArchived=False,
|
|
585
|
+
fromWaiting=False,
|
|
586
|
+
forAnal=True,
|
|
587
|
+
)[0]
|
|
588
|
+
if self.job is None:
|
|
589
|
+
self.logger.debug(f"associated job PandaID={associate_job_id} not found in DB")
|
|
590
|
+
else:
|
|
591
|
+
closer_thread = closer.Closer(self.taskBuffer, associate_dispatch_blocks, associate_job)
|
|
592
|
+
self.logger.debug(f"start Closer for PandaID={associate_job_id}")
|
|
593
|
+
closer_thread.run()
|
|
594
|
+
del closer_thread
|
|
595
|
+
self.logger.debug(f"end Closer for PandaID={associate_job_id}")
|
|
596
|
+
|
|
597
|
+
def update_worker_node(self, json_dict):
|
|
598
|
+
try:
|
|
599
|
+
self.logger.debug(f"update_worker_node: start")
|
|
600
|
+
wn_specs = json_dict.get("worker_node", {})
|
|
601
|
+
if not wn_specs:
|
|
602
|
+
self.logger.debug(f"update_worker_node: done. No worker node specs found")
|
|
603
|
+
return
|
|
604
|
+
|
|
605
|
+
site = wn_specs.get("site")
|
|
606
|
+
host_name = wn_specs.get("host_name")
|
|
607
|
+
cpu_model = wn_specs.get("cpu_model")
|
|
608
|
+
if not site or not host_name or not cpu_model:
|
|
609
|
+
self.logger.debug(f"update_worker_node: done. Incomplete worker node specs found: site={site}, host_name={host_name}, cpu_model={cpu_model}")
|
|
610
|
+
return
|
|
611
|
+
|
|
612
|
+
cpu_model_normalized = normalize_cpu_model(cpu_model)
|
|
613
|
+
panda_queue = wn_specs.get("panda_queue")
|
|
614
|
+
n_logical_cpus = wn_specs.get("n_logical_cpus")
|
|
615
|
+
n_sockets = wn_specs.get("n_sockets")
|
|
616
|
+
cores_per_socket = wn_specs.get("cores_per_socket")
|
|
617
|
+
threads_per_core = wn_specs.get("threads_per_core")
|
|
618
|
+
cpu_architecture = wn_specs.get("cpu_architecture")
|
|
619
|
+
cpu_architecture_level = wn_specs.get("cpu_architecture_level")
|
|
620
|
+
clock_speed = wn_specs.get("clock_speed")
|
|
621
|
+
total_memory = wn_specs.get("total_memory")
|
|
622
|
+
total_local_disk = wn_specs.get("total_local_disk")
|
|
623
|
+
|
|
624
|
+
self.taskBuffer.update_worker_node(
|
|
625
|
+
site,
|
|
626
|
+
panda_queue,
|
|
627
|
+
host_name,
|
|
628
|
+
cpu_model,
|
|
629
|
+
cpu_model_normalized,
|
|
630
|
+
n_logical_cpus,
|
|
631
|
+
n_sockets,
|
|
632
|
+
cores_per_socket,
|
|
633
|
+
threads_per_core,
|
|
634
|
+
cpu_architecture,
|
|
635
|
+
cpu_architecture_level,
|
|
636
|
+
clock_speed,
|
|
637
|
+
total_memory,
|
|
638
|
+
total_local_disk,
|
|
639
|
+
)
|
|
640
|
+
self.logger.debug(f"update_worker_node: done for site={site}, host_name={host_name}, cpu_model={cpu_model}")
|
|
641
|
+
return
|
|
642
|
+
except Exception:
|
|
643
|
+
self.logger.error(f"update_worker_node: issue with updating worker node specs: {traceback.format_exc()}")
|
|
644
|
+
|
|
645
|
+
def update_worker_node_gpu(self, json_dict):
|
|
646
|
+
try:
|
|
647
|
+
self.logger.debug(f"update_worker_node_gpu: start")
|
|
648
|
+
wn_gpu_specs = json_dict.get("worker_node_gpus", {})
|
|
649
|
+
if not wn_gpu_specs:
|
|
650
|
+
self.logger.debug(f"update_worker_node_gpu: done. No worker node GPU specs found")
|
|
651
|
+
return
|
|
652
|
+
|
|
653
|
+
site = wn_gpu_specs.get("site")
|
|
654
|
+
host_name = wn_gpu_specs.get("host_name")
|
|
655
|
+
if not site or not host_name:
|
|
656
|
+
self.logger.debug(f"update_worker_node_gpu: done. Incomplete worker node GPU specs found: site={site}, host_name={host_name}")
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
vendor = wn_gpu_specs.get("vendor")
|
|
660
|
+
model = wn_gpu_specs.get("model")
|
|
661
|
+
count = wn_gpu_specs.get("count")
|
|
662
|
+
vram = wn_gpu_specs.get("vram")
|
|
663
|
+
architecture = wn_gpu_specs.get("architecture")
|
|
664
|
+
framework = wn_gpu_specs.get("framework")
|
|
665
|
+
framework_version = wn_gpu_specs.get("framework_version")
|
|
666
|
+
driver_version = wn_gpu_specs.get("driver_version")
|
|
667
|
+
|
|
668
|
+
self.taskBuffer.update_worker_node_gpu(site, host_name, vendor, model, count, vram, architecture, framework, framework_version, driver_version)
|
|
669
|
+
self.logger.debug(f"update_worker_node_gpu: done for site={site}, host_name={host_name}")
|
|
670
|
+
return
|
|
671
|
+
except Exception:
|
|
672
|
+
self.logger.error(f"update_worker_node_gpu: issue with updating worker node GPU specs: {traceback.format_exc()}")
|
|
673
|
+
|
|
674
|
+
# parse JSON
|
|
675
|
+
# 0: succeeded, 1: harmless error to exit, 2: fatal error, 3: event service
|
|
676
|
+
def parse_job_output_report(self):
|
|
677
|
+
"""
|
|
678
|
+
Parse the JSON data associated with the job to extract file information.
|
|
679
|
+
|
|
680
|
+
This method processes the JSON data to retrieve Logical File Names (LFNs),
|
|
681
|
+
Globally Unique Identifiers (GUIDs), file sizes, checksums, and other metadata.
|
|
682
|
+
It updates the job's file information and ensures consistency between the JSON
|
|
683
|
+
data and the job's file records.
|
|
684
|
+
|
|
685
|
+
:return: An integer indicating the result of the parsing process.
|
|
686
|
+
0 - succeeded
|
|
687
|
+
1 - harmless error to exit
|
|
688
|
+
2 - fatal error
|
|
689
|
+
3 - event service
|
|
690
|
+
"""
|
|
691
|
+
# get LFN and GUID
|
|
692
|
+
log_out = [f for f in self.job.Files if f.type in ["log", "output"]]
|
|
693
|
+
|
|
694
|
+
# no outputs
|
|
695
|
+
if not log_out:
|
|
696
|
+
self.logger.debug("has no outputs")
|
|
697
|
+
self.logger.debug("parse_job_output_report end")
|
|
698
|
+
return 0
|
|
699
|
+
|
|
700
|
+
# get input files
|
|
701
|
+
input_lfns = [file.lfn for file in self.job.Files if file.type == "input"]
|
|
702
|
+
|
|
703
|
+
# parse JSON
|
|
704
|
+
lfns = []
|
|
705
|
+
guids = []
|
|
706
|
+
fsizes = []
|
|
707
|
+
md5sums = []
|
|
708
|
+
chksums = []
|
|
709
|
+
surls = []
|
|
710
|
+
full_lfn_map = {}
|
|
711
|
+
n_events_map = {}
|
|
712
|
+
guid_map = {}
|
|
713
|
+
|
|
714
|
+
try:
|
|
715
|
+
json_dict = json.loads(self.data)
|
|
716
|
+
for lfn in json_dict:
|
|
717
|
+
file_data = json_dict[lfn]
|
|
718
|
+
lfn = str(lfn)
|
|
719
|
+
fsize = None
|
|
720
|
+
md5sum = None
|
|
721
|
+
adler32 = None
|
|
722
|
+
surl = None
|
|
723
|
+
full_lfn = None
|
|
724
|
+
guid = str(file_data["guid"])
|
|
725
|
+
if "fsize" in file_data:
|
|
726
|
+
fsize = int(file_data["fsize"])
|
|
727
|
+
if "md5sum" in file_data:
|
|
728
|
+
md5sum = str(file_data["md5sum"])
|
|
729
|
+
# check the md5sum is a 32-character hexadecimal number
|
|
730
|
+
if re.search("^[a-fA-F0-9]{32}$", md5sum) is None:
|
|
731
|
+
md5sum = None
|
|
732
|
+
if "adler32" in file_data:
|
|
733
|
+
adler32 = str(file_data["adler32"])
|
|
734
|
+
if "surl" in file_data:
|
|
735
|
+
surl = str(file_data["surl"])
|
|
736
|
+
if "full_lfn" in file_data:
|
|
737
|
+
full_lfn = str(file_data["full_lfn"])
|
|
738
|
+
# endpoints
|
|
739
|
+
self.extra_info["endpoint"][lfn] = []
|
|
740
|
+
if "endpoint" in file_data:
|
|
741
|
+
self.extra_info["endpoint"][lfn] = [file_data["endpoint"]]
|
|
742
|
+
# error check
|
|
743
|
+
if (lfn not in input_lfns) and (fsize is None or (md5sum is None and adler32 is None)):
|
|
744
|
+
if not EventServiceUtils.isEventServiceMerge(self.job):
|
|
745
|
+
raise RuntimeError("fsize/md5sum/adler32/surl=None")
|
|
746
|
+
# append
|
|
747
|
+
lfns.append(lfn)
|
|
748
|
+
guids.append(guid)
|
|
749
|
+
fsizes.append(fsize)
|
|
750
|
+
md5sums.append(md5sum)
|
|
751
|
+
surls.append(surl)
|
|
752
|
+
if adler32 is not None:
|
|
753
|
+
# use adler32 if available
|
|
754
|
+
chksums.append(f"ad:{adler32}")
|
|
755
|
+
else:
|
|
756
|
+
chksums.append(f"md5:{md5sum}")
|
|
757
|
+
if full_lfn is not None:
|
|
758
|
+
full_lfn_map[lfn] = full_lfn
|
|
759
|
+
except Exception:
|
|
760
|
+
exc_type, value, _ = sys.exc_info()
|
|
761
|
+
self.logger.warning("Issue with parsing JSON")
|
|
762
|
+
self.logger.error(f"{exc_type} {value}")
|
|
763
|
+
# set failed anyway
|
|
764
|
+
self.job.jobStatus = "failed"
|
|
765
|
+
# JSON error happens when pilot got killed due to wall-time limit or failures in wrapper
|
|
766
|
+
if (
|
|
767
|
+
(self.job.pilotErrorCode in [0, "0", "NULL"])
|
|
768
|
+
and (self.job.taskBufferErrorCode not in [pandaserver.taskbuffer.ErrorCode.EC_WorkerDone])
|
|
769
|
+
and (self.job.transExitCode in [0, "0", "NULL"])
|
|
770
|
+
):
|
|
771
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
772
|
+
self.job.ddmErrorDiag = "Could not get GUID/LFN/MD5/FSIZE/SURL from pilot JSON"
|
|
773
|
+
return 2
|
|
774
|
+
|
|
775
|
+
# parse metadata to get nEvents
|
|
776
|
+
n_events_from = ""
|
|
777
|
+
json_dict = {}
|
|
778
|
+
try:
|
|
779
|
+
json_dict = json.loads(self.job.metadata)
|
|
780
|
+
for json_file_item in json_dict["files"]["output"]:
|
|
781
|
+
for json_sub_file_item in json_file_item["subFiles"]:
|
|
782
|
+
lfn = str(json_sub_file_item["name"])
|
|
783
|
+
try:
|
|
784
|
+
n_events = int(json_sub_file_item["nentries"])
|
|
785
|
+
n_events_map[lfn] = n_events
|
|
786
|
+
except Exception:
|
|
787
|
+
pass
|
|
788
|
+
try:
|
|
789
|
+
guid = str(json_sub_file_item["file_guid"])
|
|
790
|
+
guid_map[lfn] = guid
|
|
791
|
+
except Exception:
|
|
792
|
+
pass
|
|
793
|
+
n_events_from = "json"
|
|
794
|
+
except Exception:
|
|
795
|
+
self.logger.warning("Issue with parsing JSON for nEvents")
|
|
796
|
+
pass
|
|
797
|
+
|
|
798
|
+
# parse metadata to get worker node specs and GPU specs
|
|
799
|
+
if isinstance(json_dict, dict):
|
|
800
|
+
self.update_worker_node(json_dict)
|
|
801
|
+
self.update_worker_node_gpu(json_dict)
|
|
802
|
+
|
|
803
|
+
# use nEvents and GUIDs reported by the pilot if no job report
|
|
804
|
+
if self.job.metadata == "NULL" and self.job_status == "finished" and self.job.nEvents > 0 and self.job.prodSourceLabel in ["managed"]:
|
|
805
|
+
for file in self.job.Files:
|
|
806
|
+
if file.type == "output":
|
|
807
|
+
n_events_map[file.lfn] = self.job.nEvents
|
|
808
|
+
for lfn, guid in zip(lfns, guids):
|
|
809
|
+
guid_map[lfn] = guid
|
|
810
|
+
n_events_from = "pilot"
|
|
811
|
+
|
|
812
|
+
self.logger.debug(f"nEventsMap={str(n_events_map)}")
|
|
813
|
+
self.logger.debug(f"nEventsFrom={str(n_events_from)}")
|
|
814
|
+
self.logger.debug(f"guidMap={str(guid_map)}")
|
|
815
|
+
self.logger.debug(f"self.job.jobStatus={self.job.jobStatus} in parse_job_output_report")
|
|
816
|
+
self.logger.debug(f"isES={EventServiceUtils.isEventServiceJob(self.job)} isJumbo={EventServiceUtils.isJumboJob(self.job)}")
|
|
817
|
+
|
|
818
|
+
# get lumi block number
|
|
819
|
+
lumi_block_nr = self.job.getLumiBlockNr()
|
|
820
|
+
|
|
821
|
+
# copy files for variable number of outputs
|
|
822
|
+
tmp_stat = self.copy_files_for_variable_num_outputs(lfns)
|
|
823
|
+
if not tmp_stat:
|
|
824
|
+
err_msg = "failed to copy files for variable number of outputs"
|
|
825
|
+
self.logger.error(err_msg)
|
|
826
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
827
|
+
self.job.ddmErrorDiag = err_msg
|
|
828
|
+
self.job.jobStatus = "failed"
|
|
829
|
+
return 2
|
|
830
|
+
|
|
831
|
+
# check files
|
|
832
|
+
lfns_set = set(lfns)
|
|
833
|
+
file_list = []
|
|
834
|
+
for file in self.job.Files:
|
|
835
|
+
file_list.append(file.lfn)
|
|
836
|
+
if file.type == "input":
|
|
837
|
+
if file.lfn in lfns_set:
|
|
838
|
+
if self.job.prodSourceLabel in ["user", "panda"] or self.job.is_on_site_merging():
|
|
839
|
+
# skipped file
|
|
840
|
+
file.status = "skipped"
|
|
841
|
+
self.logger.debug(f"skipped input : {file.lfn}")
|
|
842
|
+
elif self.job.prodSourceLabel in ["managed", "test"] + JobUtils.list_ptest_prod_sources:
|
|
843
|
+
# failed by pilot
|
|
844
|
+
file.status = "failed"
|
|
845
|
+
self.logger.debug(f"failed input : {file.lfn}")
|
|
846
|
+
elif file.type in {"output", "log"}:
|
|
847
|
+
# add only log file for failed jobs
|
|
848
|
+
if self.job_status == "failed" and file.type != "log":
|
|
849
|
+
file.status = "failed"
|
|
850
|
+
continue
|
|
851
|
+
# set failed if it is missing in JSON
|
|
852
|
+
if file.lfn not in lfns_set:
|
|
853
|
+
if (self.job.jobStatus == "finished" and EventServiceUtils.isEventServiceJob(self.job)) or EventServiceUtils.isJumboJob(self.job):
|
|
854
|
+
# unset file status for ES jobs
|
|
855
|
+
pass
|
|
856
|
+
elif file.isAllowedNoOutput():
|
|
857
|
+
# allowed not to be produced
|
|
858
|
+
file.status = "nooutput"
|
|
859
|
+
self.logger.debug(f"set {file.lfn} to status={file.status}")
|
|
860
|
+
else:
|
|
861
|
+
file.status = "failed"
|
|
862
|
+
self.job.jobStatus = "failed"
|
|
863
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
864
|
+
self.job.ddmErrorDiag = f"expected output {file.lfn} is missing in pilot JSON"
|
|
865
|
+
self.logger.error(self.job.ddmErrorDiag)
|
|
866
|
+
continue
|
|
867
|
+
|
|
868
|
+
# look for GUID with LFN
|
|
869
|
+
try:
|
|
870
|
+
i = lfns.index(file.lfn)
|
|
871
|
+
file.GUID = guids[i]
|
|
872
|
+
file.fsize = fsizes[i]
|
|
873
|
+
file.md5sum = md5sums[i]
|
|
874
|
+
file.checksum = chksums[i]
|
|
875
|
+
surl = surls[i]
|
|
876
|
+
# status
|
|
877
|
+
file.status = "ready"
|
|
878
|
+
# change to full LFN
|
|
879
|
+
if file.lfn in full_lfn_map:
|
|
880
|
+
file.lfn = full_lfn_map[file.lfn]
|
|
881
|
+
# add SURL to extraInfo
|
|
882
|
+
self.extra_info["surl"][file.lfn] = surl
|
|
883
|
+
# add nevents
|
|
884
|
+
if file.lfn in n_events_map:
|
|
885
|
+
self.extra_info["nevents"][file.lfn] = n_events_map[file.lfn]
|
|
886
|
+
except Exception:
|
|
887
|
+
# status
|
|
888
|
+
file.status = "failed"
|
|
889
|
+
exc_type, value, _ = sys.exc_info()
|
|
890
|
+
self.logger.error(f"{exc_type} {value}")
|
|
891
|
+
|
|
892
|
+
# set lumi block number
|
|
893
|
+
if lumi_block_nr is not None and file.status != "failed":
|
|
894
|
+
self.extra_info["lbnr"][file.lfn] = lumi_block_nr
|
|
895
|
+
|
|
896
|
+
self.extra_info["guid"] = guid_map
|
|
897
|
+
|
|
898
|
+
# check consistency between JSON and filesTable
|
|
899
|
+
for lfn in lfns:
|
|
900
|
+
if lfn not in file_list:
|
|
901
|
+
self.logger.error(f"{lfn} is not found in filesTable")
|
|
902
|
+
self.job.jobStatus = "failed"
|
|
903
|
+
for tmp_file in self.job.Files:
|
|
904
|
+
tmp_file.status = "failed"
|
|
905
|
+
self.job.ddmErrorCode = pandaserver.dataservice.ErrorCode.EC_Adder
|
|
906
|
+
self.job.ddmErrorDiag = f"pilot produced {lfn} inconsistently with jobdef"
|
|
907
|
+
return 2
|
|
908
|
+
# return
|
|
909
|
+
self.logger.debug("parse_job_output_report end")
|
|
910
|
+
return 0
|
|
911
|
+
|
|
912
|
+
# copy files for variable number of outputs
|
|
913
|
+
def copy_files_for_variable_num_outputs(self, lfns):
|
|
914
|
+
"""
|
|
915
|
+
Copy files for variable number of outputs.
|
|
916
|
+
|
|
917
|
+
This method handles the copying of file records when there is a variable number of output files.
|
|
918
|
+
It identifies new Logical File Names (LFNs) that correspond to the original output files and
|
|
919
|
+
updates the job information accordingly.
|
|
920
|
+
|
|
921
|
+
:param lfns: List of new Logical File Names (LFNs).
|
|
922
|
+
:return: True if the operation is successful, False otherwise.
|
|
923
|
+
"""
|
|
924
|
+
# get original output files
|
|
925
|
+
original_output_files = {}
|
|
926
|
+
for tmp_file in self.job.Files:
|
|
927
|
+
if tmp_file.type in ["output", "log"]:
|
|
928
|
+
original_output_files[tmp_file.lfn] = tmp_file
|
|
929
|
+
# look for unknown files
|
|
930
|
+
orig_to_new_map = {}
|
|
931
|
+
for new_lfn in lfns:
|
|
932
|
+
if new_lfn not in original_output_files:
|
|
933
|
+
# look for corresponding original output
|
|
934
|
+
for original_lfn in original_output_files:
|
|
935
|
+
# match LFNs that have a similar base name but may have additional suffixes
|
|
936
|
+
tmp_patt = r"^{0}\.*_\d+$".format(original_lfn)
|
|
937
|
+
# removing any prefix up to and including the first pipe character (|) from the original LFN
|
|
938
|
+
reg_patt = re.sub(r"^[^|]+\|", "", original_lfn)
|
|
939
|
+
if re.search(tmp_patt, new_lfn) or (original_lfn.startswith("regex|") and re.search(reg_patt, new_lfn)):
|
|
940
|
+
self.logger.debug(f"use new LFN {new_lfn} for {original_lfn}")
|
|
941
|
+
# collect new filenames
|
|
942
|
+
orig_to_new_map.setdefault(original_lfn, [])
|
|
943
|
+
orig_to_new_map[original_lfn].append(new_lfn)
|
|
944
|
+
break
|
|
945
|
+
# copy file records
|
|
946
|
+
for original_lfn, new_lfn in orig_to_new_map.items():
|
|
947
|
+
tmp_stat = self.taskBuffer.copy_file_records(new_lfn, original_output_files[original_lfn])
|
|
948
|
+
if not tmp_stat:
|
|
949
|
+
return False
|
|
950
|
+
# refresh job info
|
|
951
|
+
if orig_to_new_map:
|
|
952
|
+
self.job = self.taskBuffer.peekJobs([self.job_id], fromDefined=False, fromWaiting=False, forAnal=True)[0]
|
|
953
|
+
# return
|
|
954
|
+
return True
|