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,1381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to provide primitive methods for DDM
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import traceback
|
|
14
|
+
from typing import Dict, List
|
|
15
|
+
|
|
16
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
17
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
18
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
19
|
+
from rucio.client import Client as RucioClient
|
|
20
|
+
from rucio.common.exception import (
|
|
21
|
+
CannotAuthenticate,
|
|
22
|
+
DataIdentifierAlreadyExists,
|
|
23
|
+
DataIdentifierNotFound,
|
|
24
|
+
Duplicate,
|
|
25
|
+
DuplicateContent,
|
|
26
|
+
DuplicateRule,
|
|
27
|
+
FileAlreadyExists,
|
|
28
|
+
InvalidObject,
|
|
29
|
+
InvalidRSEExpression,
|
|
30
|
+
RSENotFound,
|
|
31
|
+
RuleNotFound,
|
|
32
|
+
UnsupportedOperation,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from pandaserver.srvcore import CoreUtils
|
|
36
|
+
|
|
37
|
+
# logger
|
|
38
|
+
_logger = PandaLogger().getLogger("ddm_rucio_api")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# rucio
|
|
42
|
+
class RucioAPI:
|
|
43
|
+
"""
|
|
44
|
+
A class to interact with Rucio API
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# constructor
|
|
48
|
+
def __init__(self):
|
|
49
|
+
"""
|
|
50
|
+
Initialize RucioAPI instance
|
|
51
|
+
"""
|
|
52
|
+
# how frequently update DN/token map
|
|
53
|
+
self.update_interval = datetime.timedelta(seconds=60 * 10)
|
|
54
|
+
# thread-safe client initialization
|
|
55
|
+
self._client_lock = threading.Lock()
|
|
56
|
+
self._rucio_client = None
|
|
57
|
+
|
|
58
|
+
# get thread-safe RucioClient with retry logic
|
|
59
|
+
def _get_rucio_client(self, max_retries: int = 3):
|
|
60
|
+
"""
|
|
61
|
+
Get or create a Rucio client with thread-safe initialization and retry logic.
|
|
62
|
+
Handles transient auth failures by clearing cache and retrying with a fresh client.
|
|
63
|
+
|
|
64
|
+
Parameters:
|
|
65
|
+
max_retries (int): Maximum number of retry attempts on auth failure. Defaults to 3.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
RucioClient: A valid Rucio client instance
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
CannotAuthenticate: If all retries are exhausted
|
|
72
|
+
"""
|
|
73
|
+
for attempt in range(max_retries):
|
|
74
|
+
try:
|
|
75
|
+
with self._client_lock:
|
|
76
|
+
# try to reuse cached client on first attempt
|
|
77
|
+
if attempt == 0 and self._rucio_client is not None:
|
|
78
|
+
return self._rucio_client
|
|
79
|
+
# create fresh client (previous one may have stale token cache)
|
|
80
|
+
client = RucioClient()
|
|
81
|
+
self._rucio_client = client
|
|
82
|
+
return client
|
|
83
|
+
except CannotAuthenticate as e:
|
|
84
|
+
# clear cache on auth failure
|
|
85
|
+
with self._client_lock:
|
|
86
|
+
self._rucio_client = None
|
|
87
|
+
if attempt < max_retries - 1:
|
|
88
|
+
# exponential backoff: 0.1s, 0.2s, 0.4s
|
|
89
|
+
sleep_time = 0.1 * (2**attempt)
|
|
90
|
+
time.sleep(sleep_time)
|
|
91
|
+
continue
|
|
92
|
+
# re-raise on final attempt
|
|
93
|
+
raise
|
|
94
|
+
except Exception:
|
|
95
|
+
# for non-auth errors, clear cache and re-raise
|
|
96
|
+
with self._client_lock:
|
|
97
|
+
self._rucio_client = None
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
# extract scope
|
|
101
|
+
def extract_scope(self, dataset_name: str, strip_slash: bool = False) -> tuple:
|
|
102
|
+
"""
|
|
103
|
+
Extract scope from a given dataset name
|
|
104
|
+
|
|
105
|
+
Parameters:
|
|
106
|
+
dataset_name (str): Dataset name
|
|
107
|
+
strip_slash (bool): Wheter to strip trailing slash (about continer collection) in data_name
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
tuple: A tuple containing scope and dataset name
|
|
111
|
+
"""
|
|
112
|
+
if strip_slash:
|
|
113
|
+
if dataset_name.endswith("/"):
|
|
114
|
+
dataset_name = re.sub("/$", "", dataset_name)
|
|
115
|
+
if ":" in dataset_name:
|
|
116
|
+
return dataset_name.split(":")[:2]
|
|
117
|
+
scope = dataset_name.split(".")[0]
|
|
118
|
+
if dataset_name.startswith("user") or dataset_name.startswith("group"):
|
|
119
|
+
scope = ".".join(dataset_name.split(".")[0:2])
|
|
120
|
+
return scope, dataset_name
|
|
121
|
+
|
|
122
|
+
# register dataset
|
|
123
|
+
def register_dataset(
|
|
124
|
+
self,
|
|
125
|
+
dataset_name: str,
|
|
126
|
+
lfns: list = None,
|
|
127
|
+
guids: list = None,
|
|
128
|
+
sizes: list = None,
|
|
129
|
+
checksums: list = None,
|
|
130
|
+
lifetime: int = None,
|
|
131
|
+
scope: str = None,
|
|
132
|
+
metadata: dict = None,
|
|
133
|
+
) -> dict:
|
|
134
|
+
"""
|
|
135
|
+
Register a dataset in Rucio
|
|
136
|
+
|
|
137
|
+
Parameters:
|
|
138
|
+
dataset_name (str): Dataset name
|
|
139
|
+
lfns (list, optional): List of logical file names. Defaults to None.
|
|
140
|
+
guids (list, optional): List of GUIDs. Defaults to None.
|
|
141
|
+
sizes (list, optional): List of file sizes. Defaults to None.
|
|
142
|
+
checksums (list, optional): List of checksums. Defaults to None.
|
|
143
|
+
lifetime (int, optional): Lifetime of the dataset in seconds. Defaults to None.
|
|
144
|
+
scope (str, optional): Scope of the dataset. Defaults to None.
|
|
145
|
+
metadata (dict, optional): Metadata of the dataset. Defaults to None.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
dict: A dictionary containing duid, version and vuid of the dataset
|
|
149
|
+
"""
|
|
150
|
+
if lfns is None:
|
|
151
|
+
lfns = []
|
|
152
|
+
if guids is None:
|
|
153
|
+
guids = []
|
|
154
|
+
if sizes is None:
|
|
155
|
+
sizes = []
|
|
156
|
+
if checksums is None:
|
|
157
|
+
checksums = []
|
|
158
|
+
preset_scope = scope
|
|
159
|
+
files = []
|
|
160
|
+
for lfn, guid, size, checksum in zip(lfns, guids, sizes, checksums):
|
|
161
|
+
if lfn.find(":") > -1:
|
|
162
|
+
file_scope, lfn = lfn.split(":")[0], lfn.split(":")[1]
|
|
163
|
+
else:
|
|
164
|
+
file_scope = scope
|
|
165
|
+
file = {"scope": file_scope, "name": lfn, "bytes": size, "meta": {"guid": guid}}
|
|
166
|
+
if checksum.startswith("md5:"):
|
|
167
|
+
file["md5"] = checksum[4:]
|
|
168
|
+
elif checksum.startswith("ad:"):
|
|
169
|
+
file["adler32"] = checksum[3:]
|
|
170
|
+
files.append(file)
|
|
171
|
+
# register dataset
|
|
172
|
+
client = self._get_rucio_client()
|
|
173
|
+
try:
|
|
174
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
175
|
+
if preset_scope is not None:
|
|
176
|
+
scope = preset_scope
|
|
177
|
+
client.add_dataset(scope=scope, name=dataset_name, meta=metadata)
|
|
178
|
+
if lifetime is not None:
|
|
179
|
+
client.set_metadata(scope, dataset_name, key="lifetime", value=lifetime * 86400)
|
|
180
|
+
except DataIdentifierAlreadyExists:
|
|
181
|
+
pass
|
|
182
|
+
# open dataset just in case
|
|
183
|
+
try:
|
|
184
|
+
client.set_status(scope, dataset_name, open=True)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
# add files
|
|
188
|
+
if len(files) > 0:
|
|
189
|
+
i_files = 0
|
|
190
|
+
n_files = 1000
|
|
191
|
+
while i_files < len(files):
|
|
192
|
+
tmp_files = files[i_files : i_files + n_files]
|
|
193
|
+
try:
|
|
194
|
+
client.add_files_to_dataset(scope=scope, name=dataset_name, files=tmp_files, rse=None)
|
|
195
|
+
except FileAlreadyExists:
|
|
196
|
+
for tmp_file in tmp_files:
|
|
197
|
+
try:
|
|
198
|
+
client.add_files_to_dataset(scope=scope, name=dataset_name, files=[tmp_file], rse=None)
|
|
199
|
+
except FileAlreadyExists:
|
|
200
|
+
pass
|
|
201
|
+
i_files += n_files
|
|
202
|
+
# Format is a 36-character string divided into five groups separated by hyphens. The groups have 8, 4, 4, 4, and 12 characters.
|
|
203
|
+
# After the formatting, the vuid string would look something like this: 12345678-1234-1234-1234-123456789012
|
|
204
|
+
vuid = hashlib.md5((scope + ":" + dataset_name).encode()).hexdigest()
|
|
205
|
+
vuid = f"{vuid[0:8]}-{vuid[8:12]}-{vuid[12:16]}-{vuid[16:20]}-{vuid[20:32]}"
|
|
206
|
+
duid = vuid
|
|
207
|
+
return {"duid": duid, "version": 1, "vuid": vuid}
|
|
208
|
+
|
|
209
|
+
def register_dataset_location(
|
|
210
|
+
self,
|
|
211
|
+
dataset_name: str,
|
|
212
|
+
rses: list,
|
|
213
|
+
lifetime: int = None,
|
|
214
|
+
owner: str = None,
|
|
215
|
+
activity: str = None,
|
|
216
|
+
scope: str = None,
|
|
217
|
+
grouping: str = "DATASET",
|
|
218
|
+
notify: str = "N",
|
|
219
|
+
) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Register a dataset location in Rucio
|
|
222
|
+
|
|
223
|
+
Parameters:
|
|
224
|
+
dataset_name (str): Dataset name
|
|
225
|
+
rses (list): List of RSEs
|
|
226
|
+
lifetime (int, optional): Lifetime of the dataset in seconds. Defaults to None.
|
|
227
|
+
owner (str, optional): Owner of the dataset. Defaults to None.
|
|
228
|
+
activity (str, optional): Activity associated with the dataset. Defaults to None.
|
|
229
|
+
scope (str, optional): Scope of the dataset. Defaults to None.
|
|
230
|
+
grouping (str, optional): Grouping of the dataset. Defaults to "DATASET".
|
|
231
|
+
notify (str, optional): Notification option. Defaults to "N".
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
bool: True if the operation is successful, False otherwise
|
|
235
|
+
"""
|
|
236
|
+
grouping = "DATASET" if grouping is None else grouping
|
|
237
|
+
preset_scope = scope
|
|
238
|
+
lifetime = lifetime * 24 * 60 * 60 if lifetime else None
|
|
239
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
240
|
+
scope = preset_scope if preset_scope else scope
|
|
241
|
+
did = {"scope": scope, "name": dataset_name}
|
|
242
|
+
dids = [did]
|
|
243
|
+
# make location
|
|
244
|
+
rses.sort()
|
|
245
|
+
location = "|".join(rses)
|
|
246
|
+
# check if a replication rule already exists
|
|
247
|
+
client = self._get_rucio_client()
|
|
248
|
+
# owner
|
|
249
|
+
owner = client.account if owner is None else owner
|
|
250
|
+
for rule in client.list_did_rules(scope=scope, name=dataset_name):
|
|
251
|
+
if (rule["rse_expression"] == location) and (rule["account"] == owner):
|
|
252
|
+
return True
|
|
253
|
+
try:
|
|
254
|
+
client.add_replication_rule(
|
|
255
|
+
dids=dids,
|
|
256
|
+
copies=1,
|
|
257
|
+
rse_expression=location,
|
|
258
|
+
weight=None,
|
|
259
|
+
lifetime=lifetime,
|
|
260
|
+
grouping=grouping,
|
|
261
|
+
account=owner,
|
|
262
|
+
locked=False,
|
|
263
|
+
activity=activity,
|
|
264
|
+
notify=notify,
|
|
265
|
+
ignore_availability=True,
|
|
266
|
+
)
|
|
267
|
+
except (Duplicate, DuplicateRule):
|
|
268
|
+
pass
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
# get user
|
|
272
|
+
def get_user(self, client, distinguished_name: str) -> str:
|
|
273
|
+
"""
|
|
274
|
+
This method retrieves the account name associated with a given distinguished name (dn) from the Rucio client.
|
|
275
|
+
If no account is found, it returns the default account of the Rucio client.
|
|
276
|
+
|
|
277
|
+
Parameters:
|
|
278
|
+
client (RucioClient): The Rucio client instance to interact with the Rucio server.
|
|
279
|
+
dn (str): The distinguished name (dn) for which the associated account is to be retrieved.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: The account name associated with the given dn. If no account is found, it returns the default account of the Rucio client.
|
|
283
|
+
"""
|
|
284
|
+
tmp_list = list(client.list_accounts("user", distinguished_name))
|
|
285
|
+
if tmp_list:
|
|
286
|
+
owner = tmp_list[0]["account"]
|
|
287
|
+
return owner
|
|
288
|
+
return client.account
|
|
289
|
+
|
|
290
|
+
# register dataset subscription
|
|
291
|
+
def register_dataset_subscription(
|
|
292
|
+
self, dataset_name: str, rses: list, lifetime: int = None, owner: str = None, activity: str = None, distinguished_name: str = None, comment: str = None
|
|
293
|
+
) -> bool:
|
|
294
|
+
"""
|
|
295
|
+
Register a dataset subscription in Rucio.
|
|
296
|
+
|
|
297
|
+
This method registers a dataset subscription in Rucio by creating a replication rule for the dataset.
|
|
298
|
+
If a replication rule already exists for the dataset, the method simply returns True.
|
|
299
|
+
|
|
300
|
+
Parameters:
|
|
301
|
+
dataset_name (str): The name of the dataset.
|
|
302
|
+
rses (list): A list of RSEs where the dataset should be replicated.
|
|
303
|
+
lifetime (int, optional): The lifetime of the replication rule in seconds. Defaults to None.
|
|
304
|
+
owner (str, optional): The owner of the replication rule. Defaults to None.
|
|
305
|
+
activity (str, optional): The activity associated with the replication rule. Defaults to None.
|
|
306
|
+
distinguished_name (str, optional): The distinguished name of the user. Defaults to None.
|
|
307
|
+
comment (str, optional): A comment to be associated with the replication rule. Defaults to None.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
bool: True if the operation is successful, False otherwise.
|
|
311
|
+
"""
|
|
312
|
+
lifetime = lifetime * 24 * 60 * 60 if lifetime else lifetime
|
|
313
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
314
|
+
did = {"scope": scope, "name": dataset_name}
|
|
315
|
+
dids = [did]
|
|
316
|
+
# make location
|
|
317
|
+
rses.sort()
|
|
318
|
+
location = "|".join(rses)
|
|
319
|
+
# check if a replication rule already exists
|
|
320
|
+
client = self._get_rucio_client()
|
|
321
|
+
# owner
|
|
322
|
+
if owner is None:
|
|
323
|
+
if distinguished_name is not None:
|
|
324
|
+
owner = self.get_user(client, distinguished_name)
|
|
325
|
+
else:
|
|
326
|
+
owner = client.account
|
|
327
|
+
for rule in client.list_did_rules(scope=scope, name=dataset_name):
|
|
328
|
+
if (rule["rse_expression"] == location) and (rule["account"] == owner):
|
|
329
|
+
return True
|
|
330
|
+
try:
|
|
331
|
+
client.add_replication_rule(
|
|
332
|
+
dids=dids,
|
|
333
|
+
copies=1,
|
|
334
|
+
rse_expression=location,
|
|
335
|
+
weight=None,
|
|
336
|
+
lifetime=lifetime,
|
|
337
|
+
grouping="DATASET",
|
|
338
|
+
account=owner,
|
|
339
|
+
locked=False,
|
|
340
|
+
activity=activity,
|
|
341
|
+
notify="C",
|
|
342
|
+
ignore_availability=True,
|
|
343
|
+
comment=comment,
|
|
344
|
+
)
|
|
345
|
+
except (Duplicate, DuplicateRule):
|
|
346
|
+
pass
|
|
347
|
+
return True
|
|
348
|
+
|
|
349
|
+
# convert file attribute
|
|
350
|
+
def convert_file_attributes(self, tmp_file: dict, scope: str) -> dict:
|
|
351
|
+
"""
|
|
352
|
+
Convert file attribute to a dictionary
|
|
353
|
+
|
|
354
|
+
Parameters:
|
|
355
|
+
tmp_file (dict): File attribute
|
|
356
|
+
scope (str): Scope of the file
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
dict: A dictionary containing file attributes
|
|
360
|
+
"""
|
|
361
|
+
lfn = tmp_file.get("name", tmp_file.get("lfn"))
|
|
362
|
+
file_scope, lfn = lfn.split(":") if ":" in lfn else (scope, lfn)
|
|
363
|
+
# set metadata
|
|
364
|
+
meta_keys = ["guid", "events", "lumiblocknr", "panda_id", "campaign", "task_id"]
|
|
365
|
+
meta = {key: tmp_file[key] for key in meta_keys if key in tmp_file}
|
|
366
|
+
file_size = tmp_file.get("bytes", tmp_file.get("size"))
|
|
367
|
+
file = {"scope": file_scope, "name": lfn, "bytes": file_size, "meta": meta}
|
|
368
|
+
if "checksum" in tmp_file:
|
|
369
|
+
checksum = tmp_file["checksum"]
|
|
370
|
+
if checksum.startswith("md5:"):
|
|
371
|
+
file["md5"] = checksum[4:]
|
|
372
|
+
elif checksum.startswith("ad:"):
|
|
373
|
+
file["adler32"] = checksum[3:]
|
|
374
|
+
if "surl" in tmp_file:
|
|
375
|
+
file["pfn"] = tmp_file["surl"]
|
|
376
|
+
return file
|
|
377
|
+
|
|
378
|
+
# register files in dataset
|
|
379
|
+
def register_files_in_dataset(self, id_map: dict, files_without_rses: list = None) -> bool:
|
|
380
|
+
"""
|
|
381
|
+
Register files in a dataset
|
|
382
|
+
|
|
383
|
+
Parameters:
|
|
384
|
+
id_map (dict): A dictionary containing dataset information. Maps RSEs to datasets and files.
|
|
385
|
+
files_without_rses (list, optional): List of files without RSEs. Defaults to None.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
bool: True if the operation is successful, False otherwise
|
|
389
|
+
"""
|
|
390
|
+
# loop over all rse
|
|
391
|
+
attachment_list = []
|
|
392
|
+
for rse in id_map:
|
|
393
|
+
tmp_map = id_map[rse]
|
|
394
|
+
# loop over all datasets
|
|
395
|
+
for dataset_name in tmp_map:
|
|
396
|
+
file_list = tmp_map[dataset_name]
|
|
397
|
+
# extract scope from dataset
|
|
398
|
+
scope, given_dataset_name = self.extract_scope(dataset_name)
|
|
399
|
+
files_with_rse = []
|
|
400
|
+
files_without_rse = []
|
|
401
|
+
for tmp_file in file_list:
|
|
402
|
+
# convert file attribute
|
|
403
|
+
file = self.convert_file_attributes(tmp_file, scope)
|
|
404
|
+
# append files
|
|
405
|
+
if rse is not None and (files_without_rses is None or file["name"] not in files_without_rses):
|
|
406
|
+
files_with_rse.append(file)
|
|
407
|
+
else:
|
|
408
|
+
if "pfn" in file:
|
|
409
|
+
del file["pfn"]
|
|
410
|
+
files_without_rse.append(file)
|
|
411
|
+
# add attachment
|
|
412
|
+
if len(files_with_rse) > 0:
|
|
413
|
+
n_files = 100
|
|
414
|
+
i_files = 0
|
|
415
|
+
while i_files < len(files_with_rse):
|
|
416
|
+
attachment = {
|
|
417
|
+
"scope": scope,
|
|
418
|
+
"name": given_dataset_name,
|
|
419
|
+
"dids": files_with_rse[i_files : i_files + n_files],
|
|
420
|
+
"rse": rse,
|
|
421
|
+
}
|
|
422
|
+
attachment_list.append(attachment)
|
|
423
|
+
i_files += n_files
|
|
424
|
+
if len(files_without_rse) > 0:
|
|
425
|
+
n_files = 100
|
|
426
|
+
i_files = 0
|
|
427
|
+
while i_files < len(files_without_rse):
|
|
428
|
+
attachment = {
|
|
429
|
+
"scope": scope,
|
|
430
|
+
"name": given_dataset_name,
|
|
431
|
+
"dids": files_without_rse[i_files : i_files + n_files],
|
|
432
|
+
}
|
|
433
|
+
attachment_list.append(attachment)
|
|
434
|
+
i_files += n_files
|
|
435
|
+
# add files
|
|
436
|
+
client = self._get_rucio_client()
|
|
437
|
+
client.add_files_to_datasets(attachment_list, ignore_duplicate=True)
|
|
438
|
+
return True
|
|
439
|
+
|
|
440
|
+
# register zip files
|
|
441
|
+
def register_zip_files(self, zip_map: dict) -> None:
|
|
442
|
+
"""
|
|
443
|
+
Register zip files in Rucio.
|
|
444
|
+
|
|
445
|
+
This method takes a map of zip file names to their attributes and registers the zip files in Rucio.
|
|
446
|
+
It first converts the zip file attributes to a dictionary format that Rucio can understand.
|
|
447
|
+
Then, it loops over all the files contained in the zip file, converts their attributes to the Rucio dictionary format, and appends them to a list.
|
|
448
|
+
Finally, it registers the zip file and its contained files in Rucio.
|
|
449
|
+
|
|
450
|
+
Parameters:
|
|
451
|
+
zip_map (dict): A dictionary mapping zip file names to their attributes. The structure is {zipFileName: zipFileAttr, ...}.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
None
|
|
455
|
+
"""
|
|
456
|
+
# no zip files
|
|
457
|
+
if len(zip_map) == 0:
|
|
458
|
+
return
|
|
459
|
+
client = self._get_rucio_client()
|
|
460
|
+
# loop over all zip files
|
|
461
|
+
for zip_file_name in zip_map:
|
|
462
|
+
zip_file_attr = zip_map[zip_file_name]
|
|
463
|
+
# convert file attribute
|
|
464
|
+
zip_file = self.convert_file_attributes(zip_file_attr, zip_file_attr["scope"])
|
|
465
|
+
# loop over all contents
|
|
466
|
+
files = []
|
|
467
|
+
for con_file_attr in zip_file_attr["files"]:
|
|
468
|
+
# get scope
|
|
469
|
+
scope, _ = self.extract_scope(con_file_attr["ds"])
|
|
470
|
+
# convert file attribute
|
|
471
|
+
con_file = self.convert_file_attributes(con_file_attr, scope)
|
|
472
|
+
con_file["type"] = "FILE"
|
|
473
|
+
if "pfn" in con_file:
|
|
474
|
+
del con_file["pfn"]
|
|
475
|
+
# append files
|
|
476
|
+
files.append(con_file)
|
|
477
|
+
# register zip file
|
|
478
|
+
for rse in zip_file_attr["rse"]:
|
|
479
|
+
client.add_replicas(rse=rse, files=[zip_file])
|
|
480
|
+
# add files
|
|
481
|
+
n_files = 100
|
|
482
|
+
i_files = 0
|
|
483
|
+
while i_files < len(files):
|
|
484
|
+
client.add_files_to_archive(
|
|
485
|
+
scope=zip_file["scope"],
|
|
486
|
+
name=zip_file["name"],
|
|
487
|
+
files=files[i_files : i_files + n_files],
|
|
488
|
+
)
|
|
489
|
+
i_files += n_files
|
|
490
|
+
|
|
491
|
+
# list datasets
|
|
492
|
+
def list_datasets(self, dataset_name: str, old: bool = False):
|
|
493
|
+
"""
|
|
494
|
+
List datasets in Rucio.
|
|
495
|
+
|
|
496
|
+
This method lists datasets in Rucio by extracting the scope from the dataset name and getting the DIDs (Data Identifiers).
|
|
497
|
+
It generates a unique identifier (vuid) for each DID and stores it in a dictionary along with the dataset name.
|
|
498
|
+
If an exception occurs during the process, it returns None and the exception message.
|
|
499
|
+
|
|
500
|
+
Parameters:
|
|
501
|
+
dataset_name (str): The name of the dataset.
|
|
502
|
+
old (bool, optional): A flag to indicate if the dataset is old. Defaults to False.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
Tuple[Union[dict, None], str]: A tuple containing a dictionary of datasets and their unique identifiers, and a string message.
|
|
506
|
+
If an exception occurs, the dictionary is None and the string contains the exception message.
|
|
507
|
+
"""
|
|
508
|
+
result = {}
|
|
509
|
+
# extract scope from dataset
|
|
510
|
+
scope, given_dataset_name = self.extract_scope(dataset_name)
|
|
511
|
+
if given_dataset_name.endswith("/"):
|
|
512
|
+
given_dataset_name = given_dataset_name[:-1]
|
|
513
|
+
collection = "container"
|
|
514
|
+
else:
|
|
515
|
+
collection = "dataset"
|
|
516
|
+
filters = {"name": given_dataset_name}
|
|
517
|
+
try:
|
|
518
|
+
# get dids
|
|
519
|
+
client = self._get_rucio_client()
|
|
520
|
+
for name in client.list_dids(scope, filters, collection):
|
|
521
|
+
vuid = hashlib.md5((scope + ":" + name).encode()).hexdigest()
|
|
522
|
+
# Format is a 36-character string divided into five groups separated by hyphens. The groups have 8, 4, 4, 4, and 12 characters.
|
|
523
|
+
# After the formatting, the vuid string would look something like this: 12345678-1234-1234-1234-123456789012
|
|
524
|
+
vuid = f"{vuid[0:8]}-{vuid[8:12]}-{vuid[12:16]}-{vuid[16:20]}-{vuid[20:32]}"
|
|
525
|
+
duid = vuid
|
|
526
|
+
# add /
|
|
527
|
+
if dataset_name.endswith("/") and not name.endswith("/"):
|
|
528
|
+
name += "/"
|
|
529
|
+
if old or ":" not in dataset_name:
|
|
530
|
+
key_name = name
|
|
531
|
+
else:
|
|
532
|
+
key_name = str(f"{scope}:{name}")
|
|
533
|
+
if key_name not in result:
|
|
534
|
+
result[key_name] = {"duid": duid, "vuids": [vuid]}
|
|
535
|
+
return result, ""
|
|
536
|
+
except Exception as error:
|
|
537
|
+
return None, f"{str(error)} {traceback.format_exc()}"
|
|
538
|
+
|
|
539
|
+
# list datasets in container
|
|
540
|
+
def list_datasets_in_container(self, container_name: str):
|
|
541
|
+
"""
|
|
542
|
+
List datasets in a Rucio container.
|
|
543
|
+
|
|
544
|
+
This method lists datasets in a Rucio container by extracting the scope from the container name and getting the DIDs (Data Identifiers).
|
|
545
|
+
It generates a unique identifier (vuid) for each DID and stores it in a list.
|
|
546
|
+
If an exception occurs during the process, it returns None and the exception message.
|
|
547
|
+
|
|
548
|
+
Parameters:
|
|
549
|
+
container_name (str): The name of the container.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Tuple[Union[None, List[str]], str]: A tuple containing a list of datasets and a string message.
|
|
553
|
+
If an exception occurs, the list is None and the string contains the exception message.
|
|
554
|
+
"""
|
|
555
|
+
result = []
|
|
556
|
+
# extract scope from dataset
|
|
557
|
+
scope, container_name = self.extract_scope(container_name)
|
|
558
|
+
if container_name.endswith("/"):
|
|
559
|
+
container_name = container_name[:-1]
|
|
560
|
+
try:
|
|
561
|
+
# get dids
|
|
562
|
+
client = self._get_rucio_client()
|
|
563
|
+
for content in client.list_content(scope, container_name):
|
|
564
|
+
if content["type"] == "DATASET":
|
|
565
|
+
result.append(str(f"{content['scope']}:{content['name']}"))
|
|
566
|
+
return result, ""
|
|
567
|
+
except Exception:
|
|
568
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
569
|
+
return None, f"{err_type} {err_value}"
|
|
570
|
+
|
|
571
|
+
# list dataset replicas
|
|
572
|
+
def list_dataset_replicas(self, dataset_name: str):
|
|
573
|
+
"""
|
|
574
|
+
List dataset replicas in Rucio.
|
|
575
|
+
|
|
576
|
+
This method lists dataset replicas in Rucio by extracting the scope from the dataset name and getting the replicas.
|
|
577
|
+
It stores the replicas in a dictionary and returns it along with a status code.
|
|
578
|
+
If an exception occurs during the process, it returns the error type and value.
|
|
579
|
+
|
|
580
|
+
Parameters:
|
|
581
|
+
dataset_name (str): The name of the dataset.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
Tuple[int, Union[str, dict]]: A tuple containing a status code and a dictionary of dataset replicas or an error message.
|
|
585
|
+
If an exception occurs, the dictionary is None and the string contains the error message.
|
|
586
|
+
"""
|
|
587
|
+
return_map = {}
|
|
588
|
+
# extract scope from dataset
|
|
589
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
590
|
+
try:
|
|
591
|
+
# get replicas
|
|
592
|
+
client = self._get_rucio_client()
|
|
593
|
+
replica_iterator = client.list_dataset_replicas(scope, dataset_name)
|
|
594
|
+
for item in replica_iterator:
|
|
595
|
+
rse = item["rse"]
|
|
596
|
+
return_map[rse] = [
|
|
597
|
+
{
|
|
598
|
+
"total": item["length"],
|
|
599
|
+
"found": item["available_length"],
|
|
600
|
+
"immutable": 1,
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
return 0, return_map
|
|
604
|
+
except Exception:
|
|
605
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
606
|
+
return 1, f"{err_type} {err_value}"
|
|
607
|
+
|
|
608
|
+
# get metadata
|
|
609
|
+
def get_metadata(self, dataset_name: str, scope: str = None):
|
|
610
|
+
"""
|
|
611
|
+
Get metadata of a dataset in Rucio.
|
|
612
|
+
|
|
613
|
+
This method retrieves the metadata of a dataset in Rucio by extracting the scope from the dataset name and getting the metadata.
|
|
614
|
+
If an exception occurs during the process, it returns False and the exception message.
|
|
615
|
+
|
|
616
|
+
Parameters:
|
|
617
|
+
dataset_name (str): The name of the dataset.
|
|
618
|
+
scope (str, optional): The scope of the dataset. Defaults to None.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
Tuple[bool, Union[None, dict]]: A tuple containing a boolean indicating the success of the operation and a dictionary of metadata or an error message.
|
|
622
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
623
|
+
"""
|
|
624
|
+
# register dataset
|
|
625
|
+
client = self._get_rucio_client()
|
|
626
|
+
try:
|
|
627
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
628
|
+
return True, client.get_metadata(scope, dataset_name)
|
|
629
|
+
except DataIdentifierNotFound:
|
|
630
|
+
return True, None
|
|
631
|
+
except Exception:
|
|
632
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
633
|
+
return False, f"{err_type} {err_value}"
|
|
634
|
+
|
|
635
|
+
# delete dataset
|
|
636
|
+
def erase_dataset(self, dataset_name: str, scope: str = None, grace_period: int = None):
|
|
637
|
+
"""
|
|
638
|
+
Delete a dataset in Rucio.
|
|
639
|
+
|
|
640
|
+
This method deletes a dataset in Rucio by extracting the scope from the dataset name and calling the erase method.
|
|
641
|
+
If an exception occurs during the process, it returns False and the exception message.
|
|
642
|
+
|
|
643
|
+
Parameters:
|
|
644
|
+
dataset_name (str): The name of the dataset.
|
|
645
|
+
scope (str, optional): The scope of the dataset. Defaults to None.
|
|
646
|
+
grace_period (int, optional): The grace period before deletion. Defaults to None.
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
Tuple[bool, str]: A tuple containing a boolean indicating the success of the operation and a string message.
|
|
650
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
651
|
+
"""
|
|
652
|
+
preset_scope = scope
|
|
653
|
+
# register dataset
|
|
654
|
+
client = self._get_rucio_client()
|
|
655
|
+
try:
|
|
656
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
657
|
+
if preset_scope is not None:
|
|
658
|
+
scope = preset_scope
|
|
659
|
+
if grace_period is not None:
|
|
660
|
+
value = grace_period * 60 * 60
|
|
661
|
+
else:
|
|
662
|
+
value = 0.0001
|
|
663
|
+
client.set_metadata(scope=scope, name=dataset_name, key="lifetime", value=value)
|
|
664
|
+
except DataIdentifierNotFound:
|
|
665
|
+
pass
|
|
666
|
+
except Exception as error:
|
|
667
|
+
return False, str(error)
|
|
668
|
+
return True, ""
|
|
669
|
+
|
|
670
|
+
# close dataset
|
|
671
|
+
def close_dataset(self, dataset_name: str) -> bool:
|
|
672
|
+
"""
|
|
673
|
+
Close a dataset in Rucio.
|
|
674
|
+
|
|
675
|
+
This method closes a dataset in Rucio by extracting the scope from the dataset name and setting the status of the dataset to closed.
|
|
676
|
+
If an exception occurs during the process, it ignores the exception and returns True.
|
|
677
|
+
|
|
678
|
+
Parameters:
|
|
679
|
+
dataset_name (str): The name of the dataset.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
bool: True if the operation is successful, False otherwise.
|
|
683
|
+
"""
|
|
684
|
+
# register dataset
|
|
685
|
+
client = self._get_rucio_client()
|
|
686
|
+
try:
|
|
687
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
688
|
+
client.set_status(scope, dataset_name, open=False)
|
|
689
|
+
except (UnsupportedOperation, DataIdentifierNotFound):
|
|
690
|
+
pass
|
|
691
|
+
return True
|
|
692
|
+
|
|
693
|
+
# list file replicas
|
|
694
|
+
def list_file_replicas(self, scopes: List[str], lfns: List[str], rses: List[str] = None):
|
|
695
|
+
"""
|
|
696
|
+
List file replicas in Rucio.
|
|
697
|
+
|
|
698
|
+
This method lists file replicas in Rucio by creating a list of dictionaries containing the scope and name of each file.
|
|
699
|
+
It then retrieves the replicas for these files from Rucio and stores them in a dictionary.
|
|
700
|
+
If an exception occurs during the process, it returns False and the exception message.
|
|
701
|
+
|
|
702
|
+
Parameters:
|
|
703
|
+
scopes (List[str]): A list of scopes for the files.
|
|
704
|
+
lfns (List[str]): A list of Logical File Names (LFNs) for the files.
|
|
705
|
+
rses (List[str], optional): A list of Rucio Storage Elements (RSEs) where the files should be replicated. Defaults to None.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
Tuple[bool, Union[str, Dict[str, List[str]]]]: A tuple containing a boolean indicating the success of the operation and a dictionary of file replicas or an error message.
|
|
709
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
710
|
+
"""
|
|
711
|
+
try:
|
|
712
|
+
client = self._get_rucio_client()
|
|
713
|
+
dids = []
|
|
714
|
+
i_guid = 0
|
|
715
|
+
batch_size = 1000
|
|
716
|
+
ret_val = {}
|
|
717
|
+
for scope, lfn in zip(scopes, lfns):
|
|
718
|
+
i_guid += 1
|
|
719
|
+
dids.append({"scope": scope, "name": lfn})
|
|
720
|
+
if len(dids) % batch_size == 0 or i_guid == len(lfns):
|
|
721
|
+
for tmp_dict in client.list_replicas(dids):
|
|
722
|
+
tmp_lfn = str(tmp_dict["name"])
|
|
723
|
+
tmp_rses = list(tmp_dict["rses"])
|
|
724
|
+
# RSE selection
|
|
725
|
+
if rses is not None:
|
|
726
|
+
tmp_rses = [tmp_rse for tmp_rse in tmp_rses if tmp_rse in rses]
|
|
727
|
+
if len(tmp_rses) > 0:
|
|
728
|
+
ret_val[tmp_lfn] = tmp_rses
|
|
729
|
+
dids = []
|
|
730
|
+
return True, ret_val
|
|
731
|
+
except Exception:
|
|
732
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
733
|
+
return False, f"{err_type} {err_value}"
|
|
734
|
+
|
|
735
|
+
# get zip files
|
|
736
|
+
def get_zip_files(self, dids: List[str], rses: List[str]):
|
|
737
|
+
"""
|
|
738
|
+
Get zip files from Rucio.
|
|
739
|
+
|
|
740
|
+
This method retrieves zip files from Rucio by creating a list of dictionaries containing the scope and name of each file.
|
|
741
|
+
It then retrieves the replicas for these files from Rucio and stores them in a dictionary.
|
|
742
|
+
If an exception occurs during the process, it returns False and the exception message.
|
|
743
|
+
|
|
744
|
+
Parameters:
|
|
745
|
+
dids (List[str]): A list of Data Identifiers (DIDs) for which to retrieve the associated zip files.
|
|
746
|
+
rses (List[str]): A list of Rucio Storage Elements (RSEs) where the files should be replicated.
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
Tuple[bool, Union[str, Dict[str, Dict[str, Any]]]]: A tuple containing a boolean indicating the success of the operation and a dictionary of zip files or an error message.
|
|
750
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
751
|
+
"""
|
|
752
|
+
try:
|
|
753
|
+
client = self._get_rucio_client()
|
|
754
|
+
data = []
|
|
755
|
+
i_guid = 0
|
|
756
|
+
batch_size = 1000
|
|
757
|
+
ret_val = {}
|
|
758
|
+
for did in dids:
|
|
759
|
+
i_guid += 1
|
|
760
|
+
scope, lfn = did.split(":")
|
|
761
|
+
data.append({"scope": scope, "name": lfn})
|
|
762
|
+
if len(data) % batch_size == 0 or i_guid == len(dids):
|
|
763
|
+
for tmp_dict in client.list_replicas(data):
|
|
764
|
+
tmp_scope = str(tmp_dict["scope"])
|
|
765
|
+
tmp_lfn = str(tmp_dict["name"])
|
|
766
|
+
tmp_did = f"{tmp_scope}:{tmp_lfn}"
|
|
767
|
+
# RSE selection
|
|
768
|
+
for pfn in tmp_dict["pfns"]:
|
|
769
|
+
pfn_data = tmp_dict["pfns"][pfn]
|
|
770
|
+
if (rses is None or pfn_data["rse"] in rses) and pfn_data["domain"] == "zip":
|
|
771
|
+
zip_file_name = pfn.split("/")[-1]
|
|
772
|
+
zip_file_name = re.sub("\?.+$", "", zip_file_name)
|
|
773
|
+
ret_val[tmp_did] = client.get_metadata(tmp_scope, zip_file_name)
|
|
774
|
+
break
|
|
775
|
+
data = []
|
|
776
|
+
return True, ret_val
|
|
777
|
+
except Exception:
|
|
778
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
779
|
+
return False, f"{err_type} {err_value}"
|
|
780
|
+
|
|
781
|
+
# list files in dataset
|
|
782
|
+
def list_files_in_dataset(self, dataset_name: str, long: bool = False, file_list: List[str] = None):
|
|
783
|
+
"""
|
|
784
|
+
List files in a Rucio dataset.
|
|
785
|
+
|
|
786
|
+
This method lists files in a Rucio dataset by extracting the scope from the dataset name and getting the files.
|
|
787
|
+
It stores the files in a dictionary and returns it along with a status code.
|
|
788
|
+
If an exception occurs during the process, it returns the error type and value.
|
|
789
|
+
|
|
790
|
+
Parameters:
|
|
791
|
+
dataset_name (str): The name of the dataset.
|
|
792
|
+
long (bool, optional): A flag to indicate if the file list should be long. Defaults to False.
|
|
793
|
+
file_list (List[str], optional): A list of files to be listed. Defaults to None.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
Tuple[Dict[str, Dict[str, Any]], Optional[str]]: A tuple containing a dictionary of files and a string message.
|
|
797
|
+
If an exception occurs, the dictionary is None and the string contains the error message.
|
|
798
|
+
"""
|
|
799
|
+
# extract scope from dataset
|
|
800
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
801
|
+
if dataset_name.endswith("/"):
|
|
802
|
+
dataset_name = dataset_name[:-1]
|
|
803
|
+
client = self._get_rucio_client()
|
|
804
|
+
return_dict = {}
|
|
805
|
+
for file_info in client.list_files(scope, dataset_name, long=long):
|
|
806
|
+
tmp_lfn = str(file_info["name"])
|
|
807
|
+
if file_list is not None:
|
|
808
|
+
gen_lfn = re.sub("\.\d+$", "", tmp_lfn)
|
|
809
|
+
if tmp_lfn not in file_list and gen_lfn not in file_list:
|
|
810
|
+
continue
|
|
811
|
+
rucio_attrs = {}
|
|
812
|
+
rucio_attrs["chksum"] = "ad:" + str(file_info["adler32"])
|
|
813
|
+
rucio_attrs["md5sum"] = rucio_attrs["chksum"]
|
|
814
|
+
rucio_attrs["checksum"] = rucio_attrs["chksum"]
|
|
815
|
+
rucio_attrs["fsize"] = file_info["bytes"]
|
|
816
|
+
rucio_attrs["filesize"] = rucio_attrs["fsize"]
|
|
817
|
+
rucio_attrs["scope"] = str(file_info["scope"])
|
|
818
|
+
rucio_attrs["events"] = str(file_info["events"])
|
|
819
|
+
if long:
|
|
820
|
+
rucio_attrs["lumiblocknr"] = str(file_info["lumiblocknr"])
|
|
821
|
+
guid = str(f"{file_info['guid'][0:8]}-{file_info['guid'][8:12]}-{file_info['guid'][12:16]}-{file_info['guid'][16:20]}-{file_info['guid'][20:32]}")
|
|
822
|
+
rucio_attrs["guid"] = guid
|
|
823
|
+
return_dict[tmp_lfn] = rucio_attrs
|
|
824
|
+
return (return_dict, None)
|
|
825
|
+
|
|
826
|
+
# get # of files in dataset
|
|
827
|
+
def get_number_of_files(self, dataset_name: str, preset_scope: str = None):
|
|
828
|
+
"""
|
|
829
|
+
Get the number of files in a Rucio dataset.
|
|
830
|
+
|
|
831
|
+
This method retrieves the number of files in a Rucio dataset by extracting the scope from the dataset name and getting the files.
|
|
832
|
+
If an exception occurs during the process, it returns False and the exception message.
|
|
833
|
+
|
|
834
|
+
Parameters:
|
|
835
|
+
dataset_name (str): The name of the dataset.
|
|
836
|
+
preset_scope (str, optional): The scope of the dataset. Defaults to None.
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
Tuple[bool, Union[int, str]]: A tuple containing a boolean indicating the success of the operation and the number of files or an error message.
|
|
840
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
841
|
+
"""
|
|
842
|
+
# make logger
|
|
843
|
+
method_name = "get_number_of_files"
|
|
844
|
+
method_name = f"{method_name} dataset_name={dataset_name}"
|
|
845
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
846
|
+
tmp_log.debug("start")
|
|
847
|
+
# extract scope from dataset
|
|
848
|
+
scope, dataset_name = self.extract_scope(dataset_name)
|
|
849
|
+
if preset_scope is not None:
|
|
850
|
+
scope = preset_scope
|
|
851
|
+
try:
|
|
852
|
+
client = self._get_rucio_client()
|
|
853
|
+
n_files = 0
|
|
854
|
+
for _ in client.list_files(scope, dataset_name):
|
|
855
|
+
n_files += 1
|
|
856
|
+
return True, n_files
|
|
857
|
+
except DataIdentifierNotFound:
|
|
858
|
+
tmp_log.debug("dataset not found")
|
|
859
|
+
return None, "dataset not found"
|
|
860
|
+
except Exception:
|
|
861
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
862
|
+
err_msg = f"{err_type.__name__} {err_value}"
|
|
863
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
864
|
+
return False, err_msg
|
|
865
|
+
|
|
866
|
+
# list datasets with GUIDs
|
|
867
|
+
def list_datasets_by_guids(self, guids: List[str]) -> Dict[str, List[str]]:
|
|
868
|
+
"""
|
|
869
|
+
List datasets in Rucio by GUIDs.
|
|
870
|
+
|
|
871
|
+
This method lists datasets in Rucio by their GUIDs. It retrieves the datasets associated with each GUID
|
|
872
|
+
and stores them in a dictionary where the keys are the GUIDs and the values are lists of datasets.
|
|
873
|
+
|
|
874
|
+
Parameters:
|
|
875
|
+
guids (List[str]): A list of GUIDs for which to retrieve the associated datasets.
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
Dict[str, List[str]]: A dictionary mapping each GUID to a list of its associated datasets.
|
|
879
|
+
"""
|
|
880
|
+
client = self._get_rucio_client()
|
|
881
|
+
result = {}
|
|
882
|
+
for guid in guids:
|
|
883
|
+
datasets = [str(f"{i['scope']}:{i['name']}") for i in client.get_dataset_by_guid(guid)]
|
|
884
|
+
result[guid] = datasets
|
|
885
|
+
return result
|
|
886
|
+
|
|
887
|
+
# register container
|
|
888
|
+
def register_container(self, container_name: str, datasets: List[str] = None, preset_scope: str = None) -> bool:
|
|
889
|
+
"""
|
|
890
|
+
Register a container in Rucio.
|
|
891
|
+
|
|
892
|
+
This method registers a container in Rucio by extracting the scope from the container name and adding the container.
|
|
893
|
+
If the container already exists, it ignores the exception and continues.
|
|
894
|
+
If a list of datasets is provided, it adds these datasets to the container.
|
|
895
|
+
|
|
896
|
+
Parameters:
|
|
897
|
+
container_name (str): The name of the container.
|
|
898
|
+
datasets (List[str], optional): A list of datasets to be added to the container. Defaults to None.
|
|
899
|
+
preset_scope (str, optional): The scope of the container. Defaults to None.
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
bool: True if the operation is successful, False otherwise.
|
|
903
|
+
"""
|
|
904
|
+
if container_name.endswith("/"):
|
|
905
|
+
container_name = container_name[:-1]
|
|
906
|
+
# register container
|
|
907
|
+
client = self._get_rucio_client()
|
|
908
|
+
try:
|
|
909
|
+
scope, dataset_name = self.extract_scope(container_name)
|
|
910
|
+
if preset_scope is not None:
|
|
911
|
+
scope = preset_scope
|
|
912
|
+
client.add_container(scope=scope, name=container_name)
|
|
913
|
+
except DataIdentifierAlreadyExists:
|
|
914
|
+
pass
|
|
915
|
+
# add files
|
|
916
|
+
if datasets is not None and len(datasets) > 0:
|
|
917
|
+
try:
|
|
918
|
+
dataset_names = []
|
|
919
|
+
for dataset in datasets:
|
|
920
|
+
dataset_scope, dataset_name = self.extract_scope(dataset)
|
|
921
|
+
if dataset_scope:
|
|
922
|
+
dataset_name = {"scope": dataset_scope, "name": dataset_name}
|
|
923
|
+
else:
|
|
924
|
+
dataset_name = {"scope": scope, "name": dataset}
|
|
925
|
+
dataset_names.append(dataset_name)
|
|
926
|
+
client.add_datasets_to_container(scope=scope, name=container_name, dsns=dataset_names)
|
|
927
|
+
except DuplicateContent:
|
|
928
|
+
for dataset in dataset_names:
|
|
929
|
+
try:
|
|
930
|
+
client.add_datasets_to_container(scope=scope, name=container_name, dsns=[dataset])
|
|
931
|
+
except DuplicateContent:
|
|
932
|
+
pass
|
|
933
|
+
return True
|
|
934
|
+
|
|
935
|
+
# finger
|
|
936
|
+
def finger(self, distinguished_name: str):
|
|
937
|
+
"""
|
|
938
|
+
Retrieve user information from Rucio based on the distinguished name (dn).
|
|
939
|
+
|
|
940
|
+
This method retrieves user information from Rucio by using the distinguished name (dn) to identify the user.
|
|
941
|
+
It first checks if the user is identified by an X509 certificate or an OIDC token.
|
|
942
|
+
It then iterates over the list of accounts in Rucio, looking for a match with the user's distinguished name.
|
|
943
|
+
If a match is found, it retrieves the user's nickname and email and stores them in a dictionary.
|
|
944
|
+
If no match is found, it attempts to retrieve the account information directly using the distinguished name.
|
|
945
|
+
If an exception occurs during the process, it returns the error message.
|
|
946
|
+
|
|
947
|
+
Parameters:
|
|
948
|
+
distinguished_name (str): The distinguished name of the user.
|
|
949
|
+
|
|
950
|
+
Returns:
|
|
951
|
+
Tuple[bool, Union[dict, str]]: A tuple containing a boolean indicating the success of the operation and a dictionary of user information or an error message.
|
|
952
|
+
If an exception occurs, the boolean is False and the string contains the error message.
|
|
953
|
+
"""
|
|
954
|
+
try:
|
|
955
|
+
return_value = False
|
|
956
|
+
# get rucio API
|
|
957
|
+
client = self._get_rucio_client()
|
|
958
|
+
user_info = None
|
|
959
|
+
return_value = False
|
|
960
|
+
x509_user_name = CoreUtils.get_bare_dn(distinguished_name)
|
|
961
|
+
oidc_user_name = CoreUtils.get_id_from_dn(distinguished_name)
|
|
962
|
+
if oidc_user_name == x509_user_name:
|
|
963
|
+
oidc_user_name = None
|
|
964
|
+
else:
|
|
965
|
+
x509_user_name = None
|
|
966
|
+
for account_type in ["USER", "GROUP"]:
|
|
967
|
+
if x509_user_name is not None:
|
|
968
|
+
user_names = CoreUtils.get_distinguished_name_list(x509_user_name)
|
|
969
|
+
for user_name in user_names:
|
|
970
|
+
for i in client.list_accounts(account_type=account_type, identity=user_name):
|
|
971
|
+
user_info = {"nickname": i["account"], "email": i["email"]}
|
|
972
|
+
break
|
|
973
|
+
if user_info is not None:
|
|
974
|
+
break
|
|
975
|
+
else:
|
|
976
|
+
user_name = oidc_user_name
|
|
977
|
+
try:
|
|
978
|
+
if user_info is None:
|
|
979
|
+
account = client.get_account(user_name)
|
|
980
|
+
user_info = {"nickname": account["account"], "email": account["email"]}
|
|
981
|
+
except Exception:
|
|
982
|
+
pass
|
|
983
|
+
if user_info is not None:
|
|
984
|
+
return_value = True
|
|
985
|
+
break
|
|
986
|
+
except Exception as error:
|
|
987
|
+
error_message = f"{str(error)}"
|
|
988
|
+
user_info = error_message
|
|
989
|
+
return return_value, user_info
|
|
990
|
+
|
|
991
|
+
#####################################
|
|
992
|
+
# Migrated from JEDI AtlasDDMClient #
|
|
993
|
+
#####################################
|
|
994
|
+
|
|
995
|
+
# get DID string as scope:name
|
|
996
|
+
def get_did_str(self, raw_name: str) -> str:
|
|
997
|
+
scope, name = self.extract_scope(raw_name, strip_slash=True)
|
|
998
|
+
return f"{scope}:{name}"
|
|
999
|
+
|
|
1000
|
+
# wrapper for list_content
|
|
1001
|
+
def wp_list_content(self, client, scope, dsn):
|
|
1002
|
+
if dsn.endswith("/"):
|
|
1003
|
+
dsn = dsn[:-1]
|
|
1004
|
+
ret_list = []
|
|
1005
|
+
# get contents
|
|
1006
|
+
for data in client.list_content(scope, dsn):
|
|
1007
|
+
if data["type"] == "CONTAINER":
|
|
1008
|
+
ret_list += self.wp_list_content(client, data["scope"], data["name"])
|
|
1009
|
+
elif data["type"] == "DATASET":
|
|
1010
|
+
ret_list.append(f"{data['scope']}:{data['name']}")
|
|
1011
|
+
else:
|
|
1012
|
+
pass
|
|
1013
|
+
return ret_list
|
|
1014
|
+
|
|
1015
|
+
# convert output of rucio list_dataset_replicas
|
|
1016
|
+
def convert_list_dataset_replicas(self, dataset_name, use_file_lookup=False, use_vp=False, skip_incomplete_element=False):
|
|
1017
|
+
retMap = {}
|
|
1018
|
+
# get rucio API
|
|
1019
|
+
client = self._get_rucio_client()
|
|
1020
|
+
# get scope and name
|
|
1021
|
+
scope, dsn = self.extract_scope(dataset_name, strip_slash=True)
|
|
1022
|
+
# get replicas
|
|
1023
|
+
itr = client.list_dataset_replicas(scope, dsn, deep=use_file_lookup)
|
|
1024
|
+
items = []
|
|
1025
|
+
for item in itr:
|
|
1026
|
+
if "vp" not in item:
|
|
1027
|
+
item["vp"] = False
|
|
1028
|
+
items.append(item)
|
|
1029
|
+
# deep lookup if shallow gave nothing
|
|
1030
|
+
if items == [] and not use_file_lookup:
|
|
1031
|
+
itr = client.list_dataset_replicas(scope, dsn, deep=True)
|
|
1032
|
+
for item in itr:
|
|
1033
|
+
if "vp" not in item:
|
|
1034
|
+
item["vp"] = False
|
|
1035
|
+
items.append(item)
|
|
1036
|
+
# VP
|
|
1037
|
+
if use_vp:
|
|
1038
|
+
itr = client.list_dataset_replicas_vp(scope, dsn)
|
|
1039
|
+
for item in itr:
|
|
1040
|
+
if item["vp"]:
|
|
1041
|
+
# add dummy
|
|
1042
|
+
if "length" not in item:
|
|
1043
|
+
item["length"] = 1
|
|
1044
|
+
if "available_length" not in item:
|
|
1045
|
+
item["available_length"] = 1
|
|
1046
|
+
if "bytes" not in item:
|
|
1047
|
+
item["bytes"] = 1
|
|
1048
|
+
if "available_bytes" not in item:
|
|
1049
|
+
item["available_bytes"] = 1
|
|
1050
|
+
if "site" in item and "rse" not in item:
|
|
1051
|
+
item["rse"] = item["site"]
|
|
1052
|
+
items.append(item)
|
|
1053
|
+
# loop over all RSEs
|
|
1054
|
+
for item in items:
|
|
1055
|
+
rse = item["rse"]
|
|
1056
|
+
if skip_incomplete_element and (not item["available_length"] or item["length"] != item["available_length"]):
|
|
1057
|
+
continue
|
|
1058
|
+
retMap[rse] = [
|
|
1059
|
+
{
|
|
1060
|
+
"total": item["length"],
|
|
1061
|
+
"found": item["available_length"],
|
|
1062
|
+
"tsize": item["bytes"],
|
|
1063
|
+
"asize": item["available_bytes"],
|
|
1064
|
+
"vp": item["vp"],
|
|
1065
|
+
"immutable": 1,
|
|
1066
|
+
}
|
|
1067
|
+
]
|
|
1068
|
+
return retMap
|
|
1069
|
+
|
|
1070
|
+
# list RSEs
|
|
1071
|
+
def list_rses(self, filter=None):
|
|
1072
|
+
method_name = "list_rses"
|
|
1073
|
+
method_name += f" filter={filter}"
|
|
1074
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1075
|
+
tmp_log.debug("start")
|
|
1076
|
+
ret_list = []
|
|
1077
|
+
try:
|
|
1078
|
+
# get rucio API
|
|
1079
|
+
client = self._get_rucio_client()
|
|
1080
|
+
# get RSEs
|
|
1081
|
+
result = client.list_rses(filter)
|
|
1082
|
+
if result:
|
|
1083
|
+
# res is a generator yielding {"rse": "name_of_rse"}
|
|
1084
|
+
for x in result:
|
|
1085
|
+
rse = x["rse"]
|
|
1086
|
+
ret_list.append(rse)
|
|
1087
|
+
except InvalidRSEExpression:
|
|
1088
|
+
tmp_log.warning("Provided filter is considered invalid")
|
|
1089
|
+
except Exception:
|
|
1090
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1091
|
+
return None
|
|
1092
|
+
tmp_log.debug(f"got {ret_list}")
|
|
1093
|
+
return ret_list
|
|
1094
|
+
|
|
1095
|
+
# list DID rules
|
|
1096
|
+
def list_did_rules(self, dataset_name, all_accounts=False):
|
|
1097
|
+
method_name = "list_did_rules"
|
|
1098
|
+
method_name += f" dataset_name={dataset_name} all_accounts={all_accounts}"
|
|
1099
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1100
|
+
tmp_log.debug("start")
|
|
1101
|
+
ret_list = []
|
|
1102
|
+
try:
|
|
1103
|
+
# get rucio API
|
|
1104
|
+
client = self._get_rucio_client()
|
|
1105
|
+
# get scope and name
|
|
1106
|
+
scope, dsn = self.extract_scope(dataset_name, strip_slash=True)
|
|
1107
|
+
# get rules
|
|
1108
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1109
|
+
if rule["account"] == client.account or all_accounts:
|
|
1110
|
+
ret_list.append(rule)
|
|
1111
|
+
except Exception as e:
|
|
1112
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1113
|
+
return None
|
|
1114
|
+
tmp_log.debug(f"got {len(ret_list)} rules")
|
|
1115
|
+
return ret_list
|
|
1116
|
+
|
|
1117
|
+
# get dataset metadata
|
|
1118
|
+
def get_dataset_metadata(self, dataset_name, ignore_missing=False):
|
|
1119
|
+
# make logger
|
|
1120
|
+
method_name = "get_dataset_metadata"
|
|
1121
|
+
method_name = f"{method_name} dataset_name={dataset_name}"
|
|
1122
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1123
|
+
tmp_log.debug("start")
|
|
1124
|
+
try:
|
|
1125
|
+
# get rucio API
|
|
1126
|
+
client = self._get_rucio_client()
|
|
1127
|
+
# get scope and name
|
|
1128
|
+
scope, dsn = self.extract_scope(dataset_name, strip_slash=True)
|
|
1129
|
+
# get metadata
|
|
1130
|
+
if dsn.endswith("/"):
|
|
1131
|
+
dsn = dsn[:-1]
|
|
1132
|
+
tmpRet = client.get_metadata(scope, dsn)
|
|
1133
|
+
# set state
|
|
1134
|
+
if tmpRet["is_open"] is True and tmpRet["did_type"] != "CONTAINER":
|
|
1135
|
+
tmpRet["state"] = "open"
|
|
1136
|
+
else:
|
|
1137
|
+
tmpRet["state"] = "closed"
|
|
1138
|
+
tmp_log.debug(str(tmpRet))
|
|
1139
|
+
return tmpRet
|
|
1140
|
+
except DataIdentifierNotFound as e:
|
|
1141
|
+
if ignore_missing:
|
|
1142
|
+
tmp_log.warning(e)
|
|
1143
|
+
tmpRet = {}
|
|
1144
|
+
tmpRet["state"] = "missing"
|
|
1145
|
+
return tmpRet
|
|
1146
|
+
except Exception as e:
|
|
1147
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1148
|
+
return None
|
|
1149
|
+
|
|
1150
|
+
# get files in dataset
|
|
1151
|
+
def get_files_in_dataset(self, dataset_name, skip_duplicate=True, ignore_unknown=False, long_format=False, lfn_only=False):
|
|
1152
|
+
method_name = "get_files_in_dataset"
|
|
1153
|
+
method_name += f" <dataset_name={dataset_name}>"
|
|
1154
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1155
|
+
tmp_log.debug("start")
|
|
1156
|
+
try:
|
|
1157
|
+
# get Rucio API
|
|
1158
|
+
client = self._get_rucio_client()
|
|
1159
|
+
# extract scope from dataset
|
|
1160
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1161
|
+
if dsn.endswith("/"):
|
|
1162
|
+
dsn = dsn[:-1]
|
|
1163
|
+
# get length
|
|
1164
|
+
tmpMeta = client.get_metadata(scope, dsn)
|
|
1165
|
+
# get files
|
|
1166
|
+
fileMap = {}
|
|
1167
|
+
baseLFNmap = {}
|
|
1168
|
+
fileSet = set()
|
|
1169
|
+
for x in client.list_files(scope, dsn, long=long_format):
|
|
1170
|
+
# convert to old dict format
|
|
1171
|
+
lfn = str(x["name"])
|
|
1172
|
+
if lfn_only:
|
|
1173
|
+
fileSet.add(lfn)
|
|
1174
|
+
continue
|
|
1175
|
+
attrs = {}
|
|
1176
|
+
attrs["lfn"] = lfn
|
|
1177
|
+
attrs["chksum"] = "ad:" + str(x["adler32"])
|
|
1178
|
+
attrs["md5sum"] = attrs["chksum"]
|
|
1179
|
+
attrs["checksum"] = attrs["chksum"]
|
|
1180
|
+
attrs["fsize"] = x["bytes"]
|
|
1181
|
+
attrs["filesize"] = attrs["fsize"]
|
|
1182
|
+
attrs["scope"] = str(x["scope"])
|
|
1183
|
+
attrs["events"] = str(x["events"])
|
|
1184
|
+
if long_format:
|
|
1185
|
+
attrs["lumiblocknr"] = str(x["lumiblocknr"])
|
|
1186
|
+
guid = str(f"{x['guid'][0:8]}-{x['guid'][8:12]}-{x['guid'][12:16]}-{x['guid'][16:20]}-{x['guid'][20:32]}")
|
|
1187
|
+
attrs["guid"] = guid
|
|
1188
|
+
# skip duplicated files
|
|
1189
|
+
if skip_duplicate:
|
|
1190
|
+
# extract base LFN and attempt number
|
|
1191
|
+
baseLFN = re.sub("(\.(\d+))$", "", lfn)
|
|
1192
|
+
attNr = re.sub(baseLFN + "\.*", "", lfn)
|
|
1193
|
+
if attNr == "":
|
|
1194
|
+
# without attempt number
|
|
1195
|
+
attNr = -1
|
|
1196
|
+
else:
|
|
1197
|
+
attNr = int(attNr)
|
|
1198
|
+
# compare attempt numbers
|
|
1199
|
+
addMap = False
|
|
1200
|
+
if baseLFN in baseLFNmap:
|
|
1201
|
+
# use larger attempt number
|
|
1202
|
+
oldMap = baseLFNmap[baseLFN]
|
|
1203
|
+
if oldMap["attNr"] < attNr:
|
|
1204
|
+
del fileMap[oldMap["guid"]]
|
|
1205
|
+
addMap = True
|
|
1206
|
+
else:
|
|
1207
|
+
addMap = True
|
|
1208
|
+
# append
|
|
1209
|
+
if not addMap:
|
|
1210
|
+
continue
|
|
1211
|
+
baseLFNmap[baseLFN] = {"guid": guid, "attNr": attNr}
|
|
1212
|
+
fileMap[guid] = attrs
|
|
1213
|
+
if lfn_only:
|
|
1214
|
+
return_list = fileSet
|
|
1215
|
+
else:
|
|
1216
|
+
return_list = fileMap
|
|
1217
|
+
tmp_log.debug(f"done len={len(return_list)} meta={tmpMeta['length']}")
|
|
1218
|
+
if tmpMeta["length"] and tmpMeta["length"] > len(return_list):
|
|
1219
|
+
err_msg = f"file list length mismatch len={len(return_list)} != meta={tmpMeta['length']}"
|
|
1220
|
+
tmp_log.error(err_msg)
|
|
1221
|
+
return None
|
|
1222
|
+
return return_list
|
|
1223
|
+
except DataIdentifierNotFound as e:
|
|
1224
|
+
if ignore_unknown:
|
|
1225
|
+
return {}
|
|
1226
|
+
errType = e
|
|
1227
|
+
except Exception as e:
|
|
1228
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1229
|
+
return None
|
|
1230
|
+
|
|
1231
|
+
# list datasets in container
|
|
1232
|
+
def list_datasets_in_container_JEDI(self, container_name):
|
|
1233
|
+
method_name = "list_datasets_in_container_JEDI"
|
|
1234
|
+
method_name += f" <container_name={container_name}>"
|
|
1235
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1236
|
+
tmp_log.debug("start")
|
|
1237
|
+
try:
|
|
1238
|
+
# get rucio
|
|
1239
|
+
client = self._get_rucio_client()
|
|
1240
|
+
# get scope and name
|
|
1241
|
+
scope, dsn = self.extract_scope(container_name, strip_slash=True)
|
|
1242
|
+
# get contents
|
|
1243
|
+
dsList = self.wp_list_content(client, scope, dsn)
|
|
1244
|
+
tmp_log.debug("got " + str(dsList))
|
|
1245
|
+
return dsList
|
|
1246
|
+
except Exception as e:
|
|
1247
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1248
|
+
return None
|
|
1249
|
+
|
|
1250
|
+
# make staging rule
|
|
1251
|
+
def make_staging_rule(self, dataset_name, expression, activity, lifetime=None, weight=None, notify="N", source_replica_expression=None):
|
|
1252
|
+
method_name = "make_staging_rule"
|
|
1253
|
+
method_name = f"{method_name} dataset_name={dataset_name} expression={expression} activity={activity} lifetime={lifetime}"
|
|
1254
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1255
|
+
tmp_log.debug("start")
|
|
1256
|
+
ruleID = None
|
|
1257
|
+
try:
|
|
1258
|
+
if lifetime is not None:
|
|
1259
|
+
lifetime = lifetime * 24 * 60 * 60
|
|
1260
|
+
# get rucio API
|
|
1261
|
+
client = self._get_rucio_client()
|
|
1262
|
+
# get scope and name
|
|
1263
|
+
scope, dsn = self.extract_scope(dataset_name, strip_slash=True)
|
|
1264
|
+
# check if a replication rule already exists
|
|
1265
|
+
if ruleID is None:
|
|
1266
|
+
dids = [{"scope": scope, "name": dsn}]
|
|
1267
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1268
|
+
if rule["rse_expression"] == expression and rule["account"] == client.account and rule["activity"] == activity:
|
|
1269
|
+
ruleID = rule["id"]
|
|
1270
|
+
tmp_log.debug(f"rule already exists: ID={ruleID}")
|
|
1271
|
+
break
|
|
1272
|
+
# make new rule
|
|
1273
|
+
if ruleID is None:
|
|
1274
|
+
rule_id_list = client.add_replication_rule(
|
|
1275
|
+
dids=dids,
|
|
1276
|
+
copies=1,
|
|
1277
|
+
rse_expression=expression,
|
|
1278
|
+
weight=weight,
|
|
1279
|
+
lifetime=lifetime,
|
|
1280
|
+
grouping="DATASET",
|
|
1281
|
+
account=client.account,
|
|
1282
|
+
locked=False,
|
|
1283
|
+
notify=notify,
|
|
1284
|
+
ignore_availability=False,
|
|
1285
|
+
activity=activity,
|
|
1286
|
+
asynchronous=False,
|
|
1287
|
+
source_replica_expression=source_replica_expression,
|
|
1288
|
+
)
|
|
1289
|
+
ruleID = rule_id_list[0]
|
|
1290
|
+
tmp_log.debug(f"made new rule : ID={ruleID}")
|
|
1291
|
+
except Exception as e:
|
|
1292
|
+
tmp_log.error(f"failed to make staging rule with {str(e)} {traceback.format_exc()}")
|
|
1293
|
+
return None
|
|
1294
|
+
tmp_log.debug("done")
|
|
1295
|
+
return ruleID
|
|
1296
|
+
|
|
1297
|
+
# update replication rule by rule ID
|
|
1298
|
+
def update_rule_by_id(self, rule_id, set_map):
|
|
1299
|
+
method_name = "update_rule_by_id"
|
|
1300
|
+
method_name = f"{method_name} rule_id={rule_id} set_map={set_map}"
|
|
1301
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1302
|
+
tmp_log.debug("start")
|
|
1303
|
+
try:
|
|
1304
|
+
# get rucio API
|
|
1305
|
+
client = self._get_rucio_client()
|
|
1306
|
+
# update rule
|
|
1307
|
+
client.update_replication_rule(rule_id, set_map)
|
|
1308
|
+
except Exception as e:
|
|
1309
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1310
|
+
return None
|
|
1311
|
+
tmp_log.debug("done")
|
|
1312
|
+
return True
|
|
1313
|
+
|
|
1314
|
+
# get replication rule by rule ID
|
|
1315
|
+
def get_rule_by_id(self, rule_id, allow_missing=True):
|
|
1316
|
+
method_name = "get_rule_by_id"
|
|
1317
|
+
method_name = f"{method_name} rule_id={rule_id}"
|
|
1318
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1319
|
+
tmp_log.debug("start")
|
|
1320
|
+
try:
|
|
1321
|
+
# get rucio API
|
|
1322
|
+
client = self._get_rucio_client()
|
|
1323
|
+
# get rules
|
|
1324
|
+
rule = client.get_replication_rule(rule_id)
|
|
1325
|
+
except RuleNotFound as e:
|
|
1326
|
+
if allow_missing:
|
|
1327
|
+
tmp_log.warning(e)
|
|
1328
|
+
return False
|
|
1329
|
+
else:
|
|
1330
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1331
|
+
return None
|
|
1332
|
+
except Exception as e:
|
|
1333
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1334
|
+
return None
|
|
1335
|
+
tmp_log.debug(f"got rule")
|
|
1336
|
+
return rule
|
|
1337
|
+
|
|
1338
|
+
# list details of all replica locks for a rule by rule ID
|
|
1339
|
+
def list_replica_locks_by_id(self, rule_id):
|
|
1340
|
+
method_name = "list_replica_locks_by_id"
|
|
1341
|
+
method_name = f"{method_name} rule_id={rule_id}"
|
|
1342
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1343
|
+
tmp_log.debug("start")
|
|
1344
|
+
try:
|
|
1345
|
+
# get rucio API
|
|
1346
|
+
client = self._get_rucio_client()
|
|
1347
|
+
# get generator of replica locks
|
|
1348
|
+
res = client.list_replica_locks(rule_id)
|
|
1349
|
+
# turn into list
|
|
1350
|
+
ret = list(res)
|
|
1351
|
+
except Exception as e:
|
|
1352
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1353
|
+
return None
|
|
1354
|
+
tmp_log.debug(f"got replica locks")
|
|
1355
|
+
return ret
|
|
1356
|
+
|
|
1357
|
+
# delete replication rule by rule ID
|
|
1358
|
+
def delete_replication_rule(self, rule_id, allow_missing=True):
|
|
1359
|
+
method_name = "delete_replication_rule"
|
|
1360
|
+
method_name = f"{method_name} rule_id={rule_id}"
|
|
1361
|
+
tmp_log = LogWrapper(_logger, method_name)
|
|
1362
|
+
tmp_log.debug("start")
|
|
1363
|
+
try:
|
|
1364
|
+
# get rucio API
|
|
1365
|
+
client = self._get_rucio_client()
|
|
1366
|
+
# get rules
|
|
1367
|
+
ret = client.delete_replication_rule(rule_id)
|
|
1368
|
+
except RuleNotFound as e:
|
|
1369
|
+
if allow_missing:
|
|
1370
|
+
tmp_log.debug(e)
|
|
1371
|
+
return False
|
|
1372
|
+
except Exception as e:
|
|
1373
|
+
tmp_log.error(f"got error ; {traceback.format_exc()}")
|
|
1374
|
+
return None
|
|
1375
|
+
tmp_log.debug(f"deleted, return {ret}")
|
|
1376
|
+
return ret
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
# instantiate
|
|
1380
|
+
rucioAPI = RucioAPI()
|
|
1381
|
+
del RucioAPI
|