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,2343 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import math
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import traceback
|
|
8
|
+
|
|
9
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
10
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
11
|
+
|
|
12
|
+
from pandajedi.jedicore import Interaction
|
|
13
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
14
|
+
from pandajedi.jedicore.SiteCandidate import SiteCandidate
|
|
15
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
16
|
+
from pandaserver.srvcore import CoreUtils
|
|
17
|
+
from pandaserver.taskbuffer import JobUtils
|
|
18
|
+
|
|
19
|
+
from . import AtlasBrokerUtils
|
|
20
|
+
from .JobBrokerBase import JobBrokerBase
|
|
21
|
+
|
|
22
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
23
|
+
|
|
24
|
+
APP = "jedi"
|
|
25
|
+
COMPONENT = "jobbroker"
|
|
26
|
+
VO = "atlas"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# brokerage for ATLAS analysis
|
|
30
|
+
class AtlasAnalJobBroker(JobBrokerBase):
|
|
31
|
+
# constructor
|
|
32
|
+
def __init__(self, ddmIF, taskBufferIF):
|
|
33
|
+
JobBrokerBase.__init__(self, ddmIF, taskBufferIF)
|
|
34
|
+
self.dataSiteMap = {}
|
|
35
|
+
|
|
36
|
+
# load the SW availability map
|
|
37
|
+
try:
|
|
38
|
+
self.sw_map = taskBufferIF.load_sw_map()
|
|
39
|
+
except Exception:
|
|
40
|
+
logger.error("Failed to load the SW tags map!!!")
|
|
41
|
+
self.sw_map = {}
|
|
42
|
+
|
|
43
|
+
# load the worker node CPU architecture-level map
|
|
44
|
+
try:
|
|
45
|
+
self.architecture_level_map = taskBufferIF.get_architecture_level_map()
|
|
46
|
+
except BaseException:
|
|
47
|
+
logger.error("Failed to load the WN architecture level map!!!")
|
|
48
|
+
self.architecture_level_map = {}
|
|
49
|
+
|
|
50
|
+
# main
|
|
51
|
+
def doBrokerage(self, taskSpec, cloudName, inputChunk, taskParamMap):
|
|
52
|
+
# make logger
|
|
53
|
+
if inputChunk.masterDataset:
|
|
54
|
+
msg_tag = f"<jediTaskID={taskSpec.jediTaskID} datasetID={inputChunk.masterDataset.datasetID}>"
|
|
55
|
+
else:
|
|
56
|
+
msg_tag = f"<jediTaskID={taskSpec.jediTaskID}>"
|
|
57
|
+
tmpLog = MsgWrapper(logger, msg_tag, monToken=f"<jediTaskID={taskSpec.jediTaskID} {naive_utcnow().isoformat('/')}>")
|
|
58
|
+
tmpLog.debug("start")
|
|
59
|
+
# return for failure
|
|
60
|
+
retFatal = self.SC_FATAL, inputChunk
|
|
61
|
+
retTmpError = self.SC_FAILED, inputChunk
|
|
62
|
+
# new maxwdir
|
|
63
|
+
newMaxwdir = {}
|
|
64
|
+
# get primary site candidates
|
|
65
|
+
sitePreAssigned = False
|
|
66
|
+
siteListPreAssigned = False
|
|
67
|
+
excludeList = []
|
|
68
|
+
includeList = None
|
|
69
|
+
scanSiteList = []
|
|
70
|
+
# problematic sites
|
|
71
|
+
problematic_sites_dict = {}
|
|
72
|
+
# not to use VP replicas for merging, scouts, and forceStaged
|
|
73
|
+
if inputChunk.isMerging or taskSpec.avoid_vp() or taskSpec.useScout() or taskSpec.useLocalIO():
|
|
74
|
+
useVP = False
|
|
75
|
+
else:
|
|
76
|
+
useVP = True
|
|
77
|
+
# avoid VP queues for merging
|
|
78
|
+
avoidVP = False
|
|
79
|
+
if inputChunk.isMerging:
|
|
80
|
+
avoidVP = True
|
|
81
|
+
# get workQueue
|
|
82
|
+
workQueue = self.taskBufferIF.getWorkQueueMap().getQueueWithIDGshare(taskSpec.workQueue_ID, taskSpec.gshare)
|
|
83
|
+
|
|
84
|
+
# site limitation
|
|
85
|
+
if taskSpec.useLimitedSites():
|
|
86
|
+
if "excludedSite" in taskParamMap:
|
|
87
|
+
excludeList = taskParamMap["excludedSite"]
|
|
88
|
+
# str to list for task retry
|
|
89
|
+
try:
|
|
90
|
+
if not isinstance(excludeList, list):
|
|
91
|
+
excludeList = excludeList.split(",")
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
if "includedSite" in taskParamMap:
|
|
95
|
+
includeList = taskParamMap["includedSite"]
|
|
96
|
+
# str to list for task retry
|
|
97
|
+
if includeList == "":
|
|
98
|
+
includeList = None
|
|
99
|
+
try:
|
|
100
|
+
if not isinstance(includeList, list):
|
|
101
|
+
includeList = includeList.split(",")
|
|
102
|
+
siteListPreAssigned = True
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
# loop over all sites
|
|
106
|
+
for siteName, tmpSiteSpec in self.siteMapper.siteSpecList.items():
|
|
107
|
+
if tmpSiteSpec.type == "analysis" or tmpSiteSpec.is_grandly_unified():
|
|
108
|
+
scanSiteList.append(siteName)
|
|
109
|
+
# preassigned
|
|
110
|
+
preassignedSite = taskSpec.site
|
|
111
|
+
if preassignedSite not in ["", None]:
|
|
112
|
+
# site is pre-assigned
|
|
113
|
+
if not self.siteMapper.checkSite(preassignedSite):
|
|
114
|
+
# check ddm for unknown site
|
|
115
|
+
includeList = []
|
|
116
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
117
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
118
|
+
scope_input, scope_output = select_scope(tmpSiteSpec, JobUtils.ANALY_PS, JobUtils.ANALY_PS)
|
|
119
|
+
if scope_input in tmpSiteSpec.ddm_endpoints_input and preassignedSite in tmpSiteSpec.ddm_endpoints_input[scope_input].all:
|
|
120
|
+
includeList.append(tmpSiteName)
|
|
121
|
+
if not includeList:
|
|
122
|
+
includeList = None
|
|
123
|
+
tmpLog.info(f"site={preassignedSite} is ignored since unknown")
|
|
124
|
+
else:
|
|
125
|
+
tmpLog.info(f"site={preassignedSite} is converted to {','.join(includeList)}")
|
|
126
|
+
preassignedSite = None
|
|
127
|
+
else:
|
|
128
|
+
tmpLog.info(f"site={preassignedSite} is pre-assigned")
|
|
129
|
+
sitePreAssigned = True
|
|
130
|
+
if preassignedSite not in scanSiteList:
|
|
131
|
+
scanSiteList.append(preassignedSite)
|
|
132
|
+
tmpLog.info(f"initial {len(self.get_unified_sites(scanSiteList))} candidates")
|
|
133
|
+
# allowed remote access protocol
|
|
134
|
+
allowedRemoteProtocol = "fax"
|
|
135
|
+
# MP
|
|
136
|
+
if taskSpec.coreCount is not None and taskSpec.coreCount > 1:
|
|
137
|
+
# use MCORE only
|
|
138
|
+
useMP = "only"
|
|
139
|
+
elif taskSpec.coreCount == 0:
|
|
140
|
+
# use MCORE and normal
|
|
141
|
+
useMP = "any"
|
|
142
|
+
else:
|
|
143
|
+
# not use MCORE
|
|
144
|
+
useMP = "unuse"
|
|
145
|
+
# get statistics of failures
|
|
146
|
+
timeWindowForFC = self.taskBufferIF.getConfigValue("anal_jobbroker", "TW_DONE_JOB_STAT", "jedi", taskSpec.vo)
|
|
147
|
+
if timeWindowForFC is None:
|
|
148
|
+
timeWindowForFC = 6
|
|
149
|
+
|
|
150
|
+
# get minimum bad jobs to skip PQ
|
|
151
|
+
minBadJobsToSkipPQ = self.taskBufferIF.getConfigValue("anal_jobbroker", "MIN_BAD_JOBS_TO_SKIP_PQ", "jedi", taskSpec.vo)
|
|
152
|
+
if minBadJobsToSkipPQ is None:
|
|
153
|
+
minBadJobsToSkipPQ = 5
|
|
154
|
+
|
|
155
|
+
# get total job stat
|
|
156
|
+
totalJobStat = self.get_task_common("totalJobStat")
|
|
157
|
+
if totalJobStat is None:
|
|
158
|
+
if taskSpec.workingGroup:
|
|
159
|
+
totalJobStat = self.taskBufferIF.countJobsPerTarget_JEDI(taskSpec.workingGroup, False)
|
|
160
|
+
else:
|
|
161
|
+
totalJobStat = self.taskBufferIF.countJobsPerTarget_JEDI(taskSpec.origUserName, True)
|
|
162
|
+
self.set_task_common("totalJobStat", totalJobStat)
|
|
163
|
+
# check total to cap
|
|
164
|
+
if totalJobStat:
|
|
165
|
+
if taskSpec.workingGroup:
|
|
166
|
+
gdp_token_jobs = "CAP_RUNNING_GROUP_JOBS"
|
|
167
|
+
gdp_token_cores = "CAP_RUNNING_GROUP_CORES"
|
|
168
|
+
else:
|
|
169
|
+
gdp_token_jobs = "CAP_RUNNING_USER_JOBS"
|
|
170
|
+
gdp_token_cores = "CAP_RUNNING_USER_CORES"
|
|
171
|
+
maxNumRunJobs = self.taskBufferIF.getConfigValue("prio_mgr", gdp_token_jobs)
|
|
172
|
+
maxNumRunCores = self.taskBufferIF.getConfigValue("prio_mgr", gdp_token_cores)
|
|
173
|
+
maxFactor = 2
|
|
174
|
+
|
|
175
|
+
if maxNumRunJobs:
|
|
176
|
+
if totalJobStat["nRunJobs"] > maxNumRunJobs:
|
|
177
|
+
tmpLog.error(f"throttle to generate jobs due to too many running jobs {totalJobStat['nRunJobs']} > {gdp_token_jobs}")
|
|
178
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
179
|
+
return retTmpError
|
|
180
|
+
elif totalJobStat["nQueuedJobs"] > maxFactor * maxNumRunJobs:
|
|
181
|
+
tmpLog.error(f"throttle to generate jobs due to too many queued jobs {totalJobStat['nQueuedJobs']} > {maxFactor}x{gdp_token_jobs}")
|
|
182
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
183
|
+
return retTmpError
|
|
184
|
+
if maxNumRunCores:
|
|
185
|
+
if totalJobStat["nRunCores"] > maxNumRunCores:
|
|
186
|
+
tmpLog.error(f"throttle to generate jobs due to too many running cores {totalJobStat['nRunCores']} > {gdp_token_cores}")
|
|
187
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
188
|
+
return retTmpError
|
|
189
|
+
elif totalJobStat["nQueuedCores"] > maxFactor * maxNumRunCores:
|
|
190
|
+
tmpLog.error(f"throttle to generate jobs due to too many queued cores {totalJobStat['nQueuedCores']} > {maxFactor}x{gdp_token_cores}")
|
|
191
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
192
|
+
return retTmpError
|
|
193
|
+
|
|
194
|
+
# get gshare usage
|
|
195
|
+
ret_val, gshare_usage_dict = AtlasBrokerUtils.getGShareUsage(tbIF=self.taskBufferIF, gshare=taskSpec.gshare)
|
|
196
|
+
if not ret_val:
|
|
197
|
+
tmpLog.warning(f"failed to get gshare usage of {taskSpec.gshare}")
|
|
198
|
+
elif not gshare_usage_dict:
|
|
199
|
+
tmpLog.warning(f"got empty gshare usage of {taskSpec.gshare}")
|
|
200
|
+
|
|
201
|
+
# get L1 share usage
|
|
202
|
+
l1_share_usage_dict = None
|
|
203
|
+
if taskSpec.gshare == "User Analysis":
|
|
204
|
+
l1_share_name = "L1 User Analysis"
|
|
205
|
+
ret_val, l1_share_usage_dict = AtlasBrokerUtils.getGShareUsage(tbIF=self.taskBufferIF, gshare=l1_share_name)
|
|
206
|
+
if not ret_val:
|
|
207
|
+
tmpLog.warning(f"failed to get gshare usage of {l1_share_name}")
|
|
208
|
+
elif not l1_share_usage_dict:
|
|
209
|
+
tmpLog.warning(f"got empty gshare usage of {l1_share_name}")
|
|
210
|
+
|
|
211
|
+
# get analy sites classification
|
|
212
|
+
ret_val, analy_sites_class_dict = AtlasBrokerUtils.getAnalySitesClass(tbIF=self.taskBufferIF)
|
|
213
|
+
if not ret_val:
|
|
214
|
+
tmpLog.warning("failed to get analy sites classification")
|
|
215
|
+
elif not analy_sites_class_dict:
|
|
216
|
+
analy_sites_class_dict = {}
|
|
217
|
+
tmpLog.warning("got empty analy sites classification")
|
|
218
|
+
|
|
219
|
+
# get user usage
|
|
220
|
+
ret_val, user_eval_dict = AtlasBrokerUtils.getUserEval(tbIF=self.taskBufferIF, user=taskSpec.origUserName)
|
|
221
|
+
if not ret_val:
|
|
222
|
+
tmpLog.warning(f"failed to get user evaluation of {taskSpec.origUserName}")
|
|
223
|
+
elif not user_eval_dict:
|
|
224
|
+
tmpLog.warning(f"got empty user evaluation of {taskSpec.origUserName}")
|
|
225
|
+
|
|
226
|
+
# task evaluation
|
|
227
|
+
ret_val, task_eval_dict = AtlasBrokerUtils.getUserTaskEval(tbIF=self.taskBufferIF, taskID=taskSpec.jediTaskID)
|
|
228
|
+
if not ret_val:
|
|
229
|
+
tmpLog.warning("failed to get user task evaluation")
|
|
230
|
+
elif not task_eval_dict:
|
|
231
|
+
tmpLog.warning("got empty user task evaluation")
|
|
232
|
+
|
|
233
|
+
# parameters about User Analysis threshold
|
|
234
|
+
threshold_A = self.taskBufferIF.getConfigValue("analy_eval", "USER_USAGE_THRESHOLD_A", "pandaserver", taskSpec.vo)
|
|
235
|
+
if threshold_A is None:
|
|
236
|
+
threshold_A = 1000
|
|
237
|
+
threshold_B = self.taskBufferIF.getConfigValue("analy_eval", "USER_USAGE_THRESHOLD_B", "pandaserver", taskSpec.vo)
|
|
238
|
+
if threshold_B is None:
|
|
239
|
+
threshold_B = 10000
|
|
240
|
+
user_analyis_to_throttle_threshold_perc_A = 100
|
|
241
|
+
user_analyis_to_throttle_threshold_perc_B = min(95, user_analyis_to_throttle_threshold_perc_A)
|
|
242
|
+
user_analyis_to_throttle_threshold_perc_C = min(90, user_analyis_to_throttle_threshold_perc_B)
|
|
243
|
+
user_analyis_throttle_intensity_A = 1.0
|
|
244
|
+
|
|
245
|
+
# parameters about Analysis Stabilizer
|
|
246
|
+
base_queue_length_per_pq = self.taskBufferIF.getConfigValue("anal_jobbroker", "BASE_QUEUE_LENGTH_PER_PQ", "jedi", taskSpec.vo)
|
|
247
|
+
if base_queue_length_per_pq is None:
|
|
248
|
+
base_queue_length_per_pq = 100
|
|
249
|
+
base_expected_wait_hour_on_pq = self.taskBufferIF.getConfigValue("anal_jobbroker", "BASE_EXPECTED_WAIT_HOUR_ON_PQ", "jedi", taskSpec.vo)
|
|
250
|
+
if base_expected_wait_hour_on_pq is None:
|
|
251
|
+
base_expected_wait_hour_on_pq = 8
|
|
252
|
+
base_default_queue_length_per_pq_user = self.taskBufferIF.getConfigValue("anal_jobbroker", "BASE_DEFAULT_QUEUE_LENGTH_PER_PQ_USER", "jedi", taskSpec.vo)
|
|
253
|
+
if base_default_queue_length_per_pq_user is None:
|
|
254
|
+
base_default_queue_length_per_pq_user = 5
|
|
255
|
+
base_queue_ratio_on_pq = self.taskBufferIF.getConfigValue("anal_jobbroker", "BASE_QUEUE_RATIO_ON_PQ", "jedi", taskSpec.vo)
|
|
256
|
+
if base_queue_ratio_on_pq is None:
|
|
257
|
+
base_queue_ratio_on_pq = 0.05
|
|
258
|
+
static_max_queue_running_ratio = self.taskBufferIF.getConfigValue("anal_jobbroker", "STATIC_MAX_QUEUE_RUNNING_RATIO", "jedi", taskSpec.vo)
|
|
259
|
+
if static_max_queue_running_ratio is None:
|
|
260
|
+
static_max_queue_running_ratio = 2.0
|
|
261
|
+
max_expected_wait_hour = self.taskBufferIF.getConfigValue("anal_jobbroker", "MAX_EXPECTED_WAIT_HOUR", "jedi", taskSpec.vo)
|
|
262
|
+
if max_expected_wait_hour is None:
|
|
263
|
+
max_expected_wait_hour = 12.0
|
|
264
|
+
|
|
265
|
+
max_missing_input_files = self.taskBufferIF.getConfigValue("jobbroker", "MAX_MISSING_INPUT_FILES", "jedi", taskSpec.vo)
|
|
266
|
+
if max_missing_input_files is None:
|
|
267
|
+
max_missing_input_files = 10
|
|
268
|
+
min_input_completeness = self.taskBufferIF.getConfigValue("jobbroker", "MIN_INPUT_COMPLETENESS", "jedi", taskSpec.vo)
|
|
269
|
+
if min_input_completeness is None:
|
|
270
|
+
min_input_completeness = 90
|
|
271
|
+
|
|
272
|
+
# minimum brokerage weight
|
|
273
|
+
min_weight_param = f"MIN_WEIGHT_{taskSpec.prodSourceLabel}_{taskSpec.gshare}"
|
|
274
|
+
min_weight = self.taskBufferIF.getConfigValue("jobbroker", min_weight_param, "jedi", taskSpec.vo)
|
|
275
|
+
if min_weight is None:
|
|
276
|
+
min_weight_param = f"MIN_WEIGHT_{taskSpec.prodSourceLabel}"
|
|
277
|
+
min_weight = self.taskBufferIF.getConfigValue("jobbroker", min_weight_param, "jedi", taskSpec.vo)
|
|
278
|
+
if min_weight is None:
|
|
279
|
+
min_weight = 0
|
|
280
|
+
|
|
281
|
+
# throttle User Analysis tasks when close to gshare target
|
|
282
|
+
if taskSpec.gshare in ["User Analysis"] and gshare_usage_dict and task_eval_dict:
|
|
283
|
+
try:
|
|
284
|
+
usage_percent = gshare_usage_dict["usage_perc"] * 100
|
|
285
|
+
if l1_share_usage_dict and l1_share_usage_dict.get("eqiv_usage_perc") is not None:
|
|
286
|
+
usage_percent = min(usage_percent, l1_share_usage_dict["eqiv_usage_perc"] * 100)
|
|
287
|
+
task_class_value = task_eval_dict["class"]
|
|
288
|
+
usage_slot_ratio_A = 0.5
|
|
289
|
+
if user_eval_dict:
|
|
290
|
+
usage_slot_ratio_A = 1.0 - user_eval_dict["rem_slots_A"] / threshold_A
|
|
291
|
+
|
|
292
|
+
if task_class_value == -1 and usage_percent > user_analyis_to_throttle_threshold_perc_C:
|
|
293
|
+
# C-tasks to throttle
|
|
294
|
+
tmpLog.error(
|
|
295
|
+
"throttle to generate jobs due to gshare {gshare} > {threshold}% of target and task in class C".format(
|
|
296
|
+
gshare=taskSpec.gshare, threshold=user_analyis_to_throttle_threshold_perc_C
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
300
|
+
return retTmpError
|
|
301
|
+
elif task_class_value == 0 and usage_percent > user_analyis_to_throttle_threshold_perc_B:
|
|
302
|
+
# B-tasks to throttle
|
|
303
|
+
tmpLog.error(
|
|
304
|
+
"throttle to generate jobs due to gshare {gshare} > {threshold}% of target and task in class B".format(
|
|
305
|
+
gshare=taskSpec.gshare, threshold=user_analyis_to_throttle_threshold_perc_B
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
309
|
+
return retTmpError
|
|
310
|
+
elif (
|
|
311
|
+
task_class_value == 1 and usage_percent * usage_slot_ratio_A * user_analyis_throttle_intensity_A > user_analyis_to_throttle_threshold_perc_A
|
|
312
|
+
):
|
|
313
|
+
# A-tasks to throttle
|
|
314
|
+
tmpLog.error(
|
|
315
|
+
"throttle to generate jobs due to gshare {gshare} > {threshold:.3%} of target and task in class A".format(
|
|
316
|
+
gshare=taskSpec.gshare, threshold=user_analyis_throttle_intensity_A / (usage_slot_ratio_A + 2**-20)
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
320
|
+
return retTmpError
|
|
321
|
+
except Exception as e:
|
|
322
|
+
tmpLog.error(f"got error when checking low-ranked tasks to throttle; skipped : {e}")
|
|
323
|
+
|
|
324
|
+
# check global disk quota
|
|
325
|
+
if taskSpec.workingGroup:
|
|
326
|
+
global_quota_ok, near_global_limit, quota_msg = self.ddmIF.check_global_quota(taskSpec.workingGroup)
|
|
327
|
+
local_quota_ok, endpoints_over_local_quota = self.ddmIF.get_endpoints_over_local_quota(taskSpec.workingGroup)
|
|
328
|
+
else:
|
|
329
|
+
global_quota_ok, near_global_limit, quota_msg = self.ddmIF.check_global_quota(taskSpec.userName)
|
|
330
|
+
local_quota_ok, endpoints_over_local_quota = self.ddmIF.get_endpoints_over_local_quota(taskSpec.userName)
|
|
331
|
+
|
|
332
|
+
# over global quota
|
|
333
|
+
if not global_quota_ok:
|
|
334
|
+
tmpLog.error(f"throttle to generate jobs due to {quota_msg}")
|
|
335
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
336
|
+
return retTmpError
|
|
337
|
+
|
|
338
|
+
# close to global quota
|
|
339
|
+
if near_global_limit and not inputChunk.isMerging:
|
|
340
|
+
tmpLog.error(f"throttle to generate only merge jobs due to {quota_msg}")
|
|
341
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
342
|
+
return retTmpError
|
|
343
|
+
|
|
344
|
+
# cannot get local quota
|
|
345
|
+
if not local_quota_ok:
|
|
346
|
+
tmpLog.error(f"failed to check local quota")
|
|
347
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
348
|
+
return retTmpError
|
|
349
|
+
|
|
350
|
+
# get failure count
|
|
351
|
+
failureCounts = self.get_task_common("failureCounts")
|
|
352
|
+
if failureCounts is None:
|
|
353
|
+
failureCounts = self.taskBufferIF.getFailureCountsForTask_JEDI(taskSpec.jediTaskID, timeWindowForFC)
|
|
354
|
+
self.set_task_common("failureCounts", failureCounts)
|
|
355
|
+
|
|
356
|
+
# IO intensity cutoff in kB/sec to allow input transfers
|
|
357
|
+
io_intensity_key = "IO_INTENSITY_CUTOFF_USER"
|
|
358
|
+
io_intensity_cutoff = self.taskBufferIF.getConfigValue("anal_jobbroker", io_intensity_key, "jedi", taskSpec.vo)
|
|
359
|
+
|
|
360
|
+
# timelimit for data locality check
|
|
361
|
+
loc_check_timeout_key = "DATA_CHECK_TIMEOUT_USER"
|
|
362
|
+
loc_check_timeout_val = self.taskBufferIF.getConfigValue("anal_jobbroker", loc_check_timeout_key, "jedi", taskSpec.vo)
|
|
363
|
+
|
|
364
|
+
# check input datasets
|
|
365
|
+
element_map = dict()
|
|
366
|
+
ddsList = set()
|
|
367
|
+
complete_disk_ok = {}
|
|
368
|
+
complete_tape_ok = {}
|
|
369
|
+
true_complete_disk_ok = {}
|
|
370
|
+
can_be_local_source = {}
|
|
371
|
+
can_be_remote_source = {}
|
|
372
|
+
list_of_complete_replica_locations = {}
|
|
373
|
+
is_distributed_map = {}
|
|
374
|
+
if inputChunk.getDatasets():
|
|
375
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
376
|
+
datasetName = datasetSpec.datasetName
|
|
377
|
+
isDistributed = None
|
|
378
|
+
if datasetName not in self.dataSiteMap:
|
|
379
|
+
# get the list of sites where data is available
|
|
380
|
+
tmpLog.info(f"getting the list of sites where {datasetName} is available")
|
|
381
|
+
(
|
|
382
|
+
tmpSt,
|
|
383
|
+
tmpRet,
|
|
384
|
+
tmp_complete_disk_ok,
|
|
385
|
+
tmp_complete_tape_ok,
|
|
386
|
+
tmp_truly_complete_disk,
|
|
387
|
+
tmp_can_be_local_source,
|
|
388
|
+
tmp_can_be_remote_source,
|
|
389
|
+
tmp_list_of_complete_replica_locations,
|
|
390
|
+
) = AtlasBrokerUtils.get_sites_with_data(
|
|
391
|
+
self.get_unified_sites(scanSiteList),
|
|
392
|
+
self.siteMapper,
|
|
393
|
+
self.ddmIF,
|
|
394
|
+
datasetName,
|
|
395
|
+
element_map.get(datasetSpec.datasetName),
|
|
396
|
+
max_missing_input_files,
|
|
397
|
+
min_input_completeness,
|
|
398
|
+
)
|
|
399
|
+
if tmpSt in [Interaction.JEDITemporaryError, Interaction.JEDITimeoutError]:
|
|
400
|
+
tmpLog.error(f"temporary failed to get the list of sites where data is available, since {tmpRet}")
|
|
401
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
402
|
+
return retTmpError
|
|
403
|
+
if tmpSt == Interaction.JEDIFatalError:
|
|
404
|
+
tmpLog.error(f"fatal error when getting the list of sites where data is available, since {tmpRet}")
|
|
405
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
406
|
+
return retFatal
|
|
407
|
+
# append
|
|
408
|
+
self.dataSiteMap[datasetName] = tmpRet
|
|
409
|
+
complete_disk_ok[datasetName] = tmp_complete_disk_ok
|
|
410
|
+
complete_tape_ok[datasetName] = tmp_complete_tape_ok
|
|
411
|
+
true_complete_disk_ok[datasetName] = tmp_truly_complete_disk
|
|
412
|
+
can_be_local_source[datasetName] = tmp_can_be_local_source
|
|
413
|
+
can_be_remote_source[datasetName] = tmp_can_be_remote_source
|
|
414
|
+
list_of_complete_replica_locations[datasetName] = tmp_list_of_complete_replica_locations
|
|
415
|
+
if datasetName.startswith("ddo"):
|
|
416
|
+
tmpLog.debug(f" {len(tmpRet)} sites")
|
|
417
|
+
else:
|
|
418
|
+
tmpLog.debug(f" {len(tmpRet)} sites : {str(tmpRet)}")
|
|
419
|
+
# check if distributed
|
|
420
|
+
if tmpRet != {}:
|
|
421
|
+
isDistributed = True
|
|
422
|
+
for tmpMap in tmpRet.values():
|
|
423
|
+
for tmpVal in tmpMap.values():
|
|
424
|
+
if tmpVal["state"] == "complete":
|
|
425
|
+
isDistributed = False
|
|
426
|
+
break
|
|
427
|
+
if not isDistributed:
|
|
428
|
+
break
|
|
429
|
+
if isDistributed or datasetName.endswith("/"):
|
|
430
|
+
# check if really distributed
|
|
431
|
+
isDistributed = self.ddmIF.isDistributedDataset(datasetName)
|
|
432
|
+
if isDistributed or datasetName.endswith("/"):
|
|
433
|
+
isDistributed = True
|
|
434
|
+
tmpLog.debug(f" {datasetName} is distributed")
|
|
435
|
+
ddsList.add(datasetName)
|
|
436
|
+
# disable VP since distributed datasets triggers transfers
|
|
437
|
+
useVP = False
|
|
438
|
+
avoidVP = True
|
|
439
|
+
tmp_rse_list = ",".join(list_of_complete_replica_locations[datasetName])
|
|
440
|
+
is_distributed_map[datasetName] = isDistributed
|
|
441
|
+
tmpLog.info(
|
|
442
|
+
f"replica_availability disk:{complete_disk_ok[datasetName]} tape:{complete_tape_ok[datasetName]}, is_distributed:{isDistributed}, remote_readable:{can_be_remote_source[datasetName]}, rses={tmp_rse_list}"
|
|
443
|
+
)
|
|
444
|
+
# check if the data is available at somewhere
|
|
445
|
+
if not complete_disk_ok[datasetName] and not complete_tape_ok[datasetName] and isDistributed is not True:
|
|
446
|
+
err_msg = f"{datasetName} is "
|
|
447
|
+
if list_of_complete_replica_locations[datasetName]:
|
|
448
|
+
tmp_is_single = len(list_of_complete_replica_locations[datasetName]) == 1
|
|
449
|
+
err_msg += f"only complete at {tmp_rse_list} which "
|
|
450
|
+
err_msg += "is " if tmp_is_single else "are "
|
|
451
|
+
err_msg += "currently in downtime or offline. "
|
|
452
|
+
else:
|
|
453
|
+
err_msg += "incomplete at online storage. "
|
|
454
|
+
if not taskSpec.allow_incomplete_input():
|
|
455
|
+
tmpLog.error(err_msg)
|
|
456
|
+
taskSpec.setErrDiag(err_msg)
|
|
457
|
+
retVal = retTmpError
|
|
458
|
+
return retVal
|
|
459
|
+
else:
|
|
460
|
+
err_msg += "However, the task allows incomplete input."
|
|
461
|
+
tmpLog.info(err_msg)
|
|
462
|
+
|
|
463
|
+
# check if any input dataset is remotely unavailable
|
|
464
|
+
remote_source_available = True
|
|
465
|
+
remote_source_msg = ""
|
|
466
|
+
for tmp_dataset_name, tmp_ok in can_be_remote_source.items():
|
|
467
|
+
is_distributed = is_distributed_map.get(tmp_dataset_name, None)
|
|
468
|
+
if not tmp_ok and is_distributed is not True:
|
|
469
|
+
remote_source_msg = f"data locality cannot be ignored since {tmp_dataset_name} is unreadable over WAN"
|
|
470
|
+
remote_source_available = False
|
|
471
|
+
break
|
|
472
|
+
|
|
473
|
+
# two loops with/without data locality check
|
|
474
|
+
scan_site_list_loops = [(copy.copy(scanSiteList), True)]
|
|
475
|
+
to_ignore_data_loc = False
|
|
476
|
+
if len(inputChunk.getDatasets()) > 0:
|
|
477
|
+
nRealDS = 0
|
|
478
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
479
|
+
if not datasetSpec.isPseudo():
|
|
480
|
+
nRealDS += 1
|
|
481
|
+
task_prio_cutoff_for_input_data_motion = 2000
|
|
482
|
+
tmp_msg = "ignoring input data locality in the second loop due to "
|
|
483
|
+
if taskSpec.taskPriority >= task_prio_cutoff_for_input_data_motion:
|
|
484
|
+
to_ignore_data_loc = True
|
|
485
|
+
tmp_msg += f"high taskPriority {taskSpec.taskPriority} is larger than or equal to {task_prio_cutoff_for_input_data_motion}"
|
|
486
|
+
elif io_intensity_cutoff and taskSpec.ioIntensity and io_intensity_cutoff >= taskSpec.ioIntensity:
|
|
487
|
+
to_ignore_data_loc = True
|
|
488
|
+
tmp_msg += f"low ioIntensity {taskSpec.ioIntensity} is less than or equal to {io_intensity_key} ({io_intensity_cutoff})"
|
|
489
|
+
elif loc_check_timeout_val and taskSpec.frozenTime and naive_utcnow() - taskSpec.frozenTime > datetime.timedelta(hours=loc_check_timeout_val):
|
|
490
|
+
to_ignore_data_loc = True
|
|
491
|
+
tmp_msg += "check timeout (last successful cycle at {} was more than {} ({}hrs) ago)".format(
|
|
492
|
+
taskSpec.frozenTime, loc_check_timeout_key, loc_check_timeout_val
|
|
493
|
+
)
|
|
494
|
+
if not remote_source_available:
|
|
495
|
+
tmpLog.info(remote_source_msg)
|
|
496
|
+
elif to_ignore_data_loc:
|
|
497
|
+
tmpLog.info(tmp_msg)
|
|
498
|
+
scan_site_list_loops.append((copy.copy(scanSiteList), False))
|
|
499
|
+
elif taskSpec.taskPriority > 1000 or nRealDS > 1 or taskSpec.getNumSitesPerJob() > 0:
|
|
500
|
+
# add a loop without data locality check for high priority tasks, tasks with multiple input datasets, or tasks with job cloning
|
|
501
|
+
scan_site_list_loops.append((copy.copy(scanSiteList), False))
|
|
502
|
+
# element map
|
|
503
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
504
|
+
if datasetSpec.datasetName.endswith("/"):
|
|
505
|
+
file_list = [f.lfn for f in datasetSpec.Files]
|
|
506
|
+
element_map[datasetSpec.datasetName] = self.taskBufferIF.get_origin_datasets(taskSpec.jediTaskID, datasetSpec.datasetName, file_list)
|
|
507
|
+
|
|
508
|
+
retVal = None
|
|
509
|
+
checkDataLocality = False
|
|
510
|
+
scanSiteWoVP = []
|
|
511
|
+
summaryList = []
|
|
512
|
+
site_list_with_data = None
|
|
513
|
+
overall_site_list = set()
|
|
514
|
+
for i_loop, (scanSiteList, checkDataLocality) in enumerate(scan_site_list_loops):
|
|
515
|
+
useUnionLocality = False
|
|
516
|
+
self.init_summary_list("Job brokerage summary", f"data locality check: {checkDataLocality}", self.get_unified_sites(scanSiteList))
|
|
517
|
+
if checkDataLocality:
|
|
518
|
+
tmpLog.info("!!! look for candidates WITH data locality check")
|
|
519
|
+
else:
|
|
520
|
+
tmpLog.info("!!! look for candidates WITHOUT data locality check")
|
|
521
|
+
######################################
|
|
522
|
+
# selection for data availability
|
|
523
|
+
hasDDS = False
|
|
524
|
+
dataWeight = {}
|
|
525
|
+
remoteSourceList = {}
|
|
526
|
+
sites_in_nucleus = []
|
|
527
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
528
|
+
datasetSpec.reset_distributed()
|
|
529
|
+
|
|
530
|
+
if inputChunk.getDatasets() != [] and checkDataLocality:
|
|
531
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
532
|
+
oldScanUnifiedSiteList = self.get_unified_sites(oldScanSiteList)
|
|
533
|
+
|
|
534
|
+
if ddsList:
|
|
535
|
+
hasDDS = True
|
|
536
|
+
|
|
537
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
538
|
+
datasetName = datasetSpec.datasetName
|
|
539
|
+
if datasetName in ddsList:
|
|
540
|
+
datasetSpec.setDistributed()
|
|
541
|
+
|
|
542
|
+
# get the list of sites where data is available
|
|
543
|
+
scanSiteList = None
|
|
544
|
+
scanSiteListOnDisk = None
|
|
545
|
+
scanSiteListUnion = None
|
|
546
|
+
scanSiteListOnDiskUnion = None
|
|
547
|
+
scanSiteWoVpUnion = None
|
|
548
|
+
|
|
549
|
+
for datasetName, tmpDataSite in self.dataSiteMap.items():
|
|
550
|
+
# check if incomplete replica is allowed
|
|
551
|
+
if datasetName in ddsList:
|
|
552
|
+
useIncomplete = True
|
|
553
|
+
elif true_complete_disk_ok.get(datasetName) is False:
|
|
554
|
+
useIncomplete = True
|
|
555
|
+
else:
|
|
556
|
+
useIncomplete = False
|
|
557
|
+
# get sites where replica is available
|
|
558
|
+
tmpSiteList = AtlasBrokerUtils.getAnalSitesWithDataDisk(tmpDataSite, includeTape=True, use_incomplete=useIncomplete)
|
|
559
|
+
tmpDiskSiteList = AtlasBrokerUtils.getAnalSitesWithDataDisk(tmpDataSite, includeTape=False, use_vp=useVP, use_incomplete=useIncomplete)
|
|
560
|
+
tmpNonVpSiteList = AtlasBrokerUtils.getAnalSitesWithDataDisk(tmpDataSite, includeTape=True, use_vp=False, use_incomplete=useIncomplete)
|
|
561
|
+
# make weight map for local
|
|
562
|
+
for tmpSiteName in tmpSiteList:
|
|
563
|
+
if tmpSiteName not in dataWeight:
|
|
564
|
+
dataWeight[tmpSiteName] = 0
|
|
565
|
+
# give more weight to disk
|
|
566
|
+
if tmpSiteName in tmpDiskSiteList:
|
|
567
|
+
dataWeight[tmpSiteName] += 1
|
|
568
|
+
else:
|
|
569
|
+
dataWeight[tmpSiteName] += 0.001
|
|
570
|
+
|
|
571
|
+
# first list
|
|
572
|
+
if scanSiteList is None:
|
|
573
|
+
scanSiteList = []
|
|
574
|
+
for tmpSiteName in tmpSiteList:
|
|
575
|
+
if tmpSiteName not in oldScanUnifiedSiteList:
|
|
576
|
+
continue
|
|
577
|
+
if tmpSiteName not in scanSiteList:
|
|
578
|
+
scanSiteList.append(tmpSiteName)
|
|
579
|
+
scanSiteListOnDisk = set()
|
|
580
|
+
for tmpSiteName in tmpDiskSiteList:
|
|
581
|
+
if tmpSiteName not in oldScanUnifiedSiteList:
|
|
582
|
+
continue
|
|
583
|
+
scanSiteListOnDisk.add(tmpSiteName)
|
|
584
|
+
scanSiteWoVP = tmpNonVpSiteList
|
|
585
|
+
scanSiteListUnion = set(scanSiteList)
|
|
586
|
+
scanSiteListOnDiskUnion = set(scanSiteListOnDisk)
|
|
587
|
+
scanSiteWoVpUnion = set(scanSiteWoVP)
|
|
588
|
+
else:
|
|
589
|
+
# pickup sites which have all data
|
|
590
|
+
newScanList = []
|
|
591
|
+
for tmpSiteName in tmpSiteList:
|
|
592
|
+
if tmpSiteName in scanSiteList and tmpSiteName not in newScanList:
|
|
593
|
+
newScanList.append(tmpSiteName)
|
|
594
|
+
scanSiteListUnion.add(tmpSiteName)
|
|
595
|
+
scanSiteList = newScanList
|
|
596
|
+
tmpLog.debug(f"{datasetName} is available at {len(scanSiteList)} sites")
|
|
597
|
+
# pickup sites which have all data on DISK
|
|
598
|
+
newScanListOnDisk = set()
|
|
599
|
+
for tmpSiteName in tmpDiskSiteList:
|
|
600
|
+
if tmpSiteName in scanSiteListOnDisk:
|
|
601
|
+
newScanListOnDisk.add(tmpSiteName)
|
|
602
|
+
scanSiteListOnDiskUnion.add(tmpSiteName)
|
|
603
|
+
scanSiteListOnDisk = newScanListOnDisk
|
|
604
|
+
# get common elements
|
|
605
|
+
scanSiteWoVP = list(set(scanSiteWoVP).intersection(tmpNonVpSiteList))
|
|
606
|
+
scanSiteWoVpUnion = scanSiteWoVpUnion.union(tmpNonVpSiteList)
|
|
607
|
+
tmpLog.info(
|
|
608
|
+
f"{datasetName} is available at {len(scanSiteList)} sites ({len(scanSiteListOnDisk)} sites on DISK). complete disk replica: {true_complete_disk_ok[datasetName]}, complete tape replica: {complete_tape_ok[datasetName]}"
|
|
609
|
+
)
|
|
610
|
+
# check for preassigned
|
|
611
|
+
if sitePreAssigned:
|
|
612
|
+
if preassignedSite not in scanSiteList and preassignedSite not in scanSiteListUnion:
|
|
613
|
+
scanSiteList = []
|
|
614
|
+
tmpLog.info(f"data is unavailable locally or remotely at preassigned site {preassignedSite}")
|
|
615
|
+
elif preassignedSite not in scanSiteList:
|
|
616
|
+
scanSiteList = list(scanSiteListUnion)
|
|
617
|
+
elif len(scanSiteListOnDisk) > 0:
|
|
618
|
+
# use only disk sites
|
|
619
|
+
scanSiteList = list(scanSiteListOnDisk)
|
|
620
|
+
elif not scanSiteList and scanSiteListUnion:
|
|
621
|
+
tmpLog.info("use union list for data locality check since no site has all data")
|
|
622
|
+
if scanSiteListOnDiskUnion:
|
|
623
|
+
scanSiteList = list(scanSiteListOnDiskUnion)
|
|
624
|
+
elif scanSiteListUnion:
|
|
625
|
+
scanSiteList = list(scanSiteListUnion)
|
|
626
|
+
scanSiteWoVP = list(scanSiteWoVpUnion)
|
|
627
|
+
useUnionLocality = True
|
|
628
|
+
# disable VP since union locality triggers transfers to VP
|
|
629
|
+
avoidVP = True
|
|
630
|
+
scanSiteList = self.get_pseudo_sites(scanSiteList, oldScanSiteList)
|
|
631
|
+
# dump
|
|
632
|
+
for tmpSiteName in oldScanSiteList:
|
|
633
|
+
if tmpSiteName not in scanSiteList:
|
|
634
|
+
pass
|
|
635
|
+
sites_in_nucleus = copy.copy(scanSiteList)
|
|
636
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "input data check", tmpLog, {})
|
|
637
|
+
if not scanSiteList:
|
|
638
|
+
self.dump_summary(tmpLog)
|
|
639
|
+
tmpLog.error("no candidates")
|
|
640
|
+
retVal = retTmpError
|
|
641
|
+
continue
|
|
642
|
+
|
|
643
|
+
######################################
|
|
644
|
+
# selection for status
|
|
645
|
+
newScanSiteList = []
|
|
646
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
647
|
+
msg_map = {}
|
|
648
|
+
for tmpSiteName in scanSiteList:
|
|
649
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
650
|
+
# skip unified queues
|
|
651
|
+
if tmpSiteSpec.is_unified:
|
|
652
|
+
continue
|
|
653
|
+
# check site status
|
|
654
|
+
skipFlag = False
|
|
655
|
+
if tmpSiteSpec.status in ["offline"]:
|
|
656
|
+
skipFlag = True
|
|
657
|
+
elif tmpSiteSpec.status in ["brokeroff", "test"]:
|
|
658
|
+
if siteListPreAssigned:
|
|
659
|
+
pass
|
|
660
|
+
elif not sitePreAssigned:
|
|
661
|
+
skipFlag = True
|
|
662
|
+
elif preassignedSite not in [tmpSiteName, tmpSiteSpec.get_unified_name()]:
|
|
663
|
+
skipFlag = True
|
|
664
|
+
if not skipFlag:
|
|
665
|
+
newScanSiteList.append(tmpSiteName)
|
|
666
|
+
else:
|
|
667
|
+
tmp_unified_name = tmpSiteSpec.get_unified_name()
|
|
668
|
+
msg_map[tmp_unified_name] = f" skip site={tmp_unified_name} due to status={tmpSiteSpec.status} criteria=-status"
|
|
669
|
+
scanSiteList = newScanSiteList
|
|
670
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "status check", tmpLog, msg_map)
|
|
671
|
+
if not scanSiteList:
|
|
672
|
+
self.dump_summary(tmpLog)
|
|
673
|
+
tmpLog.error("no candidates")
|
|
674
|
+
retVal = retTmpError
|
|
675
|
+
continue
|
|
676
|
+
|
|
677
|
+
######################################
|
|
678
|
+
# selection for fairshare
|
|
679
|
+
if not sitePreAssigned:
|
|
680
|
+
newScanSiteList = []
|
|
681
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
682
|
+
msg_map = {}
|
|
683
|
+
for tmpSiteName in scanSiteList:
|
|
684
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
685
|
+
# check at the site
|
|
686
|
+
if AtlasBrokerUtils.hasZeroShare(tmpSiteSpec, taskSpec, inputChunk.isMerging, tmpLog):
|
|
687
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} due to zero share criteria=-zeroshare"
|
|
688
|
+
continue
|
|
689
|
+
newScanSiteList.append(tmpSiteName)
|
|
690
|
+
scanSiteList = newScanSiteList
|
|
691
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "zero share check", tmpLog, msg_map)
|
|
692
|
+
if not scanSiteList:
|
|
693
|
+
self.dump_summary(tmpLog)
|
|
694
|
+
tmpLog.error("no candidates")
|
|
695
|
+
retVal = retTmpError
|
|
696
|
+
continue
|
|
697
|
+
|
|
698
|
+
######################################
|
|
699
|
+
# selection for iointensity limits
|
|
700
|
+
# get default disk IO limit from GDP config
|
|
701
|
+
max_diskio_per_core_default = self.taskBufferIF.getConfigValue(COMPONENT, "MAX_DISKIO_DEFAULT", APP, VO)
|
|
702
|
+
if not max_diskio_per_core_default:
|
|
703
|
+
max_diskio_per_core_default = 10**10
|
|
704
|
+
|
|
705
|
+
# get the current disk IO usage per site
|
|
706
|
+
diskio_percore_usage = self.taskBufferIF.getAvgDiskIO_JEDI()
|
|
707
|
+
unified_site_list = self.get_unified_sites(scanSiteList)
|
|
708
|
+
newScanSiteList = []
|
|
709
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
710
|
+
msg_map = {}
|
|
711
|
+
for tmpSiteName in unified_site_list:
|
|
712
|
+
tmp_site_spec = self.siteMapper.getSite(tmpSiteName)
|
|
713
|
+
|
|
714
|
+
# measured diskIO at queue
|
|
715
|
+
diskio_usage_tmp = diskio_percore_usage.get(tmpSiteName, 0)
|
|
716
|
+
|
|
717
|
+
# figure out queue or default limit
|
|
718
|
+
if tmp_site_spec.maxDiskio and tmp_site_spec.maxDiskio > 0:
|
|
719
|
+
# there is a limit specified in AGIS
|
|
720
|
+
diskio_limit_tmp = tmp_site_spec.maxDiskio
|
|
721
|
+
else:
|
|
722
|
+
# we need to use the default value from GDP Config
|
|
723
|
+
diskio_limit_tmp = max_diskio_per_core_default
|
|
724
|
+
|
|
725
|
+
# normalize task diskIO by site corecount
|
|
726
|
+
diskio_task_tmp = taskSpec.diskIO
|
|
727
|
+
if taskSpec.diskIO is not None and taskSpec.coreCount not in [None, 0, 1] and tmp_site_spec.coreCount not in [None, 0]:
|
|
728
|
+
diskio_task_tmp = taskSpec.diskIO / tmp_site_spec.coreCount
|
|
729
|
+
|
|
730
|
+
try: # generate a log message parseable by logstash for monitoring
|
|
731
|
+
log_msg = f"diskIO measurements: site={tmpSiteName} jediTaskID={taskSpec.jediTaskID} "
|
|
732
|
+
if diskio_task_tmp is not None:
|
|
733
|
+
log_msg += f"diskIO_task={diskio_task_tmp:.2f} "
|
|
734
|
+
if diskio_usage_tmp is not None:
|
|
735
|
+
log_msg += f"diskIO_site_usage={diskio_usage_tmp:.2f} "
|
|
736
|
+
if diskio_limit_tmp is not None:
|
|
737
|
+
log_msg += f"diskIO_site_limit={diskio_limit_tmp:.2f} "
|
|
738
|
+
# tmpLog.info(log_msg)
|
|
739
|
+
except Exception:
|
|
740
|
+
tmpLog.debug("diskIO measurements: Error generating diskIO message")
|
|
741
|
+
|
|
742
|
+
# if the task has a diskIO defined, the queue is over the IO limit and the task IO is over the limit
|
|
743
|
+
if diskio_task_tmp and diskio_usage_tmp and diskio_limit_tmp and diskio_usage_tmp > diskio_limit_tmp and diskio_task_tmp > diskio_limit_tmp:
|
|
744
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to diskIO overload criteria=-diskIO"
|
|
745
|
+
continue
|
|
746
|
+
|
|
747
|
+
newScanSiteList.append(tmpSiteName)
|
|
748
|
+
|
|
749
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
750
|
+
|
|
751
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "diskIO check", tmpLog, msg_map)
|
|
752
|
+
if not scanSiteList:
|
|
753
|
+
self.dump_summary(tmpLog)
|
|
754
|
+
tmpLog.error("no candidates")
|
|
755
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
756
|
+
retVal = retTmpError
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
######################################
|
|
760
|
+
# selection for VP
|
|
761
|
+
if taskSpec.avoid_vp() or avoidVP or not checkDataLocality:
|
|
762
|
+
newScanSiteList = []
|
|
763
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
764
|
+
msg_map = {}
|
|
765
|
+
for tmpSiteName in scanSiteList:
|
|
766
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
767
|
+
if not tmpSiteSpec.use_vp(JobUtils.ANALY_PS):
|
|
768
|
+
newScanSiteList.append(tmpSiteName)
|
|
769
|
+
else:
|
|
770
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} to avoid VP"
|
|
771
|
+
scanSiteList = newScanSiteList
|
|
772
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "avoid VP queue check", tmpLog, msg_map)
|
|
773
|
+
if not scanSiteList:
|
|
774
|
+
self.dump_summary(tmpLog)
|
|
775
|
+
tmpLog.error("no candidates")
|
|
776
|
+
retVal = retTmpError
|
|
777
|
+
continue
|
|
778
|
+
######################################
|
|
779
|
+
# selection for MP
|
|
780
|
+
newScanSiteList = []
|
|
781
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
782
|
+
msg_map = {}
|
|
783
|
+
max_core_count = taskSpec.get_max_core_count()
|
|
784
|
+
for tmpSiteName in scanSiteList:
|
|
785
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
786
|
+
# check at the site
|
|
787
|
+
if useMP == "any" or (useMP == "only" and tmpSiteSpec.coreCount > 1) or (useMP == "unuse" and tmpSiteSpec.coreCount in [0, 1, None]):
|
|
788
|
+
if max_core_count and tmpSiteSpec.coreCount and tmpSiteSpec.coreCount > max_core_count:
|
|
789
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
790
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to larger core count "
|
|
791
|
+
f"site:{tmpSiteSpec.coreCount} than task_max={max_core_count} criteria=-max_cpucore"
|
|
792
|
+
)
|
|
793
|
+
else:
|
|
794
|
+
newScanSiteList.append(tmpSiteName)
|
|
795
|
+
else:
|
|
796
|
+
msg_map[tmpSiteSpec.get_unified_name()] = " skip site=%s due to core mismatch cores_site=%s <> cores_task=%s criteria=-cpucore" % (
|
|
797
|
+
tmpSiteSpec.get_unified_name(),
|
|
798
|
+
tmpSiteSpec.coreCount,
|
|
799
|
+
taskSpec.coreCount,
|
|
800
|
+
)
|
|
801
|
+
scanSiteList = newScanSiteList
|
|
802
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "CPU core check", tmpLog, msg_map)
|
|
803
|
+
if not scanSiteList:
|
|
804
|
+
self.dump_summary(tmpLog)
|
|
805
|
+
tmpLog.error("no candidates")
|
|
806
|
+
retVal = retTmpError
|
|
807
|
+
continue
|
|
808
|
+
######################################
|
|
809
|
+
# selection for GPU + architecture
|
|
810
|
+
newScanSiteList = []
|
|
811
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
812
|
+
msg_map = {}
|
|
813
|
+
jsonCheck = AtlasBrokerUtils.JsonSoftwareCheck(self.siteMapper, self.sw_map, self.architecture_level_map)
|
|
814
|
+
for tmpSiteName in scanSiteList:
|
|
815
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
816
|
+
if tmpSiteSpec.isGPU() and not taskSpec.is_hpo_workflow():
|
|
817
|
+
if taskSpec.get_sw_platform() in ["", None]:
|
|
818
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} since architecture is required for GPU queues"
|
|
819
|
+
continue
|
|
820
|
+
siteListWithCMTCONFIG = [tmpSiteSpec.get_unified_name()]
|
|
821
|
+
siteListWithCMTCONFIG, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
822
|
+
siteListWithCMTCONFIG, None, None, None, taskSpec.get_sw_platform(), False, True
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
if len(siteListWithCMTCONFIG) == 0:
|
|
826
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
827
|
+
f" skip site={tmpSiteSpec.get_unified_name()} since architecture={taskSpec.get_sw_platform()} is unavailable"
|
|
828
|
+
)
|
|
829
|
+
continue
|
|
830
|
+
|
|
831
|
+
newScanSiteList.append(tmpSiteName)
|
|
832
|
+
scanSiteList = newScanSiteList
|
|
833
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "architecture check", tmpLog, msg_map)
|
|
834
|
+
if not scanSiteList:
|
|
835
|
+
self.dump_summary(tmpLog)
|
|
836
|
+
tmpLog.error("no candidates")
|
|
837
|
+
retVal = retTmpError
|
|
838
|
+
continue
|
|
839
|
+
######################################
|
|
840
|
+
# selection for closed
|
|
841
|
+
if not sitePreAssigned and not inputChunk.isMerging:
|
|
842
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
843
|
+
newScanSiteList = []
|
|
844
|
+
msg_map = {}
|
|
845
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
846
|
+
if tmpSiteName in failureCounts and "closed" in failureCounts[tmpSiteName]:
|
|
847
|
+
nClosed = failureCounts[tmpSiteName]["closed"]
|
|
848
|
+
if nClosed > 0:
|
|
849
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to n_closed={nClosed} criteria=-closed"
|
|
850
|
+
continue
|
|
851
|
+
newScanSiteList.append(tmpSiteName)
|
|
852
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
853
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "too many closed check", tmpLog, msg_map)
|
|
854
|
+
if not scanSiteList:
|
|
855
|
+
self.dump_summary(tmpLog)
|
|
856
|
+
tmpLog.error("no candidates")
|
|
857
|
+
retVal = retTmpError
|
|
858
|
+
continue
|
|
859
|
+
######################################
|
|
860
|
+
# selection for release
|
|
861
|
+
cmt_config = taskSpec.get_sw_platform()
|
|
862
|
+
is_regexp_cmt_config = False
|
|
863
|
+
if cmt_config:
|
|
864
|
+
if re.match(cmt_config, cmt_config) is None:
|
|
865
|
+
is_regexp_cmt_config = True
|
|
866
|
+
base_platform = taskSpec.get_base_platform()
|
|
867
|
+
resolved_platforms = {}
|
|
868
|
+
|
|
869
|
+
host_cpu_spec = taskSpec.get_host_cpu_spec()
|
|
870
|
+
host_cpu_pref = taskSpec.get_host_cpu_preference()
|
|
871
|
+
host_gpu_spec = taskSpec.get_host_gpu_spec()
|
|
872
|
+
|
|
873
|
+
if not sitePreAssigned:
|
|
874
|
+
unified_site_list = self.get_unified_sites(scanSiteList)
|
|
875
|
+
if taskSpec.transHome is not None:
|
|
876
|
+
transHome = taskSpec.transHome
|
|
877
|
+
else:
|
|
878
|
+
transHome = ""
|
|
879
|
+
# remove AnalysisTransforms-
|
|
880
|
+
transHome = re.sub("^[^-]+-*", "", transHome)
|
|
881
|
+
transHome = re.sub("_", "-", transHome)
|
|
882
|
+
if (
|
|
883
|
+
re.search("rel_\d+(\n|$)", transHome) is None
|
|
884
|
+
and taskSpec.transHome not in ["AnalysisTransforms", None]
|
|
885
|
+
and re.search("\d{4}-\d{2}-\d{2}T\d{4}$", transHome) is None
|
|
886
|
+
and re.search("-\d+\.\d+\.\d+$", transHome) is None
|
|
887
|
+
):
|
|
888
|
+
# cache is checked
|
|
889
|
+
siteListWithSW, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
890
|
+
unified_site_list,
|
|
891
|
+
"atlas",
|
|
892
|
+
transHome.split("-")[0],
|
|
893
|
+
transHome.split("-")[1],
|
|
894
|
+
taskSpec.get_sw_platform(),
|
|
895
|
+
False,
|
|
896
|
+
False,
|
|
897
|
+
container_name=taskSpec.container_name,
|
|
898
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
899
|
+
host_cpu_specs=host_cpu_spec,
|
|
900
|
+
host_cpu_pref=host_cpu_pref,
|
|
901
|
+
host_gpu_spec=host_gpu_spec,
|
|
902
|
+
log_stream=tmpLog,
|
|
903
|
+
)
|
|
904
|
+
sitesAuto = copy.copy(siteListWithSW)
|
|
905
|
+
|
|
906
|
+
elif (transHome == "" and taskSpec.transUses is not None) or (
|
|
907
|
+
re.search("-\d+\.\d+\.\d+$", transHome) is not None and (taskSpec.transUses is None or re.search("-\d+\.\d+$", taskSpec.transUses) is None)
|
|
908
|
+
):
|
|
909
|
+
siteListWithSW = []
|
|
910
|
+
sitesNoJsonCheck = unified_site_list
|
|
911
|
+
# remove Atlas-
|
|
912
|
+
if taskSpec.transUses is not None:
|
|
913
|
+
transUses = taskSpec.transUses.split("-")[-1]
|
|
914
|
+
else:
|
|
915
|
+
transUses = None
|
|
916
|
+
if transUses is not None:
|
|
917
|
+
# release is checked
|
|
918
|
+
tmpSiteListWithSW, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
919
|
+
unified_site_list,
|
|
920
|
+
"atlas",
|
|
921
|
+
"AtlasOffline",
|
|
922
|
+
transUses,
|
|
923
|
+
taskSpec.get_sw_platform(),
|
|
924
|
+
False,
|
|
925
|
+
False,
|
|
926
|
+
container_name=taskSpec.container_name,
|
|
927
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
928
|
+
host_cpu_specs=host_cpu_spec,
|
|
929
|
+
host_gpu_spec=host_gpu_spec,
|
|
930
|
+
log_stream=tmpLog,
|
|
931
|
+
)
|
|
932
|
+
siteListWithSW += tmpSiteListWithSW
|
|
933
|
+
if len(transHome.split("-")) == 2:
|
|
934
|
+
tmpSiteListWithSW, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
935
|
+
sitesNoJsonCheck,
|
|
936
|
+
"atlas",
|
|
937
|
+
transHome.split("-")[0],
|
|
938
|
+
transHome.split("-")[1],
|
|
939
|
+
taskSpec.get_sw_platform(),
|
|
940
|
+
False,
|
|
941
|
+
False,
|
|
942
|
+
container_name=taskSpec.container_name,
|
|
943
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
944
|
+
host_cpu_specs=host_cpu_spec,
|
|
945
|
+
host_gpu_spec=host_gpu_spec,
|
|
946
|
+
log_stream=tmpLog,
|
|
947
|
+
)
|
|
948
|
+
siteListWithSW += tmpSiteListWithSW
|
|
949
|
+
sitesAuto = copy.copy(siteListWithSW)
|
|
950
|
+
|
|
951
|
+
else:
|
|
952
|
+
# nightlies or standalone uses only AUTO
|
|
953
|
+
if taskSpec.transHome is not None:
|
|
954
|
+
# CVMFS check for nightlies
|
|
955
|
+
siteListWithSW, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
956
|
+
unified_site_list,
|
|
957
|
+
"nightlies",
|
|
958
|
+
None,
|
|
959
|
+
None,
|
|
960
|
+
taskSpec.get_sw_platform(),
|
|
961
|
+
True,
|
|
962
|
+
False,
|
|
963
|
+
container_name=taskSpec.container_name,
|
|
964
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
965
|
+
host_cpu_specs=host_cpu_spec,
|
|
966
|
+
host_gpu_spec=host_gpu_spec,
|
|
967
|
+
log_stream=tmpLog,
|
|
968
|
+
)
|
|
969
|
+
sitesAuto = copy.copy(siteListWithSW)
|
|
970
|
+
|
|
971
|
+
else:
|
|
972
|
+
# no CVMFS check for standalone SW
|
|
973
|
+
siteListWithSW, sitesNoJsonCheck, _ = jsonCheck.check(
|
|
974
|
+
unified_site_list,
|
|
975
|
+
None,
|
|
976
|
+
None,
|
|
977
|
+
None,
|
|
978
|
+
taskSpec.get_sw_platform(),
|
|
979
|
+
False,
|
|
980
|
+
True,
|
|
981
|
+
container_name=taskSpec.container_name,
|
|
982
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
983
|
+
host_cpu_specs=host_cpu_spec,
|
|
984
|
+
host_gpu_spec=host_gpu_spec,
|
|
985
|
+
log_stream=tmpLog,
|
|
986
|
+
)
|
|
987
|
+
sitesAuto = copy.copy(siteListWithSW)
|
|
988
|
+
|
|
989
|
+
newScanSiteList = []
|
|
990
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
991
|
+
sitesAny = []
|
|
992
|
+
msg_map = {}
|
|
993
|
+
for tmpSiteName in unified_site_list:
|
|
994
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
995
|
+
if cmt_config:
|
|
996
|
+
platforms = AtlasBrokerUtils.resolve_cmt_config(tmpSiteName, cmt_config, base_platform, self.sw_map)
|
|
997
|
+
if platforms:
|
|
998
|
+
resolved_platforms[tmpSiteName] = platforms
|
|
999
|
+
if tmpSiteName in siteListWithSW:
|
|
1000
|
+
# passed
|
|
1001
|
+
if not is_regexp_cmt_config or tmpSiteName in resolved_platforms:
|
|
1002
|
+
newScanSiteList.append(tmpSiteName)
|
|
1003
|
+
else:
|
|
1004
|
+
# cmtconfig is not resolved
|
|
1005
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to unresolved regexp in cmtconfig={cmt_config} criteria=-regexpcmtconfig"
|
|
1006
|
+
elif host_cpu_spec is None and host_gpu_spec is None and tmpSiteSpec.releases == ["ANY"]:
|
|
1007
|
+
# release check is disabled or release is available
|
|
1008
|
+
newScanSiteList.append(tmpSiteName)
|
|
1009
|
+
sitesAny.append(tmpSiteName)
|
|
1010
|
+
else:
|
|
1011
|
+
# release is unavailable
|
|
1012
|
+
msg_map[tmpSiteName] = (
|
|
1013
|
+
f" skip site={tmpSiteName} due to missing SW cache={taskSpec.transHome}:{taskSpec.get_sw_platform()} container_name='{taskSpec.container_name}' "
|
|
1014
|
+
f"or irrelevant HW cpu={str(host_cpu_spec)} gpu={str(host_gpu_spec)} criteria=-cache"
|
|
1015
|
+
)
|
|
1016
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1017
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "SW/HW check", tmpLog, msg_map)
|
|
1018
|
+
tmpLog.info(f" {len(sitesAuto)} with AUTO, {len(sitesAny)} with ANY")
|
|
1019
|
+
if not scanSiteList:
|
|
1020
|
+
self.dump_summary(tmpLog)
|
|
1021
|
+
tmpLog.error("no candidates")
|
|
1022
|
+
retVal = retTmpError
|
|
1023
|
+
continue
|
|
1024
|
+
######################################
|
|
1025
|
+
# selection for memory
|
|
1026
|
+
origMinRamCount = inputChunk.getMaxRamCount()
|
|
1027
|
+
if origMinRamCount not in [0, None]:
|
|
1028
|
+
newScanSiteList = []
|
|
1029
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1030
|
+
msg_map = {}
|
|
1031
|
+
for tmpSiteName in scanSiteList:
|
|
1032
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1033
|
+
# scale RAM by nCores
|
|
1034
|
+
minRamCount = origMinRamCount
|
|
1035
|
+
if taskSpec.ramPerCore() and not inputChunk.isMerging:
|
|
1036
|
+
if tmpSiteSpec.coreCount not in [None, 0]:
|
|
1037
|
+
minRamCount = origMinRamCount * tmpSiteSpec.coreCount
|
|
1038
|
+
minRamCount = JobUtils.compensate_ram_count(minRamCount)
|
|
1039
|
+
# site max memory requirement
|
|
1040
|
+
site_maxmemory = 0
|
|
1041
|
+
if tmpSiteSpec.maxrss not in [0, None]:
|
|
1042
|
+
site_maxmemory = tmpSiteSpec.maxrss
|
|
1043
|
+
if site_maxmemory not in [0, None] and minRamCount != 0 and minRamCount > site_maxmemory:
|
|
1044
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
1045
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to insufficient RAM less than job's core-scaled requirement {minRamCount} MB criteria=-lowmemory"
|
|
1046
|
+
)
|
|
1047
|
+
continue
|
|
1048
|
+
# site min memory requirement
|
|
1049
|
+
site_minmemory = 0
|
|
1050
|
+
if tmpSiteSpec.minrss not in [0, None]:
|
|
1051
|
+
site_minmemory = tmpSiteSpec.minrss
|
|
1052
|
+
if site_minmemory not in [0, None] and minRamCount != 0 and minRamCount < site_minmemory:
|
|
1053
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
1054
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to RAM lower limit greater than job's core-scaled requirement {minRamCount} MB criteria=-highmemory"
|
|
1055
|
+
)
|
|
1056
|
+
continue
|
|
1057
|
+
newScanSiteList.append(tmpSiteName)
|
|
1058
|
+
scanSiteList = newScanSiteList
|
|
1059
|
+
ramUnit = taskSpec.ramUnit
|
|
1060
|
+
if ramUnit is None:
|
|
1061
|
+
ramUnit = "MB"
|
|
1062
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "memory check", tmpLog, msg_map)
|
|
1063
|
+
if not scanSiteList:
|
|
1064
|
+
self.dump_summary(tmpLog)
|
|
1065
|
+
tmpLog.error("no candidates")
|
|
1066
|
+
retVal = retTmpError
|
|
1067
|
+
continue
|
|
1068
|
+
######################################
|
|
1069
|
+
# selection for scratch disk
|
|
1070
|
+
tmpMaxAtomSize = inputChunk.getMaxAtomSize()
|
|
1071
|
+
if not inputChunk.isMerging:
|
|
1072
|
+
tmpEffAtomSize = inputChunk.getMaxAtomSize(effectiveSize=True)
|
|
1073
|
+
tmpOutDiskSize = taskSpec.getOutDiskSize()
|
|
1074
|
+
tmpWorkDiskSize = taskSpec.getWorkDiskSize()
|
|
1075
|
+
minDiskCountS = tmpOutDiskSize * tmpEffAtomSize + tmpWorkDiskSize + tmpMaxAtomSize
|
|
1076
|
+
minDiskCountS = minDiskCountS // 1024 // 1024
|
|
1077
|
+
maxSizePerJob = taskSpec.getMaxSizePerJob()
|
|
1078
|
+
if maxSizePerJob is None:
|
|
1079
|
+
maxSizePerJob = None
|
|
1080
|
+
else:
|
|
1081
|
+
maxSizePerJob //= 1024 * 1024
|
|
1082
|
+
# size for direct IO sites
|
|
1083
|
+
minDiskCountR = tmpOutDiskSize * tmpEffAtomSize + tmpWorkDiskSize
|
|
1084
|
+
minDiskCountR = minDiskCountR // 1024 // 1024
|
|
1085
|
+
tmpLog.info(f"maxAtomSize={tmpMaxAtomSize} effectiveAtomSize={tmpEffAtomSize} outDiskCount={tmpOutDiskSize} workDiskSize={tmpWorkDiskSize}")
|
|
1086
|
+
else:
|
|
1087
|
+
maxSizePerJob = None
|
|
1088
|
+
minDiskCountS = 2 * tmpMaxAtomSize // 1024 // 1024
|
|
1089
|
+
minDiskCountR = "NA"
|
|
1090
|
+
tmpLog.info(f"minDiskCountScratch={minDiskCountS} minDiskCountRemote={minDiskCountR} nGBPerJobInMB={maxSizePerJob}")
|
|
1091
|
+
newScanSiteList = []
|
|
1092
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1093
|
+
msg_map = {}
|
|
1094
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1095
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1096
|
+
# check at the site
|
|
1097
|
+
if tmpSiteSpec.maxwdir:
|
|
1098
|
+
if CoreUtils.use_direct_io_for_job(taskSpec, tmpSiteSpec, inputChunk):
|
|
1099
|
+
minDiskCount = minDiskCountR
|
|
1100
|
+
if maxSizePerJob is not None and not taskSpec.useLocalIO():
|
|
1101
|
+
tmpMinDiskCountR = tmpOutDiskSize * maxSizePerJob + tmpWorkDiskSize
|
|
1102
|
+
tmpMinDiskCountR /= 1024 * 1024
|
|
1103
|
+
if tmpMinDiskCountR > minDiskCount:
|
|
1104
|
+
minDiskCount = tmpMinDiskCountR
|
|
1105
|
+
else:
|
|
1106
|
+
minDiskCount = minDiskCountS
|
|
1107
|
+
if maxSizePerJob is not None and maxSizePerJob > minDiskCount:
|
|
1108
|
+
minDiskCount = maxSizePerJob
|
|
1109
|
+
|
|
1110
|
+
# get site and task corecount to scale maxwdir
|
|
1111
|
+
if tmpSiteSpec.coreCount in [None, 0, 1]:
|
|
1112
|
+
site_cc = 1
|
|
1113
|
+
else:
|
|
1114
|
+
site_cc = tmpSiteSpec.coreCount
|
|
1115
|
+
|
|
1116
|
+
if taskSpec.coreCount in [None, 0, 1]:
|
|
1117
|
+
task_cc = 1
|
|
1118
|
+
else:
|
|
1119
|
+
task_cc = site_cc
|
|
1120
|
+
|
|
1121
|
+
maxwdir_scaled = tmpSiteSpec.maxwdir * task_cc / site_cc
|
|
1122
|
+
|
|
1123
|
+
if minDiskCount > maxwdir_scaled:
|
|
1124
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to small scratch disk={maxwdir_scaled} < {minDiskCount} criteria=-disk"
|
|
1125
|
+
continue
|
|
1126
|
+
newMaxwdir[tmpSiteName] = maxwdir_scaled
|
|
1127
|
+
newScanSiteList.append(tmpSiteName)
|
|
1128
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1129
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "scratch disk check", tmpLog, msg_map)
|
|
1130
|
+
if not scanSiteList:
|
|
1131
|
+
self.dump_summary(tmpLog)
|
|
1132
|
+
tmpLog.error("no candidates")
|
|
1133
|
+
retVal = retTmpError
|
|
1134
|
+
continue
|
|
1135
|
+
######################################
|
|
1136
|
+
# selection for available space in SE
|
|
1137
|
+
newScanSiteList = []
|
|
1138
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1139
|
+
msg_map = {}
|
|
1140
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1141
|
+
# check endpoint
|
|
1142
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1143
|
+
scope_input, scope_output = select_scope(tmpSiteSpec, JobUtils.ANALY_PS, JobUtils.ANALY_PS)
|
|
1144
|
+
if scope_output not in tmpSiteSpec.ddm_endpoints_output:
|
|
1145
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} since {scope_output} output endpoint undefined criteria=-disk"
|
|
1146
|
+
continue
|
|
1147
|
+
tmp_output_endpoint = tmpSiteSpec.ddm_endpoints_output[scope_output].getEndPoint(tmpSiteSpec.ddm_output[scope_output])
|
|
1148
|
+
if tmp_output_endpoint is not None:
|
|
1149
|
+
# free space must be >= 200GB
|
|
1150
|
+
diskThreshold = 200
|
|
1151
|
+
tmpSpaceSize = 0
|
|
1152
|
+
if tmp_output_endpoint["space_expired"] is not None:
|
|
1153
|
+
tmpSpaceSize += tmp_output_endpoint["space_expired"]
|
|
1154
|
+
if tmp_output_endpoint["space_free"] is not None:
|
|
1155
|
+
tmpSpaceSize += tmp_output_endpoint["space_free"]
|
|
1156
|
+
if (
|
|
1157
|
+
tmpSpaceSize < diskThreshold and "skip_RSE_check" not in tmpSiteSpec.catchall
|
|
1158
|
+
): # skip_RSE_check: exceptional bypass of RSEs without storage reporting
|
|
1159
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to disk shortage in SE {tmpSpaceSize} < {diskThreshold}GB criteria=-disk"
|
|
1160
|
+
continue
|
|
1161
|
+
# check if blacklisted
|
|
1162
|
+
tmp_msg = AtlasBrokerUtils.check_endpoints_with_blacklist(tmpSiteSpec, scope_input, scope_output, sites_in_nucleus, remote_source_available)
|
|
1163
|
+
if tmp_msg is not None:
|
|
1164
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} since {tmpSiteSpec.ddm_output[scope_output]} is blacklisted in DDM criteria=-blacklist"
|
|
1165
|
+
continue
|
|
1166
|
+
# local quota
|
|
1167
|
+
if tmpSiteSpec.ddm_output[scope_output] in endpoints_over_local_quota:
|
|
1168
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} since {tmpSiteSpec.ddm_output[scope_output]} is over local quota criteria=-local_quota"
|
|
1169
|
+
continue
|
|
1170
|
+
newScanSiteList.append(tmpSiteName)
|
|
1171
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1172
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "storage space check", tmpLog, msg_map)
|
|
1173
|
+
if not scanSiteList:
|
|
1174
|
+
self.dump_summary(tmpLog)
|
|
1175
|
+
tmpLog.error("no candidates")
|
|
1176
|
+
retVal = retTmpError
|
|
1177
|
+
continue
|
|
1178
|
+
######################################
|
|
1179
|
+
# selection for walltime
|
|
1180
|
+
if not taskSpec.useHS06():
|
|
1181
|
+
tmpMaxAtomSize = inputChunk.getMaxAtomSize(effectiveSize=True)
|
|
1182
|
+
if taskSpec.walltime is not None:
|
|
1183
|
+
minWalltime = taskSpec.walltime * tmpMaxAtomSize
|
|
1184
|
+
else:
|
|
1185
|
+
minWalltime = None
|
|
1186
|
+
strMinWalltime = f"walltime*inputSize={taskSpec.walltime}*{tmpMaxAtomSize}"
|
|
1187
|
+
else:
|
|
1188
|
+
tmpMaxAtomSize = inputChunk.getMaxAtomSize(getNumEvents=True)
|
|
1189
|
+
if taskSpec.getCpuTime() is not None:
|
|
1190
|
+
minWalltime = taskSpec.getCpuTime() * tmpMaxAtomSize
|
|
1191
|
+
else:
|
|
1192
|
+
minWalltime = None
|
|
1193
|
+
strMinWalltime = f"cpuTime*nEventsPerJob={taskSpec.getCpuTime()}*{tmpMaxAtomSize}"
|
|
1194
|
+
if minWalltime and minWalltime > 0 and not inputChunk.isMerging:
|
|
1195
|
+
newScanSiteList = []
|
|
1196
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1197
|
+
msg_map = {}
|
|
1198
|
+
for tmpSiteName in scanSiteList:
|
|
1199
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1200
|
+
siteMaxTime = tmpSiteSpec.maxtime
|
|
1201
|
+
origSiteMaxTime = siteMaxTime
|
|
1202
|
+
# check max walltime at the site
|
|
1203
|
+
tmpSiteStr = f"{siteMaxTime}"
|
|
1204
|
+
if taskSpec.useHS06():
|
|
1205
|
+
oldSiteMaxTime = siteMaxTime
|
|
1206
|
+
siteMaxTime -= taskSpec.baseWalltime
|
|
1207
|
+
tmpSiteStr = f"({oldSiteMaxTime}-{taskSpec.baseWalltime})"
|
|
1208
|
+
if siteMaxTime not in [None, 0] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
1209
|
+
siteMaxTime *= tmpSiteSpec.coreCount
|
|
1210
|
+
tmpSiteStr += f"*{tmpSiteSpec.coreCount}"
|
|
1211
|
+
if taskSpec.useHS06():
|
|
1212
|
+
if siteMaxTime not in [None, 0]:
|
|
1213
|
+
siteMaxTime *= tmpSiteSpec.corepower
|
|
1214
|
+
tmpSiteStr += f"*{tmpSiteSpec.corepower}"
|
|
1215
|
+
siteMaxTime *= float(taskSpec.cpuEfficiency) / 100.0
|
|
1216
|
+
siteMaxTime = int(siteMaxTime)
|
|
1217
|
+
tmpSiteStr += f"*{taskSpec.cpuEfficiency}%"
|
|
1218
|
+
if origSiteMaxTime != 0 and minWalltime and minWalltime > siteMaxTime:
|
|
1219
|
+
tmpMsg = f" skip site={tmpSiteName} due to short site walltime {tmpSiteStr} (site upper limit) less than {strMinWalltime} "
|
|
1220
|
+
tmpMsg += "criteria=-shortwalltime"
|
|
1221
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmpMsg
|
|
1222
|
+
continue
|
|
1223
|
+
# check min walltime at the site
|
|
1224
|
+
siteMinTime = tmpSiteSpec.mintime
|
|
1225
|
+
origSiteMinTime = siteMinTime
|
|
1226
|
+
tmpSiteStr = f"{siteMinTime}"
|
|
1227
|
+
if taskSpec.useHS06():
|
|
1228
|
+
oldSiteMinTime = siteMinTime
|
|
1229
|
+
siteMinTime -= taskSpec.baseWalltime
|
|
1230
|
+
tmpSiteStr = f"({oldSiteMinTime}-{taskSpec.baseWalltime})"
|
|
1231
|
+
if siteMinTime not in [None, 0] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
1232
|
+
siteMinTime *= tmpSiteSpec.coreCount
|
|
1233
|
+
tmpSiteStr += f"*{tmpSiteSpec.coreCount}"
|
|
1234
|
+
if taskSpec.useHS06():
|
|
1235
|
+
if siteMinTime not in [None, 0]:
|
|
1236
|
+
siteMinTime *= tmpSiteSpec.corepower
|
|
1237
|
+
tmpSiteStr += f"*{tmpSiteSpec.corepower}"
|
|
1238
|
+
siteMinTime *= float(taskSpec.cpuEfficiency) / 100.0
|
|
1239
|
+
siteMinTime = int(siteMinTime)
|
|
1240
|
+
tmpSiteStr += f"*{taskSpec.cpuEfficiency}%"
|
|
1241
|
+
if origSiteMinTime != 0 and (minWalltime is None or minWalltime < siteMinTime):
|
|
1242
|
+
tmpMsg = f" skip site {tmpSiteName} due to short job walltime {tmpSiteStr} (site lower limit) greater than {strMinWalltime} "
|
|
1243
|
+
tmpMsg += "criteria=-longwalltime"
|
|
1244
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmpMsg
|
|
1245
|
+
continue
|
|
1246
|
+
newScanSiteList.append(tmpSiteName)
|
|
1247
|
+
scanSiteList = newScanSiteList
|
|
1248
|
+
if not taskSpec.useHS06():
|
|
1249
|
+
tmp_unit = taskSpec.walltimeUnit + "PerMB" if taskSpec.walltimeUnit else "kSI2ksecondsPerMB"
|
|
1250
|
+
tmpLog.info(f"walltime check with {strMinWalltime}({tmp_unit})")
|
|
1251
|
+
else:
|
|
1252
|
+
tmpLog.info(f"walltime check with {strMinWalltime}({taskSpec.cpuTimeUnit}*nEventsPerJob)")
|
|
1253
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "walltime check", tmpLog, msg_map)
|
|
1254
|
+
if not scanSiteList:
|
|
1255
|
+
self.dump_summary(tmpLog)
|
|
1256
|
+
tmpLog.error("no candidates")
|
|
1257
|
+
retVal = retTmpError
|
|
1258
|
+
continue
|
|
1259
|
+
|
|
1260
|
+
######################################
|
|
1261
|
+
# selection for zero walltime: scouts, merges, and walltime-undefined jobs
|
|
1262
|
+
# must only go to sites with at least 24hr*10HS06s of available walltime
|
|
1263
|
+
if (
|
|
1264
|
+
(not sitePreAssigned and inputChunk.useScout())
|
|
1265
|
+
or (not taskSpec.walltime and not taskSpec.walltimeUnit and not taskSpec.cpuTimeUnit)
|
|
1266
|
+
or (not taskSpec.getCpuTime() and taskSpec.cpuTimeUnit)
|
|
1267
|
+
):
|
|
1268
|
+
newScanSiteList = []
|
|
1269
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1270
|
+
msg_map = {}
|
|
1271
|
+
for tmpSiteName in scanSiteList:
|
|
1272
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1273
|
+
siteMaxTime = tmpSiteSpec.maxtime
|
|
1274
|
+
tmpSiteStr = f"{siteMaxTime}"
|
|
1275
|
+
if taskSpec.useHS06():
|
|
1276
|
+
oldSiteMaxTime = siteMaxTime
|
|
1277
|
+
siteMaxTime -= taskSpec.baseWalltime
|
|
1278
|
+
tmpSiteStr = f"({oldSiteMaxTime}-{taskSpec.baseWalltime})"
|
|
1279
|
+
if siteMaxTime not in [None, 0] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
1280
|
+
siteMaxTime *= tmpSiteSpec.coreCount
|
|
1281
|
+
tmpSiteStr += f"*{tmpSiteSpec.coreCount}"
|
|
1282
|
+
if taskSpec.useHS06():
|
|
1283
|
+
if siteMaxTime not in [None, 0]:
|
|
1284
|
+
siteMaxTime *= tmpSiteSpec.corepower
|
|
1285
|
+
tmpSiteStr += f"*{tmpSiteSpec.corepower}"
|
|
1286
|
+
siteMaxTime *= float(taskSpec.cpuEfficiency) / 100.0
|
|
1287
|
+
siteMaxTime = int(siteMaxTime)
|
|
1288
|
+
tmpSiteStr += f"*{taskSpec.cpuEfficiency}%"
|
|
1289
|
+
minTimeForZeroWalltime = 24 * 60 * 60 * 10
|
|
1290
|
+
str_minTimeForZeroWalltime = "24hr*10HS06s"
|
|
1291
|
+
if tmpSiteSpec.coreCount not in [None, 0]:
|
|
1292
|
+
minTimeForZeroWalltime *= tmpSiteSpec.coreCount
|
|
1293
|
+
str_minTimeForZeroWalltime += f"*{tmpSiteSpec.coreCount}cores"
|
|
1294
|
+
if siteMaxTime != 0 and siteMaxTime < minTimeForZeroWalltime:
|
|
1295
|
+
tmpMsg = f" skip site={tmpSiteName} due to site walltime {tmpSiteStr} (site upper limit) insufficient "
|
|
1296
|
+
if inputChunk.useScout():
|
|
1297
|
+
tmpMsg += f"for scouts ({str_minTimeForZeroWalltime} at least) "
|
|
1298
|
+
tmpMsg += "criteria=-scoutwalltime"
|
|
1299
|
+
else:
|
|
1300
|
+
tmpMsg += f"for zero walltime ({str_minTimeForZeroWalltime} at least) "
|
|
1301
|
+
tmpMsg += "criteria=-zerowalltime"
|
|
1302
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmpMsg
|
|
1303
|
+
continue
|
|
1304
|
+
newScanSiteList.append(tmpSiteName)
|
|
1305
|
+
scanSiteList = newScanSiteList
|
|
1306
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "zero walltime check", tmpLog, msg_map)
|
|
1307
|
+
if not scanSiteList:
|
|
1308
|
+
self.dump_summary(tmpLog)
|
|
1309
|
+
tmpLog.error("no candidates")
|
|
1310
|
+
retVal = retTmpError
|
|
1311
|
+
continue
|
|
1312
|
+
|
|
1313
|
+
######################################
|
|
1314
|
+
# selection for nPilot
|
|
1315
|
+
nWNmap = self.taskBufferIF.getCurrentSiteData()
|
|
1316
|
+
nPilotMap = {}
|
|
1317
|
+
newScanSiteList = []
|
|
1318
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1319
|
+
msg_map = {}
|
|
1320
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1321
|
+
# check at the site
|
|
1322
|
+
nPilot = 0
|
|
1323
|
+
if tmpSiteName in nWNmap:
|
|
1324
|
+
nPilot = nWNmap[tmpSiteName]["getJob"] + nWNmap[tmpSiteName]["updateJob"]
|
|
1325
|
+
if nPilot == 0 and taskSpec.prodSourceLabel not in ["test"]:
|
|
1326
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to no pilot criteria=-nopilot"
|
|
1327
|
+
if not self.testMode:
|
|
1328
|
+
continue
|
|
1329
|
+
newScanSiteList.append(tmpSiteName)
|
|
1330
|
+
nPilotMap[tmpSiteName] = nPilot
|
|
1331
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1332
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "pilot activity check", tmpLog, msg_map)
|
|
1333
|
+
if not scanSiteList:
|
|
1334
|
+
self.dump_summary(tmpLog)
|
|
1335
|
+
tmpLog.error("no candidates")
|
|
1336
|
+
retVal = retTmpError
|
|
1337
|
+
continue
|
|
1338
|
+
|
|
1339
|
+
######################################
|
|
1340
|
+
# check inclusion and exclusion
|
|
1341
|
+
newScanSiteList = []
|
|
1342
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1343
|
+
sitesForANY = []
|
|
1344
|
+
msg_map = {}
|
|
1345
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1346
|
+
autoSite = False
|
|
1347
|
+
# check exclusion
|
|
1348
|
+
if AtlasBrokerUtils.isMatched(tmpSiteName, excludeList):
|
|
1349
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} excluded criteria=-excluded"
|
|
1350
|
+
continue
|
|
1351
|
+
|
|
1352
|
+
# check inclusion
|
|
1353
|
+
if includeList is not None and not AtlasBrokerUtils.isMatched(tmpSiteName, includeList):
|
|
1354
|
+
if "AUTO" in includeList:
|
|
1355
|
+
autoSite = True
|
|
1356
|
+
else:
|
|
1357
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} not included criteria=-notincluded"
|
|
1358
|
+
continue
|
|
1359
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1360
|
+
|
|
1361
|
+
# check cloud
|
|
1362
|
+
if taskSpec.cloud not in [None, "", "any", tmpSiteSpec.cloud]:
|
|
1363
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} cloud mismatch criteria=-cloudmismatch"
|
|
1364
|
+
continue
|
|
1365
|
+
if autoSite:
|
|
1366
|
+
sitesForANY.append(tmpSiteName)
|
|
1367
|
+
else:
|
|
1368
|
+
newScanSiteList.append(tmpSiteName)
|
|
1369
|
+
# use AUTO sites if no sites are included
|
|
1370
|
+
if newScanSiteList == []:
|
|
1371
|
+
newScanSiteList = sitesForANY
|
|
1372
|
+
else:
|
|
1373
|
+
for tmpSiteName in sitesForANY:
|
|
1374
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} not included criteria=-notincluded"
|
|
1375
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1376
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "include/exclude check", tmpLog, msg_map)
|
|
1377
|
+
if not scanSiteList:
|
|
1378
|
+
self.dump_summary(tmpLog)
|
|
1379
|
+
tmpLog.error("no candidates")
|
|
1380
|
+
retVal = retTmpError
|
|
1381
|
+
continue
|
|
1382
|
+
######################################
|
|
1383
|
+
# sites already used by task
|
|
1384
|
+
tmpSt, sitesUsedByTask = self.taskBufferIF.getSitesUsedByTask_JEDI(taskSpec.jediTaskID)
|
|
1385
|
+
if not tmpSt:
|
|
1386
|
+
tmpLog.error("failed to get sites which already used by task")
|
|
1387
|
+
retVal = retTmpError
|
|
1388
|
+
continue
|
|
1389
|
+
sitesUsedByTask = self.get_unified_sites(sitesUsedByTask)
|
|
1390
|
+
######################################
|
|
1391
|
+
# calculate weight
|
|
1392
|
+
tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(taskSpec.vo)
|
|
1393
|
+
if not tmpSt:
|
|
1394
|
+
tmpLog.error("failed to get job statistics with priority")
|
|
1395
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1396
|
+
return retTmpError
|
|
1397
|
+
tmpSt, siteToRunRateMap = AtlasBrokerUtils.getSiteToRunRateStats(tbIF=self.taskBufferIF, vo=taskSpec.vo)
|
|
1398
|
+
if not tmpSt:
|
|
1399
|
+
tmpLog.error("failed to get site to-running rate")
|
|
1400
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1401
|
+
return retTmpError
|
|
1402
|
+
# check for preassigned
|
|
1403
|
+
if sitePreAssigned:
|
|
1404
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1405
|
+
if preassignedSite not in scanSiteList and preassignedSite not in self.get_unified_sites(scanSiteList):
|
|
1406
|
+
tmpLog.info(f"preassigned site {preassignedSite} did not pass all tests")
|
|
1407
|
+
self.add_summary_message(oldScanSiteList, [], "preassign check", tmpLog, {})
|
|
1408
|
+
self.dump_summary(tmpLog)
|
|
1409
|
+
tmpLog.error("no candidates")
|
|
1410
|
+
retVal = retFatal
|
|
1411
|
+
continue
|
|
1412
|
+
else:
|
|
1413
|
+
newScanSiteList = []
|
|
1414
|
+
msg_map = {}
|
|
1415
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1416
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1417
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1418
|
+
if tmpSiteName != preassignedSite:
|
|
1419
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} non pre-assigned site criteria=-nonpreassigned"
|
|
1420
|
+
continue
|
|
1421
|
+
newScanSiteList.append(tmpSiteName)
|
|
1422
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1423
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "preassign check", tmpLog, msg_map)
|
|
1424
|
+
######################################
|
|
1425
|
+
# selection for hospital
|
|
1426
|
+
newScanSiteList = []
|
|
1427
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1428
|
+
hasNormalSite = False
|
|
1429
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1430
|
+
if not tmpSiteName.endswith("_HOSPITAL"):
|
|
1431
|
+
hasNormalSite = True
|
|
1432
|
+
break
|
|
1433
|
+
if hasNormalSite:
|
|
1434
|
+
msg_map = {}
|
|
1435
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1436
|
+
# remove hospital
|
|
1437
|
+
if tmpSiteName.endswith("_HOSPITAL"):
|
|
1438
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to hospital queue criteria=-hospital"
|
|
1439
|
+
continue
|
|
1440
|
+
newScanSiteList.append(tmpSiteName)
|
|
1441
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1442
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "hospital check", tmpLog, msg_map)
|
|
1443
|
+
if not scanSiteList:
|
|
1444
|
+
self.dump_summary(tmpLog)
|
|
1445
|
+
tmpLog.error("no candidates")
|
|
1446
|
+
retVal = retTmpError
|
|
1447
|
+
continue
|
|
1448
|
+
######################################
|
|
1449
|
+
# cap with resource type
|
|
1450
|
+
if not sitePreAssigned:
|
|
1451
|
+
# count jobs per resource type
|
|
1452
|
+
tmpRet, tmpStatMap = self.taskBufferIF.getJobStatisticsByResourceTypeSite(workQueue)
|
|
1453
|
+
newScanSiteList = []
|
|
1454
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1455
|
+
msg_map = {}
|
|
1456
|
+
RT_Cap = 2
|
|
1457
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1458
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1459
|
+
tmpUnifiedName = tmpSiteSpec.get_unified_name()
|
|
1460
|
+
if tmpUnifiedName in tmpStatMap and taskSpec.resource_type in tmpStatMap[tmpUnifiedName]:
|
|
1461
|
+
tmpSiteStatMap = tmpStatMap[tmpUnifiedName][taskSpec.resource_type]
|
|
1462
|
+
tmpRTrunning = tmpSiteStatMap.get("running", 0)
|
|
1463
|
+
tmpRTqueue = tmpSiteStatMap.get("defined", 0)
|
|
1464
|
+
tmpRTqueue += tmpSiteStatMap.get("assigned", 0)
|
|
1465
|
+
tmpRTqueue += tmpSiteStatMap.get("activated", 0)
|
|
1466
|
+
tmpRTqueue += tmpSiteStatMap.get("starting", 0)
|
|
1467
|
+
if tmpRTqueue > max(20, tmpRTrunning * RT_Cap):
|
|
1468
|
+
tmpMsg = f" skip site={tmpSiteName} "
|
|
1469
|
+
tmpMsg += "since nQueue/max(20,nRun) with gshare+resource_type is "
|
|
1470
|
+
tmpMsg += f"{tmpRTqueue}/max(20,{tmpRTrunning}) > {RT_Cap} "
|
|
1471
|
+
tmpMsg += "criteria=-cap_rt"
|
|
1472
|
+
msg_map[tmpSiteName] = tmpMsg
|
|
1473
|
+
newScanSiteList.append(tmpSiteName)
|
|
1474
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1475
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "cap with gshare+resource_type check", tmpLog, msg_map)
|
|
1476
|
+
if not scanSiteList:
|
|
1477
|
+
self.dump_summary(tmpLog)
|
|
1478
|
+
tmpLog.error("no candidates")
|
|
1479
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1480
|
+
return retTmpError
|
|
1481
|
+
######################################
|
|
1482
|
+
# selection for un-overloaded sites
|
|
1483
|
+
if not inputChunk.isMerging:
|
|
1484
|
+
newScanSiteList = []
|
|
1485
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1486
|
+
overloadedNonVP = []
|
|
1487
|
+
msg_map = {}
|
|
1488
|
+
msgList = []
|
|
1489
|
+
msgListVP = []
|
|
1490
|
+
minQueue = self.taskBufferIF.getConfigValue("anal_jobbroker", "OVERLOAD_MIN_QUEUE", "jedi", taskSpec.vo)
|
|
1491
|
+
if minQueue is None:
|
|
1492
|
+
minQueue = 20
|
|
1493
|
+
ratioOffset = self.taskBufferIF.getConfigValue("anal_jobbroker", "OVERLOAD_RATIO_OFFSET", "jedi", taskSpec.vo)
|
|
1494
|
+
if ratioOffset is None:
|
|
1495
|
+
ratioOffset = 1.2
|
|
1496
|
+
grandRatio = AtlasBrokerUtils.get_total_nq_nr_ratio(jobStatPrioMap, taskSpec.gshare)
|
|
1497
|
+
tmpLog.info(f"grand nQueue/nRunning ratio : {grandRatio}")
|
|
1498
|
+
tmpLog.info(f"sites with non-VP data : {','.join(scanSiteWoVP)}")
|
|
1499
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1500
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1501
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1502
|
+
# get nQueue and nRunning
|
|
1503
|
+
nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running", workQueue_tag=taskSpec.gshare)
|
|
1504
|
+
nQueue = 0
|
|
1505
|
+
for jobStatus in ["defined", "assigned", "activated", "starting"]:
|
|
1506
|
+
nQueue += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus, workQueue_tag=taskSpec.gshare)
|
|
1507
|
+
# skip if overloaded
|
|
1508
|
+
if nQueue > minQueue and (nRunning == 0 or float(nQueue) / float(nRunning) > grandRatio * ratioOffset):
|
|
1509
|
+
tmpMsg = f" skip site={tmpSiteName} "
|
|
1510
|
+
tmpMsg += f"nQueue>minQueue({minQueue}) and "
|
|
1511
|
+
if nRunning == 0:
|
|
1512
|
+
tmpMsg += "nRunning=0 "
|
|
1513
|
+
problematic_sites_dict.setdefault(tmpSiteName, set())
|
|
1514
|
+
problematic_sites_dict[tmpSiteName].add(f"nQueue({nQueue})>minQueue({minQueue}) and nRunning=0")
|
|
1515
|
+
else:
|
|
1516
|
+
tmpMsg += f"nQueue({nQueue})/nRunning({nRunning}) > grandRatio({grandRatio:.2f})*offset({ratioOffset}) "
|
|
1517
|
+
if tmpSiteName in scanSiteWoVP or checkDataLocality is False or inputChunk.getDatasets() == []:
|
|
1518
|
+
tmpMsg += "criteria=-overloaded"
|
|
1519
|
+
overloadedNonVP.append(tmpPseudoSiteName)
|
|
1520
|
+
msgListVP.append(tmpMsg)
|
|
1521
|
+
else:
|
|
1522
|
+
tmpMsg += "and VP criteria=-overloaded_vp"
|
|
1523
|
+
msgList.append(tmpMsg)
|
|
1524
|
+
else:
|
|
1525
|
+
newScanSiteList.append(tmpPseudoSiteName)
|
|
1526
|
+
if len(newScanSiteList) > 0:
|
|
1527
|
+
scanSiteList = newScanSiteList
|
|
1528
|
+
for tmpMsg in msgList + msgListVP:
|
|
1529
|
+
tmp_site_name = tmpMsg.split("skip site=")[1].split(" ")[0]
|
|
1530
|
+
tmpSiteSpec = self.siteMapper.getSite(tmp_site_name)
|
|
1531
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmpMsg
|
|
1532
|
+
else:
|
|
1533
|
+
scanSiteList = overloadedNonVP
|
|
1534
|
+
for tmpMsg in msgList:
|
|
1535
|
+
tmp_site_name = tmpMsg.split("skip site=")[1].split(" ")[0]
|
|
1536
|
+
tmpSiteSpec = self.siteMapper.getSite(tmp_site_name)
|
|
1537
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmpMsg
|
|
1538
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "overload check", tmpLog, msg_map)
|
|
1539
|
+
if not scanSiteList:
|
|
1540
|
+
self.dump_summary(tmpLog)
|
|
1541
|
+
tmpLog.error("no candidates")
|
|
1542
|
+
retVal = retTmpError
|
|
1543
|
+
continue
|
|
1544
|
+
######################################
|
|
1545
|
+
# consider grid usage of the user
|
|
1546
|
+
user_name = CoreUtils.clean_user_id(taskSpec.userName)
|
|
1547
|
+
tmpSt, jobsStatsPerUser = AtlasBrokerUtils.getUsersJobsStats(
|
|
1548
|
+
tbIF=self.taskBufferIF, vo=taskSpec.vo, prod_source_label=taskSpec.prodSourceLabel, cache_lifetime=60
|
|
1549
|
+
)
|
|
1550
|
+
if not tmpSt:
|
|
1551
|
+
tmpLog.error("failed to get users jobs statistics")
|
|
1552
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1553
|
+
# send info to logger
|
|
1554
|
+
return retTmpError
|
|
1555
|
+
elif not inputChunk.isMerging:
|
|
1556
|
+
# loop over sites
|
|
1557
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1558
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1559
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1560
|
+
# get info about site
|
|
1561
|
+
nRunning_pq_total = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
|
|
1562
|
+
nRunning_pq_in_gshare = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running", workQueue_tag=taskSpec.gshare)
|
|
1563
|
+
nQueue_pq_in_gshare = 0
|
|
1564
|
+
for jobStatus in ["defined", "assigned", "activated", "starting"]:
|
|
1565
|
+
nQueue_pq_in_gshare += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus, workQueue_tag=taskSpec.gshare)
|
|
1566
|
+
# get to-running-rate
|
|
1567
|
+
try:
|
|
1568
|
+
site_to_running_rate = siteToRunRateMap[tmpSiteName]
|
|
1569
|
+
if isinstance(site_to_running_rate, dict):
|
|
1570
|
+
site_to_running_rate = sum(site_to_running_rate.values())
|
|
1571
|
+
except KeyError:
|
|
1572
|
+
site_to_running_rate = 0
|
|
1573
|
+
finally:
|
|
1574
|
+
to_running_rate = nRunning_pq_in_gshare * site_to_running_rate / nRunning_pq_total if nRunning_pq_total > 0 else 0
|
|
1575
|
+
# get conditions of the site whether to throttle
|
|
1576
|
+
if nQueue_pq_in_gshare < base_queue_length_per_pq:
|
|
1577
|
+
# not throttle since overall queue length of the site is not large enough
|
|
1578
|
+
tmpLog.debug(f"not throttle on {tmpSiteName} since nQ({nQueue_pq_in_gshare}) < base queue length ({base_queue_length_per_pq})")
|
|
1579
|
+
continue
|
|
1580
|
+
allowed_queue_length_from_wait_time = base_expected_wait_hour_on_pq * to_running_rate
|
|
1581
|
+
if nQueue_pq_in_gshare < allowed_queue_length_from_wait_time:
|
|
1582
|
+
# not statisfy since overall waiting time of the site is not long enough
|
|
1583
|
+
tmpLog.debug(
|
|
1584
|
+
"not throttle on {0} since nQ({1}) < {2:.3f} = toRunningRate({3:.3f} /hr) * base wait time ({4} hr)".format(
|
|
1585
|
+
tmpSiteName, nQueue_pq_in_gshare, allowed_queue_length_from_wait_time, to_running_rate, base_expected_wait_hour_on_pq
|
|
1586
|
+
)
|
|
1587
|
+
)
|
|
1588
|
+
continue
|
|
1589
|
+
# get user jobs stats under the gshare
|
|
1590
|
+
try:
|
|
1591
|
+
user_jobs_stats_map = jobsStatsPerUser[tmpSiteName][taskSpec.gshare][user_name]
|
|
1592
|
+
except KeyError:
|
|
1593
|
+
continue
|
|
1594
|
+
else:
|
|
1595
|
+
nQ_pq_user = user_jobs_stats_map["nQueue"]
|
|
1596
|
+
nR_pq_user = user_jobs_stats_map["nRunning"]
|
|
1597
|
+
nUsers_pq = len(jobsStatsPerUser[tmpSiteName][taskSpec.gshare])
|
|
1598
|
+
try:
|
|
1599
|
+
nR_pq = jobsStatsPerUser[tmpSiteName][taskSpec.gshare]["_total"]["nRunning"]
|
|
1600
|
+
except KeyError:
|
|
1601
|
+
nR_pq = nRunning_pq_in_gshare
|
|
1602
|
+
# evaluate max nQueue per PQ
|
|
1603
|
+
nQ_pq_limit_map = {
|
|
1604
|
+
"base_limit": base_queue_length_per_pq,
|
|
1605
|
+
"static_limit": static_max_queue_running_ratio * nR_pq,
|
|
1606
|
+
"dynamic_limit": max_expected_wait_hour * to_running_rate,
|
|
1607
|
+
}
|
|
1608
|
+
max_nQ_pq = max(nQ_pq_limit_map.values())
|
|
1609
|
+
# description for max nQueue per PQ
|
|
1610
|
+
description_of_max_nQ_pq = f"max_nQ_pq({max_nQ_pq:.3f}) "
|
|
1611
|
+
for k, v in nQ_pq_limit_map.items():
|
|
1612
|
+
if v == max_nQ_pq:
|
|
1613
|
+
if k in ["base_limit"]:
|
|
1614
|
+
description_of_max_nQ_pq += f"= {k} = BASE_QUEUE_LENGTH_PER_PQ({base_queue_length_per_pq})"
|
|
1615
|
+
elif k in ["static_limit"]:
|
|
1616
|
+
description_of_max_nQ_pq += f"= {k} = STATIC_MAX_QUEUE_RUNNING_RATIO({static_max_queue_running_ratio:.3f}) * nR_pq({nR_pq})"
|
|
1617
|
+
elif k in ["dynamic_limit"]:
|
|
1618
|
+
description_of_max_nQ_pq += "= {key} = MAX_EXPECTED_WAIT_HOUR({value:.3f} hr) * toRunningRate_pq({trr:.3f} /hr)".format(
|
|
1619
|
+
key=k, value=max_expected_wait_hour, trr=to_running_rate
|
|
1620
|
+
)
|
|
1621
|
+
break
|
|
1622
|
+
# evaluate fraction per user
|
|
1623
|
+
user_fraction_map = {
|
|
1624
|
+
"equal_distr": 1 / nUsers_pq,
|
|
1625
|
+
"prop_to_nR": nR_pq_user / nR_pq if nR_pq > 0 else 0,
|
|
1626
|
+
}
|
|
1627
|
+
max_user_fraction = max(user_fraction_map.values())
|
|
1628
|
+
# description for max fraction per user
|
|
1629
|
+
description_of_max_user_fraction = f"max_user_fraction({max_user_fraction:.3f}) "
|
|
1630
|
+
for k, v in user_fraction_map.items():
|
|
1631
|
+
if v == max_user_fraction:
|
|
1632
|
+
if k in ["equal_distr"]:
|
|
1633
|
+
description_of_max_user_fraction += f"= {k} = 1 / nUsers_pq({nUsers_pq})"
|
|
1634
|
+
elif k in ["prop_to_nR"]:
|
|
1635
|
+
description_of_max_user_fraction += f"= {k} = nR_pq_user({nR_pq_user}) / nR_pq({nR_pq})"
|
|
1636
|
+
break
|
|
1637
|
+
# evaluate max nQueue per PQ per user
|
|
1638
|
+
nQ_pq_user_limit_map = {
|
|
1639
|
+
"constant_base_user_limit": base_default_queue_length_per_pq_user,
|
|
1640
|
+
"ratio_base_user_limit": base_queue_ratio_on_pq * nR_pq,
|
|
1641
|
+
"dynamic_user_limit": max_nQ_pq * max_user_fraction,
|
|
1642
|
+
}
|
|
1643
|
+
max_nQ_pq_user = max(nQ_pq_user_limit_map.values())
|
|
1644
|
+
# description for max fraction per user
|
|
1645
|
+
description_of_max_nQ_pq_user = f"max_nQ_pq_user({max_nQ_pq_user:.3f}) "
|
|
1646
|
+
for k, v in nQ_pq_user_limit_map.items():
|
|
1647
|
+
if v == max_nQ_pq_user:
|
|
1648
|
+
if k in ["constant_base_user_limit"]:
|
|
1649
|
+
description_of_max_nQ_pq_user += f"= {k} = BASE_DEFAULT_QUEUE_LENGTH_PER_PQ_USER({base_default_queue_length_per_pq_user})"
|
|
1650
|
+
elif k in ["ratio_base_user_limit"]:
|
|
1651
|
+
description_of_max_nQ_pq_user += f"= {k} = BASE_QUEUE_RATIO_ON_PQ({base_queue_ratio_on_pq:.3f}) * nR_pq({nR_pq})"
|
|
1652
|
+
elif k in ["dynamic_user_limit"]:
|
|
1653
|
+
description_of_max_nQ_pq_user += f"= {k} = max_nQ_pq({max_nQ_pq:.3f}) * max_user_fraction({max_user_fraction:.3f})"
|
|
1654
|
+
description_of_max_nQ_pq_user += f" , where {description_of_max_nQ_pq} , and {description_of_max_user_fraction}"
|
|
1655
|
+
break
|
|
1656
|
+
# # Analysis Stabilizer: skip sites where the user queues too much
|
|
1657
|
+
if nQ_pq_user > max_nQ_pq_user:
|
|
1658
|
+
tmpMsg = f" consider {tmpSiteName} unsuitable for the user due to long queue of the user: "
|
|
1659
|
+
tmpMsg += f"nQ_pq_user({nQ_pq_user}) > {description_of_max_nQ_pq_user} "
|
|
1660
|
+
# view as problematic site in order to throttle
|
|
1661
|
+
problematic_sites_dict.setdefault(tmpSiteName, set())
|
|
1662
|
+
problematic_sites_dict[tmpSiteName].add(tmpMsg)
|
|
1663
|
+
# problematic sites with too many failed and closed jobs
|
|
1664
|
+
if tmpSiteName in failureCounts:
|
|
1665
|
+
nFailed = failureCounts[tmpSiteName].get("failed", 0)
|
|
1666
|
+
nClosed = failureCounts[tmpSiteName].get("closed", 0)
|
|
1667
|
+
nFinished = failureCounts[tmpSiteName].get("finished", 0)
|
|
1668
|
+
if not inputChunk.isMerging and (nFailed + nClosed) > max(2 * nFinished, minBadJobsToSkipPQ):
|
|
1669
|
+
problematic_sites_dict.setdefault(tmpSiteName, set())
|
|
1670
|
+
problematic_sites_dict[tmpSiteName].add("too many failed or closed jobs for last 6h")
|
|
1671
|
+
# check if good sites are still available after removing problematic sites when
|
|
1672
|
+
# * the brokerage has two loops
|
|
1673
|
+
# * it is doing the first loop with data locality check
|
|
1674
|
+
# * data locality check is disabled in the second loop due to low IO, special task priority, or timeout
|
|
1675
|
+
if len(scan_site_list_loops) > 1 and i_loop == 0 and to_ignore_data_loc:
|
|
1676
|
+
candidates_with_problems = []
|
|
1677
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1678
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1679
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1680
|
+
if tmpSiteName in problematic_sites_dict:
|
|
1681
|
+
candidates_with_problems.append([tmpPseudoSiteName, list(problematic_sites_dict[tmpSiteName])[0]])
|
|
1682
|
+
if len(scanSiteList) == len(candidates_with_problems):
|
|
1683
|
+
msg_map = {}
|
|
1684
|
+
for tmpPseudoSiteName, error_diag in candidates_with_problems:
|
|
1685
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1686
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
1687
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to a temporary user-specific problem: {error_diag} " "criteria=-tmp_user_problem"
|
|
1688
|
+
)
|
|
1689
|
+
self.add_summary_message(scanSiteList, [], "temp user problem check", tmpLog, msg_map)
|
|
1690
|
+
self.dump_summary(tmpLog)
|
|
1691
|
+
tmpLog.error("no candidates")
|
|
1692
|
+
retVal = retTmpError
|
|
1693
|
+
continue
|
|
1694
|
+
|
|
1695
|
+
############
|
|
1696
|
+
# loop end
|
|
1697
|
+
overall_site_list.update(scanSiteList)
|
|
1698
|
+
if site_list_with_data is None:
|
|
1699
|
+
# preserve site list with data
|
|
1700
|
+
site_list_with_data = set(scanSiteList)
|
|
1701
|
+
if len(overall_site_list) >= taskSpec.getNumSitesPerJob():
|
|
1702
|
+
retVal = None
|
|
1703
|
+
break
|
|
1704
|
+
# failed
|
|
1705
|
+
if retVal is not None:
|
|
1706
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1707
|
+
return retVal
|
|
1708
|
+
# get list of available files
|
|
1709
|
+
scanSiteList = list(overall_site_list)
|
|
1710
|
+
availableFileMap = {}
|
|
1711
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
1712
|
+
try:
|
|
1713
|
+
# get list of site to be scanned
|
|
1714
|
+
tmpLog.debug(f"getting the list of available files for {datasetSpec.datasetName}")
|
|
1715
|
+
fileScanSiteList = []
|
|
1716
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1717
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1718
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1719
|
+
if tmpSiteName in fileScanSiteList:
|
|
1720
|
+
continue
|
|
1721
|
+
fileScanSiteList.append(tmpSiteName)
|
|
1722
|
+
if tmpSiteName in remoteSourceList and datasetSpec.datasetName in remoteSourceList[tmpSiteName]:
|
|
1723
|
+
for tmpRemoteSite in remoteSourceList[tmpSiteName][datasetSpec.datasetName]:
|
|
1724
|
+
if tmpRemoteSite not in fileScanSiteList:
|
|
1725
|
+
fileScanSiteList.append(tmpRemoteSite)
|
|
1726
|
+
# mapping between sites and input storage endpoints
|
|
1727
|
+
siteStorageEP = AtlasBrokerUtils.getSiteInputStorageEndpointMap(fileScanSiteList, self.siteMapper, JobUtils.ANALY_PS, JobUtils.ANALY_PS)
|
|
1728
|
+
# disable file lookup for merge jobs
|
|
1729
|
+
if inputChunk.isMerging:
|
|
1730
|
+
checkCompleteness = False
|
|
1731
|
+
else:
|
|
1732
|
+
checkCompleteness = True
|
|
1733
|
+
if not datasetSpec.isMaster():
|
|
1734
|
+
useCompleteOnly = True
|
|
1735
|
+
else:
|
|
1736
|
+
useCompleteOnly = False
|
|
1737
|
+
# get available files per site/endpoint
|
|
1738
|
+
tmpAvFileMap = self.ddmIF.getAvailableFiles(
|
|
1739
|
+
datasetSpec,
|
|
1740
|
+
siteStorageEP,
|
|
1741
|
+
self.siteMapper,
|
|
1742
|
+
check_completeness=checkCompleteness,
|
|
1743
|
+
use_vp=useVP,
|
|
1744
|
+
file_scan_in_container=False,
|
|
1745
|
+
complete_only=useCompleteOnly,
|
|
1746
|
+
element_list=element_map.get(datasetSpec.datasetName),
|
|
1747
|
+
)
|
|
1748
|
+
if tmpAvFileMap is None:
|
|
1749
|
+
raise Interaction.JEDITemporaryError("ddmIF.getAvailableFiles failed")
|
|
1750
|
+
availableFileMap[datasetSpec.datasetName] = tmpAvFileMap
|
|
1751
|
+
except Exception:
|
|
1752
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
1753
|
+
tmpLog.error(f"failed to get available files with {errtype.__name__} {errvalue}")
|
|
1754
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1755
|
+
return retTmpError
|
|
1756
|
+
# make data weight
|
|
1757
|
+
totalSize = 0
|
|
1758
|
+
totalNumFiles = 0
|
|
1759
|
+
totalDiskSizeMap = dict()
|
|
1760
|
+
totalTapeSizeMap = dict()
|
|
1761
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
1762
|
+
totalNumFiles += len(datasetSpec.Files)
|
|
1763
|
+
for fileSpec in datasetSpec.Files:
|
|
1764
|
+
totalSize += fileSpec.fsize
|
|
1765
|
+
if datasetSpec.datasetName in availableFileMap:
|
|
1766
|
+
for tmpSiteName, tmpAvFileMap in availableFileMap[datasetSpec.datasetName].items():
|
|
1767
|
+
totalDiskSizeMap.setdefault(tmpSiteName, 0)
|
|
1768
|
+
totalTapeSizeMap.setdefault(tmpSiteName, 0)
|
|
1769
|
+
for fileSpec in tmpAvFileMap["localdisk"]:
|
|
1770
|
+
totalDiskSizeMap[tmpSiteName] += fileSpec.fsize
|
|
1771
|
+
for fileSpec in tmpAvFileMap["localtape"]:
|
|
1772
|
+
totalTapeSizeMap[tmpSiteName] += fileSpec.fsize
|
|
1773
|
+
totalSize //= 1024 * 1024 * 1024
|
|
1774
|
+
tmpLog.info(f"totalInputSize={totalSize} GB")
|
|
1775
|
+
for tmpSiteName in totalDiskSizeMap.keys():
|
|
1776
|
+
totalDiskSizeMap[tmpSiteName] //= 1024 * 1024 * 1024
|
|
1777
|
+
for tmpSiteName in totalTapeSizeMap.keys():
|
|
1778
|
+
totalTapeSizeMap[tmpSiteName] //= 1024 * 1024 * 1024
|
|
1779
|
+
######################################
|
|
1780
|
+
# final procedure
|
|
1781
|
+
tmpLog.info(f"{len(scanSiteList)} candidates for final check")
|
|
1782
|
+
weightMap = {}
|
|
1783
|
+
weightStr = {}
|
|
1784
|
+
candidateSpecList = []
|
|
1785
|
+
preSiteCandidateSpec = None
|
|
1786
|
+
basic_weight_compar_map = {}
|
|
1787
|
+
workerStat = self.taskBufferIF.ups_load_worker_stats()
|
|
1788
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1789
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1790
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1791
|
+
nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running", workQueue_tag=taskSpec.gshare)
|
|
1792
|
+
nDefined = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "defined", workQueue_tag=taskSpec.gshare)
|
|
1793
|
+
nAssigned = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "assigned", workQueue_tag=taskSpec.gshare)
|
|
1794
|
+
nActivated = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "activated", workQueue_tag=taskSpec.gshare)
|
|
1795
|
+
nStarting = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "starting", workQueue_tag=taskSpec.gshare)
|
|
1796
|
+
# get num workers
|
|
1797
|
+
nWorkers = 0
|
|
1798
|
+
nWorkersCutoff = 20
|
|
1799
|
+
if tmpSiteName in workerStat:
|
|
1800
|
+
for _, tmpLabelStat in workerStat[tmpSiteName].items():
|
|
1801
|
+
for _, tmpResStat in tmpLabelStat.items():
|
|
1802
|
+
for tmpResType, tmpCounts in tmpResStat.items():
|
|
1803
|
+
for tmpStatus, tmpNum in tmpCounts.items():
|
|
1804
|
+
if tmpStatus in ["running", "submitted"]:
|
|
1805
|
+
nWorkers += tmpNum
|
|
1806
|
+
# cap
|
|
1807
|
+
nWorkers = min(nWorkersCutoff, nWorkers)
|
|
1808
|
+
# use nWorkers to bootstrap
|
|
1809
|
+
if tmpSiteName in nPilotMap and nPilotMap[tmpSiteName] > 0 and nRunning < nWorkersCutoff and nWorkers > nRunning:
|
|
1810
|
+
tmpLog.debug(f"using nWorkers={nWorkers} as nRunning at {tmpPseudoSiteName} since original nRunning={nRunning} is low")
|
|
1811
|
+
nRunning = nWorkers
|
|
1812
|
+
# take into account the number of standby jobs
|
|
1813
|
+
numStandby = tmpSiteSpec.getNumStandby(taskSpec.gshare, taskSpec.resource_type)
|
|
1814
|
+
if numStandby is None:
|
|
1815
|
+
pass
|
|
1816
|
+
elif numStandby == 0:
|
|
1817
|
+
# use the number of starting jobs as the number of standby jobs
|
|
1818
|
+
nRunning = nStarting + nRunning
|
|
1819
|
+
tmpLog.debug(f"using dynamic workload provisioning at {tmpPseudoSiteName} to set nRunning={nRunning}")
|
|
1820
|
+
else:
|
|
1821
|
+
# the number of standby jobs is defined
|
|
1822
|
+
nRunning = max(int(numStandby / tmpSiteSpec.coreCount), nRunning)
|
|
1823
|
+
tmpLog.debug(f"using static workload provisioning at {tmpPseudoSiteName} with nStandby={numStandby} to set nRunning={nRunning}")
|
|
1824
|
+
nFailed = 0
|
|
1825
|
+
nClosed = 0
|
|
1826
|
+
nFinished = 0
|
|
1827
|
+
if tmpSiteName in failureCounts:
|
|
1828
|
+
if "failed" in failureCounts[tmpSiteName]:
|
|
1829
|
+
nFailed = failureCounts[tmpSiteName]["failed"]
|
|
1830
|
+
if "closed" in failureCounts[tmpSiteName]:
|
|
1831
|
+
nClosed = failureCounts[tmpSiteName]["closed"]
|
|
1832
|
+
if "finished" in failureCounts[tmpSiteName]:
|
|
1833
|
+
nFinished = failureCounts[tmpSiteName]["finished"]
|
|
1834
|
+
# to-running rate
|
|
1835
|
+
try:
|
|
1836
|
+
site_to_running_rate = siteToRunRateMap[tmpSiteName]
|
|
1837
|
+
if isinstance(site_to_running_rate, dict):
|
|
1838
|
+
site_to_running_rate = sum(site_to_running_rate.values())
|
|
1839
|
+
except KeyError:
|
|
1840
|
+
to_running_rate_str = "0(unknown)"
|
|
1841
|
+
to_running_rate = 0
|
|
1842
|
+
else:
|
|
1843
|
+
site_n_running = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
|
|
1844
|
+
to_running_rate = nRunning * site_to_running_rate / site_n_running if site_n_running > 0 else 0
|
|
1845
|
+
to_running_rate_str = f"{to_running_rate:.3f}"
|
|
1846
|
+
# site class value; by default mid-class (= 0) if unclassified
|
|
1847
|
+
site_class_value = analy_sites_class_dict.get(tmpSiteName, 0)
|
|
1848
|
+
site_class_value = 0 if site_class_value is None else site_class_value
|
|
1849
|
+
# calculate original basic weight
|
|
1850
|
+
orig_basic_weight = float(nRunning + 1) / float(nActivated + nAssigned + nDefined + nStarting + 1)
|
|
1851
|
+
# available site, take in account of new basic weight
|
|
1852
|
+
basic_weight_compar_map[tmpSiteName] = {}
|
|
1853
|
+
basic_weight_compar_map[tmpSiteName]["orig"] = orig_basic_weight
|
|
1854
|
+
basic_weight_compar_map[tmpSiteName]["trr"] = to_running_rate
|
|
1855
|
+
basic_weight_compar_map[tmpSiteName]["nq"] = nActivated + nAssigned + nDefined + nStarting
|
|
1856
|
+
basic_weight_compar_map[tmpSiteName]["nr"] = nRunning
|
|
1857
|
+
basic_weight_compar_map[tmpSiteName]["class"] = site_class_value
|
|
1858
|
+
basic_weight_compar_map[tmpSiteName]["nDefined"] = nDefined
|
|
1859
|
+
basic_weight_compar_map[tmpSiteName]["nActivated"] = nActivated
|
|
1860
|
+
basic_weight_compar_map[tmpSiteName]["nStarting"] = nStarting
|
|
1861
|
+
basic_weight_compar_map[tmpSiteName]["nAssigned"] = nAssigned
|
|
1862
|
+
basic_weight_compar_map[tmpSiteName]["nFailed"] = nFailed
|
|
1863
|
+
basic_weight_compar_map[tmpSiteName]["nClosed"] = nClosed
|
|
1864
|
+
basic_weight_compar_map[tmpSiteName]["nFinished"] = nFinished
|
|
1865
|
+
# compute new basic weight
|
|
1866
|
+
try:
|
|
1867
|
+
n_avail_sites = len(basic_weight_compar_map)
|
|
1868
|
+
if n_avail_sites == 0:
|
|
1869
|
+
tmpLog.debug("WEIGHT-COMPAR: zero available sites, skip")
|
|
1870
|
+
else:
|
|
1871
|
+
# task class value
|
|
1872
|
+
task_class_value = task_eval_dict.get("class", 1) if task_eval_dict is not None else 1
|
|
1873
|
+
# get nFilesPerJob
|
|
1874
|
+
if not inputChunk.isMerging:
|
|
1875
|
+
nFilesPerJob = taskSpec.getNumFilesPerJob()
|
|
1876
|
+
else:
|
|
1877
|
+
nFilesPerJob = taskSpec.getNumFilesPerMergeJob()
|
|
1878
|
+
if nFilesPerJob is None or nFilesPerJob < 1:
|
|
1879
|
+
nFilesPerJob = 1
|
|
1880
|
+
#
|
|
1881
|
+
_maxSizePerJob = taskSpec.getMaxSizePerJob()
|
|
1882
|
+
if _maxSizePerJob is not None:
|
|
1883
|
+
_maxSizePerJob += inputChunk.defaultOutputSize
|
|
1884
|
+
_maxSizePerJob += taskSpec.getWorkDiskSize()
|
|
1885
|
+
else:
|
|
1886
|
+
if taskSpec.useScout():
|
|
1887
|
+
_maxSizePerJob = inputChunk.maxInputSizeScouts * 1024 * 1024
|
|
1888
|
+
else:
|
|
1889
|
+
_maxSizePerJob = inputChunk.maxInputSizeAvalanche * 1024 * 1024
|
|
1890
|
+
# count subchunks
|
|
1891
|
+
n_subchunks = 0
|
|
1892
|
+
while True:
|
|
1893
|
+
subchunk, _ = inputChunk.getSubChunk(
|
|
1894
|
+
None,
|
|
1895
|
+
maxNumFiles=taskSpec.getMaxNumFilesPerJob(),
|
|
1896
|
+
nFilesPerJob=taskSpec.getNumFilesPerJob(),
|
|
1897
|
+
walltimeGradient=(taskSpec.getCpuTime() if taskSpec.useHS06() else None),
|
|
1898
|
+
maxWalltime=(taskSpec.getMaxWalltime() if taskSpec.getMaxWalltime() is not None else 345600),
|
|
1899
|
+
sizeGradients=taskSpec.getOutDiskSize(),
|
|
1900
|
+
sizeIntercepts=taskSpec.getWorkDiskSize(),
|
|
1901
|
+
maxSize=_maxSizePerJob,
|
|
1902
|
+
nEventsPerJob=taskSpec.getNumEventsPerJob(),
|
|
1903
|
+
coreCount=taskSpec.coreCount,
|
|
1904
|
+
corePower=10,
|
|
1905
|
+
respectLB=taskSpec.respectLumiblock(),
|
|
1906
|
+
)
|
|
1907
|
+
if subchunk is None:
|
|
1908
|
+
break
|
|
1909
|
+
else:
|
|
1910
|
+
n_subchunks += 1
|
|
1911
|
+
inputChunk.resetUsedCounters()
|
|
1912
|
+
n_jobs_to_submit = n_subchunks
|
|
1913
|
+
# parameters for small additional weight
|
|
1914
|
+
weight_epsilon_init = 0.001
|
|
1915
|
+
weight_epsilon_hi_mid = 0.05
|
|
1916
|
+
weight_epsilon_hi_lo = 0.002
|
|
1917
|
+
weight_epsilon_mid_lo = 0.1
|
|
1918
|
+
# initialize
|
|
1919
|
+
tmpSt, siteToRunRateMap = AtlasBrokerUtils.getSiteToRunRateStats(tbIF=self.taskBufferIF, vo=taskSpec.vo)
|
|
1920
|
+
weight_comparison_avail_sites = set(basic_weight_compar_map.keys())
|
|
1921
|
+
site_class_n_site_dict = {1: 0, 0: 0, -1: 0}
|
|
1922
|
+
site_class_rem_q_len_dict = {1: 0, 0: 0, -1: 0}
|
|
1923
|
+
total_rem_q_len = 0
|
|
1924
|
+
# loop over sites for metrics
|
|
1925
|
+
for site, bw_map in basic_weight_compar_map.items():
|
|
1926
|
+
# site class count
|
|
1927
|
+
site_class_n_site_dict[bw_map["class"]] += 1
|
|
1928
|
+
# get info about site
|
|
1929
|
+
nRunning_pq_total = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, site, "running")
|
|
1930
|
+
nRunning_pq_in_gshare = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, site, "running", workQueue_tag=taskSpec.gshare)
|
|
1931
|
+
nQueue_pq_in_gshare = 0
|
|
1932
|
+
for jobStatus in ["defined", "assigned", "activated", "starting"]:
|
|
1933
|
+
nQueue_pq_in_gshare += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, site, jobStatus, workQueue_tag=taskSpec.gshare)
|
|
1934
|
+
# get to-running-rate
|
|
1935
|
+
try:
|
|
1936
|
+
site_to_running_rate = siteToRunRateMap[site]
|
|
1937
|
+
if isinstance(site_to_running_rate, dict):
|
|
1938
|
+
site_to_running_rate = sum(site_to_running_rate.values())
|
|
1939
|
+
except Exception:
|
|
1940
|
+
site_to_running_rate = 0
|
|
1941
|
+
finally:
|
|
1942
|
+
to_running_rate = nRunning_pq_in_gshare * site_to_running_rate / nRunning_pq_total if nRunning_pq_total > 0 else 0
|
|
1943
|
+
# get user jobs stats under the gshare
|
|
1944
|
+
try:
|
|
1945
|
+
user_jobs_stats_map = jobsStatsPerUser[site][taskSpec.gshare][user_name]
|
|
1946
|
+
except KeyError:
|
|
1947
|
+
nQ_pq_user = 0
|
|
1948
|
+
nR_pq_user = 0
|
|
1949
|
+
else:
|
|
1950
|
+
nQ_pq_user = user_jobs_stats_map["nQueue"]
|
|
1951
|
+
nR_pq_user = user_jobs_stats_map["nRunning"]
|
|
1952
|
+
try:
|
|
1953
|
+
nUsers_pq = len(jobsStatsPerUser[site][taskSpec.gshare])
|
|
1954
|
+
except KeyError:
|
|
1955
|
+
nUsers_pq = 1
|
|
1956
|
+
try:
|
|
1957
|
+
nR_pq = jobsStatsPerUser[site][taskSpec.gshare]["_total"]["nRunning"]
|
|
1958
|
+
except KeyError:
|
|
1959
|
+
nR_pq = nRunning_pq_in_gshare
|
|
1960
|
+
# evaluate max nQueue per PQ
|
|
1961
|
+
nQ_pq_limit_map = {
|
|
1962
|
+
"base_limit": base_queue_length_per_pq,
|
|
1963
|
+
"static_limit": static_max_queue_running_ratio * nR_pq,
|
|
1964
|
+
"dynamic_limit": max_expected_wait_hour * to_running_rate,
|
|
1965
|
+
}
|
|
1966
|
+
max_nQ_pq = max(nQ_pq_limit_map.values())
|
|
1967
|
+
# evaluate fraction per user
|
|
1968
|
+
user_fraction_map = {
|
|
1969
|
+
"equal_distr": 1 / (nUsers_pq),
|
|
1970
|
+
"prop_to_nR": nR_pq_user / nR_pq if nR_pq > 0 else 0,
|
|
1971
|
+
}
|
|
1972
|
+
max_user_fraction = max(user_fraction_map.values())
|
|
1973
|
+
# evaluate max nQueue per PQ per user
|
|
1974
|
+
nQ_pq_user_limit_map = {
|
|
1975
|
+
"constant_base_user_limit": base_default_queue_length_per_pq_user,
|
|
1976
|
+
"ratio_base_user_limit": base_queue_ratio_on_pq * nR_pq,
|
|
1977
|
+
"dynamic_user_limit": max_nQ_pq * max_user_fraction,
|
|
1978
|
+
}
|
|
1979
|
+
max_nQ_pq_user = max(nQ_pq_user_limit_map.values())
|
|
1980
|
+
# fill in metrics for the site
|
|
1981
|
+
bw_map["user_r"] = nR_pq_user
|
|
1982
|
+
bw_map["user_q_len"] = nQ_pq_user
|
|
1983
|
+
bw_map["max_q_len"] = max_nQ_pq_user
|
|
1984
|
+
bw_map["rem_q_len"] = max(bw_map["max_q_len"] - bw_map["user_q_len"], 0)
|
|
1985
|
+
site_class_rem_q_len_dict[bw_map["class"]] += bw_map["rem_q_len"]
|
|
1986
|
+
total_rem_q_len += bw_map["rem_q_len"]
|
|
1987
|
+
# main weight, for User Analysis, determined by number of jobs to submit to each site class
|
|
1988
|
+
main_weight_site_class_dict = {1: weight_epsilon_init, 0: weight_epsilon_init, -1: weight_epsilon_init}
|
|
1989
|
+
if taskSpec.gshare in ["User Analysis", "Express Analysis"]:
|
|
1990
|
+
n_jobs_to_submit_rem = min(n_jobs_to_submit, total_rem_q_len)
|
|
1991
|
+
if task_class_value == -1:
|
|
1992
|
+
# C-task
|
|
1993
|
+
main_weight_site_class_dict[-1] = n_jobs_to_submit_rem
|
|
1994
|
+
elif task_class_value == 0:
|
|
1995
|
+
# B-task
|
|
1996
|
+
main_weight_site_class_dict[0] = min(n_jobs_to_submit_rem, site_class_rem_q_len_dict[0])
|
|
1997
|
+
n_jobs_to_submit_rem -= main_weight_site_class_dict[0]
|
|
1998
|
+
main_weight_site_class_dict[-1] = n_jobs_to_submit_rem
|
|
1999
|
+
if main_weight_site_class_dict[0] > 0:
|
|
2000
|
+
main_weight_site_class_dict[0] -= weight_epsilon_mid_lo
|
|
2001
|
+
main_weight_site_class_dict[-1] += weight_epsilon_mid_lo
|
|
2002
|
+
else:
|
|
2003
|
+
# S-task or A-task or unclassified task
|
|
2004
|
+
main_weight_site_class_dict[1] = min(n_jobs_to_submit_rem, site_class_rem_q_len_dict[1])
|
|
2005
|
+
n_jobs_to_submit_rem -= main_weight_site_class_dict[1]
|
|
2006
|
+
main_weight_site_class_dict[0] = min(n_jobs_to_submit_rem, site_class_rem_q_len_dict[0])
|
|
2007
|
+
n_jobs_to_submit_rem -= main_weight_site_class_dict[0]
|
|
2008
|
+
main_weight_site_class_dict[-1] = n_jobs_to_submit_rem
|
|
2009
|
+
if main_weight_site_class_dict[1] > 0:
|
|
2010
|
+
main_weight_site_class_dict[1] -= weight_epsilon_hi_mid + weight_epsilon_hi_lo
|
|
2011
|
+
main_weight_site_class_dict[0] += weight_epsilon_hi_mid
|
|
2012
|
+
main_weight_site_class_dict[-1] += weight_epsilon_hi_lo
|
|
2013
|
+
# find the weights
|
|
2014
|
+
for site in weight_comparison_avail_sites:
|
|
2015
|
+
bw_map = basic_weight_compar_map[site]
|
|
2016
|
+
# main weight by site & task class for User Analysis, and constant for group shares
|
|
2017
|
+
nbw_main = n_jobs_to_submit
|
|
2018
|
+
if taskSpec.gshare in ["User Analysis", "Express Analysis"]:
|
|
2019
|
+
nbw_main = main_weight_site_class_dict[bw_map["class"]]
|
|
2020
|
+
# secondary weight proportional to remaing queue length
|
|
2021
|
+
nbw_sec = 1
|
|
2022
|
+
if taskSpec.gshare in ["User Analysis", "Express Analysis"]:
|
|
2023
|
+
_nbw_numer = max(bw_map["rem_q_len"] - nbw_main / site_class_n_site_dict[bw_map["class"]], nbw_main * 0.001)
|
|
2024
|
+
reduced_site_class_rem_q_len = site_class_rem_q_len_dict[bw_map["class"]] - nbw_main
|
|
2025
|
+
if reduced_site_class_rem_q_len > 0 and not inputChunk.isMerging:
|
|
2026
|
+
nbw_sec = _nbw_numer / reduced_site_class_rem_q_len
|
|
2027
|
+
elif site_class_rem_q_len_dict[bw_map["class"]] > 0:
|
|
2028
|
+
nbw_sec = bw_map["rem_q_len"] / site_class_rem_q_len_dict[bw_map["class"]]
|
|
2029
|
+
elif site_class_n_site_dict[bw_map["class"]] > 0:
|
|
2030
|
+
nbw_sec = 1 / site_class_n_site_dict[bw_map["class"]]
|
|
2031
|
+
else:
|
|
2032
|
+
reduced_total_rem_q_len = total_rem_q_len - nbw_main
|
|
2033
|
+
_nbw_numer = max(bw_map["rem_q_len"] - nbw_main / n_avail_sites, nbw_main * 0.001)
|
|
2034
|
+
if reduced_total_rem_q_len > 0 and not inputChunk.isMerging:
|
|
2035
|
+
nbw_sec = _nbw_numer / reduced_total_rem_q_len
|
|
2036
|
+
elif total_rem_q_len > 0:
|
|
2037
|
+
nbw_sec = bw_map["rem_q_len"] / total_rem_q_len
|
|
2038
|
+
elif site_class_n_site_dict[bw_map["class"]] > 0:
|
|
2039
|
+
nbw_sec = 1 / n_avail_sites
|
|
2040
|
+
# new basic weight
|
|
2041
|
+
new_basic_weight = nbw_main * nbw_sec + 2**-20
|
|
2042
|
+
bw_map["new"] = new_basic_weight
|
|
2043
|
+
# log message to compare weights
|
|
2044
|
+
orig_sum = 0
|
|
2045
|
+
new_sum = 0
|
|
2046
|
+
for bw_map in basic_weight_compar_map.values():
|
|
2047
|
+
orig_sum += bw_map["orig"]
|
|
2048
|
+
new_sum += bw_map["new"]
|
|
2049
|
+
for site in basic_weight_compar_map:
|
|
2050
|
+
bw_map = basic_weight_compar_map[site]
|
|
2051
|
+
if bw_map["nr"] == 0:
|
|
2052
|
+
trr_over_r = None
|
|
2053
|
+
else:
|
|
2054
|
+
trr_over_r = bw_map["trr"] / bw_map["nr"]
|
|
2055
|
+
bw_map["trr_over_r"] = f"{trr_over_r:6.3f}" if trr_over_r is not None else "None"
|
|
2056
|
+
if orig_sum == 0:
|
|
2057
|
+
normalized_orig = 0
|
|
2058
|
+
else:
|
|
2059
|
+
normalized_orig = bw_map["orig"] / orig_sum
|
|
2060
|
+
bw_map["normalized_orig"] = normalized_orig
|
|
2061
|
+
if new_sum == 0:
|
|
2062
|
+
normalized_new = 0
|
|
2063
|
+
else:
|
|
2064
|
+
normalized_new = bw_map["new"] / new_sum
|
|
2065
|
+
bw_map["normalized_new"] = normalized_new
|
|
2066
|
+
prt_str_list = []
|
|
2067
|
+
prt_str_temp = (
|
|
2068
|
+
""
|
|
2069
|
+
" {site:>30} |"
|
|
2070
|
+
" {class:>2} |"
|
|
2071
|
+
" {nq:>6} |"
|
|
2072
|
+
" {nr:>6} |"
|
|
2073
|
+
" {trr:7.2f} |"
|
|
2074
|
+
" {user_q_len:>5} |"
|
|
2075
|
+
" {max_q_len:9.2f} |"
|
|
2076
|
+
" {rem_q_len:9.2f} |"
|
|
2077
|
+
" {orig:7.2f} |"
|
|
2078
|
+
" {new:7.3f} |"
|
|
2079
|
+
" {normalized_orig:6.1%} |"
|
|
2080
|
+
" {normalized_new:6.1%} |"
|
|
2081
|
+
)
|
|
2082
|
+
prt_str_title = (
|
|
2083
|
+
""
|
|
2084
|
+
" {site:>30} |"
|
|
2085
|
+
" {cl:>2} |"
|
|
2086
|
+
" {nq:>6} |"
|
|
2087
|
+
" {nr:>6} |"
|
|
2088
|
+
" {trr:>7} |"
|
|
2089
|
+
" {user_q_len:>5} |"
|
|
2090
|
+
" {max_q_len:>9} |"
|
|
2091
|
+
" {rem_q_len:>9} |"
|
|
2092
|
+
" {orig:>7} |"
|
|
2093
|
+
" {new:>7} |"
|
|
2094
|
+
" {normalized_orig:>6} |"
|
|
2095
|
+
" {normalized_new:>6} |"
|
|
2096
|
+
).format(
|
|
2097
|
+
site="Site",
|
|
2098
|
+
cl="Cl",
|
|
2099
|
+
nq="Q",
|
|
2100
|
+
nr="R",
|
|
2101
|
+
trr="TRR",
|
|
2102
|
+
user_q_len="UserQ",
|
|
2103
|
+
max_q_len="UserQ_max",
|
|
2104
|
+
rem_q_len="UserQ_rem",
|
|
2105
|
+
orig="Wb_orig",
|
|
2106
|
+
new="Wb_new",
|
|
2107
|
+
normalized_orig="orig_%",
|
|
2108
|
+
normalized_new="new_%",
|
|
2109
|
+
)
|
|
2110
|
+
prt_str_list.append(prt_str_title)
|
|
2111
|
+
for site in sorted(basic_weight_compar_map):
|
|
2112
|
+
bw_map = basic_weight_compar_map[site]
|
|
2113
|
+
prt_str = prt_str_temp.format(site=site, **{k: (x if x is not None else math.nan) for k, x in bw_map.items()})
|
|
2114
|
+
prt_str_list.append(prt_str)
|
|
2115
|
+
tmpLog.info(f"gshare: {taskSpec.gshare} ,{' merging,' if inputChunk.isMerging else ''} task_class: {task_class_value}")
|
|
2116
|
+
tmpLog.debug(
|
|
2117
|
+
"WEIGHT-COMPAR: for gshare={},{} cl={}, nJobsEst={} got \n{}".format(
|
|
2118
|
+
taskSpec.gshare, (" merging," if inputChunk.isMerging else ""), task_class_value, n_jobs_to_submit, "\n".join(prt_str_list)
|
|
2119
|
+
)
|
|
2120
|
+
)
|
|
2121
|
+
except Exception as e:
|
|
2122
|
+
tmpLog.error(f"{traceback.format_exc()}")
|
|
2123
|
+
# choose basic weight
|
|
2124
|
+
_basic_weight_version = "new"
|
|
2125
|
+
# finish computing weight
|
|
2126
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
2127
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
2128
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
2129
|
+
bw_map = basic_weight_compar_map[tmpSiteName]
|
|
2130
|
+
# fill basic weight
|
|
2131
|
+
weight = bw_map[_basic_weight_version]
|
|
2132
|
+
# penalty according to throttled jobs
|
|
2133
|
+
nThrottled = 0
|
|
2134
|
+
if tmpSiteName in remoteSourceList:
|
|
2135
|
+
nThrottled = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "throttled", workQueue_tag=taskSpec.gshare)
|
|
2136
|
+
weight /= float(nThrottled + 1)
|
|
2137
|
+
# normalize weights by taking data availability into account
|
|
2138
|
+
diskNorm = 10
|
|
2139
|
+
tapeNorm = 1000
|
|
2140
|
+
localSize = totalSize
|
|
2141
|
+
if checkDataLocality and not useUnionLocality:
|
|
2142
|
+
tmpDataWeight = 1
|
|
2143
|
+
if tmpSiteName in dataWeight:
|
|
2144
|
+
weight *= dataWeight[tmpSiteName]
|
|
2145
|
+
tmpDataWeight = dataWeight[tmpSiteName]
|
|
2146
|
+
else:
|
|
2147
|
+
tmpDataWeight = 1
|
|
2148
|
+
if totalSize > 0:
|
|
2149
|
+
if tmpSiteName in totalDiskSizeMap:
|
|
2150
|
+
tmpDataWeight += totalDiskSizeMap[tmpSiteName] / diskNorm
|
|
2151
|
+
localSize = totalDiskSizeMap[tmpSiteName]
|
|
2152
|
+
elif tmpSiteName in totalTapeSizeMap:
|
|
2153
|
+
tmpDataWeight += totalTapeSizeMap[tmpSiteName] / tapeNorm
|
|
2154
|
+
localSize = totalTapeSizeMap[tmpSiteName]
|
|
2155
|
+
weight *= tmpDataWeight
|
|
2156
|
+
# make candidate
|
|
2157
|
+
siteCandidateSpec = SiteCandidate(tmpPseudoSiteName, tmpSiteName)
|
|
2158
|
+
# preassigned
|
|
2159
|
+
if sitePreAssigned and tmpSiteName == preassignedSite:
|
|
2160
|
+
preSiteCandidateSpec = siteCandidateSpec
|
|
2161
|
+
# override attributes
|
|
2162
|
+
siteCandidateSpec.override_attribute("maxwdir", newMaxwdir.get(tmpSiteName))
|
|
2163
|
+
if cmt_config:
|
|
2164
|
+
platforms = resolved_platforms.get(tmpSiteName)
|
|
2165
|
+
if platforms:
|
|
2166
|
+
siteCandidateSpec.override_attribute("platforms", platforms)
|
|
2167
|
+
# set weight
|
|
2168
|
+
siteCandidateSpec.weight = weight
|
|
2169
|
+
tmpStr = (
|
|
2170
|
+
f"""weight={weight:.7f} """
|
|
2171
|
+
f"""gshare={taskSpec.gshare} class={bw_map["class"]} trr={bw_map["trr"]:.3f} """
|
|
2172
|
+
f"""userR={bw_map["user_r"]} userQ={bw_map["user_q_len"]} userQRem={bw_map["rem_q_len"]:.3f} """
|
|
2173
|
+
f"""nRunning={bw_map["nr"]} nDefined={bw_map["nDefined"]} nActivated={bw_map["nActivated"]} """
|
|
2174
|
+
f"""nStarting={bw_map["nStarting"]} nAssigned={bw_map["nAssigned"]} """
|
|
2175
|
+
f"""nFailed={bw_map["nFailed"]} nClosed={bw_map["nClosed"]} nFinished={bw_map["nFinished"]} """
|
|
2176
|
+
f"""dataW={tmpDataWeight} totalInGB={totalSize} localInGB={localSize} nFiles={totalNumFiles} """
|
|
2177
|
+
)
|
|
2178
|
+
weightStr[tmpPseudoSiteName] = tmpStr
|
|
2179
|
+
# append
|
|
2180
|
+
if tmpSiteName in sitesUsedByTask:
|
|
2181
|
+
candidateSpecList.append(siteCandidateSpec)
|
|
2182
|
+
else:
|
|
2183
|
+
if weight not in weightMap:
|
|
2184
|
+
weightMap[weight] = []
|
|
2185
|
+
weightMap[weight].append(siteCandidateSpec)
|
|
2186
|
+
# sort candidates by weights and data availability
|
|
2187
|
+
candidateSpecListWithData = []
|
|
2188
|
+
weightList = sorted(weightMap.keys())
|
|
2189
|
+
weightList.reverse()
|
|
2190
|
+
for weightVal in weightList:
|
|
2191
|
+
sitesWithWeight = weightMap[weightVal]
|
|
2192
|
+
for tmp_site in list(sitesWithWeight):
|
|
2193
|
+
if tmp_site in site_list_with_data:
|
|
2194
|
+
candidateSpecListWithData.append(tmp_site)
|
|
2195
|
+
sitesWithWeight.remove(tmp_site)
|
|
2196
|
+
random.shuffle(sitesWithWeight)
|
|
2197
|
+
candidateSpecList += sitesWithWeight
|
|
2198
|
+
candidateSpecList = candidateSpecListWithData + candidateSpecList
|
|
2199
|
+
# limit the number of sites
|
|
2200
|
+
if not hasDDS:
|
|
2201
|
+
if taskSpec.getNumSitesPerJob() > 1:
|
|
2202
|
+
# use the number of sites in the task spec for job cloning
|
|
2203
|
+
maxNumSites = max(len(site_list_with_data), taskSpec.getNumSitesPerJob())
|
|
2204
|
+
else:
|
|
2205
|
+
maxNumSites = 10
|
|
2206
|
+
else:
|
|
2207
|
+
# use all sites for distributed datasets
|
|
2208
|
+
maxNumSites = None
|
|
2209
|
+
# remove problematic sites
|
|
2210
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
2211
|
+
candidateSpecList = AtlasBrokerUtils.skipProblematicSites(
|
|
2212
|
+
candidateSpecList, set(problematic_sites_dict), sitesUsedByTask, preSiteCandidateSpec, maxNumSites, tmpLog
|
|
2213
|
+
)
|
|
2214
|
+
# append preassigned
|
|
2215
|
+
if sitePreAssigned and preSiteCandidateSpec is not None and preSiteCandidateSpec not in candidateSpecList:
|
|
2216
|
+
candidateSpecList.append(preSiteCandidateSpec)
|
|
2217
|
+
# collect site names
|
|
2218
|
+
scanSiteList = []
|
|
2219
|
+
for siteCandidateSpec in candidateSpecList:
|
|
2220
|
+
scanSiteList.append(siteCandidateSpec.siteName)
|
|
2221
|
+
# append candidates
|
|
2222
|
+
newScanSiteList = []
|
|
2223
|
+
msgList = []
|
|
2224
|
+
below_min_weight = []
|
|
2225
|
+
for siteCandidateSpec in candidateSpecList:
|
|
2226
|
+
tmpPseudoSiteName = siteCandidateSpec.siteName
|
|
2227
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
2228
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
2229
|
+
# preassigned
|
|
2230
|
+
if sitePreAssigned and tmpSiteName != preassignedSite:
|
|
2231
|
+
tmpLog.info(f" skip site={tmpPseudoSiteName} non pre-assigned site criteria=-nonpreassigned")
|
|
2232
|
+
try:
|
|
2233
|
+
del weightStr[tmpPseudoSiteName]
|
|
2234
|
+
except Exception:
|
|
2235
|
+
pass
|
|
2236
|
+
continue
|
|
2237
|
+
# below minimum brokerage weight
|
|
2238
|
+
if min_weight > 0 and siteCandidateSpec.weight < min_weight:
|
|
2239
|
+
below_min_weight.append(tmpSiteName)
|
|
2240
|
+
continue
|
|
2241
|
+
# set available files
|
|
2242
|
+
if inputChunk.getDatasets() == [] or (not checkDataLocality and not tmpSiteSpec.use_only_local_data()):
|
|
2243
|
+
isAvailable = True
|
|
2244
|
+
else:
|
|
2245
|
+
isAvailable = False
|
|
2246
|
+
isAvailableBase = isAvailable
|
|
2247
|
+
for tmpDatasetName, availableFiles in availableFileMap.items():
|
|
2248
|
+
tmpDatasetSpec = inputChunk.getDatasetWithName(tmpDatasetName)
|
|
2249
|
+
isAvailable = isAvailableBase
|
|
2250
|
+
# check remote files
|
|
2251
|
+
if tmpSiteName in remoteSourceList and tmpDatasetName in remoteSourceList[tmpSiteName] and not tmpSiteSpec.use_only_local_data():
|
|
2252
|
+
for tmpRemoteSite in remoteSourceList[tmpSiteName][tmpDatasetName]:
|
|
2253
|
+
if tmpRemoteSite in availableFiles and len(tmpDatasetSpec.Files) <= len(availableFiles[tmpRemoteSite]["localdisk"]):
|
|
2254
|
+
# use only remote disk files
|
|
2255
|
+
siteCandidateSpec.add_remote_files(availableFiles[tmpRemoteSite]["localdisk"])
|
|
2256
|
+
# set remote site and access protocol
|
|
2257
|
+
siteCandidateSpec.remoteProtocol = allowedRemoteProtocol
|
|
2258
|
+
siteCandidateSpec.remoteSource = tmpRemoteSite
|
|
2259
|
+
isAvailable = True
|
|
2260
|
+
break
|
|
2261
|
+
# local files
|
|
2262
|
+
if tmpSiteName in availableFiles:
|
|
2263
|
+
if (
|
|
2264
|
+
len(tmpDatasetSpec.Files) <= len(availableFiles[tmpSiteName]["localdisk"])
|
|
2265
|
+
or len(tmpDatasetSpec.Files) <= len(availableFiles[tmpSiteName]["cache"])
|
|
2266
|
+
or len(tmpDatasetSpec.Files) <= len(availableFiles[tmpSiteName]["localtape"])
|
|
2267
|
+
or tmpDatasetSpec.isDistributed()
|
|
2268
|
+
or true_complete_disk_ok[tmpDatasetName] is False
|
|
2269
|
+
or ((checkDataLocality is False or useUnionLocality) and not tmpSiteSpec.use_only_local_data())
|
|
2270
|
+
):
|
|
2271
|
+
siteCandidateSpec.add_local_disk_files(availableFiles[tmpSiteName]["localdisk"])
|
|
2272
|
+
# add cached files to local list since cached files go to pending when reassigned
|
|
2273
|
+
siteCandidateSpec.add_local_disk_files(availableFiles[tmpSiteName]["cache"])
|
|
2274
|
+
siteCandidateSpec.add_local_tape_files(availableFiles[tmpSiteName]["localtape"])
|
|
2275
|
+
siteCandidateSpec.add_cache_files(availableFiles[tmpSiteName]["cache"])
|
|
2276
|
+
siteCandidateSpec.add_remote_files(availableFiles[tmpSiteName]["remote"])
|
|
2277
|
+
siteCandidateSpec.addAvailableFiles(availableFiles[tmpSiteName]["all"])
|
|
2278
|
+
isAvailable = True
|
|
2279
|
+
else:
|
|
2280
|
+
tmpMsg = "{0} is incomplete at {1} : nFiles={2} nLocal={3} nCached={4} nTape={5}"
|
|
2281
|
+
tmpLog.debug(
|
|
2282
|
+
tmpMsg.format(
|
|
2283
|
+
tmpDatasetName,
|
|
2284
|
+
tmpPseudoSiteName,
|
|
2285
|
+
len(tmpDatasetSpec.Files),
|
|
2286
|
+
len(availableFiles[tmpSiteName]["localdisk"]),
|
|
2287
|
+
len(availableFiles[tmpSiteName]["cache"]),
|
|
2288
|
+
len(availableFiles[tmpSiteName]["localtape"]),
|
|
2289
|
+
)
|
|
2290
|
+
)
|
|
2291
|
+
if not isAvailable:
|
|
2292
|
+
break
|
|
2293
|
+
# append
|
|
2294
|
+
if not isAvailable and tmpSiteSpec.use_only_local_data():
|
|
2295
|
+
tmpLog.info(f" skip site={siteCandidateSpec.siteName} file unavailable criteria=-fileunavailable")
|
|
2296
|
+
try:
|
|
2297
|
+
del weightStr[siteCandidateSpec.siteName]
|
|
2298
|
+
except Exception:
|
|
2299
|
+
pass
|
|
2300
|
+
continue
|
|
2301
|
+
inputChunk.addSiteCandidate(siteCandidateSpec)
|
|
2302
|
+
newScanSiteList.append(siteCandidateSpec.siteName)
|
|
2303
|
+
tmpMsg = " use site={0} with {1} nLocalDisk={2} nLocalTape={3} nCache={4} nRemote={5} criteria=+use".format(
|
|
2304
|
+
siteCandidateSpec.siteName,
|
|
2305
|
+
weightStr[siteCandidateSpec.siteName],
|
|
2306
|
+
len(siteCandidateSpec.localDiskFiles),
|
|
2307
|
+
len(siteCandidateSpec.localTapeFiles),
|
|
2308
|
+
len(siteCandidateSpec.cacheFiles),
|
|
2309
|
+
len(siteCandidateSpec.remoteFiles),
|
|
2310
|
+
)
|
|
2311
|
+
msgList.append(tmpMsg)
|
|
2312
|
+
del weightStr[siteCandidateSpec.siteName]
|
|
2313
|
+
# dump
|
|
2314
|
+
for tmpPseudoSiteName in oldScanSiteList:
|
|
2315
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
2316
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
2317
|
+
tmpWeightStr = None
|
|
2318
|
+
if tmpSiteName in weightStr:
|
|
2319
|
+
tmpWeightStr = weightStr[tmpSiteName]
|
|
2320
|
+
elif tmpPseudoSiteName in weightStr:
|
|
2321
|
+
tmpWeightStr = weightStr[tmpPseudoSiteName]
|
|
2322
|
+
if tmpWeightStr is not None:
|
|
2323
|
+
if tmpSiteName in problematic_sites_dict:
|
|
2324
|
+
bad_reasons = " ; ".join(list(problematic_sites_dict[tmpSiteName]))
|
|
2325
|
+
tmpMsg = f" skip site={tmpPseudoSiteName} {bad_reasons} ; with {tmpWeightStr} criteria=-badsite"
|
|
2326
|
+
elif tmpSiteName in below_min_weight:
|
|
2327
|
+
tmpMsg = f" skip site={tmpPseudoSiteName} due to weight below the minimum {min_weight_param}={min_weight} with {tmpWeightStr} criteria=-below_min_weight"
|
|
2328
|
+
else:
|
|
2329
|
+
tmpMsg = f" skip site={tmpPseudoSiteName} due to low weight and not-used by old jobs with {tmpWeightStr} criteria=-low_weight"
|
|
2330
|
+
tmpLog.info(tmpMsg)
|
|
2331
|
+
for tmpMsg in msgList:
|
|
2332
|
+
tmpLog.info(tmpMsg)
|
|
2333
|
+
scanSiteList = newScanSiteList
|
|
2334
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "final check", tmpLog, {})
|
|
2335
|
+
if not scanSiteList:
|
|
2336
|
+
self.dump_summary(tmpLog)
|
|
2337
|
+
tmpLog.error("no candidates")
|
|
2338
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
2339
|
+
return retTmpError
|
|
2340
|
+
self.dump_summary(tmpLog, scanSiteList)
|
|
2341
|
+
# return
|
|
2342
|
+
tmpLog.debug("done")
|
|
2343
|
+
return self.SC_SUCCEEDED, inputChunk
|