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,824 @@
|
|
|
1
|
+
"""
|
|
2
|
+
find candidate site to distribute input datasets
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import fnmatch
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Dict, List, Tuple
|
|
12
|
+
|
|
13
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
14
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
15
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
16
|
+
|
|
17
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
18
|
+
from pandaserver.dataservice.ddm import rucioAPI
|
|
19
|
+
from pandaserver.taskbuffer import JobUtils
|
|
20
|
+
|
|
21
|
+
_logger = PandaLogger().getLogger("dyn_data_distributer")
|
|
22
|
+
|
|
23
|
+
# files in datasets
|
|
24
|
+
g_files_in_ds_map = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DynDataDistributer:
|
|
28
|
+
"""
|
|
29
|
+
Find candidate site to distribute input datasets.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, jobs, siteMapper, simul=False, token=None, dataset_lifetime=14):
|
|
33
|
+
self.jobs = jobs
|
|
34
|
+
self.site_mapper = siteMapper
|
|
35
|
+
if token is None:
|
|
36
|
+
self.token = naive_utcnow().isoformat(" ")
|
|
37
|
+
else:
|
|
38
|
+
self.token = token
|
|
39
|
+
# use a fixed list since some clouds don't have active T2s
|
|
40
|
+
self.simul = simul
|
|
41
|
+
self.last_message = ""
|
|
42
|
+
# lifetime for temporary datasets
|
|
43
|
+
self.dataset_lifetime = dataset_lifetime
|
|
44
|
+
|
|
45
|
+
def get_replica_locations(self, input_ds: str, check_used_file: bool) -> Tuple[bool, Dict]:
|
|
46
|
+
"""
|
|
47
|
+
Get replica locations for a given dataset.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
input_ds (str): The name of the input dataset.
|
|
51
|
+
check_used_file (bool): Flag to check used file.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
tuple: A tuple containing the status (bool) and the result (dict or str).
|
|
55
|
+
"""
|
|
56
|
+
tmp_log = LogWrapper(_logger, f"get_replica_locations-{naive_utcnow().isoformat('/')}")
|
|
57
|
+
tmp_log.debug(f"get_replica_locations {input_ds}")
|
|
58
|
+
|
|
59
|
+
# return for failure
|
|
60
|
+
res_for_failure = False, {"": {"": ([], [], [], 0, False, False, 0, 0, [])}}
|
|
61
|
+
|
|
62
|
+
# get replica locations
|
|
63
|
+
if input_ds.endswith("/"):
|
|
64
|
+
# container
|
|
65
|
+
status, tmp_rep_maps = self.get_list_dataset_replicas_in_container(input_ds)
|
|
66
|
+
# get used datasets
|
|
67
|
+
if status and check_used_file:
|
|
68
|
+
status, tmp_used_dataset_list = self.get_used_datasets(tmp_rep_maps)
|
|
69
|
+
# remove unused datasets
|
|
70
|
+
new_rep_maps = {}
|
|
71
|
+
for tmp_key, tmp_val in tmp_rep_maps.items():
|
|
72
|
+
if tmp_key in tmp_used_dataset_list:
|
|
73
|
+
new_rep_maps[tmp_key] = tmp_val
|
|
74
|
+
tmp_rep_maps = new_rep_maps
|
|
75
|
+
else:
|
|
76
|
+
# normal dataset
|
|
77
|
+
status, tmp_rep_map = self.get_list_dataset_replicas(input_ds)
|
|
78
|
+
tmp_rep_maps = {input_ds: tmp_rep_map}
|
|
79
|
+
|
|
80
|
+
if not status:
|
|
81
|
+
# failed
|
|
82
|
+
tmp_log.error("failed to get replica locations for {input_ds}")
|
|
83
|
+
tmp_log.debug("end")
|
|
84
|
+
return res_for_failure
|
|
85
|
+
|
|
86
|
+
tmp_log.debug("end")
|
|
87
|
+
return True, tmp_rep_maps
|
|
88
|
+
|
|
89
|
+
def get_all_sites(self) -> List:
|
|
90
|
+
"""
|
|
91
|
+
Retrieves all sites that meet certain conditions.
|
|
92
|
+
|
|
93
|
+
This method filters out sites based on the following conditions:
|
|
94
|
+
- The site should be capable of running analysis.
|
|
95
|
+
- The site should not be a GPU site.
|
|
96
|
+
- The site should not use VP.
|
|
97
|
+
- The status of the site should be "online".
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
list: A list of SiteSpec objects that meet the above conditions.
|
|
101
|
+
"""
|
|
102
|
+
all_sites = []
|
|
103
|
+
for site_name in self.site_mapper.siteSpecList:
|
|
104
|
+
site_spec = self.site_mapper.siteSpecList[site_name]
|
|
105
|
+
# analysis only
|
|
106
|
+
if not site_spec.runs_analysis():
|
|
107
|
+
continue
|
|
108
|
+
# skip GPU
|
|
109
|
+
if site_spec.isGPU():
|
|
110
|
+
continue
|
|
111
|
+
# skip VP
|
|
112
|
+
if site_spec.use_vp(JobUtils.ANALY_PS):
|
|
113
|
+
continue
|
|
114
|
+
# online
|
|
115
|
+
if site_spec.status not in ["online"]:
|
|
116
|
+
continue
|
|
117
|
+
all_sites.append(site_spec)
|
|
118
|
+
return all_sites
|
|
119
|
+
|
|
120
|
+
def get_candidate_sites(self, tmp_rep_maps: Dict, prod_source_label: str, job_label: str) -> Tuple[bool, Dict]:
|
|
121
|
+
"""
|
|
122
|
+
Retrieves candidate sites for data distribution based on certain conditions.
|
|
123
|
+
|
|
124
|
+
This method filters out candidate sites based on the following condition:
|
|
125
|
+
- The site should have a replica of the dataset.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
tmp_rep_maps (dict): A dictionary containing dataset names as keys and their replica maps as values.
|
|
129
|
+
prod_source_label (str): The label of the production source.
|
|
130
|
+
job_label (str): The label of the job.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
tuple: A tuple containing a boolean status and a dictionary. The dictionary has dataset names as keys and
|
|
134
|
+
another dictionary as values. The inner dictionary has cloud names as keys and a tuple of various
|
|
135
|
+
site-related lists and values as values.
|
|
136
|
+
"""
|
|
137
|
+
all_site_map = self.get_all_sites()
|
|
138
|
+
return_map = {}
|
|
139
|
+
cloud = "WORLD"
|
|
140
|
+
for tmp_ds, tmp_rep_map in tmp_rep_maps.items():
|
|
141
|
+
cand_sites = []
|
|
142
|
+
sites_com_ds = []
|
|
143
|
+
sites_comp_pd2p = []
|
|
144
|
+
t1_has_replica = False
|
|
145
|
+
t1_has_primary = False
|
|
146
|
+
n_sec_replicas = 0
|
|
147
|
+
cand_for_mou = []
|
|
148
|
+
n_user_sub = 0
|
|
149
|
+
for tmp_site_spec in all_site_map:
|
|
150
|
+
tmp_scope_input, tmp_scope_output = select_scope(tmp_site_spec, prod_source_label, job_label)
|
|
151
|
+
if tmp_scope_input not in tmp_site_spec.ddm_endpoints_input:
|
|
152
|
+
continue
|
|
153
|
+
rses = tmp_site_spec.ddm_endpoints_input[tmp_scope_input].getLocalEndPoints()
|
|
154
|
+
for ddm_endpoint in tmp_rep_map:
|
|
155
|
+
tmp_stat_map = tmp_rep_map[ddm_endpoint]
|
|
156
|
+
if ddm_endpoint in rses and tmp_stat_map[0]["total"] == tmp_stat_map[0]["found"] and ddm_endpoint.endswith("DATADISK"):
|
|
157
|
+
sites_com_ds.append(tmp_site_spec.sitename)
|
|
158
|
+
break
|
|
159
|
+
cand_sites.append(tmp_site_spec.sitename)
|
|
160
|
+
return_map.setdefault(tmp_ds, {})
|
|
161
|
+
if sites_com_ds:
|
|
162
|
+
cand_sites = sites_com_ds
|
|
163
|
+
return_map[tmp_ds][cloud] = (
|
|
164
|
+
cand_sites,
|
|
165
|
+
sites_com_ds,
|
|
166
|
+
sites_comp_pd2p,
|
|
167
|
+
n_user_sub,
|
|
168
|
+
t1_has_replica,
|
|
169
|
+
t1_has_primary,
|
|
170
|
+
n_sec_replicas,
|
|
171
|
+
0,
|
|
172
|
+
cand_for_mou,
|
|
173
|
+
)
|
|
174
|
+
return True, return_map
|
|
175
|
+
|
|
176
|
+
def get_candidates(self, input_ds: str, prod_source_label: str, job_label: str, check_used_file: bool = True) -> Tuple[bool, Dict]:
|
|
177
|
+
"""
|
|
178
|
+
Get candidate sites for subscription.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
input_ds (str): The name of the input dataset.
|
|
182
|
+
prod_source_label (str): The label of the production source.
|
|
183
|
+
job_label (str): The label of the job.
|
|
184
|
+
check_used_file (bool, optional): Flag to check used file. Defaults to True.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
tuple: A tuple containing the status (bool) and the result (dict or str).
|
|
188
|
+
"""
|
|
189
|
+
# Get replica locations
|
|
190
|
+
status, tmp_rep_maps = self.get_replica_locations(input_ds, check_used_file)
|
|
191
|
+
if not status:
|
|
192
|
+
return status, tmp_rep_maps
|
|
193
|
+
|
|
194
|
+
# Get candidate sites
|
|
195
|
+
return self.get_candidate_sites(tmp_rep_maps, prod_source_label, job_label)
|
|
196
|
+
|
|
197
|
+
def get_list_dataset_replicas(self, dataset: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
|
|
198
|
+
"""
|
|
199
|
+
Get the list of replicas for a given dataset.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
dataset (str): The name of the dataset.
|
|
203
|
+
max_attempts (int, optional): The maximum number of attempts to get the replicas. Defaults to 3.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
tuple: A tuple containing the status (bool) and the result (dict or str).
|
|
207
|
+
"""
|
|
208
|
+
tmp_log = LogWrapper(_logger, f"get_list_dataset_replicas-{naive_utcnow().isoformat('/')}")
|
|
209
|
+
tmp_log.debug(f"get_list_dataset_replicas {dataset}")
|
|
210
|
+
|
|
211
|
+
for attempt in range(max_attempts):
|
|
212
|
+
tmp_log.debug(f"{attempt}/{max_attempts} listDatasetReplicas {dataset}")
|
|
213
|
+
status, replicas = rucioAPI.list_dataset_replicas(dataset)
|
|
214
|
+
if status == 0:
|
|
215
|
+
tmp_log.debug(f"get_list_dataset_replicas->{str(replicas)}")
|
|
216
|
+
tmp_log.debug("end")
|
|
217
|
+
return True, replicas
|
|
218
|
+
time.sleep(10)
|
|
219
|
+
|
|
220
|
+
tmp_log.error(f"bad response for {dataset}")
|
|
221
|
+
tmp_log.debug("end")
|
|
222
|
+
return False, {}
|
|
223
|
+
|
|
224
|
+
def get_list_dataset_replicas_in_container(self, container: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
|
|
225
|
+
"""
|
|
226
|
+
Get the list of replicas for a given container.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
container (str): The name of the container.
|
|
230
|
+
max_attempts (int, optional): The maximum number of attempts to get the replicas. Defaults to 3.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
tuple: A tuple containing the status (bool) and the result (dict or str).
|
|
234
|
+
"""
|
|
235
|
+
tmp_log = LogWrapper(_logger, f"get_list_dataset_replicas_in_container-{naive_utcnow().isoformat('/')}")
|
|
236
|
+
|
|
237
|
+
tmp_log.debug(f"get_list_dataset_replicas_in_container {container}")
|
|
238
|
+
|
|
239
|
+
# response for failure
|
|
240
|
+
res_for_failure = False, {}
|
|
241
|
+
|
|
242
|
+
# get datasets in container
|
|
243
|
+
for attempt in range(max_attempts):
|
|
244
|
+
tmp_log.debug(f"{attempt}/{max_attempts} listDatasetsInContainer {container}")
|
|
245
|
+
datasets, out = rucioAPI.list_datasets_in_container(container)
|
|
246
|
+
if datasets is not None:
|
|
247
|
+
break
|
|
248
|
+
time.sleep(60)
|
|
249
|
+
|
|
250
|
+
if datasets is None:
|
|
251
|
+
tmp_log.error(out)
|
|
252
|
+
tmp_log.error(f"bad DDM response for {container}")
|
|
253
|
+
tmp_log.debug("end")
|
|
254
|
+
return res_for_failure
|
|
255
|
+
|
|
256
|
+
# loop over all datasets
|
|
257
|
+
all_rep_map = {}
|
|
258
|
+
for dataset in datasets:
|
|
259
|
+
# get replicas
|
|
260
|
+
status, tmp_rep_sites = self.get_list_dataset_replicas(dataset)
|
|
261
|
+
if not status:
|
|
262
|
+
tmp_log.debug("end")
|
|
263
|
+
return res_for_failure
|
|
264
|
+
# append
|
|
265
|
+
all_rep_map[dataset] = tmp_rep_sites
|
|
266
|
+
|
|
267
|
+
# return
|
|
268
|
+
tmp_log.debug("end")
|
|
269
|
+
return True, all_rep_map
|
|
270
|
+
|
|
271
|
+
def get_used_datasets(self, dataset_map: Dict, max_attempts: int = 3) -> Tuple[bool, Dict]:
|
|
272
|
+
"""
|
|
273
|
+
Get the datasets that are used by jobs.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
dataset_map (dict): The map of datasets.
|
|
277
|
+
max_attempts (int, optional): The maximum number of attempts to get the file list. Defaults to 3.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
tuple: A tuple containing the status (bool) and the used datasets list.
|
|
281
|
+
"""
|
|
282
|
+
tmp_log = LogWrapper(_logger, f"get_used_datasets-{naive_utcnow().isoformat('/')}")
|
|
283
|
+
tmp_log.debug(f"get_used_datasets {str(dataset_map)}")
|
|
284
|
+
|
|
285
|
+
res_for_failure = (False, [])
|
|
286
|
+
used_ds_list = []
|
|
287
|
+
|
|
288
|
+
# loop over all datasets
|
|
289
|
+
for dataset_name in dataset_map:
|
|
290
|
+
# get file list
|
|
291
|
+
for attempt in range(max_attempts):
|
|
292
|
+
try:
|
|
293
|
+
tmp_log.debug(f"{attempt}/{max_attempts} listFilesInDataset {dataset_name}")
|
|
294
|
+
file_items, out = rucioAPI.list_files_in_dataset(dataset_name)
|
|
295
|
+
status = True
|
|
296
|
+
break
|
|
297
|
+
except Exception:
|
|
298
|
+
status = False
|
|
299
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
300
|
+
out = f"{err_type} {err_value}"
|
|
301
|
+
time.sleep(60)
|
|
302
|
+
|
|
303
|
+
if not status:
|
|
304
|
+
tmp_log.error(out)
|
|
305
|
+
tmp_log.error(f"bad DDM response to get size of {dataset_name}")
|
|
306
|
+
tmp_log.debug("end")
|
|
307
|
+
return res_for_failure
|
|
308
|
+
|
|
309
|
+
# check if jobs use the dataset
|
|
310
|
+
used_flag = False
|
|
311
|
+
for tmp_job in self.jobs:
|
|
312
|
+
for tmp_file in tmp_job.Files:
|
|
313
|
+
if tmp_file.type == "input" and tmp_file.lfn in file_items:
|
|
314
|
+
used_flag = True
|
|
315
|
+
break
|
|
316
|
+
# escape
|
|
317
|
+
if used_flag:
|
|
318
|
+
break
|
|
319
|
+
|
|
320
|
+
# used
|
|
321
|
+
if used_flag:
|
|
322
|
+
used_ds_list.append(dataset_name)
|
|
323
|
+
|
|
324
|
+
# return
|
|
325
|
+
tmp_log.debug(f"used datasets = {str(used_ds_list)}")
|
|
326
|
+
tmp_log.debug("end")
|
|
327
|
+
return True, used_ds_list
|
|
328
|
+
|
|
329
|
+
def get_file_from_dataset(self, dataset_name: str, guid: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
|
|
330
|
+
"""
|
|
331
|
+
Get file information from a dataset.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
dataset_name (str): The name of the dataset.
|
|
335
|
+
guid (str): The GUID of the file.
|
|
336
|
+
max_attempts (int, optional): The maximum number of attempts to get the file list. Defaults to 3.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
tuple: A tuple containing the status (bool) and the file information (dict or None).
|
|
340
|
+
"""
|
|
341
|
+
tmp_log = LogWrapper(_logger, f"get_file_from_dataset-{naive_utcnow().isoformat('/')}")
|
|
342
|
+
tmp_log.debug(f"get_file_from_dataset {dataset_name} {guid}")
|
|
343
|
+
|
|
344
|
+
res_for_failure = (False, None)
|
|
345
|
+
|
|
346
|
+
# get files in datasets
|
|
347
|
+
global g_files_in_ds_map
|
|
348
|
+
if dataset_name not in g_files_in_ds_map:
|
|
349
|
+
# get file list
|
|
350
|
+
for attempt in range(max_attempts):
|
|
351
|
+
try:
|
|
352
|
+
tmp_log.debug(f"{attempt}/{max_attempts} listFilesInDataset {dataset_name}")
|
|
353
|
+
file_items, out = rucioAPI.list_files_in_dataset(dataset_name)
|
|
354
|
+
status = True
|
|
355
|
+
break
|
|
356
|
+
except Exception:
|
|
357
|
+
status = False
|
|
358
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
359
|
+
out = f"{err_type} {err_value}"
|
|
360
|
+
time.sleep(60)
|
|
361
|
+
|
|
362
|
+
if not status:
|
|
363
|
+
tmp_log.error(out)
|
|
364
|
+
tmp_log.error(f"bad DDM response to get size of {dataset_name}")
|
|
365
|
+
tmp_log.debug("end")
|
|
366
|
+
return res_for_failure
|
|
367
|
+
# append
|
|
368
|
+
g_files_in_ds_map[dataset_name] = file_items
|
|
369
|
+
|
|
370
|
+
# check if file is in the dataset
|
|
371
|
+
for tmp_lfn, tmp_val in g_files_in_ds_map[dataset_name].items():
|
|
372
|
+
if uuid.UUID(tmp_val["guid"]) == uuid.UUID(guid):
|
|
373
|
+
ret_map = tmp_val
|
|
374
|
+
ret_map["lfn"] = tmp_lfn
|
|
375
|
+
ret_map["dataset"] = dataset_name
|
|
376
|
+
tmp_log.debug("end")
|
|
377
|
+
return True, ret_map
|
|
378
|
+
|
|
379
|
+
tmp_log.debug("end")
|
|
380
|
+
return res_for_failure
|
|
381
|
+
|
|
382
|
+
def register_dataset_container_with_datasets(
|
|
383
|
+
self, container_name: str, files: List, replica_map: Dict, n_sites: int = 1, owner: str = None, max_attempts: int = 3
|
|
384
|
+
) -> Tuple[bool, Dict]:
|
|
385
|
+
"""
|
|
386
|
+
Register a new dataset container with datasets.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
container_name (str): The name of the container.
|
|
390
|
+
files (list): The list of files to be included in the dataset.
|
|
391
|
+
replica_map (dict): The map of replicas.
|
|
392
|
+
n_sites (int, optional): The number of sites. Defaults to 1.
|
|
393
|
+
owner (str, optional): The owner of the dataset. Defaults to None.
|
|
394
|
+
max_attempts (int, optional): The maximum number of attempts to register the container. Defaults to 3.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
bool: The status of the registration process.
|
|
398
|
+
"""
|
|
399
|
+
tmp_logger = LogWrapper(_logger, f"register_dataset_container_with_datasets-{naive_utcnow().isoformat('/')}")
|
|
400
|
+
tmp_logger.debug(f"register_dataset_container_with_datasets {container_name}")
|
|
401
|
+
|
|
402
|
+
# parse DN
|
|
403
|
+
if owner is not None:
|
|
404
|
+
status, user_info = rucioAPI.finger(owner)
|
|
405
|
+
if not status:
|
|
406
|
+
tmp_logger.debug(f"failed to finger: {user_info}")
|
|
407
|
+
else:
|
|
408
|
+
owner = user_info["nickname"]
|
|
409
|
+
tmp_logger.debug(f"parsed DN={owner}")
|
|
410
|
+
|
|
411
|
+
# sort by locations
|
|
412
|
+
files_map = {}
|
|
413
|
+
for tmp_file in files:
|
|
414
|
+
tmp_locations = sorted(replica_map[tmp_file["dataset"]])
|
|
415
|
+
new_locations = []
|
|
416
|
+
# skip STAGING
|
|
417
|
+
for tmp_location in tmp_locations:
|
|
418
|
+
if not tmp_location.endswith("STAGING"):
|
|
419
|
+
new_locations.append(tmp_location)
|
|
420
|
+
if not new_locations:
|
|
421
|
+
continue
|
|
422
|
+
tmp_locations = new_locations
|
|
423
|
+
tmp_key = tuple(tmp_locations)
|
|
424
|
+
files_map.setdefault(tmp_key, [])
|
|
425
|
+
# append file
|
|
426
|
+
files_map[tmp_key].append(tmp_file)
|
|
427
|
+
|
|
428
|
+
# get nfiles per dataset
|
|
429
|
+
n_files_per_dataset, _ = divmod(len(files), n_sites)
|
|
430
|
+
if n_files_per_dataset == 0:
|
|
431
|
+
n_files_per_dataset = 1
|
|
432
|
+
max_files_per_dataset = 1000
|
|
433
|
+
if n_files_per_dataset >= max_files_per_dataset:
|
|
434
|
+
n_files_per_dataset = max_files_per_dataset
|
|
435
|
+
|
|
436
|
+
# register new datasets
|
|
437
|
+
dataset_names = []
|
|
438
|
+
tmp_index = 1
|
|
439
|
+
for tmp_locations, tmp_files in files_map.items():
|
|
440
|
+
tmp_sub_index = 0
|
|
441
|
+
while tmp_sub_index < len(tmp_files):
|
|
442
|
+
tmp_dataset_name = container_name[:-1] + "_%04d" % tmp_index
|
|
443
|
+
tmp_ret = self.register_dataset_with_location(
|
|
444
|
+
tmp_dataset_name,
|
|
445
|
+
tmp_files[tmp_sub_index : tmp_sub_index + n_files_per_dataset],
|
|
446
|
+
tmp_locations,
|
|
447
|
+
owner=None,
|
|
448
|
+
)
|
|
449
|
+
# failed
|
|
450
|
+
if not tmp_ret:
|
|
451
|
+
tmp_logger.error(f"failed to register {tmp_dataset_name}")
|
|
452
|
+
tmp_logger.debug("end")
|
|
453
|
+
return False
|
|
454
|
+
# append dataset
|
|
455
|
+
dataset_names.append(tmp_dataset_name)
|
|
456
|
+
tmp_index += 1
|
|
457
|
+
tmp_sub_index += n_files_per_dataset
|
|
458
|
+
|
|
459
|
+
# register container
|
|
460
|
+
for attempt in range(max_attempts):
|
|
461
|
+
try:
|
|
462
|
+
tmp_logger.debug(f"{attempt}/{max_attempts} registerContainer {container_name}")
|
|
463
|
+
status = rucioAPI.register_container(container_name, dataset_names)
|
|
464
|
+
out = "OK"
|
|
465
|
+
break
|
|
466
|
+
except Exception:
|
|
467
|
+
status = False
|
|
468
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
469
|
+
out = f"{err_type} {err_value}"
|
|
470
|
+
time.sleep(10)
|
|
471
|
+
|
|
472
|
+
if not status:
|
|
473
|
+
tmp_logger.error(out)
|
|
474
|
+
tmp_logger.error(f"bad DDM response to register {container_name}")
|
|
475
|
+
tmp_logger.debug("end")
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
# return
|
|
479
|
+
tmp_logger.debug(out)
|
|
480
|
+
tmp_logger.debug("end")
|
|
481
|
+
return True
|
|
482
|
+
|
|
483
|
+
def register_dataset_with_location(self, dataset_name: str, files: List, locations: List, owner: str = None, max_attempts: int = 3) -> bool:
|
|
484
|
+
"""
|
|
485
|
+
Register a new dataset with locations.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
dataset_name (str): The name of the dataset.
|
|
489
|
+
files (list): The list of files to be included in the dataset.
|
|
490
|
+
locations (list): The list of locations where the dataset will be registered.
|
|
491
|
+
owner (str, optional): The owner of the dataset. Defaults to None.
|
|
492
|
+
max_attempts (int, optional): The maximum number of attempts to register the dataset. Defaults to 3.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
bool: The status of the registration process.
|
|
496
|
+
"""
|
|
497
|
+
tmp_logger = LogWrapper(_logger, f"register_dataset_with_location-{naive_utcnow().isoformat('/')}")
|
|
498
|
+
tmp_logger.debug(f"register_dataset_with_location {dataset_name}")
|
|
499
|
+
|
|
500
|
+
res_for_failure = False
|
|
501
|
+
|
|
502
|
+
# get file info
|
|
503
|
+
guids = []
|
|
504
|
+
lfns = []
|
|
505
|
+
file_sizes = []
|
|
506
|
+
checksums = []
|
|
507
|
+
for tmp_file in files:
|
|
508
|
+
guids.append(tmp_file["guid"])
|
|
509
|
+
lfns.append(tmp_file["scope"] + ":" + tmp_file["lfn"])
|
|
510
|
+
file_sizes.append(int(tmp_file["filesize"]))
|
|
511
|
+
checksums.append(tmp_file["checksum"])
|
|
512
|
+
|
|
513
|
+
# register new dataset
|
|
514
|
+
for attempt in range(max_attempts):
|
|
515
|
+
try:
|
|
516
|
+
tmp_logger.debug(f"{attempt}/{max_attempts} registerNewDataset {dataset_name} len={len(files)}")
|
|
517
|
+
out = rucioAPI.register_dataset(dataset_name, lfns, guids, file_sizes, checksums, lifetime=self.dataset_lifetime)
|
|
518
|
+
tmp_logger.debug(out)
|
|
519
|
+
break
|
|
520
|
+
except Exception:
|
|
521
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
522
|
+
tmp_logger.error(f"{err_type} {err_value}")
|
|
523
|
+
if attempt + 1 == max_attempts:
|
|
524
|
+
tmp_logger.error(f"failed to register {dataset_name} in rucio")
|
|
525
|
+
tmp_logger.debug("end")
|
|
526
|
+
return res_for_failure
|
|
527
|
+
time.sleep(10)
|
|
528
|
+
|
|
529
|
+
# freeze dataset
|
|
530
|
+
for attempt in range(max_attempts):
|
|
531
|
+
tmp_logger.debug(f"{attempt}/{max_attempts} freezeDataset {dataset_name}")
|
|
532
|
+
try:
|
|
533
|
+
rucioAPI.close_dataset(dataset_name)
|
|
534
|
+
status = True
|
|
535
|
+
except Exception:
|
|
536
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
537
|
+
out = f"failed to freeze : {err_type} {err_value}"
|
|
538
|
+
status = False
|
|
539
|
+
if not status:
|
|
540
|
+
time.sleep(10)
|
|
541
|
+
else:
|
|
542
|
+
break
|
|
543
|
+
if not status:
|
|
544
|
+
tmp_logger.error(out)
|
|
545
|
+
tmp_logger.error(f"bad DDM response to freeze {dataset_name}")
|
|
546
|
+
tmp_logger.debug("end")
|
|
547
|
+
return res_for_failure
|
|
548
|
+
|
|
549
|
+
# register locations
|
|
550
|
+
for tmp_location in locations:
|
|
551
|
+
for attempt in range(max_attempts):
|
|
552
|
+
try:
|
|
553
|
+
tmp_logger.debug(f"{attempt}/{max_attempts} registerDatasetLocation {dataset_name} {tmp_location}")
|
|
554
|
+
out = rucioAPI.register_dataset_location(dataset_name, [tmp_location], self.dataset_lifetime, owner)
|
|
555
|
+
tmp_logger.debug(out)
|
|
556
|
+
status = True
|
|
557
|
+
break
|
|
558
|
+
|
|
559
|
+
except Exception:
|
|
560
|
+
status = False
|
|
561
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
562
|
+
tmp_logger.error(f"{err_type} {err_value}")
|
|
563
|
+
if attempt + 1 == max_attempts:
|
|
564
|
+
tmp_logger.error(f"failed to register {dataset_name} in rucio")
|
|
565
|
+
tmp_logger.debug("end")
|
|
566
|
+
return res_for_failure
|
|
567
|
+
time.sleep(10)
|
|
568
|
+
|
|
569
|
+
if not status:
|
|
570
|
+
tmp_logger.error(out)
|
|
571
|
+
tmp_logger.error(f"bad DDM response to register location {dataset_name}")
|
|
572
|
+
tmp_logger.debug("end")
|
|
573
|
+
return res_for_failure
|
|
574
|
+
return True
|
|
575
|
+
|
|
576
|
+
def get_datasets_by_guids(self, out_map: Dict, guids: List[str], dataset_filters: List[str]) -> Tuple[bool, Dict]:
|
|
577
|
+
"""
|
|
578
|
+
Get datasets by GUIDs.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
out_map (dict): The output map.
|
|
582
|
+
guids (list): The list of GUIDs.
|
|
583
|
+
dataset_filters (list): The list of dataset filters.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
tuple: A tuple containing a boolean status and a dictionary.
|
|
587
|
+
"""
|
|
588
|
+
tmp_logger = LogWrapper(_logger, f"get_datasets_by_guids-{naive_utcnow().isoformat('/')}")
|
|
589
|
+
tmp_logger.debug(f"get_datasets_by_guids {str(guids)}")
|
|
590
|
+
|
|
591
|
+
ret_map = {}
|
|
592
|
+
try:
|
|
593
|
+
for guid in guids:
|
|
594
|
+
tmp_dataset_names = []
|
|
595
|
+
if guid not in out_map:
|
|
596
|
+
tmp_logger.error(f"GUID={guid} not found")
|
|
597
|
+
tmp_logger.debug("end")
|
|
598
|
+
return False, {}
|
|
599
|
+
|
|
600
|
+
for tmp_dataset_name in out_map[guid]:
|
|
601
|
+
if (
|
|
602
|
+
tmp_dataset_name.startswith("panda")
|
|
603
|
+
or tmp_dataset_name.startswith("user")
|
|
604
|
+
or tmp_dataset_name.startswith("group")
|
|
605
|
+
or tmp_dataset_name.startswith("archive")
|
|
606
|
+
or re.search("_sub\d+$", tmp_dataset_name) is not None
|
|
607
|
+
or re.search("_dis\d+$", tmp_dataset_name) is not None
|
|
608
|
+
or re.search("_shadow$", tmp_dataset_name) is not None
|
|
609
|
+
):
|
|
610
|
+
continue
|
|
611
|
+
|
|
612
|
+
if dataset_filters:
|
|
613
|
+
flag_match = False
|
|
614
|
+
for tmp_filter in dataset_filters:
|
|
615
|
+
if fnmatch.fnmatchcase(tmp_dataset_name, tmp_filter):
|
|
616
|
+
flag_match = True
|
|
617
|
+
break
|
|
618
|
+
if not flag_match:
|
|
619
|
+
continue
|
|
620
|
+
|
|
621
|
+
tmp_dataset_names.append(tmp_dataset_name)
|
|
622
|
+
|
|
623
|
+
if not tmp_dataset_names:
|
|
624
|
+
tmp_logger.debug(f"no datasets found for GUID={guid}")
|
|
625
|
+
continue
|
|
626
|
+
|
|
627
|
+
if len(tmp_dataset_names) != 1:
|
|
628
|
+
tmp_logger.debug(f"use the first dataset in {str(tmp_dataset_names)} for GUID:{guid}")
|
|
629
|
+
|
|
630
|
+
ret_map[guid] = tmp_dataset_names[0]
|
|
631
|
+
except Exception as e:
|
|
632
|
+
tmp_logger.error(f"failed to parse get_datasets_by_guids: {e}")
|
|
633
|
+
tmp_logger.debug("end")
|
|
634
|
+
return False, {}
|
|
635
|
+
|
|
636
|
+
return True, ret_map
|
|
637
|
+
|
|
638
|
+
def list_datasets_by_guids(self, guids: List[str], dataset_filters: List[str], max_attempts: int = 3) -> Tuple[bool, Dict]:
|
|
639
|
+
"""
|
|
640
|
+
List datasets by GUIDs.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
guids (list): The list of GUIDs.
|
|
644
|
+
dataset_filters (list): The list of dataset filters.
|
|
645
|
+
max_attempts (int, optional): The maximum number of attempts to list the datasets. Defaults to 3.
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
tuple: A tuple containing the status (bool) and the result (dict or str).
|
|
649
|
+
"""
|
|
650
|
+
tmp_logger = LogWrapper(_logger, f"list_datasets_by_guids-{naive_utcnow().isoformat('/')}")
|
|
651
|
+
tmp_logger.debug(f"list_datasets_by_guids {str(guids)}")
|
|
652
|
+
|
|
653
|
+
res_for_failure = (False, {})
|
|
654
|
+
res_for_fatal = (False, {"isFatal": True})
|
|
655
|
+
|
|
656
|
+
# get size of datasets
|
|
657
|
+
for attempt in range(max_attempts):
|
|
658
|
+
tmp_logger.debug(f"{attempt}/{max_attempts} listDatasetsByGUIDs GUIDs={str(guids)}")
|
|
659
|
+
try:
|
|
660
|
+
out = rucioAPI.list_datasets_by_guids(guids)
|
|
661
|
+
status = True
|
|
662
|
+
break
|
|
663
|
+
except Exception:
|
|
664
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
665
|
+
out = f"failed to get datasets with GUIDs : {err_type} {err_value}"
|
|
666
|
+
status = False
|
|
667
|
+
time.sleep(10)
|
|
668
|
+
|
|
669
|
+
if not status:
|
|
670
|
+
tmp_logger.error(out)
|
|
671
|
+
tmp_logger.error(f"bad DDM response to get size of {str(guids)}")
|
|
672
|
+
if "DataIdentifierNotFound" in out:
|
|
673
|
+
tmp_logger.error("DataIdentifierNotFound in listDatasetsByGUIDs")
|
|
674
|
+
tmp_logger.debug("end")
|
|
675
|
+
return res_for_fatal
|
|
676
|
+
tmp_logger.debug("end")
|
|
677
|
+
return res_for_failure
|
|
678
|
+
|
|
679
|
+
tmp_logger.debug(out)
|
|
680
|
+
|
|
681
|
+
# get map
|
|
682
|
+
status, ret_map = self.get_datasets_by_guids(out, guids, dataset_filters)
|
|
683
|
+
|
|
684
|
+
if not status:
|
|
685
|
+
return res_for_failure
|
|
686
|
+
|
|
687
|
+
tmp_logger.debug("end")
|
|
688
|
+
return True, ret_map
|
|
689
|
+
|
|
690
|
+
def convert_evt_run_to_datasets(
|
|
691
|
+
self, event_run_list: List, dataset_type: str, stream_name: str, dataset_filters: List, ami_tag: str, run_evt_guid_map: Dict
|
|
692
|
+
) -> Tuple[bool, Dict, List]:
|
|
693
|
+
"""
|
|
694
|
+
Convert event/run list to datasets.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
event_run_list (list): The list of run events.
|
|
698
|
+
dataset_type (str): The type of the dataset.
|
|
699
|
+
stream_name (str): The name of the stream.
|
|
700
|
+
dataset_filters (list): The list of dataset filters.
|
|
701
|
+
ami_tag (str): The AMI tag.
|
|
702
|
+
run_evt_guid_map (dict): The map of run events to GUIDs.
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
tuple: A tuple containing the status (bool), the result (dict or str), and the list of all files.
|
|
706
|
+
"""
|
|
707
|
+
tmp_logger = LogWrapper(_logger, f"convert_evt_run_to_datasets-{naive_utcnow().isoformat('/')}")
|
|
708
|
+
tmp_logger.debug(f"convert_evt_run_to_datasets type={dataset_type} stream={stream_name} dsPatt={str(dataset_filters)} amitag={ami_tag}")
|
|
709
|
+
|
|
710
|
+
# check data type
|
|
711
|
+
failed_ret = False, {}, []
|
|
712
|
+
fatal_ret = False, {"isFatal": True}, []
|
|
713
|
+
stream_ref = "Stream" + dataset_type
|
|
714
|
+
# import event lookup client
|
|
715
|
+
if run_evt_guid_map == {}:
|
|
716
|
+
if len(event_run_list) == 0:
|
|
717
|
+
tmp_logger.error("Empty list for run and events was provided")
|
|
718
|
+
tmp_logger.debug("end")
|
|
719
|
+
return failed_ret
|
|
720
|
+
# Hadoop EI
|
|
721
|
+
from .event_lookup_client_ei import EventLookupClientEI
|
|
722
|
+
|
|
723
|
+
event_lookup_if = EventLookupClientEI()
|
|
724
|
+
# loop over all events
|
|
725
|
+
n_events_per_loop = 500
|
|
726
|
+
i_events_total = 0
|
|
727
|
+
while i_events_total < len(event_run_list):
|
|
728
|
+
tmp_event_run_list = event_run_list[i_events_total : i_events_total + n_events_per_loop]
|
|
729
|
+
tmp_logger.debug(f"EI lookup for {i_events_total}/{len(event_run_list)}")
|
|
730
|
+
i_events_total += n_events_per_loop
|
|
731
|
+
reg_start = naive_utcnow()
|
|
732
|
+
guid_list_elssi, tmp_com, tmp_out, tmp_err = event_lookup_if.do_lookup(
|
|
733
|
+
tmp_event_run_list,
|
|
734
|
+
stream=stream_name,
|
|
735
|
+
tokens=stream_ref,
|
|
736
|
+
ami_tag=ami_tag,
|
|
737
|
+
)
|
|
738
|
+
reg_time = naive_utcnow() - reg_start
|
|
739
|
+
tmp_logger.debug(f"EI command: {tmp_com}")
|
|
740
|
+
tmp_logger.debug(f"took {reg_time.seconds}.{reg_time.microseconds / 1000:03f} sec for {len(tmp_event_run_list)} events")
|
|
741
|
+
# failed
|
|
742
|
+
if tmp_err not in [None, ""] or len(guid_list_elssi) == 0:
|
|
743
|
+
tmp_logger.debug(tmp_com)
|
|
744
|
+
tmp_logger.debug(tmp_out)
|
|
745
|
+
tmp_logger.debug(tmp_err)
|
|
746
|
+
tmp_logger.error("invalid return from EventIndex")
|
|
747
|
+
tmp_logger.debug("end")
|
|
748
|
+
return failed_ret
|
|
749
|
+
# check events
|
|
750
|
+
for run_nr, evt_nr in tmp_event_run_list:
|
|
751
|
+
param_str = f"Run:{run_nr} Evt:{evt_nr} Stream:{stream_name}"
|
|
752
|
+
tmp_logger.debug(param_str)
|
|
753
|
+
tmp_run_evt_key = (int(run_nr), int(evt_nr))
|
|
754
|
+
# not found
|
|
755
|
+
if tmp_run_evt_key not in guid_list_elssi or len(guid_list_elssi[tmp_run_evt_key]) == 0:
|
|
756
|
+
tmp_logger.debug(tmp_com)
|
|
757
|
+
tmp_logger.debug(tmp_out)
|
|
758
|
+
tmp_logger.debug(tmp_err)
|
|
759
|
+
tmp_logger.error(f"no GUIDs were found in EventIndex for {param_str}")
|
|
760
|
+
tmp_logger.debug("end")
|
|
761
|
+
return fatal_ret
|
|
762
|
+
# append
|
|
763
|
+
run_evt_guid_map[tmp_run_evt_key] = guid_list_elssi[tmp_run_evt_key]
|
|
764
|
+
# convert to datasets
|
|
765
|
+
all_datasets = []
|
|
766
|
+
all_files = []
|
|
767
|
+
all_locations = {}
|
|
768
|
+
for tmp_idx in run_evt_guid_map:
|
|
769
|
+
tmp_guids = run_evt_guid_map[tmp_idx]
|
|
770
|
+
run_nr, evt_nr = tmp_idx
|
|
771
|
+
tmp_ds_ret, tmp_dataset_map = self.list_datasets_by_guids(tmp_guids, dataset_filters)
|
|
772
|
+
# failed
|
|
773
|
+
if not tmp_ds_ret:
|
|
774
|
+
tmp_logger.error(f"failed to convert GUIDs to datasets")
|
|
775
|
+
if "isFatal" in tmp_dataset_map and tmp_dataset_map["isFatal"] is True:
|
|
776
|
+
tmp_logger.debug("end")
|
|
777
|
+
return fatal_ret
|
|
778
|
+
tmp_logger.debug("end")
|
|
779
|
+
return failed_ret
|
|
780
|
+
# empty
|
|
781
|
+
if not tmp_dataset_map:
|
|
782
|
+
tmp_logger.error(f"there is no dataset for Run:{run_nr} Evt:{evt_nr} GUIDs:{str(tmp_guids)}")
|
|
783
|
+
tmp_logger.debug("end")
|
|
784
|
+
return fatal_ret
|
|
785
|
+
if len(tmp_dataset_map) != 1:
|
|
786
|
+
tmp_logger.error(f"there are multiple datasets {str(tmp_dataset_map)} for Run:{run_nr} Evt:{evt_nr} GUIDs:{str(tmp_guids)}")
|
|
787
|
+
tmp_logger.debug("end")
|
|
788
|
+
return fatal_ret
|
|
789
|
+
|
|
790
|
+
# append
|
|
791
|
+
for tmp_guid, tmp_dataset_name in tmp_dataset_map.items():
|
|
792
|
+
# collect dataset names
|
|
793
|
+
if tmp_dataset_name not in all_datasets:
|
|
794
|
+
all_datasets.append(tmp_dataset_name)
|
|
795
|
+
# get location
|
|
796
|
+
stat_rep, replica_map = self.get_list_dataset_replicas(tmp_dataset_name)
|
|
797
|
+
# failed
|
|
798
|
+
if not stat_rep:
|
|
799
|
+
tmp_logger.error(f"failed to get locations for {tmp_dataset_name}")
|
|
800
|
+
tmp_logger.debug("end")
|
|
801
|
+
return failed_ret
|
|
802
|
+
# collect locations
|
|
803
|
+
tmp_location_list = []
|
|
804
|
+
for tmp_location in replica_map:
|
|
805
|
+
# use only complete replicas
|
|
806
|
+
ds_stat_dict = replica_map[tmp_location][0]
|
|
807
|
+
if ds_stat_dict["total"] is not None and ds_stat_dict["total"] == ds_stat_dict["found"]:
|
|
808
|
+
if tmp_location not in tmp_location_list:
|
|
809
|
+
tmp_location_list.append(tmp_location)
|
|
810
|
+
all_locations[tmp_dataset_name] = tmp_location_list
|
|
811
|
+
|
|
812
|
+
# get file info
|
|
813
|
+
tmp_file_ret, tmp_file_info = self.get_file_from_dataset(tmp_dataset_name, tmp_guid)
|
|
814
|
+
# failed
|
|
815
|
+
if not tmp_file_ret:
|
|
816
|
+
tmp_logger.error(f"failed to get fileinfo for GUID:{tmp_guid} DS:{tmp_dataset_name}")
|
|
817
|
+
tmp_logger.debug("end")
|
|
818
|
+
return failed_ret
|
|
819
|
+
# collect files
|
|
820
|
+
all_files.append(tmp_file_info)
|
|
821
|
+
# return
|
|
822
|
+
tmp_logger.debug(f"converted to {str(all_datasets)}, {str(all_locations)}, {str(all_files)}")
|
|
823
|
+
tmp_logger.debug("end")
|
|
824
|
+
return True, all_locations, all_files
|