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,1708 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import traceback
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import urllib3
|
|
14
|
+
|
|
15
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
16
|
+
except Exception:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
20
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
21
|
+
from rucio.client import Client as RucioClient
|
|
22
|
+
from rucio.common.exception import (
|
|
23
|
+
DataIdentifierAlreadyExists,
|
|
24
|
+
DataIdentifierNotFound,
|
|
25
|
+
DuplicateContent,
|
|
26
|
+
DuplicateRule,
|
|
27
|
+
InvalidObject,
|
|
28
|
+
RSENotFound,
|
|
29
|
+
RuleNotFound,
|
|
30
|
+
UnsupportedOperation,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from pandajedi.jediconfig import jedi_config
|
|
34
|
+
from pandajedi.jedicore.Interaction import StatusCode
|
|
35
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
36
|
+
from pandaserver.dataservice import DataServiceUtils, ddm
|
|
37
|
+
|
|
38
|
+
from .DDMClientBase import DDMClientBase
|
|
39
|
+
|
|
40
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# class to access to ATLAS DDM
|
|
44
|
+
class AtlasDDMClient(DDMClientBase):
|
|
45
|
+
# constructor
|
|
46
|
+
def __init__(self, con):
|
|
47
|
+
# initialize base class
|
|
48
|
+
DDMClientBase.__init__(self, con)
|
|
49
|
+
# the list of fatal error
|
|
50
|
+
self.fatalErrors = []
|
|
51
|
+
# how frequently update DN/token map
|
|
52
|
+
self.timeIntervalBL = datetime.timedelta(seconds=60 * 10)
|
|
53
|
+
# dict of endpoints
|
|
54
|
+
self.endPointDict = {}
|
|
55
|
+
# time of last update for endpoint dict
|
|
56
|
+
self.lastUpdateEP = None
|
|
57
|
+
# how frequently update endpoint dict
|
|
58
|
+
self.timeIntervalEP = datetime.timedelta(seconds=60 * 10)
|
|
59
|
+
# pid
|
|
60
|
+
self.pid = os.getpid()
|
|
61
|
+
|
|
62
|
+
# get files in dataset
|
|
63
|
+
def getFilesInDataset(self, datasetName, getNumEvents=False, skipDuplicate=True, ignoreUnknown=False, longFormat=False, lfn_only=False):
|
|
64
|
+
methodName = "getFilesInDataset"
|
|
65
|
+
methodName += f" pid={self.pid}"
|
|
66
|
+
methodName += f" <datasetName={datasetName}>"
|
|
67
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
68
|
+
tmpLog.debug("start")
|
|
69
|
+
try:
|
|
70
|
+
# get Rucio API
|
|
71
|
+
client = RucioClient()
|
|
72
|
+
# extract scope from dataset
|
|
73
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
74
|
+
if dsn.endswith("/"):
|
|
75
|
+
dsn = dsn[:-1]
|
|
76
|
+
# get length
|
|
77
|
+
tmpMeta = client.get_metadata(scope, dsn)
|
|
78
|
+
# get files
|
|
79
|
+
fileMap = {}
|
|
80
|
+
baseLFNmap = {}
|
|
81
|
+
fileSet = set()
|
|
82
|
+
for x in client.list_files(scope, dsn, long=longFormat):
|
|
83
|
+
# convert to old dict format
|
|
84
|
+
lfn = str(x["name"])
|
|
85
|
+
if lfn_only:
|
|
86
|
+
fileSet.add(lfn)
|
|
87
|
+
continue
|
|
88
|
+
attrs = {}
|
|
89
|
+
attrs["lfn"] = lfn
|
|
90
|
+
attrs["chksum"] = "ad:" + str(x["adler32"])
|
|
91
|
+
attrs["md5sum"] = attrs["chksum"]
|
|
92
|
+
attrs["checksum"] = attrs["chksum"]
|
|
93
|
+
attrs["fsize"] = x["bytes"]
|
|
94
|
+
attrs["filesize"] = attrs["fsize"]
|
|
95
|
+
attrs["scope"] = str(x["scope"])
|
|
96
|
+
attrs["events"] = str(x["events"])
|
|
97
|
+
if longFormat:
|
|
98
|
+
attrs["lumiblocknr"] = str(x["lumiblocknr"])
|
|
99
|
+
guid = str(f"{x['guid'][0:8]}-{x['guid'][8:12]}-{x['guid'][12:16]}-{x['guid'][16:20]}-{x['guid'][20:32]}")
|
|
100
|
+
attrs["guid"] = guid
|
|
101
|
+
# skip duplicated files
|
|
102
|
+
if skipDuplicate:
|
|
103
|
+
# extract base LFN and attempt number
|
|
104
|
+
baseLFN = re.sub("(\.(\d+))$", "", lfn)
|
|
105
|
+
attNr = re.sub(baseLFN + "\.*", "", lfn)
|
|
106
|
+
if attNr == "":
|
|
107
|
+
# without attempt number
|
|
108
|
+
attNr = -1
|
|
109
|
+
else:
|
|
110
|
+
attNr = int(attNr)
|
|
111
|
+
# compare attempt numbers
|
|
112
|
+
addMap = False
|
|
113
|
+
if baseLFN in baseLFNmap:
|
|
114
|
+
# use larger attempt number
|
|
115
|
+
oldMap = baseLFNmap[baseLFN]
|
|
116
|
+
if oldMap["attNr"] < attNr:
|
|
117
|
+
del fileMap[oldMap["guid"]]
|
|
118
|
+
addMap = True
|
|
119
|
+
else:
|
|
120
|
+
addMap = True
|
|
121
|
+
# append
|
|
122
|
+
if not addMap:
|
|
123
|
+
continue
|
|
124
|
+
baseLFNmap[baseLFN] = {"guid": guid, "attNr": attNr}
|
|
125
|
+
fileMap[guid] = attrs
|
|
126
|
+
if lfn_only:
|
|
127
|
+
return_list = fileSet
|
|
128
|
+
else:
|
|
129
|
+
return_list = fileMap
|
|
130
|
+
tmpLog.debug(f"done len={len(return_list)} meta={tmpMeta['length']}")
|
|
131
|
+
if tmpMeta["length"] and tmpMeta["length"] > len(return_list):
|
|
132
|
+
errMsg = f"file list length mismatch len={len(return_list)} != meta={tmpMeta['length']}"
|
|
133
|
+
tmpLog.error(errMsg)
|
|
134
|
+
return self.SC_FAILED, errMsg
|
|
135
|
+
return self.SC_SUCCEEDED, return_list
|
|
136
|
+
except DataIdentifierNotFound as e:
|
|
137
|
+
if ignoreUnknown:
|
|
138
|
+
return self.SC_SUCCEEDED, {}
|
|
139
|
+
errType = e
|
|
140
|
+
except Exception as e:
|
|
141
|
+
errType = e
|
|
142
|
+
errCode, errMsg = self.checkError(errType)
|
|
143
|
+
tmpLog.error(errMsg)
|
|
144
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
145
|
+
|
|
146
|
+
# list dataset replicas
|
|
147
|
+
def listDatasetReplicas(self, datasetName, use_vp=False, detailed=False, skip_incomplete_element=False, use_deep=False, element_list=None):
|
|
148
|
+
methodName = "listDatasetReplicas"
|
|
149
|
+
methodName += f" pid={self.pid}"
|
|
150
|
+
methodName += f" <datasetName={datasetName}>"
|
|
151
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
152
|
+
tmpLog.debug("start")
|
|
153
|
+
try:
|
|
154
|
+
if not datasetName.endswith("/"):
|
|
155
|
+
# get file list
|
|
156
|
+
tmpRet = self.convertOutListDatasetReplicas(datasetName, usefileLookup=use_deep, use_vp=use_vp)
|
|
157
|
+
tmpLog.debug("got new " + str(tmpRet))
|
|
158
|
+
if detailed:
|
|
159
|
+
return self.SC_SUCCEEDED, tmpRet, {datasetName: tmpRet}
|
|
160
|
+
return self.SC_SUCCEEDED, tmpRet
|
|
161
|
+
else:
|
|
162
|
+
# list of attributes summed up
|
|
163
|
+
retMap = {}
|
|
164
|
+
detailedRetMap = {}
|
|
165
|
+
# get constituent datasets
|
|
166
|
+
if element_list:
|
|
167
|
+
dsList = ["{}:{}".format(*self.extract_scope(n)) for n in element_list]
|
|
168
|
+
else:
|
|
169
|
+
tmpS, dsList = self.listDatasetsInContainer(datasetName)
|
|
170
|
+
grandTotal = 0
|
|
171
|
+
for tmpName in dsList:
|
|
172
|
+
tmpLog.debug(tmpName)
|
|
173
|
+
tmp_status, tmp_output = self.getDatasetMetaData(tmpName)
|
|
174
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
175
|
+
raise RuntimeError(f"failed to get metadata with {tmp_output}")
|
|
176
|
+
try:
|
|
177
|
+
totalFiles = tmp_output["length"]
|
|
178
|
+
if not totalFiles:
|
|
179
|
+
totalFiles = 0
|
|
180
|
+
except Exception:
|
|
181
|
+
totalFiles = 0
|
|
182
|
+
tmpRet = self.convertOutListDatasetReplicas(tmpName, usefileLookup=use_deep, use_vp=use_vp, skip_incomplete_element=skip_incomplete_element)
|
|
183
|
+
detailedRetMap[tmpName] = tmpRet
|
|
184
|
+
# loop over all sites
|
|
185
|
+
for tmpSite, tmpValMap in tmpRet.items():
|
|
186
|
+
# add site
|
|
187
|
+
retMap.setdefault(tmpSite, [{"found": 0}])
|
|
188
|
+
# sum
|
|
189
|
+
try:
|
|
190
|
+
retMap[tmpSite][-1]["found"] += int(tmpValMap[-1]["found"])
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
# total
|
|
194
|
+
try:
|
|
195
|
+
if totalFiles < int(tmpValMap[-1]["total"]):
|
|
196
|
+
totalFiles = int(tmpValMap[-1]["total"])
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
grandTotal += totalFiles
|
|
200
|
+
# set total
|
|
201
|
+
for tmpSite in retMap.keys():
|
|
202
|
+
retMap[tmpSite][-1]["total"] = grandTotal
|
|
203
|
+
# return
|
|
204
|
+
tmpLog.debug("got " + str(retMap))
|
|
205
|
+
if detailed:
|
|
206
|
+
return self.SC_SUCCEEDED, retMap, detailedRetMap
|
|
207
|
+
return self.SC_SUCCEEDED, retMap
|
|
208
|
+
except Exception as e:
|
|
209
|
+
errType = e
|
|
210
|
+
errCode, errMsg = self.checkError(errType)
|
|
211
|
+
tmpLog.error(errMsg + traceback.format_exc())
|
|
212
|
+
if detailed:
|
|
213
|
+
return errCode, f"{methodName} : {errMsg}", None
|
|
214
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
215
|
+
|
|
216
|
+
# list replicas per dataset
|
|
217
|
+
def listReplicasPerDataset(self, datasetName, deepScan=False):
|
|
218
|
+
methodName = "listReplicasPerDataset"
|
|
219
|
+
methodName += f" pid={self.pid}"
|
|
220
|
+
methodName += f" <datasetName={datasetName}>"
|
|
221
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
222
|
+
tmpLog.debug(f"start with deepScan={deepScan}")
|
|
223
|
+
try:
|
|
224
|
+
# get rucio API
|
|
225
|
+
client = RucioClient()
|
|
226
|
+
# get scope and name
|
|
227
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
228
|
+
datasets = []
|
|
229
|
+
if not datasetName.endswith("/"):
|
|
230
|
+
datasets = [dsn]
|
|
231
|
+
else:
|
|
232
|
+
# get constituent datasets
|
|
233
|
+
itr = client.list_content(scope, dsn)
|
|
234
|
+
datasets = [i["name"] for i in itr]
|
|
235
|
+
retMap = {}
|
|
236
|
+
for tmpName in datasets:
|
|
237
|
+
retMap[tmpName] = self.convertOutListDatasetReplicas(tmpName, deepScan)
|
|
238
|
+
tmpLog.debug("got " + str(retMap))
|
|
239
|
+
return self.SC_SUCCEEDED, retMap
|
|
240
|
+
except Exception as e:
|
|
241
|
+
errType = e
|
|
242
|
+
errCode, errMsg = self.checkError(errType)
|
|
243
|
+
tmpLog.error(errMsg)
|
|
244
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
245
|
+
|
|
246
|
+
# get site property
|
|
247
|
+
def getSiteProperty(self, seName, attribute):
|
|
248
|
+
methodName = "getSiteProperty"
|
|
249
|
+
methodName += f" pid={self.pid}"
|
|
250
|
+
self.updateEndPointDict()
|
|
251
|
+
try:
|
|
252
|
+
retVal = self.endPointDict[seName][attribute]
|
|
253
|
+
return self.SC_SUCCEEDED, retVal
|
|
254
|
+
except Exception as e:
|
|
255
|
+
errType = e
|
|
256
|
+
errCode, errMsg = self.checkError(errType)
|
|
257
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
258
|
+
|
|
259
|
+
# get site alternateName
|
|
260
|
+
def getSiteAlternateName(self, se_name):
|
|
261
|
+
self.updateEndPointDict()
|
|
262
|
+
if se_name in self.endPointDict:
|
|
263
|
+
return [self.endPointDict[se_name]["site"]]
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
def SiteHasCompleteReplica(self, dataset_replica_map, endpoint, total_files_in_dataset):
|
|
267
|
+
"""
|
|
268
|
+
Checks the #found files at site == #total files at site == #files in dataset. VP is regarded as complete
|
|
269
|
+
:return: True or False
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
if "vp" in dataset_replica_map[endpoint][-1]:
|
|
273
|
+
if dataset_replica_map[endpoint][-1]["vp"]:
|
|
274
|
+
return True
|
|
275
|
+
found_tmp = dataset_replica_map[endpoint][-1]["found"]
|
|
276
|
+
total_tmp = dataset_replica_map[endpoint][-1]["total"]
|
|
277
|
+
if found_tmp is not None and total_tmp == found_tmp and total_tmp >= total_files_in_dataset:
|
|
278
|
+
return True
|
|
279
|
+
except KeyError:
|
|
280
|
+
pass
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
def getAvailableFiles(
|
|
284
|
+
self,
|
|
285
|
+
dataset_spec,
|
|
286
|
+
site_endpoint_map,
|
|
287
|
+
site_mapper,
|
|
288
|
+
check_LFC=False,
|
|
289
|
+
check_completeness=True,
|
|
290
|
+
storage_token=None,
|
|
291
|
+
complete_only=False,
|
|
292
|
+
use_vp=True,
|
|
293
|
+
file_scan_in_container=True,
|
|
294
|
+
use_deep=False,
|
|
295
|
+
element_list=None,
|
|
296
|
+
):
|
|
297
|
+
"""
|
|
298
|
+
:param dataset_spec: dataset spec object
|
|
299
|
+
:param site_endpoint_map: panda sites to ddm endpoints map. The list of panda sites includes the ones to scan
|
|
300
|
+
:param site_mapper: site mapper object
|
|
301
|
+
:param check_LFC: check/ask Tadashi/probably obsolete
|
|
302
|
+
:param check_completeness:
|
|
303
|
+
:param storage_token:
|
|
304
|
+
:param complete_only: check only for complete replicas
|
|
305
|
+
:param use_vp: use virtual placement
|
|
306
|
+
:param file_scan_in_container: enable file lookup for container
|
|
307
|
+
:param use_deep: use deep option for replica lookup
|
|
308
|
+
:param element_list: interesting elements in dataset container
|
|
309
|
+
|
|
310
|
+
TODO: do we need NG, do we need alternate names
|
|
311
|
+
TODO: the storage_token is not used anymore
|
|
312
|
+
:return:
|
|
313
|
+
"""
|
|
314
|
+
# make logger
|
|
315
|
+
method_name = "getAvailableFiles"
|
|
316
|
+
method_name += f" pid={self.pid}"
|
|
317
|
+
method_name += f" < jediTaskID={dataset_spec.jediTaskID} datasetID={dataset_spec.datasetID} >"
|
|
318
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
319
|
+
loopStart = naive_utcnow()
|
|
320
|
+
try:
|
|
321
|
+
tmp_log.debug(
|
|
322
|
+
"start datasetName={} check_completeness={} nFiles={} nSites={} "
|
|
323
|
+
"complete_only={}".format(dataset_spec.datasetName, check_completeness, len(dataset_spec.Files), len(site_endpoint_map), complete_only)
|
|
324
|
+
)
|
|
325
|
+
# update the definition of all endpoints from AGIS
|
|
326
|
+
self.updateEndPointDict()
|
|
327
|
+
|
|
328
|
+
# get the file map
|
|
329
|
+
tmp_status, tmp_output = self.getDatasetMetaData(dataset_spec.datasetName)
|
|
330
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
331
|
+
regTime = naive_utcnow() - loopStart
|
|
332
|
+
tmp_log.error(f"failed in {regTime.seconds} sec to get metadata with {tmp_output}")
|
|
333
|
+
return tmp_status, tmp_output
|
|
334
|
+
total_files_in_dataset = tmp_output["length"]
|
|
335
|
+
if total_files_in_dataset is None:
|
|
336
|
+
total_files_in_dataset = 0
|
|
337
|
+
if tmp_output["did_type"] == "CONTAINER":
|
|
338
|
+
is_container = True
|
|
339
|
+
else:
|
|
340
|
+
is_container = False
|
|
341
|
+
|
|
342
|
+
# get the dataset replica map
|
|
343
|
+
tmp_status, tmp_output, detailed_replica_map = self.listDatasetReplicas(
|
|
344
|
+
dataset_spec.datasetName, use_vp=use_vp, detailed=True, use_deep=use_deep, element_list=element_list
|
|
345
|
+
)
|
|
346
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
347
|
+
regTime = naive_utcnow() - loopStart
|
|
348
|
+
tmp_log.error(f"failed in {regTime.seconds} sec to get dataset replicas with {tmp_output}")
|
|
349
|
+
return tmp_status, tmp_output
|
|
350
|
+
dataset_replica_map = tmp_output
|
|
351
|
+
|
|
352
|
+
# collect GUIDs and LFNs
|
|
353
|
+
file_map = {} # GUID to LFN
|
|
354
|
+
lfn_filespec_map = {} # LFN to file spec
|
|
355
|
+
scope_map = {} # LFN to scope list
|
|
356
|
+
for tmp_file in dataset_spec.Files:
|
|
357
|
+
file_map[tmp_file.GUID] = tmp_file.lfn
|
|
358
|
+
lfn_filespec_map.setdefault(tmp_file.lfn, [])
|
|
359
|
+
lfn_filespec_map[tmp_file.lfn].append(tmp_file)
|
|
360
|
+
scope_map[tmp_file.lfn] = tmp_file.scope
|
|
361
|
+
|
|
362
|
+
complete_replica_map = {}
|
|
363
|
+
endpoint_storagetype_map = {}
|
|
364
|
+
rse_list = []
|
|
365
|
+
|
|
366
|
+
# figure out complete replicas and storage types
|
|
367
|
+
for site_name, endpoint_list in site_endpoint_map.items():
|
|
368
|
+
tmp_site_spec = site_mapper.getSite(site_name)
|
|
369
|
+
|
|
370
|
+
has_complete = False
|
|
371
|
+
tmp_rse_list = []
|
|
372
|
+
|
|
373
|
+
# loop over all endpoints
|
|
374
|
+
for endpoint in endpoint_list:
|
|
375
|
+
# storage type
|
|
376
|
+
tmp_status, is_tape = self.getSiteProperty(endpoint, "is_tape")
|
|
377
|
+
if is_tape:
|
|
378
|
+
storage_type = "localtape"
|
|
379
|
+
else:
|
|
380
|
+
storage_type = "localdisk"
|
|
381
|
+
|
|
382
|
+
if (
|
|
383
|
+
self.SiteHasCompleteReplica(dataset_replica_map, endpoint, total_files_in_dataset)
|
|
384
|
+
or (endpoint in dataset_replica_map and not check_completeness)
|
|
385
|
+
or DataServiceUtils.isCachedFile(dataset_spec.datasetName, tmp_site_spec)
|
|
386
|
+
):
|
|
387
|
+
complete_replica_map[endpoint] = storage_type
|
|
388
|
+
has_complete = True
|
|
389
|
+
|
|
390
|
+
# no scan for many-time datasets or disabled completeness check
|
|
391
|
+
if dataset_spec.isManyTime() or (not check_completeness and endpoint not in dataset_replica_map) or complete_only:
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
# disable file lookup if unnecessary
|
|
395
|
+
if endpoint not in rse_list and (file_scan_in_container or not is_container):
|
|
396
|
+
tmp_rse_list.append(endpoint)
|
|
397
|
+
|
|
398
|
+
endpoint_storagetype_map[endpoint] = storage_type
|
|
399
|
+
|
|
400
|
+
# add to list to trigger file lookup if complete replica is unavailable
|
|
401
|
+
if not has_complete and tmp_rse_list:
|
|
402
|
+
rse_list += tmp_rse_list
|
|
403
|
+
|
|
404
|
+
# get the file locations from Rucio
|
|
405
|
+
if len(rse_list) > 0:
|
|
406
|
+
tmp_log.debug(f"lookup file replicas in Rucio for RSEs: {rse_list}")
|
|
407
|
+
tmp_status, rucio_lfn_to_rse_map = self.jedi_list_replicas(file_map, rse_list, scopes=scope_map)
|
|
408
|
+
tmp_log.debug(f"lookup file replicas return status: {str(tmp_status)}")
|
|
409
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
410
|
+
raise RuntimeError(rucio_lfn_to_rse_map)
|
|
411
|
+
else:
|
|
412
|
+
rucio_lfn_to_rse_map = dict()
|
|
413
|
+
if not file_scan_in_container and is_container:
|
|
414
|
+
# remove incomplete
|
|
415
|
+
detailed_comp_replica_map = dict()
|
|
416
|
+
for tmp_ds_name, tmp_ds_value in detailed_replica_map.items():
|
|
417
|
+
new_map = {}
|
|
418
|
+
for tmp_k, tmp_v in tmp_ds_value.items():
|
|
419
|
+
if tmp_v[0]["total"] and tmp_v[0]["total"] == tmp_v[0]["found"]:
|
|
420
|
+
new_map[tmp_k] = tmp_v
|
|
421
|
+
if new_map:
|
|
422
|
+
detailed_comp_replica_map[tmp_ds_name] = new_map
|
|
423
|
+
# make file list from detailed replica map
|
|
424
|
+
files_in_container = {}
|
|
425
|
+
for tmp_ds_name in detailed_comp_replica_map.keys():
|
|
426
|
+
tmp_status, tmp_files = self.getFilesInDataset(tmp_ds_name, ignoreUnknown=True, lfn_only=True)
|
|
427
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
428
|
+
raise RuntimeError(tmp_files)
|
|
429
|
+
for tmp_lfn in tmp_files:
|
|
430
|
+
files_in_container[tmp_lfn] = tmp_ds_name
|
|
431
|
+
for tmp_file in dataset_spec.Files:
|
|
432
|
+
if tmp_file.lfn in files_in_container and files_in_container[tmp_file.lfn] in detailed_comp_replica_map:
|
|
433
|
+
rucio_lfn_to_rse_map[tmp_file.lfn] = detailed_comp_replica_map[files_in_container[tmp_file.lfn]]
|
|
434
|
+
|
|
435
|
+
# initialize the return map and add complete/cached replicas
|
|
436
|
+
return_map = {}
|
|
437
|
+
checked_dst = set()
|
|
438
|
+
for site_name, tmp_endpoints in site_endpoint_map.items():
|
|
439
|
+
return_map.setdefault(site_name, {"localdisk": [], "localtape": [], "cache": [], "remote": []})
|
|
440
|
+
tmp_site_spec = site_mapper.getSite(site_name)
|
|
441
|
+
|
|
442
|
+
# check if the dataset is cached
|
|
443
|
+
if DataServiceUtils.isCachedFile(dataset_spec.datasetName, tmp_site_spec):
|
|
444
|
+
# add to cached file list
|
|
445
|
+
return_map[site_name]["cache"] += dataset_spec.Files
|
|
446
|
+
|
|
447
|
+
# complete replicas
|
|
448
|
+
if not check_LFC:
|
|
449
|
+
for tmp_endpoint in tmp_endpoints:
|
|
450
|
+
if tmp_endpoint in complete_replica_map:
|
|
451
|
+
storage_type = complete_replica_map[tmp_endpoint]
|
|
452
|
+
return_map[site_name][storage_type] += dataset_spec.Files
|
|
453
|
+
checked_dst.add(site_name)
|
|
454
|
+
|
|
455
|
+
# loop over all available LFNs
|
|
456
|
+
available_lfns = sorted(rucio_lfn_to_rse_map.keys())
|
|
457
|
+
for tmp_lfn in available_lfns:
|
|
458
|
+
tmp_filespec_list = lfn_filespec_map[tmp_lfn]
|
|
459
|
+
tmp_filespec = lfn_filespec_map[tmp_lfn][0]
|
|
460
|
+
for site in site_endpoint_map:
|
|
461
|
+
for endpoint in site_endpoint_map[site]:
|
|
462
|
+
if endpoint in rucio_lfn_to_rse_map[tmp_lfn] and endpoint in endpoint_storagetype_map:
|
|
463
|
+
storage_type = endpoint_storagetype_map[endpoint]
|
|
464
|
+
if tmp_filespec not in return_map[site][storage_type]:
|
|
465
|
+
return_map[site][storage_type] += tmp_filespec_list
|
|
466
|
+
checked_dst.add(site)
|
|
467
|
+
break
|
|
468
|
+
|
|
469
|
+
# aggregate all types of storage types into the 'all' key
|
|
470
|
+
for site, storage_type_files in return_map.items():
|
|
471
|
+
site_all_file_list = set()
|
|
472
|
+
for storage_type, file_list in storage_type_files.items():
|
|
473
|
+
for tmp_file_spec in file_list:
|
|
474
|
+
site_all_file_list.add(tmp_file_spec)
|
|
475
|
+
storage_type_files["all"] = site_all_file_list
|
|
476
|
+
|
|
477
|
+
# dump for logging
|
|
478
|
+
logging_str = ""
|
|
479
|
+
for site, storage_type_file in return_map.items():
|
|
480
|
+
logging_str += f"{site}:("
|
|
481
|
+
for storage_type, file_list in storage_type_file.items():
|
|
482
|
+
logging_str += f"{storage_type}:{len(file_list)},"
|
|
483
|
+
logging_str = logging_str[:-1]
|
|
484
|
+
logging_str += ") "
|
|
485
|
+
logging_str = logging_str[:-1]
|
|
486
|
+
tmp_log.debug(logging_str)
|
|
487
|
+
|
|
488
|
+
# return
|
|
489
|
+
regTime = naive_utcnow() - loopStart
|
|
490
|
+
tmp_log.debug(f"done in {regTime.seconds} sec")
|
|
491
|
+
return self.SC_SUCCEEDED, return_map
|
|
492
|
+
except Exception as e:
|
|
493
|
+
regTime = naive_utcnow() - loopStart
|
|
494
|
+
error_message = f"failed in {regTime.seconds} sec with {str(e)} {traceback.format_exc()} "
|
|
495
|
+
tmp_log.error(error_message)
|
|
496
|
+
return self.SC_FAILED, f"{self.__class__.__name__}.{method_name} {error_message}"
|
|
497
|
+
|
|
498
|
+
def jedi_list_replicas(self, files, storages, scopes={}):
|
|
499
|
+
try:
|
|
500
|
+
method_name = "jedi_list_replicas"
|
|
501
|
+
method_name += f" pid={self.pid}"
|
|
502
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
503
|
+
client = RucioClient()
|
|
504
|
+
i_guid = 0
|
|
505
|
+
max_guid = 1000 # do 1000 guids in each Rucio call
|
|
506
|
+
lfn_to_rses_map = {}
|
|
507
|
+
dids = []
|
|
508
|
+
i_loop = 0
|
|
509
|
+
startTime = naive_utcnow()
|
|
510
|
+
tmp_log.debug("start")
|
|
511
|
+
for guid, lfn in files.items():
|
|
512
|
+
i_guid += 1
|
|
513
|
+
scope = scopes[lfn]
|
|
514
|
+
dids.append({"scope": scope, "name": lfn})
|
|
515
|
+
if len(dids) % max_guid == 0 or i_guid == len(files):
|
|
516
|
+
i_loop += 1
|
|
517
|
+
tmp_log.debug(f"lookup {i_loop} start")
|
|
518
|
+
loopStart = naive_utcnow()
|
|
519
|
+
x = client.list_replicas(dids, resolve_archives=True)
|
|
520
|
+
regTime = naive_utcnow() - loopStart
|
|
521
|
+
tmp_log.info(f"rucio.list_replicas took {regTime.seconds} sec for {len(dids)} files")
|
|
522
|
+
loopStart = naive_utcnow()
|
|
523
|
+
for tmp_dict in x:
|
|
524
|
+
try:
|
|
525
|
+
tmp_LFN = str(tmp_dict["name"])
|
|
526
|
+
lfn_to_rses_map[tmp_LFN] = tmp_dict["rses"]
|
|
527
|
+
except Exception:
|
|
528
|
+
pass
|
|
529
|
+
# reset the dids list for the next bulk for Rucio
|
|
530
|
+
dids = []
|
|
531
|
+
regTime = naive_utcnow() - loopStart
|
|
532
|
+
tmp_log.debug(f"lookup {i_loop} end in {regTime.seconds} sec")
|
|
533
|
+
regTime = naive_utcnow() - startTime
|
|
534
|
+
tmp_log.debug(f"end in {regTime.seconds} sec")
|
|
535
|
+
except Exception as e:
|
|
536
|
+
regTime = naive_utcnow() - startTime
|
|
537
|
+
tmp_log.error(f"failed in {regTime.seconds} sec")
|
|
538
|
+
return self.SC_FAILED, f"file lookup failed with {str(e)} {traceback.format_exc()}"
|
|
539
|
+
|
|
540
|
+
return self.SC_SUCCEEDED, lfn_to_rses_map
|
|
541
|
+
|
|
542
|
+
# list file replicas with dataset name/scope
|
|
543
|
+
def jedi_list_replicas_with_dataset(self, datasetName):
|
|
544
|
+
try:
|
|
545
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
546
|
+
client = RucioClient()
|
|
547
|
+
lfn_to_rses_map = {}
|
|
548
|
+
dids = [{"scope": scope, "name": dsn}]
|
|
549
|
+
for tmp_dict in client.list_replicas(dids, resolve_archives=True):
|
|
550
|
+
try:
|
|
551
|
+
tmp_LFN = str(tmp_dict["name"])
|
|
552
|
+
except Exception:
|
|
553
|
+
continue
|
|
554
|
+
lfn_to_rses_map[tmp_LFN] = tmp_dict["rses"]
|
|
555
|
+
except Exception:
|
|
556
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
557
|
+
return self.SC_FAILED, f"file lookup failed with {err_type}:{err_value} {traceback.format_exc()}"
|
|
558
|
+
return self.SC_SUCCEEDED, lfn_to_rses_map
|
|
559
|
+
|
|
560
|
+
# get dataset metadata
|
|
561
|
+
def getDatasetMetaData(self, datasetName, ignore_missing=False):
|
|
562
|
+
# make logger
|
|
563
|
+
methodName = "getDatasetMetaData"
|
|
564
|
+
methodName += f" pid={self.pid}"
|
|
565
|
+
methodName = f"{methodName} datasetName={datasetName}"
|
|
566
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
567
|
+
tmpLog.debug("start")
|
|
568
|
+
try:
|
|
569
|
+
# get rucio API
|
|
570
|
+
client = RucioClient()
|
|
571
|
+
# get scope and name
|
|
572
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
573
|
+
# get metadata
|
|
574
|
+
if dsn.endswith("/"):
|
|
575
|
+
dsn = dsn[:-1]
|
|
576
|
+
tmpRet = client.get_metadata(scope, dsn)
|
|
577
|
+
# set state
|
|
578
|
+
if tmpRet["is_open"] is True and tmpRet["did_type"] != "CONTAINER":
|
|
579
|
+
tmpRet["state"] = "open"
|
|
580
|
+
else:
|
|
581
|
+
tmpRet["state"] = "closed"
|
|
582
|
+
tmpLog.debug(str(tmpRet))
|
|
583
|
+
return self.SC_SUCCEEDED, tmpRet
|
|
584
|
+
except DataIdentifierNotFound as e:
|
|
585
|
+
errType = e
|
|
586
|
+
errCode, errMsg = self.checkError(errType)
|
|
587
|
+
if ignore_missing:
|
|
588
|
+
tmpLog.debug(errMsg)
|
|
589
|
+
tmpRet = {}
|
|
590
|
+
tmpRet["state"] = "missing"
|
|
591
|
+
return self.SC_SUCCEEDED, tmpRet
|
|
592
|
+
except Exception as e:
|
|
593
|
+
errType = e
|
|
594
|
+
errCode, errMsg = self.checkError(errType)
|
|
595
|
+
tmpLog.error(errMsg)
|
|
596
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
597
|
+
|
|
598
|
+
# check error
|
|
599
|
+
def checkError(self, errType):
|
|
600
|
+
errMsg = f"{str(type(errType))} : {str(errType)}"
|
|
601
|
+
if type(errType) in self.fatalErrors:
|
|
602
|
+
# fatal error
|
|
603
|
+
return self.SC_FATAL, errMsg
|
|
604
|
+
else:
|
|
605
|
+
# temporary error
|
|
606
|
+
return self.SC_FAILED, errMsg
|
|
607
|
+
|
|
608
|
+
# list dataset/container
|
|
609
|
+
def listDatasets(self, datasetName, ignorePandaDS=True):
|
|
610
|
+
methodName = "listDatasets"
|
|
611
|
+
methodName += f" pid={self.pid}"
|
|
612
|
+
methodName += f" <datasetName={datasetName}>"
|
|
613
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
614
|
+
tmpLog.debug("start")
|
|
615
|
+
try:
|
|
616
|
+
# get rucio API
|
|
617
|
+
client = RucioClient()
|
|
618
|
+
# get scope and name
|
|
619
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
620
|
+
filters = {}
|
|
621
|
+
if dsn.endswith("/"):
|
|
622
|
+
dsn = dsn[:-1]
|
|
623
|
+
filters["name"] = dsn
|
|
624
|
+
dsList = set()
|
|
625
|
+
for name in client.list_dids(scope, filters, "dataset"):
|
|
626
|
+
dsList.add(f"{scope}:{name}")
|
|
627
|
+
for name in client.list_dids(scope, filters, "container"):
|
|
628
|
+
dsList.add(f"{scope}:{name}/")
|
|
629
|
+
dsList = list(dsList)
|
|
630
|
+
# ignore panda internal datasets
|
|
631
|
+
if ignorePandaDS:
|
|
632
|
+
tmpDsList = []
|
|
633
|
+
for tmpDS in dsList:
|
|
634
|
+
if re.search("_dis\d+$", tmpDS) is not None or re.search("_sub\d+$", tmpDS):
|
|
635
|
+
continue
|
|
636
|
+
tmpDsList.append(tmpDS)
|
|
637
|
+
dsList = tmpDsList
|
|
638
|
+
tmpLog.debug("got " + str(dsList))
|
|
639
|
+
return self.SC_SUCCEEDED, dsList
|
|
640
|
+
except Exception as e:
|
|
641
|
+
errType = e
|
|
642
|
+
errCode, errMsg = self.checkError(errType)
|
|
643
|
+
tmpLog.error(errMsg)
|
|
644
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
645
|
+
|
|
646
|
+
# register new dataset/container
|
|
647
|
+
def registerNewDataset(self, datasetName, backEnd="rucio", location=None, lifetime=None, metaData=None, resurrect=False):
|
|
648
|
+
methodName = "registerNewDataset"
|
|
649
|
+
methodName += f" pid={self.pid}"
|
|
650
|
+
methodName += f" <datasetName={datasetName}>"
|
|
651
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
652
|
+
tmpLog.debug(f"start location={location} lifetime={lifetime}")
|
|
653
|
+
try:
|
|
654
|
+
# get rucio API
|
|
655
|
+
client = RucioClient()
|
|
656
|
+
# get scope and name
|
|
657
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
658
|
+
# lifetime
|
|
659
|
+
if lifetime is not None:
|
|
660
|
+
lifetime = lifetime * 86400
|
|
661
|
+
# register
|
|
662
|
+
if not datasetName.endswith("/"):
|
|
663
|
+
# register dataset
|
|
664
|
+
name = dsn
|
|
665
|
+
client.add_dataset(scope, name, meta=metaData, lifetime=lifetime, rse=location)
|
|
666
|
+
else:
|
|
667
|
+
# register container
|
|
668
|
+
name = dsn
|
|
669
|
+
client.add_container(scope=scope, name=name)
|
|
670
|
+
except DataIdentifierAlreadyExists:
|
|
671
|
+
pass
|
|
672
|
+
except InvalidObject as e:
|
|
673
|
+
errMsg = f"{InvalidObject} : {str(e)}"
|
|
674
|
+
tmpLog.error(errMsg)
|
|
675
|
+
return self.SC_FATAL, f"{methodName} : {errMsg}"
|
|
676
|
+
except Exception as e:
|
|
677
|
+
errType = e
|
|
678
|
+
resurrected = False
|
|
679
|
+
# try to resurrect
|
|
680
|
+
if "DELETED_DIDS_PK violated" in str(errType) and resurrect:
|
|
681
|
+
try:
|
|
682
|
+
client.resurrect([{"scope": scope, "name": name}])
|
|
683
|
+
resurrected = True
|
|
684
|
+
except Exception:
|
|
685
|
+
pass
|
|
686
|
+
if not resurrected:
|
|
687
|
+
errCode, errMsg = self.checkError(errType)
|
|
688
|
+
tmpLog.error(errMsg)
|
|
689
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
690
|
+
tmpLog.debug("done")
|
|
691
|
+
return self.SC_SUCCEEDED, True
|
|
692
|
+
|
|
693
|
+
# wrapper for list_content
|
|
694
|
+
def wp_list_content(self, client, scope, dsn):
|
|
695
|
+
if dsn.endswith("/"):
|
|
696
|
+
dsn = dsn[:-1]
|
|
697
|
+
retList = []
|
|
698
|
+
# get contents
|
|
699
|
+
for data in client.list_content(scope, dsn):
|
|
700
|
+
if data["type"] == "CONTAINER":
|
|
701
|
+
retList += self.wp_list_content(client, data["scope"], data["name"])
|
|
702
|
+
elif data["type"] == "DATASET":
|
|
703
|
+
retList.append(f"{data['scope']}:{data['name']}")
|
|
704
|
+
else:
|
|
705
|
+
pass
|
|
706
|
+
return retList
|
|
707
|
+
|
|
708
|
+
# list datasets in container
|
|
709
|
+
def listDatasetsInContainer(self, containerName):
|
|
710
|
+
methodName = "listDatasetsInContainer"
|
|
711
|
+
methodName += f" pid={self.pid}"
|
|
712
|
+
methodName += f" <containerName={containerName}>"
|
|
713
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
714
|
+
tmpLog.debug("start")
|
|
715
|
+
try:
|
|
716
|
+
# get rucio
|
|
717
|
+
client = RucioClient()
|
|
718
|
+
# get scope and name
|
|
719
|
+
scope, dsn = self.extract_scope(containerName)
|
|
720
|
+
# get contents
|
|
721
|
+
dsList = self.wp_list_content(client, scope, dsn)
|
|
722
|
+
tmpLog.debug("got " + str(dsList))
|
|
723
|
+
return self.SC_SUCCEEDED, dsList
|
|
724
|
+
except Exception as e:
|
|
725
|
+
errType = e
|
|
726
|
+
errCode, errMsg = self.checkError(errType)
|
|
727
|
+
tmpLog.error(errMsg)
|
|
728
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
729
|
+
|
|
730
|
+
# expand Container
|
|
731
|
+
def expandContainer(self, containerName):
|
|
732
|
+
methodName = "expandContainer"
|
|
733
|
+
methodName += f" pid={self.pid}"
|
|
734
|
+
methodName += f" <contName={containerName}>"
|
|
735
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
736
|
+
tmpLog.debug("start")
|
|
737
|
+
try:
|
|
738
|
+
ds_size_map = {}
|
|
739
|
+
# get real names
|
|
740
|
+
tmpS, tmpRealNameList = self.listDatasets(containerName)
|
|
741
|
+
if tmpS != self.SC_SUCCEEDED:
|
|
742
|
+
tmpLog.error("failed to get real names")
|
|
743
|
+
return tmpS, tmpRealNameList
|
|
744
|
+
# loop over all names
|
|
745
|
+
for tmpRealName in tmpRealNameList:
|
|
746
|
+
# container
|
|
747
|
+
if tmpRealName.endswith("/"):
|
|
748
|
+
# get contents
|
|
749
|
+
tmpS, tmpO = self.listDatasetsInContainer(tmpRealName)
|
|
750
|
+
if tmpS != self.SC_SUCCEEDED:
|
|
751
|
+
tmpLog.error(f"failed to get datasets in {tmpRealName}")
|
|
752
|
+
return tmpS, tmpO
|
|
753
|
+
else:
|
|
754
|
+
tmpO = [tmpRealName]
|
|
755
|
+
# collect dataset names
|
|
756
|
+
non_empty_datasets = 0
|
|
757
|
+
for dataset_name in tmpO:
|
|
758
|
+
if dataset_name in ds_size_map:
|
|
759
|
+
continue
|
|
760
|
+
if non_empty_datasets > 10 or len(tmpO) == 1:
|
|
761
|
+
# skip metadata check as only one or enough datasets are available
|
|
762
|
+
n_files = 0
|
|
763
|
+
else:
|
|
764
|
+
# get number of files
|
|
765
|
+
tmp_status, tmp_output = self.getDatasetMetaData(dataset_name)
|
|
766
|
+
if tmp_status != self.SC_SUCCEEDED:
|
|
767
|
+
raise RuntimeError(f"failed to get metadata with {tmp_output}")
|
|
768
|
+
n_files = tmp_output["length"] if tmp_output["length"] else 0
|
|
769
|
+
if n_files > 0:
|
|
770
|
+
non_empty_datasets += 1
|
|
771
|
+
ds_size_map[dataset_name] = n_files
|
|
772
|
+
# sort by name
|
|
773
|
+
ds_size_map = dict(sorted(ds_size_map.items()))
|
|
774
|
+
# reverse sort by size to have larger datasets first
|
|
775
|
+
ds_list = [k for k in sorted(ds_size_map, key=ds_size_map.get, reverse=True)]
|
|
776
|
+
# return
|
|
777
|
+
tmpLog.debug(f"got {str(ds_list)}")
|
|
778
|
+
return self.SC_SUCCEEDED, ds_list
|
|
779
|
+
except Exception as e:
|
|
780
|
+
errType = e
|
|
781
|
+
errCode, errMsg = self.checkError(errType)
|
|
782
|
+
tmpLog.error(errMsg)
|
|
783
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
784
|
+
|
|
785
|
+
# add dataset to container
|
|
786
|
+
def addDatasetsToContainer(self, containerName, datasetNames, backEnd="rucio"):
|
|
787
|
+
methodName = "addDatasetsToContainer"
|
|
788
|
+
methodName += f" pid={self.pid}"
|
|
789
|
+
methodName += f" <contName={containerName}>"
|
|
790
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
791
|
+
tmpLog.debug("start")
|
|
792
|
+
try:
|
|
793
|
+
# get Rucio API
|
|
794
|
+
client = RucioClient()
|
|
795
|
+
c_scope, c_name = self.extract_scope(containerName)
|
|
796
|
+
if c_name.endswith("/"):
|
|
797
|
+
c_name = c_name[:-1]
|
|
798
|
+
dsns = []
|
|
799
|
+
for ds in datasetNames:
|
|
800
|
+
ds_scope, ds_name = self.extract_scope(ds)
|
|
801
|
+
dsn = {"scope": ds_scope, "name": ds_name}
|
|
802
|
+
dsns.append(dsn)
|
|
803
|
+
try:
|
|
804
|
+
# add datasets
|
|
805
|
+
client.add_datasets_to_container(scope=c_scope, name=c_name, dsns=dsns)
|
|
806
|
+
except DuplicateContent:
|
|
807
|
+
# add datasets one by one
|
|
808
|
+
for ds in dsns:
|
|
809
|
+
try:
|
|
810
|
+
client.add_datasets_to_container(scope=c_scope, name=c_name, dsns=[ds])
|
|
811
|
+
except DuplicateContent:
|
|
812
|
+
pass
|
|
813
|
+
except Exception as e:
|
|
814
|
+
errType = e
|
|
815
|
+
errCode, errMsg = self.checkError(errType)
|
|
816
|
+
tmpLog.error(errMsg)
|
|
817
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
818
|
+
tmpLog.debug("done")
|
|
819
|
+
return self.SC_SUCCEEDED, True
|
|
820
|
+
|
|
821
|
+
# get latest DBRelease
|
|
822
|
+
def getLatestDBRelease(self):
|
|
823
|
+
methodName = "getLatestDBRelease"
|
|
824
|
+
methodName += f" pid={self.pid}"
|
|
825
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
826
|
+
tmpLog.debug("trying to get the latest version number of DBR")
|
|
827
|
+
# get ddo datasets
|
|
828
|
+
tmpStat, ddoDatasets = self.listDatasets("ddo.*")
|
|
829
|
+
if tmpStat != self.SC_SUCCEEDED or ddoDatasets == {}:
|
|
830
|
+
tmpLog.error("failed to get a list of DBRelease datasets from DDM")
|
|
831
|
+
return self.SC_FAILED, None
|
|
832
|
+
# reverse sort to avoid redundant lookup
|
|
833
|
+
ddoDatasets.sort()
|
|
834
|
+
ddoDatasets.reverse()
|
|
835
|
+
# extract version number
|
|
836
|
+
latestVerMajor = 0
|
|
837
|
+
latestVerMinor = 0
|
|
838
|
+
latestVerBuild = 0
|
|
839
|
+
latestVerRev = 0
|
|
840
|
+
latestDBR = ""
|
|
841
|
+
for tmpName in ddoDatasets:
|
|
842
|
+
# ignore CDRelease
|
|
843
|
+
if ".CDRelease." in tmpName:
|
|
844
|
+
continue
|
|
845
|
+
# ignore user
|
|
846
|
+
if tmpName.startswith("ddo.user"):
|
|
847
|
+
continue
|
|
848
|
+
# use Atlas.Ideal
|
|
849
|
+
if ".Atlas.Ideal." not in tmpName:
|
|
850
|
+
continue
|
|
851
|
+
match = re.search("\.v(\d+)(_*[^\.]*)$", tmpName)
|
|
852
|
+
if match is None:
|
|
853
|
+
tmpLog.warning(f"cannot extract version number from {tmpName}")
|
|
854
|
+
continue
|
|
855
|
+
# ignore special DBRs
|
|
856
|
+
if match.group(2) != "":
|
|
857
|
+
continue
|
|
858
|
+
# get major,minor,build,revision numbers
|
|
859
|
+
tmpVerStr = match.group(1)
|
|
860
|
+
tmpVerMajor = 0
|
|
861
|
+
tmpVerMinor = 0
|
|
862
|
+
tmpVerBuild = 0
|
|
863
|
+
tmpVerRev = 0
|
|
864
|
+
try:
|
|
865
|
+
tmpVerMajor = int(tmpVerStr[0:2])
|
|
866
|
+
except Exception:
|
|
867
|
+
pass
|
|
868
|
+
try:
|
|
869
|
+
tmpVerMinor = int(tmpVerStr[2:4])
|
|
870
|
+
except Exception:
|
|
871
|
+
pass
|
|
872
|
+
try:
|
|
873
|
+
tmpVerBuild = int(tmpVerStr[4:6])
|
|
874
|
+
except Exception:
|
|
875
|
+
pass
|
|
876
|
+
try:
|
|
877
|
+
tmpVerRev = int(tmpVerStr[6:])
|
|
878
|
+
# use only three digit DBR
|
|
879
|
+
continue
|
|
880
|
+
except Exception:
|
|
881
|
+
pass
|
|
882
|
+
# compare
|
|
883
|
+
if latestVerMajor > tmpVerMajor:
|
|
884
|
+
continue
|
|
885
|
+
elif latestVerMajor == tmpVerMajor:
|
|
886
|
+
if latestVerMinor > tmpVerMinor:
|
|
887
|
+
continue
|
|
888
|
+
elif latestVerMinor == tmpVerMinor:
|
|
889
|
+
if latestVerBuild > tmpVerBuild:
|
|
890
|
+
continue
|
|
891
|
+
elif latestVerBuild == tmpVerBuild:
|
|
892
|
+
if latestVerRev > tmpVerRev:
|
|
893
|
+
continue
|
|
894
|
+
# check if well replicated
|
|
895
|
+
tmpStat, ddoReplicas = self.listDatasetReplicas(tmpName)
|
|
896
|
+
if ddoReplicas == []:
|
|
897
|
+
continue
|
|
898
|
+
# higher or equal version
|
|
899
|
+
latestVerMajor = tmpVerMajor
|
|
900
|
+
latestVerMinor = tmpVerMinor
|
|
901
|
+
latestVerBuild = tmpVerBuild
|
|
902
|
+
latestVerRev = tmpVerRev
|
|
903
|
+
latestDBR = tmpName
|
|
904
|
+
# failed
|
|
905
|
+
if latestDBR == "":
|
|
906
|
+
tmpLog.error("failed to get the latest version of DBRelease dataset from DDM")
|
|
907
|
+
return self.SC_FAILED, None
|
|
908
|
+
tmpLog.debug(f"use {latestDBR}")
|
|
909
|
+
return self.SC_SUCCEEDED, latestDBR
|
|
910
|
+
|
|
911
|
+
# freeze dataset
|
|
912
|
+
def freezeDataset(self, datasetName, ignoreUnknown=False):
|
|
913
|
+
methodName = "freezeDataset"
|
|
914
|
+
methodName += f" pid={self.pid}"
|
|
915
|
+
methodName = f"{methodName} datasetName={datasetName}"
|
|
916
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
917
|
+
tmpLog.debug("start")
|
|
918
|
+
isOK = True
|
|
919
|
+
try:
|
|
920
|
+
# get rucio API
|
|
921
|
+
client = RucioClient()
|
|
922
|
+
# get scope and name
|
|
923
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
924
|
+
# check metadata to avoid a bug in rucio
|
|
925
|
+
if dsn.endswith("/"):
|
|
926
|
+
dsn = dsn[:-1]
|
|
927
|
+
tmpRet = client.get_metadata(scope, dsn)
|
|
928
|
+
# close
|
|
929
|
+
client.set_status(scope, dsn, open=False)
|
|
930
|
+
except UnsupportedOperation:
|
|
931
|
+
pass
|
|
932
|
+
except DataIdentifierNotFound as e:
|
|
933
|
+
errType = e
|
|
934
|
+
if ignoreUnknown:
|
|
935
|
+
pass
|
|
936
|
+
else:
|
|
937
|
+
isOK = False
|
|
938
|
+
except Exception as e:
|
|
939
|
+
errType = e
|
|
940
|
+
isOK = False
|
|
941
|
+
if isOK:
|
|
942
|
+
tmpLog.debug("done")
|
|
943
|
+
return self.SC_SUCCEEDED, True
|
|
944
|
+
else:
|
|
945
|
+
errCode, errMsg = self.checkError(errType)
|
|
946
|
+
tmpLog.error(errMsg)
|
|
947
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
948
|
+
|
|
949
|
+
# finger
|
|
950
|
+
def finger(self, dn):
|
|
951
|
+
methodName = "finger"
|
|
952
|
+
methodName += f" pid={self.pid}"
|
|
953
|
+
methodName = f"{methodName} userName={dn}"
|
|
954
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
955
|
+
tmpLog.debug("start")
|
|
956
|
+
status, user_info = ddm.rucioAPI.finger(dn)
|
|
957
|
+
if status:
|
|
958
|
+
tmpLog.debug(f"done with {str(user_info)}")
|
|
959
|
+
return self.SC_SUCCEEDED, user_info
|
|
960
|
+
else:
|
|
961
|
+
err_msg = f"failed with {str(user_info)}"
|
|
962
|
+
tmpLog.error(err_msg)
|
|
963
|
+
return self.SC_FAILED, err_msg
|
|
964
|
+
|
|
965
|
+
# set dataset metadata
|
|
966
|
+
def setDatasetMetadata(self, datasetName, metadataName, metadaValue):
|
|
967
|
+
methodName = "setDatasetMetadata"
|
|
968
|
+
methodName += f" pid={self.pid}"
|
|
969
|
+
methodName = f"{methodName} datasetName={datasetName} metadataName={metadataName} metadaValue={metadaValue}"
|
|
970
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
971
|
+
tmpLog.debug("start")
|
|
972
|
+
try:
|
|
973
|
+
# get rucio API
|
|
974
|
+
client = RucioClient()
|
|
975
|
+
# get scope and name
|
|
976
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
977
|
+
# set
|
|
978
|
+
client.set_metadata(scope, dsn, metadataName, metadaValue)
|
|
979
|
+
except (UnsupportedOperation, DataIdentifierNotFound):
|
|
980
|
+
pass
|
|
981
|
+
except Exception as e:
|
|
982
|
+
errType = e
|
|
983
|
+
errCode, errMsg = self.checkError(errType)
|
|
984
|
+
tmpLog.error(errMsg)
|
|
985
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
986
|
+
tmpLog.debug("done")
|
|
987
|
+
return self.SC_SUCCEEDED, True
|
|
988
|
+
|
|
989
|
+
# register location
|
|
990
|
+
def registerDatasetLocation(
|
|
991
|
+
self, datasetName, location, lifetime=None, owner=None, backEnd="rucio", activity=None, grouping=None, weight=None, copies=1, ignore_availability=True
|
|
992
|
+
):
|
|
993
|
+
methodName = "registerDatasetLocation"
|
|
994
|
+
methodName += f" pid={self.pid}"
|
|
995
|
+
methodName = f"{methodName} datasetName={datasetName} location={location}"
|
|
996
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
997
|
+
tmpLog.debug("start")
|
|
998
|
+
try:
|
|
999
|
+
# get rucio API
|
|
1000
|
+
client = RucioClient()
|
|
1001
|
+
# get scope and name
|
|
1002
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1003
|
+
# lifetime
|
|
1004
|
+
if lifetime is not None:
|
|
1005
|
+
lifetime = lifetime * 86400
|
|
1006
|
+
elif "SCRATCHDISK" in location:
|
|
1007
|
+
lifetime = 14 * 86400
|
|
1008
|
+
# get owner
|
|
1009
|
+
if owner is not None:
|
|
1010
|
+
tmpStat, userInfo = self.finger(owner)
|
|
1011
|
+
if tmpStat != self.SC_SUCCEEDED:
|
|
1012
|
+
raise RuntimeError(f"failed to get nickname for {owner}")
|
|
1013
|
+
owner = userInfo["nickname"]
|
|
1014
|
+
else:
|
|
1015
|
+
owner = client.account
|
|
1016
|
+
if grouping is None:
|
|
1017
|
+
grouping = "DATASET"
|
|
1018
|
+
# add rule
|
|
1019
|
+
dids = []
|
|
1020
|
+
did = {"scope": scope, "name": dsn}
|
|
1021
|
+
dids.append(did)
|
|
1022
|
+
locList = location.split(",")
|
|
1023
|
+
for tmpLoc in locList:
|
|
1024
|
+
client.add_replication_rule(
|
|
1025
|
+
dids=dids,
|
|
1026
|
+
copies=copies,
|
|
1027
|
+
rse_expression=tmpLoc,
|
|
1028
|
+
lifetime=lifetime,
|
|
1029
|
+
grouping=grouping,
|
|
1030
|
+
account=owner,
|
|
1031
|
+
locked=False,
|
|
1032
|
+
notify="N",
|
|
1033
|
+
ignore_availability=ignore_availability,
|
|
1034
|
+
activity=activity,
|
|
1035
|
+
weight=weight,
|
|
1036
|
+
)
|
|
1037
|
+
except DuplicateRule:
|
|
1038
|
+
pass
|
|
1039
|
+
except Exception as e:
|
|
1040
|
+
errType = e
|
|
1041
|
+
errCode, errMsg = self.checkError(errType)
|
|
1042
|
+
tmpLog.error(errMsg)
|
|
1043
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1044
|
+
tmpLog.debug(f"done for owner={owner}")
|
|
1045
|
+
return self.SC_SUCCEEDED, True
|
|
1046
|
+
|
|
1047
|
+
# delete dataset
|
|
1048
|
+
def deleteDataset(self, datasetName, emptyOnly, ignoreUnknown=False):
|
|
1049
|
+
methodName = "deleteDataset"
|
|
1050
|
+
methodName += f" pid={self.pid}"
|
|
1051
|
+
methodName = f"{methodName} datasetName={datasetName}"
|
|
1052
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1053
|
+
tmpLog.debug("start")
|
|
1054
|
+
isOK = True
|
|
1055
|
+
retStr = ""
|
|
1056
|
+
nFiles = -1
|
|
1057
|
+
try:
|
|
1058
|
+
# get rucio API
|
|
1059
|
+
client = RucioClient()
|
|
1060
|
+
# get scope and name
|
|
1061
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1062
|
+
# get the number of files
|
|
1063
|
+
if emptyOnly:
|
|
1064
|
+
nFiles = 0
|
|
1065
|
+
for x in client.list_files(scope, dsn):
|
|
1066
|
+
nFiles += 1
|
|
1067
|
+
# erase
|
|
1068
|
+
if not emptyOnly or nFiles == 0:
|
|
1069
|
+
client.set_metadata(scope=scope, name=dsn, key="lifetime", value=0.0001)
|
|
1070
|
+
retStr = f"deleted {datasetName}"
|
|
1071
|
+
else:
|
|
1072
|
+
retStr = f"keep {datasetName} where {nFiles} files are available"
|
|
1073
|
+
except DataIdentifierNotFound as e:
|
|
1074
|
+
errType = e
|
|
1075
|
+
if ignoreUnknown:
|
|
1076
|
+
pass
|
|
1077
|
+
else:
|
|
1078
|
+
isOK = False
|
|
1079
|
+
except Exception as e:
|
|
1080
|
+
isOK = False
|
|
1081
|
+
errType = e
|
|
1082
|
+
if isOK:
|
|
1083
|
+
tmpLog.debug("done")
|
|
1084
|
+
return self.SC_SUCCEEDED, retStr
|
|
1085
|
+
else:
|
|
1086
|
+
errCode, errMsg = self.checkError(errType)
|
|
1087
|
+
tmpLog.error(errMsg)
|
|
1088
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1089
|
+
|
|
1090
|
+
# register subscription
|
|
1091
|
+
def registerDatasetSubscription(self, datasetName, location, activity, lifetime=None, asynchronous=False):
|
|
1092
|
+
methodName = "registerDatasetSubscription"
|
|
1093
|
+
methodName += f" pid={self.pid}"
|
|
1094
|
+
methodName = f"{methodName} datasetName={datasetName} location={location} activity={activity} asyn={asynchronous}"
|
|
1095
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1096
|
+
tmpLog.debug("start")
|
|
1097
|
+
isOK = True
|
|
1098
|
+
try:
|
|
1099
|
+
if lifetime is not None:
|
|
1100
|
+
lifetime = lifetime * 24 * 60 * 60
|
|
1101
|
+
# get rucio API
|
|
1102
|
+
client = RucioClient()
|
|
1103
|
+
# get scope and name
|
|
1104
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1105
|
+
dids = [{"scope": scope, "name": dsn}]
|
|
1106
|
+
# check if a replication rule already exists
|
|
1107
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1108
|
+
if (rule["rse_expression"] == location) and (rule["account"] == client.account):
|
|
1109
|
+
return True
|
|
1110
|
+
client.add_replication_rule(
|
|
1111
|
+
dids=dids,
|
|
1112
|
+
copies=1,
|
|
1113
|
+
rse_expression=location,
|
|
1114
|
+
weight=None,
|
|
1115
|
+
lifetime=lifetime,
|
|
1116
|
+
grouping="DATASET",
|
|
1117
|
+
account=client.account,
|
|
1118
|
+
locked=False,
|
|
1119
|
+
notify="N",
|
|
1120
|
+
ignore_availability=True,
|
|
1121
|
+
activity=activity,
|
|
1122
|
+
asynchronous=asynchronous,
|
|
1123
|
+
)
|
|
1124
|
+
except DuplicateRule:
|
|
1125
|
+
pass
|
|
1126
|
+
except DataIdentifierNotFound:
|
|
1127
|
+
pass
|
|
1128
|
+
except Exception as e:
|
|
1129
|
+
isOK = False
|
|
1130
|
+
errType = e
|
|
1131
|
+
if not isOK:
|
|
1132
|
+
errCode, errMsg = self.checkError(errType)
|
|
1133
|
+
tmpLog.error(errMsg)
|
|
1134
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1135
|
+
tmpLog.debug("done")
|
|
1136
|
+
return self.SC_SUCCEEDED, True
|
|
1137
|
+
|
|
1138
|
+
# convert output of listDatasetReplicas
|
|
1139
|
+
def convertOutListDatasetReplicas(self, datasetName, usefileLookup=False, use_vp=False, skip_incomplete_element=False):
|
|
1140
|
+
retMap = {}
|
|
1141
|
+
# get rucio API
|
|
1142
|
+
client = RucioClient()
|
|
1143
|
+
# get scope and name
|
|
1144
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1145
|
+
# get replicas
|
|
1146
|
+
itr = client.list_dataset_replicas(scope, dsn, deep=usefileLookup)
|
|
1147
|
+
items = []
|
|
1148
|
+
for item in itr:
|
|
1149
|
+
if "vp" not in item:
|
|
1150
|
+
item["vp"] = False
|
|
1151
|
+
items.append(item)
|
|
1152
|
+
# deep lookup if shallow gave nothing
|
|
1153
|
+
if items == [] and not usefileLookup:
|
|
1154
|
+
itr = client.list_dataset_replicas(scope, dsn, deep=True)
|
|
1155
|
+
for item in itr:
|
|
1156
|
+
if "vp" not in item:
|
|
1157
|
+
item["vp"] = False
|
|
1158
|
+
items.append(item)
|
|
1159
|
+
# VP
|
|
1160
|
+
if use_vp:
|
|
1161
|
+
itr = client.list_dataset_replicas_vp(scope, dsn)
|
|
1162
|
+
for item in itr:
|
|
1163
|
+
if item["vp"]:
|
|
1164
|
+
# add dummy
|
|
1165
|
+
if "length" not in item:
|
|
1166
|
+
item["length"] = 1
|
|
1167
|
+
if "available_length" not in item:
|
|
1168
|
+
item["available_length"] = 1
|
|
1169
|
+
if "bytes" not in item:
|
|
1170
|
+
item["bytes"] = 1
|
|
1171
|
+
if "available_bytes" not in item:
|
|
1172
|
+
item["available_bytes"] = 1
|
|
1173
|
+
if "site" in item and "rse" not in item:
|
|
1174
|
+
item["rse"] = item["site"]
|
|
1175
|
+
items.append(item)
|
|
1176
|
+
# loop over all RSEs
|
|
1177
|
+
for item in items:
|
|
1178
|
+
rse = item["rse"]
|
|
1179
|
+
if skip_incomplete_element and (not item["available_length"] or item["length"] != item["available_length"]):
|
|
1180
|
+
continue
|
|
1181
|
+
retMap[rse] = [
|
|
1182
|
+
{
|
|
1183
|
+
"total": item["length"],
|
|
1184
|
+
"found": item["available_length"],
|
|
1185
|
+
"tsize": item["bytes"],
|
|
1186
|
+
"asize": item["available_bytes"],
|
|
1187
|
+
"vp": item["vp"],
|
|
1188
|
+
"immutable": 1,
|
|
1189
|
+
}
|
|
1190
|
+
]
|
|
1191
|
+
return retMap
|
|
1192
|
+
|
|
1193
|
+
# delete files from dataset
|
|
1194
|
+
def deleteFilesFromDataset(self, datasetName, filesToDelete):
|
|
1195
|
+
methodName = "deleteFilesFromDataset"
|
|
1196
|
+
methodName += f" pid={self.pid}"
|
|
1197
|
+
methodName += f" <datasetName={datasetName}>"
|
|
1198
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1199
|
+
tmpLog.debug("start")
|
|
1200
|
+
isOK = True
|
|
1201
|
+
try:
|
|
1202
|
+
# get rucio API
|
|
1203
|
+
client = RucioClient()
|
|
1204
|
+
# get scope and name
|
|
1205
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1206
|
+
# open dataset
|
|
1207
|
+
try:
|
|
1208
|
+
client.set_status(scope, dsn, open=True)
|
|
1209
|
+
except UnsupportedOperation:
|
|
1210
|
+
pass
|
|
1211
|
+
# exec
|
|
1212
|
+
client.detach_dids(scope=scope, name=dsn, dids=filesToDelete)
|
|
1213
|
+
except Exception as e:
|
|
1214
|
+
isOK = False
|
|
1215
|
+
errType = e
|
|
1216
|
+
if not isOK:
|
|
1217
|
+
errCode, errMsg = self.checkError(errType)
|
|
1218
|
+
tmpLog.error(errMsg)
|
|
1219
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1220
|
+
tmpLog.debug("done")
|
|
1221
|
+
return self.SC_SUCCEEDED, True
|
|
1222
|
+
|
|
1223
|
+
# extract scope
|
|
1224
|
+
def extract_scope(self, dsn):
|
|
1225
|
+
if dsn.endswith("/"):
|
|
1226
|
+
dsn = re.sub("/$", "", dsn)
|
|
1227
|
+
if ":" in dsn:
|
|
1228
|
+
return dsn.split(":")[:2]
|
|
1229
|
+
scope = dsn.split(".")[0]
|
|
1230
|
+
if dsn.startswith("user") or dsn.startswith("group"):
|
|
1231
|
+
scope = ".".join(dsn.split(".")[0:2])
|
|
1232
|
+
return scope, dsn
|
|
1233
|
+
|
|
1234
|
+
# get DID string as scope:name
|
|
1235
|
+
def get_did_str(self, raw_name):
|
|
1236
|
+
scope, name = self.extract_scope(raw_name)
|
|
1237
|
+
return f"{scope}:{name}"
|
|
1238
|
+
|
|
1239
|
+
# open dataset
|
|
1240
|
+
def openDataset(self, datasetName):
|
|
1241
|
+
methodName = "openDataset"
|
|
1242
|
+
methodName += f" pid={self.pid}"
|
|
1243
|
+
methodName += f" <datasetName={datasetName}>"
|
|
1244
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1245
|
+
tmpLog.debug("start")
|
|
1246
|
+
isOK = True
|
|
1247
|
+
try:
|
|
1248
|
+
# get rucio API
|
|
1249
|
+
client = RucioClient()
|
|
1250
|
+
# get scope and name
|
|
1251
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1252
|
+
# open dataset
|
|
1253
|
+
try:
|
|
1254
|
+
client.set_status(scope, dsn, open=True)
|
|
1255
|
+
except (UnsupportedOperation, DataIdentifierNotFound):
|
|
1256
|
+
pass
|
|
1257
|
+
except Exception as e:
|
|
1258
|
+
isOK = False
|
|
1259
|
+
errType = e
|
|
1260
|
+
if not isOK:
|
|
1261
|
+
errCode, errMsg = self.checkError(errType)
|
|
1262
|
+
tmpLog.error(errMsg)
|
|
1263
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1264
|
+
tmpLog.debug("done")
|
|
1265
|
+
return self.SC_SUCCEEDED, True
|
|
1266
|
+
|
|
1267
|
+
# update endpoint dict
|
|
1268
|
+
def updateEndPointDict(self):
|
|
1269
|
+
methodName = "updateEndPointDict"
|
|
1270
|
+
methodName += f" pid={self.pid}"
|
|
1271
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1272
|
+
# check freshness
|
|
1273
|
+
timeNow = naive_utcnow()
|
|
1274
|
+
if self.lastUpdateEP is not None and timeNow - self.lastUpdateEP < self.timeIntervalEP:
|
|
1275
|
+
return
|
|
1276
|
+
self.lastUpdateEP = timeNow
|
|
1277
|
+
# get json
|
|
1278
|
+
try:
|
|
1279
|
+
tmpLog.debug("start")
|
|
1280
|
+
if hasattr(jedi_config.ddm, "endpoints_json_path"):
|
|
1281
|
+
tmp_path = jedi_config.ddm.endpoints_json_path
|
|
1282
|
+
else:
|
|
1283
|
+
tmp_path = "/cvmfs/atlas.cern.ch/repo/sw/local/etc/cric_ddmendpoints.json"
|
|
1284
|
+
if tmp_path.startswith("http"):
|
|
1285
|
+
ddd = requests.get(tmp_path, verify=False).json()
|
|
1286
|
+
else:
|
|
1287
|
+
with open(tmp_path) as f:
|
|
1288
|
+
ddd = json.load(f)
|
|
1289
|
+
self.endPointDict = {k: ddd[k] for k in ddd if ddd[k]["state"] == "ACTIVE"}
|
|
1290
|
+
tmpLog.debug(f"got {len(self.endPointDict)} endpoints ")
|
|
1291
|
+
except Exception as e:
|
|
1292
|
+
errStr = f"failed to update EP with {str(e)}"
|
|
1293
|
+
tmpLog.error(errStr)
|
|
1294
|
+
return
|
|
1295
|
+
|
|
1296
|
+
# check if the dataset is distributed
|
|
1297
|
+
def isDistributedDataset(self, dataset_name: str) -> tuple[Any, bool | str]:
|
|
1298
|
+
"""
|
|
1299
|
+
Check if the dataset/container is distributed
|
|
1300
|
+
:param dataset_name: dataset name
|
|
1301
|
+
:return: status, is_distributed (True if distributed, error message if failed)
|
|
1302
|
+
"""
|
|
1303
|
+
method_name = "isDistributedDataset"
|
|
1304
|
+
method_name += f" pid={self.pid}"
|
|
1305
|
+
method_name += f" <datasetName={dataset_name}>"
|
|
1306
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
1307
|
+
tmp_log.debug("start")
|
|
1308
|
+
is_distributed = True
|
|
1309
|
+
is_ok = True
|
|
1310
|
+
try:
|
|
1311
|
+
# get rucio API
|
|
1312
|
+
client = RucioClient()
|
|
1313
|
+
# get scope and name
|
|
1314
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1315
|
+
# get metadata
|
|
1316
|
+
if dsn.endswith("/"):
|
|
1317
|
+
dsn = dsn[:-1]
|
|
1318
|
+
tmp_ret = client.get_metadata(scope, dsn)
|
|
1319
|
+
did_type = tmp_ret["did_type"]
|
|
1320
|
+
# get rules
|
|
1321
|
+
for rule in client.list_did_rules(scope, dsn):
|
|
1322
|
+
if rule["grouping"] == "NONE":
|
|
1323
|
+
continue
|
|
1324
|
+
elif rule["grouping"] == "DATASET" and did_type == "CONTAINER":
|
|
1325
|
+
continue
|
|
1326
|
+
else:
|
|
1327
|
+
is_distributed = False
|
|
1328
|
+
break
|
|
1329
|
+
except DataIdentifierNotFound:
|
|
1330
|
+
# ignore missing dataset
|
|
1331
|
+
pass
|
|
1332
|
+
except Exception as e:
|
|
1333
|
+
is_ok = False
|
|
1334
|
+
err_type = e
|
|
1335
|
+
if not is_ok:
|
|
1336
|
+
err_code, err_msg = self.checkError(err_type)
|
|
1337
|
+
tmp_log.error(err_msg)
|
|
1338
|
+
return err_code, f"{method_name} : {err_msg}"
|
|
1339
|
+
tmp_log.debug(f"done with {is_distributed}")
|
|
1340
|
+
return self.SC_SUCCEEDED, is_distributed
|
|
1341
|
+
|
|
1342
|
+
# update replication rules
|
|
1343
|
+
def updateReplicationRules(self, datasetName, dataMap):
|
|
1344
|
+
methodName = "updateReplicationRules"
|
|
1345
|
+
methodName += f" pid={self.pid}"
|
|
1346
|
+
methodName = f"{methodName} datasetName={datasetName}"
|
|
1347
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1348
|
+
tmpLog.debug("start")
|
|
1349
|
+
isOK = True
|
|
1350
|
+
try:
|
|
1351
|
+
# get rucio API
|
|
1352
|
+
client = RucioClient()
|
|
1353
|
+
# get scope and name
|
|
1354
|
+
scope, dsn = self.extract_scope(datasetName)
|
|
1355
|
+
# get rules
|
|
1356
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1357
|
+
for dataKey, data in dataMap.items():
|
|
1358
|
+
if rule["rse_expression"] == dataKey or re.search(dataKey, rule["rse_expression"]) is not None:
|
|
1359
|
+
tmpLog.debug(f"set data={str(data)} on {rule['rse_expression']}")
|
|
1360
|
+
client.update_replication_rule(rule["id"], data)
|
|
1361
|
+
break
|
|
1362
|
+
except DataIdentifierNotFound:
|
|
1363
|
+
pass
|
|
1364
|
+
except Exception as e:
|
|
1365
|
+
isOK = False
|
|
1366
|
+
errType = e
|
|
1367
|
+
if not isOK:
|
|
1368
|
+
errCode, errMsg = self.checkError(errType)
|
|
1369
|
+
tmpLog.error(errMsg)
|
|
1370
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1371
|
+
tmpLog.debug("done")
|
|
1372
|
+
return self.SC_SUCCEEDED, True
|
|
1373
|
+
|
|
1374
|
+
# get active staging rule
|
|
1375
|
+
def getActiveStagingRule(self, dataset_name):
|
|
1376
|
+
methodName = "getActiveStagingRule"
|
|
1377
|
+
methodName += f" datasetName={dataset_name}"
|
|
1378
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1379
|
+
tmpLog.debug("start")
|
|
1380
|
+
ruleID = None
|
|
1381
|
+
try:
|
|
1382
|
+
# get rucio API
|
|
1383
|
+
client = RucioClient()
|
|
1384
|
+
# get scope and name
|
|
1385
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1386
|
+
# get rules
|
|
1387
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1388
|
+
if rule["activity"] == "Staging":
|
|
1389
|
+
ruleID = rule["id"]
|
|
1390
|
+
break
|
|
1391
|
+
except Exception as e:
|
|
1392
|
+
errType = e
|
|
1393
|
+
errCode, errMsg = self.checkError(errType)
|
|
1394
|
+
tmpLog.error(errMsg)
|
|
1395
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1396
|
+
tmpLog.debug(f"got ruleID={ruleID}")
|
|
1397
|
+
return self.SC_SUCCEEDED, ruleID
|
|
1398
|
+
|
|
1399
|
+
# check global quota
|
|
1400
|
+
def check_global_quota(self, user_name: str) -> tuple[StatusCode, tuple[bool, bool | None, str | None]]:
|
|
1401
|
+
"""
|
|
1402
|
+
Check if the user has exceeded the global quota or is close to the limit.
|
|
1403
|
+
|
|
1404
|
+
:param user_name: Username to check the quota for.
|
|
1405
|
+
:return: A tuple of (StatusCode, (is_quota_ok, is_near_limit, error_message))
|
|
1406
|
+
"""
|
|
1407
|
+
method_name = "check_global_quota"
|
|
1408
|
+
method_name += f" pid={self.pid}"
|
|
1409
|
+
method_name = f"{method_name} userName={user_name}"
|
|
1410
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
1411
|
+
tmp_log.debug("start")
|
|
1412
|
+
is_quota_ok = True
|
|
1413
|
+
is_near_limit = False
|
|
1414
|
+
err_msg = None
|
|
1415
|
+
try:
|
|
1416
|
+
# get rucio API
|
|
1417
|
+
client = RucioClient()
|
|
1418
|
+
tmp_stat, user_info = self.finger(user_name)
|
|
1419
|
+
if tmp_stat != self.SC_SUCCEEDED:
|
|
1420
|
+
is_quota_ok = False
|
|
1421
|
+
err_msg = "failed to get nickname"
|
|
1422
|
+
else:
|
|
1423
|
+
owner = user_info["nickname"]
|
|
1424
|
+
quota_info = client.get_global_account_usage(owner)
|
|
1425
|
+
for info in quota_info:
|
|
1426
|
+
usage_in_tb = math.ceil(info["bytes"] / (1000**4))
|
|
1427
|
+
limit_in_tb = int(info["bytes_limit"] / (1000**4))
|
|
1428
|
+
if info["bytes"] > info["bytes_limit"] > 0:
|
|
1429
|
+
is_quota_ok = False
|
|
1430
|
+
rse_expression = info["rse_expression"]
|
|
1431
|
+
err_msg = f"exceeded global quota on {rse_expression} (Usage:{usage_in_tb} > Limit:{limit_in_tb} in TB). see output from 'rucio list-account-usage {owner}'"
|
|
1432
|
+
break
|
|
1433
|
+
limit_value = 0.9
|
|
1434
|
+
if info["bytes"] > info["bytes_limit"] * limit_value > 0:
|
|
1435
|
+
is_near_limit = True
|
|
1436
|
+
rse_expression = info["rse_expression"]
|
|
1437
|
+
err_msg = f"close to global quota limit on {rse_expression} (Usage:{usage_in_tb} / Limit:{limit_in_tb} in TB > {int(limit_value*100)}%). see output from 'rucio list-account-usage {owner}'"
|
|
1438
|
+
break
|
|
1439
|
+
except Exception as e:
|
|
1440
|
+
err_msg = f"failed to get global quota info with {str(e)}"
|
|
1441
|
+
tmp_log.error(err_msg)
|
|
1442
|
+
is_quota_ok = False
|
|
1443
|
+
tmp_log.debug(f"done: is_quota_ok={is_quota_ok} is_near_limit={is_near_limit} err_msg={err_msg}")
|
|
1444
|
+
return self.SC_SUCCEEDED, (is_quota_ok, is_near_limit, err_msg)
|
|
1445
|
+
|
|
1446
|
+
# get endpoints over local quota for a user
|
|
1447
|
+
def get_endpoints_over_local_quota(self, user_name: str) -> tuple[StatusCode, tuple[bool, set]]:
|
|
1448
|
+
"""
|
|
1449
|
+
Get a list of endpoints where the user exceeds local quota.
|
|
1450
|
+
|
|
1451
|
+
:param user_name: Username to check the quota for.
|
|
1452
|
+
:return: A tuple of (StatusCode, (is_ok, endpoints))
|
|
1453
|
+
"""
|
|
1454
|
+
method_name = "get_endpoints_over_local_quota"
|
|
1455
|
+
method_name += f" pid={self.pid}"
|
|
1456
|
+
method_name = f"{method_name} userName={user_name}"
|
|
1457
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
1458
|
+
tmp_log.debug("start")
|
|
1459
|
+
is_ok = True
|
|
1460
|
+
full_endpoints = set()
|
|
1461
|
+
try:
|
|
1462
|
+
# get rucio API
|
|
1463
|
+
client = RucioClient()
|
|
1464
|
+
tmp_stat, user_info = self.finger(user_name)
|
|
1465
|
+
if tmp_stat != self.SC_SUCCEEDED:
|
|
1466
|
+
is_ok = False
|
|
1467
|
+
err_msg = "failed to get nickname"
|
|
1468
|
+
else:
|
|
1469
|
+
owner = user_info["nickname"]
|
|
1470
|
+
quota_info = client.get_local_account_usage(owner)
|
|
1471
|
+
for info in quota_info:
|
|
1472
|
+
if info["bytes"] >= info["bytes_limit"] > 0:
|
|
1473
|
+
full_endpoints.add(info["rse"])
|
|
1474
|
+
except Exception as e:
|
|
1475
|
+
err_msg = f"failed to get local quota info with {str(e)}"
|
|
1476
|
+
tmp_log.error(err_msg)
|
|
1477
|
+
is_ok = False
|
|
1478
|
+
tmp_log.debug(f"done: is_ok={is_ok} endpoints={','.join(full_endpoints)}")
|
|
1479
|
+
return self.SC_SUCCEEDED, (is_ok, full_endpoints)
|
|
1480
|
+
|
|
1481
|
+
# make staging rule
|
|
1482
|
+
def make_staging_rule(self, dataset_name, expression, activity, lifetime=None, weight=None, notify="N", source_replica_expression=None):
|
|
1483
|
+
methodName = "make_staging_rule"
|
|
1484
|
+
methodName += f" pid={self.pid}"
|
|
1485
|
+
methodName = f"{methodName} datasetName={dataset_name} expression={expression} activity={activity} lifetime={lifetime}"
|
|
1486
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1487
|
+
tmpLog.debug("start")
|
|
1488
|
+
isOK = True
|
|
1489
|
+
ruleID = None
|
|
1490
|
+
try:
|
|
1491
|
+
if lifetime is not None:
|
|
1492
|
+
lifetime = lifetime * 24 * 60 * 60
|
|
1493
|
+
# get rucio API
|
|
1494
|
+
client = RucioClient()
|
|
1495
|
+
# get scope and name
|
|
1496
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1497
|
+
# check if a replication rule already exists
|
|
1498
|
+
if ruleID is None:
|
|
1499
|
+
dids = [{"scope": scope, "name": dsn}]
|
|
1500
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1501
|
+
if rule["rse_expression"] == expression and rule["account"] == client.account and rule["activity"] == activity:
|
|
1502
|
+
ruleID = rule["id"]
|
|
1503
|
+
tmpLog.debug(f"rule already exists: ID={ruleID}")
|
|
1504
|
+
break
|
|
1505
|
+
# make new rule
|
|
1506
|
+
if ruleID is None:
|
|
1507
|
+
rule = client.add_replication_rule(
|
|
1508
|
+
dids=dids,
|
|
1509
|
+
copies=1,
|
|
1510
|
+
rse_expression=expression,
|
|
1511
|
+
weight=weight,
|
|
1512
|
+
lifetime=lifetime,
|
|
1513
|
+
grouping="DATASET",
|
|
1514
|
+
account=client.account,
|
|
1515
|
+
locked=False,
|
|
1516
|
+
notify=notify,
|
|
1517
|
+
ignore_availability=False,
|
|
1518
|
+
activity=activity,
|
|
1519
|
+
asynchronous=False,
|
|
1520
|
+
source_replica_expression=source_replica_expression,
|
|
1521
|
+
)
|
|
1522
|
+
ruleID = rule["id"]
|
|
1523
|
+
tmpLog.debug(f"made new rule : ID={ruleID}")
|
|
1524
|
+
except Exception as e:
|
|
1525
|
+
errMsg = f"failed to make staging rule with {str(e)}"
|
|
1526
|
+
tmpLog.error(errMsg + traceback.format_exc())
|
|
1527
|
+
isOK = False
|
|
1528
|
+
errType = e
|
|
1529
|
+
if not isOK:
|
|
1530
|
+
errCode, errMsg = self.checkError(errType)
|
|
1531
|
+
tmpLog.error(errMsg)
|
|
1532
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1533
|
+
tmpLog.debug("done")
|
|
1534
|
+
return self.SC_SUCCEEDED, ruleID
|
|
1535
|
+
|
|
1536
|
+
# get state of all rules of a dataset
|
|
1537
|
+
def get_rules_state(self, dataset_name):
|
|
1538
|
+
methodName = "get_rules_state"
|
|
1539
|
+
methodName += f" datasetName={dataset_name}"
|
|
1540
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1541
|
+
tmpLog.debug("start")
|
|
1542
|
+
res_dict = {}
|
|
1543
|
+
all_ok = False
|
|
1544
|
+
try:
|
|
1545
|
+
# get rucio API
|
|
1546
|
+
client = RucioClient()
|
|
1547
|
+
# get scope and name
|
|
1548
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1549
|
+
# get rules and state
|
|
1550
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1551
|
+
rule_id = rule["id"]
|
|
1552
|
+
res_dict[rule_id] = {"state": rule["state"], "rse_expression": rule["rse_expression"]}
|
|
1553
|
+
# if all ok
|
|
1554
|
+
if all((x == "OK" for x in res_dict.values())):
|
|
1555
|
+
all_ok = True
|
|
1556
|
+
except Exception as e:
|
|
1557
|
+
errType = e
|
|
1558
|
+
errCode, errMsg = self.checkError(errType)
|
|
1559
|
+
tmpLog.error(errMsg)
|
|
1560
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1561
|
+
tmpLog.debug(f"got {all_ok}, {res_dict}")
|
|
1562
|
+
return self.SC_SUCCEEDED, (all_ok, res_dict)
|
|
1563
|
+
|
|
1564
|
+
# list DID rules
|
|
1565
|
+
def list_did_rules(self, dataset_name, all_accounts=False):
|
|
1566
|
+
methodName = "list_did_rules"
|
|
1567
|
+
methodName += f" datasetName={dataset_name} all_accounts={all_accounts}"
|
|
1568
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1569
|
+
tmpLog.debug("start")
|
|
1570
|
+
ret_list = []
|
|
1571
|
+
try:
|
|
1572
|
+
# get rucio API
|
|
1573
|
+
client = RucioClient()
|
|
1574
|
+
# get scope and name
|
|
1575
|
+
scope, dsn = self.extract_scope(dataset_name)
|
|
1576
|
+
# get rules
|
|
1577
|
+
for rule in client.list_did_rules(scope=scope, name=dsn):
|
|
1578
|
+
if rule["account"] == client.account or all_accounts:
|
|
1579
|
+
ret_list.append(rule)
|
|
1580
|
+
except Exception as e:
|
|
1581
|
+
errType = e
|
|
1582
|
+
errCode, errMsg = self.checkError(errType)
|
|
1583
|
+
tmpLog.error(errMsg)
|
|
1584
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1585
|
+
tmpLog.debug(f"got {len(ret_list)} rules")
|
|
1586
|
+
return self.SC_SUCCEEDED, ret_list
|
|
1587
|
+
|
|
1588
|
+
# update replication rule by rule ID
|
|
1589
|
+
def update_rule_by_id(self, rule_id, set_map):
|
|
1590
|
+
methodName = "update_rule_by_id"
|
|
1591
|
+
methodName += f" pid={self.pid}"
|
|
1592
|
+
methodName = f"{methodName} rule_id={rule_id}"
|
|
1593
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1594
|
+
tmpLog.debug("start")
|
|
1595
|
+
isOK = True
|
|
1596
|
+
try:
|
|
1597
|
+
# get rucio API
|
|
1598
|
+
client = RucioClient()
|
|
1599
|
+
# update rule
|
|
1600
|
+
client.update_replication_rule(rule_id, set_map)
|
|
1601
|
+
except Exception as e:
|
|
1602
|
+
isOK = False
|
|
1603
|
+
errType = e
|
|
1604
|
+
if not isOK:
|
|
1605
|
+
errCode, errMsg = self.checkError(errType)
|
|
1606
|
+
tmpLog.error(errMsg)
|
|
1607
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1608
|
+
tmpLog.debug("done")
|
|
1609
|
+
return self.SC_SUCCEEDED, True
|
|
1610
|
+
|
|
1611
|
+
# get replication rule by rule ID
|
|
1612
|
+
def get_rule_by_id(self, rule_id, allow_missing=True):
|
|
1613
|
+
methodName = "get_rule_by_id"
|
|
1614
|
+
methodName += f" pid={self.pid}"
|
|
1615
|
+
methodName = f"{methodName} rule_id={rule_id}"
|
|
1616
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1617
|
+
tmpLog.debug("start")
|
|
1618
|
+
try:
|
|
1619
|
+
# get rucio API
|
|
1620
|
+
client = RucioClient()
|
|
1621
|
+
# get rules
|
|
1622
|
+
rule = client.get_replication_rule(rule_id)
|
|
1623
|
+
except RuleNotFound as e:
|
|
1624
|
+
errType = e
|
|
1625
|
+
errCode, errMsg = self.checkError(errType)
|
|
1626
|
+
if allow_missing:
|
|
1627
|
+
tmpLog.debug(errMsg)
|
|
1628
|
+
return self.SC_SUCCEEDED, False
|
|
1629
|
+
except Exception as e:
|
|
1630
|
+
errType = e
|
|
1631
|
+
errCode, errMsg = self.checkError(errType)
|
|
1632
|
+
tmpLog.error(errMsg)
|
|
1633
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1634
|
+
tmpLog.debug(f"got rule")
|
|
1635
|
+
return self.SC_SUCCEEDED, rule
|
|
1636
|
+
|
|
1637
|
+
# list details of all replica locks for a rule by rule ID
|
|
1638
|
+
def list_replica_locks_by_id(self, rule_id):
|
|
1639
|
+
methodName = "list_replica_locks_by_id"
|
|
1640
|
+
methodName += f" pid={self.pid}"
|
|
1641
|
+
methodName = f"{methodName} rule_id={rule_id}"
|
|
1642
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1643
|
+
tmpLog.debug("start")
|
|
1644
|
+
try:
|
|
1645
|
+
# get rucio API
|
|
1646
|
+
client = RucioClient()
|
|
1647
|
+
# get generator of replica locks
|
|
1648
|
+
res = client.list_replica_locks(rule_id)
|
|
1649
|
+
# turn into list
|
|
1650
|
+
ret = list(res)
|
|
1651
|
+
except Exception as e:
|
|
1652
|
+
errType = e
|
|
1653
|
+
errCode, errMsg = self.checkError(errType)
|
|
1654
|
+
tmpLog.error(errMsg)
|
|
1655
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1656
|
+
tmpLog.debug(f"got replica locks")
|
|
1657
|
+
return self.SC_SUCCEEDED, ret
|
|
1658
|
+
|
|
1659
|
+
# delete replication rule by rule ID
|
|
1660
|
+
def delete_replication_rule(self, rule_id, allow_missing=True):
|
|
1661
|
+
methodName = "delete_replication_rule"
|
|
1662
|
+
methodName += f" pid={self.pid}"
|
|
1663
|
+
methodName = f"{methodName} rule_id={rule_id}"
|
|
1664
|
+
tmpLog = MsgWrapper(logger, methodName)
|
|
1665
|
+
tmpLog.debug("start")
|
|
1666
|
+
try:
|
|
1667
|
+
# get rucio API
|
|
1668
|
+
client = RucioClient()
|
|
1669
|
+
# get rules
|
|
1670
|
+
ret = client.delete_replication_rule(rule_id)
|
|
1671
|
+
except RuleNotFound as e:
|
|
1672
|
+
errType = e
|
|
1673
|
+
errCode, errMsg = self.checkError(errType)
|
|
1674
|
+
if allow_missing:
|
|
1675
|
+
tmpLog.debug(errMsg)
|
|
1676
|
+
return self.SC_SUCCEEDED, False
|
|
1677
|
+
except Exception as e:
|
|
1678
|
+
errType = e
|
|
1679
|
+
errCode, errMsg = self.checkError(errType)
|
|
1680
|
+
tmpLog.error(errMsg)
|
|
1681
|
+
return errCode, f"{methodName} : {errMsg}"
|
|
1682
|
+
tmpLog.debug(f"deleted, return {ret}")
|
|
1683
|
+
return self.SC_SUCCEEDED, ret
|
|
1684
|
+
|
|
1685
|
+
# check endpoint
|
|
1686
|
+
def check_endpoint(self, rse):
|
|
1687
|
+
method_name = "check_endpoint"
|
|
1688
|
+
method_name = f"{method_name} rse={rse}"
|
|
1689
|
+
tmp_log = MsgWrapper(logger, method_name)
|
|
1690
|
+
tmp_log.debug("start")
|
|
1691
|
+
try:
|
|
1692
|
+
# get rucio API
|
|
1693
|
+
client = RucioClient()
|
|
1694
|
+
# get rules
|
|
1695
|
+
ret = client.get_rse(rse)
|
|
1696
|
+
# check availability for write
|
|
1697
|
+
if ret.get("availability_write") is False:
|
|
1698
|
+
result = False, f"{rse} unavailable for write"
|
|
1699
|
+
else:
|
|
1700
|
+
result = True, None
|
|
1701
|
+
except RSENotFound:
|
|
1702
|
+
# unknown endpoint
|
|
1703
|
+
result = False, f"unknown endpoint: {rse}"
|
|
1704
|
+
except Exception as e:
|
|
1705
|
+
# unknown error
|
|
1706
|
+
result = None, f"failed with {str(e)}"
|
|
1707
|
+
tmp_log.debug(f"done with {result}")
|
|
1708
|
+
return self.SC_SUCCEEDED, result
|