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,323 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from pandaserver.taskbuffer import JobUtils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def isCachedFile(dataset_name, site_spec):
|
|
7
|
+
"""
|
|
8
|
+
Check if the file is cached on a site using CVMFS, e.g. for DB releases.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
dataset_name (str): The name of the dataset.
|
|
12
|
+
site_spec (SiteSpec): The site specification object.
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if the file is cached, False otherwise.
|
|
15
|
+
"""
|
|
16
|
+
# Site does not use CVMFS
|
|
17
|
+
if site_spec.iscvmfs is not True:
|
|
18
|
+
return False
|
|
19
|
+
# look for DBR
|
|
20
|
+
if not dataset_name.startswith("ddo"):
|
|
21
|
+
return False
|
|
22
|
+
# look for the letter 'v' followed by 6 digits
|
|
23
|
+
if re.search(r"v\d{6}$", dataset_name) is None:
|
|
24
|
+
return False
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# check invalid characters in dataset name
|
|
29
|
+
def checkInvalidCharacters(dataset_name):
|
|
30
|
+
"""
|
|
31
|
+
Checks the validity of a dataset name.
|
|
32
|
+
- The dataset name starts with an alphanumeric character ([A-Za-z0-9]).
|
|
33
|
+
- The rest of the dataset name can contain alphanumeric characters, dots(.), hyphens(-),
|
|
34
|
+
underscores(_), or slashes( /), and its length is between 1 and 255 characters({1, 255}).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
dataset_name: The name of the dataset.
|
|
38
|
+
Returns:
|
|
39
|
+
True if the dataset name is valid, False otherwise.
|
|
40
|
+
"""
|
|
41
|
+
if re.match("^[A-Za-z0-9][A-Za-z0-9\.\-\_/]{1,255}$", dataset_name) is not None:
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# get dataset type
|
|
47
|
+
def getDatasetType(dataset):
|
|
48
|
+
"""
|
|
49
|
+
Get the dataset type by extracting the fifth element from the dataset name.
|
|
50
|
+
|
|
51
|
+
The dataset name is expected to be a string with elements separated by dots.
|
|
52
|
+
For example:
|
|
53
|
+
- 'mc23_13p6TeV:mc23_13p6TeV.801169.Py8EG_A14NNPDF23LO_jj_JZ4.merge.EVNT.e8514_e8528_tid38750682_00' would return 'EVNT'.
|
|
54
|
+
- 'mc23_13p6TeV.801169.Py8EG_A14NNPDF23LO_jj_JZ4.simul.HITS.e8514_e8528_a934_tid41381346_00' would return 'HITS'.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
dataset (str): The name of the dataset.
|
|
58
|
+
Returns:
|
|
59
|
+
str: The dataset type if it can be extracted, otherwise None.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
if dataset.startswith("user") or dataset.startswith("group"):
|
|
63
|
+
return None
|
|
64
|
+
dataset_type = dataset.split(".")[4]
|
|
65
|
+
except Exception:
|
|
66
|
+
dataset_type = None
|
|
67
|
+
return dataset_type
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def getSitesShareDDM(site_mapper, site_name, prod_source_label, job_label, output_share=False):
|
|
71
|
+
"""
|
|
72
|
+
Get sites which share the DDM endpoint.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
site_mapper (SiteMapper): The site mapper object.
|
|
76
|
+
site_name (str): The name of the site.
|
|
77
|
+
prod_source_label (str): The production source label.
|
|
78
|
+
job_label (str): The job label.
|
|
79
|
+
output_share (bool): False to get sites which use the output RSE as input,
|
|
80
|
+
True to get sites which use the input RSEs as output.
|
|
81
|
+
Returns:
|
|
82
|
+
list: A list of site names that share the DDM endpoint.
|
|
83
|
+
"""
|
|
84
|
+
if not site_mapper.checkSite(site_name):
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
site_spec = site_mapper.getSite(site_name)
|
|
88
|
+
scope_site_input, scope_site_output = select_scope(site_spec, prod_source_label, job_label)
|
|
89
|
+
runs_production = site_spec.runs_production()
|
|
90
|
+
runs_analysis = site_spec.runs_analysis()
|
|
91
|
+
|
|
92
|
+
ret_sites = []
|
|
93
|
+
for tmp_site_name, tmp_site_spec in site_mapper.siteSpecList.items():
|
|
94
|
+
scope_tmp_site_input, scope_tmp_site_output = select_scope(tmp_site_spec, prod_source_label, job_label)
|
|
95
|
+
|
|
96
|
+
if (runs_production and not tmp_site_spec.runs_production()) or (runs_analysis and not tmp_site_spec.runs_analysis()):
|
|
97
|
+
continue
|
|
98
|
+
if tmp_site_spec.status != "online":
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
if not output_share and site_spec.ddm_output[scope_site_output] not in tmp_site_spec.ddm_endpoints_input[scope_tmp_site_input].all:
|
|
103
|
+
continue
|
|
104
|
+
if output_share and tmp_site_spec.ddm_output[scope_site_output] not in site_spec.ddm_endpoints_input[scope_tmp_site_input].all:
|
|
105
|
+
continue
|
|
106
|
+
except Exception:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if site_name != tmp_site_spec.sitename and tmp_site_spec.sitename not in ret_sites:
|
|
110
|
+
ret_sites.append(tmp_site_spec.sitename)
|
|
111
|
+
|
|
112
|
+
return ret_sites
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def checkJobDestinationSE(tmp_job):
|
|
116
|
+
"""
|
|
117
|
+
Check if the job has a destination storage element in a file specification.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tmp_job (JobSpec): The job object containing file specifications.
|
|
121
|
+
Returns:
|
|
122
|
+
str: The destination SE if specified, otherwise None.
|
|
123
|
+
"""
|
|
124
|
+
for tmp_file in tmp_job.Files:
|
|
125
|
+
destination_se = getDestinationSE(tmp_file.destinationDBlockToken)
|
|
126
|
+
if destination_se is not None:
|
|
127
|
+
return tmp_file.destinationSE
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def getDestinationSE(destination_dblock_token):
|
|
132
|
+
"""
|
|
133
|
+
Check if the destination is specified (e.g. dst:CERN-PROD_DATADISK) and extract it.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
destination_dblock_token (str): The destination data block token.
|
|
137
|
+
Returns:
|
|
138
|
+
str: The extracted destination if specified, otherwise None.
|
|
139
|
+
"""
|
|
140
|
+
if destination_dblock_token is not None:
|
|
141
|
+
for tmp_token in destination_dblock_token.split(","):
|
|
142
|
+
tmp_match = re.search(r"^dst:([^/]*)(/.*)*$", tmp_token)
|
|
143
|
+
if tmp_match is not None:
|
|
144
|
+
return tmp_match.group(1)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def getDistributedDestination(destination_dblock_token, ignore_empty=True):
|
|
149
|
+
"""
|
|
150
|
+
Check if the destination is distributed (e.g. ddd:CERN-PROD_DATADISK) and extract it.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
destination_dblock_token (str): The destination data block token.
|
|
154
|
+
ignore_empty (bool): Flag to ignore empty locations.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
str: The extracted location if specified, otherwise None.
|
|
158
|
+
"""
|
|
159
|
+
if destination_dblock_token is not None:
|
|
160
|
+
for tmp_token in destination_dblock_token.split(","):
|
|
161
|
+
tmp_match = re.search(r"^ddd:([^/]*)(/.*)*$", tmp_token)
|
|
162
|
+
if tmp_match is not None:
|
|
163
|
+
location = tmp_match.group(1)
|
|
164
|
+
if ignore_empty and not location:
|
|
165
|
+
return None
|
|
166
|
+
return location
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def extractImportantError(message):
|
|
171
|
+
"""
|
|
172
|
+
Extract important error strings from a message. This function searches for specific substrings within a given message and returns
|
|
173
|
+
a concatenated string of lines containing those substrings.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
message (str): The message string to search within.
|
|
177
|
+
Returns:
|
|
178
|
+
str: A concatenated string of lines containing the specified substrings.
|
|
179
|
+
Returns an empty string if no matches are found or an exception occurs.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
# list of strings to search for
|
|
183
|
+
str_list = ["InvalidRSEExpression", "Details:"]
|
|
184
|
+
return_string = " ".join(line for line in message.split("\n") if any(tmp_string in line for tmp_string in str_list))
|
|
185
|
+
except Exception:
|
|
186
|
+
return_string = ""
|
|
187
|
+
return return_string
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def getActivityForOut(prod_source_label):
|
|
191
|
+
"""
|
|
192
|
+
Get the DDM activity type for the job output based on the production source label.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
prod_source_label (str): The production source label.
|
|
196
|
+
Returns:
|
|
197
|
+
str: The activity type for output.
|
|
198
|
+
"""
|
|
199
|
+
if prod_source_label in ["managed"]:
|
|
200
|
+
activity = "Production Output"
|
|
201
|
+
elif prod_source_label in ["user", "panda"]:
|
|
202
|
+
activity = "Analysis Output"
|
|
203
|
+
else:
|
|
204
|
+
activity = "Functional Test"
|
|
205
|
+
return activity
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def select_scope(site_spec, prod_source_label, job_label):
|
|
209
|
+
"""
|
|
210
|
+
Select the scopes of the activity for input and output. The scope was introduced for prod-analy queues where you might want to associate
|
|
211
|
+
different RSEs depending on production or analysis.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
site_spec (SiteSpec): The site specification object.
|
|
215
|
+
prod_source_label (str): The production source label.
|
|
216
|
+
job_label (str): The job label.
|
|
217
|
+
Returns:
|
|
218
|
+
tuple: A tuple containing the input scope and output scope.
|
|
219
|
+
"""
|
|
220
|
+
scope_input = "default"
|
|
221
|
+
aux_scopes_input = site_spec.ddm_endpoints_input.keys()
|
|
222
|
+
if (job_label == JobUtils.ANALY_PS or prod_source_label in JobUtils.analy_sources) and "analysis" in aux_scopes_input:
|
|
223
|
+
scope_input = "analysis"
|
|
224
|
+
|
|
225
|
+
scope_output = "default"
|
|
226
|
+
aux_scopes_output = site_spec.ddm_endpoints_output.keys()
|
|
227
|
+
if (job_label == JobUtils.ANALY_PS or prod_source_label in JobUtils.analy_sources) and "analysis" in aux_scopes_output:
|
|
228
|
+
scope_output = "analysis"
|
|
229
|
+
|
|
230
|
+
return scope_input, scope_output
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def isDBR(dataset_name):
|
|
234
|
+
"""
|
|
235
|
+
Check if the dataset is a DB release. A DB release dataset name starts with 'ddo'.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
dataset_name (str): The name of the dataset.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
bool: True if the dataset name starts with 'ddo', False otherwise.
|
|
242
|
+
"""
|
|
243
|
+
if dataset_name.startswith("ddo"):
|
|
244
|
+
return True
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def is_top_level_dataset(dataset_name: str) -> bool:
|
|
249
|
+
"""
|
|
250
|
+
Check if top dataset. Top datasets do not finish with '_sub' followed by one or more digits.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
dataset_name (str): Dataset name.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
bool: True if top dataset, False otherwise.
|
|
257
|
+
"""
|
|
258
|
+
return re.sub("_sub\d+$", "", dataset_name) == dataset_name
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def is_sub_dataset(dataset_name: str) -> bool:
|
|
262
|
+
"""
|
|
263
|
+
Check if the dataset name ends with '_sub' followed by one or more digits.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
dataset_name (str): The name of the dataset.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
bool: True if the dataset name ends with '_sub' followed by one or more digits, False otherwise.
|
|
270
|
+
"""
|
|
271
|
+
return re.search("_sub\d+$", dataset_name) is not None
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def is_tid_dataset(destination_data_block: str) -> bool:
|
|
275
|
+
"""
|
|
276
|
+
Check if the destination data block ends with '_tid' followed by one or more digits.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
destination_data_block (str): The destination data block.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
bool: True if the destination data block ends with '_tid' followed by one or more digits, False otherwise.
|
|
283
|
+
"""
|
|
284
|
+
return re.search("_tid[\d_]+$", destination_data_block) is not None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def is_hammercloud_dataset(destination_data_block: str) -> bool:
|
|
288
|
+
"""
|
|
289
|
+
Check if the destination data block starts with 'hc_test.'.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
destination_data_block (str): The destination data block.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
bool: True if the destination data block starts with 'hc_test.', False otherwise.
|
|
296
|
+
"""
|
|
297
|
+
return re.search("^hc_test\.", destination_data_block) is not None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def is_user_gangarbt_dataset(destination_data_block: str) -> bool:
|
|
301
|
+
"""
|
|
302
|
+
Check if the destination data block starts with 'user.gangarbt.'.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
destination_data_block (str): The destination data block.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
bool: True if the destination data block starts with 'user.gangarbt.', False otherwise.
|
|
309
|
+
"""
|
|
310
|
+
return re.search("^user\.gangarbt\.", destination_data_block) is not None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def is_lib_dataset(destination_data_block: str) -> bool:
|
|
314
|
+
"""
|
|
315
|
+
Check if the destination data block ends with '.lib'.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
destination_data_block (str): The destination data block.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
bool: True if the destination data block ends with '.lib', False otherwise.
|
|
322
|
+
"""
|
|
323
|
+
return re.search("\.lib$", destination_data_block) is not None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# error code
|
|
2
|
+
|
|
3
|
+
# Setupper
|
|
4
|
+
EC_Setupper = 100
|
|
5
|
+
|
|
6
|
+
# Setupper
|
|
7
|
+
EC_GUID = 101
|
|
8
|
+
|
|
9
|
+
# Adder
|
|
10
|
+
EC_Adder = 200
|
|
11
|
+
|
|
12
|
+
# Subscription failures
|
|
13
|
+
EC_Subscription = 201
|
|
14
|
+
|
|
15
|
+
# location registration failures
|
|
16
|
+
EC_Location = 203
|
|
17
|
+
|
|
18
|
+
# missing nEvents
|
|
19
|
+
EC_MissingNumEvents = 204
|
|
20
|
+
|
|
21
|
+
# inconsistent GUID
|
|
22
|
+
EC_InconsistentGUID = 205
|
|
23
|
+
|
|
24
|
+
# wrong RSE configuration
|
|
25
|
+
EC_RSE = 206
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
6
|
+
from pandacommon.pandautils.thread_utils import GenericThread
|
|
7
|
+
from rucio.client import Client as RucioClient
|
|
8
|
+
from rucio.common.exception import DataIdentifierNotFound
|
|
9
|
+
|
|
10
|
+
from pandaserver.config import panda_config
|
|
11
|
+
from pandaserver.dataservice.ddm import rucioAPI
|
|
12
|
+
from pandaserver.userinterface import Client
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# get files form rucio
|
|
16
|
+
def get_files_from_rucio(ds_name):
|
|
17
|
+
# get files from rucio
|
|
18
|
+
try:
|
|
19
|
+
rc = RucioClient()
|
|
20
|
+
scope, name = rucioAPI.extract_scope(ds_name)
|
|
21
|
+
if name.endswith("/"):
|
|
22
|
+
name = name[:-1]
|
|
23
|
+
files_rucio = set()
|
|
24
|
+
for i in rc.list_files(scope, name):
|
|
25
|
+
files_rucio.add(i["name"])
|
|
26
|
+
return True, files_rucio
|
|
27
|
+
except DataIdentifierNotFound:
|
|
28
|
+
return False, f"unknown dataset {ds_name}"
|
|
29
|
+
except Exception as e:
|
|
30
|
+
msg_str = f"failed to get files from rucio: {str(e)}"
|
|
31
|
+
return None, msg_str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# print a message
|
|
35
|
+
def print_msg(message: str, log_stream: LogWrapper | None, is_error: bool = False, put_log: str | None = None):
|
|
36
|
+
"""
|
|
37
|
+
Print a message to log stream or stdout.
|
|
38
|
+
|
|
39
|
+
:param message: Message string.
|
|
40
|
+
:param log_stream: LogWrapper object or None.
|
|
41
|
+
:param is_error: If True, log as error.
|
|
42
|
+
:param put_log: Log filename to dump log to this file in cache_dir.
|
|
43
|
+
"""
|
|
44
|
+
if log_stream:
|
|
45
|
+
if is_error:
|
|
46
|
+
log_stream.error("Error: " + message)
|
|
47
|
+
else:
|
|
48
|
+
log_stream.info(message)
|
|
49
|
+
if put_log:
|
|
50
|
+
with open(os.path.join(panda_config.cache_dir, put_log), "w") as f:
|
|
51
|
+
f.write(log_stream.dumpToString())
|
|
52
|
+
f.write("done\n")
|
|
53
|
+
else:
|
|
54
|
+
if is_error:
|
|
55
|
+
print("ERROR: ", message)
|
|
56
|
+
else:
|
|
57
|
+
print(message)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# main
|
|
61
|
+
def main(taskBuffer=None, exec_options=None, log_stream=None, args_list=None):
|
|
62
|
+
# options
|
|
63
|
+
parser = argparse.ArgumentParser()
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--ds",
|
|
66
|
+
action="store",
|
|
67
|
+
dest="ds",
|
|
68
|
+
default=None,
|
|
69
|
+
help="dataset name",
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--files",
|
|
73
|
+
action="store",
|
|
74
|
+
dest="files",
|
|
75
|
+
default=None,
|
|
76
|
+
help="comma-separated list of lost file names. The list is deduced if this option is omitted",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"--jediTaskID",
|
|
80
|
+
action="store",
|
|
81
|
+
dest="jediTaskID",
|
|
82
|
+
type=int,
|
|
83
|
+
default=None,
|
|
84
|
+
help="JEDI task ID that produced the dataset",
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--noChildRetry",
|
|
88
|
+
action="store_const",
|
|
89
|
+
const=True,
|
|
90
|
+
dest="noChildRetry",
|
|
91
|
+
default=False,
|
|
92
|
+
help="not retry child tasks",
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--resurrectDS",
|
|
96
|
+
action="store_const",
|
|
97
|
+
const=True,
|
|
98
|
+
dest="resurrectDS",
|
|
99
|
+
default=False,
|
|
100
|
+
help="resurrect output and log datasets if they were already deleted",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--dryRun",
|
|
104
|
+
action="store_const",
|
|
105
|
+
const=True,
|
|
106
|
+
dest="dryRun",
|
|
107
|
+
default=False,
|
|
108
|
+
help="dry run",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--force",
|
|
112
|
+
action="store_const",
|
|
113
|
+
const=True,
|
|
114
|
+
dest="force",
|
|
115
|
+
default=False,
|
|
116
|
+
help="force retry even if no lost files",
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--reproduceParent",
|
|
120
|
+
action="store_const",
|
|
121
|
+
const=True,
|
|
122
|
+
dest="reproduceParent",
|
|
123
|
+
default=False,
|
|
124
|
+
help="reproduce the input files from which the lost files were produced. "
|
|
125
|
+
"Typically useful to recover merged files when unmerged files were already deleted",
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--reproduceUptoNthGen",
|
|
129
|
+
action="store",
|
|
130
|
+
dest="reproduceUptoNthGen",
|
|
131
|
+
type=int,
|
|
132
|
+
default=0,
|
|
133
|
+
help="reproduce files up to N-th upper generation. --reproduceUptoNthGen=1 corresponds "
|
|
134
|
+
"to --reproduceParent. 0 by default so that any provenance files are not reproduced",
|
|
135
|
+
)
|
|
136
|
+
# parse options
|
|
137
|
+
if taskBuffer:
|
|
138
|
+
if args_list:
|
|
139
|
+
options = parser.parse_args(args_list)
|
|
140
|
+
else:
|
|
141
|
+
options, unknown = parser.parse_known_args()
|
|
142
|
+
else:
|
|
143
|
+
if args_list:
|
|
144
|
+
options = parser.parse_args(args_list)
|
|
145
|
+
else:
|
|
146
|
+
options = parser.parse_args()
|
|
147
|
+
|
|
148
|
+
requester_id = GenericThread().get_full_id(__name__, sys.modules[__name__].__file__)
|
|
149
|
+
|
|
150
|
+
if taskBuffer is None:
|
|
151
|
+
# instantiate TB
|
|
152
|
+
from pandaserver.taskbuffer.TaskBuffer import taskBuffer
|
|
153
|
+
|
|
154
|
+
taskBuffer.init(
|
|
155
|
+
panda_config.dbhost,
|
|
156
|
+
panda_config.dbpasswd,
|
|
157
|
+
nDBConnection=1,
|
|
158
|
+
requester=requester_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# set options from dict
|
|
162
|
+
if exec_options is None:
|
|
163
|
+
exec_options = {}
|
|
164
|
+
keys = set(vars(options).keys())
|
|
165
|
+
for k in exec_options:
|
|
166
|
+
if k in keys:
|
|
167
|
+
setattr(options, k, exec_options[k])
|
|
168
|
+
|
|
169
|
+
dn = exec_options.get("userName")
|
|
170
|
+
is_production_manager = exec_options.get("isProductionManager", False)
|
|
171
|
+
log_filename = exec_options.get("logFilename")
|
|
172
|
+
print_msg(f"User='{dn}' is_prod={is_production_manager} log_name={log_filename}", log_stream)
|
|
173
|
+
|
|
174
|
+
if options.reproduceUptoNthGen > 0:
|
|
175
|
+
options.reproduceUptoNthGen -= 1
|
|
176
|
+
options.reproduceParent = True
|
|
177
|
+
|
|
178
|
+
# check if dataset name or taskID is given
|
|
179
|
+
if options.jediTaskID is None and options.ds is None:
|
|
180
|
+
msg_str = "Dataset name or jediTaskID is required"
|
|
181
|
+
print_msg(msg_str, log_stream, is_error=True, put_log=log_filename)
|
|
182
|
+
return False, msg_str
|
|
183
|
+
|
|
184
|
+
ds_files = {}
|
|
185
|
+
if options.files is not None:
|
|
186
|
+
files = options.files.split(",")
|
|
187
|
+
ds_files[options.ds] = files
|
|
188
|
+
else:
|
|
189
|
+
# look for lost files
|
|
190
|
+
if not options.jediTaskID:
|
|
191
|
+
# get files from rucio
|
|
192
|
+
st, files_rucio = get_files_from_rucio(options.ds)
|
|
193
|
+
if st is not True:
|
|
194
|
+
print_msg(files_rucio, log_stream, is_error=True, put_log=log_filename)
|
|
195
|
+
return st, files_rucio
|
|
196
|
+
# get files from panda
|
|
197
|
+
dsName = options.ds.split(":")[-1]
|
|
198
|
+
fd, fo = taskBuffer.querySQLS(
|
|
199
|
+
"SELECT c.lfn FROM ATLAS_PANDA.JEDI_Datasets d,ATLAS_PANDA.JEDI_Dataset_Contents c "
|
|
200
|
+
"WHERE c.jediTaskID=d.jediTaskID AND c.datasetID=d.datasetID AND "
|
|
201
|
+
"d.type=:t1 AND c.status=:s AND d.datasetName=:name ",
|
|
202
|
+
{":s": "finished", ":t1": "output", ":name": dsName},
|
|
203
|
+
)
|
|
204
|
+
for (tmpLFN,) in fo:
|
|
205
|
+
if tmpLFN not in files_rucio:
|
|
206
|
+
ds_files.setdefault(options.ds, [])
|
|
207
|
+
ds_files[options.ds].append(tmpLFN)
|
|
208
|
+
# get taskID
|
|
209
|
+
td, to = taskBuffer.querySQLS(
|
|
210
|
+
"SELECT jediTaskID FROM ATLAS_PANDA.JEDI_Datasets " "WHERE datasetName=:datasetName AND type=:t1 ",
|
|
211
|
+
{":t1": "output", ":datasetName": dsName},
|
|
212
|
+
)
|
|
213
|
+
(jediTaskID,) = to[0]
|
|
214
|
+
else:
|
|
215
|
+
# get dataset names
|
|
216
|
+
dd, do = taskBuffer.querySQLS(
|
|
217
|
+
"SELECT datasetName FROM ATLAS_PANDA.JEDI_Datasets " "WHERE jediTaskID=:jediTaskID AND type=:t1 ",
|
|
218
|
+
{":t1": "output", ":jediTaskID": options.jediTaskID},
|
|
219
|
+
)
|
|
220
|
+
# get files from rucio
|
|
221
|
+
files_rucio = set()
|
|
222
|
+
for (tmpDS,) in do:
|
|
223
|
+
st, tmp_files_rucio = get_files_from_rucio(tmpDS)
|
|
224
|
+
if st is None:
|
|
225
|
+
print_msg(tmp_files_rucio, log_stream, is_error=True, put_log=log_filename)
|
|
226
|
+
return st, tmp_files_rucio
|
|
227
|
+
# ignore unknown dataset
|
|
228
|
+
if st:
|
|
229
|
+
files_rucio = files_rucio.union(tmp_files_rucio)
|
|
230
|
+
# get files from PanDA
|
|
231
|
+
fd, fo = taskBuffer.querySQLS(
|
|
232
|
+
"SELECT d.datasetName,c.lfn FROM ATLAS_PANDA.JEDI_Datasets d,ATLAS_PANDA.JEDI_Dataset_Contents c "
|
|
233
|
+
"WHERE d.jediTaskID=:jediTaskID AND c.jediTaskID=d.jediTaskID AND c.datasetID=d.datasetID AND "
|
|
234
|
+
"d.type=:t1 AND c.status=:s ",
|
|
235
|
+
{
|
|
236
|
+
":s": "finished",
|
|
237
|
+
":t1": "output",
|
|
238
|
+
":jediTaskID": options.jediTaskID,
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
for tmpDS, tmpLFN in fo:
|
|
242
|
+
if tmpLFN not in files_rucio:
|
|
243
|
+
ds_files.setdefault(tmpDS, [])
|
|
244
|
+
ds_files[tmpDS].append(tmpLFN)
|
|
245
|
+
for tmpDS in ds_files:
|
|
246
|
+
files = ds_files[tmpDS]
|
|
247
|
+
msg_str = f"{tmpDS} has {len(files)} lost files -> {','.join(files)}"
|
|
248
|
+
print_msg(msg_str, log_stream)
|
|
249
|
+
|
|
250
|
+
# no lost files
|
|
251
|
+
if not ds_files and not options.force:
|
|
252
|
+
msg_str = "No lost files. Use --force to ignore this check"
|
|
253
|
+
print_msg(msg_str, log_stream, put_log=log_filename)
|
|
254
|
+
return True, msg_str
|
|
255
|
+
|
|
256
|
+
# reset file status
|
|
257
|
+
s = False
|
|
258
|
+
for tmpDS in ds_files:
|
|
259
|
+
files = ds_files[tmpDS]
|
|
260
|
+
if not is_production_manager:
|
|
261
|
+
ts, jediTaskID, lostInputFiles, error_message = taskBuffer.resetFileStatusInJEDI(dn, False, tmpDS, files, options.reproduceParent, options.dryRun)
|
|
262
|
+
else:
|
|
263
|
+
ts, jediTaskID, lostInputFiles, error_message = taskBuffer.resetFileStatusInJEDI("", True, tmpDS, files, options.reproduceParent, options.dryRun)
|
|
264
|
+
if error_message:
|
|
265
|
+
msg_str = f"Failed to reset file status in the DB since {error_message}"
|
|
266
|
+
print_msg(msg_str, log_stream, is_error=True)
|
|
267
|
+
else:
|
|
268
|
+
msg_str = f"Reset file status for {tmpDS} in the DB: done with {ts} for jediTaskID={jediTaskID}"
|
|
269
|
+
print_msg(msg_str, log_stream)
|
|
270
|
+
s |= ts
|
|
271
|
+
# recover provenance
|
|
272
|
+
if options.reproduceParent:
|
|
273
|
+
# reproduce input
|
|
274
|
+
for lostDS in lostInputFiles:
|
|
275
|
+
com_args = ["--ds", lostDS, "--noChildRetry", "--resurrectDS"]
|
|
276
|
+
if options.reproduceUptoNthGen > 0:
|
|
277
|
+
com_args += [
|
|
278
|
+
"--reproduceUptoNthGen",
|
|
279
|
+
str(options.reproduceUptoNthGen),
|
|
280
|
+
]
|
|
281
|
+
if options.dryRun:
|
|
282
|
+
com_args.append("--dryRun")
|
|
283
|
+
com_args += ["--files", ",".join(lostInputFiles[lostDS])]
|
|
284
|
+
main(taskBuffer=taskBuffer, log_stream=log_stream, args_list=com_args)
|
|
285
|
+
|
|
286
|
+
# go ahead
|
|
287
|
+
if options.dryRun:
|
|
288
|
+
msg_str = f"Done in the dry-run mode with {s}"
|
|
289
|
+
print_msg(msg_str, log_stream, put_log=log_filename)
|
|
290
|
+
return True, msg_str
|
|
291
|
+
if s or options.force:
|
|
292
|
+
if options.resurrectDS:
|
|
293
|
+
sd, so = taskBuffer.querySQLS(
|
|
294
|
+
"SELECT datasetName FROM ATLAS_PANDA.JEDI_Datasets WHERE jediTaskID=:id AND type IN (:t1,:t2)",
|
|
295
|
+
{":id": jediTaskID, ":t1": "output", ":t2": "log"},
|
|
296
|
+
)
|
|
297
|
+
rc = RucioClient()
|
|
298
|
+
for (datasetName,) in so:
|
|
299
|
+
for i in range(3):
|
|
300
|
+
try:
|
|
301
|
+
scope, name = rucioAPI.extract_scope(datasetName)
|
|
302
|
+
rc.get_did(scope, name)
|
|
303
|
+
break
|
|
304
|
+
except DataIdentifierNotFound:
|
|
305
|
+
msg_str = f"resurrect {datasetName}"
|
|
306
|
+
print_msg(msg_str, log_stream)
|
|
307
|
+
rc.resurrect([{"scope": scope, "name": name}])
|
|
308
|
+
try:
|
|
309
|
+
rc.set_metadata(scope, name, "lifetime", None)
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
if not options.reproduceParent:
|
|
313
|
+
tmp_ret = Client.retry_task(jediTaskID, no_child_retry=options.noChildRetry)
|
|
314
|
+
else:
|
|
315
|
+
tmp_ret = Client.reload_input(jediTaskID)
|
|
316
|
+
msg_str = f"Retried task {jediTaskID}: done with {tmp_ret}"
|
|
317
|
+
print_msg(msg_str, log_stream, put_log=log_filename)
|
|
318
|
+
return True, msg_str
|
|
319
|
+
else:
|
|
320
|
+
msg_str = "Failed"
|
|
321
|
+
print_msg(msg_str, log_stream, is_error=True, put_log=log_filename)
|
|
322
|
+
return False, msg_str
|
|
File without changes
|