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,1106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin class for adding data to datasets in ATLAS.
|
|
3
|
+
Inherits from AdderPluginBase.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
import gc
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import traceback
|
|
13
|
+
from typing import Dict, List
|
|
14
|
+
|
|
15
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
16
|
+
from rucio.common.exception import (
|
|
17
|
+
DataIdentifierNotFound,
|
|
18
|
+
FileConsistencyMismatch,
|
|
19
|
+
InsufficientAccountLimit,
|
|
20
|
+
InvalidObject,
|
|
21
|
+
InvalidPath,
|
|
22
|
+
InvalidRSEExpression,
|
|
23
|
+
RSENotFound,
|
|
24
|
+
RSEProtocolNotSupported,
|
|
25
|
+
UnsupportedOperation,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from pandaserver.config import panda_config
|
|
29
|
+
from pandaserver.dataservice import DataServiceUtils, ErrorCode
|
|
30
|
+
from pandaserver.dataservice.adder_plugin_base import AdderPluginBase
|
|
31
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
32
|
+
from pandaserver.dataservice.ddm import rucioAPI
|
|
33
|
+
from pandaserver.srvcore.MailUtils import MailUtils
|
|
34
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AdderAtlasPlugin(AdderPluginBase):
|
|
38
|
+
"""
|
|
39
|
+
Plugin class for adding data to datasets in ATLAS.
|
|
40
|
+
Inherits from AdderPluginBase.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# constructor
|
|
44
|
+
def __init__(self, job, **params):
|
|
45
|
+
"""
|
|
46
|
+
Constructor for AdderAtlasPlugin.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
job: The job object containing job details.
|
|
50
|
+
**params: Additional parameters.
|
|
51
|
+
"""
|
|
52
|
+
AdderPluginBase.__init__(self, job, params)
|
|
53
|
+
self.job_id = self.job.PandaID
|
|
54
|
+
self.job_status = self.job.jobStatus
|
|
55
|
+
self.dataset_map = {}
|
|
56
|
+
self.add_to_top_only = False
|
|
57
|
+
self.go_to_transferring = False
|
|
58
|
+
self.log_transferring = False
|
|
59
|
+
self.subscription_map = {}
|
|
60
|
+
self.go_to_merging = False
|
|
61
|
+
|
|
62
|
+
# main
|
|
63
|
+
def execute(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Main execution method for the plugin.
|
|
66
|
+
Handles the logic for adding files to datasets based on job status and other conditions.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
self.logger.debug(f"start plugin : {self.job_status}")
|
|
70
|
+
# add files only to top-level datasets for transferring jobs
|
|
71
|
+
if self.job.jobStatus == "transferring":
|
|
72
|
+
self.add_to_top_only = True
|
|
73
|
+
self.logger.debug("adder for transferring")
|
|
74
|
+
self.check_output_file_metadata()
|
|
75
|
+
# check if the job goes to merging
|
|
76
|
+
if self.job.produceUnMerge():
|
|
77
|
+
self.go_to_merging = True
|
|
78
|
+
# check if the job should go to transferring
|
|
79
|
+
src_site_spec = self.siteMapper.getSite(self.job.computingSite)
|
|
80
|
+
_, scope_src_site_spec_output = select_scope(src_site_spec, self.job.prodSourceLabel, self.job.job_label)
|
|
81
|
+
tmp_src_ddm = src_site_spec.ddm_output[scope_src_site_spec_output]
|
|
82
|
+
if self.job.prodSourceLabel == "user" and self.job.destinationSE not in self.siteMapper.siteSpecList:
|
|
83
|
+
# DQ2 ID was set by using --destSE for analysis job to transfer output
|
|
84
|
+
tmp_dst_ddm = self.job.destinationSE
|
|
85
|
+
else:
|
|
86
|
+
dst_site_spec = self.siteMapper.getSite(self.job.destinationSE)
|
|
87
|
+
_, scope_dst_site_spec_output = select_scope(dst_site_spec, self.job.prodSourceLabel, self.job.job_label)
|
|
88
|
+
tmp_dst_ddm = dst_site_spec.ddm_output[scope_dst_site_spec_output]
|
|
89
|
+
# protection against disappearance of dest from schedconfig
|
|
90
|
+
if self.job.destinationSE not in ["NULL", None] and not self.siteMapper.checkSite(self.job.destinationSE) and self.job.destinationSE != "local":
|
|
91
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
92
|
+
self.job.ddmErrorDiag = f"destinationSE {self.job.destinationSE} is unknown in schedconfig"
|
|
93
|
+
self.logger.error(f"{self.job.ddmErrorDiag}")
|
|
94
|
+
# set fatal error code and return
|
|
95
|
+
self.result.set_fatal()
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# protection against disappearance of src from schedconfig
|
|
99
|
+
if not self.siteMapper.checkSite(self.job.computingSite):
|
|
100
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
101
|
+
self.job.ddmErrorDiag = f"computingSite {self.job.computingSite} is unknown in schedconfig"
|
|
102
|
+
self.logger.error(f"{self.job.ddmErrorDiag}")
|
|
103
|
+
# set fatal error code and return
|
|
104
|
+
self.result.set_fatal()
|
|
105
|
+
return
|
|
106
|
+
# check if the job has something to transfer
|
|
107
|
+
self.logger.debug(f"alt stage-out:{str(self.job.altStgOutFileList())}")
|
|
108
|
+
something_to_transfer = False
|
|
109
|
+
for file in self.job.Files:
|
|
110
|
+
if file.type in {"output", "log"}:
|
|
111
|
+
if file.status == "nooutput":
|
|
112
|
+
continue
|
|
113
|
+
if DataServiceUtils.getDistributedDestination(file.destinationDBlockToken) is None and file.lfn not in self.job.altStgOutFileList():
|
|
114
|
+
something_to_transfer = True
|
|
115
|
+
break
|
|
116
|
+
self.logger.debug(f"DDM src:{tmp_src_ddm} dst:{tmp_dst_ddm}")
|
|
117
|
+
job_type = JobUtils.translate_prodsourcelabel_to_jobtype(src_site_spec.type, self.job.prodSourceLabel)
|
|
118
|
+
if re.search("^ANALY_", self.job.computingSite) is not None:
|
|
119
|
+
# analysis site. Should be obsoleted by the next check
|
|
120
|
+
pass
|
|
121
|
+
elif job_type == JobUtils.ANALY_PS:
|
|
122
|
+
# analysis job
|
|
123
|
+
pass
|
|
124
|
+
elif self.job.computingSite == self.job.destinationSE:
|
|
125
|
+
# same site ID for computingSite and destinationSE
|
|
126
|
+
pass
|
|
127
|
+
elif tmp_src_ddm == tmp_dst_ddm:
|
|
128
|
+
# same DDMID for src/dest
|
|
129
|
+
pass
|
|
130
|
+
elif self.job.destinationSE in ["NULL", None]:
|
|
131
|
+
# jobs stayed without destinationSE, e.g. there is no job output
|
|
132
|
+
pass
|
|
133
|
+
elif self.add_to_top_only:
|
|
134
|
+
# already in transferring
|
|
135
|
+
pass
|
|
136
|
+
elif self.go_to_merging:
|
|
137
|
+
# no transferring for merging
|
|
138
|
+
pass
|
|
139
|
+
elif self.job.jobStatus == "failed":
|
|
140
|
+
# failed jobs
|
|
141
|
+
if self.job.prodSourceLabel in ["managed", "test"]:
|
|
142
|
+
self.log_transferring = True
|
|
143
|
+
elif (
|
|
144
|
+
self.job.jobStatus == "finished"
|
|
145
|
+
and (EventServiceUtils.isEventServiceJob(self.job) or EventServiceUtils.isJumboJob(self.job))
|
|
146
|
+
and not EventServiceUtils.isJobCloningJob(self.job)
|
|
147
|
+
):
|
|
148
|
+
# transfer only log file for normal ES jobs
|
|
149
|
+
self.log_transferring = True
|
|
150
|
+
elif not something_to_transfer:
|
|
151
|
+
# nothing to transfer
|
|
152
|
+
pass
|
|
153
|
+
else:
|
|
154
|
+
self.go_to_transferring = True
|
|
155
|
+
self.logger.debug(f"somethingToTransfer={something_to_transfer}")
|
|
156
|
+
self.logger.debug(f"goToTransferring={self.go_to_transferring}")
|
|
157
|
+
self.logger.debug(f"logTransferring={self.log_transferring}")
|
|
158
|
+
self.logger.debug(f"goToMerging={self.go_to_merging}")
|
|
159
|
+
self.logger.debug(f"addToTopOnly={self.add_to_top_only}")
|
|
160
|
+
return_output = self.update_outputs()
|
|
161
|
+
self.logger.debug(f"added outputs with {return_output}")
|
|
162
|
+
if return_output != 0:
|
|
163
|
+
self.logger.debug("terminated when adding")
|
|
164
|
+
return
|
|
165
|
+
# succeeded
|
|
166
|
+
self.result.set_succeeded()
|
|
167
|
+
self.logger.debug("end plugin")
|
|
168
|
+
except Exception:
|
|
169
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
170
|
+
err_str = f"execute() : {error_type} {error_value}"
|
|
171
|
+
err_str += traceback.format_exc()
|
|
172
|
+
self.logger.debug(err_str)
|
|
173
|
+
# set fatal error code
|
|
174
|
+
self.result.set_fatal()
|
|
175
|
+
# return
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
# check output file metadata
|
|
179
|
+
def check_output_file_metadata(self):
|
|
180
|
+
"""
|
|
181
|
+
Check the metadata of output files for consistency and change the job status if inconsistencies are found.
|
|
182
|
+
"""
|
|
183
|
+
# check only successful jobs
|
|
184
|
+
if self.job_status in ["failed", "cancelled", "closed"]:
|
|
185
|
+
return
|
|
186
|
+
# get the zip file map
|
|
187
|
+
zip_file_map = self.job.getZipFileMap()
|
|
188
|
+
# check only output files
|
|
189
|
+
for file in self.job.Files:
|
|
190
|
+
is_zip_file = file.lfn in zip_file_map
|
|
191
|
+
# check num events and GUID
|
|
192
|
+
if file.type == "output" and not is_zip_file and not self.add_to_top_only and self.job.prodSourceLabel == "managed":
|
|
193
|
+
if file.lfn not in self.extra_info["nevents"]:
|
|
194
|
+
to_skip = False
|
|
195
|
+
# exclude some formats
|
|
196
|
+
for patt in ["TXT", "NTUP", "HIST"]:
|
|
197
|
+
if file.lfn.startswith(patt):
|
|
198
|
+
to_skip = True
|
|
199
|
+
if not to_skip:
|
|
200
|
+
err_msg = f"nEvents is missing in jobReport for {file.lfn}"
|
|
201
|
+
self.logger.error(err_msg)
|
|
202
|
+
self.job.ddmErrorCode = ErrorCode.EC_MissingNumEvents
|
|
203
|
+
self.job.ddmErrorDiag = err_msg
|
|
204
|
+
self.job.jobStatus = "failed"
|
|
205
|
+
self.job_status = "failed"
|
|
206
|
+
return
|
|
207
|
+
if file.lfn not in self.extra_info["guid"] or file.GUID != self.extra_info["guid"][file.lfn]:
|
|
208
|
+
self.logger.debug(f"extra_info = {str(self.extra_info)}")
|
|
209
|
+
err_msg = f"GUID is inconsistent between jobReport and pilot report for {file.lfn}"
|
|
210
|
+
self.logger.error(err_msg)
|
|
211
|
+
self.job.ddmErrorCode = ErrorCode.EC_InconsistentGUID
|
|
212
|
+
self.job.ddmErrorDiag = err_msg
|
|
213
|
+
self.job.jobStatus = "failed"
|
|
214
|
+
self.job_status = "failed"
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# update output files
|
|
218
|
+
def update_outputs(self):
|
|
219
|
+
"""
|
|
220
|
+
Update output files for the job.
|
|
221
|
+
Handles the logic for adding files to datasets and registering them with Rucio.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
int: 0 if successful, 1 if there was an error.
|
|
225
|
+
"""
|
|
226
|
+
# return if non-DQ2
|
|
227
|
+
if self.job.destinationSE == "local":
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
# Get the computingsite spec and scope
|
|
231
|
+
src_site_spec = self.siteMapper.getSite(self.job.computingSite)
|
|
232
|
+
_, scope_src_site_spec_output = select_scope(src_site_spec, self.job.prodSourceLabel, self.job.job_label)
|
|
233
|
+
|
|
234
|
+
# Get the zip file map
|
|
235
|
+
zip_file_map = self.job.getZipFileMap()
|
|
236
|
+
|
|
237
|
+
# Get campaign and nEvents input
|
|
238
|
+
campaign = None
|
|
239
|
+
n_events_input = {}
|
|
240
|
+
|
|
241
|
+
if self.job.jediTaskID not in [0, None, "NULL"]:
|
|
242
|
+
tmp_ret = self.taskBuffer.getTaskAttributesPanda(self.job.jediTaskID, ["campaign"])
|
|
243
|
+
campaign = tmp_ret.get("campaign")
|
|
244
|
+
for file_spec in self.job.Files:
|
|
245
|
+
if file_spec.type == "input":
|
|
246
|
+
tmp_dict = self.taskBuffer.getJediFileAttributes(
|
|
247
|
+
file_spec.PandaID, file_spec.jediTaskID, file_spec.datasetID, file_spec.fileID, ["nEvents"]
|
|
248
|
+
)
|
|
249
|
+
if "nEvents" in tmp_dict:
|
|
250
|
+
n_events_input[file_spec.lfn] = tmp_dict["nEvents"]
|
|
251
|
+
|
|
252
|
+
# Add nEvents info for zip files
|
|
253
|
+
for tmp_zip_file_name, tmp_zip_contents in zip_file_map.items():
|
|
254
|
+
if tmp_zip_file_name not in self.extra_info["nevents"]:
|
|
255
|
+
for tmp_zip_content in tmp_zip_contents:
|
|
256
|
+
for tmp_lfn in list(n_events_input):
|
|
257
|
+
# checking if a logical file name (LFN) matches a pattern corresponding to a zip file content and if the LFN exists in the n_events_input dictionary with a non-None value.
|
|
258
|
+
# If these conditions are met, the code updates the extra_info["nevents"] dictionary by setting a default value of 0 for the zip file name.
|
|
259
|
+
if re.search("^" + tmp_zip_content + "$", tmp_lfn) is not None and tmp_lfn in n_events_input and n_events_input[tmp_lfn] is not None:
|
|
260
|
+
self.extra_info["nevents"].setdefault(tmp_zip_file_name, 0)
|
|
261
|
+
self.extra_info["nevents"][tmp_zip_file_name] += n_events_input[tmp_lfn]
|
|
262
|
+
|
|
263
|
+
# check files
|
|
264
|
+
id_map = {}
|
|
265
|
+
# fileList = []
|
|
266
|
+
sub_map = {}
|
|
267
|
+
dataset_destination_map = {}
|
|
268
|
+
dist_datasets = set()
|
|
269
|
+
map_for_alt_stage_out = {}
|
|
270
|
+
alt_staged_files = set()
|
|
271
|
+
zip_files = {}
|
|
272
|
+
cont_zip_map = {}
|
|
273
|
+
sub_to_ds_map = {}
|
|
274
|
+
ds_id_to_ds_map = self.taskBuffer.getOutputDatasetsJEDI(self.job.PandaID)
|
|
275
|
+
self.logger.debug(f"dsInJEDI={str(ds_id_to_ds_map)}")
|
|
276
|
+
|
|
277
|
+
for file in self.job.Files:
|
|
278
|
+
# gc
|
|
279
|
+
gc.collect()
|
|
280
|
+
if file.type not in {"output", "log"}:
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
# prepare the site spec and scope for the destinationSE site
|
|
284
|
+
destination_se_site_spec = self.siteMapper.getSite(file.destinationSE)
|
|
285
|
+
_, scope_dst_se_site_spec_output = select_scope(destination_se_site_spec, self.job.prodSourceLabel, self.job.job_label)
|
|
286
|
+
|
|
287
|
+
# added to map
|
|
288
|
+
sub_to_ds_map[file.destinationDBlock] = ds_id_to_ds_map.get(file.datasetID, file.dataset)
|
|
289
|
+
|
|
290
|
+
# add only log file for failed jobs
|
|
291
|
+
if self.job_status == "failed" and file.type != "log":
|
|
292
|
+
continue
|
|
293
|
+
# add only log file for successful ES jobs
|
|
294
|
+
if (
|
|
295
|
+
self.job.jobStatus == "finished"
|
|
296
|
+
and EventServiceUtils.isEventServiceJob(self.job)
|
|
297
|
+
and not EventServiceUtils.isJobCloningJob(self.job)
|
|
298
|
+
and file.type != "log"
|
|
299
|
+
):
|
|
300
|
+
continue
|
|
301
|
+
# add only log for jumbo jobs
|
|
302
|
+
if EventServiceUtils.isJumboJob(self.job) and file.type != "log":
|
|
303
|
+
continue
|
|
304
|
+
# skip no output or failed
|
|
305
|
+
if file.status in ["nooutput", "failed"]:
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
# check if zip file
|
|
309
|
+
if file.lfn in zip_file_map:
|
|
310
|
+
is_zip_file = True
|
|
311
|
+
if file.lfn not in zip_files and not self.add_to_top_only:
|
|
312
|
+
zip_files[file.lfn] = {}
|
|
313
|
+
else:
|
|
314
|
+
is_zip_file = False
|
|
315
|
+
|
|
316
|
+
# check if zip content
|
|
317
|
+
zip_file_name = None
|
|
318
|
+
if not is_zip_file and not self.add_to_top_only:
|
|
319
|
+
for tmp_zip_file_name in zip_file_map:
|
|
320
|
+
tmp_zip_contents = zip_file_map[tmp_zip_file_name]
|
|
321
|
+
for tmp_zip_content in tmp_zip_contents:
|
|
322
|
+
if re.search("^" + tmp_zip_content + "$", file.lfn) is not None:
|
|
323
|
+
zip_file_name = tmp_zip_file_name
|
|
324
|
+
break
|
|
325
|
+
if zip_file_name is not None:
|
|
326
|
+
break
|
|
327
|
+
if zip_file_name is not None:
|
|
328
|
+
if zip_file_name not in zip_files:
|
|
329
|
+
zip_files[zip_file_name] = {}
|
|
330
|
+
cont_zip_map[file.lfn] = zip_file_name
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
# check nevents
|
|
334
|
+
if file.type == "output" and not is_zip_file and not self.add_to_top_only and self.job.prodSourceLabel == "managed":
|
|
335
|
+
if file.lfn not in self.extra_info["nevents"]:
|
|
336
|
+
to_skip = False
|
|
337
|
+
# exclude some formats
|
|
338
|
+
for patt in ["TXT", "NTUP", "HIST"]:
|
|
339
|
+
if file.lfn.startswith(patt):
|
|
340
|
+
to_skip = True
|
|
341
|
+
if not to_skip:
|
|
342
|
+
err_msg = f"nevents is missing in jobReport for {file.lfn}"
|
|
343
|
+
self.logger.warning(err_msg)
|
|
344
|
+
self.job.ddmErrorCode = ErrorCode.EC_MissingNumEvents
|
|
345
|
+
raise ValueError(err_msg)
|
|
346
|
+
if file.lfn not in self.extra_info["guid"] or file.GUID != self.extra_info["guid"][file.lfn]:
|
|
347
|
+
self.logger.debug(f"extra_info = {str(self.extra_info)}")
|
|
348
|
+
err_msg = f"GUID is inconsistent between jobReport and pilot report for {file.lfn}"
|
|
349
|
+
self.logger.warning(err_msg)
|
|
350
|
+
self.job.ddmErrorCode = ErrorCode.EC_InconsistentGUID
|
|
351
|
+
raise ValueError(err_msg)
|
|
352
|
+
|
|
353
|
+
# fsize
|
|
354
|
+
fsize = None
|
|
355
|
+
if file.fsize not in ["NULL", "", 0]:
|
|
356
|
+
try:
|
|
357
|
+
fsize = int(file.fsize)
|
|
358
|
+
except Exception:
|
|
359
|
+
error_type, error_value, _ = sys.exc_info()
|
|
360
|
+
self.logger.error(f"{self.job_id} : {error_type} {error_value}")
|
|
361
|
+
# use top-level dataset name for alternative stage-out
|
|
362
|
+
if file.lfn not in self.job.altStgOutFileList():
|
|
363
|
+
file_destination_dispatch_block = file.destinationDBlock
|
|
364
|
+
else:
|
|
365
|
+
file_destination_dispatch_block = file.dataset
|
|
366
|
+
# append to map
|
|
367
|
+
if file_destination_dispatch_block not in id_map:
|
|
368
|
+
id_map[file_destination_dispatch_block] = []
|
|
369
|
+
file_attrs = {
|
|
370
|
+
"guid": file.GUID,
|
|
371
|
+
"lfn": file.lfn,
|
|
372
|
+
"size": fsize,
|
|
373
|
+
"checksum": file.checksum,
|
|
374
|
+
"ds": file_destination_dispatch_block,
|
|
375
|
+
}
|
|
376
|
+
# add SURLs if LFC registration is required
|
|
377
|
+
if not self.add_to_top_only:
|
|
378
|
+
file_attrs["surl"] = self.extra_info["surl"][file.lfn]
|
|
379
|
+
if file_attrs["surl"] is None:
|
|
380
|
+
del file_attrs["surl"]
|
|
381
|
+
|
|
382
|
+
# get destination
|
|
383
|
+
if file_destination_dispatch_block not in dataset_destination_map:
|
|
384
|
+
if file.lfn in self.job.altStgOutFileList():
|
|
385
|
+
# alternative stage-out
|
|
386
|
+
tmp_dest_list = (
|
|
387
|
+
[DataServiceUtils.getDestinationSE(file.destinationDBlockToken)]
|
|
388
|
+
if DataServiceUtils.getDestinationSE(file.destinationDBlockToken)
|
|
389
|
+
else [
|
|
390
|
+
destination_se_site_spec.setokens_output[scope_dst_se_site_spec_output].get(
|
|
391
|
+
file.destinationDBlockToken, destination_se_site_spec.ddm_output[scope_dst_se_site_spec_output]
|
|
392
|
+
)
|
|
393
|
+
]
|
|
394
|
+
)
|
|
395
|
+
elif file.destinationDBlockToken in ["", None, "NULL"]:
|
|
396
|
+
# use default endpoint
|
|
397
|
+
tmp_dest_list = [src_site_spec.ddm_output[scope_src_site_spec_output]]
|
|
398
|
+
elif (
|
|
399
|
+
DataServiceUtils.getDestinationSE(file.destinationDBlockToken)
|
|
400
|
+
and src_site_spec.ddm_output[scope_src_site_spec_output] == destination_se_site_spec.ddm_output[scope_dst_se_site_spec_output]
|
|
401
|
+
):
|
|
402
|
+
tmp_dest_list = [DataServiceUtils.getDestinationSE(file.destinationDBlockToken)]
|
|
403
|
+
# RSE is specified
|
|
404
|
+
elif DataServiceUtils.getDistributedDestination(file.destinationDBlockToken):
|
|
405
|
+
tmp_dest_list = [DataServiceUtils.getDistributedDestination(file.destinationDBlockToken)]
|
|
406
|
+
dist_datasets.add(file_destination_dispatch_block)
|
|
407
|
+
# RSE is specified for distributed datasets
|
|
408
|
+
elif src_site_spec.cloud != self.job.cloud and (self.job.prodSourceLabel not in ["user", "panda"]):
|
|
409
|
+
# T1 used as T2
|
|
410
|
+
tmp_dest_list = [src_site_spec.ddm_output[scope_src_site_spec_output]]
|
|
411
|
+
else:
|
|
412
|
+
tmp_dest_list = []
|
|
413
|
+
tmp_se_tokens = src_site_spec.setokens_output[scope_src_site_spec_output]
|
|
414
|
+
for tmp_dest_token in file.destinationDBlockToken.split(","):
|
|
415
|
+
if tmp_dest_token in tmp_se_tokens:
|
|
416
|
+
tmp_dest = tmp_se_tokens[tmp_dest_token]
|
|
417
|
+
else:
|
|
418
|
+
tmp_dest = src_site_spec.ddm_output[scope_src_site_spec_output]
|
|
419
|
+
if tmp_dest not in tmp_dest_list:
|
|
420
|
+
tmp_dest_list.append(tmp_dest)
|
|
421
|
+
# add
|
|
422
|
+
dataset_destination_map[file_destination_dispatch_block] = tmp_dest_list
|
|
423
|
+
|
|
424
|
+
# extra meta data
|
|
425
|
+
if file.lfn in self.extra_info["lbnr"]:
|
|
426
|
+
file_attrs["lumiblocknr"] = self.extra_info["lbnr"][file.lfn]
|
|
427
|
+
if file.lfn in self.extra_info["nevents"]:
|
|
428
|
+
file_attrs["events"] = self.extra_info["nevents"][file.lfn]
|
|
429
|
+
elif self.extra_info["nevents"] != {}:
|
|
430
|
+
file_attrs["events"] = None
|
|
431
|
+
# if file.jediTaskID not in [0,None,'NULL']:
|
|
432
|
+
# fileAttrs['task_id'] = file.jediTaskID
|
|
433
|
+
file_attrs["panda_id"] = file.PandaID
|
|
434
|
+
if campaign not in ["", None]:
|
|
435
|
+
file_attrs["campaign"] = campaign
|
|
436
|
+
|
|
437
|
+
# For files uploaded to alternative RSEs
|
|
438
|
+
has_normal_url = True
|
|
439
|
+
if file.lfn in self.extra_info["endpoint"] and self.extra_info["endpoint"][file.lfn] != []:
|
|
440
|
+
for pilot_end_point in self.extra_info["endpoint"][file.lfn]:
|
|
441
|
+
# pilot uploaded to endpoint consistently with original job definition
|
|
442
|
+
if pilot_end_point in dataset_destination_map[file_destination_dispatch_block]:
|
|
443
|
+
has_normal_url = True
|
|
444
|
+
else:
|
|
445
|
+
# alternative stage-out
|
|
446
|
+
alt_staged_files.add(file.lfn)
|
|
447
|
+
map_for_alt_stage_out.setdefault(pilot_end_point, {})
|
|
448
|
+
if file_destination_dispatch_block in dist_datasets:
|
|
449
|
+
# add files to top-level distributed datasets without triggering aggregation
|
|
450
|
+
tmp_destination_dispatch_block = sub_to_ds_map[file_destination_dispatch_block]
|
|
451
|
+
else:
|
|
452
|
+
# add files to dispatch blocks for aggregation to the destination
|
|
453
|
+
tmp_destination_dispatch_block = file_destination_dispatch_block
|
|
454
|
+
map_for_alt_stage_out[pilot_end_point].setdefault(tmp_destination_dispatch_block, [])
|
|
455
|
+
map_for_alt_stage_out[pilot_end_point][tmp_destination_dispatch_block].append(file_attrs)
|
|
456
|
+
|
|
457
|
+
if has_normal_url:
|
|
458
|
+
# add file to be added to dataset
|
|
459
|
+
id_map[file_destination_dispatch_block].append(file_attrs)
|
|
460
|
+
# add file to be added to zip
|
|
461
|
+
if not is_zip_file and zip_file_name:
|
|
462
|
+
if "files" not in zip_files[zip_file_name]:
|
|
463
|
+
zip_files[zip_file_name]["files"] = []
|
|
464
|
+
zip_files[zip_file_name]["files"].append(file_attrs)
|
|
465
|
+
if is_zip_file and not self.add_to_top_only:
|
|
466
|
+
# copy file attribute for zip file registration
|
|
467
|
+
for tmp_file_attr_name, tmp_file_attr_val in file_attrs.items():
|
|
468
|
+
zip_files[file.lfn][tmp_file_attr_name] = tmp_file_attr_val
|
|
469
|
+
zip_files[file.lfn]["scope"] = file.scope
|
|
470
|
+
zip_files[file.lfn]["rse"] = dataset_destination_map[file_destination_dispatch_block]
|
|
471
|
+
|
|
472
|
+
# for subscription
|
|
473
|
+
if re.search("_sub\d+$", file_destination_dispatch_block) and (not self.add_to_top_only) and self.job.destinationSE != "local":
|
|
474
|
+
if not self.siteMapper:
|
|
475
|
+
self.logger.error("SiteMapper is None")
|
|
476
|
+
else:
|
|
477
|
+
# get dataset spec
|
|
478
|
+
if file_destination_dispatch_block not in self.dataset_map:
|
|
479
|
+
tmp_dataset = self.taskBuffer.queryDatasetWithMap({"name": file_destination_dispatch_block})
|
|
480
|
+
self.dataset_map[file_destination_dispatch_block] = tmp_dataset
|
|
481
|
+
# check if valid dataset
|
|
482
|
+
if self.dataset_map[file_destination_dispatch_block] is None:
|
|
483
|
+
self.logger.error(f": cannot find {file_destination_dispatch_block} in DB")
|
|
484
|
+
else:
|
|
485
|
+
if self.dataset_map[file_destination_dispatch_block].status not in ["defined"]:
|
|
486
|
+
# not a fresh dataset
|
|
487
|
+
self.logger.debug(
|
|
488
|
+
f": subscription was already made for {self.dataset_map[file_destination_dispatch_block].status}:{file_destination_dispatch_block}"
|
|
489
|
+
)
|
|
490
|
+
else:
|
|
491
|
+
# get DDM IDs
|
|
492
|
+
tmp_src_ddm = src_site_spec.ddm_output[scope_src_site_spec_output]
|
|
493
|
+
if self.job.prodSourceLabel == "user" and file.destinationSE not in self.siteMapper.siteSpecList:
|
|
494
|
+
# DDM ID was set by using --destSE for analysis job to transfer output
|
|
495
|
+
tmp_dst_ddm = file.destinationSE
|
|
496
|
+
else:
|
|
497
|
+
if DataServiceUtils.getDestinationSE(file.destinationDBlockToken):
|
|
498
|
+
tmp_dst_ddm = DataServiceUtils.getDestinationSE(file.destinationDBlockToken)
|
|
499
|
+
else:
|
|
500
|
+
tmp_dst_ddm = destination_se_site_spec.ddm_output[scope_dst_se_site_spec_output]
|
|
501
|
+
# if src != dest or multi-token
|
|
502
|
+
if (tmp_src_ddm != tmp_dst_ddm) or (tmp_src_ddm == tmp_dst_ddm and file.destinationDBlockToken.count(",") != 0):
|
|
503
|
+
opt_sub = {
|
|
504
|
+
"DATASET_COMPLETE_EVENT": [
|
|
505
|
+
f"http://{panda_config.pserverhosthttp}:{panda_config.pserverporthttp}/server/panda/datasetCompleted"
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
# append
|
|
509
|
+
if file_destination_dispatch_block not in sub_map:
|
|
510
|
+
sub_map[file_destination_dispatch_block] = []
|
|
511
|
+
# sources
|
|
512
|
+
opt_source = {}
|
|
513
|
+
# set sources
|
|
514
|
+
if file.destinationDBlockToken in [
|
|
515
|
+
"NULL",
|
|
516
|
+
"",
|
|
517
|
+
]:
|
|
518
|
+
# use default DDM ID as source
|
|
519
|
+
opt_source[tmp_src_ddm] = {"policy": 0}
|
|
520
|
+
else:
|
|
521
|
+
# convert token to DDM ID
|
|
522
|
+
ddm_id = tmp_src_ddm
|
|
523
|
+
# use the first token's location as source for T1D1
|
|
524
|
+
tmp_src_token = file.destinationDBlockToken.split(",")[0]
|
|
525
|
+
if tmp_src_token in src_site_spec.setokens_output[scope_src_site_spec_output]:
|
|
526
|
+
ddm_id = src_site_spec.setokens_output[scope_src_site_spec_output][tmp_src_token]
|
|
527
|
+
opt_source[ddm_id] = {"policy": 0}
|
|
528
|
+
# T1 used as T2
|
|
529
|
+
if src_site_spec.cloud != self.job.cloud and (self.job.prodSourceLabel not in ["user", "panda"]):
|
|
530
|
+
if tmp_src_ddm not in opt_source:
|
|
531
|
+
opt_source[tmp_src_ddm] = {"policy": 0}
|
|
532
|
+
# use another location when token is set
|
|
533
|
+
if file.destinationDBlockToken not in [
|
|
534
|
+
"NULL",
|
|
535
|
+
"",
|
|
536
|
+
]:
|
|
537
|
+
tmp_ddm_id_list = []
|
|
538
|
+
tmp_dst_tokens = file.destinationDBlockToken.split(",")
|
|
539
|
+
# remove the first one because it is already used as a location
|
|
540
|
+
if tmp_src_ddm == tmp_dst_ddm:
|
|
541
|
+
tmp_dst_tokens = tmp_dst_tokens[1:]
|
|
542
|
+
# loop over all tokens
|
|
543
|
+
for idx_token, tmp_dst_token in enumerate(tmp_dst_tokens):
|
|
544
|
+
ddm_id = tmp_dst_ddm
|
|
545
|
+
if tmp_dst_token in destination_se_site_spec.setokens_output[scope_dst_se_site_spec_output]:
|
|
546
|
+
ddm_id = destination_se_site_spec.setokens_output[scope_dst_se_site_spec_output][tmp_dst_token]
|
|
547
|
+
# keep the fist destination for multi-hop
|
|
548
|
+
if idx_token == 0:
|
|
549
|
+
first_destination_ddm = ddm_id
|
|
550
|
+
else:
|
|
551
|
+
# use the fist destination as source for T1D1
|
|
552
|
+
opt_source = {}
|
|
553
|
+
opt_source[first_destination_ddm] = {"policy": 0}
|
|
554
|
+
# remove looping subscription
|
|
555
|
+
if ddm_id == tmp_src_ddm:
|
|
556
|
+
continue
|
|
557
|
+
# avoid duplication
|
|
558
|
+
if ddm_id not in tmp_ddm_id_list:
|
|
559
|
+
sub_map[file_destination_dispatch_block].append((ddm_id, opt_sub, opt_source))
|
|
560
|
+
else:
|
|
561
|
+
# use default DDM
|
|
562
|
+
for ddm_id in tmp_dst_ddm.split(","):
|
|
563
|
+
sub_map[file_destination_dispatch_block].append((ddm_id, opt_sub, opt_source))
|
|
564
|
+
except Exception as e:
|
|
565
|
+
err_str = str(e)
|
|
566
|
+
self.logger.error(err_str + " " + traceback.format_exc())
|
|
567
|
+
self.result.set_fatal()
|
|
568
|
+
self.job.ddmErrorDiag = "failed before adding files : " + err_str
|
|
569
|
+
return 1
|
|
570
|
+
|
|
571
|
+
# release some memory
|
|
572
|
+
del ds_id_to_ds_map
|
|
573
|
+
gc.collect()
|
|
574
|
+
|
|
575
|
+
# zipping input files
|
|
576
|
+
if len(zip_file_map) > 0 and not self.add_to_top_only:
|
|
577
|
+
for file_spec in self.job.Files:
|
|
578
|
+
if file_spec.type != "input":
|
|
579
|
+
continue
|
|
580
|
+
|
|
581
|
+
zip_file_name = None
|
|
582
|
+
for tmp_zip_file_name in zip_file_map:
|
|
583
|
+
tmp_zip_contents = zip_file_map[tmp_zip_file_name]
|
|
584
|
+
for tmp_zip_content in tmp_zip_contents:
|
|
585
|
+
if re.search("^" + tmp_zip_content + "$", file_spec.lfn) is not None:
|
|
586
|
+
zip_file_name = tmp_zip_file_name
|
|
587
|
+
break
|
|
588
|
+
if zip_file_name:
|
|
589
|
+
break
|
|
590
|
+
if zip_file_name:
|
|
591
|
+
if zip_file_name not in zip_files:
|
|
592
|
+
continue
|
|
593
|
+
if "files" not in zip_files[zip_file_name]:
|
|
594
|
+
zip_files[zip_file_name]["files"] = []
|
|
595
|
+
file_attrs = {
|
|
596
|
+
"guid": file_spec.GUID,
|
|
597
|
+
"lfn": file_spec.lfn,
|
|
598
|
+
"size": file_spec.fsize,
|
|
599
|
+
"checksum": file_spec.checksum,
|
|
600
|
+
"ds": file_spec.dataset,
|
|
601
|
+
}
|
|
602
|
+
zip_files[zip_file_name]["files"].append(file_attrs)
|
|
603
|
+
|
|
604
|
+
# cleanup submap
|
|
605
|
+
sub_map = {k: v for k, v in sub_map.items() if v}
|
|
606
|
+
|
|
607
|
+
# add data to original dataset
|
|
608
|
+
for destination_dispatch_block in list(id_map):
|
|
609
|
+
orig_dispatch_block = None
|
|
610
|
+
match = re.search("^(.+)_sub\d+$", destination_dispatch_block)
|
|
611
|
+
if match:
|
|
612
|
+
# add files to top-level datasets
|
|
613
|
+
orig_dispatch_block = sub_to_ds_map[destination_dispatch_block]
|
|
614
|
+
if (not self.go_to_transferring) or (not self.add_to_top_only and destination_dispatch_block in dist_datasets):
|
|
615
|
+
id_map[orig_dispatch_block] = id_map[destination_dispatch_block]
|
|
616
|
+
# add files to top-level datasets only
|
|
617
|
+
if self.add_to_top_only or self.go_to_merging or (destination_dispatch_block in dist_datasets and orig_dispatch_block):
|
|
618
|
+
del id_map[destination_dispatch_block]
|
|
619
|
+
# skip sub unless getting transferred
|
|
620
|
+
if orig_dispatch_block:
|
|
621
|
+
if not self.go_to_transferring and not self.log_transferring and destination_dispatch_block in id_map:
|
|
622
|
+
del id_map[destination_dispatch_block]
|
|
623
|
+
|
|
624
|
+
# print idMap
|
|
625
|
+
self.logger.debug(f"dataset to files map = {id_map}")
|
|
626
|
+
self.logger.debug(f"transfer rules = {sub_map}")
|
|
627
|
+
self.logger.debug(f"dataset to destination map = {dataset_destination_map}")
|
|
628
|
+
self.logger.debug(f"extra info = {str(self.extra_info)}")
|
|
629
|
+
self.logger.debug(f"""alternatively staged files = {",".join(list(alt_staged_files)) if alt_staged_files else "NA"}""")
|
|
630
|
+
|
|
631
|
+
# check consistency of destinationDBlock
|
|
632
|
+
has_sub = False
|
|
633
|
+
for destination_dispatch_block in id_map:
|
|
634
|
+
match = re.search("^(.+)_sub\d+$", destination_dispatch_block)
|
|
635
|
+
if match is not None:
|
|
636
|
+
has_sub = True
|
|
637
|
+
break
|
|
638
|
+
if id_map and self.go_to_transferring and not has_sub:
|
|
639
|
+
err_str = "no sub datasets for transferring. destinationDBlock may be wrong"
|
|
640
|
+
self.logger.error(err_str)
|
|
641
|
+
self.result.set_fatal()
|
|
642
|
+
self.job.ddmErrorDiag = "failed before adding files : " + err_str
|
|
643
|
+
return 1
|
|
644
|
+
|
|
645
|
+
# add data
|
|
646
|
+
self.logger.debug("addFiles start")
|
|
647
|
+
# count the number of files
|
|
648
|
+
reg_file_list = {tmp_reg_item["lfn"] for tmp_reg_list in id_map.values() for tmp_reg_item in tmp_reg_list}
|
|
649
|
+
reg_num_files = len(reg_file_list)
|
|
650
|
+
|
|
651
|
+
# decompose idMap
|
|
652
|
+
if self.add_to_top_only:
|
|
653
|
+
dest_id_map = {None: id_map}
|
|
654
|
+
else:
|
|
655
|
+
dest_id_map = self.decompose_id_map(id_map, dataset_destination_map, map_for_alt_stage_out, sub_to_ds_map, alt_staged_files)
|
|
656
|
+
|
|
657
|
+
# add files
|
|
658
|
+
result = self.register_files(reg_num_files, zip_files, dest_id_map, cont_zip_map)
|
|
659
|
+
if result == 1:
|
|
660
|
+
return 1
|
|
661
|
+
|
|
662
|
+
# release some memory
|
|
663
|
+
del dest_id_map
|
|
664
|
+
del dataset_destination_map
|
|
665
|
+
del map_for_alt_stage_out
|
|
666
|
+
del zip_files
|
|
667
|
+
del cont_zip_map
|
|
668
|
+
gc.collect()
|
|
669
|
+
|
|
670
|
+
if self.job.processingType == "urgent" or self.job.currentPriority > 1000:
|
|
671
|
+
sub_activity = "Express"
|
|
672
|
+
else:
|
|
673
|
+
sub_activity = "Production Output"
|
|
674
|
+
|
|
675
|
+
# register dataset subscription
|
|
676
|
+
self.process_subscriptions(sub_map, sub_to_ds_map, dist_datasets, sub_activity)
|
|
677
|
+
|
|
678
|
+
# collect list of merging files
|
|
679
|
+
if self.go_to_merging and self.job_status not in ["failed", "cancelled", "closed"]:
|
|
680
|
+
for tmp_file_list in id_map.values():
|
|
681
|
+
for tmp_file in tmp_file_list:
|
|
682
|
+
if tmp_file["lfn"] not in self.result.merging_files:
|
|
683
|
+
self.result.merging_files.append(tmp_file["lfn"])
|
|
684
|
+
|
|
685
|
+
# register ES files
|
|
686
|
+
if (EventServiceUtils.isEventServiceJob(self.job) or EventServiceUtils.isJumboJob(self.job)) and not EventServiceUtils.isJobCloningJob(self.job):
|
|
687
|
+
if self.job.registerEsFiles():
|
|
688
|
+
try:
|
|
689
|
+
self.register_event_service_files()
|
|
690
|
+
except Exception:
|
|
691
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
692
|
+
self.logger.error(f"failed to register ES files with {err_type}:{err_value}")
|
|
693
|
+
self.result.set_temporary()
|
|
694
|
+
return 1
|
|
695
|
+
|
|
696
|
+
# properly finished
|
|
697
|
+
self.logger.debug("addFiles end")
|
|
698
|
+
return 0
|
|
699
|
+
|
|
700
|
+
def register_files(self, reg_num_files: int, zip_files: list, dest_id_map: dict, cont_zip_map: dict) -> int | None:
|
|
701
|
+
"""
|
|
702
|
+
Register files with Rucio.
|
|
703
|
+
|
|
704
|
+
:param reg_num_files: Number of files to register.
|
|
705
|
+
:param zip_files: List of zip files to register.
|
|
706
|
+
:param dest_id_map: Destination ID map.
|
|
707
|
+
:param cont_zip_map: Container zip map.
|
|
708
|
+
:return: 1 if registration fails, None otherwise.
|
|
709
|
+
"""
|
|
710
|
+
max_attempt = 3
|
|
711
|
+
for attempt_number in range(max_attempt):
|
|
712
|
+
is_fatal = False
|
|
713
|
+
is_failed = False
|
|
714
|
+
reg_start = naive_utcnow()
|
|
715
|
+
try:
|
|
716
|
+
if self.add_to_top_only:
|
|
717
|
+
reg_msg_str = f"File registration for {reg_num_files} files "
|
|
718
|
+
else:
|
|
719
|
+
reg_msg_str = f"File registration with rucio for {reg_num_files} files "
|
|
720
|
+
if len(zip_files) > 0:
|
|
721
|
+
self.logger.debug(f"registerZipFiles {str(zip_files)}")
|
|
722
|
+
rucioAPI.register_zip_files(zip_files)
|
|
723
|
+
self.logger.debug(f"registerFilesInDatasets {str(dest_id_map)} zip={str(cont_zip_map)}")
|
|
724
|
+
out = rucioAPI.register_files_in_dataset(dest_id_map, cont_zip_map)
|
|
725
|
+
except (
|
|
726
|
+
DataIdentifierNotFound,
|
|
727
|
+
FileConsistencyMismatch,
|
|
728
|
+
UnsupportedOperation,
|
|
729
|
+
InvalidPath,
|
|
730
|
+
InvalidObject,
|
|
731
|
+
RSENotFound,
|
|
732
|
+
RSEProtocolNotSupported,
|
|
733
|
+
InvalidRSEExpression,
|
|
734
|
+
KeyError,
|
|
735
|
+
):
|
|
736
|
+
# fatal errors
|
|
737
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
738
|
+
out = f"{err_type} : {err_value}"
|
|
739
|
+
out += traceback.format_exc()
|
|
740
|
+
is_fatal = True
|
|
741
|
+
is_failed = True
|
|
742
|
+
except Exception:
|
|
743
|
+
# unknown errors
|
|
744
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
745
|
+
out = f"{err_type} : {err_value}"
|
|
746
|
+
out += traceback.format_exc()
|
|
747
|
+
is_fatal = (
|
|
748
|
+
"value too large for column" in out
|
|
749
|
+
or "unique constraint (ATLAS_RUCIO.DIDS_GUID_IDX) violate" in out
|
|
750
|
+
or "unique constraint (ATLAS_RUCIO.DIDS_PK) violated" in out
|
|
751
|
+
or "unique constraint (ATLAS_RUCIO.ARCH_CONTENTS_PK) violated" in out
|
|
752
|
+
)
|
|
753
|
+
is_failed = True
|
|
754
|
+
reg_time = naive_utcnow() - reg_start
|
|
755
|
+
self.logger.debug(f"{reg_msg_str} took {reg_time.seconds}.{reg_time.microseconds // 1000:03d} sec")
|
|
756
|
+
# failed
|
|
757
|
+
if is_failed or is_fatal:
|
|
758
|
+
self.logger.error(f"{out}")
|
|
759
|
+
if (attempt_number + 1) == max_attempt or is_fatal:
|
|
760
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
761
|
+
# extract important error string
|
|
762
|
+
extracted_err_str = DataServiceUtils.extractImportantError(out)
|
|
763
|
+
err_msg = "Could not add files to DDM: "
|
|
764
|
+
if extracted_err_str == "":
|
|
765
|
+
self.job.ddmErrorDiag = err_msg + out.split("\n")[-1]
|
|
766
|
+
else:
|
|
767
|
+
self.job.ddmErrorDiag = err_msg + extracted_err_str
|
|
768
|
+
if is_fatal:
|
|
769
|
+
self.result.set_fatal()
|
|
770
|
+
else:
|
|
771
|
+
self.result.set_temporary()
|
|
772
|
+
return 1
|
|
773
|
+
self.logger.error(f"Try:{attempt_number}")
|
|
774
|
+
# sleep
|
|
775
|
+
time.sleep(10)
|
|
776
|
+
else:
|
|
777
|
+
self.logger.debug(f"{str(out)}")
|
|
778
|
+
break
|
|
779
|
+
|
|
780
|
+
def process_subscriptions(self, sub_map: Dict[str, str], sub_to_ds_map: Dict[str, List[str]], dist_datasets: List[str], sub_activity: str):
|
|
781
|
+
"""
|
|
782
|
+
Process the subscriptions for the job.
|
|
783
|
+
|
|
784
|
+
This method handles the processing of subscriptions, including keeping subscriptions,
|
|
785
|
+
collecting transferring jobs, and sending requests to DaTRI.
|
|
786
|
+
|
|
787
|
+
:param sub_map: A dictionary mapping subscription names to their values.
|
|
788
|
+
:param sub_to_ds_map: A dictionary mapping subscriptions to datasets.
|
|
789
|
+
:param dist_datasets: A list of distributed datasets.
|
|
790
|
+
:param sub_activity: The subscription activity type.
|
|
791
|
+
"""
|
|
792
|
+
if self.job.prodSourceLabel not in ["user"]:
|
|
793
|
+
for tmp_name, tmp_val in sub_map.items():
|
|
794
|
+
for ddm_id, opt_sub, opt_source in tmp_val:
|
|
795
|
+
if not self.go_to_merging:
|
|
796
|
+
# make subscription for prod jobs
|
|
797
|
+
rep_life_time = 14
|
|
798
|
+
self.logger.debug(
|
|
799
|
+
f"registerDatasetSubscription {tmp_name} {ddm_id} {{'activity': {sub_activity}, 'replica_lifetime': {rep_life_time}}}"
|
|
800
|
+
)
|
|
801
|
+
for _ in range(3):
|
|
802
|
+
is_failed = False
|
|
803
|
+
try:
|
|
804
|
+
status = rucioAPI.register_dataset_subscription(
|
|
805
|
+
tmp_name,
|
|
806
|
+
[ddm_id],
|
|
807
|
+
owner="panda",
|
|
808
|
+
activity=sub_activity,
|
|
809
|
+
lifetime=rep_life_time,
|
|
810
|
+
)
|
|
811
|
+
out = "OK"
|
|
812
|
+
break
|
|
813
|
+
except InvalidRSEExpression:
|
|
814
|
+
status = False
|
|
815
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
816
|
+
out = f"{err_type} {err_value}"
|
|
817
|
+
is_failed = True
|
|
818
|
+
self.job.ddmErrorCode = ErrorCode.EC_Subscription
|
|
819
|
+
break
|
|
820
|
+
except Exception:
|
|
821
|
+
status = False
|
|
822
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
823
|
+
out = f"{err_type} {err_value}"
|
|
824
|
+
is_failed = True
|
|
825
|
+
# retry for temporary errors
|
|
826
|
+
time.sleep(10)
|
|
827
|
+
if is_failed:
|
|
828
|
+
self.logger.error(f"{out}")
|
|
829
|
+
if self.job.ddmErrorCode == ErrorCode.EC_Subscription:
|
|
830
|
+
# fatal error
|
|
831
|
+
self.job.ddmErrorDiag = f"subscription failure with {out}"
|
|
832
|
+
self.result.set_fatal()
|
|
833
|
+
else:
|
|
834
|
+
# temporary errors
|
|
835
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
836
|
+
self.job.ddmErrorDiag = f"could not register subscription : {tmp_name}"
|
|
837
|
+
self.result.set_temporary()
|
|
838
|
+
return 1
|
|
839
|
+
self.logger.debug(f"{str(out)}")
|
|
840
|
+
else:
|
|
841
|
+
# register location
|
|
842
|
+
tmp_dataset_name_location = sub_to_ds_map[tmp_name]
|
|
843
|
+
rep_life_time = 14
|
|
844
|
+
for tmp_location_name in opt_source:
|
|
845
|
+
self.logger.debug(f"registerDatasetLocation {tmp_dataset_name_location} {tmp_location_name} {{'lifetime': '14 days'}}")
|
|
846
|
+
|
|
847
|
+
for _ in range(3):
|
|
848
|
+
is_failed = False
|
|
849
|
+
try:
|
|
850
|
+
rucioAPI.register_dataset_location(
|
|
851
|
+
tmp_dataset_name_location,
|
|
852
|
+
[tmp_location_name],
|
|
853
|
+
owner="panda",
|
|
854
|
+
activity=sub_activity,
|
|
855
|
+
lifetime=rep_life_time,
|
|
856
|
+
)
|
|
857
|
+
out = "OK"
|
|
858
|
+
break
|
|
859
|
+
except Exception:
|
|
860
|
+
status = False
|
|
861
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
862
|
+
out = f"{err_type} {err_value}"
|
|
863
|
+
is_failed = True
|
|
864
|
+
# retry for temporary errors
|
|
865
|
+
time.sleep(10)
|
|
866
|
+
if is_failed:
|
|
867
|
+
self.logger.error(f"{out}")
|
|
868
|
+
if self.job.ddmErrorCode == ErrorCode.EC_Location:
|
|
869
|
+
# fatal error
|
|
870
|
+
self.job.ddmErrorDiag = f"location registration failure with {out}"
|
|
871
|
+
self.result.set_fatal()
|
|
872
|
+
else:
|
|
873
|
+
# temporary errors
|
|
874
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
875
|
+
self.job.ddmErrorDiag = f"could not register location : {tmp_dataset_name_location}"
|
|
876
|
+
self.result.set_temporary()
|
|
877
|
+
return 1
|
|
878
|
+
self.logger.debug(f"{str(out)}")
|
|
879
|
+
|
|
880
|
+
# set dataset status
|
|
881
|
+
self.dataset_map[tmp_name].status = "running"
|
|
882
|
+
|
|
883
|
+
# keep subscriptions
|
|
884
|
+
self.subscription_map = sub_map
|
|
885
|
+
|
|
886
|
+
# collect list of transferring jobs
|
|
887
|
+
for tmp_file in self.job.Files:
|
|
888
|
+
if tmp_file.type in ["log", "output"]:
|
|
889
|
+
if self.go_to_transferring or (self.log_transferring and tmp_file.type == "log"):
|
|
890
|
+
# don't go to transferring for successful ES jobs
|
|
891
|
+
if (
|
|
892
|
+
self.job.jobStatus == "finished"
|
|
893
|
+
and (EventServiceUtils.isEventServiceJob(self.job) and EventServiceUtils.isJumboJob(self.job))
|
|
894
|
+
and not EventServiceUtils.isJobCloningJob(self.job)
|
|
895
|
+
):
|
|
896
|
+
continue
|
|
897
|
+
# skip distributed datasets
|
|
898
|
+
if tmp_file.destinationDBlock in dist_datasets:
|
|
899
|
+
continue
|
|
900
|
+
# skip no output
|
|
901
|
+
if tmp_file.status == "nooutput":
|
|
902
|
+
continue
|
|
903
|
+
# skip alternative stage-out
|
|
904
|
+
if tmp_file.lfn in self.job.altStgOutFileList():
|
|
905
|
+
continue
|
|
906
|
+
self.result.transferring_files.append(tmp_file.lfn)
|
|
907
|
+
|
|
908
|
+
elif "--mergeOutput" not in self.job.jobParameters:
|
|
909
|
+
# send request to DaTRI unless files will be merged
|
|
910
|
+
tmp_top_datasets = {}
|
|
911
|
+
# collect top-level datasets
|
|
912
|
+
for tmp_name, tmp_val in sub_map.items():
|
|
913
|
+
for ddm_id, opt_sub, opt_source in tmp_val:
|
|
914
|
+
tmp_top_name = sub_to_ds_map[tmp_name]
|
|
915
|
+
# append
|
|
916
|
+
if tmp_top_name not in tmp_top_datasets:
|
|
917
|
+
tmp_top_datasets[tmp_top_name] = []
|
|
918
|
+
if ddm_id not in tmp_top_datasets[tmp_top_name]:
|
|
919
|
+
tmp_top_datasets[tmp_top_name].append(ddm_id)
|
|
920
|
+
# remove redundant CN from DN
|
|
921
|
+
tmp_dn = self.job.prodUserID
|
|
922
|
+
# send request
|
|
923
|
+
if tmp_top_datasets and self.job_status == "finished":
|
|
924
|
+
try:
|
|
925
|
+
status, user_info = rucioAPI.finger(tmp_dn)
|
|
926
|
+
if not status:
|
|
927
|
+
raise RuntimeError(f"user info not found for {tmp_dn} with {user_info}")
|
|
928
|
+
user_endpoints = []
|
|
929
|
+
# loop over all output datasets
|
|
930
|
+
for tmp_dataset_name, ddm_id_list in tmp_top_datasets.items():
|
|
931
|
+
for tmp_ddm_id in ddm_id_list:
|
|
932
|
+
if tmp_ddm_id == "NULL":
|
|
933
|
+
continue
|
|
934
|
+
if tmp_ddm_id not in user_endpoints:
|
|
935
|
+
user_endpoints.append(tmp_ddm_id)
|
|
936
|
+
# use group account for group.*
|
|
937
|
+
if tmp_dataset_name.startswith("group") and self.job.workingGroup not in ["", "NULL", None]:
|
|
938
|
+
tmp_dn = self.job.workingGroup
|
|
939
|
+
else:
|
|
940
|
+
tmp_dn = user_info["nickname"]
|
|
941
|
+
tmp_msg = f"registerDatasetLocation for Rucio ds={tmp_dataset_name} site={tmp_ddm_id} id={tmp_dn}"
|
|
942
|
+
self.logger.debug(tmp_msg)
|
|
943
|
+
rucioAPI.register_dataset_location(
|
|
944
|
+
tmp_dataset_name,
|
|
945
|
+
[tmp_ddm_id],
|
|
946
|
+
owner=tmp_dn,
|
|
947
|
+
activity="Analysis Output",
|
|
948
|
+
)
|
|
949
|
+
# set dataset status
|
|
950
|
+
for tmp_name in sub_map:
|
|
951
|
+
self.dataset_map[tmp_name].status = "running"
|
|
952
|
+
except (InsufficientAccountLimit, InvalidRSEExpression) as err_type:
|
|
953
|
+
tmp_msg = f"Rucio rejected to transfer files to {','.join(user_endpoints)} since {err_type}"
|
|
954
|
+
self.logger.error(tmp_msg)
|
|
955
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
956
|
+
self.job.ddmErrorDiag = f"Rucio failed with {err_type}"
|
|
957
|
+
# set dataset status
|
|
958
|
+
for tmp_name in sub_map:
|
|
959
|
+
self.dataset_map[tmp_name].status = "running"
|
|
960
|
+
# send warning
|
|
961
|
+
tmp_st = self.taskBuffer.update_problematic_resource_info(self.job.prodUserName, self.job.jediTaskID, user_endpoints[0], "dest")
|
|
962
|
+
if not tmp_st:
|
|
963
|
+
self.logger.debug("skip to send warning since already done")
|
|
964
|
+
else:
|
|
965
|
+
to_adder = self.taskBuffer.getEmailAddr(self.job.prodUserName)
|
|
966
|
+
if to_adder is None or to_adder.startswith("notsend"):
|
|
967
|
+
self.logger.debug("skip to send warning since suppressed")
|
|
968
|
+
else:
|
|
969
|
+
tmp_sm = self.send_email(to_adder, tmp_msg, self.job.jediTaskID)
|
|
970
|
+
self.logger.debug(f"sent warning with {tmp_sm}")
|
|
971
|
+
except Exception:
|
|
972
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
973
|
+
tmp_msg = f"registerDatasetLocation failed with {err_type} {err_value}"
|
|
974
|
+
self.logger.error(tmp_msg)
|
|
975
|
+
self.job.ddmErrorCode = ErrorCode.EC_Adder
|
|
976
|
+
self.job.ddmErrorDiag = f"Rucio failed with {err_type} {err_value}"
|
|
977
|
+
|
|
978
|
+
# decompose idMap
|
|
979
|
+
def decompose_id_map(self, id_map, dataset_destination_map, map_for_alt_stage_out, sub_to_dataset_map, alt_staged_files):
|
|
980
|
+
"""
|
|
981
|
+
Decompose the idMap into a structure suitable for file registration.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
id_map (dict): A dictionary mapping dataset names to file attributes.
|
|
985
|
+
dataset_destination_map (dict): A dictionary mapping dataset names to destination sites.
|
|
986
|
+
map_for_alt_stage_out (dict): A dictionary mapping alternative destination sites to file attributes.
|
|
987
|
+
sub_to_dataset_map (dict): A dictionary mapping sub-dataset names to top-level dataset names.
|
|
988
|
+
alt_staged_files (set): A set of files uploaded with alternative stage-out.
|
|
989
|
+
|
|
990
|
+
Returns:
|
|
991
|
+
dict: A decomposed idMap suitable for file registration.
|
|
992
|
+
"""
|
|
993
|
+
# add item for top datasets
|
|
994
|
+
for tmp_dataset in list(dataset_destination_map):
|
|
995
|
+
tmp_top_dataset = sub_to_dataset_map[tmp_dataset]
|
|
996
|
+
if tmp_top_dataset != tmp_dataset:
|
|
997
|
+
dataset_destination_map[tmp_top_dataset] = dataset_destination_map[tmp_dataset]
|
|
998
|
+
|
|
999
|
+
destination_id_map = {}
|
|
1000
|
+
for tmp_dataset in id_map:
|
|
1001
|
+
# exclude files uploaded with alternative stage-out
|
|
1002
|
+
tmp_files = [f for f in id_map[tmp_dataset] if f["lfn"] not in alt_staged_files]
|
|
1003
|
+
if not tmp_files:
|
|
1004
|
+
continue
|
|
1005
|
+
for tmp_dest in dataset_destination_map[tmp_dataset]:
|
|
1006
|
+
if tmp_dest not in destination_id_map:
|
|
1007
|
+
destination_id_map[tmp_dest] = {}
|
|
1008
|
+
destination_id_map[tmp_dest][tmp_dataset] = tmp_files
|
|
1009
|
+
|
|
1010
|
+
# add alternative stage-out info
|
|
1011
|
+
for tmp_dest in map_for_alt_stage_out:
|
|
1012
|
+
tmp_id_map = map_for_alt_stage_out[tmp_dest]
|
|
1013
|
+
for tmp_dataset in tmp_id_map:
|
|
1014
|
+
tmp_files = tmp_id_map[tmp_dataset]
|
|
1015
|
+
if tmp_dest not in destination_id_map:
|
|
1016
|
+
destination_id_map[tmp_dest] = {}
|
|
1017
|
+
destination_id_map[tmp_dest][tmp_dataset] = tmp_files
|
|
1018
|
+
return destination_id_map
|
|
1019
|
+
|
|
1020
|
+
# send email notification
|
|
1021
|
+
def send_email(self, to_adder, message, jedi_task_id):
|
|
1022
|
+
"""
|
|
1023
|
+
Send an email notification.
|
|
1024
|
+
|
|
1025
|
+
Args:
|
|
1026
|
+
to_adder (str): The email address to send the notification to.
|
|
1027
|
+
message (str): The message body of the email.
|
|
1028
|
+
jedi_task_id (int): The JEDI task ID associated with the email.
|
|
1029
|
+
|
|
1030
|
+
Returns:
|
|
1031
|
+
bool: True if the email was sent successfully, False otherwise.
|
|
1032
|
+
"""
|
|
1033
|
+
# subject
|
|
1034
|
+
mail_subject = f"PANDA WARNING for TaskID:{jedi_task_id} with --destSE"
|
|
1035
|
+
# message
|
|
1036
|
+
mail_body = f"Hello,\n\nTaskID:{jedi_task_id} cannot process the --destSE request\n\n"
|
|
1037
|
+
mail_body += f"Reason : {message}\n"
|
|
1038
|
+
# send
|
|
1039
|
+
ret_val = MailUtils().send(to_adder, mail_subject, mail_body)
|
|
1040
|
+
# return
|
|
1041
|
+
return ret_val
|
|
1042
|
+
|
|
1043
|
+
# register ES files
|
|
1044
|
+
def register_event_service_files(self) -> None:
|
|
1045
|
+
"""
|
|
1046
|
+
Register Event Service (ES) files with Rucio.
|
|
1047
|
+
|
|
1048
|
+
Raises:
|
|
1049
|
+
Exception: If there is an error during the registration process.
|
|
1050
|
+
"""
|
|
1051
|
+
self.logger.debug("registering ES files")
|
|
1052
|
+
try:
|
|
1053
|
+
# get ES dataset name
|
|
1054
|
+
event_service_dataset = EventServiceUtils.getEsDatasetName(self.job.jediTaskID)
|
|
1055
|
+
# collect files
|
|
1056
|
+
id_map = {}
|
|
1057
|
+
file_set = set()
|
|
1058
|
+
for file_spec in self.job.Files:
|
|
1059
|
+
if file_spec.type != "zipoutput":
|
|
1060
|
+
continue
|
|
1061
|
+
if file_spec.lfn in file_set:
|
|
1062
|
+
continue
|
|
1063
|
+
file_set.add(file_spec.lfn)
|
|
1064
|
+
# make file data
|
|
1065
|
+
file_data = {
|
|
1066
|
+
"scope": EventServiceUtils.esScopeDDM,
|
|
1067
|
+
"name": file_spec.lfn,
|
|
1068
|
+
"bytes": file_spec.fsize,
|
|
1069
|
+
"panda_id": file_spec.PandaID,
|
|
1070
|
+
"task_id": file_spec.jediTaskID,
|
|
1071
|
+
}
|
|
1072
|
+
if file_spec.GUID not in [None, "NULL", ""]:
|
|
1073
|
+
file_data["guid"] = file_spec.GUID
|
|
1074
|
+
if file_spec.dispatchDBlockToken not in [None, "NULL", ""]:
|
|
1075
|
+
try:
|
|
1076
|
+
file_data["events"] = int(file_spec.dispatchDBlockToken)
|
|
1077
|
+
except Exception:
|
|
1078
|
+
pass
|
|
1079
|
+
if file_spec.checksum not in [None, "NULL", ""]:
|
|
1080
|
+
file_data["checksum"] = file_spec.checksum
|
|
1081
|
+
# get endpoint ID
|
|
1082
|
+
endpoint_id = int(file_spec.destinationSE.split("/")[0])
|
|
1083
|
+
# convert to DDM endpoint
|
|
1084
|
+
rse = self.taskBuffer.convertObjIDtoEndPoint(panda_config.endpoint_mapfile, endpoint_id)
|
|
1085
|
+
if rse is not None and rse["is_deterministic"]:
|
|
1086
|
+
endpoint_name = rse["name"]
|
|
1087
|
+
if endpoint_name not in id_map:
|
|
1088
|
+
id_map[endpoint_name] = {}
|
|
1089
|
+
if event_service_dataset not in id_map[endpoint_name]:
|
|
1090
|
+
id_map[endpoint_name][event_service_dataset] = []
|
|
1091
|
+
id_map[endpoint_name][event_service_dataset].append(file_data)
|
|
1092
|
+
|
|
1093
|
+
# add files to dataset
|
|
1094
|
+
if id_map:
|
|
1095
|
+
self.logger.debug(f"adding ES files {str(id_map)}")
|
|
1096
|
+
try:
|
|
1097
|
+
rucioAPI.register_files_in_dataset(id_map)
|
|
1098
|
+
except DataIdentifierNotFound:
|
|
1099
|
+
self.logger.debug("ignored DataIdentifierNotFound")
|
|
1100
|
+
except Exception:
|
|
1101
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
1102
|
+
err_str = f" : {err_type} {err_value}"
|
|
1103
|
+
err_str += traceback.format_exc()
|
|
1104
|
+
self.logger.error(err_str)
|
|
1105
|
+
raise
|
|
1106
|
+
self.logger.debug("done")
|