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,1292 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import socket
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from packaging import version
|
|
12
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
13
|
+
|
|
14
|
+
from pandajedi.jedicore import Interaction
|
|
15
|
+
from pandajedi.jediddm.DDMInterface import DDMInterface
|
|
16
|
+
from pandaserver.brokerage.SiteMapper import SiteMapper
|
|
17
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
18
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
19
|
+
from pandaserver.taskbuffer import JobUtils, ProcessGroups, SiteSpec
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# get nuclei where data is available
|
|
23
|
+
def getNucleiWithData(siteMapper, ddmIF, datasetName, candidateNuclei, deepScan=False):
|
|
24
|
+
# get replicas
|
|
25
|
+
try:
|
|
26
|
+
replica_map = ddmIF.listReplicasPerDataset(datasetName, deepScan)
|
|
27
|
+
except Exception:
|
|
28
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
29
|
+
return errtype, f"ddmIF.listReplicasPerDataset failed with {errvalue}", None
|
|
30
|
+
# check if remote source is available at any sites (not only nuclei)
|
|
31
|
+
remote_source_available = False
|
|
32
|
+
for tmp_replica_data in replica_map.values():
|
|
33
|
+
for tmp_location, tmp_stat_data in tmp_replica_data.items():
|
|
34
|
+
if tmp_stat_data[0]["total"] in [None, 0]:
|
|
35
|
+
continue
|
|
36
|
+
if tmp_stat_data[0]["total"] != tmp_stat_data[0]["found"]:
|
|
37
|
+
continue
|
|
38
|
+
if siteMapper.is_readable_remotely(tmp_location):
|
|
39
|
+
remote_source_available = True
|
|
40
|
+
break
|
|
41
|
+
# loop over all clouds
|
|
42
|
+
return_map = {}
|
|
43
|
+
for tmpNucleus in candidateNuclei:
|
|
44
|
+
tmpNucleusSpec = siteMapper.getNucleus(tmpNucleus)
|
|
45
|
+
# loop over all datasets
|
|
46
|
+
totalNum = 0
|
|
47
|
+
totalSize = 0
|
|
48
|
+
avaNumDisk = 0
|
|
49
|
+
avaNumAny = 0
|
|
50
|
+
avaSizeDisk = 0
|
|
51
|
+
avaSizeAny = 0
|
|
52
|
+
can_be_remote_source = False
|
|
53
|
+
for tmpDataset, tmpRepMap in replica_map.items():
|
|
54
|
+
tmpTotalNum = 0
|
|
55
|
+
tmpTotalSize = 0
|
|
56
|
+
tmpAvaNumDisk = 0
|
|
57
|
+
tmpAvaNumAny = 0
|
|
58
|
+
tmpAvaSizeDisk = 0
|
|
59
|
+
tmpAvaSizeAny = 0
|
|
60
|
+
# loop over all endpoints
|
|
61
|
+
for tmpLoc, locData in tmpRepMap.items():
|
|
62
|
+
# get total
|
|
63
|
+
if tmpTotalNum == 0:
|
|
64
|
+
tmpTotalNum = locData[0]["total"]
|
|
65
|
+
tmpTotalSize = locData[0]["tsize"]
|
|
66
|
+
# check if the endpoint is associated
|
|
67
|
+
if tmpNucleusSpec.is_associated_for_input(tmpLoc):
|
|
68
|
+
# check blacklist
|
|
69
|
+
if siteMapper.is_readable_remotely(tmpLoc):
|
|
70
|
+
can_be_remote_source = True
|
|
71
|
+
# sum
|
|
72
|
+
tmpEndpoint = tmpNucleusSpec.getEndpoint(tmpLoc)
|
|
73
|
+
tmpAvaNum = locData[0]["found"]
|
|
74
|
+
tmpAvaSize = locData[0]["asize"]
|
|
75
|
+
# disk
|
|
76
|
+
if tmpEndpoint["is_tape"] != "Y":
|
|
77
|
+
# complete replica is available at DISK
|
|
78
|
+
if tmpTotalNum == tmpAvaNum and tmpTotalNum > 0:
|
|
79
|
+
tmpAvaNumDisk = tmpAvaNum
|
|
80
|
+
tmpAvaNumAny = tmpAvaNum
|
|
81
|
+
tmpAvaSizeDisk = tmpAvaSize
|
|
82
|
+
tmpAvaSizeAny = tmpAvaSize
|
|
83
|
+
break
|
|
84
|
+
if tmpAvaNum > tmpAvaNumDisk:
|
|
85
|
+
tmpAvaNumDisk = tmpAvaNum
|
|
86
|
+
tmpAvaSizeDisk = tmpAvaSize
|
|
87
|
+
# tape
|
|
88
|
+
if tmpAvaNumAny < tmpAvaNum:
|
|
89
|
+
tmpAvaNumAny = tmpAvaNum
|
|
90
|
+
tmpAvaSizeAny = tmpAvaSize
|
|
91
|
+
# total
|
|
92
|
+
totalNum = tmpTotalNum
|
|
93
|
+
totalSize = tmpTotalSize
|
|
94
|
+
avaNumDisk += tmpAvaNumDisk
|
|
95
|
+
avaNumAny += tmpAvaNumAny
|
|
96
|
+
avaSizeDisk += tmpAvaSizeDisk
|
|
97
|
+
avaSizeAny += tmpAvaSizeAny
|
|
98
|
+
# append
|
|
99
|
+
if tmpNucleus in candidateNuclei or avaNumAny > 0:
|
|
100
|
+
return_map[tmpNucleus] = {
|
|
101
|
+
"tot_num": totalNum,
|
|
102
|
+
"tot_size": totalSize,
|
|
103
|
+
"ava_num_disk": avaNumDisk,
|
|
104
|
+
"ava_num_any": avaNumAny,
|
|
105
|
+
"ava_size_disk": avaSizeDisk,
|
|
106
|
+
"ava_size_any": avaSizeAny,
|
|
107
|
+
"can_be_remote_source": can_be_remote_source,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return Interaction.SC_SUCCEEDED, return_map, remote_source_available
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# get sites where data is available and check if complete replica is available at online RSE
|
|
114
|
+
def get_sites_with_data(
|
|
115
|
+
site_list: list,
|
|
116
|
+
site_mapper: SiteMapper,
|
|
117
|
+
ddm_if: DDMInterface,
|
|
118
|
+
dataset_name: str,
|
|
119
|
+
element_list: list,
|
|
120
|
+
max_missing_input_files: int,
|
|
121
|
+
min_input_completeness: int,
|
|
122
|
+
) -> tuple[Any, dict | str, bool | None, bool | None, bool | None, bool | None, bool | None, list]:
|
|
123
|
+
"""
|
|
124
|
+
Get sites where data is available and check if complete replica is available at online RSE
|
|
125
|
+
1) regarded_as_complete_disk: True if a replica is regarded as complete at disk (missing files within threshold)
|
|
126
|
+
2) complete_tape: True if a complete replica is available at tape
|
|
127
|
+
3) truly_complete_disk: True if a truly complete replica is available at disk (no missing files)
|
|
128
|
+
4) can_be_local_source: True if the site can read the replica locally over LAN
|
|
129
|
+
5) can_be_remote_source: True if the site can read the replica remotely over WAN
|
|
130
|
+
6) list_of_complete_replica_locations : list of RSEs where truly complete replica is available at disk
|
|
131
|
+
Note that VP replicas are not taken into account for the above flags
|
|
132
|
+
|
|
133
|
+
:param site_list: list of site names to be checked
|
|
134
|
+
:param site_mapper: SiteMapper object
|
|
135
|
+
:param ddm_if: DDMInterface object
|
|
136
|
+
:param dataset_name: dataset name
|
|
137
|
+
:param element_list: list of constituent datasets
|
|
138
|
+
:param max_missing_input_files: maximum number of missing files to be regarded as complete
|
|
139
|
+
:param min_input_completeness: minimum completeness (%) to be regarded as complete
|
|
140
|
+
|
|
141
|
+
:return: tuple of (status code or exception type, dict of sites with data availability info, regarded_as_complete_disk, complete_tape, truly_complete_disk, can_be_local_source, can_be_remote_source, list_of_complete_replica_locations)
|
|
142
|
+
"""
|
|
143
|
+
# get replicas
|
|
144
|
+
try:
|
|
145
|
+
replica_map = {
|
|
146
|
+
dataset_name: ddm_if.listDatasetReplicas(dataset_name, use_vp=True, skip_incomplete_element=True, element_list=element_list, use_deep=True)
|
|
147
|
+
}
|
|
148
|
+
except Exception:
|
|
149
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
150
|
+
return errtype, f"ddmIF.listDatasetReplicas failed with {errvalue}", None, None, None, None, None, []
|
|
151
|
+
|
|
152
|
+
# check completeness and storage availability
|
|
153
|
+
is_tape = {}
|
|
154
|
+
replica_availability_info = {}
|
|
155
|
+
list_of_complete_replica_locations = []
|
|
156
|
+
for tmp_rse, tmp_data_list in replica_map[dataset_name].items():
|
|
157
|
+
# check tape attribute
|
|
158
|
+
try:
|
|
159
|
+
is_tape[tmp_rse] = ddm_if.getSiteProperty(tmp_rse, "is_tape")
|
|
160
|
+
except Exception:
|
|
161
|
+
is_tape[tmp_rse] = None
|
|
162
|
+
# look for complete replicas
|
|
163
|
+
tmp_regarded_as_complete_disk = False
|
|
164
|
+
tmp_truly_complete_disk = False
|
|
165
|
+
tmp_complete_tape = False
|
|
166
|
+
tmp_can_be_local_source = False
|
|
167
|
+
tmp_can_be_remote_source = False
|
|
168
|
+
for tmp_data in tmp_data_list:
|
|
169
|
+
if not tmp_data.get("vp"):
|
|
170
|
+
if tmp_data["found"] == tmp_data["total"]:
|
|
171
|
+
# truly complete
|
|
172
|
+
truly_complete = True
|
|
173
|
+
elif (
|
|
174
|
+
tmp_data["total"]
|
|
175
|
+
and tmp_data["found"]
|
|
176
|
+
and (
|
|
177
|
+
tmp_data["total"] - tmp_data["found"] <= max_missing_input_files
|
|
178
|
+
or tmp_data["found"] / tmp_data["total"] * 100 >= min_input_completeness
|
|
179
|
+
)
|
|
180
|
+
):
|
|
181
|
+
# regarded as complete
|
|
182
|
+
truly_complete = False
|
|
183
|
+
else:
|
|
184
|
+
continue
|
|
185
|
+
list_of_complete_replica_locations.append(tmp_rse)
|
|
186
|
+
if is_tape[tmp_rse]:
|
|
187
|
+
tmp_complete_tape = True
|
|
188
|
+
else:
|
|
189
|
+
tmp_regarded_as_complete_disk = True
|
|
190
|
+
if truly_complete:
|
|
191
|
+
tmp_truly_complete_disk = True
|
|
192
|
+
# check if it is locally accessible over LAN
|
|
193
|
+
if site_mapper.is_readable_locally(tmp_rse):
|
|
194
|
+
tmp_can_be_local_source = True
|
|
195
|
+
# check if it is remotely accessible over WAN
|
|
196
|
+
if site_mapper.is_readable_remotely(tmp_rse):
|
|
197
|
+
tmp_can_be_remote_source = True
|
|
198
|
+
replica_availability_info[tmp_rse] = {
|
|
199
|
+
"regarded_as_complete_disk": tmp_regarded_as_complete_disk,
|
|
200
|
+
"truly_complete_disk": tmp_truly_complete_disk,
|
|
201
|
+
"complete_tape": tmp_complete_tape,
|
|
202
|
+
"can_be_local_source": tmp_can_be_local_source,
|
|
203
|
+
"can_be_remote_source": tmp_can_be_remote_source,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# loop over all candidate sites
|
|
207
|
+
regarded_as_complete_disk = False
|
|
208
|
+
truly_complete_disk = False
|
|
209
|
+
complete_tape = False
|
|
210
|
+
can_be_local_source = False
|
|
211
|
+
can_be_remote_source = False
|
|
212
|
+
return_map = {}
|
|
213
|
+
if not site_list:
|
|
214
|
+
# make sure at least one loop to set the flags
|
|
215
|
+
site_list = [None]
|
|
216
|
+
for tmp_site_name in site_list:
|
|
217
|
+
if not site_mapper.checkSite(tmp_site_name):
|
|
218
|
+
continue
|
|
219
|
+
# get associated DDM endpoints
|
|
220
|
+
tmp_site_spec = site_mapper.getSite(tmp_site_name)
|
|
221
|
+
scope_input, scope_output = select_scope(tmp_site_spec, JobUtils.ANALY_PS, JobUtils.ANALY_PS)
|
|
222
|
+
try:
|
|
223
|
+
input_endpoints = tmp_site_spec.ddm_endpoints_input[scope_input].all.keys()
|
|
224
|
+
except Exception:
|
|
225
|
+
input_endpoints = {}
|
|
226
|
+
# loop over all associated endpoints
|
|
227
|
+
for tmp_rse in input_endpoints:
|
|
228
|
+
if tmp_rse in replica_map[dataset_name]:
|
|
229
|
+
# check completeness
|
|
230
|
+
tmp_statistics = replica_map[dataset_name][tmp_rse][-1]
|
|
231
|
+
if tmp_statistics["found"] is None:
|
|
232
|
+
tmp_dataset_completeness = "unknown"
|
|
233
|
+
# refresh request
|
|
234
|
+
pass
|
|
235
|
+
elif tmp_statistics["total"] == tmp_statistics["found"]:
|
|
236
|
+
tmp_dataset_completeness = "complete"
|
|
237
|
+
else:
|
|
238
|
+
tmp_dataset_completeness = "incomplete"
|
|
239
|
+
# append
|
|
240
|
+
if tmp_site_name not in return_map:
|
|
241
|
+
return_map[tmp_site_name] = {}
|
|
242
|
+
return_map[tmp_site_name][tmp_rse] = {"tape": is_tape[tmp_rse], "state": tmp_dataset_completeness}
|
|
243
|
+
if "vp" in tmp_statistics:
|
|
244
|
+
return_map[tmp_site_name][tmp_rse]["vp"] = tmp_statistics["vp"]
|
|
245
|
+
# set flags
|
|
246
|
+
for tmp_rse in replica_availability_info:
|
|
247
|
+
if replica_availability_info[tmp_rse]["can_be_local_source"] or replica_availability_info[tmp_rse]["can_be_remote_source"]:
|
|
248
|
+
if replica_availability_info[tmp_rse]["regarded_as_complete_disk"]:
|
|
249
|
+
regarded_as_complete_disk = True
|
|
250
|
+
if replica_availability_info[tmp_rse]["truly_complete_disk"]:
|
|
251
|
+
truly_complete_disk = True
|
|
252
|
+
if replica_availability_info[tmp_rse]["complete_tape"]:
|
|
253
|
+
complete_tape = True
|
|
254
|
+
if replica_availability_info[tmp_rse]["can_be_local_source"]:
|
|
255
|
+
can_be_local_source = True
|
|
256
|
+
if replica_availability_info[tmp_rse]["can_be_remote_source"]:
|
|
257
|
+
can_be_remote_source = True
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
Interaction.SC_SUCCEEDED,
|
|
261
|
+
return_map,
|
|
262
|
+
regarded_as_complete_disk,
|
|
263
|
+
complete_tape,
|
|
264
|
+
truly_complete_disk,
|
|
265
|
+
can_be_local_source,
|
|
266
|
+
can_be_remote_source,
|
|
267
|
+
list_of_complete_replica_locations,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# get analysis sites where data is available at disk
|
|
272
|
+
def getAnalSitesWithDataDisk(dataSiteMap, includeTape=False, use_vp=True, use_incomplete=False):
|
|
273
|
+
sites_with_complete_replicas = []
|
|
274
|
+
sites_with_incomplete_replicas = []
|
|
275
|
+
sites_with_non_vp_disk_replicas = set()
|
|
276
|
+
sites_using_vp = set()
|
|
277
|
+
for tmp_site_name, tmpSeValMap in dataSiteMap.items():
|
|
278
|
+
for tmpSE, tmpValMap in tmpSeValMap.items():
|
|
279
|
+
# VP
|
|
280
|
+
if tmpValMap.get("vp"):
|
|
281
|
+
sites_using_vp.add(tmp_site_name)
|
|
282
|
+
if not use_vp:
|
|
283
|
+
continue
|
|
284
|
+
# on disk or tape
|
|
285
|
+
if includeTape or not tmpValMap["tape"]:
|
|
286
|
+
# complete replica available at disk, tape, or VP
|
|
287
|
+
if tmpValMap["state"] == "complete":
|
|
288
|
+
# has complete replica
|
|
289
|
+
if tmp_site_name not in sites_with_complete_replicas:
|
|
290
|
+
sites_with_complete_replicas.append(tmp_site_name)
|
|
291
|
+
# has non-VP disk replica
|
|
292
|
+
if not tmpValMap["tape"] and not tmpValMap.get("vp"):
|
|
293
|
+
sites_with_non_vp_disk_replicas.add(tmp_site_name)
|
|
294
|
+
else:
|
|
295
|
+
# incomplete replica at disk
|
|
296
|
+
if tmp_site_name not in sites_with_incomplete_replicas:
|
|
297
|
+
sites_with_incomplete_replicas.append(tmp_site_name)
|
|
298
|
+
# remove VP if complete non-VP disk replica is unavailable
|
|
299
|
+
if sites_with_non_vp_disk_replicas:
|
|
300
|
+
for tmpSiteNameVP in sites_using_vp:
|
|
301
|
+
if tmpSiteNameVP in sites_with_complete_replicas:
|
|
302
|
+
sites_with_complete_replicas.remove(tmpSiteNameVP)
|
|
303
|
+
if tmpSiteNameVP in sites_with_incomplete_replicas:
|
|
304
|
+
sites_with_incomplete_replicas.remove(tmpSiteNameVP)
|
|
305
|
+
# return sites with complete
|
|
306
|
+
if sites_with_complete_replicas:
|
|
307
|
+
if not use_incomplete or sites_with_non_vp_disk_replicas:
|
|
308
|
+
return sites_with_complete_replicas
|
|
309
|
+
else:
|
|
310
|
+
return sites_with_complete_replicas + sites_with_incomplete_replicas
|
|
311
|
+
# return sites with incomplete if complete is unavailable
|
|
312
|
+
return sites_with_incomplete_replicas
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# get the number of jobs in a status
|
|
316
|
+
def getNumJobs(jobStatMap, computingSite, jobStatus, cloud=None, workQueue_tag=None):
|
|
317
|
+
if computingSite not in jobStatMap:
|
|
318
|
+
return 0
|
|
319
|
+
nJobs = 0
|
|
320
|
+
# loop over all workQueues
|
|
321
|
+
for tmpWorkQueue, tmpWorkQueueVal in jobStatMap[computingSite].items():
|
|
322
|
+
# workQueue is defined
|
|
323
|
+
if workQueue_tag is not None and workQueue_tag != tmpWorkQueue:
|
|
324
|
+
continue
|
|
325
|
+
# loop over all job status
|
|
326
|
+
for tmpJobStatus, tmpCount in tmpWorkQueueVal.items():
|
|
327
|
+
if tmpJobStatus == jobStatus:
|
|
328
|
+
nJobs += tmpCount
|
|
329
|
+
|
|
330
|
+
return nJobs
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def get_total_nq_nr_ratio(job_stat_map, work_queue_tag=None):
|
|
334
|
+
"""
|
|
335
|
+
Get the ratio of number of queued jobs to number of running jobs
|
|
336
|
+
"""
|
|
337
|
+
nRunning = 0
|
|
338
|
+
nQueue = 0
|
|
339
|
+
|
|
340
|
+
# loop over all workQueues
|
|
341
|
+
for siteVal in job_stat_map.values():
|
|
342
|
+
for tmpWorkQueue in siteVal:
|
|
343
|
+
# workQueue is defined
|
|
344
|
+
if work_queue_tag is not None and work_queue_tag != tmpWorkQueue:
|
|
345
|
+
continue
|
|
346
|
+
tmpWorkQueueVal = siteVal[tmpWorkQueue]
|
|
347
|
+
# loop over all job status
|
|
348
|
+
for tmpJobStatus in ["defined", "assigned", "activated", "starting"]:
|
|
349
|
+
if tmpJobStatus in tmpWorkQueueVal:
|
|
350
|
+
nQueue += tmpWorkQueueVal[tmpJobStatus]
|
|
351
|
+
if "running" in tmpWorkQueueVal:
|
|
352
|
+
nRunning += tmpWorkQueueVal["running"]
|
|
353
|
+
try:
|
|
354
|
+
ratio = float(nQueue) / float(nRunning)
|
|
355
|
+
except Exception:
|
|
356
|
+
ratio = None
|
|
357
|
+
|
|
358
|
+
return ratio
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def hasZeroShare(site_spec, task_spec, ignore_priority, tmp_log):
|
|
362
|
+
"""
|
|
363
|
+
Check if the site has a zero share for the given task. Zero share means there is a policy preventing the site to be used for the task.
|
|
364
|
+
|
|
365
|
+
:param site_spec: SiteSpec object describing the site being checked in brokerage
|
|
366
|
+
:param task_spec: TaskSpec object describing the task being brokered
|
|
367
|
+
:param ignore_priority: Merging job chunks will skip the priority check
|
|
368
|
+
:param tmp_log: Logger object
|
|
369
|
+
|
|
370
|
+
:return: False means there is no policy defined and the site can be used.
|
|
371
|
+
True means the site has a fair share policy that prevents the site to be used for the task
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
# there is no per-site share defined (CRIC "fairsharePolicy" field), the site can be used
|
|
375
|
+
if site_spec.fairsharePolicy in ["", None]:
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
# get the group of processing types from a pre-defined mapping
|
|
380
|
+
processing_group = ProcessGroups.getProcessGroup(task_spec.processingType)
|
|
381
|
+
|
|
382
|
+
# don't suppress test tasks - the site can be used
|
|
383
|
+
if processing_group in ["test"]:
|
|
384
|
+
return False
|
|
385
|
+
|
|
386
|
+
# loop over all policies
|
|
387
|
+
for policy in site_spec.fairsharePolicy.split(","):
|
|
388
|
+
# Examples of policies are:
|
|
389
|
+
# type=evgen:100%,type=simul:100%,type=any:0%
|
|
390
|
+
# type=evgen:100%,type=simul:100%,type=any:0%,group=(AP_Higgs|AP_Susy|AP_Exotics|Higgs):0%
|
|
391
|
+
# gshare=Express:100%,gshare=any:0%
|
|
392
|
+
# priority>400:0
|
|
393
|
+
tmp_processing_type = None
|
|
394
|
+
tmp_working_group = None
|
|
395
|
+
tmp_priority = None
|
|
396
|
+
tmp_gshare = None
|
|
397
|
+
tmp_fair_share = policy.split(":")[-1]
|
|
398
|
+
|
|
399
|
+
# break down each fair share policy into its fields
|
|
400
|
+
for tmp_field in policy.split(":"):
|
|
401
|
+
if tmp_field.startswith("type="):
|
|
402
|
+
tmp_processing_type = tmp_field.split("=")[-1]
|
|
403
|
+
elif tmp_field.startswith("group="):
|
|
404
|
+
tmp_working_group = tmp_field.split("=")[-1]
|
|
405
|
+
elif tmp_field.startswith("gshare="):
|
|
406
|
+
tmp_gshare = tmp_field.split("=")[-1]
|
|
407
|
+
elif tmp_field.startswith("priority"):
|
|
408
|
+
tmp_priority = re.sub("priority", "", tmp_field)
|
|
409
|
+
|
|
410
|
+
# check for a matching processing type
|
|
411
|
+
if tmp_processing_type not in ["any", None]:
|
|
412
|
+
if "*" in tmp_processing_type:
|
|
413
|
+
tmp_processing_type = tmp_processing_type.replace("*", ".*")
|
|
414
|
+
# if there is no match between the site's fair share policy and the task's processing type,
|
|
415
|
+
# so continue looking for other policies that trigger the zero share condition
|
|
416
|
+
if re.search(f"^{tmp_processing_type}$", processing_group) is None:
|
|
417
|
+
continue
|
|
418
|
+
|
|
419
|
+
# check for matching working group
|
|
420
|
+
if tmp_working_group not in ["any", None]:
|
|
421
|
+
# None causes an exception in re.search, so convert to empty string
|
|
422
|
+
task_working_group = task_spec.workingGroup or ""
|
|
423
|
+
if "*" in tmp_working_group:
|
|
424
|
+
tmp_working_group = tmp_working_group.replace("*", ".*")
|
|
425
|
+
# if there is no match between the site's fair share policy and the task's working group
|
|
426
|
+
# continue looking for other policies that trigger the zero share condition
|
|
427
|
+
if re.search(f"^{tmp_working_group}$", task_working_group) is None:
|
|
428
|
+
continue
|
|
429
|
+
|
|
430
|
+
# check for matching gshare. Note that this only works for "leave gshares" in the fairsharePolicy,
|
|
431
|
+
# i.e. the ones that have no sub-gshares, since the task only gets "leave gshares" assigned
|
|
432
|
+
if tmp_gshare not in ["any", None] and task_spec.gshare is not None:
|
|
433
|
+
# None causes an exception in re.search, so convert to empty string
|
|
434
|
+
task_gshare = task_spec.gshare or ""
|
|
435
|
+
if "*" in tmp_gshare:
|
|
436
|
+
tmp_gshare = tmp_gshare.replace("*", ".*")
|
|
437
|
+
# if there is no match between the site's fair share policy and the task's gshare
|
|
438
|
+
# continue looking for other policies that trigger the zero share condition
|
|
439
|
+
if re.search(f"^{tmp_gshare}$", task_gshare) is None:
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
# check priority
|
|
443
|
+
if tmp_priority is not None and not ignore_priority:
|
|
444
|
+
try:
|
|
445
|
+
exec(f"tmpStat = {task_spec.currentPriority}{tmp_priority}", globals())
|
|
446
|
+
tmp_log.debug(
|
|
447
|
+
f"Priority check for {site_spec.sitename}, {task_spec.currentPriority}): " f"{task_spec.currentPriority}{tmp_priority} = {tmpStat}"
|
|
448
|
+
)
|
|
449
|
+
if not tmpStat:
|
|
450
|
+
continue
|
|
451
|
+
except Exception:
|
|
452
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
453
|
+
tmp_log.error(f"Priority check for {site_spec.sitename} failed with {error_type}:{error_value}")
|
|
454
|
+
|
|
455
|
+
# check fair share value
|
|
456
|
+
# if 0, we need to skip the site
|
|
457
|
+
# if different than 0, the site can be used
|
|
458
|
+
if tmp_fair_share in ["0", "0%"]:
|
|
459
|
+
return True
|
|
460
|
+
else:
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
except Exception:
|
|
464
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
465
|
+
tmp_log.error(f"hasZeroShare failed with {error_type}:{error_value}")
|
|
466
|
+
|
|
467
|
+
# if we reach this point, it means there is no policy preventing the site to be used
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# check if site name is matched with one of list items
|
|
472
|
+
def isMatched(siteName, nameList):
|
|
473
|
+
for tmpName in nameList:
|
|
474
|
+
# ignore empty
|
|
475
|
+
if tmpName == "":
|
|
476
|
+
continue
|
|
477
|
+
# wild card
|
|
478
|
+
if "*" in tmpName:
|
|
479
|
+
tmpName = tmpName.replace("*", ".*")
|
|
480
|
+
if re.search(tmpName, siteName) is not None:
|
|
481
|
+
return True
|
|
482
|
+
else:
|
|
483
|
+
# normal pattern
|
|
484
|
+
if tmpName == siteName:
|
|
485
|
+
return True
|
|
486
|
+
|
|
487
|
+
return False
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# get dict to set nucleus
|
|
491
|
+
def getDictToSetNucleus(nucleusSpec, tmpDatasetSpecs):
|
|
492
|
+
# get destinations
|
|
493
|
+
return_map = {"datasets": [], "nucleus": nucleusSpec.name}
|
|
494
|
+
for datasetSpec in tmpDatasetSpecs:
|
|
495
|
+
# skip distributed datasets
|
|
496
|
+
if DataServiceUtils.getDistributedDestination(datasetSpec.storageToken) is not None:
|
|
497
|
+
continue
|
|
498
|
+
# get endpoint relevant to token
|
|
499
|
+
endPoint = nucleusSpec.getAssociatedEndpoint(datasetSpec.storageToken)
|
|
500
|
+
if endPoint is None and not nucleusSpec.is_nucleus() and nucleusSpec.get_default_endpoint_out():
|
|
501
|
+
# use default endpoint for satellite that doesn't have relevant endpoint
|
|
502
|
+
endPoint = nucleusSpec.get_default_endpoint_out()
|
|
503
|
+
if endPoint is None:
|
|
504
|
+
continue
|
|
505
|
+
token = endPoint["ddm_endpoint_name"]
|
|
506
|
+
# add original token
|
|
507
|
+
if datasetSpec.storageToken not in ["", None]:
|
|
508
|
+
token += f"/{datasetSpec.storageToken.split('/')[-1]}"
|
|
509
|
+
return_map["datasets"].append({"datasetID": datasetSpec.datasetID, "token": f"dst:{token}", "destination": f"nucleus:{nucleusSpec.name}"})
|
|
510
|
+
return return_map
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# remove problematic sites
|
|
514
|
+
def skipProblematicSites(candidateSpecList, ngSites, sitesUsedByTask, preSetSiteSpec, maxNumSites, tmpLog):
|
|
515
|
+
skippedSites = set()
|
|
516
|
+
usedSitesGood = []
|
|
517
|
+
newSitesGood = []
|
|
518
|
+
# collect sites already used by the task
|
|
519
|
+
for candidateSpec in candidateSpecList:
|
|
520
|
+
# check if problematic
|
|
521
|
+
if (candidateSpec.siteName in ngSites or candidateSpec.unifiedName in ngSites) and (
|
|
522
|
+
preSetSiteSpec is None or candidateSpec.siteName != preSetSiteSpec.siteName
|
|
523
|
+
):
|
|
524
|
+
skippedSites.add(candidateSpec.siteName)
|
|
525
|
+
else:
|
|
526
|
+
if candidateSpec.siteName in sitesUsedByTask or candidateSpec.unifiedName in sitesUsedByTask:
|
|
527
|
+
usedSitesGood.append(candidateSpec)
|
|
528
|
+
else:
|
|
529
|
+
newSitesGood.append(candidateSpec)
|
|
530
|
+
# set number of sites if undefined
|
|
531
|
+
if maxNumSites in [0, None]:
|
|
532
|
+
maxNumSites = len(candidateSpecList)
|
|
533
|
+
newcandidateSpecList = usedSitesGood + newSitesGood
|
|
534
|
+
newcandidateSpecList = newcandidateSpecList[:maxNumSites]
|
|
535
|
+
# dump
|
|
536
|
+
for skippedSite in skippedSites:
|
|
537
|
+
tmpLog.debug(f"getting rid of problematic site {skippedSite}")
|
|
538
|
+
return newcandidateSpecList
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
# get mapping between sites and input storage endpoints
|
|
542
|
+
def getSiteInputStorageEndpointMap(site_list, site_mapper, prod_source_label, job_label):
|
|
543
|
+
# make a map of panda sites to ddm endpoints
|
|
544
|
+
ret_map = {}
|
|
545
|
+
for site_name in site_list:
|
|
546
|
+
tmp_site_spec = site_mapper.getSite(site_name)
|
|
547
|
+
scope_input, scope_output = select_scope(tmp_site_spec, prod_source_label, job_label)
|
|
548
|
+
|
|
549
|
+
# skip if scope not available
|
|
550
|
+
if scope_input not in tmp_site_spec.ddm_endpoints_input:
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
# add the schedconfig.ddm endpoints
|
|
554
|
+
ret_map[site_name] = list(tmp_site_spec.ddm_endpoints_input[scope_input].all.keys())
|
|
555
|
+
|
|
556
|
+
return ret_map
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
# get to-running rate of sites from various resources
|
|
560
|
+
CACHE_SiteToRunRateStats = {}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def getSiteToRunRateStats(tbIF, vo, time_window=21600, cutoff=300, cache_lifetime=600):
|
|
564
|
+
# initialize
|
|
565
|
+
ret_val = False
|
|
566
|
+
ret_map = {}
|
|
567
|
+
# DB cache keys
|
|
568
|
+
dc_main_key = "AtlasSites"
|
|
569
|
+
dc_sub_key = "SiteToRunRate"
|
|
570
|
+
# arguments for process lock
|
|
571
|
+
this_prodsourcelabel = "user"
|
|
572
|
+
this_pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}_{os.getpgrp()}-broker"
|
|
573
|
+
this_component = "Cache.SiteToRunRate"
|
|
574
|
+
# timestamps
|
|
575
|
+
current_time = naive_utcnow()
|
|
576
|
+
starttime_max = current_time - datetime.timedelta(seconds=cutoff)
|
|
577
|
+
starttime_min = current_time - datetime.timedelta(seconds=time_window)
|
|
578
|
+
# rounded with 10 minutes
|
|
579
|
+
starttime_max_rounded = starttime_max.replace(minute=starttime_max.minute // 10 * 10, second=0, microsecond=0)
|
|
580
|
+
starttime_min_rounded = starttime_min.replace(minute=starttime_min.minute // 10 * 10, second=0, microsecond=0)
|
|
581
|
+
real_interval_hours = (starttime_max_rounded - starttime_min_rounded).total_seconds() / 3600
|
|
582
|
+
# local cache key
|
|
583
|
+
local_cache_key = (starttime_min_rounded, starttime_max_rounded)
|
|
584
|
+
# condition of query
|
|
585
|
+
if local_cache_key in CACHE_SiteToRunRateStats and current_time <= CACHE_SiteToRunRateStats[local_cache_key]["exp"]:
|
|
586
|
+
# query from local cache
|
|
587
|
+
ret_val = True
|
|
588
|
+
ret_map = CACHE_SiteToRunRateStats[local_cache_key]["data"]
|
|
589
|
+
else:
|
|
590
|
+
# try some times
|
|
591
|
+
for _ in range(99):
|
|
592
|
+
# skip if too long after original current time
|
|
593
|
+
if naive_utcnow() - current_time > datetime.timedelta(seconds=min(10, cache_lifetime / 4)):
|
|
594
|
+
# break trying
|
|
595
|
+
break
|
|
596
|
+
try:
|
|
597
|
+
# query from DB cache
|
|
598
|
+
cache_spec = tbIF.getCache_JEDI(main_key=dc_main_key, sub_key=dc_sub_key)
|
|
599
|
+
if cache_spec is not None:
|
|
600
|
+
expired_time = cache_spec.last_update + datetime.timedelta(seconds=cache_lifetime)
|
|
601
|
+
if current_time <= expired_time:
|
|
602
|
+
# valid DB cache
|
|
603
|
+
ret_val = True
|
|
604
|
+
ret_map = json.loads(cache_spec.data)
|
|
605
|
+
# fill local cache
|
|
606
|
+
CACHE_SiteToRunRateStats[local_cache_key] = {"exp": expired_time, "data": ret_map}
|
|
607
|
+
# break trying
|
|
608
|
+
break
|
|
609
|
+
# got process lock
|
|
610
|
+
got_lock = tbIF.lockProcess_JEDI(
|
|
611
|
+
vo=vo,
|
|
612
|
+
prodSourceLabel=this_prodsourcelabel,
|
|
613
|
+
cloud=None,
|
|
614
|
+
workqueue_id=None,
|
|
615
|
+
resource_name=None,
|
|
616
|
+
component=this_component,
|
|
617
|
+
pid=this_pid,
|
|
618
|
+
timeLimit=5,
|
|
619
|
+
)
|
|
620
|
+
if not got_lock:
|
|
621
|
+
# not getting lock, sleep and query cache again
|
|
622
|
+
time.sleep(1)
|
|
623
|
+
continue
|
|
624
|
+
# query from PanDA DB directly
|
|
625
|
+
ret_val, ret_map = tbIF.getSiteToRunRateStats(vo=vo, exclude_rwq=False, starttime_min=starttime_min, starttime_max=starttime_max)
|
|
626
|
+
if ret_val:
|
|
627
|
+
# expired time
|
|
628
|
+
expired_time = current_time + datetime.timedelta(seconds=cache_lifetime)
|
|
629
|
+
# fill local cache
|
|
630
|
+
CACHE_SiteToRunRateStats[local_cache_key] = {"exp": expired_time, "data": ret_map}
|
|
631
|
+
# json of data
|
|
632
|
+
data_json = json.dumps(ret_map)
|
|
633
|
+
# fill DB cache
|
|
634
|
+
tbIF.updateCache_JEDI(main_key=dc_main_key, sub_key=dc_sub_key, data=data_json)
|
|
635
|
+
# unlock process
|
|
636
|
+
tbIF.unlockProcess_JEDI(
|
|
637
|
+
vo=vo, prodSourceLabel=this_prodsourcelabel, cloud=None, workqueue_id=None, resource_name=None, component=this_component, pid=this_pid
|
|
638
|
+
)
|
|
639
|
+
# break trying
|
|
640
|
+
break
|
|
641
|
+
except Exception as e:
|
|
642
|
+
# dump error message
|
|
643
|
+
err_str = f"AtlasBrokerUtils.getSiteToRunRateStats got {e.__class__.__name__}: {e} \n"
|
|
644
|
+
sys.stderr.write(err_str)
|
|
645
|
+
# break trying
|
|
646
|
+
break
|
|
647
|
+
# delete outdated entries in local cache
|
|
648
|
+
for lc_key in list(CACHE_SiteToRunRateStats.keys()):
|
|
649
|
+
lc_time_min, lc_time_max = lc_key
|
|
650
|
+
if lc_time_max < starttime_max_rounded - datetime.timedelta(seconds=cache_lifetime):
|
|
651
|
+
try:
|
|
652
|
+
del CACHE_SiteToRunRateStats[lc_key]
|
|
653
|
+
except Exception as e:
|
|
654
|
+
err_str = f"AtlasBrokerUtils.getSiteToRunRateStats when deleting outdated entries got {e.__class__.__name__}: {e} \n"
|
|
655
|
+
sys.stderr.write(err_str)
|
|
656
|
+
|
|
657
|
+
return ret_val, ret_map
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
# get users jobs stats from various resources
|
|
661
|
+
CACHE_UsersJobsStats = {}
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def getUsersJobsStats(tbIF, vo, prod_source_label, cache_lifetime=60):
|
|
665
|
+
# initialize
|
|
666
|
+
ret_val = False
|
|
667
|
+
ret_map = {}
|
|
668
|
+
# DB cache keys
|
|
669
|
+
dc_main_key = "AtlasSites"
|
|
670
|
+
dc_sub_key = "UsersJobsStats"
|
|
671
|
+
# arguments for process lock
|
|
672
|
+
this_prodsourcelabel = prod_source_label
|
|
673
|
+
this_pid = this_pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}_{os.getpgrp()}-broker"
|
|
674
|
+
this_component = "Cache.UsersJobsStats"
|
|
675
|
+
# local cache key; a must if not using global variable
|
|
676
|
+
local_cache_key = "_main"
|
|
677
|
+
# timestamps
|
|
678
|
+
current_time = naive_utcnow()
|
|
679
|
+
# condition of query
|
|
680
|
+
if local_cache_key in CACHE_UsersJobsStats and current_time <= CACHE_UsersJobsStats[local_cache_key]["exp"]:
|
|
681
|
+
# query from local cache
|
|
682
|
+
ret_val = True
|
|
683
|
+
ret_map = CACHE_UsersJobsStats[local_cache_key]["data"]
|
|
684
|
+
else:
|
|
685
|
+
# try some times
|
|
686
|
+
for _ in range(99):
|
|
687
|
+
# skip if too long after original current time
|
|
688
|
+
if naive_utcnow() - current_time > datetime.timedelta(seconds=min(15, cache_lifetime / 4)):
|
|
689
|
+
# break trying
|
|
690
|
+
break
|
|
691
|
+
try:
|
|
692
|
+
# query from DB cache
|
|
693
|
+
cache_spec = tbIF.getCache_JEDI(main_key=dc_main_key, sub_key=dc_sub_key)
|
|
694
|
+
if cache_spec is not None:
|
|
695
|
+
expired_time = cache_spec.last_update + datetime.timedelta(seconds=cache_lifetime)
|
|
696
|
+
if current_time <= expired_time:
|
|
697
|
+
# valid DB cache
|
|
698
|
+
ret_val = True
|
|
699
|
+
ret_map = json.loads(cache_spec.data)
|
|
700
|
+
# fill local cache
|
|
701
|
+
CACHE_UsersJobsStats[local_cache_key] = {"exp": expired_time, "data": ret_map}
|
|
702
|
+
# break trying
|
|
703
|
+
break
|
|
704
|
+
# got process lock
|
|
705
|
+
got_lock = tbIF.lockProcess_JEDI(
|
|
706
|
+
vo=vo,
|
|
707
|
+
prodSourceLabel=this_prodsourcelabel,
|
|
708
|
+
cloud=None,
|
|
709
|
+
workqueue_id=None,
|
|
710
|
+
resource_name=None,
|
|
711
|
+
component=this_component,
|
|
712
|
+
pid=this_pid,
|
|
713
|
+
timeLimit=(cache_lifetime * 0.75 / 60),
|
|
714
|
+
)
|
|
715
|
+
if not got_lock:
|
|
716
|
+
# not getting lock, sleep and query cache again
|
|
717
|
+
time.sleep(1)
|
|
718
|
+
continue
|
|
719
|
+
# query from PanDA DB directly
|
|
720
|
+
ret_map = tbIF.getUsersJobsStats_JEDI(prod_source_label=this_prodsourcelabel)
|
|
721
|
+
if ret_map is not None:
|
|
722
|
+
# expired time
|
|
723
|
+
expired_time = current_time + datetime.timedelta(seconds=cache_lifetime)
|
|
724
|
+
# fill local cache
|
|
725
|
+
CACHE_UsersJobsStats[local_cache_key] = {"exp": expired_time, "data": ret_map}
|
|
726
|
+
# json of data
|
|
727
|
+
data_json = json.dumps(ret_map)
|
|
728
|
+
# fill DB cache
|
|
729
|
+
tbIF.updateCache_JEDI(main_key=dc_main_key, sub_key=dc_sub_key, data=data_json)
|
|
730
|
+
# make True return
|
|
731
|
+
ret_val = True
|
|
732
|
+
# unlock process
|
|
733
|
+
tbIF.unlockProcess_JEDI(
|
|
734
|
+
vo=vo, prodSourceLabel=this_prodsourcelabel, cloud=None, workqueue_id=None, resource_name=None, component=this_component, pid=this_pid
|
|
735
|
+
)
|
|
736
|
+
# break trying
|
|
737
|
+
break
|
|
738
|
+
except Exception as e:
|
|
739
|
+
# dump error message
|
|
740
|
+
err_str = f"AtlasBrokerUtils.getUsersJobsStats got {e.__class__.__name__}: {e} \n"
|
|
741
|
+
sys.stderr.write(err_str)
|
|
742
|
+
# break trying
|
|
743
|
+
break
|
|
744
|
+
|
|
745
|
+
return ret_val, ret_map
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
# get gshare usage
|
|
749
|
+
def getGShareUsage(tbIF, gshare, fresher_than_minutes_ago=15):
|
|
750
|
+
# initialize
|
|
751
|
+
ret_val = False
|
|
752
|
+
ret_map = {}
|
|
753
|
+
# timestamps
|
|
754
|
+
current_time = naive_utcnow()
|
|
755
|
+
# try some times
|
|
756
|
+
for _ in range(99):
|
|
757
|
+
now_time = naive_utcnow()
|
|
758
|
+
# skip if too long after original current time
|
|
759
|
+
if now_time - current_time > datetime.timedelta(seconds=max(3, fresher_than_minutes_ago / 3)):
|
|
760
|
+
# break trying
|
|
761
|
+
break
|
|
762
|
+
try:
|
|
763
|
+
# query from PanDA DB directly
|
|
764
|
+
sql_get_gshare = "SELECT m.value_json FROM ATLAS_PANDA.Metrics m WHERE m.metric=:metric AND m.gshare=:gshare AND m.timestamp>=:min_timestamp "
|
|
765
|
+
var_map = {
|
|
766
|
+
":metric": "gshare_preference",
|
|
767
|
+
":gshare": gshare,
|
|
768
|
+
":min_timestamp": now_time - datetime.timedelta(minutes=fresher_than_minutes_ago),
|
|
769
|
+
}
|
|
770
|
+
# result
|
|
771
|
+
res = tbIF.querySQL(sql_get_gshare, var_map)
|
|
772
|
+
if res:
|
|
773
|
+
value_json = res[0][0]
|
|
774
|
+
# json of data
|
|
775
|
+
ret_map = json.loads(value_json)
|
|
776
|
+
# make True return
|
|
777
|
+
ret_val = True
|
|
778
|
+
# break trying
|
|
779
|
+
break
|
|
780
|
+
except Exception as e:
|
|
781
|
+
# dump error message
|
|
782
|
+
err_str = f"AtlasBrokerUtils.getGShareUsage got {e.__class__.__name__}: {e} \n"
|
|
783
|
+
sys.stderr.write(err_str)
|
|
784
|
+
# break trying
|
|
785
|
+
break
|
|
786
|
+
|
|
787
|
+
return ret_val, ret_map
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
# get user evaluation
|
|
791
|
+
def getUserEval(tbIF, user, fresher_than_minutes_ago=20):
|
|
792
|
+
# initialize
|
|
793
|
+
ret_val = False
|
|
794
|
+
ret_map = {}
|
|
795
|
+
# timestamps
|
|
796
|
+
current_time = naive_utcnow()
|
|
797
|
+
# try some times
|
|
798
|
+
for _ in range(99):
|
|
799
|
+
now_time = naive_utcnow()
|
|
800
|
+
# skip if too long after original current time
|
|
801
|
+
if now_time - current_time > datetime.timedelta(seconds=max(3, fresher_than_minutes_ago / 3)):
|
|
802
|
+
# break trying
|
|
803
|
+
break
|
|
804
|
+
try:
|
|
805
|
+
# query from PanDA DB directly
|
|
806
|
+
sql_get_user_eval = f'SELECT m.value_json."{user}" FROM ATLAS_PANDA.Metrics m WHERE m.metric=:metric AND m.timestamp>=:min_timestamp '
|
|
807
|
+
var_map = {
|
|
808
|
+
":metric": "analy_user_eval",
|
|
809
|
+
":min_timestamp": now_time - datetime.timedelta(minutes=fresher_than_minutes_ago),
|
|
810
|
+
}
|
|
811
|
+
# result
|
|
812
|
+
res = tbIF.querySQL(sql_get_user_eval, var_map)
|
|
813
|
+
if res:
|
|
814
|
+
value_json = res[0][0]
|
|
815
|
+
# json of data
|
|
816
|
+
ret_map = json.loads(value_json) if value_json else None
|
|
817
|
+
# make True return
|
|
818
|
+
ret_val = True
|
|
819
|
+
# break trying
|
|
820
|
+
break
|
|
821
|
+
except Exception as e:
|
|
822
|
+
# dump error message
|
|
823
|
+
err_str = f"AtlasBrokerUtils.getUserEval got {e.__class__.__name__}: {e} \n"
|
|
824
|
+
sys.stderr.write(err_str)
|
|
825
|
+
# break trying
|
|
826
|
+
break
|
|
827
|
+
|
|
828
|
+
return ret_val, ret_map
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
# get user task evaluation
|
|
832
|
+
def getUserTaskEval(tbIF, taskID, fresher_than_minutes_ago=15):
|
|
833
|
+
# initialize
|
|
834
|
+
ret_val = False
|
|
835
|
+
ret_map = {}
|
|
836
|
+
# timestamps
|
|
837
|
+
current_time = naive_utcnow()
|
|
838
|
+
# try some times
|
|
839
|
+
for _ in range(99):
|
|
840
|
+
now_time = naive_utcnow()
|
|
841
|
+
# skip if too long after original current time
|
|
842
|
+
if now_time - current_time > datetime.timedelta(seconds=max(3, fresher_than_minutes_ago / 3)):
|
|
843
|
+
# break trying
|
|
844
|
+
break
|
|
845
|
+
try:
|
|
846
|
+
# query from PanDA DB directly
|
|
847
|
+
sql_get_task_eval = (
|
|
848
|
+
"SELECT tev.value_json "
|
|
849
|
+
"FROM ATLAS_PANDA.Task_Evaluation tev "
|
|
850
|
+
"WHERE tev.metric=:metric "
|
|
851
|
+
"AND tev.jediTaskID=:taskID "
|
|
852
|
+
"AND tev.timestamp>=:min_timestamp "
|
|
853
|
+
)
|
|
854
|
+
var_map = {
|
|
855
|
+
":metric": "analy_task_eval",
|
|
856
|
+
":taskID": taskID,
|
|
857
|
+
":min_timestamp": now_time - datetime.timedelta(minutes=fresher_than_minutes_ago),
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
res = tbIF.querySQL(sql_get_task_eval, var_map)
|
|
861
|
+
if res:
|
|
862
|
+
value_json = res[0][0]
|
|
863
|
+
# json of data
|
|
864
|
+
ret_map = json.loads(value_json) if value_json else None
|
|
865
|
+
# make True return
|
|
866
|
+
ret_val = True
|
|
867
|
+
# break trying
|
|
868
|
+
break
|
|
869
|
+
except Exception as e:
|
|
870
|
+
# dump error message
|
|
871
|
+
err_str = f"AtlasBrokerUtils.getUserTaskEval got {e.__class__.__name__}: {e} \n"
|
|
872
|
+
sys.stderr.write(err_str)
|
|
873
|
+
# break trying
|
|
874
|
+
break
|
|
875
|
+
|
|
876
|
+
return ret_val, ret_map
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
# get analysis sites class
|
|
880
|
+
def getAnalySitesClass(tbIF, fresher_than_minutes_ago=60):
|
|
881
|
+
# initialize
|
|
882
|
+
ret_val = False
|
|
883
|
+
ret_map = {}
|
|
884
|
+
# timestamps
|
|
885
|
+
current_time = naive_utcnow()
|
|
886
|
+
# try some times
|
|
887
|
+
for _ in range(99):
|
|
888
|
+
now_time = naive_utcnow()
|
|
889
|
+
# skip if too long after original current time
|
|
890
|
+
if now_time - current_time > datetime.timedelta(seconds=max(3, fresher_than_minutes_ago / 3)):
|
|
891
|
+
# break trying
|
|
892
|
+
break
|
|
893
|
+
try:
|
|
894
|
+
# query from PanDA DB directly
|
|
895
|
+
sql_get_task_eval = "SELECT m.computingSite, m.value_json.class FROM ATLAS_PANDA.Metrics m WHERE m.metric=:metric AND m.timestamp>=:min_timestamp "
|
|
896
|
+
var_map = {
|
|
897
|
+
":metric": "analy_site_eval",
|
|
898
|
+
":min_timestamp": now_time - datetime.timedelta(minutes=fresher_than_minutes_ago),
|
|
899
|
+
}
|
|
900
|
+
res = tbIF.querySQL(sql_get_task_eval, var_map)
|
|
901
|
+
if res:
|
|
902
|
+
for site, class_value in res:
|
|
903
|
+
ret_map[site] = int(class_value)
|
|
904
|
+
# make True return
|
|
905
|
+
ret_val = True
|
|
906
|
+
# break trying
|
|
907
|
+
break
|
|
908
|
+
except Exception as e:
|
|
909
|
+
# dump error message
|
|
910
|
+
err_str = f"AtlasBrokerUtils.getAnalySitesEval got {e.__class__.__name__}: {e} \n"
|
|
911
|
+
sys.stderr.write(err_str)
|
|
912
|
+
# break trying
|
|
913
|
+
break
|
|
914
|
+
|
|
915
|
+
return ret_val, ret_map
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def compare_version_string(version_string, comparison_string):
|
|
919
|
+
"""
|
|
920
|
+
Compares a version string with another string composed of a comparison operator and a version string.
|
|
921
|
+
|
|
922
|
+
Args:
|
|
923
|
+
version_string (str): The version string to compare.
|
|
924
|
+
comparison_string (str): The string containing the comparison operator and version string (e.g., ">=2.0").
|
|
925
|
+
|
|
926
|
+
Returns:
|
|
927
|
+
bool or None: True if the version string satisfies the comparison, False if it doesn't,
|
|
928
|
+
or None if the comparison string is invalid.
|
|
929
|
+
"""
|
|
930
|
+
match = re.match(r"([=><]+)(.+)", comparison_string)
|
|
931
|
+
if not match:
|
|
932
|
+
return None
|
|
933
|
+
|
|
934
|
+
operator = match.group(1).strip()
|
|
935
|
+
version_to_compare = match.group(2).strip()
|
|
936
|
+
|
|
937
|
+
try:
|
|
938
|
+
version1 = version.parse(version_string)
|
|
939
|
+
version2 = version.parse(version_to_compare)
|
|
940
|
+
except version.InvalidVersion:
|
|
941
|
+
return None
|
|
942
|
+
|
|
943
|
+
if operator == "==":
|
|
944
|
+
return version1 == version2
|
|
945
|
+
elif operator == "!=":
|
|
946
|
+
return version1 != version2
|
|
947
|
+
elif operator == ">=":
|
|
948
|
+
return version1 >= version2
|
|
949
|
+
elif operator == "<=":
|
|
950
|
+
return version1 <= version2
|
|
951
|
+
elif operator == ">":
|
|
952
|
+
return version1 > version2
|
|
953
|
+
elif operator == "<":
|
|
954
|
+
return version1 < version2
|
|
955
|
+
else:
|
|
956
|
+
return None
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
# check SW with json
|
|
960
|
+
class JsonSoftwareCheck:
|
|
961
|
+
# constructor
|
|
962
|
+
def __init__(self, site_mapper, sw_map, wn_architecture_level_map):
|
|
963
|
+
self.siteMapper = site_mapper
|
|
964
|
+
self.sw_map = sw_map
|
|
965
|
+
self.wn_architecture_level_map = wn_architecture_level_map
|
|
966
|
+
|
|
967
|
+
# get lists
|
|
968
|
+
def check(
|
|
969
|
+
self,
|
|
970
|
+
site_list,
|
|
971
|
+
cvmfs_tag,
|
|
972
|
+
sw_project,
|
|
973
|
+
sw_version,
|
|
974
|
+
cmt_config,
|
|
975
|
+
need_cvmfs,
|
|
976
|
+
cmt_config_only,
|
|
977
|
+
need_container=False,
|
|
978
|
+
container_name=None,
|
|
979
|
+
only_tags_fc=False,
|
|
980
|
+
host_cpu_specs=None,
|
|
981
|
+
host_cpu_pref=None,
|
|
982
|
+
host_gpu_spec=None,
|
|
983
|
+
log_stream=None,
|
|
984
|
+
):
|
|
985
|
+
ok_sites = []
|
|
986
|
+
no_auto_sites = []
|
|
987
|
+
preference_weight_map = {}
|
|
988
|
+
|
|
989
|
+
# Does the task define a preferred architecture level?
|
|
990
|
+
preferred_architecture_level = host_cpu_pref.get("preferred_architecture_level") if host_cpu_pref else None
|
|
991
|
+
|
|
992
|
+
for tmp_site_name in site_list:
|
|
993
|
+
tmp_site_spec = self.siteMapper.getSite(tmp_site_name)
|
|
994
|
+
if tmp_site_spec.releases == ["AUTO"] and tmp_site_name in self.sw_map:
|
|
995
|
+
go_ahead = False
|
|
996
|
+
try:
|
|
997
|
+
# convert to a dict
|
|
998
|
+
architecture_map = {}
|
|
999
|
+
if "architectures" in self.sw_map[tmp_site_name]:
|
|
1000
|
+
for arch_spec in self.sw_map[tmp_site_name]["architectures"]:
|
|
1001
|
+
if "type" in arch_spec:
|
|
1002
|
+
architecture_map[arch_spec["type"]] = arch_spec
|
|
1003
|
+
|
|
1004
|
+
# check if need CPU
|
|
1005
|
+
if "cpu" in architecture_map:
|
|
1006
|
+
need_cpu = False
|
|
1007
|
+
for k in architecture_map["cpu"]:
|
|
1008
|
+
if isinstance(architecture_map["cpu"][k], list):
|
|
1009
|
+
if "excl" in architecture_map["cpu"][k]:
|
|
1010
|
+
need_cpu = True
|
|
1011
|
+
break
|
|
1012
|
+
if need_cpu and host_cpu_specs is None:
|
|
1013
|
+
continue
|
|
1014
|
+
|
|
1015
|
+
# check if need GPU
|
|
1016
|
+
if "gpu" in architecture_map:
|
|
1017
|
+
need_gpu = False
|
|
1018
|
+
for k in architecture_map["gpu"]:
|
|
1019
|
+
if isinstance(architecture_map["gpu"][k], list):
|
|
1020
|
+
if "excl" in architecture_map["gpu"][k]:
|
|
1021
|
+
need_gpu = True
|
|
1022
|
+
break
|
|
1023
|
+
if need_gpu and host_gpu_spec is None:
|
|
1024
|
+
continue
|
|
1025
|
+
|
|
1026
|
+
# calculate the preference_weight_map for the site
|
|
1027
|
+
if preferred_architecture_level:
|
|
1028
|
+
preference_weight_map[tmp_site_name] = (
|
|
1029
|
+
1 + self.wn_architecture_level_map.get(tmp_site_name, {}).get(preferred_architecture_level, {}).get("pct_within_queue", 0) / 100
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
if host_cpu_specs or host_gpu_spec:
|
|
1033
|
+
# skip since the PQ doesn't describe HW spec
|
|
1034
|
+
if not architecture_map:
|
|
1035
|
+
continue
|
|
1036
|
+
# check CPU
|
|
1037
|
+
if host_cpu_specs:
|
|
1038
|
+
host_ok = False
|
|
1039
|
+
for host_cpu_spec in host_cpu_specs:
|
|
1040
|
+
# CPU not specified
|
|
1041
|
+
if "cpu" not in architecture_map:
|
|
1042
|
+
continue
|
|
1043
|
+
|
|
1044
|
+
# check architecture
|
|
1045
|
+
if host_cpu_spec["arch"] == "*":
|
|
1046
|
+
if "excl" in architecture_map["cpu"]["arch"]:
|
|
1047
|
+
continue
|
|
1048
|
+
else:
|
|
1049
|
+
if "any" not in architecture_map["cpu"]["arch"]:
|
|
1050
|
+
if host_cpu_spec["arch"] not in architecture_map["cpu"]["arch"]:
|
|
1051
|
+
# check with regex
|
|
1052
|
+
if not [True for iii in architecture_map["cpu"]["arch"] if re.search("^" + host_cpu_spec["arch"] + "$", iii)]:
|
|
1053
|
+
continue
|
|
1054
|
+
|
|
1055
|
+
# check vendor
|
|
1056
|
+
if host_cpu_spec["vendor"] == "*":
|
|
1057
|
+
# task doesn't specify a vendor and PQ explicitly requests a specific vendor
|
|
1058
|
+
if "vendor" in architecture_map["cpu"] and "excl" in architecture_map["cpu"]["vendor"]:
|
|
1059
|
+
continue
|
|
1060
|
+
else:
|
|
1061
|
+
# task specifies a vendor and PQ doesn't request any specific vendor
|
|
1062
|
+
if "vendor" not in architecture_map["cpu"]:
|
|
1063
|
+
continue
|
|
1064
|
+
# task specifies a vendor and PQ doesn't accept any vendor or the specific vendor
|
|
1065
|
+
if "any" not in architecture_map["cpu"]["vendor"] and host_cpu_spec["vendor"] not in architecture_map["cpu"]["vendor"]:
|
|
1066
|
+
continue
|
|
1067
|
+
|
|
1068
|
+
# check instruction set
|
|
1069
|
+
if host_cpu_spec["instr"] == "*":
|
|
1070
|
+
if "instr" in architecture_map["cpu"] and "excl" in architecture_map["cpu"]["instr"]:
|
|
1071
|
+
continue
|
|
1072
|
+
else:
|
|
1073
|
+
if "instr" not in architecture_map["cpu"]:
|
|
1074
|
+
continue
|
|
1075
|
+
if "any" not in architecture_map["cpu"]["instr"] and host_cpu_spec["instr"] not in architecture_map["cpu"]["instr"]:
|
|
1076
|
+
continue
|
|
1077
|
+
host_ok = True
|
|
1078
|
+
break
|
|
1079
|
+
if not host_ok:
|
|
1080
|
+
continue
|
|
1081
|
+
|
|
1082
|
+
# check GPU
|
|
1083
|
+
if host_gpu_spec:
|
|
1084
|
+
# GPU not specified
|
|
1085
|
+
if "gpu" not in architecture_map:
|
|
1086
|
+
continue
|
|
1087
|
+
|
|
1088
|
+
# check vendor
|
|
1089
|
+
if host_gpu_spec["vendor"] == "*":
|
|
1090
|
+
# task doesn't specify CPU vendor and PQ explicitly requests a specific vendor
|
|
1091
|
+
if "vendor" in architecture_map["gpu"] and "excl" in architecture_map["gpu"]["vendor"]:
|
|
1092
|
+
continue
|
|
1093
|
+
else:
|
|
1094
|
+
# task specifies a vendor and PQ doesn't request any specific vendor
|
|
1095
|
+
if "vendor" not in architecture_map["gpu"]:
|
|
1096
|
+
continue
|
|
1097
|
+
# task specifies a vendor and PQ doesn't accept any vendor or the specific vendor
|
|
1098
|
+
if "any" not in architecture_map["gpu"]["vendor"] and host_gpu_spec["vendor"] not in architecture_map["gpu"]["vendor"]:
|
|
1099
|
+
continue
|
|
1100
|
+
|
|
1101
|
+
# check model
|
|
1102
|
+
if host_gpu_spec["model"] == "*":
|
|
1103
|
+
if "model" in architecture_map["gpu"] and "excl" in architecture_map["gpu"]["model"]:
|
|
1104
|
+
continue
|
|
1105
|
+
else:
|
|
1106
|
+
if isinstance(host_gpu_spec["model"], dict):
|
|
1107
|
+
model_pattern = host_gpu_spec["model"]["pattern"]
|
|
1108
|
+
model_excl = host_gpu_spec["model"].get("excl", False)
|
|
1109
|
+
else:
|
|
1110
|
+
model_pattern = host_gpu_spec["model"]
|
|
1111
|
+
model_excl = False
|
|
1112
|
+
if "model" not in architecture_map["gpu"] or (
|
|
1113
|
+
"any" not in architecture_map["gpu"]["model"]
|
|
1114
|
+
and any(re.match(model_pattern, m, re.IGNORECASE) for m in architecture_map["gpu"]["model"])
|
|
1115
|
+
== model_excl
|
|
1116
|
+
):
|
|
1117
|
+
continue
|
|
1118
|
+
|
|
1119
|
+
# check version
|
|
1120
|
+
if "version" in host_gpu_spec:
|
|
1121
|
+
if "version" not in architecture_map["gpu"]:
|
|
1122
|
+
# PQ doesn't specify version
|
|
1123
|
+
continue
|
|
1124
|
+
elif "any" == architecture_map["gpu"]["version"]:
|
|
1125
|
+
# PQ accepts any version
|
|
1126
|
+
pass
|
|
1127
|
+
else:
|
|
1128
|
+
# check version at PQ
|
|
1129
|
+
if not compare_version_string(architecture_map["gpu"]["version"], host_gpu_spec["version"]):
|
|
1130
|
+
continue
|
|
1131
|
+
go_ahead = True
|
|
1132
|
+
except Exception as e:
|
|
1133
|
+
if log_stream:
|
|
1134
|
+
log_stream.error(f"json check {str(architecture_map)} failed for {tmp_site_name} {str(e)} {traceback.format_exc()} ")
|
|
1135
|
+
if not go_ahead:
|
|
1136
|
+
continue
|
|
1137
|
+
# only HW check
|
|
1138
|
+
if not (cvmfs_tag or cmt_config or sw_project or sw_version or container_name) and (host_cpu_specs or host_gpu_spec):
|
|
1139
|
+
ok_sites.append(tmp_site_name)
|
|
1140
|
+
continue
|
|
1141
|
+
# check for fat container
|
|
1142
|
+
if container_name:
|
|
1143
|
+
# check for container
|
|
1144
|
+
if not only_tags_fc and ("any" in self.sw_map[tmp_site_name]["containers"] or "/cvmfs" in self.sw_map[tmp_site_name]["containers"]):
|
|
1145
|
+
# any in containers
|
|
1146
|
+
ok_sites.append(tmp_site_name)
|
|
1147
|
+
elif container_name in set([t["container_name"] for t in self.sw_map[tmp_site_name]["tags"] if t["container_name"]]):
|
|
1148
|
+
# logical name in tags or any in containers
|
|
1149
|
+
ok_sites.append(tmp_site_name)
|
|
1150
|
+
elif container_name in set([s for t in self.sw_map[tmp_site_name]["tags"] for s in t["sources"] if t["sources"]]):
|
|
1151
|
+
# full path in sources
|
|
1152
|
+
ok_sites.append(tmp_site_name)
|
|
1153
|
+
elif not only_tags_fc:
|
|
1154
|
+
# get sources in all tag list
|
|
1155
|
+
if "ALL" in self.sw_map:
|
|
1156
|
+
source_list_in_all_tag = [s for t in self.sw_map["ALL"]["tags"] for s in t["sources"] if t["container_name"] == container_name]
|
|
1157
|
+
else:
|
|
1158
|
+
source_list_in_all_tag = []
|
|
1159
|
+
# prefix with full path
|
|
1160
|
+
for tmp_prefix in self.sw_map[tmp_site_name]["containers"]:
|
|
1161
|
+
if container_name.startswith(tmp_prefix):
|
|
1162
|
+
ok_sites.append(tmp_site_name)
|
|
1163
|
+
break
|
|
1164
|
+
toBreak = False
|
|
1165
|
+
for source_in_all_tag in source_list_in_all_tag:
|
|
1166
|
+
if source_in_all_tag.startswith(tmp_prefix):
|
|
1167
|
+
ok_sites.append(tmp_site_name)
|
|
1168
|
+
toBreak = True
|
|
1169
|
+
break
|
|
1170
|
+
if toBreak:
|
|
1171
|
+
break
|
|
1172
|
+
continue
|
|
1173
|
+
# only cmt config check
|
|
1174
|
+
if cmt_config_only:
|
|
1175
|
+
ok_sites.append(tmp_site_name)
|
|
1176
|
+
continue
|
|
1177
|
+
# check if CVMFS is available
|
|
1178
|
+
if "any" in self.sw_map[tmp_site_name]["cvmfs"] or cvmfs_tag in self.sw_map[tmp_site_name]["cvmfs"]:
|
|
1179
|
+
# check if container is available
|
|
1180
|
+
if "any" in self.sw_map[tmp_site_name]["containers"] or "/cvmfs" in self.sw_map[tmp_site_name]["containers"]:
|
|
1181
|
+
ok_sites.append(tmp_site_name)
|
|
1182
|
+
# check cmt config
|
|
1183
|
+
elif not need_container and cmt_config in self.sw_map[tmp_site_name]["cmtconfigs"]:
|
|
1184
|
+
ok_sites.append(tmp_site_name)
|
|
1185
|
+
elif not need_cvmfs:
|
|
1186
|
+
if not need_container or "any" in self.sw_map[tmp_site_name]["containers"]:
|
|
1187
|
+
# check tags
|
|
1188
|
+
for tag in self.sw_map[tmp_site_name]["tags"]:
|
|
1189
|
+
if tag["cmtconfig"] == cmt_config and tag["project"] == sw_project and tag["release"] == sw_version:
|
|
1190
|
+
ok_sites.append(tmp_site_name)
|
|
1191
|
+
break
|
|
1192
|
+
# don't pass to subsequent check if AUTO is enabled
|
|
1193
|
+
continue
|
|
1194
|
+
# use only AUTO for container or HW
|
|
1195
|
+
if container_name is not None or host_cpu_specs is not None or host_gpu_spec is not None:
|
|
1196
|
+
continue
|
|
1197
|
+
no_auto_sites.append(tmp_site_name)
|
|
1198
|
+
|
|
1199
|
+
return ok_sites, no_auto_sites, preference_weight_map
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
# resolve cmt_config
|
|
1203
|
+
def resolve_cmt_config(queue_name: str, cmt_config: str, base_platform, sw_map: dict) -> str | None:
|
|
1204
|
+
"""
|
|
1205
|
+
resolve cmt config at a given queue_name
|
|
1206
|
+
:param queue_name: queue name
|
|
1207
|
+
:param cmt_config: cmt confing to resolve
|
|
1208
|
+
:param base_platform: base platform
|
|
1209
|
+
:param sw_map: software map
|
|
1210
|
+
:return: resolved cmt config or None if unavailable or valid
|
|
1211
|
+
"""
|
|
1212
|
+
# return None if queue_name is unavailable
|
|
1213
|
+
if queue_name not in sw_map:
|
|
1214
|
+
return None
|
|
1215
|
+
# return None if cmt_config is valid
|
|
1216
|
+
if cmt_config in sw_map[queue_name]["cmtconfigs"]:
|
|
1217
|
+
return None
|
|
1218
|
+
# check if cmt_config matches with any of the queue's cmt_configs
|
|
1219
|
+
for tmp_cmt_config in sw_map[queue_name]["cmtconfigs"]:
|
|
1220
|
+
if re.search("^" + cmt_config + "$", tmp_cmt_config):
|
|
1221
|
+
if base_platform:
|
|
1222
|
+
# add base_platform if necessary
|
|
1223
|
+
tmp_cmt_config = tmp_cmt_config + "@" + base_platform
|
|
1224
|
+
return tmp_cmt_config
|
|
1225
|
+
# return None if cmt_config is unavailable
|
|
1226
|
+
return None
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
def check_endpoints_with_blacklist(
|
|
1230
|
+
site_spec: SiteSpec.SiteSpec, scope_input: str, scope_output: str, sites_in_nucleus: list, remote_source_available: bool
|
|
1231
|
+
) -> str | None:
|
|
1232
|
+
"""
|
|
1233
|
+
Check if site's endpoints are in the blacklist
|
|
1234
|
+
|
|
1235
|
+
:param site_spec: site spec
|
|
1236
|
+
:param scope_input: input scope
|
|
1237
|
+
:param scope_output: output scope
|
|
1238
|
+
:param sites_in_nucleus: list of sites in nucleus
|
|
1239
|
+
:param remote_source_available: if remote source is available
|
|
1240
|
+
|
|
1241
|
+
:return: description of blacklisted reason or None
|
|
1242
|
+
"""
|
|
1243
|
+
tmp_msg = None
|
|
1244
|
+
receive_input_over_wan = False
|
|
1245
|
+
read_input_over_lan = False
|
|
1246
|
+
write_output_over_lan = False
|
|
1247
|
+
send_output_over_wan = False
|
|
1248
|
+
tmp_site_name = site_spec.sitename
|
|
1249
|
+
if scope_input in site_spec.ddm_endpoints_input:
|
|
1250
|
+
for tmp_input_endpoint in site_spec.ddm_endpoints_input[scope_input].all.values():
|
|
1251
|
+
tmp_read_input_over_lan = tmp_input_endpoint["detailed_status"].get("read_lan")
|
|
1252
|
+
tmp_receive_input_over_wan = tmp_input_endpoint["detailed_status"].get("write_wan")
|
|
1253
|
+
# can read input from local
|
|
1254
|
+
if tmp_read_input_over_lan not in ["OFF", "TEST"]:
|
|
1255
|
+
read_input_over_lan = True
|
|
1256
|
+
# can receive input from remote to local
|
|
1257
|
+
if tmp_site_name not in sites_in_nucleus:
|
|
1258
|
+
# satellite sites
|
|
1259
|
+
if tmp_receive_input_over_wan not in ["OFF", "TEST"]:
|
|
1260
|
+
receive_input_over_wan = True
|
|
1261
|
+
else:
|
|
1262
|
+
# NA for nucleus sites
|
|
1263
|
+
receive_input_over_wan = True
|
|
1264
|
+
remote_source_available = True
|
|
1265
|
+
if scope_output in site_spec.ddm_endpoints_output:
|
|
1266
|
+
for tmp_output_endpoint in site_spec.ddm_endpoints_output[scope_output].all.values():
|
|
1267
|
+
tmp_write_output_over_lan = tmp_output_endpoint["detailed_status"].get("write_lan")
|
|
1268
|
+
tmp_send_output_over_wan = tmp_output_endpoint["detailed_status"].get("read_wan")
|
|
1269
|
+
# can write output to local
|
|
1270
|
+
if tmp_write_output_over_lan not in ["OFF", "TEST"]:
|
|
1271
|
+
write_output_over_lan = True
|
|
1272
|
+
# can send output from local to remote
|
|
1273
|
+
if tmp_site_name not in sites_in_nucleus:
|
|
1274
|
+
# satellite sites
|
|
1275
|
+
if tmp_send_output_over_wan not in ["OFF", "TEST"]:
|
|
1276
|
+
send_output_over_wan = True
|
|
1277
|
+
else:
|
|
1278
|
+
# NA for nucleus sites
|
|
1279
|
+
send_output_over_wan = True
|
|
1280
|
+
remote_source_available = True
|
|
1281
|
+
# take the status for logging
|
|
1282
|
+
if not read_input_over_lan:
|
|
1283
|
+
tmp_msg = f" skip site={tmp_site_name} since input endpoints cannot read over LAN, read_lan is not ON criteria=-read_lan_blacklist"
|
|
1284
|
+
elif not write_output_over_lan:
|
|
1285
|
+
tmp_msg = f" skip site={tmp_site_name} since output endpoints cannot write over LAN, write_lan is not ON criteria=-write_lan_blacklist"
|
|
1286
|
+
elif not receive_input_over_wan:
|
|
1287
|
+
tmp_msg = f" skip site={tmp_site_name} since input endpoints cannot receive files over WAN, write_wan is not ON criteria=-write_wan_blacklist"
|
|
1288
|
+
elif not send_output_over_wan:
|
|
1289
|
+
tmp_msg = f" skip site={tmp_site_name} since output endpoints cannot send out files over WAN, read_wan is not ON criteria=-read_wan_blacklist"
|
|
1290
|
+
elif not remote_source_available:
|
|
1291
|
+
tmp_msg = f" skip site={tmp_site_name} since source endpoints cannot transfer files over WAN and it is satellite criteria=-source_blacklist"
|
|
1292
|
+
return tmp_msg
|