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,1851 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is designed to setup datasets for the ATLAS experiment.
|
|
3
|
+
The setup process involves preparing the necessary data and resources for the ATLAS jobs to run.
|
|
4
|
+
This includes tasks such as data placement, dataset creation, and job configuration.
|
|
5
|
+
The 'SetupperAtlasPlugin' class in this module inherits from the 'SetupperPluginBase' class and overrides its methods to provide ATLAS-specific functionality.
|
|
6
|
+
The 'setupper.py' module uses this plugin when setting up datasets for ATLAS jobs.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import datetime
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
import traceback
|
|
16
|
+
import uuid
|
|
17
|
+
from typing import Dict, List, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
20
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
21
|
+
from rucio.common.exception import DataIdentifierNotFound
|
|
22
|
+
|
|
23
|
+
import pandaserver.brokerage.broker
|
|
24
|
+
from pandaserver.brokerage.SiteMapper import SiteMapper
|
|
25
|
+
from pandaserver.config import panda_config
|
|
26
|
+
from pandaserver.dataservice import DataServiceUtils, ErrorCode
|
|
27
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
28
|
+
from pandaserver.dataservice.ddm import rucioAPI
|
|
29
|
+
from pandaserver.dataservice.setupper_plugin_base import SetupperPluginBase
|
|
30
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils
|
|
31
|
+
from pandaserver.taskbuffer.DatasetSpec import DatasetSpec
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SetupperAtlasPlugin(SetupperPluginBase):
|
|
35
|
+
"""
|
|
36
|
+
This class is a plugin for setting up datasets specifically for the ATLAS experiment.
|
|
37
|
+
It inherits from the SetupperPluginBase class and overrides its methods to provide
|
|
38
|
+
ATLAS-specific functionality.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# constructor
|
|
42
|
+
def __init__(self, taskBuffer, jobs: List, logger, **params: Dict) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Constructor for the SetupperAtlasPlugin class.
|
|
45
|
+
|
|
46
|
+
:param taskBuffer: The buffer for tasks.
|
|
47
|
+
:param jobs: The jobs to be processed.
|
|
48
|
+
:param logger: The logger to be used for logging.
|
|
49
|
+
:param params: Additional parameters.
|
|
50
|
+
"""
|
|
51
|
+
# defaults
|
|
52
|
+
default_map = {
|
|
53
|
+
"resubmit": False,
|
|
54
|
+
}
|
|
55
|
+
SetupperPluginBase.__init__(self, taskBuffer, jobs, logger, params, default_map)
|
|
56
|
+
# VUIDs of dispatchDBlocks
|
|
57
|
+
self.vuid_map = {}
|
|
58
|
+
# file list for dispDS for PandaDDM
|
|
59
|
+
self.disp_file_list = {}
|
|
60
|
+
# site mapper
|
|
61
|
+
self.site_mapper = None
|
|
62
|
+
# available files at satellite sites
|
|
63
|
+
self.available_lfns_in_satellites = {}
|
|
64
|
+
# list of missing datasets
|
|
65
|
+
self.missing_dataset_list = {}
|
|
66
|
+
# lfn ds map
|
|
67
|
+
self.lfn_dataset_map = {}
|
|
68
|
+
# source label
|
|
69
|
+
self.prod_source_label = None
|
|
70
|
+
self.job_label = None
|
|
71
|
+
|
|
72
|
+
# main
|
|
73
|
+
def run(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Main method for running the setup process.
|
|
76
|
+
"""
|
|
77
|
+
tmp_logger = LogWrapper(self.logger, "<run>")
|
|
78
|
+
try:
|
|
79
|
+
tmp_logger.debug("start")
|
|
80
|
+
self.memory_check()
|
|
81
|
+
bunch_tag = ""
|
|
82
|
+
tag_job = None
|
|
83
|
+
time_start = naive_utcnow()
|
|
84
|
+
if self.jobs is not None and len(self.jobs) > 0:
|
|
85
|
+
tag_job = self.jobs[0]
|
|
86
|
+
elif len(self.jumbo_jobs) > 0:
|
|
87
|
+
tag_job = self.jumbo_jobs[0]
|
|
88
|
+
if tag_job is not None:
|
|
89
|
+
bunch_tag = f"PandaID:{tag_job.PandaID} type:{tag_job.prodSourceLabel} taskID:{tag_job.taskID} pType={tag_job.processingType}"
|
|
90
|
+
tmp_logger.debug(bunch_tag)
|
|
91
|
+
self.prod_source_label = tag_job.prodSourceLabel
|
|
92
|
+
self.job_label = tag_job.job_label
|
|
93
|
+
# instantiate site mapper
|
|
94
|
+
self.site_mapper = SiteMapper(self.task_buffer)
|
|
95
|
+
# correctLFN
|
|
96
|
+
self.correct_lfn()
|
|
97
|
+
# run full Setupper
|
|
98
|
+
# invoke brokerage
|
|
99
|
+
tmp_logger.debug("running broker.schedule")
|
|
100
|
+
self.memory_check()
|
|
101
|
+
pandaserver.brokerage.broker.schedule(self.jobs, self.site_mapper)
|
|
102
|
+
|
|
103
|
+
# remove waiting jobs
|
|
104
|
+
self.remove_waiting_jobs()
|
|
105
|
+
|
|
106
|
+
# setup dispatch dataset
|
|
107
|
+
tmp_logger.debug("running setup_source")
|
|
108
|
+
self.memory_check()
|
|
109
|
+
self.setup_source()
|
|
110
|
+
self.memory_check()
|
|
111
|
+
|
|
112
|
+
# sort by site so that larger sub datasets are created in the next step
|
|
113
|
+
if self.jobs != [] and self.jobs[0].prodSourceLabel in [
|
|
114
|
+
"managed",
|
|
115
|
+
"test",
|
|
116
|
+
]:
|
|
117
|
+
tmp_job_map = {}
|
|
118
|
+
for tmp_job in self.jobs:
|
|
119
|
+
# add site
|
|
120
|
+
if tmp_job.computingSite not in tmp_job_map:
|
|
121
|
+
tmp_job_map[tmp_job.computingSite] = []
|
|
122
|
+
# add job
|
|
123
|
+
tmp_job_map[tmp_job.computingSite].append(tmp_job)
|
|
124
|
+
# make new list
|
|
125
|
+
tmp_job_list = []
|
|
126
|
+
for jobs in tmp_job_map.values():
|
|
127
|
+
tmp_job_list += jobs
|
|
128
|
+
# set new list
|
|
129
|
+
self.jobs = tmp_job_list
|
|
130
|
+
# create dataset for outputs and assign destination
|
|
131
|
+
if self.jobs != [] and self.jobs[0].prodSourceLabel in [
|
|
132
|
+
"managed",
|
|
133
|
+
"test",
|
|
134
|
+
]:
|
|
135
|
+
# count the number of jobs per _dis
|
|
136
|
+
i_bunch = 0
|
|
137
|
+
prev_dis_ds_name = None
|
|
138
|
+
n_jobs_per_dis_list = []
|
|
139
|
+
for tmp_job in self.jobs:
|
|
140
|
+
if prev_dis_ds_name is not None and prev_dis_ds_name != tmp_job.dispatchDBlock:
|
|
141
|
+
n_jobs_per_dis_list.append(i_bunch)
|
|
142
|
+
i_bunch = 0
|
|
143
|
+
# increment
|
|
144
|
+
i_bunch += 1
|
|
145
|
+
# set _dis name
|
|
146
|
+
prev_dis_ds_name = tmp_job.dispatchDBlock
|
|
147
|
+
# remaining
|
|
148
|
+
if i_bunch != 0:
|
|
149
|
+
n_jobs_per_dis_list.append(i_bunch)
|
|
150
|
+
# split sub datasets
|
|
151
|
+
i_bunch = 0
|
|
152
|
+
n_bunch_max = 50
|
|
153
|
+
tmp_index_job = 0
|
|
154
|
+
for n_jobs_per_dis in n_jobs_per_dis_list:
|
|
155
|
+
# check _dis boundary so that the same _dis doesn't contribute to many _subs
|
|
156
|
+
if i_bunch + n_jobs_per_dis > n_bunch_max:
|
|
157
|
+
if i_bunch != 0:
|
|
158
|
+
self.setup_destination(start_idx=tmp_index_job, n_jobs_in_loop=i_bunch)
|
|
159
|
+
tmp_index_job += i_bunch
|
|
160
|
+
i_bunch = 0
|
|
161
|
+
# increment
|
|
162
|
+
i_bunch += n_jobs_per_dis
|
|
163
|
+
# remaining
|
|
164
|
+
if i_bunch != 0:
|
|
165
|
+
self.setup_destination(start_idx=tmp_index_job, n_jobs_in_loop=i_bunch)
|
|
166
|
+
else:
|
|
167
|
+
# make one sub dataset per job so that each job doesn't have to wait for others to be done
|
|
168
|
+
# Special jobs for SW installation and HC seem to be above 6000
|
|
169
|
+
if self.jobs != [] and self.jobs[0].prodSourceLabel in ["user", "panda"] and self.jobs[-1].currentPriority > 6000:
|
|
170
|
+
for i_bunch in range(len(self.jobs)):
|
|
171
|
+
self.setup_destination(start_idx=i_bunch, n_jobs_in_loop=1)
|
|
172
|
+
else:
|
|
173
|
+
# at a burst
|
|
174
|
+
self.setup_destination()
|
|
175
|
+
# make dis datasets for existing files
|
|
176
|
+
self.memory_check()
|
|
177
|
+
self.make_dis_datasets_for_existing_files()
|
|
178
|
+
self.memory_check()
|
|
179
|
+
# setup jumbo jobs
|
|
180
|
+
self.setup_jumbo_jobs()
|
|
181
|
+
self.memory_check()
|
|
182
|
+
reg_time = naive_utcnow() - time_start
|
|
183
|
+
tmp_logger.debug(f"{bunch_tag} took {reg_time.seconds}sec")
|
|
184
|
+
tmp_logger.debug("end")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
err_str = f"setupper.run failed with {str(e)}"
|
|
187
|
+
err_str.strip()
|
|
188
|
+
for job in self.jobs:
|
|
189
|
+
if job.jobStatus not in ["failed", "cancelled"]:
|
|
190
|
+
job.jobStatus = "failed"
|
|
191
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
192
|
+
job.ddmErrorDiag = err_str
|
|
193
|
+
err_str += traceback.format_exc()
|
|
194
|
+
tmp_logger.error(err_str)
|
|
195
|
+
|
|
196
|
+
# post run
|
|
197
|
+
def post_run(self) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Post run method for running the setup process.
|
|
200
|
+
"""
|
|
201
|
+
tmp_logger = LogWrapper(self.logger, "<post_run>")
|
|
202
|
+
try:
|
|
203
|
+
tmp_logger.debug("start")
|
|
204
|
+
self.memory_check()
|
|
205
|
+
# subscribe sites dispatchDBlocks. this must be the last method
|
|
206
|
+
tmp_logger.debug("running subscribe_dispatch_data_block")
|
|
207
|
+
self.subscribe_dispatch_data_block()
|
|
208
|
+
|
|
209
|
+
# dynamic data placement for analysis jobs
|
|
210
|
+
self.memory_check()
|
|
211
|
+
tmp_logger.debug("running dynamic_data_placement")
|
|
212
|
+
self.dynamic_data_placement()
|
|
213
|
+
self.memory_check()
|
|
214
|
+
tmp_logger.debug("end")
|
|
215
|
+
except Exception:
|
|
216
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
217
|
+
tmp_logger.error(f"{error_type} {error_value}")
|
|
218
|
+
|
|
219
|
+
# make dispatchDBlocks, insert prod/dispatchDBlock to database
|
|
220
|
+
def setup_source(self) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Make dispatchDBlocks, insert prod/dispatchDBlock to database
|
|
223
|
+
Transfer input to satellite
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
tmp_logger = LogWrapper(self.logger, "<setup_source>")
|
|
227
|
+
|
|
228
|
+
file_list = {}
|
|
229
|
+
prod_list = []
|
|
230
|
+
prod_error = {}
|
|
231
|
+
disp_error = {}
|
|
232
|
+
back_end_map = {}
|
|
233
|
+
ds_task_map = dict()
|
|
234
|
+
jedi_task_id = None
|
|
235
|
+
# special datasets in rucio where files are zipped into a file
|
|
236
|
+
use_zip_to_pin_map = dict()
|
|
237
|
+
# extract prodDBlock
|
|
238
|
+
for job in self.jobs:
|
|
239
|
+
# ignore failed jobs
|
|
240
|
+
if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
|
|
241
|
+
continue
|
|
242
|
+
if jedi_task_id is None and job.jediTaskID not in ["NULL", None, 0]:
|
|
243
|
+
jedi_task_id = job.jediTaskID
|
|
244
|
+
# production datablock
|
|
245
|
+
if job.prodDBlock != "NULL" and job.prodDBlock and (job.prodSourceLabel not in ["user", "panda"]):
|
|
246
|
+
# get VUID and record prodDBlock into DB
|
|
247
|
+
if job.prodDBlock not in prod_error:
|
|
248
|
+
tmp_logger.debug(f"list_datasets {job.prodDBlock}")
|
|
249
|
+
prod_error[job.prodDBlock] = ""
|
|
250
|
+
new_out = None
|
|
251
|
+
for _ in range(3):
|
|
252
|
+
new_out, err_msg = rucioAPI.list_datasets(job.prodDBlock)
|
|
253
|
+
if new_out is None:
|
|
254
|
+
time.sleep(10)
|
|
255
|
+
else:
|
|
256
|
+
break
|
|
257
|
+
if new_out is None:
|
|
258
|
+
prod_error[job.prodDBlock] = f"setupper.setup_source() could not get VUID of prodDBlock with {err_msg}"
|
|
259
|
+
tmp_logger.error(prod_error[job.prodDBlock])
|
|
260
|
+
else:
|
|
261
|
+
tmp_logger.debug(new_out)
|
|
262
|
+
try:
|
|
263
|
+
vuids = new_out[job.prodDBlock]["vuids"]
|
|
264
|
+
n_files = 0
|
|
265
|
+
# dataset spec
|
|
266
|
+
dataset = DatasetSpec()
|
|
267
|
+
dataset.vuid = vuids[0]
|
|
268
|
+
dataset.name = job.prodDBlock
|
|
269
|
+
dataset.type = "input"
|
|
270
|
+
dataset.status = "completed"
|
|
271
|
+
dataset.numberfiles = n_files
|
|
272
|
+
dataset.currentfiles = n_files
|
|
273
|
+
prod_list.append(dataset)
|
|
274
|
+
except Exception:
|
|
275
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
276
|
+
tmp_logger.error(f"{error_type} {error_value}")
|
|
277
|
+
prod_error[job.prodDBlock] = "setupper.setup_source() could not decode VUID of prodDBlock"
|
|
278
|
+
# error
|
|
279
|
+
if prod_error[job.prodDBlock] != "":
|
|
280
|
+
if job.jobStatus != "failed":
|
|
281
|
+
job.jobStatus = "failed"
|
|
282
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
283
|
+
job.ddmErrorDiag = prod_error[job.prodDBlock]
|
|
284
|
+
tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
# dispatch datablock
|
|
288
|
+
if job.dispatchDBlock != "NULL":
|
|
289
|
+
# useZipToPin mapping
|
|
290
|
+
use_zip_to_pin_map[job.dispatchDBlock] = job.useZipToPin()
|
|
291
|
+
|
|
292
|
+
# filelist
|
|
293
|
+
if job.dispatchDBlock not in file_list:
|
|
294
|
+
file_list[job.dispatchDBlock] = {
|
|
295
|
+
"lfns": [],
|
|
296
|
+
"guids": [],
|
|
297
|
+
"fsizes": [],
|
|
298
|
+
"md5sums": [],
|
|
299
|
+
"chksums": [],
|
|
300
|
+
}
|
|
301
|
+
ds_task_map[job.dispatchDBlock] = job.jediTaskID
|
|
302
|
+
# DDM backend
|
|
303
|
+
if job.dispatchDBlock not in back_end_map:
|
|
304
|
+
back_end_map[job.dispatchDBlock] = "rucio"
|
|
305
|
+
# collect LFN and GUID
|
|
306
|
+
for file in job.Files:
|
|
307
|
+
if file.type == "input" and file.status == "pending":
|
|
308
|
+
if back_end_map[job.dispatchDBlock] != "rucio":
|
|
309
|
+
tmp_lfn = file.lfn
|
|
310
|
+
else:
|
|
311
|
+
tmp_lfn = f"{file.scope}:{file.lfn}"
|
|
312
|
+
if tmp_lfn not in file_list[job.dispatchDBlock]["lfns"]:
|
|
313
|
+
file_list[job.dispatchDBlock]["lfns"].append(tmp_lfn)
|
|
314
|
+
file_list[job.dispatchDBlock]["guids"].append(file.GUID)
|
|
315
|
+
file_list[job.dispatchDBlock]["fsizes"].append(None if file.fsize in ["NULL", 0] else int(file.fsize))
|
|
316
|
+
file_list[job.dispatchDBlock]["chksums"].append(None if file.checksum in ["NULL", ""] else file.checksum)
|
|
317
|
+
|
|
318
|
+
if file.md5sum in ["NULL", ""]:
|
|
319
|
+
file_list[job.dispatchDBlock]["md5sums"].append(None)
|
|
320
|
+
elif file.md5sum.startswith("md5:"):
|
|
321
|
+
file_list[job.dispatchDBlock]["md5sums"].append(file.md5sum)
|
|
322
|
+
else:
|
|
323
|
+
file_list[job.dispatchDBlock]["md5sums"].append(f"md5:{file.md5sum}")
|
|
324
|
+
|
|
325
|
+
# register dispatch dataset
|
|
326
|
+
disp_list = self.register_dispatch_datasets(file_list, use_zip_to_pin_map, ds_task_map, tmp_logger, disp_error, jedi_task_id)
|
|
327
|
+
# insert datasets to DB
|
|
328
|
+
self.task_buffer.insertDatasets(prod_list + disp_list)
|
|
329
|
+
# job status
|
|
330
|
+
for job in self.jobs:
|
|
331
|
+
if job.dispatchDBlock in disp_error and disp_error[job.dispatchDBlock] != "":
|
|
332
|
+
if job.jobStatus != "failed":
|
|
333
|
+
job.jobStatus = "failed"
|
|
334
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
335
|
+
job.ddmErrorDiag = disp_error[job.dispatchDBlock]
|
|
336
|
+
tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
|
|
337
|
+
# delete explicitly some huge variables
|
|
338
|
+
del file_list
|
|
339
|
+
del prod_list
|
|
340
|
+
del prod_error
|
|
341
|
+
|
|
342
|
+
def register_dispatch_datasets(
|
|
343
|
+
self,
|
|
344
|
+
file_list: Dict[str, Dict[str, List[str]]],
|
|
345
|
+
use_zip_to_pin_map: Dict[str, bool],
|
|
346
|
+
ds_task_map: Dict[str, int],
|
|
347
|
+
tmp_logger: LogWrapper,
|
|
348
|
+
disp_error: Dict[str, str],
|
|
349
|
+
jedi_task_id: Optional[int],
|
|
350
|
+
) -> List[DatasetSpec]:
|
|
351
|
+
"""
|
|
352
|
+
Register dispatch datasets in Rucio.
|
|
353
|
+
|
|
354
|
+
This method registers dispatch datasets in Rucio, handles the creation of zip files if necessary,
|
|
355
|
+
and updates the dataset metadata. It also handles errors and retries the registration process if needed.
|
|
356
|
+
|
|
357
|
+
:param file_list: Dictionary containing file information for each dispatch dataset block.
|
|
358
|
+
:param use_zip_to_pin_map: Dictionary mapping dispatch dataset blocks to a boolean indicating if zip files should be used.
|
|
359
|
+
:param ds_task_map: Dictionary mapping dispatch dataset blocks to their corresponding JEDI task IDs.
|
|
360
|
+
:param tmp_logger: Logger instance for logging messages.
|
|
361
|
+
:param disp_error: Dictionary to store errors encountered during the registration process.
|
|
362
|
+
:param jedi_task_id: JEDI task ID associated with the jobs.
|
|
363
|
+
:return: List of DatasetSpec objects representing the registered dispatch datasets.
|
|
364
|
+
"""
|
|
365
|
+
disp_list = []
|
|
366
|
+
for dispatch_data_block, block_data in file_list.items():
|
|
367
|
+
# ignore empty dataset
|
|
368
|
+
if len(block_data["lfns"]) == 0:
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
# register dispatch dataset
|
|
372
|
+
self.disp_file_list[dispatch_data_block] = file_list[dispatch_data_block]
|
|
373
|
+
if not use_zip_to_pin_map[dispatch_data_block]:
|
|
374
|
+
dis_files = file_list[dispatch_data_block]
|
|
375
|
+
else:
|
|
376
|
+
dids = file_list[dispatch_data_block]["lfns"]
|
|
377
|
+
tmp_zip_stat, tmp_zip_out = rucioAPI.get_zip_files(dids, None)
|
|
378
|
+
if not tmp_zip_stat:
|
|
379
|
+
tmp_logger.debug(f"failed to get zip files : {tmp_zip_out}")
|
|
380
|
+
tmp_zip_out = {}
|
|
381
|
+
dis_files = {"lfns": [], "guids": [], "fsizes": [], "chksums": []}
|
|
382
|
+
for tmp_lfn, tmp_guid, tmp_file_size, tmp_checksum in zip(
|
|
383
|
+
file_list[dispatch_data_block]["lfns"],
|
|
384
|
+
file_list[dispatch_data_block]["guids"],
|
|
385
|
+
file_list[dispatch_data_block]["fsizes"],
|
|
386
|
+
file_list[dispatch_data_block]["chksums"],
|
|
387
|
+
):
|
|
388
|
+
if tmp_lfn in tmp_zip_out:
|
|
389
|
+
tmp_zip_file_name = f"{tmp_zip_out[tmp_lfn]['scope']}:{tmp_zip_out[tmp_lfn]['name']}"
|
|
390
|
+
if tmp_zip_file_name not in dis_files["lfns"]:
|
|
391
|
+
dis_files["lfns"].append(tmp_zip_file_name)
|
|
392
|
+
dis_files["guids"].append(tmp_zip_out[tmp_lfn]["guid"])
|
|
393
|
+
dis_files["fsizes"].append(tmp_zip_out[tmp_lfn]["bytes"])
|
|
394
|
+
dis_files["chksums"].append(tmp_zip_out[tmp_lfn]["adler32"])
|
|
395
|
+
else:
|
|
396
|
+
dis_files["lfns"].append(tmp_lfn)
|
|
397
|
+
dis_files["guids"].append(tmp_guid)
|
|
398
|
+
dis_files["fsizes"].append(tmp_file_size)
|
|
399
|
+
dis_files["chksums"].append(tmp_checksum)
|
|
400
|
+
|
|
401
|
+
metadata = {"hidden": True, "purge_replicas": 0}
|
|
402
|
+
if dispatch_data_block in ds_task_map and ds_task_map[dispatch_data_block] not in ["NULL", 0]:
|
|
403
|
+
metadata["task_id"] = str(ds_task_map[dispatch_data_block])
|
|
404
|
+
tmp_logger.debug(f"register_dataset {dispatch_data_block} {str(metadata)}")
|
|
405
|
+
max_attempt = 3
|
|
406
|
+
is_ok = False
|
|
407
|
+
err_str = ""
|
|
408
|
+
for attempt in range(max_attempt):
|
|
409
|
+
try:
|
|
410
|
+
out = rucioAPI.register_dataset(
|
|
411
|
+
dispatch_data_block,
|
|
412
|
+
dis_files["lfns"],
|
|
413
|
+
dis_files["guids"],
|
|
414
|
+
dis_files["fsizes"],
|
|
415
|
+
dis_files["chksums"],
|
|
416
|
+
lifetime=7,
|
|
417
|
+
scope="panda",
|
|
418
|
+
metadata=metadata,
|
|
419
|
+
)
|
|
420
|
+
is_ok = True
|
|
421
|
+
break
|
|
422
|
+
except Exception:
|
|
423
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
424
|
+
err_str = f"{error_type}:{error_value}"
|
|
425
|
+
tmp_logger.error(f"register_dataset : failed with {err_str}")
|
|
426
|
+
if attempt + 1 == max_attempt:
|
|
427
|
+
break
|
|
428
|
+
self.logger.debug(f"sleep {attempt}/{max_attempt}")
|
|
429
|
+
time.sleep(10)
|
|
430
|
+
if not is_ok:
|
|
431
|
+
tmp_str = err_str.split("\n")[-1]
|
|
432
|
+
disp_error[dispatch_data_block] = f"setupper.setup_source() could not register dispatch_data_block with {tmp_str}"
|
|
433
|
+
continue
|
|
434
|
+
tmp_logger.debug(out)
|
|
435
|
+
new_out = out
|
|
436
|
+
# freezeDataset dispatch dataset
|
|
437
|
+
tmp_logger.debug(f"closeDataset {dispatch_data_block}")
|
|
438
|
+
for attempt in range(max_attempt):
|
|
439
|
+
status = False
|
|
440
|
+
try:
|
|
441
|
+
rucioAPI.close_dataset(dispatch_data_block)
|
|
442
|
+
status = True
|
|
443
|
+
break
|
|
444
|
+
except Exception:
|
|
445
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
446
|
+
out = f"failed to close : {error_type} {error_value}"
|
|
447
|
+
time.sleep(10)
|
|
448
|
+
if not status:
|
|
449
|
+
tmp_logger.error(out)
|
|
450
|
+
disp_error[dispatch_data_block] = f"setupper.setup_source() could not freeze dispatch_data_block with {out}"
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
# get VUID
|
|
454
|
+
try:
|
|
455
|
+
vuid = new_out["vuid"]
|
|
456
|
+
# dataset spec. currentfiles is used to count the number of failed jobs
|
|
457
|
+
dataset = DatasetSpec()
|
|
458
|
+
dataset.vuid = vuid
|
|
459
|
+
dataset.name = dispatch_data_block
|
|
460
|
+
dataset.type = "dispatch"
|
|
461
|
+
dataset.status = "defined"
|
|
462
|
+
dataset.numberfiles = len(file_list[dispatch_data_block]["lfns"])
|
|
463
|
+
try:
|
|
464
|
+
dataset.currentfiles = int(sum(filter(None, file_list[dispatch_data_block]["fsizes"])) / 1024 / 1024)
|
|
465
|
+
except Exception:
|
|
466
|
+
dataset.currentfiles = 0
|
|
467
|
+
if jedi_task_id is not None:
|
|
468
|
+
dataset.MoverID = jedi_task_id
|
|
469
|
+
disp_list.append(dataset)
|
|
470
|
+
self.vuid_map[dataset.name] = dataset.vuid
|
|
471
|
+
except Exception:
|
|
472
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
473
|
+
dispatch_data_block.error(f"{error_type} {error_value}")
|
|
474
|
+
disp_error[dispatch_data_block] = "setupper.setup_source() could not decode VUID dispatch_data_block"
|
|
475
|
+
return disp_list
|
|
476
|
+
|
|
477
|
+
# create dataset for outputs in the repository and assign destination
|
|
478
|
+
def setup_destination(self, start_idx: int = -1, n_jobs_in_loop: int = 50) -> None:
|
|
479
|
+
"""
|
|
480
|
+
Create dataset for outputs in the repository and assign destination
|
|
481
|
+
|
|
482
|
+
:param start_idx: The starting index for the jobs to be processed. Defaults to -1.
|
|
483
|
+
:param n_jobs_in_loop: The number of jobs to be processed in a loop. Defaults to 50.
|
|
484
|
+
"""
|
|
485
|
+
tmp_logger = LogWrapper(self.logger, "<setup_destination>")
|
|
486
|
+
tmp_logger.debug(f"idx:{start_idx} n:{n_jobs_in_loop}")
|
|
487
|
+
|
|
488
|
+
dest_error = {}
|
|
489
|
+
dataset_list = {}
|
|
490
|
+
newname_list = {}
|
|
491
|
+
sn_gotten_ds = []
|
|
492
|
+
if start_idx == -1:
|
|
493
|
+
jobs_list = self.jobs
|
|
494
|
+
else:
|
|
495
|
+
jobs_list = self.jobs[start_idx : start_idx + n_jobs_in_loop]
|
|
496
|
+
for job in jobs_list:
|
|
497
|
+
# ignore failed jobs
|
|
498
|
+
if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
|
|
499
|
+
continue
|
|
500
|
+
for file in job.Files:
|
|
501
|
+
# ignore input files
|
|
502
|
+
if file.type in ["input", "pseudo_input"]:
|
|
503
|
+
continue
|
|
504
|
+
# don't touch with outDS for unmerge jobs
|
|
505
|
+
if job.prodSourceLabel == "panda" and job.processingType == "unmerge" and file.type != "log":
|
|
506
|
+
continue
|
|
507
|
+
# extract destinationDBlock, destinationSE and computing_site
|
|
508
|
+
dest = (
|
|
509
|
+
file.destinationDBlock,
|
|
510
|
+
file.destinationSE,
|
|
511
|
+
job.computingSite,
|
|
512
|
+
file.destinationDBlockToken,
|
|
513
|
+
)
|
|
514
|
+
if dest not in dest_error:
|
|
515
|
+
dest_error[dest] = ""
|
|
516
|
+
original_name = ""
|
|
517
|
+
if (
|
|
518
|
+
(job.prodSourceLabel == "panda")
|
|
519
|
+
or panda_config.disable_file_aggregation
|
|
520
|
+
or (job.prodSourceLabel in JobUtils.list_ptest_prod_sources and job.processingType in ["pathena", "prun"])
|
|
521
|
+
or job.processingType.startswith("gangarobot")
|
|
522
|
+
):
|
|
523
|
+
# keep original name
|
|
524
|
+
name_list = [file.destinationDBlock]
|
|
525
|
+
else:
|
|
526
|
+
# set freshness to avoid redundant DB lookup
|
|
527
|
+
defined_fresh_flag = None
|
|
528
|
+
if file.destinationDBlock in sn_gotten_ds:
|
|
529
|
+
# already checked
|
|
530
|
+
defined_fresh_flag = False
|
|
531
|
+
elif job.prodSourceLabel in ["user", "test", "prod_test"]:
|
|
532
|
+
# user or test datasets are always fresh in DB
|
|
533
|
+
defined_fresh_flag = True
|
|
534
|
+
# get serial number
|
|
535
|
+
serial_number, fresh_flag = self.task_buffer.getSerialNumber(file.destinationDBlock, defined_fresh_flag)
|
|
536
|
+
if serial_number == -1:
|
|
537
|
+
dest_error[dest] = f"setupper.setup_destination() could not get serial num for {file.destinationDBlock}"
|
|
538
|
+
break
|
|
539
|
+
if file.destinationDBlock not in sn_gotten_ds:
|
|
540
|
+
sn_gotten_ds.append(file.destinationDBlock)
|
|
541
|
+
# new dataset name
|
|
542
|
+
newname_list[dest] = self.make_sub_dataset_name(file.destinationDBlock, serial_number, job.jediTaskID)
|
|
543
|
+
if fresh_flag:
|
|
544
|
+
# register original dataset and new dataset
|
|
545
|
+
name_list = [file.destinationDBlock, newname_list[dest]]
|
|
546
|
+
original_name = file.destinationDBlock
|
|
547
|
+
else:
|
|
548
|
+
# register new dataset only
|
|
549
|
+
name_list = [newname_list[dest]]
|
|
550
|
+
# create dataset
|
|
551
|
+
for name in name_list:
|
|
552
|
+
computing_site = job.computingSite
|
|
553
|
+
tmp_site = self.site_mapper.getSite(computing_site)
|
|
554
|
+
_, scope_output = select_scope(tmp_site, job.prodSourceLabel, job.job_label)
|
|
555
|
+
if name == original_name and not name.startswith("panda.um."):
|
|
556
|
+
# for original dataset
|
|
557
|
+
computing_site = file.destinationSE
|
|
558
|
+
new_vuid = None
|
|
559
|
+
if job.destinationSE != "local":
|
|
560
|
+
# get src and dest DDM conversion is needed for unknown sites
|
|
561
|
+
if job.prodSourceLabel == "user" and computing_site not in self.site_mapper.siteSpecList:
|
|
562
|
+
# DDM ID was set by using --destSE for analysis job to transfer output
|
|
563
|
+
tmp_src_ddm = tmp_site.ddm_output[scope_output]
|
|
564
|
+
else:
|
|
565
|
+
tmp_src_ddm = tmp_site.ddm_output[scope_output]
|
|
566
|
+
if job.prodSourceLabel == "user" and file.destinationSE not in self.site_mapper.siteSpecList:
|
|
567
|
+
# DDM ID was set by using --destSE for analysis job to transfer output
|
|
568
|
+
tmp_dst_ddm = tmp_src_ddm
|
|
569
|
+
elif DataServiceUtils.getDestinationSE(file.destinationDBlockToken) is not None:
|
|
570
|
+
# destination is specified
|
|
571
|
+
tmp_dst_ddm = DataServiceUtils.getDestinationSE(file.destinationDBlockToken)
|
|
572
|
+
else:
|
|
573
|
+
tmp_dst_site = self.site_mapper.getSite(file.destinationSE)
|
|
574
|
+
_, scope_dst_site_output = select_scope(tmp_dst_site, job.prodSourceLabel, job.job_label)
|
|
575
|
+
tmp_dst_ddm = tmp_dst_site.ddm_output[scope_dst_site_output]
|
|
576
|
+
# skip registration for _sub when src=dest
|
|
577
|
+
if (
|
|
578
|
+
(
|
|
579
|
+
(tmp_src_ddm == tmp_dst_ddm and not EventServiceUtils.isMergeAtOS(job.specialHandling))
|
|
580
|
+
or DataServiceUtils.getDistributedDestination(file.destinationDBlockToken) is not None
|
|
581
|
+
)
|
|
582
|
+
and name != original_name
|
|
583
|
+
and DataServiceUtils.is_sub_dataset(name)
|
|
584
|
+
):
|
|
585
|
+
# create a fake vuid
|
|
586
|
+
new_vuid = str(uuid.uuid4())
|
|
587
|
+
else:
|
|
588
|
+
# get list of tokens
|
|
589
|
+
tmp_token_list = file.destinationDBlockToken.split(",")
|
|
590
|
+
|
|
591
|
+
# get locations
|
|
592
|
+
using_nucleus_as_satellite = False
|
|
593
|
+
if job.prodSourceLabel == "user" and computing_site not in self.site_mapper.siteSpecList:
|
|
594
|
+
ddm_id_list = [tmp_site.ddm_output[scope_output]]
|
|
595
|
+
else:
|
|
596
|
+
if (
|
|
597
|
+
tmp_site.cloud != job.getCloud()
|
|
598
|
+
and DataServiceUtils.is_sub_dataset(name)
|
|
599
|
+
and (job.prodSourceLabel not in ["user", "panda"])
|
|
600
|
+
):
|
|
601
|
+
# Nucleus used as Satellite. Use DATADISK as location
|
|
602
|
+
ddm_id_list = [tmp_site.ddm_output[scope_output]]
|
|
603
|
+
using_nucleus_as_satellite = True
|
|
604
|
+
else:
|
|
605
|
+
ddm_id_list = [tmp_site.ddm_output[scope_output]]
|
|
606
|
+
# use another location when token is set
|
|
607
|
+
if not DataServiceUtils.is_sub_dataset(name) and DataServiceUtils.getDestinationSE(file.destinationDBlockToken) is not None:
|
|
608
|
+
# destination is specified
|
|
609
|
+
ddm_id_list = [DataServiceUtils.getDestinationSE(file.destinationDBlockToken)]
|
|
610
|
+
elif (not using_nucleus_as_satellite) and (file.destinationDBlockToken not in ["NULL", ""]):
|
|
611
|
+
ddm_id_list = []
|
|
612
|
+
for tmp_token in tmp_token_list:
|
|
613
|
+
# set default
|
|
614
|
+
ddm_id = tmp_site.ddm_output[scope_output]
|
|
615
|
+
# convert token to DDM ID
|
|
616
|
+
if tmp_token in tmp_site.setokens_output[scope_output]:
|
|
617
|
+
ddm_id = tmp_site.setokens_output[scope_output][tmp_token]
|
|
618
|
+
# replace or append
|
|
619
|
+
if len(tmp_token_list) <= 1 or name != original_name:
|
|
620
|
+
# use location consistent with token
|
|
621
|
+
ddm_id_list = [ddm_id]
|
|
622
|
+
break
|
|
623
|
+
else:
|
|
624
|
+
# use multiple locations for _tid
|
|
625
|
+
if ddm_id not in ddm_id_list:
|
|
626
|
+
ddm_id_list.append(ddm_id)
|
|
627
|
+
# set hidden flag for _sub
|
|
628
|
+
tmp_life_time = None
|
|
629
|
+
tmp_metadata = None
|
|
630
|
+
if name != original_name and DataServiceUtils.is_sub_dataset(name):
|
|
631
|
+
tmp_life_time = 14
|
|
632
|
+
tmp_metadata = {"hidden": True, "purge_replicas": 0}
|
|
633
|
+
|
|
634
|
+
# register dataset
|
|
635
|
+
tmp_logger.debug(f"register_dataset {name} metadata={tmp_metadata}")
|
|
636
|
+
is_ok = False
|
|
637
|
+
for _ in range(3):
|
|
638
|
+
try:
|
|
639
|
+
out = rucioAPI.register_dataset(
|
|
640
|
+
name,
|
|
641
|
+
metadata=tmp_metadata,
|
|
642
|
+
lifetime=tmp_life_time,
|
|
643
|
+
)
|
|
644
|
+
tmp_logger.debug(out)
|
|
645
|
+
new_vuid = out["vuid"]
|
|
646
|
+
is_ok = True
|
|
647
|
+
break
|
|
648
|
+
except Exception:
|
|
649
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
650
|
+
tmp_logger.error(f"register_dataset : failed with {error_type}:{error_value}")
|
|
651
|
+
time.sleep(10)
|
|
652
|
+
if not is_ok:
|
|
653
|
+
tmp_msg = f"setupper.setup_destination() could not register : {name}"
|
|
654
|
+
dest_error[dest] = tmp_msg
|
|
655
|
+
tmp_logger.error(tmp_msg)
|
|
656
|
+
break
|
|
657
|
+
# register dataset locations
|
|
658
|
+
if (
|
|
659
|
+
job.lockedby == "jedi" and job.getDdmBackEnd() == "rucio" and job.prodSourceLabel in ["panda", "user"]
|
|
660
|
+
) or DataServiceUtils.getDistributedDestination(file.destinationDBlockToken, ignore_empty=False) is not None:
|
|
661
|
+
# skip registerDatasetLocations
|
|
662
|
+
status, out = True, ""
|
|
663
|
+
elif (
|
|
664
|
+
name == original_name
|
|
665
|
+
or tmp_src_ddm != tmp_dst_ddm
|
|
666
|
+
or job.prodSourceLabel == "panda"
|
|
667
|
+
or (
|
|
668
|
+
job.prodSourceLabel in JobUtils.list_ptest_prod_sources
|
|
669
|
+
and job.processingType in ["pathena", "prun", "gangarobot-rctest"]
|
|
670
|
+
)
|
|
671
|
+
or len(tmp_token_list) > 1
|
|
672
|
+
or EventServiceUtils.isMergeAtOS(job.specialHandling)
|
|
673
|
+
):
|
|
674
|
+
# set replica lifetime to _sub
|
|
675
|
+
rep_life_time = None
|
|
676
|
+
if (name != original_name and DataServiceUtils.is_sub_dataset(name)) or (
|
|
677
|
+
name == original_name and name.startswith("panda.")
|
|
678
|
+
):
|
|
679
|
+
rep_life_time = 14
|
|
680
|
+
elif name.startswith("hc_test") or name.startswith("panda.install.") or name.startswith("user.gangarbt."):
|
|
681
|
+
rep_life_time = 7
|
|
682
|
+
# distributed datasets for Event Service output
|
|
683
|
+
grouping = None
|
|
684
|
+
if name != original_name and DataServiceUtils.is_sub_dataset(name) and EventServiceUtils.isEventServiceJob(job):
|
|
685
|
+
ddm_id_list = ["type=DATADISK"]
|
|
686
|
+
grouping = "NONE"
|
|
687
|
+
# register location
|
|
688
|
+
is_ok = True
|
|
689
|
+
for ddm_id in ddm_id_list:
|
|
690
|
+
activity = DataServiceUtils.getActivityForOut(job.prodSourceLabel)
|
|
691
|
+
tmp_logger.debug(
|
|
692
|
+
f"register_dataset_location {name} {ddm_id} lifetime={rep_life_time} activity={activity} grouping={grouping}"
|
|
693
|
+
)
|
|
694
|
+
status = False
|
|
695
|
+
# invalid location
|
|
696
|
+
if ddm_id is None:
|
|
697
|
+
tmp_logger.error(f"wrong location : {ddm_id}")
|
|
698
|
+
break
|
|
699
|
+
for _ in range(3):
|
|
700
|
+
try:
|
|
701
|
+
out = rucioAPI.register_dataset_location(
|
|
702
|
+
name,
|
|
703
|
+
[ddm_id],
|
|
704
|
+
lifetime=rep_life_time,
|
|
705
|
+
activity=activity,
|
|
706
|
+
grouping=grouping,
|
|
707
|
+
)
|
|
708
|
+
tmp_logger.debug(out)
|
|
709
|
+
status = True
|
|
710
|
+
break
|
|
711
|
+
except Exception:
|
|
712
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
713
|
+
out = f"{error_type}:{error_value}"
|
|
714
|
+
tmp_logger.error(f"register_dataset_location : failed with {out}")
|
|
715
|
+
time.sleep(10)
|
|
716
|
+
# failed
|
|
717
|
+
if not status:
|
|
718
|
+
break
|
|
719
|
+
else:
|
|
720
|
+
# skip registerDatasetLocations
|
|
721
|
+
status, out = True, ""
|
|
722
|
+
if not status:
|
|
723
|
+
dest_error[dest] = f"Could not register location : {name} {out.splitlines()[-1]}"
|
|
724
|
+
break
|
|
725
|
+
# already failed
|
|
726
|
+
if dest_error[dest] != "" and name == original_name:
|
|
727
|
+
break
|
|
728
|
+
# get vuid
|
|
729
|
+
if new_vuid is None:
|
|
730
|
+
tmp_logger.debug("listDatasets " + name)
|
|
731
|
+
for _ in range(3):
|
|
732
|
+
new_out, err_msg = rucioAPI.list_datasets(name)
|
|
733
|
+
if new_out is None:
|
|
734
|
+
time.sleep(10)
|
|
735
|
+
else:
|
|
736
|
+
break
|
|
737
|
+
if new_out is None:
|
|
738
|
+
tmp_logger.error(f"failed to get VUID for {name} with {err_msg}")
|
|
739
|
+
else:
|
|
740
|
+
tmp_logger.debug(new_out)
|
|
741
|
+
new_vuid = new_out[name]["vuids"][0]
|
|
742
|
+
try:
|
|
743
|
+
# dataset spec
|
|
744
|
+
dataset = DatasetSpec()
|
|
745
|
+
dataset.vuid = new_vuid
|
|
746
|
+
dataset.name = name
|
|
747
|
+
dataset.type = "output"
|
|
748
|
+
dataset.numberfiles = 0
|
|
749
|
+
dataset.currentfiles = 0
|
|
750
|
+
dataset.status = "defined"
|
|
751
|
+
# append
|
|
752
|
+
dataset_list[(name, file.destinationSE, computing_site)] = dataset
|
|
753
|
+
except Exception:
|
|
754
|
+
# set status
|
|
755
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
756
|
+
tmp_logger.error(f"{error_type} {error_value}")
|
|
757
|
+
dest_error[dest] = f"setupper.setup_destination() could not get VUID : {name}"
|
|
758
|
+
# update job status if failed. set new destDBlock and increment number of files, otherwise
|
|
759
|
+
for job in jobs_list:
|
|
760
|
+
# ignore failed jobs
|
|
761
|
+
if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
|
|
762
|
+
continue
|
|
763
|
+
for file in job.Files:
|
|
764
|
+
dest = (
|
|
765
|
+
file.destinationDBlock,
|
|
766
|
+
file.destinationSE,
|
|
767
|
+
job.computingSite,
|
|
768
|
+
file.destinationDBlockToken,
|
|
769
|
+
)
|
|
770
|
+
# update job status if failed
|
|
771
|
+
if dest in dest_error and dest_error[dest] != "":
|
|
772
|
+
if job.jobStatus != "failed":
|
|
773
|
+
job.jobStatus = "failed"
|
|
774
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
775
|
+
job.ddmErrorDiag = dest_error[dest]
|
|
776
|
+
tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
|
|
777
|
+
break
|
|
778
|
+
else:
|
|
779
|
+
# set new destDBlock
|
|
780
|
+
if dest in newname_list:
|
|
781
|
+
file.destinationDBlock = newname_list[dest]
|
|
782
|
+
new_dest = (
|
|
783
|
+
file.destinationDBlock,
|
|
784
|
+
file.destinationSE,
|
|
785
|
+
job.computingSite,
|
|
786
|
+
)
|
|
787
|
+
# increment number of files
|
|
788
|
+
if new_dest in dataset_list:
|
|
789
|
+
dataset_list[new_dest].numberfiles = dataset_list[new_dest].numberfiles + 1
|
|
790
|
+
# dump
|
|
791
|
+
for dataset_name, dataset in dataset_list.items():
|
|
792
|
+
# Ensure dataset_name is a string
|
|
793
|
+
if isinstance(dataset_name, tuple):
|
|
794
|
+
dataset_name = dataset_name[0]
|
|
795
|
+
if DataServiceUtils.is_sub_dataset(dataset_name):
|
|
796
|
+
tmp_logger.debug(f"made sub:{dataset_name} for nFiles={dataset.numberfiles}")
|
|
797
|
+
# insert datasets to DB
|
|
798
|
+
return self.task_buffer.insertDatasets(dataset_list.values())
|
|
799
|
+
|
|
800
|
+
# subscribe sites to dispatchDBlocks
|
|
801
|
+
def subscribe_dispatch_data_block(self) -> None:
|
|
802
|
+
"""
|
|
803
|
+
Subscribe dispatch db method for running the setup process.
|
|
804
|
+
"""
|
|
805
|
+
tmp_logger = LogWrapper(self.logger, "<subscribe_dispatch_data_block>")
|
|
806
|
+
|
|
807
|
+
disp_error = {}
|
|
808
|
+
failed_jobs = []
|
|
809
|
+
|
|
810
|
+
for job in self.jobs:
|
|
811
|
+
# ignore failed jobs
|
|
812
|
+
if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
|
|
813
|
+
continue
|
|
814
|
+
# ignore no dispatch jobs
|
|
815
|
+
if job.dispatchDBlock == "NULL" or job.computingSite == "NULL":
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
# extract dispatchDBlock and computingSite
|
|
819
|
+
disp = (job.dispatchDBlock, job.computingSite)
|
|
820
|
+
if disp not in disp_error:
|
|
821
|
+
disp_error[disp] = ""
|
|
822
|
+
|
|
823
|
+
site_spec = self.site_mapper.getSite(job.computingSite)
|
|
824
|
+
|
|
825
|
+
# use input RSE with read_lan/0 as destination
|
|
826
|
+
scope_dst_input, _ = select_scope(site_spec, job.prodSourceLabel, job.job_label)
|
|
827
|
+
ddm_id = site_spec.ddm_input[scope_dst_input]
|
|
828
|
+
|
|
829
|
+
# set share and activity
|
|
830
|
+
option_activity = "Production Input"
|
|
831
|
+
if job.prodSourceLabel in ["user", "panda"]:
|
|
832
|
+
option_activity = "Analysis Input"
|
|
833
|
+
elif job.processingType == "urgent" or job.currentPriority > 1000:
|
|
834
|
+
option_activity = "Express"
|
|
835
|
+
|
|
836
|
+
# taskID
|
|
837
|
+
option_comment = None
|
|
838
|
+
if job.jediTaskID not in ["NULL", 0]:
|
|
839
|
+
option_comment = f"task_id:{job.jediTaskID}"
|
|
840
|
+
|
|
841
|
+
option_owner = None
|
|
842
|
+
|
|
843
|
+
tmp_logger.debug(
|
|
844
|
+
f"register_dataset_subscription {job.dispatchDBlock, ddm_id} "
|
|
845
|
+
f"{{'activity': {option_activity}, 'lifetime': 7, 'dn': {option_owner}, 'comment': {option_comment}}}"
|
|
846
|
+
)
|
|
847
|
+
for _ in range(3):
|
|
848
|
+
try:
|
|
849
|
+
status = rucioAPI.register_dataset_subscription(
|
|
850
|
+
job.dispatchDBlock,
|
|
851
|
+
[ddm_id],
|
|
852
|
+
activity=option_activity,
|
|
853
|
+
lifetime=7,
|
|
854
|
+
distinguished_name=option_owner,
|
|
855
|
+
comment=option_comment,
|
|
856
|
+
)
|
|
857
|
+
out = "register_dataset_subscription finished correctly"
|
|
858
|
+
break
|
|
859
|
+
except Exception as error:
|
|
860
|
+
status = False
|
|
861
|
+
out = f"register_dataset_subscription failed with {str(error)} {traceback.format_exc()}"
|
|
862
|
+
time.sleep(10)
|
|
863
|
+
|
|
864
|
+
if not status:
|
|
865
|
+
tmp_logger.error(out)
|
|
866
|
+
disp_error[disp] = "setupper.subscribe_dispatch_data_block() could not register subscription"
|
|
867
|
+
else:
|
|
868
|
+
tmp_logger.debug(out)
|
|
869
|
+
|
|
870
|
+
# failed jobs
|
|
871
|
+
if disp_error[disp] != "":
|
|
872
|
+
if job.jobStatus != "failed":
|
|
873
|
+
job.jobStatus = "failed"
|
|
874
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
875
|
+
job.ddmErrorDiag = disp_error[disp]
|
|
876
|
+
tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
|
|
877
|
+
failed_jobs.append(job)
|
|
878
|
+
# update failed jobs only. succeeded jobs should be activated by DDM callback
|
|
879
|
+
self.update_failed_jobs(failed_jobs)
|
|
880
|
+
|
|
881
|
+
def collect_input_lfns(self):
|
|
882
|
+
# collect input LFNs
|
|
883
|
+
input_lfns = set()
|
|
884
|
+
for tmp_job in self.jobs:
|
|
885
|
+
for tmp_file in tmp_job.Files:
|
|
886
|
+
if tmp_file.type == "input":
|
|
887
|
+
input_lfns.add(tmp_file.lfn)
|
|
888
|
+
# Removes attemptNr from LFN name
|
|
889
|
+
gen_lfn = re.sub("\.\d+$", "", tmp_file.lfn)
|
|
890
|
+
input_lfns.add(gen_lfn)
|
|
891
|
+
if tmp_file.GUID not in ["NULL", "", None]:
|
|
892
|
+
if tmp_file.dataset not in self.lfn_dataset_map:
|
|
893
|
+
self.lfn_dataset_map[tmp_file.dataset] = {}
|
|
894
|
+
self.lfn_dataset_map[tmp_file.dataset][tmp_file.lfn] = {
|
|
895
|
+
"guid": tmp_file.GUID,
|
|
896
|
+
"chksum": tmp_file.checksum,
|
|
897
|
+
"md5sum": tmp_file.md5sum,
|
|
898
|
+
"fsize": tmp_file.fsize,
|
|
899
|
+
"scope": tmp_file.scope,
|
|
900
|
+
}
|
|
901
|
+
return input_lfns
|
|
902
|
+
|
|
903
|
+
# correct LFN for attemptNr
|
|
904
|
+
def correct_lfn(self) -> None:
|
|
905
|
+
"""
|
|
906
|
+
Correct lfn method for running the setup process.
|
|
907
|
+
"""
|
|
908
|
+
|
|
909
|
+
tmp_logger = LogWrapper(self.logger, "<correct_lfn>")
|
|
910
|
+
|
|
911
|
+
lfn_map = {}
|
|
912
|
+
val_map = {}
|
|
913
|
+
prod_error = {}
|
|
914
|
+
missing_datasets = {}
|
|
915
|
+
jobs_waiting = []
|
|
916
|
+
jobs_failed = []
|
|
917
|
+
jobs_processed = []
|
|
918
|
+
all_lfns = {}
|
|
919
|
+
all_guids = {}
|
|
920
|
+
all_scopes = {}
|
|
921
|
+
lfn_ds_map = {}
|
|
922
|
+
tmp_logger.debug("start")
|
|
923
|
+
|
|
924
|
+
# collect input LFNs
|
|
925
|
+
input_lfns = self.collect_input_lfns()
|
|
926
|
+
|
|
927
|
+
for job in self.jobs:
|
|
928
|
+
# check if sitename is known
|
|
929
|
+
if job.computingSite != "NULL" and job.computingSite not in self.site_mapper.siteSpecList:
|
|
930
|
+
job.jobStatus = "failed"
|
|
931
|
+
job.ddmErrorCode = ErrorCode.EC_Setupper
|
|
932
|
+
job.ddmErrorDiag = f"computingSite:{job.computingSite} is unknown"
|
|
933
|
+
# append job for downstream process
|
|
934
|
+
jobs_processed.append(job)
|
|
935
|
+
continue
|
|
936
|
+
|
|
937
|
+
# ignore no prodDBlock jobs or container dataset
|
|
938
|
+
if job.prodDBlock == "NULL":
|
|
939
|
+
# append job to processed list
|
|
940
|
+
jobs_processed.append(job)
|
|
941
|
+
continue
|
|
942
|
+
|
|
943
|
+
# collect datasets
|
|
944
|
+
datasets = []
|
|
945
|
+
for file in job.Files:
|
|
946
|
+
# make a list of input datasets
|
|
947
|
+
if file.type == "input" and file.dispatchDBlock == "NULL" and (file.GUID == "NULL" or job.prodSourceLabel in ["managed", "test", "ptest"]):
|
|
948
|
+
if file.dataset not in datasets:
|
|
949
|
+
datasets.append(file.dataset)
|
|
950
|
+
|
|
951
|
+
# get file information for the LFNs
|
|
952
|
+
for dataset in datasets:
|
|
953
|
+
# make a dataset to lfn map
|
|
954
|
+
if dataset not in lfn_map:
|
|
955
|
+
prod_error[dataset] = ""
|
|
956
|
+
lfn_map[dataset] = {}
|
|
957
|
+
|
|
958
|
+
# get the file information (size, checksum, ...) for the selected LFNs
|
|
959
|
+
status, out = self.get_list_files_in_dataset(dataset, input_lfns)
|
|
960
|
+
# issue getting the files in dataset
|
|
961
|
+
if status != 0:
|
|
962
|
+
tmp_logger.error(out)
|
|
963
|
+
error_message = f"could not get file list of prodDBlock {dataset}"
|
|
964
|
+
prod_error[dataset] = error_message
|
|
965
|
+
tmp_logger.error(error_message)
|
|
966
|
+
# doesn't exist in DDM, record it as missing
|
|
967
|
+
if status == -1:
|
|
968
|
+
missing_datasets[dataset] = f"DS:{dataset} not found in DDM"
|
|
969
|
+
else:
|
|
970
|
+
missing_datasets[dataset] = out
|
|
971
|
+
# issue getting the files in dataset
|
|
972
|
+
else:
|
|
973
|
+
# make map (key: LFN w/o attemptNr, value: LFN with attemptNr)
|
|
974
|
+
items = out
|
|
975
|
+
try:
|
|
976
|
+
# loop over all files
|
|
977
|
+
for tmp_lfn in items:
|
|
978
|
+
vals = items[tmp_lfn]
|
|
979
|
+
val_map[tmp_lfn] = vals
|
|
980
|
+
# Removes attemptNr from LFN name
|
|
981
|
+
gen_lfn = re.sub("\.\d+$", "", tmp_lfn)
|
|
982
|
+
if gen_lfn in lfn_map[dataset]:
|
|
983
|
+
# get attemptNr
|
|
984
|
+
new_att_nr = 0
|
|
985
|
+
new_mat = re.search("\.(\d+)$", tmp_lfn)
|
|
986
|
+
if new_mat is not None:
|
|
987
|
+
new_att_nr = int(new_mat.group(1))
|
|
988
|
+
old_att_nr = 0
|
|
989
|
+
old_mat = re.search("\.(\d+)$", lfn_map[dataset][gen_lfn])
|
|
990
|
+
if old_mat is not None:
|
|
991
|
+
old_att_nr = int(old_mat.group(1))
|
|
992
|
+
# compare
|
|
993
|
+
if new_att_nr > old_att_nr:
|
|
994
|
+
lfn_map[dataset][gen_lfn] = tmp_lfn
|
|
995
|
+
else:
|
|
996
|
+
lfn_map[dataset][gen_lfn] = tmp_lfn
|
|
997
|
+
# mapping from LFN to DS
|
|
998
|
+
lfn_ds_map[lfn_map[dataset][gen_lfn]] = dataset
|
|
999
|
+
except Exception:
|
|
1000
|
+
prod_error[dataset] = f"could not convert HTTP-res to map for prodDBlock {dataset}"
|
|
1001
|
+
tmp_logger.error(prod_error[dataset])
|
|
1002
|
+
tmp_logger.error(out)
|
|
1003
|
+
|
|
1004
|
+
# mark job with a missing dataset as failed and update files as missing
|
|
1005
|
+
is_failed = False
|
|
1006
|
+
for dataset in datasets:
|
|
1007
|
+
if dataset in missing_datasets:
|
|
1008
|
+
job.jobStatus = "failed"
|
|
1009
|
+
job.ddmErrorCode = ErrorCode.EC_GUID
|
|
1010
|
+
job.ddmErrorDiag = missing_datasets[dataset]
|
|
1011
|
+
|
|
1012
|
+
# set missing
|
|
1013
|
+
for tmp_file in job.Files:
|
|
1014
|
+
if tmp_file.dataset == dataset:
|
|
1015
|
+
tmp_file.status = "missing"
|
|
1016
|
+
|
|
1017
|
+
# append to job_failed
|
|
1018
|
+
jobs_failed.append(job)
|
|
1019
|
+
is_failed = True
|
|
1020
|
+
tmp_logger.debug(f"{job.PandaID} failed with {missing_datasets[dataset]}")
|
|
1021
|
+
break
|
|
1022
|
+
# skip to the next job
|
|
1023
|
+
if is_failed:
|
|
1024
|
+
continue
|
|
1025
|
+
|
|
1026
|
+
# check for waiting jobs
|
|
1027
|
+
is_failed = False
|
|
1028
|
+
for dataset in datasets:
|
|
1029
|
+
if prod_error[dataset] != "":
|
|
1030
|
+
# append job to waiting list
|
|
1031
|
+
jobs_waiting.append(job)
|
|
1032
|
+
is_failed = True
|
|
1033
|
+
break
|
|
1034
|
+
if is_failed:
|
|
1035
|
+
continue
|
|
1036
|
+
|
|
1037
|
+
# replace generic LFN with real LFN
|
|
1038
|
+
replace_list = []
|
|
1039
|
+
is_failed = False
|
|
1040
|
+
for file in job.Files:
|
|
1041
|
+
if file.type == "input" and file.dispatchDBlock == "NULL":
|
|
1042
|
+
add_to_lfn_map = True
|
|
1043
|
+
if file.GUID == "NULL":
|
|
1044
|
+
# get LFN w/o attemptNr
|
|
1045
|
+
# regexp to remove a dot followed by one or more digits at the end of the file name
|
|
1046
|
+
# for example "data_file.123" will become "data_file"
|
|
1047
|
+
basename = re.sub("\.\d+$", "", file.lfn)
|
|
1048
|
+
if basename == file.lfn:
|
|
1049
|
+
# replace
|
|
1050
|
+
if basename in lfn_map[file.dataset]:
|
|
1051
|
+
file.lfn = lfn_map[file.dataset][basename]
|
|
1052
|
+
replace_list.append((basename, file.lfn))
|
|
1053
|
+
# set GUID
|
|
1054
|
+
if file.lfn in val_map:
|
|
1055
|
+
file.GUID = val_map[file.lfn]["guid"]
|
|
1056
|
+
file.fsize = val_map[file.lfn]["fsize"]
|
|
1057
|
+
file.md5sum = val_map[file.lfn]["md5sum"]
|
|
1058
|
+
file.checksum = val_map[file.lfn]["chksum"]
|
|
1059
|
+
file.scope = val_map[file.lfn]["scope"]
|
|
1060
|
+
# remove white space
|
|
1061
|
+
if file.md5sum is not None:
|
|
1062
|
+
file.md5sum = file.md5sum.strip()
|
|
1063
|
+
if file.checksum is not None:
|
|
1064
|
+
file.checksum = file.checksum.strip()
|
|
1065
|
+
else:
|
|
1066
|
+
if job.prodSourceLabel not in ["managed", "test"]:
|
|
1067
|
+
add_to_lfn_map = False
|
|
1068
|
+
|
|
1069
|
+
# check missing file
|
|
1070
|
+
if file.GUID == "NULL" or job.prodSourceLabel in [
|
|
1071
|
+
"managed",
|
|
1072
|
+
"test",
|
|
1073
|
+
]:
|
|
1074
|
+
if file.lfn not in val_map:
|
|
1075
|
+
# append job to waiting list
|
|
1076
|
+
err_msg = f"GUID for {file.lfn} not found in rucio"
|
|
1077
|
+
tmp_logger.error(err_msg)
|
|
1078
|
+
file.status = "missing"
|
|
1079
|
+
if job not in jobs_failed:
|
|
1080
|
+
job.jobStatus = "failed"
|
|
1081
|
+
job.ddmErrorCode = ErrorCode.EC_GUID
|
|
1082
|
+
job.ddmErrorDiag = err_msg
|
|
1083
|
+
jobs_failed.append(job)
|
|
1084
|
+
is_failed = True
|
|
1085
|
+
continue
|
|
1086
|
+
|
|
1087
|
+
# add to all_lfns/all_guids
|
|
1088
|
+
if add_to_lfn_map:
|
|
1089
|
+
tmp_cloud = job.getCloud()
|
|
1090
|
+
all_lfns.setdefault(tmp_cloud, []).append(file.lfn)
|
|
1091
|
+
all_guids.setdefault(tmp_cloud, []).append(file.GUID)
|
|
1092
|
+
all_scopes.setdefault(tmp_cloud, []).append(file.scope)
|
|
1093
|
+
|
|
1094
|
+
# modify jobParameters
|
|
1095
|
+
if not is_failed:
|
|
1096
|
+
for patt, repl in replace_list:
|
|
1097
|
+
job.jobParameters = re.sub(f"{patt} ", f"{repl} ", job.jobParameters)
|
|
1098
|
+
# append job to processed list
|
|
1099
|
+
jobs_processed.append(job)
|
|
1100
|
+
|
|
1101
|
+
# set data summary fields
|
|
1102
|
+
for tmp_job in self.jobs:
|
|
1103
|
+
try:
|
|
1104
|
+
# set only for production/analysis/test
|
|
1105
|
+
if tmp_job.prodSourceLabel not in ["managed", "test", "user", "prod_test"] + JobUtils.list_ptest_prod_sources:
|
|
1106
|
+
continue
|
|
1107
|
+
|
|
1108
|
+
# loop over all files
|
|
1109
|
+
tmp_job.nInputDataFiles = 0
|
|
1110
|
+
tmp_job.inputFileBytes = 0
|
|
1111
|
+
tmp_input_file_project = None
|
|
1112
|
+
for tmp_file in tmp_job.Files:
|
|
1113
|
+
# use input files and ignore DBR/lib.tgz
|
|
1114
|
+
# lib.tgz is the user sandbox
|
|
1115
|
+
if tmp_file.type == "input" and (not tmp_file.dataset.startswith("ddo")) and not tmp_file.lfn.endswith(".lib.tgz"):
|
|
1116
|
+
tmp_job.nInputDataFiles += 1
|
|
1117
|
+
if tmp_file.fsize not in ["NULL", None, 0, "0"]:
|
|
1118
|
+
tmp_job.inputFileBytes += tmp_file.fsize
|
|
1119
|
+
# get input type and project
|
|
1120
|
+
if tmp_input_file_project is None:
|
|
1121
|
+
tmp_input_items = tmp_file.dataset.split(".")
|
|
1122
|
+
# input project
|
|
1123
|
+
tmp_input_file_project = tmp_input_items[0].split(":")[-1]
|
|
1124
|
+
# set input type and project
|
|
1125
|
+
if tmp_job.prodDBlock not in ["", None, "NULL"]:
|
|
1126
|
+
# input project
|
|
1127
|
+
if tmp_input_file_project is not None:
|
|
1128
|
+
tmp_job.inputFileProject = tmp_input_file_project
|
|
1129
|
+
# protection
|
|
1130
|
+
max_input_file_bytes = 10**16 - 1 # 15 digit precision in database column
|
|
1131
|
+
tmp_job.inputFileBytes = min(tmp_job.inputFileBytes, max_input_file_bytes)
|
|
1132
|
+
# set background-able flag
|
|
1133
|
+
tmp_job.setBackgroundableFlag()
|
|
1134
|
+
# set input and output file types
|
|
1135
|
+
tmp_job.set_input_output_file_types()
|
|
1136
|
+
except Exception:
|
|
1137
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1138
|
+
tmp_logger.error(f"failed to set data summary fields for PandaID={tmp_job.PandaID}: {error_type} {error_value}")
|
|
1139
|
+
|
|
1140
|
+
# send jobs to jobs_waiting
|
|
1141
|
+
self.task_buffer.keepJobs(jobs_waiting)
|
|
1142
|
+
# update failed job
|
|
1143
|
+
self.update_failed_jobs(jobs_failed)
|
|
1144
|
+
# remove waiting/failed jobs
|
|
1145
|
+
self.jobs = jobs_processed
|
|
1146
|
+
|
|
1147
|
+
tmp_logger.debug("done")
|
|
1148
|
+
|
|
1149
|
+
# delete huge variables
|
|
1150
|
+
del lfn_map
|
|
1151
|
+
del val_map
|
|
1152
|
+
del prod_error
|
|
1153
|
+
del jobs_waiting
|
|
1154
|
+
del jobs_processed
|
|
1155
|
+
del all_lfns
|
|
1156
|
+
del all_guids
|
|
1157
|
+
|
|
1158
|
+
# remove waiting jobs
|
|
1159
|
+
def remove_waiting_jobs(self) -> None:
|
|
1160
|
+
"""
|
|
1161
|
+
Remove waiting jobs method for running the setup process.
|
|
1162
|
+
"""
|
|
1163
|
+
jobs_waiting = []
|
|
1164
|
+
jobs_processed = []
|
|
1165
|
+
for tmp_job in self.jobs:
|
|
1166
|
+
if tmp_job.jobStatus == "waiting":
|
|
1167
|
+
jobs_waiting.append(tmp_job)
|
|
1168
|
+
else:
|
|
1169
|
+
jobs_processed.append(tmp_job)
|
|
1170
|
+
# send jobs to jobs_waiting
|
|
1171
|
+
self.task_buffer.keepJobs(jobs_waiting)
|
|
1172
|
+
# remove waiting/failed jobs
|
|
1173
|
+
self.jobs = jobs_processed
|
|
1174
|
+
|
|
1175
|
+
# memory checker
|
|
1176
|
+
def memory_check(self) -> None:
|
|
1177
|
+
"""
|
|
1178
|
+
Memory check method for running the setup process.
|
|
1179
|
+
"""
|
|
1180
|
+
tmp_logger = LogWrapper(self.logger, "<memory_check>")
|
|
1181
|
+
|
|
1182
|
+
try:
|
|
1183
|
+
proc_status = f"/proc/{os.getpid()}/status"
|
|
1184
|
+
proc_file = open(proc_status)
|
|
1185
|
+
name = ""
|
|
1186
|
+
vm_size = ""
|
|
1187
|
+
vm_rss = ""
|
|
1188
|
+
# extract Name,VmSize,VmRSS
|
|
1189
|
+
for line in proc_file:
|
|
1190
|
+
if line.startswith("Name:"):
|
|
1191
|
+
name = line.split()[-1]
|
|
1192
|
+
continue
|
|
1193
|
+
if line.startswith("VmSize:"):
|
|
1194
|
+
vm_size = ""
|
|
1195
|
+
for item in line.split()[1:]:
|
|
1196
|
+
vm_size += item
|
|
1197
|
+
continue
|
|
1198
|
+
if line.startswith("VmRSS:"):
|
|
1199
|
+
vm_rss = ""
|
|
1200
|
+
for item in line.split()[1:]:
|
|
1201
|
+
vm_rss += item
|
|
1202
|
+
continue
|
|
1203
|
+
proc_file.close()
|
|
1204
|
+
tmp_logger.debug(f"PID={os.getpid()} Name={name} VSZ={vm_size} RSS={vm_rss}")
|
|
1205
|
+
except Exception:
|
|
1206
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1207
|
+
tmp_logger.error(f"{error_type} {error_value}")
|
|
1208
|
+
tmp_logger.debug(f"PID={os.getpid()} unknown")
|
|
1209
|
+
return
|
|
1210
|
+
|
|
1211
|
+
# get list of files in dataset
|
|
1212
|
+
def get_list_files_in_dataset(self, dataset: str, file_list: Optional[List[str]] = None, use_cache: bool = True) -> Tuple[int, List[str]]:
|
|
1213
|
+
"""
|
|
1214
|
+
Get list files in dataset method for running the setup process.
|
|
1215
|
+
|
|
1216
|
+
:param dataset: The dataset to get the list of files from.
|
|
1217
|
+
:param file_list: The list of files. Defaults to None.
|
|
1218
|
+
:param use_cache: Whether to use cache. Defaults to True.
|
|
1219
|
+
:return: A tuple containing the status and the list of files.
|
|
1220
|
+
"""
|
|
1221
|
+
|
|
1222
|
+
tmp_logger = LogWrapper(self.logger, "<get_list_files_in_dataset>")
|
|
1223
|
+
|
|
1224
|
+
# use cache data
|
|
1225
|
+
if use_cache and dataset in self.lfn_dataset_map:
|
|
1226
|
+
return 0, self.lfn_dataset_map[dataset]
|
|
1227
|
+
status = None
|
|
1228
|
+
items = []
|
|
1229
|
+
for _ in range(3):
|
|
1230
|
+
try:
|
|
1231
|
+
tmp_logger.debug(f"list_files_in_dataset {dataset}")
|
|
1232
|
+
items, _ = rucioAPI.list_files_in_dataset(dataset, file_list=file_list)
|
|
1233
|
+
status = 0
|
|
1234
|
+
break
|
|
1235
|
+
except DataIdentifierNotFound:
|
|
1236
|
+
status = -1
|
|
1237
|
+
break
|
|
1238
|
+
except Exception:
|
|
1239
|
+
status = -2
|
|
1240
|
+
|
|
1241
|
+
if status != 0:
|
|
1242
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1243
|
+
out = f"{error_type} {error_value}"
|
|
1244
|
+
return status, out
|
|
1245
|
+
# keep to avoid redundant lookup
|
|
1246
|
+
self.lfn_dataset_map[dataset] = items
|
|
1247
|
+
return status, items
|
|
1248
|
+
|
|
1249
|
+
# get list of datasets in container
|
|
1250
|
+
def get_list_dataset_in_container(self, container: str) -> Tuple[bool, List[str]]:
|
|
1251
|
+
"""
|
|
1252
|
+
Get list dataset in container method for running the setup process.
|
|
1253
|
+
|
|
1254
|
+
:param container: The container to get the list of datasets from.
|
|
1255
|
+
:return: A tuple containing a boolean indicating the status and the list of datasets.
|
|
1256
|
+
"""
|
|
1257
|
+
|
|
1258
|
+
tmp_logger = LogWrapper(self.logger, "<get_list_dataset_in_container>")
|
|
1259
|
+
|
|
1260
|
+
# get datasets in container
|
|
1261
|
+
tmp_logger.debug(container)
|
|
1262
|
+
out = ""
|
|
1263
|
+
for _ in range(3):
|
|
1264
|
+
datasets, out = rucioAPI.list_datasets_in_container(container)
|
|
1265
|
+
if datasets is not None:
|
|
1266
|
+
return True, datasets
|
|
1267
|
+
time.sleep(10)
|
|
1268
|
+
tmp_logger.error(out)
|
|
1269
|
+
return False, out
|
|
1270
|
+
|
|
1271
|
+
# get datasets in container
|
|
1272
|
+
def get_list_dataset_replicas_in_container(self, container: str, get_map: bool = False) -> Tuple[int, str]:
|
|
1273
|
+
"""
|
|
1274
|
+
Get list dataset replicas in container method for running the setup process.
|
|
1275
|
+
|
|
1276
|
+
:param container: The container to get the list of dataset replicas from.
|
|
1277
|
+
:param get_map: Whether to get the map. Defaults to False.
|
|
1278
|
+
:return: A tuple containing the status and the map of dataset replicas.
|
|
1279
|
+
"""
|
|
1280
|
+
tmp_logger = LogWrapper(self.logger, "<get_list_dataset_replicas_in_container>")
|
|
1281
|
+
tmp_logger.debug(container)
|
|
1282
|
+
|
|
1283
|
+
datasets = None
|
|
1284
|
+
out = ""
|
|
1285
|
+
for _ in range(3):
|
|
1286
|
+
datasets, out = rucioAPI.list_datasets_in_container(container)
|
|
1287
|
+
if datasets is None:
|
|
1288
|
+
time.sleep(10)
|
|
1289
|
+
else:
|
|
1290
|
+
break
|
|
1291
|
+
if datasets is None:
|
|
1292
|
+
tmp_logger.error(out)
|
|
1293
|
+
if get_map:
|
|
1294
|
+
return False, out
|
|
1295
|
+
return 1, out
|
|
1296
|
+
|
|
1297
|
+
# loop over all datasets
|
|
1298
|
+
all_rep_map = {}
|
|
1299
|
+
for dataset in datasets:
|
|
1300
|
+
tmp_logger.debug(f"listDatasetReplicas {dataset}")
|
|
1301
|
+
status, out = self.get_list_dataset_replicas(dataset)
|
|
1302
|
+
tmp_logger.debug(out)
|
|
1303
|
+
if not status:
|
|
1304
|
+
if get_map:
|
|
1305
|
+
return False, out
|
|
1306
|
+
return status, out
|
|
1307
|
+
tmp_rep_sites = out
|
|
1308
|
+
# get map
|
|
1309
|
+
if get_map:
|
|
1310
|
+
all_rep_map[dataset] = tmp_rep_sites
|
|
1311
|
+
continue
|
|
1312
|
+
# otherwise get sum
|
|
1313
|
+
for site_id in tmp_rep_sites:
|
|
1314
|
+
stat_list = tmp_rep_sites[site_id]
|
|
1315
|
+
if site_id not in all_rep_map:
|
|
1316
|
+
# append
|
|
1317
|
+
all_rep_map[site_id] = [
|
|
1318
|
+
stat_list[-1],
|
|
1319
|
+
]
|
|
1320
|
+
else:
|
|
1321
|
+
# add
|
|
1322
|
+
new_st_map = {}
|
|
1323
|
+
for st_name in all_rep_map[site_id][0]:
|
|
1324
|
+
st_num = all_rep_map[site_id][0][st_name]
|
|
1325
|
+
if st_name in stat_list[-1]:
|
|
1326
|
+
# try mainly for archived=None
|
|
1327
|
+
try:
|
|
1328
|
+
new_st_map[st_name] = st_num + stat_list[-1][st_name]
|
|
1329
|
+
except Exception:
|
|
1330
|
+
new_st_map[st_name] = st_num
|
|
1331
|
+
else:
|
|
1332
|
+
new_st_map[st_name] = st_num
|
|
1333
|
+
all_rep_map[site_id] = [
|
|
1334
|
+
new_st_map,
|
|
1335
|
+
]
|
|
1336
|
+
# return
|
|
1337
|
+
tmp_logger.debug(str(all_rep_map))
|
|
1338
|
+
if get_map:
|
|
1339
|
+
return True, all_rep_map
|
|
1340
|
+
return 0, str(all_rep_map)
|
|
1341
|
+
|
|
1342
|
+
# get list of replicas for a dataset
|
|
1343
|
+
def get_list_dataset_replicas(self, dataset: str, get_map: bool = True) -> Tuple[bool, str]:
|
|
1344
|
+
"""
|
|
1345
|
+
Get list dataset replicas method for running the setup process.
|
|
1346
|
+
|
|
1347
|
+
:param dataset: The dataset to get the list of replicas from.
|
|
1348
|
+
:param get_map: Whether to get the map. Defaults to True.
|
|
1349
|
+
:return: A tuple containing a boolean indicating the status and the map of dataset replicas.
|
|
1350
|
+
"""
|
|
1351
|
+
|
|
1352
|
+
tmp_logger = LogWrapper(self.logger, "<get_list_dataset_replicas>")
|
|
1353
|
+
|
|
1354
|
+
out = ""
|
|
1355
|
+
for attempt in range(3):
|
|
1356
|
+
tmp_logger.debug(f"{attempt}/{3} listDatasetReplicas {dataset}")
|
|
1357
|
+
status, out = rucioAPI.list_dataset_replicas(dataset)
|
|
1358
|
+
if status != 0:
|
|
1359
|
+
time.sleep(10)
|
|
1360
|
+
else:
|
|
1361
|
+
break
|
|
1362
|
+
# result
|
|
1363
|
+
if status != 0:
|
|
1364
|
+
tmp_logger.error(out)
|
|
1365
|
+
tmp_logger.error(f"bad response for {dataset}")
|
|
1366
|
+
if get_map:
|
|
1367
|
+
return False, {}
|
|
1368
|
+
else:
|
|
1369
|
+
return 1, str({})
|
|
1370
|
+
try:
|
|
1371
|
+
ret_map = out
|
|
1372
|
+
tmp_logger.debug(f"list_dataset_replicas->{str(ret_map)}")
|
|
1373
|
+
if get_map:
|
|
1374
|
+
return True, ret_map
|
|
1375
|
+
else:
|
|
1376
|
+
return 0, str(ret_map)
|
|
1377
|
+
except Exception:
|
|
1378
|
+
tmp_logger.error(out)
|
|
1379
|
+
tmp_logger.error(f"could not convert HTTP-res to replica map for {dataset}")
|
|
1380
|
+
if get_map:
|
|
1381
|
+
return False, {}
|
|
1382
|
+
else:
|
|
1383
|
+
return 1, str({})
|
|
1384
|
+
|
|
1385
|
+
# dynamic data placement for analysis jobs
|
|
1386
|
+
def dynamic_data_placement(self) -> None:
|
|
1387
|
+
"""
|
|
1388
|
+
Dynamic data placement method for running the setup process.
|
|
1389
|
+
"""
|
|
1390
|
+
# only first submission
|
|
1391
|
+
if not self.first_submission:
|
|
1392
|
+
return
|
|
1393
|
+
# no jobs
|
|
1394
|
+
if len(self.jobs) == 0:
|
|
1395
|
+
return
|
|
1396
|
+
# only successful analysis
|
|
1397
|
+
if self.jobs[0].jobStatus in ["failed", "cancelled"] or self.jobs[0].isCancelled() or (not self.jobs[0].prodSourceLabel in ["user", "panda"]):
|
|
1398
|
+
return
|
|
1399
|
+
# disable for JEDI
|
|
1400
|
+
if self.jobs[0].lockedby == "jedi":
|
|
1401
|
+
return
|
|
1402
|
+
return
|
|
1403
|
+
|
|
1404
|
+
def collect_existing_files(self):
|
|
1405
|
+
"""
|
|
1406
|
+
Collects existing files to avoid deletion when jobs are queued.
|
|
1407
|
+
This method iterates over all jobs and collects files that should not be deleted,
|
|
1408
|
+
organizing them by destination DDM endpoint and log dataset name.
|
|
1409
|
+
|
|
1410
|
+
:return: A dictionary mapping (destination DDM endpoint, log dataset name) to a list of files.
|
|
1411
|
+
"""
|
|
1412
|
+
dataset_file_map = {}
|
|
1413
|
+
n_max_jobs = 20
|
|
1414
|
+
n_jobs_map = {}
|
|
1415
|
+
for tmp_job in self.jobs:
|
|
1416
|
+
# use production or test jobs only
|
|
1417
|
+
if tmp_job.prodSourceLabel not in ["managed", "test"]:
|
|
1418
|
+
continue
|
|
1419
|
+
# skip for prefetcher or transferType=direct
|
|
1420
|
+
if tmp_job.usePrefetcher() or tmp_job.transferType == "direct":
|
|
1421
|
+
continue
|
|
1422
|
+
# ignore inappropriate status
|
|
1423
|
+
if tmp_job.jobStatus in ["failed", "cancelled", "waiting"] or tmp_job.isCancelled():
|
|
1424
|
+
continue
|
|
1425
|
+
# check cloud
|
|
1426
|
+
if tmp_job.getCloud() == "ND" and self.site_mapper.getSite(tmp_job.computingSite).cloud == "ND":
|
|
1427
|
+
continue
|
|
1428
|
+
# look for log _sub dataset to be used as a key
|
|
1429
|
+
log_sub_ds_name = ""
|
|
1430
|
+
for tmp_file in tmp_job.Files:
|
|
1431
|
+
if tmp_file.type == "log":
|
|
1432
|
+
log_sub_ds_name = tmp_file.destinationDBlock
|
|
1433
|
+
break
|
|
1434
|
+
# append site
|
|
1435
|
+
dest_site = self.site_mapper.getSite(tmp_job.computingSite)
|
|
1436
|
+
scope_dest_input, _ = select_scope(dest_site, tmp_job.prodSourceLabel, tmp_job.job_label)
|
|
1437
|
+
dest_ddm_id = dest_site.ddm_input[scope_dest_input]
|
|
1438
|
+
map_key_job = (dest_ddm_id, log_sub_ds_name)
|
|
1439
|
+
# increment the number of jobs per key
|
|
1440
|
+
if map_key_job not in n_jobs_map:
|
|
1441
|
+
n_jobs_map[map_key_job] = 0
|
|
1442
|
+
map_key = (
|
|
1443
|
+
dest_ddm_id,
|
|
1444
|
+
log_sub_ds_name,
|
|
1445
|
+
n_jobs_map[map_key_job] // n_max_jobs,
|
|
1446
|
+
)
|
|
1447
|
+
n_jobs_map[map_key_job] += 1
|
|
1448
|
+
if map_key not in dataset_file_map:
|
|
1449
|
+
dataset_file_map[map_key] = {}
|
|
1450
|
+
# add files
|
|
1451
|
+
for tmp_file in tmp_job.Files:
|
|
1452
|
+
if tmp_file.type != "input":
|
|
1453
|
+
continue
|
|
1454
|
+
# if files are unavailable at the dest site normal dis datasets contain them
|
|
1455
|
+
# or files are cached
|
|
1456
|
+
if tmp_file.status not in ["ready"]:
|
|
1457
|
+
continue
|
|
1458
|
+
# if available at Satellite
|
|
1459
|
+
real_dest_ddm_id = (dest_ddm_id,)
|
|
1460
|
+
if (
|
|
1461
|
+
tmp_job.getCloud() in self.available_lfns_in_satellites
|
|
1462
|
+
and tmp_file.dataset in self.available_lfns_in_satellites[tmp_job.getCloud()]
|
|
1463
|
+
and tmp_job.computingSite in self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["sites"]
|
|
1464
|
+
and tmp_file.lfn in self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["sites"][tmp_job.computingSite]
|
|
1465
|
+
):
|
|
1466
|
+
real_dest_ddm_id = self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["siteDQ2IDs"][tmp_job.computingSite]
|
|
1467
|
+
real_dest_ddm_id = tuple(real_dest_ddm_id)
|
|
1468
|
+
# append
|
|
1469
|
+
if real_dest_ddm_id not in dataset_file_map[map_key]:
|
|
1470
|
+
dataset_file_map[map_key][real_dest_ddm_id] = {
|
|
1471
|
+
"taskID": tmp_job.taskID,
|
|
1472
|
+
"PandaID": tmp_job.PandaID,
|
|
1473
|
+
"useZipToPin": tmp_job.useZipToPin(),
|
|
1474
|
+
"files": {},
|
|
1475
|
+
}
|
|
1476
|
+
if tmp_file.lfn not in dataset_file_map[map_key][real_dest_ddm_id]["files"]:
|
|
1477
|
+
# add scope
|
|
1478
|
+
tmp_lfn = f"{tmp_file.scope}:{tmp_file.lfn}"
|
|
1479
|
+
dataset_file_map[map_key][real_dest_ddm_id]["files"][tmp_file.lfn] = {
|
|
1480
|
+
"lfn": tmp_lfn,
|
|
1481
|
+
"guid": tmp_file.GUID,
|
|
1482
|
+
"fileSpecs": [],
|
|
1483
|
+
}
|
|
1484
|
+
# add file spec
|
|
1485
|
+
dataset_file_map[map_key][real_dest_ddm_id]["files"][tmp_file.lfn]["fileSpecs"].append(tmp_file)
|
|
1486
|
+
return dataset_file_map
|
|
1487
|
+
|
|
1488
|
+
def create_dispatch_datasets(self, dataset_file_map):
|
|
1489
|
+
"""
|
|
1490
|
+
Creates dispatch datasets for the collected files.
|
|
1491
|
+
Returns a list of datasets to be inserted into the database.
|
|
1492
|
+
"""
|
|
1493
|
+
|
|
1494
|
+
tmp_logger = LogWrapper(self.logger, "<create_dispatch_datasets>")
|
|
1495
|
+
|
|
1496
|
+
# loop over all locations
|
|
1497
|
+
disp_list = []
|
|
1498
|
+
for _, tmp_dum_val in dataset_file_map.items():
|
|
1499
|
+
for tmp_location_list in tmp_dum_val:
|
|
1500
|
+
tmp_val = tmp_dum_val[tmp_location_list]
|
|
1501
|
+
for tmp_location in tmp_location_list:
|
|
1502
|
+
tmp_file_list = tmp_val["files"]
|
|
1503
|
+
if tmp_file_list == {}:
|
|
1504
|
+
continue
|
|
1505
|
+
n_max_files = 500
|
|
1506
|
+
i_files = 0
|
|
1507
|
+
i_loop = 0
|
|
1508
|
+
while i_files < len(tmp_file_list):
|
|
1509
|
+
sub_file_names = list(tmp_file_list)[i_files : i_files + n_max_files]
|
|
1510
|
+
if len(sub_file_names) == 0:
|
|
1511
|
+
break
|
|
1512
|
+
# dis name
|
|
1513
|
+
dis_dispatch_block = f"panda.{tmp_val['taskID']}.{time.strftime('%m.%d')}.GEN.{str(uuid.uuid4())}_dis0{i_loop}{tmp_val['PandaID']}"
|
|
1514
|
+
i_files += n_max_files
|
|
1515
|
+
lfns = []
|
|
1516
|
+
guids = []
|
|
1517
|
+
fsizes = []
|
|
1518
|
+
chksums = []
|
|
1519
|
+
tmp_zip_out = {}
|
|
1520
|
+
if tmp_val["useZipToPin"]:
|
|
1521
|
+
dids = [tmp_file_list[tmp_sub_file_name]["lfn"] for tmp_sub_file_name in sub_file_names]
|
|
1522
|
+
tmp_zip_stat, tmp_zip_out = rucioAPI.get_zip_files(dids, [tmp_location])
|
|
1523
|
+
if not tmp_zip_stat:
|
|
1524
|
+
tmp_logger.debug(f"failed to get zip files : {tmp_zip_out}")
|
|
1525
|
+
tmp_zip_out = {}
|
|
1526
|
+
for tmp_sub_file_name in sub_file_names:
|
|
1527
|
+
tmp_lfn = tmp_file_list[tmp_sub_file_name]["lfn"]
|
|
1528
|
+
if tmp_lfn in tmp_zip_out:
|
|
1529
|
+
tmp_zip_file_name = f"{tmp_zip_out[tmp_lfn]['scope']}:{tmp_zip_out[tmp_lfn]['name']}"
|
|
1530
|
+
if tmp_zip_file_name not in lfns:
|
|
1531
|
+
lfns.append(tmp_zip_file_name)
|
|
1532
|
+
guids.append(tmp_zip_out[tmp_lfn]["guid"])
|
|
1533
|
+
fsizes.append(tmp_zip_out[tmp_lfn]["bytes"])
|
|
1534
|
+
chksums.append(tmp_zip_out[tmp_lfn]["adler32"])
|
|
1535
|
+
else:
|
|
1536
|
+
lfns.append(tmp_lfn)
|
|
1537
|
+
guids.append(tmp_file_list[tmp_sub_file_name]["guid"])
|
|
1538
|
+
fsizes.append(int(tmp_file_list[tmp_sub_file_name]["fileSpecs"][0].fsize))
|
|
1539
|
+
chksums.append(tmp_file_list[tmp_sub_file_name]["fileSpecs"][0].checksum)
|
|
1540
|
+
# set dis name
|
|
1541
|
+
for tmp_file_spec in tmp_file_list[tmp_sub_file_name]["fileSpecs"]:
|
|
1542
|
+
if tmp_file_spec.status in ["ready"] and tmp_file_spec.dispatchDBlock == "NULL":
|
|
1543
|
+
tmp_file_spec.dispatchDBlock = dis_dispatch_block
|
|
1544
|
+
# register datasets
|
|
1545
|
+
i_loop += 1
|
|
1546
|
+
max_attempt = 3
|
|
1547
|
+
is_ok = False
|
|
1548
|
+
metadata = {"hidden": True, "purge_replicas": 0}
|
|
1549
|
+
if tmp_val["taskID"] not in [None, "NULL"]:
|
|
1550
|
+
metadata["task_id"] = str(tmp_val["taskID"])
|
|
1551
|
+
|
|
1552
|
+
tmp_logger.debug(f"ext registerNewDataset {dis_dispatch_block} {str(lfns)} {str(guids)} {str(fsizes)} {str(chksums)} {str(metadata)}")
|
|
1553
|
+
|
|
1554
|
+
for attempt in range(max_attempt):
|
|
1555
|
+
try:
|
|
1556
|
+
out = rucioAPI.register_dataset(
|
|
1557
|
+
dis_dispatch_block,
|
|
1558
|
+
lfns,
|
|
1559
|
+
guids,
|
|
1560
|
+
fsizes,
|
|
1561
|
+
chksums,
|
|
1562
|
+
lifetime=7,
|
|
1563
|
+
scope="panda",
|
|
1564
|
+
metadata=metadata,
|
|
1565
|
+
)
|
|
1566
|
+
self.logger.debug(out)
|
|
1567
|
+
is_ok = True
|
|
1568
|
+
break
|
|
1569
|
+
except Exception:
|
|
1570
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1571
|
+
tmp_logger.error(f"ext registerDataset : failed with {error_type}:{error_value}" + traceback.format_exc())
|
|
1572
|
+
if attempt + 1 == max_attempt:
|
|
1573
|
+
break
|
|
1574
|
+
tmp_logger.debug(f"sleep {attempt}/{max_attempt}")
|
|
1575
|
+
time.sleep(10)
|
|
1576
|
+
# failure
|
|
1577
|
+
if not is_ok:
|
|
1578
|
+
continue
|
|
1579
|
+
# get VUID
|
|
1580
|
+
try:
|
|
1581
|
+
vuid = out["vuid"]
|
|
1582
|
+
# dataset spec. currentfiles is used to count the number of failed jobs
|
|
1583
|
+
dataset = DatasetSpec()
|
|
1584
|
+
dataset.vuid = vuid
|
|
1585
|
+
dataset.name = dis_dispatch_block
|
|
1586
|
+
dataset.type = "dispatch"
|
|
1587
|
+
dataset.status = "defined"
|
|
1588
|
+
dataset.numberfiles = len(lfns)
|
|
1589
|
+
dataset.currentfiles = 0
|
|
1590
|
+
if tmp_val["taskID"] not in [None, "NULL"]:
|
|
1591
|
+
dataset.MoverID = tmp_val["taskID"]
|
|
1592
|
+
disp_list.append(dataset)
|
|
1593
|
+
except Exception:
|
|
1594
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1595
|
+
tmp_logger.error(f"ext registerNewDataset : failed to decode VUID for {dis_dispatch_block} - {error_type} {error_value}")
|
|
1596
|
+
continue
|
|
1597
|
+
# freezeDataset dispatch dataset
|
|
1598
|
+
tmp_logger.debug(f"freezeDataset {dis_dispatch_block}")
|
|
1599
|
+
|
|
1600
|
+
for attempt in range(3):
|
|
1601
|
+
status = False
|
|
1602
|
+
try:
|
|
1603
|
+
rucioAPI.close_dataset(dis_dispatch_block)
|
|
1604
|
+
status = True
|
|
1605
|
+
break
|
|
1606
|
+
except Exception:
|
|
1607
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1608
|
+
out = f"failed to close : {error_type} {error_value}"
|
|
1609
|
+
time.sleep(10)
|
|
1610
|
+
if not status:
|
|
1611
|
+
tmp_logger.error(out)
|
|
1612
|
+
continue
|
|
1613
|
+
# register location
|
|
1614
|
+
is_ok = False
|
|
1615
|
+
tmp_logger.debug(f"ext registerDatasetLocation {dis_dispatch_block} {tmp_location} {7}days asynchronous=True")
|
|
1616
|
+
max_attempt = 3
|
|
1617
|
+
for attempt in range(max_attempt):
|
|
1618
|
+
try:
|
|
1619
|
+
out = rucioAPI.register_dataset_location(
|
|
1620
|
+
dis_dispatch_block,
|
|
1621
|
+
[tmp_location],
|
|
1622
|
+
7,
|
|
1623
|
+
activity="Production Input",
|
|
1624
|
+
scope="panda",
|
|
1625
|
+
grouping="NONE",
|
|
1626
|
+
)
|
|
1627
|
+
tmp_logger.debug(out)
|
|
1628
|
+
is_ok = True
|
|
1629
|
+
break
|
|
1630
|
+
except Exception:
|
|
1631
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1632
|
+
tmp_logger.error(f"ext registerDatasetLocation : failed with {error_type}:{error_value}")
|
|
1633
|
+
if attempt + 1 == max_attempt:
|
|
1634
|
+
break
|
|
1635
|
+
tmp_logger.debug(f"sleep {attempt}/{max_attempt}")
|
|
1636
|
+
time.sleep(10)
|
|
1637
|
+
|
|
1638
|
+
# failure
|
|
1639
|
+
if not is_ok:
|
|
1640
|
+
continue
|
|
1641
|
+
return disp_list
|
|
1642
|
+
|
|
1643
|
+
# make dis datasets for existing files to avoid deletion when jobs are queued
|
|
1644
|
+
def make_dis_datasets_for_existing_files(self) -> None:
|
|
1645
|
+
"""
|
|
1646
|
+
Make dis datasets for existing files method for running the setup process.
|
|
1647
|
+
"""
|
|
1648
|
+
tmp_logger = LogWrapper(self.logger, "<make_dis_datasets_for_existing_files>")
|
|
1649
|
+
tmp_logger.debug("start")
|
|
1650
|
+
|
|
1651
|
+
# collect existing files
|
|
1652
|
+
dataset_file_map = self.collect_existing_files()
|
|
1653
|
+
# create dispatch datasets for the collected files
|
|
1654
|
+
disp_list = self.create_dispatch_datasets(dataset_file_map)
|
|
1655
|
+
|
|
1656
|
+
# insert datasets to DB
|
|
1657
|
+
self.task_buffer.insertDatasets(disp_list)
|
|
1658
|
+
tmp_logger.debug("finished")
|
|
1659
|
+
return
|
|
1660
|
+
|
|
1661
|
+
# make subscription
|
|
1662
|
+
def make_subscription(self, dataset: str, ddm_id: str) -> bool:
|
|
1663
|
+
"""
|
|
1664
|
+
Make subscription method for running the setup process.
|
|
1665
|
+
|
|
1666
|
+
:param dataset: The dataset to make the subscription for.
|
|
1667
|
+
:param ddm_id: The DDM ID.
|
|
1668
|
+
:return: A boolean indicating whether the subscription was made successfully.
|
|
1669
|
+
"""
|
|
1670
|
+
ret_failed = False
|
|
1671
|
+
tmp_logger = LogWrapper(self.logger, "<make_subscription>")
|
|
1672
|
+
tmp_logger.debug(f"register_dataset_subscription {dataset} {ddm_id}")
|
|
1673
|
+
for _ in range(3):
|
|
1674
|
+
try:
|
|
1675
|
+
# register subscription
|
|
1676
|
+
status = rucioAPI.register_dataset_subscription(dataset, [ddm_id], activity="Production Input")
|
|
1677
|
+
out = "OK"
|
|
1678
|
+
break
|
|
1679
|
+
except Exception:
|
|
1680
|
+
status = False
|
|
1681
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1682
|
+
out = f"{error_type} {error_value}"
|
|
1683
|
+
time.sleep(10)
|
|
1684
|
+
|
|
1685
|
+
if not status:
|
|
1686
|
+
tmp_logger.error(out)
|
|
1687
|
+
return ret_failed
|
|
1688
|
+
|
|
1689
|
+
tmp_logger.debug(f"{status} {out}")
|
|
1690
|
+
return True
|
|
1691
|
+
|
|
1692
|
+
# setup jumbo jobs
|
|
1693
|
+
def setup_jumbo_jobs(self) -> None:
|
|
1694
|
+
"""
|
|
1695
|
+
Setup jumbo jobs method for running the setup process.
|
|
1696
|
+
"""
|
|
1697
|
+
|
|
1698
|
+
tmp_logger = LogWrapper(self.logger, "<setup_jumbo_jobs>")
|
|
1699
|
+
|
|
1700
|
+
if len(self.jumbo_jobs) == 0:
|
|
1701
|
+
return
|
|
1702
|
+
tmp_logger.debug("start")
|
|
1703
|
+
# get files in datasets
|
|
1704
|
+
datasets_lfns_map = {}
|
|
1705
|
+
failed_ds = set()
|
|
1706
|
+
for jumbo_job_spec in self.jumbo_jobs:
|
|
1707
|
+
for tmp_file_spec in jumbo_job_spec.Files:
|
|
1708
|
+
# only input
|
|
1709
|
+
if tmp_file_spec.type not in ["input"]:
|
|
1710
|
+
continue
|
|
1711
|
+
# get files
|
|
1712
|
+
if tmp_file_spec.dataset not in datasets_lfns_map:
|
|
1713
|
+
if tmp_file_spec.dataset not in failed_ds:
|
|
1714
|
+
tmp_stat, tmp_map = self.get_list_files_in_dataset(tmp_file_spec.dataset, use_cache=False)
|
|
1715
|
+
# failed
|
|
1716
|
+
if tmp_stat != 0:
|
|
1717
|
+
failed_ds.add(tmp_file_spec.dataset)
|
|
1718
|
+
tmp_logger.debug(f"failed to get files in {tmp_file_spec.dataset} with {tmp_map}")
|
|
1719
|
+
else:
|
|
1720
|
+
# append
|
|
1721
|
+
datasets_lfns_map[tmp_file_spec.dataset] = tmp_map
|
|
1722
|
+
# set failed if file lookup failed
|
|
1723
|
+
if tmp_file_spec.dataset in failed_ds:
|
|
1724
|
+
jumbo_job_spec.jobStatus = "failed"
|
|
1725
|
+
jumbo_job_spec.ddmErrorCode = ErrorCode.EC_GUID
|
|
1726
|
+
jumbo_job_spec.ddmErrorDiag = f"failed to get files in {tmp_file_spec.dataset}"
|
|
1727
|
+
break
|
|
1728
|
+
# make dis datasets
|
|
1729
|
+
ok_jobs = []
|
|
1730
|
+
ng_jobs = []
|
|
1731
|
+
for jumbo_job_spec in self.jumbo_jobs:
|
|
1732
|
+
# skip failed
|
|
1733
|
+
if jumbo_job_spec.jobStatus == "failed":
|
|
1734
|
+
ng_jobs.append(jumbo_job_spec)
|
|
1735
|
+
continue
|
|
1736
|
+
# get datatype
|
|
1737
|
+
try:
|
|
1738
|
+
tmp_data_type = jumbo_job_spec.prodDBlock.split(".")[-2]
|
|
1739
|
+
if len(tmp_data_type) > 20:
|
|
1740
|
+
raise RuntimeError(f"data type is too log : {len(tmp_data_type)} chars")
|
|
1741
|
+
except Exception:
|
|
1742
|
+
# default
|
|
1743
|
+
tmp_data_type = "GEN"
|
|
1744
|
+
# files for jumbo job
|
|
1745
|
+
lfns_for_jumbo = self.task_buffer.getLFNsForJumbo(jumbo_job_spec.jediTaskID)
|
|
1746
|
+
# make dis dataset name
|
|
1747
|
+
dispatch_data_block = f"panda.{jumbo_job_spec.taskID}.{time.strftime('%m.%d.%H%M')}.{tmp_data_type}.jumbo_dis{jumbo_job_spec.PandaID}"
|
|
1748
|
+
# collect file attributes
|
|
1749
|
+
lfns = []
|
|
1750
|
+
guids = []
|
|
1751
|
+
sizes = []
|
|
1752
|
+
checksums = []
|
|
1753
|
+
for tmp_file_spec in jumbo_job_spec.Files:
|
|
1754
|
+
# only input
|
|
1755
|
+
if tmp_file_spec.type not in ["input"]:
|
|
1756
|
+
continue
|
|
1757
|
+
for tmp_lfn in datasets_lfns_map[tmp_file_spec.dataset]:
|
|
1758
|
+
tmp_var = datasets_lfns_map[tmp_file_spec.dataset][tmp_lfn]
|
|
1759
|
+
tmp_lfn = f"{tmp_var['scope']}:{tmp_lfn}"
|
|
1760
|
+
if tmp_lfn not in lfns_for_jumbo:
|
|
1761
|
+
continue
|
|
1762
|
+
lfns.append(tmp_lfn)
|
|
1763
|
+
guids.append(tmp_var["guid"])
|
|
1764
|
+
sizes.append(tmp_var["fsize"])
|
|
1765
|
+
checksums.append(tmp_var["chksum"])
|
|
1766
|
+
# set dis dataset
|
|
1767
|
+
tmp_file_spec.dispatchDBlock = dispatch_data_block
|
|
1768
|
+
# register and subscribe dis dataset
|
|
1769
|
+
if len(lfns) != 0:
|
|
1770
|
+
# set dis dataset
|
|
1771
|
+
jumbo_job_spec.dispatchDBlock = dispatch_data_block
|
|
1772
|
+
# register dis dataset
|
|
1773
|
+
try:
|
|
1774
|
+
tmp_logger.debug(f"register_dataset {dispatch_data_block} with {len(lfns)} files")
|
|
1775
|
+
out = rucioAPI.register_dataset(dispatch_data_block, lfns, guids, sizes, checksums, lifetime=14)
|
|
1776
|
+
vuid = out["vuid"]
|
|
1777
|
+
rucioAPI.close_dataset(dispatch_data_block)
|
|
1778
|
+
except Exception:
|
|
1779
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1780
|
+
tmp_logger.debug(f"failed to register jumbo dis dataset {dispatch_data_block} with {error_type}:{error_value}")
|
|
1781
|
+
jumbo_job_spec.jobStatus = "failed"
|
|
1782
|
+
jumbo_job_spec.ddmErrorCode = ErrorCode.EC_Setupper
|
|
1783
|
+
jumbo_job_spec.ddmErrorDiag = f"failed to register jumbo dispatch dataset {dispatch_data_block}"
|
|
1784
|
+
ng_jobs.append(jumbo_job_spec)
|
|
1785
|
+
continue
|
|
1786
|
+
# subscribe dis dataset
|
|
1787
|
+
try:
|
|
1788
|
+
tmp_site_spec = self.site_mapper.getSite(jumbo_job_spec.computingSite)
|
|
1789
|
+
scope_input, _ = select_scope(
|
|
1790
|
+
tmp_site_spec,
|
|
1791
|
+
jumbo_job_spec.prodSourceLabel,
|
|
1792
|
+
jumbo_job_spec.job_label,
|
|
1793
|
+
)
|
|
1794
|
+
end_point = tmp_site_spec.ddm_input[scope_input]
|
|
1795
|
+
tmp_logger.debug(f"register_dataset_subscription {dispatch_data_block} to {end_point}")
|
|
1796
|
+
rucioAPI.register_dataset_subscription(
|
|
1797
|
+
dispatch_data_block,
|
|
1798
|
+
[end_point],
|
|
1799
|
+
lifetime=14,
|
|
1800
|
+
activity="Production Input",
|
|
1801
|
+
)
|
|
1802
|
+
except Exception:
|
|
1803
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1804
|
+
tmp_logger.debug(f"failed to subscribe jumbo dis dataset {dispatch_data_block} to {end_point} with {error_type}:{error_value}")
|
|
1805
|
+
jumbo_job_spec.jobStatus = "failed"
|
|
1806
|
+
jumbo_job_spec.ddmErrorCode = ErrorCode.EC_Setupper
|
|
1807
|
+
jumbo_job_spec.ddmErrorDiag = f"failed to subscribe jumbo dispatch dataset {dispatch_data_block} to {end_point}"
|
|
1808
|
+
ng_jobs.append(jumbo_job_spec)
|
|
1809
|
+
continue
|
|
1810
|
+
|
|
1811
|
+
# add dataset in DB
|
|
1812
|
+
dataset = DatasetSpec()
|
|
1813
|
+
dataset.vuid = vuid
|
|
1814
|
+
dataset.name = dispatch_data_block
|
|
1815
|
+
dataset.type = "dispatch"
|
|
1816
|
+
dataset.status = "defined"
|
|
1817
|
+
dataset.numberfiles = len(lfns)
|
|
1818
|
+
dataset.currentfiles = 0
|
|
1819
|
+
dataset.MoverID = jumbo_job_spec.jediTaskID
|
|
1820
|
+
self.task_buffer.insertDatasets([dataset])
|
|
1821
|
+
# set destination
|
|
1822
|
+
jumbo_job_spec.destinationSE = jumbo_job_spec.computingSite
|
|
1823
|
+
for tmp_file_spec in jumbo_job_spec.Files:
|
|
1824
|
+
if tmp_file_spec.type in ["output", "log"] and DataServiceUtils.getDistributedDestination(tmp_file_spec.destinationDBlockToken) is None:
|
|
1825
|
+
tmp_file_spec.destinationSE = jumbo_job_spec.computingSite
|
|
1826
|
+
ok_jobs.append(jumbo_job_spec)
|
|
1827
|
+
# update failed jobs
|
|
1828
|
+
self.update_failed_jobs(ng_jobs)
|
|
1829
|
+
self.jumbo_jobs = ok_jobs
|
|
1830
|
+
tmp_logger.debug("done")
|
|
1831
|
+
return
|
|
1832
|
+
|
|
1833
|
+
# make sub dataset name
|
|
1834
|
+
def make_sub_dataset_name(self, original_name: str, serial_number: int, task_id: int) -> str:
|
|
1835
|
+
"""
|
|
1836
|
+
Make sub dataset name method for running the setup process.
|
|
1837
|
+
|
|
1838
|
+
:param original_name: The original name of the dataset.
|
|
1839
|
+
:param serial_number: The serial number.
|
|
1840
|
+
:param task_id: The task ID.
|
|
1841
|
+
:return: The sub dataset name.
|
|
1842
|
+
"""
|
|
1843
|
+
try:
|
|
1844
|
+
task_id = int(task_id)
|
|
1845
|
+
if original_name.startswith("user") or original_name.startswith("panda"):
|
|
1846
|
+
part_name = ".".join(original_name.split(".")[:3])
|
|
1847
|
+
else:
|
|
1848
|
+
part_name = f"{'.'.join(original_name.split('.')[:2])}.NA.{'.'.join(original_name.split('.')[3:5])}"
|
|
1849
|
+
return f"{part_name}.{task_id}_sub{serial_number}"
|
|
1850
|
+
except Exception:
|
|
1851
|
+
return f"{original_name}_sub{serial_number}"
|