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,908 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import threading
|
|
3
|
+
import traceback
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
|
|
6
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
7
|
+
|
|
8
|
+
from pandaserver.config import panda_config
|
|
9
|
+
from pandaserver.configurator import aux
|
|
10
|
+
from pandaserver.configurator.aux import *
|
|
11
|
+
|
|
12
|
+
_logger = PandaLogger().getLogger("configurator")
|
|
13
|
+
|
|
14
|
+
# Definitions of roles
|
|
15
|
+
WRITE_LAN = "write_lan"
|
|
16
|
+
READ_LAN = "read_lan"
|
|
17
|
+
DEFAULT = "default"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Configurator(threading.Thread):
|
|
21
|
+
def __init__(self, taskBuffer, log_stream=None):
|
|
22
|
+
threading.Thread.__init__(self)
|
|
23
|
+
|
|
24
|
+
self.taskBuffer = taskBuffer
|
|
25
|
+
if log_stream:
|
|
26
|
+
self.log_stream = log_stream
|
|
27
|
+
else:
|
|
28
|
+
self.log_stream = _logger
|
|
29
|
+
|
|
30
|
+
if hasattr(panda_config, "CRIC_URL_SITES"):
|
|
31
|
+
self.CRIC_URL_SITES = panda_config.CRIC_URL_SITES
|
|
32
|
+
else:
|
|
33
|
+
self.CRIC_URL_SITES = "https://atlas-cric.cern.ch/api/atlas/site/query/?json"
|
|
34
|
+
|
|
35
|
+
if hasattr(panda_config, "CRIC_URL_DDMENDPOINTS"):
|
|
36
|
+
self.CRIC_URL_DDMENDPOINTS = panda_config.CRIC_URL_DDMENDPOINTS
|
|
37
|
+
else:
|
|
38
|
+
self.CRIC_URL_DDMENDPOINTS = "https://atlas-cric.cern.ch/api/atlas/ddmendpoint/query/?json"
|
|
39
|
+
|
|
40
|
+
if hasattr(panda_config, "CRIC_URL_SCHEDCONFIG"):
|
|
41
|
+
self.CRIC_URL_SCHEDCONFIG = panda_config.CRIC_URL_SCHEDCONFIG
|
|
42
|
+
else:
|
|
43
|
+
self.CRIC_URL_SCHEDCONFIG = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json"
|
|
44
|
+
|
|
45
|
+
if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST"):
|
|
46
|
+
self.CRIC_URL_DDMBLACKLIST = panda_config.CRIC_URL_DDMBLACKLIST
|
|
47
|
+
else:
|
|
48
|
+
self.CRIC_URL_DDMBLACKLIST = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json&activity=write_wan&fstate=OFF"
|
|
49
|
+
|
|
50
|
+
if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST_READ"):
|
|
51
|
+
self.CRIC_URL_DDMBLACKLIST_READ = panda_config.CRIC_URL_DDMBLACKLIST_READ
|
|
52
|
+
else:
|
|
53
|
+
self.CRIC_URL_DDMBLACKLIST_READ = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json&activity=read_wan&fstate=OFF"
|
|
54
|
+
|
|
55
|
+
if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST_FULL"):
|
|
56
|
+
self.CRIC_URL_DDMBLACKLIST_FULL = panda_config.CRIC_URL_DDMBLACKLIST_FULL
|
|
57
|
+
else:
|
|
58
|
+
self.CRIC_URL_DDMBLACKLIST_FULL = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json"
|
|
59
|
+
|
|
60
|
+
if hasattr(panda_config, "RUCIO_RSE_USAGE"):
|
|
61
|
+
self.RUCIO_RSE_USAGE = panda_config.RUCIO_RSE_USAGE
|
|
62
|
+
else:
|
|
63
|
+
self.RUCIO_RSE_USAGE = "https://rucio-hadoop.cern.ch/dumps/rse_usage/current.json"
|
|
64
|
+
|
|
65
|
+
def retrieve_data(self):
|
|
66
|
+
self.log_stream.debug("Getting site dump...")
|
|
67
|
+
self.site_dump = aux.get_dump(self.CRIC_URL_SITES)
|
|
68
|
+
if not self.site_dump:
|
|
69
|
+
self.log_stream.error("The site dump was not retrieved correctly")
|
|
70
|
+
return False
|
|
71
|
+
self.log_stream.debug("Done")
|
|
72
|
+
|
|
73
|
+
self.log_stream.debug("Getting DDM endpoints dump...")
|
|
74
|
+
self.endpoint_dump = aux.get_dump(self.CRIC_URL_DDMENDPOINTS)
|
|
75
|
+
if not self.endpoint_dump:
|
|
76
|
+
self.log_stream.error("The endpoint dump was not retrieved correctly")
|
|
77
|
+
return False
|
|
78
|
+
self.log_stream.debug("Done")
|
|
79
|
+
|
|
80
|
+
self.log_stream.debug("Parsing endpoints...")
|
|
81
|
+
self.endpoint_token_dict = self.parse_endpoints()
|
|
82
|
+
self.log_stream.debug("Done")
|
|
83
|
+
|
|
84
|
+
self.log_stream.debug("Getting schedconfig dump...")
|
|
85
|
+
self.schedconfig_dump = aux.get_dump(self.CRIC_URL_SCHEDCONFIG)
|
|
86
|
+
if not self.schedconfig_dump:
|
|
87
|
+
self.log_stream.error("The schedconfig dump was not retrieved correctly")
|
|
88
|
+
return False
|
|
89
|
+
self.log_stream.debug("Done")
|
|
90
|
+
|
|
91
|
+
# --- WRITE ---
|
|
92
|
+
self.log_stream.debug("Getting ddmblacklist dump...")
|
|
93
|
+
try:
|
|
94
|
+
if self.CRIC_URL_DDMBLACKLIST:
|
|
95
|
+
dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST)
|
|
96
|
+
if dump is None:
|
|
97
|
+
# True failure (e.g., fetch/parse problem)
|
|
98
|
+
self.log_stream.error("The blacklisted endpoint dump could not be retrieved (None)")
|
|
99
|
+
return False
|
|
100
|
+
# Treat {} as valid/empty
|
|
101
|
+
self.blacklisted_endpoints = list(dump) if isinstance(dump, (dict, list, tuple, set)) else list(
|
|
102
|
+
dump or [])
|
|
103
|
+
self.blacklisted_endpoints_write = self.blacklisted_endpoints
|
|
104
|
+
self.log_stream.debug(f"ddmblacklist(write) retrieved {len(self.blacklisted_endpoints)} endpoints")
|
|
105
|
+
else:
|
|
106
|
+
self.blacklisted_endpoints = []
|
|
107
|
+
self.blacklisted_endpoints_write = []
|
|
108
|
+
except Exception as e:
|
|
109
|
+
self.log_stream.warning(f"Failed to retrieve ddmblacklist(write); defaulting to empty. Error: {e}")
|
|
110
|
+
self.blacklisted_endpoints = []
|
|
111
|
+
self.blacklisted_endpoints_write = []
|
|
112
|
+
|
|
113
|
+
self.log_stream.debug(f"Blacklisted endpoints {self.blacklisted_endpoints}")
|
|
114
|
+
self.log_stream.debug(f"Blacklisted endpoints write {self.blacklisted_endpoints_write}")
|
|
115
|
+
self.log_stream.debug("Done")
|
|
116
|
+
|
|
117
|
+
# --- READ ---
|
|
118
|
+
self.log_stream.debug("Getting ddmblacklist read dump...")
|
|
119
|
+
try:
|
|
120
|
+
if self.CRIC_URL_DDMBLACKLIST_READ:
|
|
121
|
+
dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST_READ)
|
|
122
|
+
# Accept {} as empty; only treat None as failure
|
|
123
|
+
if dump is None:
|
|
124
|
+
self.log_stream.warning("ddmblacklist(read) returned None; defaulting to empty")
|
|
125
|
+
self.blacklisted_endpoints_read = []
|
|
126
|
+
else:
|
|
127
|
+
self.blacklisted_endpoints_read = list(dump) if isinstance(dump,
|
|
128
|
+
(dict, list, tuple, set)) else list(
|
|
129
|
+
dump or [])
|
|
130
|
+
self.log_stream.debug(f"ddmblacklist(read) retrieved {len(self.blacklisted_endpoints_read)} endpoints")
|
|
131
|
+
else:
|
|
132
|
+
self.blacklisted_endpoints_read = []
|
|
133
|
+
except Exception as e:
|
|
134
|
+
self.log_stream.warning(f"Failed to retrieve ddmblacklist(read); defaulting to empty. Error: {e}")
|
|
135
|
+
self.blacklisted_endpoints_read = []
|
|
136
|
+
|
|
137
|
+
self.log_stream.debug(f"Blacklisted endpoints read {self.blacklisted_endpoints_read}")
|
|
138
|
+
self.log_stream.debug("Done")
|
|
139
|
+
|
|
140
|
+
# --- FULL / DETAILED ---
|
|
141
|
+
self.log_stream.debug("Getting DDM detailed status...")
|
|
142
|
+
try:
|
|
143
|
+
if self.CRIC_URL_DDMBLACKLIST_FULL:
|
|
144
|
+
dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST_FULL)
|
|
145
|
+
if dump is None:
|
|
146
|
+
self.log_stream.warning("ddmblacklist(full) returned None; defaulting to empty")
|
|
147
|
+
self.ddm_detailed_exclusions = {}
|
|
148
|
+
else:
|
|
149
|
+
self.ddm_detailed_exclusions = dump if isinstance(dump, dict) else dict(dump or {})
|
|
150
|
+
# Expecting a dict here; {} is valid/empty
|
|
151
|
+
if isinstance(dump, dict):
|
|
152
|
+
self.ddm_detailed_exclusions = dump
|
|
153
|
+
else:
|
|
154
|
+
# Be tolerant but safe: coerce to dict if possible, else empty
|
|
155
|
+
self.ddm_detailed_exclusions = dict(dump) if hasattr(dump, "items") else {}
|
|
156
|
+
self.log_stream.debug(
|
|
157
|
+
f"ddmblacklist(full) retrieved {len(self.ddm_detailed_exclusions)} entries"
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
self.ddm_detailed_exclusions = {}
|
|
161
|
+
except Exception as e:
|
|
162
|
+
self.log_stream.warning(f"Failed to retrieve ddmblacklist(full); defaulting to empty. Error: {e}")
|
|
163
|
+
self.ddm_detailed_exclusions = {}
|
|
164
|
+
|
|
165
|
+
self.log_stream.debug(f"Blacklisted endpoints full {self.ddm_detailed_exclusions}")
|
|
166
|
+
self.log_stream.debug("Done")
|
|
167
|
+
|
|
168
|
+
if self.RUCIO_RSE_USAGE:
|
|
169
|
+
self.log_stream.debug("Getting Rucio RSE usage dump...")
|
|
170
|
+
self.rse_usage = aux.get_dump(self.RUCIO_RSE_USAGE)
|
|
171
|
+
if not self.rse_usage:
|
|
172
|
+
self.log_stream.error("The RSE usage dump was not retrieved correctly")
|
|
173
|
+
return False
|
|
174
|
+
self.log_stream.debug("Done")
|
|
175
|
+
else:
|
|
176
|
+
self.rse_usage = {}
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
def get_site_info(self, site):
|
|
180
|
+
"""
|
|
181
|
+
Gets the relevant information from a site
|
|
182
|
+
"""
|
|
183
|
+
state = site["state"]
|
|
184
|
+
tier_level = site["tier_level"]
|
|
185
|
+
|
|
186
|
+
if "Nucleus" in site["datapolicies"]: # or site['tier_level'] <= 1:
|
|
187
|
+
role = "nucleus"
|
|
188
|
+
else:
|
|
189
|
+
role = "satellite"
|
|
190
|
+
|
|
191
|
+
return role, state, tier_level
|
|
192
|
+
|
|
193
|
+
def parse_endpoints(self):
|
|
194
|
+
"""
|
|
195
|
+
Puts the relevant information from endpoint_dump into a more usable format
|
|
196
|
+
"""
|
|
197
|
+
endpoint_token_dict = {}
|
|
198
|
+
for endpoint, endpoint_config in self.endpoint_dump.items():
|
|
199
|
+
# Filter out testing and inactive endpoints
|
|
200
|
+
if endpoint_config["state"] == "ACTIVE": # and endpoint['type'] != 'TEST'
|
|
201
|
+
endpoint_token_dict[endpoint] = {}
|
|
202
|
+
endpoint_token_dict[endpoint]["token"] = endpoint_config["token"]
|
|
203
|
+
endpoint_token_dict[endpoint]["site_name"] = endpoint_config["site"]
|
|
204
|
+
endpoint_token_dict[endpoint]["type"] = endpoint_config["type"]
|
|
205
|
+
if endpoint_config["is_tape"]:
|
|
206
|
+
endpoint_token_dict[endpoint]["is_tape"] = "Y"
|
|
207
|
+
else:
|
|
208
|
+
endpoint_token_dict[endpoint]["is_tape"] = "N"
|
|
209
|
+
else:
|
|
210
|
+
self.log_stream.debug(f"parse_endpoints: skipped endpoint {endpoint} (type: {endpoint_config['type']}, state: {endpoint_config['state']})")
|
|
211
|
+
|
|
212
|
+
return endpoint_token_dict
|
|
213
|
+
|
|
214
|
+
def process_site_dumps(self):
|
|
215
|
+
"""
|
|
216
|
+
Parses the CRIC site and endpoint dumps and prepares a format loadable to the DB
|
|
217
|
+
"""
|
|
218
|
+
# Variables that will contain only the relevant information
|
|
219
|
+
sites_list = []
|
|
220
|
+
included_sites = []
|
|
221
|
+
ddm_endpoints_list = []
|
|
222
|
+
panda_sites_list = []
|
|
223
|
+
|
|
224
|
+
# New relationship information based on astorages field in CRIC.
|
|
225
|
+
# Used to fill atlas_panda.panda_ddm_relation table
|
|
226
|
+
try:
|
|
227
|
+
panda_ddm_relation_list = self.get_panda_ddm_relations()
|
|
228
|
+
except Exception:
|
|
229
|
+
# Temporary protection to prevent issues
|
|
230
|
+
self.log_stream.error(f"get_panda_ddm_relations excepted with {traceback.print_exc()}")
|
|
231
|
+
panda_ddm_relation_list = []
|
|
232
|
+
|
|
233
|
+
# Iterate the site dump
|
|
234
|
+
for site_name, site_config in self.site_dump.items():
|
|
235
|
+
# Add the site info to a list
|
|
236
|
+
(site_role, site_state, tier_level) = self.get_site_info(site_config)
|
|
237
|
+
if site_state == "ACTIVE" and site_name not in included_sites:
|
|
238
|
+
sites_list.append(
|
|
239
|
+
{
|
|
240
|
+
"site_name": site_name,
|
|
241
|
+
"role": site_role,
|
|
242
|
+
"tier_level": tier_level,
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
included_sites.append(site_name)
|
|
246
|
+
else:
|
|
247
|
+
self.log_stream.debug(f"process_site_dumps: skipped site {site_name} (state: {site_state})")
|
|
248
|
+
|
|
249
|
+
# Get the DDM endpoints for the site we are inspecting
|
|
250
|
+
for ddm_endpoint_name in site_config["ddmendpoints"]:
|
|
251
|
+
try:
|
|
252
|
+
ddm_spacetoken_name = self.endpoint_token_dict[ddm_endpoint_name]["token"]
|
|
253
|
+
ddm_endpoint_type = self.endpoint_token_dict[ddm_endpoint_name]["type"]
|
|
254
|
+
ddm_endpoint_is_tape = self.endpoint_token_dict[ddm_endpoint_name]["is_tape"]
|
|
255
|
+
if ddm_endpoint_name in self.blacklisted_endpoints:
|
|
256
|
+
ddm_endpoint_blacklisted_write = "Y"
|
|
257
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is blacklisted for write")
|
|
258
|
+
else:
|
|
259
|
+
ddm_endpoint_blacklisted_write = "N"
|
|
260
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is NOT blacklisted for write")
|
|
261
|
+
|
|
262
|
+
if ddm_endpoint_name in self.blacklisted_endpoints_read:
|
|
263
|
+
ddm_endpoint_blacklisted_read = "Y"
|
|
264
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is blacklisted for read")
|
|
265
|
+
else:
|
|
266
|
+
ddm_endpoint_blacklisted_read = "N"
|
|
267
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is NOT blacklisted for read")
|
|
268
|
+
|
|
269
|
+
detailed_status = {}
|
|
270
|
+
if ddm_endpoint_name in self.ddm_detailed_exclusions:
|
|
271
|
+
for activity in self.ddm_detailed_exclusions[ddm_endpoint_name]:
|
|
272
|
+
detailed_status[activity] = self.ddm_detailed_exclusions[ddm_endpoint_name][activity]["status"]["value"]
|
|
273
|
+
|
|
274
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has detailed exclusions {detailed_status}")
|
|
275
|
+
except KeyError:
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
# Get the storage space
|
|
279
|
+
try:
|
|
280
|
+
space_used = self.rse_usage[ddm_endpoint_name]["storage"]["used"] / GB
|
|
281
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has used space {space_used}GB")
|
|
282
|
+
space_free = self.rse_usage[ddm_endpoint_name]["storage"]["free"] / GB
|
|
283
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has free space {space_free}GB")
|
|
284
|
+
space_total = space_used + space_free
|
|
285
|
+
space_timestamp = datetime.strptime(
|
|
286
|
+
self.rse_usage[ddm_endpoint_name]["storage"]["updated_at"],
|
|
287
|
+
"%Y-%m-%d %H:%M:%S",
|
|
288
|
+
)
|
|
289
|
+
self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has space timestamp {space_timestamp}")
|
|
290
|
+
|
|
291
|
+
except (KeyError, ValueError):
|
|
292
|
+
space_used, space_free, space_total, space_timestamp = (
|
|
293
|
+
None,
|
|
294
|
+
None,
|
|
295
|
+
None,
|
|
296
|
+
None,
|
|
297
|
+
)
|
|
298
|
+
self.log_stream.warning(f"process_site_dumps: no rse storage usage information for {ddm_endpoint_name}")
|
|
299
|
+
|
|
300
|
+
# Get the Expired space
|
|
301
|
+
try:
|
|
302
|
+
space_expired = self.rse_usage[ddm_endpoint_name]["expired"]["used"] / GB
|
|
303
|
+
except KeyError:
|
|
304
|
+
space_expired = 0
|
|
305
|
+
self.log_stream.warning(f"process_site_dumps: no rse EXPIRED usage information for {ddm_endpoint_name}")
|
|
306
|
+
|
|
307
|
+
ddm_spacetoken_state = site_config["ddmendpoints"][ddm_endpoint_name]["state"]
|
|
308
|
+
if ddm_spacetoken_state == "ACTIVE":
|
|
309
|
+
ddm_endpoints_list.append(
|
|
310
|
+
{
|
|
311
|
+
"ddm_endpoint_name": ddm_endpoint_name,
|
|
312
|
+
"site_name": site_name,
|
|
313
|
+
"ddm_spacetoken_name": ddm_spacetoken_name,
|
|
314
|
+
"type": ddm_endpoint_type,
|
|
315
|
+
"is_tape": ddm_endpoint_is_tape,
|
|
316
|
+
"blacklisted": ddm_endpoint_blacklisted_write,
|
|
317
|
+
"blacklisted_write": ddm_endpoint_blacklisted_write,
|
|
318
|
+
"blacklisted_read": ddm_endpoint_blacklisted_read,
|
|
319
|
+
"detailed_status": json.dumps(detailed_status),
|
|
320
|
+
"space_used": space_used,
|
|
321
|
+
"space_free": space_free,
|
|
322
|
+
"space_total": space_total,
|
|
323
|
+
"space_expired": space_expired,
|
|
324
|
+
"space_timestamp": space_timestamp,
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
self.log_stream.debug(f"process_site_dumps: added DDM endpoint {ddm_endpoint_name}")
|
|
328
|
+
else:
|
|
329
|
+
self.log_stream.debug(f"process_site_dumps: skipped DDM endpoint {ddm_endpoint_name} because of state {ddm_spacetoken_state}")
|
|
330
|
+
|
|
331
|
+
# Get the PanDA resources
|
|
332
|
+
for panda_resource in site_config["presources"]:
|
|
333
|
+
for panda_site in site_config["presources"][panda_resource]:
|
|
334
|
+
panda_site_state = site_config["presources"][panda_resource][panda_site]["state"]
|
|
335
|
+
if panda_site_state != "ACTIVE":
|
|
336
|
+
self.log_stream.debug(f"process_site_dumps: skipped PanDA site {panda_site} (state: {panda_site_state})")
|
|
337
|
+
continue
|
|
338
|
+
panda_site_name = panda_site
|
|
339
|
+
panda_sites_list.append({"panda_site_name": panda_site_name, "site_name": site_name})
|
|
340
|
+
|
|
341
|
+
return sites_list, panda_sites_list, ddm_endpoints_list, panda_ddm_relation_list
|
|
342
|
+
|
|
343
|
+
def parse_role(self, role):
|
|
344
|
+
"""
|
|
345
|
+
Traditionally roles have been "read_lan" or "write_lan". We will consider them the default roles.
|
|
346
|
+
If you want to overwrite the role for specific jobs in CRIC, you can define e.g. "read_lan_analysis". Here we
|
|
347
|
+
want to strip the role to the "analysis" tag and return role "read_lan" and scope "analysis"
|
|
348
|
+
|
|
349
|
+
Examples:
|
|
350
|
+
"read_lan_analysis" --> "read_lan" and "analysis"
|
|
351
|
+
"read_lan" --> "read_lan" and "default"
|
|
352
|
+
"""
|
|
353
|
+
# default roles: "read_lan" or "write_lan"
|
|
354
|
+
if role == READ_LAN or role == WRITE_LAN:
|
|
355
|
+
return role, DEFAULT
|
|
356
|
+
|
|
357
|
+
# special read_lan roles, e.g. "read_lan_analysis"
|
|
358
|
+
elif role.startswith(READ_LAN):
|
|
359
|
+
role_clean = READ_LAN
|
|
360
|
+
scope = role.replace(f"{READ_LAN}_", "")
|
|
361
|
+
return role_clean, scope
|
|
362
|
+
|
|
363
|
+
# special write_lan roles, e.g. "write_lan_analysis"
|
|
364
|
+
elif role.startswith(WRITE_LAN):
|
|
365
|
+
role_clean = WRITE_LAN
|
|
366
|
+
scope = role.replace(f"{WRITE_LAN}_", "")
|
|
367
|
+
return role_clean, scope
|
|
368
|
+
|
|
369
|
+
# roles we are currently not expecting
|
|
370
|
+
else:
|
|
371
|
+
return role, DEFAULT
|
|
372
|
+
|
|
373
|
+
def get_panda_ddm_relations(self):
|
|
374
|
+
"""
|
|
375
|
+
Gets the DDM endpoints assigned to a panda queue, based on the CRIC astorages field of the panda queue definition
|
|
376
|
+
"""
|
|
377
|
+
relation_list = []
|
|
378
|
+
|
|
379
|
+
# iterate on panda queues
|
|
380
|
+
for long_panda_site_name in self.schedconfig_dump:
|
|
381
|
+
panda_site_name = self.schedconfig_dump[long_panda_site_name]["panda_resource"]
|
|
382
|
+
cpu_site_name = self.schedconfig_dump[long_panda_site_name]["atlas_site"]
|
|
383
|
+
dict_ddm_endpoint = {}
|
|
384
|
+
|
|
385
|
+
# get the astorages field
|
|
386
|
+
if self.schedconfig_dump[long_panda_site_name]["astorages"]:
|
|
387
|
+
astorages = self.schedconfig_dump[long_panda_site_name]["astorages"]
|
|
388
|
+
|
|
389
|
+
# iterate the storages to establish their roles and orders
|
|
390
|
+
order_read = {DEFAULT: 1}
|
|
391
|
+
order_write = {DEFAULT: 1}
|
|
392
|
+
for role in astorages:
|
|
393
|
+
# ignore old roles (pr, pw) that we are not supposed to use
|
|
394
|
+
if not role.startswith(READ_LAN) and not role.startswith(WRITE_LAN):
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
for ddm_endpoint_name in astorages[role]:
|
|
398
|
+
# set the read/write order and increment the respective counter
|
|
399
|
+
role_clean, scope = self.parse_role(role)
|
|
400
|
+
order_read.setdefault(scope, 1)
|
|
401
|
+
order_write.setdefault(scope, 1)
|
|
402
|
+
|
|
403
|
+
# initialize DDM endpoint and scope if it does not exist
|
|
404
|
+
dict_ddm_endpoint.setdefault(ddm_endpoint_name, {})
|
|
405
|
+
dict_ddm_endpoint[ddm_endpoint_name].setdefault(scope, {"order_write": None, "order_read": None, "role": []})
|
|
406
|
+
|
|
407
|
+
if role.startswith(WRITE_LAN):
|
|
408
|
+
dict_ddm_endpoint[ddm_endpoint_name][scope]["order_write"] = order_write[scope]
|
|
409
|
+
order_write[scope] += 1
|
|
410
|
+
elif role.startswith(READ_LAN):
|
|
411
|
+
dict_ddm_endpoint[ddm_endpoint_name][scope]["order_read"] = order_read[scope]
|
|
412
|
+
order_read[scope] += 1
|
|
413
|
+
# append the roles
|
|
414
|
+
dict_ddm_endpoint[ddm_endpoint_name][scope]["role"].append(role_clean)
|
|
415
|
+
|
|
416
|
+
for ddm_endpoint_name, ddm_endpoint_values in dict_ddm_endpoint.items():
|
|
417
|
+
for scope, scope_values in ddm_endpoint_values.items():
|
|
418
|
+
# unpack the values
|
|
419
|
+
roles = scope_values["role"]
|
|
420
|
+
order_write = scope_values["order_write"]
|
|
421
|
+
order_read = scope_values["order_read"]
|
|
422
|
+
|
|
423
|
+
# figure out the ATLAS site the DDM endpoint belongs to
|
|
424
|
+
try:
|
|
425
|
+
storage_site_name = self.endpoint_token_dict[ddm_endpoint_name]["site_name"]
|
|
426
|
+
except KeyError:
|
|
427
|
+
self.log_stream.warning(
|
|
428
|
+
f"Skipped {long_panda_site_name}, because primary associated DDM endpoint {ddm_endpoint_name} not found (e.g.DISABLED)"
|
|
429
|
+
)
|
|
430
|
+
continue
|
|
431
|
+
|
|
432
|
+
# figure out if the storage is local to the cpu
|
|
433
|
+
if storage_site_name == cpu_site_name:
|
|
434
|
+
is_local = "Y"
|
|
435
|
+
else:
|
|
436
|
+
is_local = "N"
|
|
437
|
+
|
|
438
|
+
# endpoints with order 1 will be considered default
|
|
439
|
+
if order_read == 1:
|
|
440
|
+
default_read = "Y"
|
|
441
|
+
else:
|
|
442
|
+
default_read = "N"
|
|
443
|
+
if order_write == 1:
|
|
444
|
+
default_write = "Y"
|
|
445
|
+
else:
|
|
446
|
+
default_write = "N"
|
|
447
|
+
|
|
448
|
+
# add the values to the list of relations if the ddm endpoint is valid
|
|
449
|
+
if ddm_endpoint_name in self.endpoint_token_dict:
|
|
450
|
+
relation_list.append(
|
|
451
|
+
{
|
|
452
|
+
"panda_site_name": panda_site_name,
|
|
453
|
+
"ddm_endpoint_name": ddm_endpoint_name,
|
|
454
|
+
"roles": ",".join(roles),
|
|
455
|
+
"default_read": default_read,
|
|
456
|
+
"default_write": default_write,
|
|
457
|
+
"is_local": is_local,
|
|
458
|
+
"order_read": order_read,
|
|
459
|
+
"order_write": order_write,
|
|
460
|
+
"scope": scope,
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return relation_list
|
|
465
|
+
|
|
466
|
+
def consistency_check(self):
|
|
467
|
+
"""
|
|
468
|
+
Point out sites, panda sites and DDM endpoints that are missing in one of the sources
|
|
469
|
+
"""
|
|
470
|
+
# Check for site inconsistencies
|
|
471
|
+
CRIC_sites = set([site_name for site_name, site_config in self.site_dump.items() if site_config["state"] == "ACTIVE"])
|
|
472
|
+
self.log_stream.debug(f"Sites in CRIC {CRIC_sites}")
|
|
473
|
+
configurator_sites = self.taskBuffer.configurator_read_sites()
|
|
474
|
+
self.log_stream.debug(f"Sites in Configurator {configurator_sites}")
|
|
475
|
+
schedconfig_sites = self.taskBuffer.configurator_read_cric_sites()
|
|
476
|
+
self.log_stream.debug(f"Sites in Schedconfig {schedconfig_sites}")
|
|
477
|
+
|
|
478
|
+
all_sites = sorted(filter(None, CRIC_sites | configurator_sites | schedconfig_sites))
|
|
479
|
+
|
|
480
|
+
for site in all_sites:
|
|
481
|
+
missing = []
|
|
482
|
+
if site not in CRIC_sites:
|
|
483
|
+
missing.append("CRIC")
|
|
484
|
+
if site not in configurator_sites:
|
|
485
|
+
missing.append("Configurator")
|
|
486
|
+
if site not in schedconfig_sites:
|
|
487
|
+
missing.append("Schedconfig")
|
|
488
|
+
if missing:
|
|
489
|
+
self.log_stream.warning(f"SITE inconsistency: {site} was not found in {missing}")
|
|
490
|
+
|
|
491
|
+
# Check for panda-site inconsistencies
|
|
492
|
+
CRIC_panda_sites = set([self.schedconfig_dump[long_panda_site_name]["panda_resource"] for long_panda_site_name in self.schedconfig_dump])
|
|
493
|
+
self.log_stream.debug(f"PanDA sites in CRIC {CRIC_panda_sites}")
|
|
494
|
+
configurator_panda_sites = self.taskBuffer.configurator_read_panda_sites()
|
|
495
|
+
self.log_stream.debug(f"PanDA sites in Configurator {configurator_panda_sites}")
|
|
496
|
+
schedconfig_panda_sites = self.taskBuffer.configurator_read_cric_panda_sites()
|
|
497
|
+
self.log_stream.debug(f"PanDA sites in Schedconfig {schedconfig_panda_sites}")
|
|
498
|
+
|
|
499
|
+
all_panda_sites = sorted(CRIC_panda_sites | configurator_panda_sites | schedconfig_panda_sites)
|
|
500
|
+
|
|
501
|
+
for site in all_panda_sites:
|
|
502
|
+
missing = []
|
|
503
|
+
if site not in CRIC_panda_sites:
|
|
504
|
+
missing.append("CRIC")
|
|
505
|
+
if site not in configurator_panda_sites:
|
|
506
|
+
missing.append("Configurator")
|
|
507
|
+
if site not in schedconfig_panda_sites:
|
|
508
|
+
missing.append("Schedconfig")
|
|
509
|
+
if missing:
|
|
510
|
+
self.log_stream.warning(f"PanDA SITE inconsistency: {site} was not found in {missing}")
|
|
511
|
+
|
|
512
|
+
# Check for DDM endpoint inconsistencies
|
|
513
|
+
CRIC_ddm_endpoints = set([ddm_endpoint_name for ddm_endpoint_name in self.endpoint_token_dict])
|
|
514
|
+
self.log_stream.debug(f"DDM endpoints in CRIC {CRIC_ddm_endpoints}")
|
|
515
|
+
configurator_ddm_endpoints = self.taskBuffer.configurator_read_ddm_endpoints()
|
|
516
|
+
self.log_stream.debug(f"DDM endpoints in Configurator {configurator_ddm_endpoints}")
|
|
517
|
+
|
|
518
|
+
all_ddm_endpoints = sorted(CRIC_ddm_endpoints | configurator_ddm_endpoints)
|
|
519
|
+
|
|
520
|
+
for site in all_ddm_endpoints:
|
|
521
|
+
missing = []
|
|
522
|
+
if site not in CRIC_ddm_endpoints:
|
|
523
|
+
missing.append("CRIC")
|
|
524
|
+
if site not in configurator_ddm_endpoints:
|
|
525
|
+
missing.append("Configurator")
|
|
526
|
+
if missing:
|
|
527
|
+
self.log_stream.warning(f"DDM ENDPOINT inconsistency: {site} was not found in {missing}")
|
|
528
|
+
|
|
529
|
+
self.cleanup_configurator(
|
|
530
|
+
CRIC_sites,
|
|
531
|
+
CRIC_panda_sites,
|
|
532
|
+
CRIC_ddm_endpoints,
|
|
533
|
+
configurator_sites,
|
|
534
|
+
configurator_panda_sites,
|
|
535
|
+
configurator_ddm_endpoints,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
def cleanup_configurator(
|
|
539
|
+
self,
|
|
540
|
+
CRIC_sites,
|
|
541
|
+
CRIC_panda_sites,
|
|
542
|
+
CRIC_ddm_endpoints,
|
|
543
|
+
configurator_sites,
|
|
544
|
+
configurator_panda_sites,
|
|
545
|
+
configurator_ddm_endpoints,
|
|
546
|
+
):
|
|
547
|
+
"""
|
|
548
|
+
Cleans up information from configurator that is not in CRIC
|
|
549
|
+
"""
|
|
550
|
+
if not CRIC_sites or not CRIC_panda_sites or not CRIC_ddm_endpoints:
|
|
551
|
+
self.log_stream.warning("Exiting cleanup because one of CRIC sets was empty")
|
|
552
|
+
|
|
553
|
+
# Clean up sites
|
|
554
|
+
sites_to_delete = configurator_sites - CRIC_sites
|
|
555
|
+
self.taskBuffer.configurator_delete_sites(sites_to_delete)
|
|
556
|
+
|
|
557
|
+
# Clean up panda sites
|
|
558
|
+
panda_sites_to_delete = configurator_panda_sites - CRIC_panda_sites
|
|
559
|
+
self.taskBuffer.configurator_delete_panda_sites(panda_sites_to_delete)
|
|
560
|
+
|
|
561
|
+
# Clean up DDM endpoints
|
|
562
|
+
ddm_endpoints_to_delete = configurator_ddm_endpoints - CRIC_ddm_endpoints
|
|
563
|
+
self.taskBuffer.configurator_delete_ddm_endpoints(ddm_endpoints_to_delete)
|
|
564
|
+
|
|
565
|
+
def run(self):
|
|
566
|
+
"""
|
|
567
|
+
Principal function
|
|
568
|
+
"""
|
|
569
|
+
|
|
570
|
+
"""
|
|
571
|
+
site_dump
|
|
572
|
+
endpoint_dump
|
|
573
|
+
schedconfig_dump
|
|
574
|
+
blacklisted_endpoints
|
|
575
|
+
blacklisted_endpoints_read
|
|
576
|
+
rse_usage
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
if self.schedconfig_dump is None:
|
|
580
|
+
self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SCHEDCONFIG}")
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
if self.endpoint_dump is None:
|
|
584
|
+
self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_DDMENDPOINTS}")
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
if self.site_dump is None:
|
|
588
|
+
self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SITES}")
|
|
589
|
+
return False
|
|
590
|
+
|
|
591
|
+
# Get pre-processed CRIC dumps
|
|
592
|
+
(
|
|
593
|
+
sites_list,
|
|
594
|
+
panda_sites_list,
|
|
595
|
+
ddm_endpoints_list,
|
|
596
|
+
panda_ddm_relation_dict,
|
|
597
|
+
) = self.process_site_dumps()
|
|
598
|
+
|
|
599
|
+
# Persist the information to the PanDA DB
|
|
600
|
+
self.taskBuffer.configurator_write_sites(sites_list)
|
|
601
|
+
self.taskBuffer.configurator_write_panda_sites(panda_sites_list)
|
|
602
|
+
self.taskBuffer.configurator_write_ddm_endpoints(ddm_endpoints_list)
|
|
603
|
+
self.taskBuffer.configurator_write_panda_ddm_relations(panda_ddm_relation_dict)
|
|
604
|
+
|
|
605
|
+
# Do a data quality check
|
|
606
|
+
self.consistency_check()
|
|
607
|
+
|
|
608
|
+
return True
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class NetworkConfigurator(threading.Thread):
|
|
612
|
+
def __init__(self, taskBuffer, log_stream=None):
|
|
613
|
+
threading.Thread.__init__(self)
|
|
614
|
+
|
|
615
|
+
self.taskBuffer = taskBuffer
|
|
616
|
+
if log_stream:
|
|
617
|
+
self.log_stream = log_stream
|
|
618
|
+
else:
|
|
619
|
+
self.log_stream = _logger
|
|
620
|
+
|
|
621
|
+
if hasattr(panda_config, "NWS_URL"):
|
|
622
|
+
self.NWS_URL = panda_config.NWS_URL
|
|
623
|
+
else:
|
|
624
|
+
self.NWS_URL = "https://atlas-rucio-network-metrics.cern.ch/metrics.json"
|
|
625
|
+
|
|
626
|
+
if hasattr(panda_config, "CRIC_URL_CM"):
|
|
627
|
+
self.CRIC_URL_CM = panda_config.CRIC_URL_CM
|
|
628
|
+
else:
|
|
629
|
+
self.CRIC_URL_CM = "https://atlas-cric.cern.ch/api/core/sitematrix/query/?json&json_pretty=0"
|
|
630
|
+
|
|
631
|
+
def retrieve_data(self):
|
|
632
|
+
self.log_stream.debug("Getting NWS dump...")
|
|
633
|
+
self.nws_dump = aux.get_dump(self.NWS_URL)
|
|
634
|
+
if not self.nws_dump:
|
|
635
|
+
self.log_stream.error("Could not retrieve the NWS data")
|
|
636
|
+
return False
|
|
637
|
+
self.log_stream.debug("Done")
|
|
638
|
+
|
|
639
|
+
self.log_stream.debug("Getting CRIC cost matrix dump...")
|
|
640
|
+
self.CRIC_cm_dump = aux.get_dump(self.CRIC_URL_CM)
|
|
641
|
+
if not self.CRIC_cm_dump:
|
|
642
|
+
self.log_stream.error("Could not retrieve the cost matrix data")
|
|
643
|
+
return False
|
|
644
|
+
self.log_stream.debug("Done")
|
|
645
|
+
|
|
646
|
+
return True
|
|
647
|
+
|
|
648
|
+
def process_nws_dump(self):
|
|
649
|
+
"""
|
|
650
|
+
Gets the second generation NWS information dump, filters out irrelevant information
|
|
651
|
+
and prepares it for insertion into the PanDA DB
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
data = []
|
|
655
|
+
sites_list = self.taskBuffer.configurator_read_sites()
|
|
656
|
+
# source or destination shown as UNKNOWN before the rule is processed
|
|
657
|
+
sites_list.add("UNKNOWN")
|
|
658
|
+
|
|
659
|
+
# Ignore outdated values
|
|
660
|
+
latest_validity = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(minutes=30)
|
|
661
|
+
|
|
662
|
+
for src_dst in self.nws_dump:
|
|
663
|
+
try:
|
|
664
|
+
source, destination = src_dst.split(":")
|
|
665
|
+
skip_sites = []
|
|
666
|
+
|
|
667
|
+
# Skip entries with sites not recognized by configurator
|
|
668
|
+
if source not in sites_list:
|
|
669
|
+
skip_sites.append(source)
|
|
670
|
+
if destination not in sites_list:
|
|
671
|
+
skip_sites.append(destination)
|
|
672
|
+
if skip_sites:
|
|
673
|
+
self.log_stream.warning(f"Could not find site(s) {skip_sites} in configurator sites")
|
|
674
|
+
continue
|
|
675
|
+
|
|
676
|
+
except ValueError:
|
|
677
|
+
self.log_stream.error(f"Json wrongly formatted. Expected key with format src:dst, but found key {src_dst}")
|
|
678
|
+
continue
|
|
679
|
+
|
|
680
|
+
# Transferred files
|
|
681
|
+
try:
|
|
682
|
+
done = self.nws_dump[src_dst][FILES][DONE]
|
|
683
|
+
for activity in [PROD_INPUT, PROD_OUTPUT, EXPRESS]:
|
|
684
|
+
if activity not in done:
|
|
685
|
+
continue
|
|
686
|
+
try:
|
|
687
|
+
updated_at = datetime.strptime(done[activity][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
|
|
688
|
+
if updated_at > latest_validity:
|
|
689
|
+
done_1h = done[activity][H1]
|
|
690
|
+
done_6h = done[activity][H6]
|
|
691
|
+
data.append(
|
|
692
|
+
(
|
|
693
|
+
source,
|
|
694
|
+
destination,
|
|
695
|
+
activity + "_done_1h",
|
|
696
|
+
done_1h,
|
|
697
|
+
updated_at,
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
data.append(
|
|
701
|
+
(
|
|
702
|
+
source,
|
|
703
|
+
destination,
|
|
704
|
+
activity + "_done_6h",
|
|
705
|
+
done_6h,
|
|
706
|
+
updated_at,
|
|
707
|
+
)
|
|
708
|
+
)
|
|
709
|
+
except (KeyError, ValueError):
|
|
710
|
+
self.log_stream.debug(f"Entry {done} ({source}->{destination}) key {activity} does not follow standards")
|
|
711
|
+
continue
|
|
712
|
+
except KeyError:
|
|
713
|
+
pass
|
|
714
|
+
|
|
715
|
+
# Queued files
|
|
716
|
+
try:
|
|
717
|
+
queued = self.nws_dump[src_dst][FILES][QUEUED]
|
|
718
|
+
for activity in [PROD_INPUT, PROD_OUTPUT, EXPRESS]:
|
|
719
|
+
if activity not in queued:
|
|
720
|
+
continue
|
|
721
|
+
try:
|
|
722
|
+
updated_at = datetime.strptime(queued[activity][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
|
|
723
|
+
if updated_at > latest_validity:
|
|
724
|
+
nqueued = queued[activity][LATEST]
|
|
725
|
+
data.append(
|
|
726
|
+
(
|
|
727
|
+
source,
|
|
728
|
+
destination,
|
|
729
|
+
activity + "_queued",
|
|
730
|
+
nqueued,
|
|
731
|
+
updated_at,
|
|
732
|
+
)
|
|
733
|
+
)
|
|
734
|
+
except (KeyError, ValueError):
|
|
735
|
+
self.log_stream.error(f"Entry {queued} ({source}->{destination}) key {activity} does not follow standards")
|
|
736
|
+
continue
|
|
737
|
+
except KeyError:
|
|
738
|
+
pass
|
|
739
|
+
|
|
740
|
+
# MBps for Rucio, FAX, PerfSonar
|
|
741
|
+
try:
|
|
742
|
+
mbps = self.nws_dump[src_dst][MBPS]
|
|
743
|
+
for system in mbps:
|
|
744
|
+
try:
|
|
745
|
+
updated_at = datetime.strptime(mbps[system][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
|
|
746
|
+
except ValueError:
|
|
747
|
+
self.log_stream.debug(f"Entry {mbps} has wrong timestamp for system {system}")
|
|
748
|
+
if updated_at > latest_validity:
|
|
749
|
+
for duration in [H1, D1, W1]:
|
|
750
|
+
try:
|
|
751
|
+
mbps_entry = mbps[system][duration]
|
|
752
|
+
data.append(
|
|
753
|
+
(
|
|
754
|
+
source,
|
|
755
|
+
destination,
|
|
756
|
+
f"{system}_mbps_{duration}",
|
|
757
|
+
mbps_entry,
|
|
758
|
+
updated_at,
|
|
759
|
+
)
|
|
760
|
+
)
|
|
761
|
+
except KeyError:
|
|
762
|
+
self.log_stream.debug(
|
|
763
|
+
f"Entry {mbps} ({source}->{destination}) system {system} duration {duration} not available or wrongly formatted"
|
|
764
|
+
)
|
|
765
|
+
self.log_stream.debug(sys.exc_info())
|
|
766
|
+
except KeyError:
|
|
767
|
+
pass
|
|
768
|
+
|
|
769
|
+
return data
|
|
770
|
+
|
|
771
|
+
def process_CRIC_cm_dump(self):
|
|
772
|
+
"""
|
|
773
|
+
Gets the CRIC CM information dump, filters out irrelevant information
|
|
774
|
+
and prepares it for insertion into the PanDA DB
|
|
775
|
+
"""
|
|
776
|
+
data = []
|
|
777
|
+
sites_list = self.taskBuffer.configurator_read_sites()
|
|
778
|
+
|
|
779
|
+
for entry in self.CRIC_cm_dump:
|
|
780
|
+
self.log_stream.debug(f"Processing CRIC CM entry {entry}")
|
|
781
|
+
|
|
782
|
+
try:
|
|
783
|
+
src = entry["src"]
|
|
784
|
+
dst = entry["dst"]
|
|
785
|
+
closeness = entry["closeness"]
|
|
786
|
+
ts = datetime.now()
|
|
787
|
+
|
|
788
|
+
# filter out sites that are not in CRIC
|
|
789
|
+
skip_sites = []
|
|
790
|
+
if src not in sites_list:
|
|
791
|
+
skip_sites.append(src)
|
|
792
|
+
if dst not in sites_list:
|
|
793
|
+
skip_sites.append(dst)
|
|
794
|
+
if skip_sites:
|
|
795
|
+
self.log_stream.warning(f"Could not find site(s) {skip_sites} in configurator sites")
|
|
796
|
+
continue
|
|
797
|
+
|
|
798
|
+
# Skip broken entries (protection against errors in CRIC)
|
|
799
|
+
if not src or not dst:
|
|
800
|
+
continue
|
|
801
|
+
|
|
802
|
+
# Prepare data for bulk upserts
|
|
803
|
+
data.append((src, dst, "AGIS_closeness", closeness, ts))
|
|
804
|
+
|
|
805
|
+
except KeyError:
|
|
806
|
+
self.log_stream.warning(f"CRIC CM entry {entry} does not contain one or more of the keys src/dst/closeness")
|
|
807
|
+
continue
|
|
808
|
+
|
|
809
|
+
return data
|
|
810
|
+
|
|
811
|
+
def run(self):
|
|
812
|
+
"""
|
|
813
|
+
Principal function
|
|
814
|
+
"""
|
|
815
|
+
|
|
816
|
+
# Process and store the NWS information (NWS=Network Weather Service)
|
|
817
|
+
data_nws = self.process_nws_dump()
|
|
818
|
+
if not data_nws:
|
|
819
|
+
self.log_stream.error("Could not retrieve any data from the NWS!")
|
|
820
|
+
|
|
821
|
+
# Process and store the CRIC connectivity information
|
|
822
|
+
data_CRIC_cm = self.process_CRIC_cm_dump()
|
|
823
|
+
if not data_CRIC_cm:
|
|
824
|
+
self.log_stream.error("Could not retrieve any data from the CRIC Cost Matrix!")
|
|
825
|
+
|
|
826
|
+
data_combined = data_nws + data_CRIC_cm
|
|
827
|
+
if data_combined:
|
|
828
|
+
# Insert the new data
|
|
829
|
+
self.taskBuffer.insertNetworkMatrixData(data_combined)
|
|
830
|
+
# Do some cleanup of old data
|
|
831
|
+
self.taskBuffer.deleteOldNetworkData()
|
|
832
|
+
return True
|
|
833
|
+
else:
|
|
834
|
+
return False
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
class SchedconfigJsonDumper(threading.Thread):
|
|
838
|
+
"""
|
|
839
|
+
Downloads the CRIC schedconfig dump and stores it in the DB, one row per queue
|
|
840
|
+
"""
|
|
841
|
+
|
|
842
|
+
def __init__(self, taskBuffer, log_stream=None):
|
|
843
|
+
"""
|
|
844
|
+
Initialization and configuration
|
|
845
|
+
"""
|
|
846
|
+
threading.Thread.__init__(self)
|
|
847
|
+
|
|
848
|
+
self.taskBuffer = taskBuffer
|
|
849
|
+
if log_stream:
|
|
850
|
+
self.log_stream = log_stream
|
|
851
|
+
else:
|
|
852
|
+
self.log_stream = _logger
|
|
853
|
+
|
|
854
|
+
if hasattr(panda_config, "CRIC_URL_SCHEDCONFIG"):
|
|
855
|
+
self.CRIC_URL_SCHEDCONFIG = panda_config.CRIC_URL_SCHEDCONFIG
|
|
856
|
+
else:
|
|
857
|
+
self.CRIC_URL_SCHEDCONFIG = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json"
|
|
858
|
+
|
|
859
|
+
self.log_stream.debug("Getting schedconfig dump...")
|
|
860
|
+
self.schedconfig_dump = aux.get_dump(self.CRIC_URL_SCHEDCONFIG)
|
|
861
|
+
self.log_stream.debug("Done")
|
|
862
|
+
|
|
863
|
+
def run(self):
|
|
864
|
+
"""
|
|
865
|
+
Principal function
|
|
866
|
+
"""
|
|
867
|
+
if self.schedconfig_dump is None:
|
|
868
|
+
self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SCHEDCONFIG}")
|
|
869
|
+
return False
|
|
870
|
+
|
|
871
|
+
return self.taskBuffer.upsertQueuesInJSONSchedconfig(self.schedconfig_dump)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
class SWTagsDumper(threading.Thread):
|
|
875
|
+
"""
|
|
876
|
+
Downloads the CRIC tags dump, flattens it out and stores it in the DB, one row per queue
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
def __init__(self, taskBuffer, log_stream=None):
|
|
880
|
+
"""
|
|
881
|
+
Initialization and configuration
|
|
882
|
+
"""
|
|
883
|
+
threading.Thread.__init__(self)
|
|
884
|
+
|
|
885
|
+
self.taskBuffer = taskBuffer
|
|
886
|
+
if log_stream:
|
|
887
|
+
self.log_stream = log_stream
|
|
888
|
+
else:
|
|
889
|
+
self.log_stream = _logger
|
|
890
|
+
|
|
891
|
+
if hasattr(panda_config, "CRIC_URL_TAGS"):
|
|
892
|
+
self.CRIC_URL_TAGS = panda_config.CRIC_URL_TAGS
|
|
893
|
+
else:
|
|
894
|
+
self.CRIC_URL_TAGS = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json&preset=tags"
|
|
895
|
+
|
|
896
|
+
self.log_stream.debug("Getting tags dump...")
|
|
897
|
+
self.tags_dump = aux.get_dump(self.CRIC_URL_TAGS)
|
|
898
|
+
self.log_stream.debug("Done")
|
|
899
|
+
|
|
900
|
+
def run(self):
|
|
901
|
+
"""
|
|
902
|
+
Principal function
|
|
903
|
+
"""
|
|
904
|
+
if self.tags_dump is None:
|
|
905
|
+
self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_TAGS}")
|
|
906
|
+
return False
|
|
907
|
+
|
|
908
|
+
return self.taskBuffer.loadSWTags(self.tags_dump)
|