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,2064 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
6
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
7
|
+
|
|
8
|
+
from pandajedi.jedicore import Interaction
|
|
9
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
10
|
+
from pandajedi.jedicore.SiteCandidate import SiteCandidate
|
|
11
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
12
|
+
from pandaserver.dataservice.DataServiceUtils import select_scope
|
|
13
|
+
from pandaserver.srvcore import CoreUtils
|
|
14
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils
|
|
15
|
+
|
|
16
|
+
from . import AtlasBrokerUtils
|
|
17
|
+
from .JobBrokerBase import JobBrokerBase
|
|
18
|
+
|
|
19
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
20
|
+
|
|
21
|
+
# definitions for network
|
|
22
|
+
AGIS_CLOSENESS = "AGIS_closeness"
|
|
23
|
+
BLOCKED_LINK = -1
|
|
24
|
+
MIN_CLOSENESS = 0 # closeness goes from 0(best) to 11(worst)
|
|
25
|
+
MAX_CLOSENESS = 11
|
|
26
|
+
# NWS tags need to be prepended with activity
|
|
27
|
+
TRANSFERRED_1H = "_done_1h"
|
|
28
|
+
TRANSFERRED_6H = "_done_6h"
|
|
29
|
+
QUEUED = "_queued"
|
|
30
|
+
ZERO_TRANSFERS = 0.00001
|
|
31
|
+
URG_ACTIVITY = "Express"
|
|
32
|
+
PRD_ACTIVITY = "Production Output"
|
|
33
|
+
# NWS tags for FTS throughput
|
|
34
|
+
FTS_1H = "dashb_mbps_1h"
|
|
35
|
+
FTS_1D = "dashb_mbps_1d"
|
|
36
|
+
FTS_1W = "dashb_mbps_1w"
|
|
37
|
+
APP = "jedi"
|
|
38
|
+
COMPONENT = "jobbroker"
|
|
39
|
+
VO = "atlas"
|
|
40
|
+
|
|
41
|
+
WORLD_NUCLEUS_WEIGHT = 4
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# brokerage for ATLAS production
|
|
45
|
+
class AtlasProdJobBroker(JobBrokerBase):
|
|
46
|
+
# constructor
|
|
47
|
+
def __init__(self, ddmIF, taskBufferIF):
|
|
48
|
+
JobBrokerBase.__init__(self, ddmIF, taskBufferIF)
|
|
49
|
+
self.dataSiteMap = {}
|
|
50
|
+
self.suppressLogSending = False
|
|
51
|
+
|
|
52
|
+
self.nwActive = taskBufferIF.getConfigValue(COMPONENT, "NW_ACTIVE", APP, VO)
|
|
53
|
+
if self.nwActive is None:
|
|
54
|
+
self.nwActive = False
|
|
55
|
+
|
|
56
|
+
self.nwQueueImportance = taskBufferIF.getConfigValue(COMPONENT, "NW_QUEUE_IMPORTANCE", APP, VO)
|
|
57
|
+
if self.nwQueueImportance is None:
|
|
58
|
+
self.nwQueueImportance = 0.5
|
|
59
|
+
self.nwThroughputImportance = 1 - self.nwQueueImportance
|
|
60
|
+
|
|
61
|
+
self.nw_threshold = taskBufferIF.getConfigValue(COMPONENT, "NW_THRESHOLD", APP, VO)
|
|
62
|
+
if self.nw_threshold is None:
|
|
63
|
+
self.nw_threshold = 1.7
|
|
64
|
+
|
|
65
|
+
self.queue_threshold = taskBufferIF.getConfigValue(COMPONENT, "NQUEUED_SAT_CAP", APP, VO)
|
|
66
|
+
if self.queue_threshold is None:
|
|
67
|
+
self.queue_threshold = 150
|
|
68
|
+
|
|
69
|
+
self.total_queue_threshold = taskBufferIF.getConfigValue(COMPONENT, "NQUEUED_NUC_CAP_FOR_JOBS", APP, VO)
|
|
70
|
+
if self.total_queue_threshold is None:
|
|
71
|
+
self.total_queue_threshold = 10000
|
|
72
|
+
|
|
73
|
+
self.nw_weight_multiplier = taskBufferIF.getConfigValue(COMPONENT, "NW_WEIGHT_MULTIPLIER", APP, VO)
|
|
74
|
+
if self.nw_weight_multiplier is None:
|
|
75
|
+
self.nw_weight_multiplier = 1
|
|
76
|
+
|
|
77
|
+
self.io_intensity_cutoff = taskBufferIF.getConfigValue(COMPONENT, "IO_INTENSITY_CUTOFF", APP, VO)
|
|
78
|
+
if self.io_intensity_cutoff is None:
|
|
79
|
+
self.io_intensity_cutoff = 200
|
|
80
|
+
|
|
81
|
+
self.max_prio_for_bootstrap = taskBufferIF.getConfigValue(COMPONENT, "MAX_PRIO_TO_BOOTSTRAP", APP, VO)
|
|
82
|
+
if self.max_prio_for_bootstrap is None:
|
|
83
|
+
self.max_prio_for_bootstrap = 150
|
|
84
|
+
|
|
85
|
+
# load the SW availability map
|
|
86
|
+
try:
|
|
87
|
+
self.sw_map = taskBufferIF.load_sw_map()
|
|
88
|
+
except BaseException:
|
|
89
|
+
logger.error("Failed to load the SW tags map!!!")
|
|
90
|
+
self.sw_map = {}
|
|
91
|
+
|
|
92
|
+
# load the worker node CPU architecture-level map
|
|
93
|
+
try:
|
|
94
|
+
self.architecture_level_map = taskBufferIF.get_architecture_level_map()
|
|
95
|
+
except BaseException:
|
|
96
|
+
logger.error("Failed to load the WN architecture level map!!!")
|
|
97
|
+
self.architecture_level_map = {}
|
|
98
|
+
|
|
99
|
+
def convertMBpsToWeight(self, mbps):
|
|
100
|
+
"""
|
|
101
|
+
Takes MBps value and converts to a weight between 1 and 2
|
|
102
|
+
"""
|
|
103
|
+
mbps_thresholds = [200, 100, 75, 50, 20, 10, 2, 1]
|
|
104
|
+
weights = [2, 1.9, 1.8, 1.6, 1.5, 1.3, 1.2, 1.1]
|
|
105
|
+
|
|
106
|
+
for threshold, weight in zip(mbps_thresholds, weights):
|
|
107
|
+
if mbps > threshold:
|
|
108
|
+
return weight
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
# main
|
|
112
|
+
def doBrokerage(self, taskSpec, cloudName, inputChunk, taskParamMap, hintForTB=False, siteListForTB=None, glLog=None):
|
|
113
|
+
# suppress sending log
|
|
114
|
+
if hintForTB:
|
|
115
|
+
self.suppressLogSending = True
|
|
116
|
+
# make logger
|
|
117
|
+
if glLog is None:
|
|
118
|
+
tmpLog = MsgWrapper(
|
|
119
|
+
logger,
|
|
120
|
+
f"<jediTaskID={taskSpec.jediTaskID}>",
|
|
121
|
+
monToken=f"<jediTaskID={taskSpec.jediTaskID} {naive_utcnow().isoformat('/')}>",
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
tmpLog = glLog
|
|
125
|
+
|
|
126
|
+
if not hintForTB:
|
|
127
|
+
tmpLog.debug(f"start currentPriority={taskSpec.currentPriority}")
|
|
128
|
+
|
|
129
|
+
if self.nwActive:
|
|
130
|
+
tmpLog.debug("Network weights are ACTIVE!")
|
|
131
|
+
else:
|
|
132
|
+
tmpLog.debug("Network weights are PASSIVE!")
|
|
133
|
+
|
|
134
|
+
timeNow = naive_utcnow()
|
|
135
|
+
|
|
136
|
+
# return for failure
|
|
137
|
+
retTmpError = self.SC_FAILED, inputChunk
|
|
138
|
+
|
|
139
|
+
# new maxwdir
|
|
140
|
+
newMaxwdir = {}
|
|
141
|
+
|
|
142
|
+
# short of work
|
|
143
|
+
work_shortage = self.taskBufferIF.getConfigValue("core", "WORK_SHORTAGE", APP, VO)
|
|
144
|
+
if work_shortage is True:
|
|
145
|
+
tmp_status, core_statistics = self.taskBufferIF.get_core_statistics(taskSpec.vo, taskSpec.prodSourceLabel)
|
|
146
|
+
if not tmp_status:
|
|
147
|
+
tmpLog.error("failed to get core statistics")
|
|
148
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
149
|
+
return retTmpError
|
|
150
|
+
tmpLog.debug(f"Work shortage is {work_shortage}")
|
|
151
|
+
else:
|
|
152
|
+
core_statistics = {}
|
|
153
|
+
|
|
154
|
+
# thresholds for incomplete datasets
|
|
155
|
+
max_missing_input_files = self.taskBufferIF.getConfigValue("jobbroker", "MAX_MISSING_INPUT_FILES", "jedi", taskSpec.vo)
|
|
156
|
+
if max_missing_input_files is None:
|
|
157
|
+
max_missing_input_files = 10
|
|
158
|
+
min_input_completeness = self.taskBufferIF.getConfigValue("jobbroker", "MIN_INPUT_COMPLETENESS", "jedi", taskSpec.vo)
|
|
159
|
+
if min_input_completeness is None:
|
|
160
|
+
min_input_completeness = 90
|
|
161
|
+
|
|
162
|
+
# minimum brokerage weight
|
|
163
|
+
min_weight_param = f"MIN_WEIGHT_{taskSpec.prodSourceLabel}_{taskSpec.gshare}"
|
|
164
|
+
min_weight = self.taskBufferIF.getConfigValue("jobbroker", min_weight_param, "jedi", taskSpec.vo)
|
|
165
|
+
if min_weight is None:
|
|
166
|
+
min_weight_param = f"MIN_WEIGHT_{taskSpec.prodSourceLabel}"
|
|
167
|
+
min_weight = self.taskBufferIF.getConfigValue("jobbroker", min_weight_param, "jedi", taskSpec.vo)
|
|
168
|
+
if min_weight is None:
|
|
169
|
+
min_weight = 0
|
|
170
|
+
|
|
171
|
+
# get sites in the cloud
|
|
172
|
+
siteSkippedTmp = dict()
|
|
173
|
+
sitePreAssigned = False
|
|
174
|
+
siteListPreAssigned = False
|
|
175
|
+
if siteListForTB is not None:
|
|
176
|
+
scanSiteList = siteListForTB
|
|
177
|
+
|
|
178
|
+
elif taskSpec.site not in ["", None] and inputChunk.getPreassignedSite() is None:
|
|
179
|
+
# convert to pseudo queues if needed
|
|
180
|
+
scanSiteList = []
|
|
181
|
+
for tmpSiteName in taskSpec.site.split(","):
|
|
182
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
183
|
+
if tmpSiteSpec is None or not tmpSiteSpec.is_unified:
|
|
184
|
+
scanSiteList.append(tmpSiteName)
|
|
185
|
+
else:
|
|
186
|
+
scanSiteList += self.get_pseudo_sites([tmpSiteName], self.siteMapper.getCloud(cloudName)["sites"])
|
|
187
|
+
for tmpSiteName in scanSiteList:
|
|
188
|
+
tmpLog.info(f"site={tmpSiteName} is pre-assigned criteria=+preassign")
|
|
189
|
+
if len(scanSiteList) > 1:
|
|
190
|
+
siteListPreAssigned = True
|
|
191
|
+
else:
|
|
192
|
+
sitePreAssigned = True
|
|
193
|
+
|
|
194
|
+
elif inputChunk.getPreassignedSite() is not None:
|
|
195
|
+
if (
|
|
196
|
+
inputChunk.masterDataset.creationTime is not None
|
|
197
|
+
and inputChunk.masterDataset.modificationTime is not None
|
|
198
|
+
and inputChunk.masterDataset.modificationTime != inputChunk.masterDataset.creationTime
|
|
199
|
+
and timeNow - inputChunk.masterDataset.modificationTime > datetime.timedelta(hours=24)
|
|
200
|
+
and taskSpec.frozenTime is not None
|
|
201
|
+
and timeNow - taskSpec.frozenTime > datetime.timedelta(hours=6)
|
|
202
|
+
):
|
|
203
|
+
# ignore pre-assigned site since pmerge is timed out
|
|
204
|
+
tmpLog.info("ignore pre-assigned for pmerge due to timeout")
|
|
205
|
+
scanSiteList = self.siteMapper.getCloud(cloudName)["sites"]
|
|
206
|
+
tmpLog.info(f"cloud={cloudName} has {len(scanSiteList)} candidates")
|
|
207
|
+
else:
|
|
208
|
+
# pmerge
|
|
209
|
+
siteListPreAssigned = True
|
|
210
|
+
scanSiteList = DataServiceUtils.getSitesShareDDM(self.siteMapper, inputChunk.getPreassignedSite(), JobUtils.PROD_PS, JobUtils.PROD_PS)
|
|
211
|
+
scanSiteList.append(inputChunk.getPreassignedSite())
|
|
212
|
+
tmp_msg = (
|
|
213
|
+
f"use site={scanSiteList} since they share DDM endpoints with original_site={inputChunk.getPreassignedSite()} "
|
|
214
|
+
f"which is pre-assigned in masterDS criteria=+premerge"
|
|
215
|
+
)
|
|
216
|
+
tmpLog.info(tmp_msg)
|
|
217
|
+
|
|
218
|
+
else:
|
|
219
|
+
scanSiteList = self.siteMapper.getCloud(cloudName)["sites"]
|
|
220
|
+
tmpLog.info(f"cloud={cloudName} has {len(scanSiteList)} candidates")
|
|
221
|
+
|
|
222
|
+
# high prio ES
|
|
223
|
+
if taskSpec.useEventService() and not taskSpec.useJobCloning() and (taskSpec.currentPriority >= 900 or inputChunk.useScout()):
|
|
224
|
+
esHigh = True
|
|
225
|
+
else:
|
|
226
|
+
esHigh = False
|
|
227
|
+
|
|
228
|
+
# get job statistics
|
|
229
|
+
tmpSt, jobStatMap = self.taskBufferIF.getJobStatisticsByGlobalShare(taskSpec.vo)
|
|
230
|
+
if not tmpSt:
|
|
231
|
+
tmpLog.error("failed to get job statistics")
|
|
232
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
233
|
+
return retTmpError
|
|
234
|
+
|
|
235
|
+
tmp_st, transferring_job_map = self.taskBufferIF.get_num_jobs_with_status_by_nucleus(taskSpec.vo, "transferring")
|
|
236
|
+
if not tmp_st:
|
|
237
|
+
tmpLog.error("failed to get transferring job statistics")
|
|
238
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
239
|
+
return retTmpError
|
|
240
|
+
|
|
241
|
+
# get destination for WORLD cloud
|
|
242
|
+
nucleusSpec = None
|
|
243
|
+
nucleus_with_storages_unwritable_over_wan = {}
|
|
244
|
+
if not hintForTB:
|
|
245
|
+
# get nucleus
|
|
246
|
+
nucleusSpec = self.siteMapper.getNucleus(taskSpec.nucleus)
|
|
247
|
+
if nucleusSpec is None:
|
|
248
|
+
tmpLog.error(f"unknown nucleus {taskSpec.nucleus}")
|
|
249
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
250
|
+
return retTmpError
|
|
251
|
+
sites_in_nucleus = nucleusSpec.allPandaSites
|
|
252
|
+
# get black list
|
|
253
|
+
for tmp_name, tmp_nucleus_dict in self.siteMapper.nuclei.items():
|
|
254
|
+
default_endpoint_out = tmp_nucleus_dict.get_default_endpoint_out()
|
|
255
|
+
if not default_endpoint_out:
|
|
256
|
+
continue
|
|
257
|
+
receive_output_over_wan = default_endpoint_out["detailed_status"].get("write_wan")
|
|
258
|
+
if receive_output_over_wan in ["OFF", "TEST"]:
|
|
259
|
+
nucleus_with_storages_unwritable_over_wan[tmp_name] = receive_output_over_wan
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
# use all sites in nuclei for WORLD task brokerage
|
|
263
|
+
sites_in_nucleus = []
|
|
264
|
+
for tmpNucleus in self.siteMapper.nuclei.values():
|
|
265
|
+
sites_in_nucleus += tmpNucleus.allPandaSites
|
|
266
|
+
|
|
267
|
+
# sites sharing SE with T1
|
|
268
|
+
if len(sites_in_nucleus) > 0:
|
|
269
|
+
sites_sharing_output_storages_in_nucleus = DataServiceUtils.getSitesShareDDM(
|
|
270
|
+
self.siteMapper, sites_in_nucleus[0], JobUtils.PROD_PS, JobUtils.PROD_PS, True
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
sites_sharing_output_storages_in_nucleus = []
|
|
274
|
+
|
|
275
|
+
# core count
|
|
276
|
+
if inputChunk.isMerging and taskSpec.mergeCoreCount is not None:
|
|
277
|
+
taskCoreCount = taskSpec.mergeCoreCount
|
|
278
|
+
else:
|
|
279
|
+
taskCoreCount = taskSpec.coreCount
|
|
280
|
+
|
|
281
|
+
# MP
|
|
282
|
+
if taskCoreCount is not None and taskCoreCount > 1:
|
|
283
|
+
# use MCORE only
|
|
284
|
+
useMP = "only"
|
|
285
|
+
elif taskCoreCount == 0 and (taskSpec.currentPriority >= 900 or inputChunk.useScout()):
|
|
286
|
+
# use MCORE only for ES scouts and close to completion
|
|
287
|
+
useMP = "only"
|
|
288
|
+
elif taskCoreCount == 0:
|
|
289
|
+
# use MCORE and normal
|
|
290
|
+
useMP = "any"
|
|
291
|
+
else:
|
|
292
|
+
# not use MCORE
|
|
293
|
+
useMP = "unuse"
|
|
294
|
+
|
|
295
|
+
# get workQueue
|
|
296
|
+
workQueue = self.taskBufferIF.getWorkQueueMap().getQueueWithIDGshare(taskSpec.workQueue_ID, taskSpec.gshare)
|
|
297
|
+
if workQueue.is_global_share:
|
|
298
|
+
wq_tag = workQueue.queue_name
|
|
299
|
+
wq_tag_global_share = wq_tag
|
|
300
|
+
else:
|
|
301
|
+
wq_tag = workQueue.queue_id
|
|
302
|
+
workQueueGS = self.taskBufferIF.getWorkQueueMap().getQueueWithIDGshare(None, taskSpec.gshare)
|
|
303
|
+
wq_tag_global_share = workQueueGS.queue_name
|
|
304
|
+
|
|
305
|
+
# init summary list
|
|
306
|
+
self.init_summary_list("Job brokerage summary", None, self.get_unified_sites(scanSiteList))
|
|
307
|
+
|
|
308
|
+
######################################
|
|
309
|
+
# check dataset completeness
|
|
310
|
+
remote_source_available = True
|
|
311
|
+
if inputChunk.getDatasets() and not taskSpec.inputPreStaging():
|
|
312
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
313
|
+
datasetName = datasetSpec.datasetName
|
|
314
|
+
# skip distributed datasets
|
|
315
|
+
is_distributed = self.ddmIF.isDistributedDataset(datasetName)
|
|
316
|
+
if is_distributed:
|
|
317
|
+
tmpLog.debug(f"completeness check disabled for {datasetName} since it is distributed")
|
|
318
|
+
continue
|
|
319
|
+
# check if complete replicas are available at online endpoints
|
|
320
|
+
(
|
|
321
|
+
tmpSt,
|
|
322
|
+
tmpRet,
|
|
323
|
+
tmp_complete_disk_ok,
|
|
324
|
+
tmp_complete_tape_ok,
|
|
325
|
+
tmp_truly_complete_disk,
|
|
326
|
+
tmp_can_be_local_source,
|
|
327
|
+
tmp_can_be_remote_source,
|
|
328
|
+
tmp_list_of_complete_replica_locations,
|
|
329
|
+
) = AtlasBrokerUtils.get_sites_with_data(
|
|
330
|
+
[],
|
|
331
|
+
self.siteMapper,
|
|
332
|
+
self.ddmIF,
|
|
333
|
+
datasetName,
|
|
334
|
+
[],
|
|
335
|
+
max_missing_input_files,
|
|
336
|
+
min_input_completeness,
|
|
337
|
+
)
|
|
338
|
+
if tmpSt != Interaction.SC_SUCCEEDED:
|
|
339
|
+
tmpLog.error(f"failed to get available storage endpoints with {datasetName}")
|
|
340
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
341
|
+
return retTmpError
|
|
342
|
+
# pending if the dataset is incomplete or missing at online endpoints
|
|
343
|
+
if not tmp_complete_disk_ok and not tmp_complete_tape_ok:
|
|
344
|
+
err_msg = f"{datasetName} is "
|
|
345
|
+
if tmp_list_of_complete_replica_locations:
|
|
346
|
+
tmp_rse_list = ",".join(tmp_list_of_complete_replica_locations)
|
|
347
|
+
tmp_is_single = len(tmp_list_of_complete_replica_locations) == 1
|
|
348
|
+
err_msg += f"only complete at {tmp_rse_list} which "
|
|
349
|
+
err_msg += "is " if tmp_is_single else "are "
|
|
350
|
+
err_msg += "currently in downtime or offline"
|
|
351
|
+
else:
|
|
352
|
+
err_msg += "incomplete at any online storage"
|
|
353
|
+
tmpLog.error(err_msg)
|
|
354
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
355
|
+
return retTmpError
|
|
356
|
+
tmp_msg = f"complete replicas of {datasetName} are available at online storages"
|
|
357
|
+
if not tmp_can_be_remote_source:
|
|
358
|
+
if tmp_can_be_local_source:
|
|
359
|
+
tmp_msg += ", but files cannot be sent out to satellites since read_wan is not ON"
|
|
360
|
+
remote_source_available = False
|
|
361
|
+
else:
|
|
362
|
+
tmp_msg = f"complete replicas of {datasetName} are available at endpoints where read_wan/lan are not ON"
|
|
363
|
+
tmpLog.error(tmp_msg)
|
|
364
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
365
|
+
return retTmpError
|
|
366
|
+
tmpLog.info(tmp_msg)
|
|
367
|
+
|
|
368
|
+
######################################
|
|
369
|
+
# selection for status
|
|
370
|
+
if not sitePreAssigned and not siteListPreAssigned:
|
|
371
|
+
newScanSiteList = []
|
|
372
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
373
|
+
msg_map = {}
|
|
374
|
+
tmpLog.set_message_slot()
|
|
375
|
+
for tmpSiteName in scanSiteList:
|
|
376
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
377
|
+
|
|
378
|
+
# skip unified queues
|
|
379
|
+
if tmpSiteSpec.is_unified:
|
|
380
|
+
continue
|
|
381
|
+
# check site status
|
|
382
|
+
msgFlag = False
|
|
383
|
+
skipFlag = False
|
|
384
|
+
|
|
385
|
+
if tmpSiteSpec.status in ["online", "standby"]:
|
|
386
|
+
newScanSiteList.append(tmpSiteName)
|
|
387
|
+
else:
|
|
388
|
+
msgFlag = True
|
|
389
|
+
skipFlag = True
|
|
390
|
+
|
|
391
|
+
if msgFlag:
|
|
392
|
+
tmpStr = f" skip site={tmpSiteName} due to status={tmpSiteSpec.status} criteria=-status"
|
|
393
|
+
if skipFlag:
|
|
394
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
395
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to status={tmpSiteSpec.status} criteria=-status"
|
|
396
|
+
)
|
|
397
|
+
else:
|
|
398
|
+
siteSkippedTmp[tmpSiteName] = tmpStr
|
|
399
|
+
|
|
400
|
+
tmpLog.set_message_slot()
|
|
401
|
+
scanSiteList = newScanSiteList
|
|
402
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "status check", tmpLog, msg_map)
|
|
403
|
+
if not scanSiteList:
|
|
404
|
+
self.dump_summary(tmpLog)
|
|
405
|
+
tmpLog.error("no candidates")
|
|
406
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
407
|
+
return retTmpError
|
|
408
|
+
|
|
409
|
+
#################################################
|
|
410
|
+
# get the nucleus and the network map
|
|
411
|
+
nucleus = taskSpec.nucleus
|
|
412
|
+
storageMapping = self.taskBufferIF.getPandaSiteToOutputStorageSiteMapping()
|
|
413
|
+
|
|
414
|
+
if nucleus:
|
|
415
|
+
# get connectivity stats to the nucleus
|
|
416
|
+
if inputChunk.isExpress():
|
|
417
|
+
transferred_tag = f"{URG_ACTIVITY}{TRANSFERRED_6H}"
|
|
418
|
+
queued_tag = f"{URG_ACTIVITY}{QUEUED}"
|
|
419
|
+
else:
|
|
420
|
+
transferred_tag = f"{PRD_ACTIVITY}{TRANSFERRED_6H}"
|
|
421
|
+
queued_tag = f"{PRD_ACTIVITY}{QUEUED}"
|
|
422
|
+
|
|
423
|
+
networkMap = self.taskBufferIF.getNetworkMetrics(nucleus, [AGIS_CLOSENESS, transferred_tag, queued_tag, FTS_1H, FTS_1D, FTS_1W])
|
|
424
|
+
|
|
425
|
+
#####################################################
|
|
426
|
+
# filtering out blacklisted or links with long queues
|
|
427
|
+
if nucleus:
|
|
428
|
+
# get the number of files being transferred to the nucleus
|
|
429
|
+
if queued_tag in networkMap["total"]:
|
|
430
|
+
totalQueued = networkMap["total"][queued_tag]
|
|
431
|
+
else:
|
|
432
|
+
totalQueued = 0
|
|
433
|
+
|
|
434
|
+
tmpLog.debug(f"Total number of files being transferred to the nucleus : {totalQueued}")
|
|
435
|
+
newScanSiteList = []
|
|
436
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
437
|
+
newSkippedTmp = dict()
|
|
438
|
+
msg_map = {}
|
|
439
|
+
tmpLog.set_message_slot()
|
|
440
|
+
for tmpPandaSiteName in self.get_unified_sites(scanSiteList):
|
|
441
|
+
try:
|
|
442
|
+
tmpAtlasSiteName = storageMapping[tmpPandaSiteName]["default"]
|
|
443
|
+
skipFlag = False
|
|
444
|
+
tempFlag = False
|
|
445
|
+
criteria = "-link_unusable"
|
|
446
|
+
from_str = f"from satellite={tmpAtlasSiteName}"
|
|
447
|
+
reason = ""
|
|
448
|
+
if nucleus == tmpAtlasSiteName:
|
|
449
|
+
# nucleus
|
|
450
|
+
pass
|
|
451
|
+
elif nucleus in nucleus_with_storages_unwritable_over_wan:
|
|
452
|
+
# destination blacklisted
|
|
453
|
+
reason = (
|
|
454
|
+
nucleusSpec.get_default_endpoint_out()["ddm_endpoint_name"]
|
|
455
|
+
+ f" at nucleus={nucleus} unwritable over WAN write_wan={nucleus_with_storages_unwritable_over_wan[nucleus]}"
|
|
456
|
+
)
|
|
457
|
+
criteria = "-dest_blacklisted"
|
|
458
|
+
from_str = ""
|
|
459
|
+
tempFlag = True
|
|
460
|
+
elif totalQueued >= self.total_queue_threshold:
|
|
461
|
+
# total exceed
|
|
462
|
+
reason = f"too many output files being transferred to the nucleus {totalQueued}(>{self.total_queue_threshold} total limit)"
|
|
463
|
+
criteria = "-links_full"
|
|
464
|
+
from_str = ""
|
|
465
|
+
tempFlag = True
|
|
466
|
+
elif tmpAtlasSiteName not in networkMap:
|
|
467
|
+
# Don't skip missing links for the moment. In later stages missing links
|
|
468
|
+
# default to the worst connectivity and will be penalized.
|
|
469
|
+
pass
|
|
470
|
+
elif AGIS_CLOSENESS in networkMap[tmpAtlasSiteName] and networkMap[tmpAtlasSiteName][AGIS_CLOSENESS] == BLOCKED_LINK:
|
|
471
|
+
# blocked link
|
|
472
|
+
reason = f"agis_closeness={networkMap[tmpAtlasSiteName][AGIS_CLOSENESS]}"
|
|
473
|
+
skipFlag = True
|
|
474
|
+
elif queued_tag in networkMap[tmpAtlasSiteName] and networkMap[tmpAtlasSiteName][queued_tag] >= self.queue_threshold:
|
|
475
|
+
# too many queued
|
|
476
|
+
reason = f"too many output files queued in the channel {networkMap[tmpAtlasSiteName][queued_tag]}(>{self.queue_threshold} link limit)"
|
|
477
|
+
tempFlag = True
|
|
478
|
+
|
|
479
|
+
# add
|
|
480
|
+
tmpStr = f" skip site={tmpPandaSiteName} due to {reason}"
|
|
481
|
+
if from_str:
|
|
482
|
+
tmpStr += f", {from_str} to nucleus={nucleus}"
|
|
483
|
+
tmpStr += f": criteria={criteria}"
|
|
484
|
+
if skipFlag:
|
|
485
|
+
msg_map[tmpPandaSiteName] = tmpStr
|
|
486
|
+
else:
|
|
487
|
+
newScanSiteList.append(tmpPandaSiteName)
|
|
488
|
+
if tempFlag:
|
|
489
|
+
newSkippedTmp[tmpPandaSiteName] = tmpStr
|
|
490
|
+
except KeyError:
|
|
491
|
+
# Don't skip missing links for the moment. In later stages missing links
|
|
492
|
+
# default to the worst connectivity and will be penalized.
|
|
493
|
+
newScanSiteList.append(tmpPandaSiteName)
|
|
494
|
+
tmpLog.unset_message_slot()
|
|
495
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
496
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
497
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "link check", tmpLog, msg_map)
|
|
498
|
+
if not scanSiteList:
|
|
499
|
+
self.dump_summary(tmpLog)
|
|
500
|
+
tmpLog.error("no candidates")
|
|
501
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
502
|
+
return retTmpError
|
|
503
|
+
######################################
|
|
504
|
+
# selection for high priorities
|
|
505
|
+
t1WeightForHighPrio = 1
|
|
506
|
+
if (taskSpec.currentPriority >= 800 or inputChunk.useScout()) and not sitePreAssigned and not siteListPreAssigned:
|
|
507
|
+
if not taskSpec.useEventService():
|
|
508
|
+
if taskSpec.currentPriority >= 900 or inputChunk.useScout():
|
|
509
|
+
t1WeightForHighPrio = 100
|
|
510
|
+
newScanSiteList = []
|
|
511
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
512
|
+
msg_map = {}
|
|
513
|
+
tmpLog.set_message_slot()
|
|
514
|
+
|
|
515
|
+
for tmpSiteName in scanSiteList:
|
|
516
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
517
|
+
if not tmpSiteSpec.is_opportunistic():
|
|
518
|
+
newScanSiteList.append(tmpSiteName)
|
|
519
|
+
else:
|
|
520
|
+
tmp_msg = f" skip site={tmpSiteName} to avoid opportunistic for high priority jobs "
|
|
521
|
+
tmp_msg += "criteria=-opportunistic"
|
|
522
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
523
|
+
f" skip site={tmpSiteSpec.get_unified_name()} to avoid opportunistic for high priority jobs criteria=-opportunistic"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
tmpLog.unset_message_slot()
|
|
527
|
+
scanSiteList = newScanSiteList
|
|
528
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "opportunistic check", tmpLog, msg_map)
|
|
529
|
+
|
|
530
|
+
if not scanSiteList:
|
|
531
|
+
self.dump_summary(tmpLog)
|
|
532
|
+
tmpLog.error("no candidates")
|
|
533
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
534
|
+
return retTmpError
|
|
535
|
+
|
|
536
|
+
######################################
|
|
537
|
+
# selection to avoid slow or inactive sites
|
|
538
|
+
if (
|
|
539
|
+
(taskSpec.currentPriority >= 800 or inputChunk.useScout() or inputChunk.isMerging or taskSpec.mergeOutput())
|
|
540
|
+
and not sitePreAssigned
|
|
541
|
+
and not siteListPreAssigned
|
|
542
|
+
):
|
|
543
|
+
# get inactive sites
|
|
544
|
+
inactiveTimeLimit = 2
|
|
545
|
+
inactiveSites = self.taskBufferIF.getInactiveSites_JEDI("production", inactiveTimeLimit)
|
|
546
|
+
newScanSiteList = []
|
|
547
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
548
|
+
newSkippedTmp = dict()
|
|
549
|
+
tmpLog.set_message_slot()
|
|
550
|
+
|
|
551
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
552
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
553
|
+
nToGetAll = AtlasBrokerUtils.getNumJobs(jobStatMap, tmpSiteSpec.get_unified_name(), "activated") + AtlasBrokerUtils.getNumJobs(
|
|
554
|
+
jobStatMap, tmpSiteSpec.get_unified_name(), "starting"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
if tmpSiteName in inactiveSites and nToGetAll > 0:
|
|
558
|
+
tmp_msg = (
|
|
559
|
+
f" skip site={tmpSiteName} since high prio/scouts/merge needs to avoid inactive sites "
|
|
560
|
+
f"(laststart is older than {inactiveTimeLimit}h) criteria=-inactive"
|
|
561
|
+
)
|
|
562
|
+
# temporary problem
|
|
563
|
+
newSkippedTmp[tmpSiteName] = tmp_msg
|
|
564
|
+
newScanSiteList.append(tmpSiteName)
|
|
565
|
+
|
|
566
|
+
tmpLog.unset_message_slot()
|
|
567
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
568
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
569
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "slowness/inactive check", tmpLog, {})
|
|
570
|
+
if not scanSiteList:
|
|
571
|
+
self.dump_summary(tmpLog)
|
|
572
|
+
tmpLog.error("no candidates")
|
|
573
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
574
|
+
return retTmpError
|
|
575
|
+
|
|
576
|
+
######################################
|
|
577
|
+
# selection for fairshare
|
|
578
|
+
if sitePreAssigned and taskSpec.prodSourceLabel not in [JobUtils.PROD_PS] or workQueue.queue_name not in ["test", "validation"]:
|
|
579
|
+
newScanSiteList = []
|
|
580
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
581
|
+
msg_map = {}
|
|
582
|
+
tmpLog.set_message_slot()
|
|
583
|
+
for tmpSiteName in scanSiteList:
|
|
584
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
585
|
+
# check at the site
|
|
586
|
+
if AtlasBrokerUtils.hasZeroShare(tmpSiteSpec, taskSpec, inputChunk.isMerging, tmpLog):
|
|
587
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} due to zero share criteria=-zeroshare"
|
|
588
|
+
continue
|
|
589
|
+
newScanSiteList.append(tmpSiteName)
|
|
590
|
+
tmpLog.unset_message_slot()
|
|
591
|
+
scanSiteList = newScanSiteList
|
|
592
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "zero share check", tmpLog, msg_map)
|
|
593
|
+
if not scanSiteList:
|
|
594
|
+
self.dump_summary(tmpLog)
|
|
595
|
+
tmpLog.error("no candidates")
|
|
596
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
597
|
+
return retTmpError
|
|
598
|
+
|
|
599
|
+
######################################
|
|
600
|
+
# selection for jumbo jobs
|
|
601
|
+
if not sitePreAssigned and taskSpec.getNumJumboJobs() is None:
|
|
602
|
+
newScanSiteList = []
|
|
603
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
604
|
+
msg_map = {}
|
|
605
|
+
tmpLog.set_message_slot()
|
|
606
|
+
for tmpSiteName in scanSiteList:
|
|
607
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
608
|
+
if tmpSiteSpec.useJumboJobs():
|
|
609
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} since it is only for jumbo jobs criteria=-jumbo"
|
|
610
|
+
continue
|
|
611
|
+
newScanSiteList.append(tmpSiteName)
|
|
612
|
+
tmpLog.unset_message_slot()
|
|
613
|
+
scanSiteList = newScanSiteList
|
|
614
|
+
tmpLog.info(f"{len(scanSiteList)} candidates passed jumbo job check")
|
|
615
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "jumbo job check", tmpLog, msg_map)
|
|
616
|
+
if scanSiteList == []:
|
|
617
|
+
self.dump_summary(tmpLog)
|
|
618
|
+
tmpLog.error("no candidates")
|
|
619
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
620
|
+
return retTmpError
|
|
621
|
+
elif not sitePreAssigned and taskSpec.getNumJumboJobs() is not None:
|
|
622
|
+
nReadyEvents = self.taskBufferIF.getNumReadyEvents(taskSpec.jediTaskID)
|
|
623
|
+
if nReadyEvents is not None:
|
|
624
|
+
newScanSiteList = []
|
|
625
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
626
|
+
msg_map = {}
|
|
627
|
+
tmpLog.set_message_slot()
|
|
628
|
+
for tmpSiteName in scanSiteList:
|
|
629
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
630
|
+
if tmpSiteSpec.useJumboJobs():
|
|
631
|
+
minEvents = tmpSiteSpec.getMinEventsForJumbo()
|
|
632
|
+
if minEvents is not None and nReadyEvents < minEvents:
|
|
633
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
634
|
+
f" skip site={tmpSiteSpec.get_unified_name()} since not enough events ({minEvents}<{nReadyEvents}) "
|
|
635
|
+
"for jumbo criteria=-fewevents"
|
|
636
|
+
)
|
|
637
|
+
continue
|
|
638
|
+
newScanSiteList.append(tmpSiteName)
|
|
639
|
+
tmpLog.unset_message_slot()
|
|
640
|
+
scanSiteList = newScanSiteList
|
|
641
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "jumbo events check", tmpLog, msg_map)
|
|
642
|
+
if not scanSiteList:
|
|
643
|
+
self.dump_summary(tmpLog)
|
|
644
|
+
tmpLog.error("no candidates")
|
|
645
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
646
|
+
return retTmpError
|
|
647
|
+
|
|
648
|
+
######################################
|
|
649
|
+
# selection for iointensity limits
|
|
650
|
+
|
|
651
|
+
# get default disk IO limit from GDP config
|
|
652
|
+
max_diskio_per_core_default = self.taskBufferIF.getConfigValue(COMPONENT, "MAX_DISKIO_DEFAULT", APP, VO)
|
|
653
|
+
if not max_diskio_per_core_default:
|
|
654
|
+
max_diskio_per_core_default = 10**10
|
|
655
|
+
|
|
656
|
+
# get the current disk IO usage per site
|
|
657
|
+
diskio_percore_usage = self.taskBufferIF.getAvgDiskIO_JEDI()
|
|
658
|
+
unified_site_list = self.get_unified_sites(scanSiteList)
|
|
659
|
+
newScanSiteList = []
|
|
660
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
661
|
+
msg_map = {}
|
|
662
|
+
tmpLog.set_message_slot()
|
|
663
|
+
newSkippedTmp = dict()
|
|
664
|
+
for tmpSiteName in unified_site_list:
|
|
665
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
666
|
+
|
|
667
|
+
# measured diskIO at queue
|
|
668
|
+
diskio_usage_tmp = diskio_percore_usage.get(tmpSiteName, 0)
|
|
669
|
+
|
|
670
|
+
# figure out queue or default limit
|
|
671
|
+
if tmpSiteSpec.maxDiskio and tmpSiteSpec.maxDiskio > 0:
|
|
672
|
+
# there is a limit specified in AGIS
|
|
673
|
+
diskio_limit_tmp = tmpSiteSpec.maxDiskio
|
|
674
|
+
else:
|
|
675
|
+
# we need to use the default value from GDP Config
|
|
676
|
+
diskio_limit_tmp = max_diskio_per_core_default
|
|
677
|
+
|
|
678
|
+
# normalize task diskIO by site corecount
|
|
679
|
+
diskio_task_tmp = taskSpec.diskIO
|
|
680
|
+
if taskSpec.diskIO is not None and taskSpec.coreCount not in [None, 0, 1] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
681
|
+
diskio_task_tmp = taskSpec.diskIO // tmpSiteSpec.coreCount
|
|
682
|
+
|
|
683
|
+
try: # generate a log message parseable by logstash for monitoring
|
|
684
|
+
log_msg = f"diskIO measurements: site={tmpSiteName} jediTaskID={taskSpec.jediTaskID} "
|
|
685
|
+
if diskio_task_tmp is not None:
|
|
686
|
+
log_msg += f"diskIO_task={diskio_task_tmp:.2f} "
|
|
687
|
+
if diskio_usage_tmp is not None:
|
|
688
|
+
log_msg += f"diskIO_site_usage={diskio_usage_tmp:.2f} "
|
|
689
|
+
if diskio_limit_tmp is not None:
|
|
690
|
+
log_msg += f"diskIO_site_limit={diskio_limit_tmp:.2f} "
|
|
691
|
+
tmpLog.info(log_msg)
|
|
692
|
+
except Exception:
|
|
693
|
+
tmpLog.debug("diskIO measurements: Error generating diskIO message")
|
|
694
|
+
|
|
695
|
+
# if the task has a diskIO defined, the queue is over the IO limit and the task IO is over the limit
|
|
696
|
+
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:
|
|
697
|
+
tmp_msg = f" skip site={tmpSiteName} due to diskIO overload criteria=-diskIO"
|
|
698
|
+
newSkippedTmp[tmpSiteName] = tmp_msg
|
|
699
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
700
|
+
|
|
701
|
+
newScanSiteList.append(tmpSiteName)
|
|
702
|
+
|
|
703
|
+
tmpLog.unset_message_slot()
|
|
704
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
705
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
706
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "diskIO check", tmpLog, msg_map)
|
|
707
|
+
if not scanSiteList:
|
|
708
|
+
self.dump_summary(tmpLog)
|
|
709
|
+
tmpLog.error("no candidates")
|
|
710
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
711
|
+
return retTmpError
|
|
712
|
+
|
|
713
|
+
######################################
|
|
714
|
+
# selection for MP
|
|
715
|
+
if not sitePreAssigned:
|
|
716
|
+
newScanSiteList = []
|
|
717
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
718
|
+
msg_map = {}
|
|
719
|
+
tmpLog.set_message_slot()
|
|
720
|
+
max_core_count = taskSpec.get_max_core_count()
|
|
721
|
+
for tmpSiteName in scanSiteList:
|
|
722
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
723
|
+
# check at the site
|
|
724
|
+
if useMP == "any" or (useMP == "only" and tmpSiteSpec.coreCount > 1) or (useMP == "unuse" and tmpSiteSpec.coreCount in [0, 1, None]):
|
|
725
|
+
if max_core_count and tmpSiteSpec.coreCount and tmpSiteSpec.coreCount > max_core_count:
|
|
726
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
727
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to larger core count site:{tmpSiteSpec.coreCount} "
|
|
728
|
+
f"than task_max={max_core_count} criteria=-max_cpucore"
|
|
729
|
+
)
|
|
730
|
+
else:
|
|
731
|
+
newScanSiteList.append(tmpSiteName)
|
|
732
|
+
else:
|
|
733
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
734
|
+
f" skip site={tmpSiteSpec.get_unified_name()} due to core mismatch "
|
|
735
|
+
f"site:{tmpSiteSpec.coreCount} <> task:{taskCoreCount} criteria=-cpucore"
|
|
736
|
+
)
|
|
737
|
+
tmpLog.unset_message_slot()
|
|
738
|
+
scanSiteList = newScanSiteList
|
|
739
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "core count check", tmpLog, msg_map)
|
|
740
|
+
if not scanSiteList:
|
|
741
|
+
self.dump_summary(tmpLog)
|
|
742
|
+
tmpLog.error("no candidates")
|
|
743
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
744
|
+
return retTmpError
|
|
745
|
+
|
|
746
|
+
######################################
|
|
747
|
+
# selection for release and CPU/GPU architectures
|
|
748
|
+
cmt_config = taskSpec.get_sw_platform()
|
|
749
|
+
is_regexp_cmt_config = False
|
|
750
|
+
if cmt_config:
|
|
751
|
+
if re.match(cmt_config, cmt_config) is None:
|
|
752
|
+
is_regexp_cmt_config = True
|
|
753
|
+
base_platform = taskSpec.get_base_platform()
|
|
754
|
+
resolved_platforms = {}
|
|
755
|
+
preference_weight_map = {}
|
|
756
|
+
if taskSpec.transHome is not None:
|
|
757
|
+
jsonCheck = AtlasBrokerUtils.JsonSoftwareCheck(self.siteMapper, self.sw_map, self.architecture_level_map)
|
|
758
|
+
unified_site_list = self.get_unified_sites(scanSiteList)
|
|
759
|
+
|
|
760
|
+
host_cpu_spec = taskSpec.get_host_cpu_spec()
|
|
761
|
+
host_cpu_pref = taskSpec.get_host_cpu_preference()
|
|
762
|
+
host_gpu_spec = taskSpec.get_host_gpu_spec()
|
|
763
|
+
|
|
764
|
+
if taskSpec.get_base_platform() is None:
|
|
765
|
+
use_container = False
|
|
766
|
+
else:
|
|
767
|
+
use_container = True
|
|
768
|
+
|
|
769
|
+
need_cvmfs = False
|
|
770
|
+
|
|
771
|
+
# 3 digits base release or normal tasks
|
|
772
|
+
if (re.search("-\d+\.\d+\.\d+$", taskSpec.transHome) is not None) or (
|
|
773
|
+
re.search("rel_\d+(\n|$)", taskSpec.transHome) is None and re.search("\d{4}-\d{2}-\d{2}T\d{4}$", taskSpec.transHome) is None
|
|
774
|
+
):
|
|
775
|
+
cvmfs_repo = "atlas"
|
|
776
|
+
sw_project = taskSpec.transHome.split("-")[0]
|
|
777
|
+
sw_version = taskSpec.transHome.split("-")[1]
|
|
778
|
+
cmt_config_only = False
|
|
779
|
+
|
|
780
|
+
# nightlies
|
|
781
|
+
else:
|
|
782
|
+
cvmfs_repo = "nightlies"
|
|
783
|
+
sw_project = None
|
|
784
|
+
sw_version = None
|
|
785
|
+
cmt_config_only = False
|
|
786
|
+
|
|
787
|
+
siteListWithSW, sitesNoJsonCheck, preference_weight_map = jsonCheck.check(
|
|
788
|
+
unified_site_list,
|
|
789
|
+
cvmfs_repo,
|
|
790
|
+
sw_project,
|
|
791
|
+
sw_version,
|
|
792
|
+
cmt_config,
|
|
793
|
+
need_cvmfs,
|
|
794
|
+
cmt_config_only,
|
|
795
|
+
need_container=use_container,
|
|
796
|
+
container_name=taskSpec.container_name,
|
|
797
|
+
only_tags_fc=taskSpec.use_only_tags_fc(),
|
|
798
|
+
host_cpu_specs=host_cpu_spec,
|
|
799
|
+
host_cpu_pref=host_cpu_pref,
|
|
800
|
+
host_gpu_spec=host_gpu_spec,
|
|
801
|
+
log_stream=tmpLog,
|
|
802
|
+
)
|
|
803
|
+
sitesAuto = copy.copy(siteListWithSW)
|
|
804
|
+
sitesAny = []
|
|
805
|
+
|
|
806
|
+
newScanSiteList = []
|
|
807
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
808
|
+
msg_map = {}
|
|
809
|
+
tmpLog.set_message_slot()
|
|
810
|
+
|
|
811
|
+
for tmpSiteName in unified_site_list:
|
|
812
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
813
|
+
if cmt_config:
|
|
814
|
+
platforms = AtlasBrokerUtils.resolve_cmt_config(tmpSiteName, cmt_config, base_platform, self.sw_map)
|
|
815
|
+
if platforms:
|
|
816
|
+
resolved_platforms[tmpSiteName] = platforms
|
|
817
|
+
if tmpSiteName in siteListWithSW:
|
|
818
|
+
# passed
|
|
819
|
+
if not is_regexp_cmt_config or tmpSiteName in resolved_platforms:
|
|
820
|
+
newScanSiteList.append(tmpSiteName)
|
|
821
|
+
else:
|
|
822
|
+
# cmtconfig is not resolved
|
|
823
|
+
msg_map[tmpSiteName] = f" skip site={tmpSiteName} due to unresolved regexp in cmtconfig={cmt_config} criteria=-regexpcmtconfig"
|
|
824
|
+
elif (
|
|
825
|
+
not (taskSpec.container_name and taskSpec.use_only_tags_fc())
|
|
826
|
+
and host_cpu_spec is None
|
|
827
|
+
and host_gpu_spec is None
|
|
828
|
+
and tmpSiteSpec.releases == ["ANY"]
|
|
829
|
+
):
|
|
830
|
+
# release check is disabled or release is available
|
|
831
|
+
newScanSiteList.append(tmpSiteName)
|
|
832
|
+
sitesAny.append(tmpSiteName)
|
|
833
|
+
else:
|
|
834
|
+
if tmpSiteSpec.releases == ["AUTO"]:
|
|
835
|
+
autoStr = "with AUTO"
|
|
836
|
+
else:
|
|
837
|
+
autoStr = "without AUTO"
|
|
838
|
+
# release is unavailable
|
|
839
|
+
msg_map[tmpSiteName] = (
|
|
840
|
+
f" skip site={tmpSiteName} {autoStr} due to missing SW cache={taskSpec.transHome}:{taskSpec.get_sw_platform()} sw_platform='{taskSpec.container_name}' "
|
|
841
|
+
f"or irrelevant HW cpu={str(host_cpu_spec)} gpu={str(host_gpu_spec)} criteria=-cache"
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
tmpLog.unset_message_slot()
|
|
845
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
846
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "SW/HW check", tmpLog, msg_map)
|
|
847
|
+
tmpLog.info(f" {len(sitesAuto)} with AUTO, {len(sitesAny)} with ANY")
|
|
848
|
+
if not scanSiteList:
|
|
849
|
+
self.dump_summary(tmpLog)
|
|
850
|
+
tmpLog.error("no candidates")
|
|
851
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
852
|
+
return retTmpError
|
|
853
|
+
|
|
854
|
+
######################################
|
|
855
|
+
# selection for memory
|
|
856
|
+
origMinRamCount = inputChunk.getMaxRamCount()
|
|
857
|
+
if origMinRamCount not in [0, None]:
|
|
858
|
+
if inputChunk.isMerging:
|
|
859
|
+
strMinRamCount = f"{origMinRamCount}(MB)"
|
|
860
|
+
else:
|
|
861
|
+
str_ram_unit = taskSpec.ramUnit
|
|
862
|
+
if str_ram_unit:
|
|
863
|
+
str_ram_unit = str_ram_unit.replace("PerCore", " ").strip()
|
|
864
|
+
strMinRamCount = f"{origMinRamCount}({str_ram_unit})"
|
|
865
|
+
if not inputChunk.isMerging and taskSpec.baseRamCount not in [0, None]:
|
|
866
|
+
strMinRamCount += f"+{taskSpec.baseRamCount}"
|
|
867
|
+
newScanSiteList = []
|
|
868
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
869
|
+
msg_map = {}
|
|
870
|
+
tmpLog.set_message_slot()
|
|
871
|
+
for tmpSiteName in scanSiteList:
|
|
872
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
873
|
+
# job memory requirement
|
|
874
|
+
minRamCount = origMinRamCount
|
|
875
|
+
if taskSpec.ramPerCore() and not inputChunk.isMerging:
|
|
876
|
+
if tmpSiteSpec.coreCount not in [None, 0]:
|
|
877
|
+
minRamCount = origMinRamCount * tmpSiteSpec.coreCount
|
|
878
|
+
minRamCount += taskSpec.baseRamCount
|
|
879
|
+
# compensate
|
|
880
|
+
minRamCount = JobUtils.compensate_ram_count(minRamCount)
|
|
881
|
+
# site max memory requirement
|
|
882
|
+
site_maxmemory = 0
|
|
883
|
+
if tmpSiteSpec.maxrss not in [0, None]:
|
|
884
|
+
site_maxmemory = tmpSiteSpec.maxrss
|
|
885
|
+
# check at the site
|
|
886
|
+
if site_maxmemory not in [0, None] and minRamCount != 0 and minRamCount > site_maxmemory:
|
|
887
|
+
tmp_msg = f" skip site={tmpSiteName} due to sue to insufficient RAM less than less than job's core-scaled requirement {minRamCount} MB "
|
|
888
|
+
tmp_msg += "criteria=-lowmemory"
|
|
889
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
890
|
+
continue
|
|
891
|
+
# site min memory requirement
|
|
892
|
+
site_minmemory = 0
|
|
893
|
+
if tmpSiteSpec.minrss not in [0, None]:
|
|
894
|
+
site_minmemory = tmpSiteSpec.minrss
|
|
895
|
+
if site_minmemory not in [0, None] and minRamCount != 0 and minRamCount < site_minmemory:
|
|
896
|
+
tmp_msg = f" skip site={tmpSiteName} due to RAM lower limit greater than than job's core-scaled requirement {minRamCount} MB "
|
|
897
|
+
tmp_msg += "criteria=-highmemory"
|
|
898
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
899
|
+
continue
|
|
900
|
+
newScanSiteList.append(tmpSiteName)
|
|
901
|
+
tmpLog.unset_message_slot()
|
|
902
|
+
scanSiteList = newScanSiteList
|
|
903
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "memory check", tmpLog, msg_map)
|
|
904
|
+
if not scanSiteList:
|
|
905
|
+
self.dump_summary(tmpLog)
|
|
906
|
+
tmpLog.error("no candidates")
|
|
907
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
908
|
+
return retTmpError
|
|
909
|
+
|
|
910
|
+
######################################
|
|
911
|
+
# selection for scratch disk
|
|
912
|
+
if taskSpec.outputScaleWithEvents():
|
|
913
|
+
minDiskCount = max(taskSpec.getOutDiskSize() * inputChunk.getMaxAtomSize(getNumEvents=True), inputChunk.defaultOutputSize)
|
|
914
|
+
else:
|
|
915
|
+
minDiskCount = max(taskSpec.getOutDiskSize() * inputChunk.getMaxAtomSize(effectiveSize=True), inputChunk.defaultOutputSize)
|
|
916
|
+
minDiskCount += taskSpec.getWorkDiskSize()
|
|
917
|
+
minDiskCountL = minDiskCount
|
|
918
|
+
minDiskCountD = minDiskCount
|
|
919
|
+
minDiskCountL += inputChunk.getMaxAtomSize()
|
|
920
|
+
minDiskCountL = minDiskCountL // 1024 // 1024
|
|
921
|
+
minDiskCountD = minDiskCountD // 1024 // 1024
|
|
922
|
+
newScanSiteList = []
|
|
923
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
924
|
+
msg_map = {}
|
|
925
|
+
tmpLog.set_message_slot()
|
|
926
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
927
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
928
|
+
# check direct access
|
|
929
|
+
if taskSpec.allowInputLAN() == "only" and not tmpSiteSpec.isDirectIO() and not tmpSiteSpec.always_use_direct_io() and not inputChunk.isMerging:
|
|
930
|
+
tmp_msg = f" skip site={tmpSiteName} since direct IO is disabled "
|
|
931
|
+
tmp_msg += "criteria=-remoteio"
|
|
932
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
933
|
+
continue
|
|
934
|
+
# check scratch size
|
|
935
|
+
if tmpSiteSpec.maxwdir != 0:
|
|
936
|
+
if CoreUtils.use_direct_io_for_job(taskSpec, tmpSiteSpec, inputChunk):
|
|
937
|
+
# size for remote access
|
|
938
|
+
minDiskCount = minDiskCountD
|
|
939
|
+
else:
|
|
940
|
+
# size for copy-to-scratch
|
|
941
|
+
minDiskCount = minDiskCountL
|
|
942
|
+
|
|
943
|
+
# get site and task corecount to scale maxwdir
|
|
944
|
+
if tmpSiteSpec.coreCount in [None, 0, 1]:
|
|
945
|
+
site_cc = 1
|
|
946
|
+
else:
|
|
947
|
+
site_cc = tmpSiteSpec.coreCount
|
|
948
|
+
|
|
949
|
+
if taskSpec.coreCount in [None, 0, 1] and not taskSpec.useEventService():
|
|
950
|
+
task_cc = 1
|
|
951
|
+
else:
|
|
952
|
+
task_cc = site_cc
|
|
953
|
+
|
|
954
|
+
maxwdir_scaled = tmpSiteSpec.maxwdir * task_cc / site_cc
|
|
955
|
+
|
|
956
|
+
if minDiskCount > maxwdir_scaled:
|
|
957
|
+
tmp_msg = f" skip site={tmpSiteName} due to small scratch disk {maxwdir_scaled} MB less than {minDiskCount} MB"
|
|
958
|
+
tmp_msg += " criteria=-disk"
|
|
959
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
960
|
+
continue
|
|
961
|
+
newMaxwdir[tmpSiteName] = maxwdir_scaled
|
|
962
|
+
newScanSiteList.append(tmpSiteName)
|
|
963
|
+
tmpLog.unset_message_slot()
|
|
964
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
965
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "disk check", tmpLog, msg_map)
|
|
966
|
+
if not scanSiteList:
|
|
967
|
+
self.dump_summary(tmpLog)
|
|
968
|
+
tmpLog.error("no candidates")
|
|
969
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
970
|
+
return retTmpError
|
|
971
|
+
|
|
972
|
+
######################################
|
|
973
|
+
# selection for available space in SE
|
|
974
|
+
newScanSiteList = []
|
|
975
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
976
|
+
msg_map = {}
|
|
977
|
+
tmpLog.set_message_slot()
|
|
978
|
+
newSkippedTmp = dict()
|
|
979
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
980
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
981
|
+
scope_input, scope_output = select_scope(tmpSiteSpec, JobUtils.PROD_PS, JobUtils.PROD_PS)
|
|
982
|
+
# check endpoint
|
|
983
|
+
tmp_default_output_endpoint = tmpSiteSpec.ddm_endpoints_output[scope_output].getEndPoint(tmpSiteSpec.ddm_output[scope_output])
|
|
984
|
+
tmp_msg = None
|
|
985
|
+
# check free size on output endpoint
|
|
986
|
+
if tmp_default_output_endpoint is not None:
|
|
987
|
+
tmpSpaceSize = 0
|
|
988
|
+
if tmp_default_output_endpoint["space_free"] is not None:
|
|
989
|
+
tmpSpaceSize += tmp_default_output_endpoint["space_free"]
|
|
990
|
+
if tmp_default_output_endpoint["space_expired"] is not None:
|
|
991
|
+
tmpSpaceSize += tmp_default_output_endpoint["space_expired"]
|
|
992
|
+
diskThreshold = 200
|
|
993
|
+
|
|
994
|
+
# skip_RSE_check: exceptional bypass of RSEs without storage reporting
|
|
995
|
+
if tmpSpaceSize < diskThreshold and "skip_RSE_check" not in tmpSiteSpec.catchall:
|
|
996
|
+
tmp_msg = (
|
|
997
|
+
f" skip site={tmpSiteName} due to disk shortage at "
|
|
998
|
+
f"{tmpSiteSpec.ddm_output[scope_output]} {tmpSpaceSize}GB < {diskThreshold}GB criteria=-disk"
|
|
999
|
+
)
|
|
1000
|
+
# check if blacklisted
|
|
1001
|
+
if not tmp_msg:
|
|
1002
|
+
tmp_msg = AtlasBrokerUtils.check_endpoints_with_blacklist(tmpSiteSpec, scope_input, scope_output, sites_in_nucleus, remote_source_available)
|
|
1003
|
+
if tmp_msg is not None:
|
|
1004
|
+
newSkippedTmp[tmpSiteName] = tmp_msg
|
|
1005
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
1006
|
+
newScanSiteList.append(tmpSiteName)
|
|
1007
|
+
tmpLog.unset_message_slot()
|
|
1008
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
1009
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1010
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "Storage check", tmpLog, msg_map)
|
|
1011
|
+
if not scanSiteList:
|
|
1012
|
+
self.dump_summary(tmpLog)
|
|
1013
|
+
tmpLog.error("no candidates")
|
|
1014
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1015
|
+
return retTmpError
|
|
1016
|
+
|
|
1017
|
+
######################################
|
|
1018
|
+
# selection for walltime
|
|
1019
|
+
if taskSpec.useEventService() and not taskSpec.useJobCloning():
|
|
1020
|
+
nEsConsumers = taskSpec.getNumEventServiceConsumer()
|
|
1021
|
+
if nEsConsumers is None:
|
|
1022
|
+
nEsConsumers = 1
|
|
1023
|
+
maxAttemptEsJob = taskSpec.getMaxAttemptEsJob()
|
|
1024
|
+
if maxAttemptEsJob is None:
|
|
1025
|
+
maxAttemptEsJob = EventServiceUtils.defMaxAttemptEsJob
|
|
1026
|
+
else:
|
|
1027
|
+
maxAttemptEsJob += 1
|
|
1028
|
+
else:
|
|
1029
|
+
nEsConsumers = 1
|
|
1030
|
+
maxAttemptEsJob = 1
|
|
1031
|
+
maxWalltime = None
|
|
1032
|
+
maxWalltime_dyn = None
|
|
1033
|
+
minWalltime_dyn = None
|
|
1034
|
+
minWalltime = None
|
|
1035
|
+
strMaxWalltime = None
|
|
1036
|
+
strMaxWalltime_dyn = None
|
|
1037
|
+
strMinWalltime_dyn = None
|
|
1038
|
+
if not taskSpec.useHS06():
|
|
1039
|
+
tmpMaxAtomSize = inputChunk.getMaxAtomSize(effectiveSize=True)
|
|
1040
|
+
if taskSpec.walltime is not None:
|
|
1041
|
+
minWalltime = taskSpec.walltime * tmpMaxAtomSize
|
|
1042
|
+
else:
|
|
1043
|
+
minWalltime = None
|
|
1044
|
+
# take # of consumers into account
|
|
1045
|
+
if not taskSpec.useEventService() or taskSpec.useJobCloning():
|
|
1046
|
+
strMinWalltime = f"walltime*inputSize={taskSpec.walltime}*{tmpMaxAtomSize}"
|
|
1047
|
+
else:
|
|
1048
|
+
strMinWalltime = f"walltime*inputSize/nEsConsumers/maxAttemptEsJob={taskSpec.walltime}*{tmpMaxAtomSize}/{nEsConsumers}/{maxAttemptEsJob}"
|
|
1049
|
+
else:
|
|
1050
|
+
tmpMaxAtomSize = inputChunk.getMaxAtomSize(getNumEvents=True)
|
|
1051
|
+
if taskSpec.getCpuTime() is not None:
|
|
1052
|
+
minWalltime = taskSpec.getCpuTime() * tmpMaxAtomSize
|
|
1053
|
+
if taskSpec.dynamicNumEvents():
|
|
1054
|
+
# use minGranularity as the smallest chunk
|
|
1055
|
+
minGranularity = taskSpec.get_min_granularity()
|
|
1056
|
+
minWalltime_dyn = taskSpec.getCpuTime() * minGranularity
|
|
1057
|
+
strMinWalltime_dyn = f"cpuTime*minGranularity={taskSpec.getCpuTime()}*{minGranularity}"
|
|
1058
|
+
# use most consecutive events as the largest chunk
|
|
1059
|
+
eventJump, totalEvents = inputChunk.check_event_jump_and_sum()
|
|
1060
|
+
# use maxEventsPerJob if smaller
|
|
1061
|
+
maxEventsPerJob = taskSpec.get_max_events_per_job()
|
|
1062
|
+
if maxEventsPerJob:
|
|
1063
|
+
totalEvents = min(totalEvents, maxEventsPerJob)
|
|
1064
|
+
maxWalltime_dyn = taskSpec.getCpuTime() * totalEvents
|
|
1065
|
+
strMaxWalltime_dyn = f"cpuTime*maxEventsPerJob={taskSpec.getCpuTime()}*{totalEvents}"
|
|
1066
|
+
# take # of consumers into account
|
|
1067
|
+
if not taskSpec.useEventService() or taskSpec.useJobCloning():
|
|
1068
|
+
strMinWalltime = f"cpuTime*nEventsPerJob={taskSpec.getCpuTime()}*{tmpMaxAtomSize}"
|
|
1069
|
+
else:
|
|
1070
|
+
strMinWalltime = f"cpuTime*nEventsPerJob/nEsConsumers/maxAttemptEsJob={taskSpec.getCpuTime()}*{tmpMaxAtomSize}/{nEsConsumers}/{maxAttemptEsJob}"
|
|
1071
|
+
if minWalltime:
|
|
1072
|
+
minWalltime /= nEsConsumers * maxAttemptEsJob
|
|
1073
|
+
newScanSiteList = []
|
|
1074
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1075
|
+
msg_map = {}
|
|
1076
|
+
tmpLog.set_message_slot()
|
|
1077
|
+
for tmpSiteName in scanSiteList:
|
|
1078
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1079
|
+
siteMaxTime = tmpSiteSpec.maxtime
|
|
1080
|
+
origSiteMaxTime = siteMaxTime
|
|
1081
|
+
# check max walltime at the site
|
|
1082
|
+
tmpSiteStr = f"{siteMaxTime}"
|
|
1083
|
+
if taskSpec.useHS06():
|
|
1084
|
+
oldSiteMaxTime = siteMaxTime
|
|
1085
|
+
siteMaxTime -= taskSpec.baseWalltime
|
|
1086
|
+
tmpSiteStr = f"({oldSiteMaxTime}-{taskSpec.baseWalltime})"
|
|
1087
|
+
if siteMaxTime not in [None, 0] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
1088
|
+
siteMaxTime *= tmpSiteSpec.coreCount
|
|
1089
|
+
tmpSiteStr += f"*{tmpSiteSpec.coreCount}"
|
|
1090
|
+
if taskSpec.useHS06():
|
|
1091
|
+
if siteMaxTime not in [None, 0]:
|
|
1092
|
+
siteMaxTime *= tmpSiteSpec.corepower
|
|
1093
|
+
tmpSiteStr += f"*{tmpSiteSpec.corepower}"
|
|
1094
|
+
siteMaxTime *= float(taskSpec.cpuEfficiency) / 100.0
|
|
1095
|
+
siteMaxTime = int(siteMaxTime)
|
|
1096
|
+
tmpSiteStr += f"*{taskSpec.cpuEfficiency}%"
|
|
1097
|
+
if origSiteMaxTime != 0:
|
|
1098
|
+
toSkip = False
|
|
1099
|
+
if minWalltime_dyn and tmpSiteSpec.mintime and tmpSiteSpec.mintime > 0:
|
|
1100
|
+
if minWalltime_dyn > siteMaxTime:
|
|
1101
|
+
tmp_msg = f" skip site={tmpSiteName} due to short site walltime {tmpSiteStr} " f"(site upper limit) less than {strMinWalltime_dyn} "
|
|
1102
|
+
toSkip = True
|
|
1103
|
+
else:
|
|
1104
|
+
if minWalltime and minWalltime > siteMaxTime:
|
|
1105
|
+
tmp_msg = f" skip site={tmpSiteName} due to short site walltime {tmpSiteStr} " f"(site upper limit) less than {strMinWalltime} "
|
|
1106
|
+
toSkip = True
|
|
1107
|
+
if toSkip:
|
|
1108
|
+
tmp_msg += "criteria=-shortwalltime"
|
|
1109
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1110
|
+
continue
|
|
1111
|
+
# sending scouts or merge or walltime-undefined jobs to only sites where walltime is more than 1 day
|
|
1112
|
+
if (
|
|
1113
|
+
(not sitePreAssigned and inputChunk.useScout())
|
|
1114
|
+
or inputChunk.isMerging
|
|
1115
|
+
or (not taskSpec.walltime and not taskSpec.walltimeUnit and not taskSpec.cpuTimeUnit)
|
|
1116
|
+
or (not taskSpec.getCpuTime() and taskSpec.cpuTimeUnit)
|
|
1117
|
+
):
|
|
1118
|
+
minTimeForZeroWalltime = 24
|
|
1119
|
+
str_minTimeForZeroWalltime = f"{minTimeForZeroWalltime}hr*10HS06s"
|
|
1120
|
+
minTimeForZeroWalltime *= 60 * 60 * 10
|
|
1121
|
+
|
|
1122
|
+
if tmpSiteSpec.coreCount not in [None, 0]:
|
|
1123
|
+
minTimeForZeroWalltime *= tmpSiteSpec.coreCount
|
|
1124
|
+
str_minTimeForZeroWalltime += f"*{tmpSiteSpec.coreCount}cores"
|
|
1125
|
+
|
|
1126
|
+
if siteMaxTime != 0 and siteMaxTime < minTimeForZeroWalltime:
|
|
1127
|
+
tmp_msg = f" skip site={tmpSiteName} due to site walltime {tmpSiteStr} (site upper limit) insufficient "
|
|
1128
|
+
if inputChunk.useScout():
|
|
1129
|
+
tmp_msg += f"for scouts ({str_minTimeForZeroWalltime} at least) "
|
|
1130
|
+
tmp_msg += "criteria=-scoutwalltime"
|
|
1131
|
+
else:
|
|
1132
|
+
tmp_msg += f"for zero walltime ({str_minTimeForZeroWalltime} at least) "
|
|
1133
|
+
tmp_msg += "criteria=-zerowalltime"
|
|
1134
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1135
|
+
continue
|
|
1136
|
+
# check min walltime at the site
|
|
1137
|
+
siteMinTime = tmpSiteSpec.mintime
|
|
1138
|
+
origSiteMinTime = siteMinTime
|
|
1139
|
+
tmpSiteStr = f"{siteMinTime}"
|
|
1140
|
+
if taskSpec.useHS06():
|
|
1141
|
+
oldSiteMinTime = siteMinTime
|
|
1142
|
+
siteMinTime -= taskSpec.baseWalltime
|
|
1143
|
+
tmpSiteStr = f"({oldSiteMinTime}-{taskSpec.baseWalltime})"
|
|
1144
|
+
if siteMinTime not in [None, 0] and tmpSiteSpec.coreCount not in [None, 0]:
|
|
1145
|
+
siteMinTime *= tmpSiteSpec.coreCount
|
|
1146
|
+
tmpSiteStr += f"*{tmpSiteSpec.coreCount}"
|
|
1147
|
+
if taskSpec.useHS06():
|
|
1148
|
+
if siteMinTime not in [None, 0]:
|
|
1149
|
+
siteMinTime *= tmpSiteSpec.corepower
|
|
1150
|
+
tmpSiteStr += f"*{tmpSiteSpec.corepower}"
|
|
1151
|
+
siteMinTime *= float(taskSpec.cpuEfficiency) / 100.0
|
|
1152
|
+
siteMinTime = int(siteMinTime)
|
|
1153
|
+
tmpSiteStr += f"*{taskSpec.cpuEfficiency}%"
|
|
1154
|
+
if origSiteMinTime != 0:
|
|
1155
|
+
toSkip = False
|
|
1156
|
+
if minWalltime_dyn and tmpSiteSpec.mintime and tmpSiteSpec.mintime > 0:
|
|
1157
|
+
if minWalltime_dyn < siteMinTime and (maxWalltime_dyn is None or maxWalltime_dyn < siteMinTime):
|
|
1158
|
+
tmp_msg = f" skip site {tmpSiteName} due to short job walltime {tmpSiteStr} " f"(site lower limit) greater than {strMinWalltime_dyn} "
|
|
1159
|
+
if maxWalltime_dyn:
|
|
1160
|
+
tmp_msg += f"and {strMinWalltime_dyn} "
|
|
1161
|
+
toSkip = True
|
|
1162
|
+
else:
|
|
1163
|
+
if (minWalltime is None or minWalltime < siteMinTime) and (maxWalltime is None or maxWalltime < siteMinTime):
|
|
1164
|
+
tmp_msg = f" skip site {tmpSiteName} due to short job walltime {tmpSiteStr} " f"(site lower limit) greater than {strMinWalltime} "
|
|
1165
|
+
if maxWalltime:
|
|
1166
|
+
tmp_msg += f"and {strMaxWalltime} "
|
|
1167
|
+
toSkip = True
|
|
1168
|
+
if toSkip:
|
|
1169
|
+
tmp_msg += "criteria=-longwalltime"
|
|
1170
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1171
|
+
continue
|
|
1172
|
+
newScanSiteList.append(tmpSiteName)
|
|
1173
|
+
tmpLog.unset_message_slot()
|
|
1174
|
+
scanSiteList = newScanSiteList
|
|
1175
|
+
if not taskSpec.useHS06():
|
|
1176
|
+
tmpLog.info(f"walltime check with {minWalltime}({taskSpec.walltimeUnit})")
|
|
1177
|
+
else:
|
|
1178
|
+
tmpStr = f"walltime check with {strMinWalltime}({taskSpec.cpuTimeUnit})"
|
|
1179
|
+
if maxWalltime:
|
|
1180
|
+
tmpStr += f" and {strMaxWalltime}"
|
|
1181
|
+
if minWalltime_dyn:
|
|
1182
|
+
tmpStr += f" for normal PQs, {strMinWalltime_dyn}"
|
|
1183
|
+
if maxWalltime_dyn:
|
|
1184
|
+
tmpStr += f" and {strMaxWalltime_dyn}"
|
|
1185
|
+
tmpStr += " for PQs with non-zero mintime"
|
|
1186
|
+
tmpLog.info(tmpStr)
|
|
1187
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "walltime check", tmpLog, msg_map)
|
|
1188
|
+
if not scanSiteList:
|
|
1189
|
+
self.dump_summary(tmpLog)
|
|
1190
|
+
tmpLog.error("no candidates")
|
|
1191
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1192
|
+
return retTmpError
|
|
1193
|
+
|
|
1194
|
+
######################################
|
|
1195
|
+
# selection for network connectivity
|
|
1196
|
+
if not sitePreAssigned:
|
|
1197
|
+
ipConnectivity = taskSpec.getIpConnectivity()
|
|
1198
|
+
ipStack = taskSpec.getIpStack()
|
|
1199
|
+
if ipConnectivity or ipStack:
|
|
1200
|
+
newScanSiteList = []
|
|
1201
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1202
|
+
msg_map = {}
|
|
1203
|
+
tmpLog.set_message_slot()
|
|
1204
|
+
for tmpSiteName in scanSiteList:
|
|
1205
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1206
|
+
# check connectivity
|
|
1207
|
+
if ipConnectivity:
|
|
1208
|
+
wn_connectivity = tmpSiteSpec.get_wn_connectivity()
|
|
1209
|
+
if wn_connectivity == "full":
|
|
1210
|
+
pass
|
|
1211
|
+
elif wn_connectivity == "http" and ipConnectivity == "http":
|
|
1212
|
+
pass
|
|
1213
|
+
else:
|
|
1214
|
+
tmp_msg = f" skip site={tmpSiteName} due to insufficient connectivity site={wn_connectivity} vs task={ipConnectivity} "
|
|
1215
|
+
tmp_msg += "criteria=-network"
|
|
1216
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1217
|
+
continue
|
|
1218
|
+
# check IP stack
|
|
1219
|
+
if ipStack:
|
|
1220
|
+
wn_ipstack = tmpSiteSpec.get_ipstack()
|
|
1221
|
+
if ipStack != wn_ipstack:
|
|
1222
|
+
tmp_msg = f" skip site={tmpSiteName} due to IP stack mismatch site={wn_ipstack} vs task={ipStack} "
|
|
1223
|
+
tmp_msg += "criteria=-network"
|
|
1224
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1225
|
+
continue
|
|
1226
|
+
|
|
1227
|
+
newScanSiteList.append(tmpSiteName)
|
|
1228
|
+
tmpLog.unset_message_slot()
|
|
1229
|
+
scanSiteList = newScanSiteList
|
|
1230
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "network check", tmpLog, msg_map)
|
|
1231
|
+
if not scanSiteList:
|
|
1232
|
+
self.dump_summary(tmpLog)
|
|
1233
|
+
tmpLog.error("no candidates")
|
|
1234
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1235
|
+
return retTmpError
|
|
1236
|
+
|
|
1237
|
+
######################################
|
|
1238
|
+
# selection for event service
|
|
1239
|
+
if not sitePreAssigned:
|
|
1240
|
+
newScanSiteList = []
|
|
1241
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1242
|
+
msg_map = {}
|
|
1243
|
+
tmpLog.set_message_slot()
|
|
1244
|
+
for tmpSiteName in scanSiteList:
|
|
1245
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1246
|
+
# event service
|
|
1247
|
+
if taskSpec.useEventService() and not taskSpec.useJobCloning():
|
|
1248
|
+
if tmpSiteSpec.getJobSeed() == "std":
|
|
1249
|
+
tmp_msg = f" skip site={tmpSiteName} since EventService is not allowed "
|
|
1250
|
+
tmp_msg += "criteria=-es"
|
|
1251
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1252
|
+
continue
|
|
1253
|
+
if tmpSiteSpec.getJobSeed() == "eshigh" and not esHigh:
|
|
1254
|
+
tmp_msg = f" skip site={tmpSiteName} since low prio EventService is not allowed "
|
|
1255
|
+
tmp_msg += "criteria=-eshigh"
|
|
1256
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1257
|
+
continue
|
|
1258
|
+
else:
|
|
1259
|
+
if tmpSiteSpec.getJobSeed() == "es":
|
|
1260
|
+
tmp_msg = f" skip site={tmpSiteName} since only EventService is allowed "
|
|
1261
|
+
tmp_msg += "criteria=-nones"
|
|
1262
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1263
|
+
continue
|
|
1264
|
+
# skip UCORE/SCORE
|
|
1265
|
+
if (
|
|
1266
|
+
taskSpec.useEventService()
|
|
1267
|
+
and not taskSpec.useJobCloning()
|
|
1268
|
+
and tmpSiteSpec.sitename != tmpSiteSpec.get_unified_name()
|
|
1269
|
+
and tmpSiteSpec.coreCount == 1
|
|
1270
|
+
):
|
|
1271
|
+
tmp_msg = f" skip site={tmpSiteName} since EventService on UCORE/SCORE "
|
|
1272
|
+
tmp_msg += "criteria=-es_ucore"
|
|
1273
|
+
msg_map[tmpSiteSpec.get_unified_name()] = tmp_msg
|
|
1274
|
+
continue
|
|
1275
|
+
newScanSiteList.append(tmpSiteName)
|
|
1276
|
+
tmpLog.unset_message_slot()
|
|
1277
|
+
scanSiteList = newScanSiteList
|
|
1278
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "EventService check", tmpLog, msg_map)
|
|
1279
|
+
if not scanSiteList:
|
|
1280
|
+
self.dump_summary(tmpLog)
|
|
1281
|
+
tmpLog.error("no candidates")
|
|
1282
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1283
|
+
return retTmpError
|
|
1284
|
+
|
|
1285
|
+
######################################
|
|
1286
|
+
# selection for transferring
|
|
1287
|
+
newScanSiteList = []
|
|
1288
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1289
|
+
msg_map = {}
|
|
1290
|
+
tmpLog.set_message_slot()
|
|
1291
|
+
newSkippedTmp = dict()
|
|
1292
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1293
|
+
try:
|
|
1294
|
+
tmpAtlasSiteName = storageMapping[tmpSiteName]["default"]
|
|
1295
|
+
if tmpSiteName not in sites_in_nucleus + sites_sharing_output_storages_in_nucleus and nucleus != tmpAtlasSiteName:
|
|
1296
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1297
|
+
# limit
|
|
1298
|
+
def_maxTransferring = 2000
|
|
1299
|
+
if tmpSiteSpec.transferringlimit == 0:
|
|
1300
|
+
# use default value
|
|
1301
|
+
maxTransferring = def_maxTransferring
|
|
1302
|
+
else:
|
|
1303
|
+
maxTransferring = tmpSiteSpec.transferringlimit
|
|
1304
|
+
# transferring jobs with nuclei in downtime
|
|
1305
|
+
n_jobs_bad_transfer = 0
|
|
1306
|
+
if tmpSiteName in transferring_job_map:
|
|
1307
|
+
for tmp_nucleus in transferring_job_map[tmpSiteName]:
|
|
1308
|
+
if tmp_nucleus in nucleus_with_storages_unwritable_over_wan:
|
|
1309
|
+
n_jobs_bad_transfer += transferring_job_map[tmpSiteName][tmp_nucleus]
|
|
1310
|
+
# check at the site
|
|
1311
|
+
nTraJobs = AtlasBrokerUtils.getNumJobs(jobStatMap, tmpSiteName, "transferring") - n_jobs_bad_transfer
|
|
1312
|
+
nRunJobs = AtlasBrokerUtils.getNumJobs(jobStatMap, tmpSiteName, "running")
|
|
1313
|
+
if max(maxTransferring, 2 * nRunJobs) < nTraJobs:
|
|
1314
|
+
tmpStr = " skip site=%s due to too many transferring=%s greater than max(%s,2x%s) criteria=-transferring" % (
|
|
1315
|
+
tmpSiteName,
|
|
1316
|
+
nTraJobs,
|
|
1317
|
+
maxTransferring,
|
|
1318
|
+
nRunJobs,
|
|
1319
|
+
)
|
|
1320
|
+
newSkippedTmp[tmpSiteName] = tmpStr
|
|
1321
|
+
msg_map[tmpSiteName] = tmpStr
|
|
1322
|
+
except KeyError:
|
|
1323
|
+
pass
|
|
1324
|
+
newScanSiteList.append(tmpSiteName)
|
|
1325
|
+
tmpLog.unset_message_slot()
|
|
1326
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
1327
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1328
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "transferring check", tmpLog, msg_map)
|
|
1329
|
+
if not scanSiteList:
|
|
1330
|
+
self.dump_summary(tmpLog)
|
|
1331
|
+
tmpLog.error("no candidates")
|
|
1332
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1333
|
+
return retTmpError
|
|
1334
|
+
|
|
1335
|
+
######################################
|
|
1336
|
+
# selection for pledge when work is short
|
|
1337
|
+
if not sitePreAssigned and work_shortage:
|
|
1338
|
+
newScanSiteList = []
|
|
1339
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1340
|
+
msg_map = {}
|
|
1341
|
+
tmpLog.set_message_slot()
|
|
1342
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1343
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1344
|
+
if tmpSiteSpec.is_opportunistic():
|
|
1345
|
+
# skip opportunistic sites when plenty of work is unavailable
|
|
1346
|
+
tmp_msg = f" skip site={tmpSiteName} to avoid opportunistic in case of work shortage "
|
|
1347
|
+
tmp_msg += "criteria=-non_pledged"
|
|
1348
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
1349
|
+
continue
|
|
1350
|
+
elif tmpSiteSpec.pledgedCPU is not None and tmpSiteSpec.pledgedCPU > 0:
|
|
1351
|
+
# check number of cores
|
|
1352
|
+
tmp_stat_dict = core_statistics.get(tmpSiteName, {})
|
|
1353
|
+
n_running_cores = tmp_stat_dict.get("running", 0)
|
|
1354
|
+
n_starting_cores = tmp_stat_dict.get("starting", 0)
|
|
1355
|
+
tmpLog.debug(f" {tmpSiteName} running={n_running_cores} starting={n_starting_cores}")
|
|
1356
|
+
if n_running_cores + n_starting_cores > tmpSiteSpec.pledgedCPU:
|
|
1357
|
+
tmp_msg = f" skip site={tmpSiteName} since nCores(running+starting)={n_running_cores+n_starting_cores} more than pledgedCPU={tmpSiteSpec.pledgedCPU} "
|
|
1358
|
+
tmp_msg += "in case of work shortage "
|
|
1359
|
+
tmp_msg += "criteria=-over_pledged"
|
|
1360
|
+
msg_map[tmpSiteName] = tmp_msg
|
|
1361
|
+
continue
|
|
1362
|
+
newScanSiteList.append(tmpSiteName)
|
|
1363
|
+
tmpLog.unset_message_slot()
|
|
1364
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1365
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "pledge check", tmpLog, msg_map)
|
|
1366
|
+
if not scanSiteList:
|
|
1367
|
+
self.dump_summary(tmpLog)
|
|
1368
|
+
tmpLog.error("no candidates")
|
|
1369
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1370
|
+
return retTmpError
|
|
1371
|
+
|
|
1372
|
+
######################################
|
|
1373
|
+
# selection for T1 weight
|
|
1374
|
+
if taskSpec.getT1Weight() < 0 and not inputChunk.isMerging:
|
|
1375
|
+
useT1Weight = True
|
|
1376
|
+
else:
|
|
1377
|
+
useT1Weight = False
|
|
1378
|
+
t1Weight = taskSpec.getT1Weight()
|
|
1379
|
+
if t1Weight == 0:
|
|
1380
|
+
tmpLog.info(f"IO intensity {taskSpec.ioIntensity}")
|
|
1381
|
+
# use T1 weight if IO intensive
|
|
1382
|
+
t1Weight = 1
|
|
1383
|
+
if taskSpec.ioIntensity is not None and taskSpec.ioIntensity > 500:
|
|
1384
|
+
t1Weight = WORLD_NUCLEUS_WEIGHT
|
|
1385
|
+
|
|
1386
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1387
|
+
msg_map = {}
|
|
1388
|
+
if t1Weight < 0 and not inputChunk.isMerging:
|
|
1389
|
+
newScanSiteList = []
|
|
1390
|
+
tmpLog.set_message_slot()
|
|
1391
|
+
for tmpSiteName in scanSiteList:
|
|
1392
|
+
if tmpSiteName not in sites_in_nucleus + sites_sharing_output_storages_in_nucleus:
|
|
1393
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1394
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} due to negative T1 weight criteria=-t1weight"
|
|
1395
|
+
continue
|
|
1396
|
+
newScanSiteList.append(tmpSiteName)
|
|
1397
|
+
tmpLog.unset_message_slot()
|
|
1398
|
+
scanSiteList = newScanSiteList
|
|
1399
|
+
t1Weight = 1
|
|
1400
|
+
t1Weight = max(t1Weight, t1WeightForHighPrio)
|
|
1401
|
+
tmpLog.info(f"T1 weight {t1Weight}")
|
|
1402
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "T1 weight check", tmpLog, msg_map)
|
|
1403
|
+
if not scanSiteList:
|
|
1404
|
+
self.dump_summary(tmpLog)
|
|
1405
|
+
tmpLog.error("no candidates")
|
|
1406
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1407
|
+
return retTmpError
|
|
1408
|
+
|
|
1409
|
+
######################################
|
|
1410
|
+
# selection for full chain
|
|
1411
|
+
if nucleusSpec:
|
|
1412
|
+
full_chain = taskSpec.check_full_chain_with_nucleus(nucleusSpec)
|
|
1413
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1414
|
+
msg_map = {}
|
|
1415
|
+
tmpLog.set_message_slot()
|
|
1416
|
+
newScanSiteList = []
|
|
1417
|
+
for tmpSiteName in scanSiteList:
|
|
1418
|
+
if full_chain:
|
|
1419
|
+
# skip PQs not in the nucleus
|
|
1420
|
+
if tmpSiteName not in sites_in_nucleus:
|
|
1421
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1422
|
+
msg_map[tmpSiteSpec.get_unified_name()] = (
|
|
1423
|
+
f" skip site={tmpSiteSpec.get_unified_name()} not in nucleus for full chain criteria=-full_chain"
|
|
1424
|
+
)
|
|
1425
|
+
continue
|
|
1426
|
+
else:
|
|
1427
|
+
# skip PQs only for full-chain
|
|
1428
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1429
|
+
if tmpSiteSpec.bare_nucleus_mode() == "only":
|
|
1430
|
+
msg_map[tmpSiteSpec.get_unified_name()] = f" skip site={tmpSiteSpec.get_unified_name()} only for full chain criteria=-not_full_chain"
|
|
1431
|
+
continue
|
|
1432
|
+
newScanSiteList.append(tmpSiteName)
|
|
1433
|
+
tmpLog.unset_message_slot()
|
|
1434
|
+
scanSiteList = newScanSiteList
|
|
1435
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "full chain check", tmpLog, msg_map)
|
|
1436
|
+
if not scanSiteList:
|
|
1437
|
+
self.dump_summary(tmpLog)
|
|
1438
|
+
tmpLog.error("no candidates")
|
|
1439
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1440
|
+
return retTmpError
|
|
1441
|
+
|
|
1442
|
+
######################################
|
|
1443
|
+
# selection for nPilot
|
|
1444
|
+
nPilotMap = {}
|
|
1445
|
+
if not sitePreAssigned and not siteListPreAssigned:
|
|
1446
|
+
nWNmap = self.taskBufferIF.getCurrentSiteData()
|
|
1447
|
+
newScanSiteList = []
|
|
1448
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1449
|
+
tmpLog.set_message_slot()
|
|
1450
|
+
newSkippedTmp = dict()
|
|
1451
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1452
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1453
|
+
# check at the site
|
|
1454
|
+
nPilot = 0
|
|
1455
|
+
if tmpSiteName in nWNmap:
|
|
1456
|
+
nPilot = nWNmap[tmpSiteName]["getJob"] + nWNmap[tmpSiteName]["updateJob"]
|
|
1457
|
+
# skip no pilot sites unless the task and the site use jumbo jobs or the site is standby
|
|
1458
|
+
if (
|
|
1459
|
+
nPilot == 0
|
|
1460
|
+
and ("test" not in taskSpec.prodSourceLabel or inputChunk.isExpress())
|
|
1461
|
+
and (taskSpec.getNumJumboJobs() is None or not tmpSiteSpec.useJumboJobs())
|
|
1462
|
+
and tmpSiteSpec.getNumStandby(wq_tag, taskSpec.resource_type) is None
|
|
1463
|
+
):
|
|
1464
|
+
tmpStr = f" skip site={tmpSiteName} due to no pilot criteria=-nopilot"
|
|
1465
|
+
newSkippedTmp[tmpSiteName] = tmpStr
|
|
1466
|
+
newScanSiteList.append(tmpSiteName)
|
|
1467
|
+
nPilotMap[tmpSiteName] = nPilot
|
|
1468
|
+
tmpLog.unset_message_slot()
|
|
1469
|
+
siteSkippedTmp = self.add_pseudo_sites_to_skip(newSkippedTmp, scanSiteList, siteSkippedTmp)
|
|
1470
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1471
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "pilot check", tmpLog, {})
|
|
1472
|
+
if not scanSiteList:
|
|
1473
|
+
self.dump_summary(tmpLog)
|
|
1474
|
+
tmpLog.error("no candidates")
|
|
1475
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1476
|
+
return retTmpError
|
|
1477
|
+
|
|
1478
|
+
# return if to give a hint for task brokerage
|
|
1479
|
+
if hintForTB:
|
|
1480
|
+
######################################
|
|
1481
|
+
# temporary problems
|
|
1482
|
+
newScanSiteList = []
|
|
1483
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1484
|
+
msg_map = {}
|
|
1485
|
+
tmpLog.set_message_slot()
|
|
1486
|
+
for tmpSiteName in scanSiteList:
|
|
1487
|
+
if tmpSiteName in siteSkippedTmp:
|
|
1488
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1489
|
+
msg_map[tmpSiteSpec.get_unified_name()] = siteSkippedTmp[tmpSiteName]
|
|
1490
|
+
else:
|
|
1491
|
+
newScanSiteList.append(tmpSiteName)
|
|
1492
|
+
tmpLog.unset_message_slot()
|
|
1493
|
+
scanSiteList = newScanSiteList
|
|
1494
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "temporary problem check", tmpLog, msg_map)
|
|
1495
|
+
if not scanSiteList:
|
|
1496
|
+
self.dump_summary(tmpLog)
|
|
1497
|
+
tmpLog.error("no candidates")
|
|
1498
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1499
|
+
return retTmpError
|
|
1500
|
+
return self.SC_SUCCEEDED, scanSiteList
|
|
1501
|
+
|
|
1502
|
+
######################################
|
|
1503
|
+
# get available files
|
|
1504
|
+
siteSizeMap = {}
|
|
1505
|
+
siteSizeMapWT = {}
|
|
1506
|
+
availableFileMap = {}
|
|
1507
|
+
siteFilesMap = {}
|
|
1508
|
+
siteFilesMapWT = {}
|
|
1509
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
1510
|
+
try:
|
|
1511
|
+
# mapping between sites and input storage endpoints
|
|
1512
|
+
siteStorageEP = AtlasBrokerUtils.getSiteInputStorageEndpointMap(
|
|
1513
|
+
self.get_unified_sites(scanSiteList), self.siteMapper, JobUtils.PROD_PS, JobUtils.PROD_PS
|
|
1514
|
+
)
|
|
1515
|
+
# disable file lookup for merge jobs or secondary datasets
|
|
1516
|
+
checkCompleteness = True
|
|
1517
|
+
useCompleteOnly = False
|
|
1518
|
+
if inputChunk.isMerging:
|
|
1519
|
+
checkCompleteness = False
|
|
1520
|
+
if not datasetSpec.isMaster() or (taskSpec.ioIntensity and taskSpec.ioIntensity > self.io_intensity_cutoff and not taskSpec.inputPreStaging()):
|
|
1521
|
+
useCompleteOnly = True
|
|
1522
|
+
# get available files per site/endpoint
|
|
1523
|
+
tmpLog.debug(f"getting available files for {datasetSpec.datasetName}")
|
|
1524
|
+
tmpAvFileMap = self.ddmIF.getAvailableFiles(
|
|
1525
|
+
datasetSpec,
|
|
1526
|
+
siteStorageEP,
|
|
1527
|
+
self.siteMapper,
|
|
1528
|
+
check_completeness=checkCompleteness,
|
|
1529
|
+
storage_token=datasetSpec.storageToken,
|
|
1530
|
+
complete_only=useCompleteOnly,
|
|
1531
|
+
)
|
|
1532
|
+
tmpLog.debug("got")
|
|
1533
|
+
if tmpAvFileMap is None:
|
|
1534
|
+
raise Interaction.JEDITemporaryError("ddmIF.getAvailableFiles failed")
|
|
1535
|
+
availableFileMap[datasetSpec.datasetName] = tmpAvFileMap
|
|
1536
|
+
except Exception as e:
|
|
1537
|
+
tmp_str = str(e).replace("\\n", " ")
|
|
1538
|
+
tmpLog.error(f"failed to get available files with {tmp_str}")
|
|
1539
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1540
|
+
return retTmpError
|
|
1541
|
+
# loop over all sites to get the size of available files
|
|
1542
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1543
|
+
siteSizeMap.setdefault(tmpSiteName, 0)
|
|
1544
|
+
siteSizeMapWT.setdefault(tmpSiteName, 0)
|
|
1545
|
+
siteFilesMap.setdefault(tmpSiteName, set())
|
|
1546
|
+
siteFilesMapWT.setdefault(tmpSiteName, set())
|
|
1547
|
+
# get the total size of available files
|
|
1548
|
+
if tmpSiteName in availableFileMap[datasetSpec.datasetName]:
|
|
1549
|
+
availableFiles = availableFileMap[datasetSpec.datasetName][tmpSiteName]
|
|
1550
|
+
for tmpFileSpec in availableFiles["localdisk"] + availableFiles["cache"]:
|
|
1551
|
+
if tmpFileSpec.lfn not in siteFilesMap[tmpSiteName]:
|
|
1552
|
+
siteSizeMap[tmpSiteName] += tmpFileSpec.fsize
|
|
1553
|
+
siteSizeMapWT[tmpSiteName] += tmpFileSpec.fsize
|
|
1554
|
+
siteFilesMap[tmpSiteName].add(tmpFileSpec.lfn)
|
|
1555
|
+
siteFilesMapWT[tmpSiteName].add(tmpFileSpec.lfn)
|
|
1556
|
+
for tmpFileSpec in availableFiles["localtape"]:
|
|
1557
|
+
if tmpFileSpec.lfn not in siteFilesMapWT[tmpSiteName]:
|
|
1558
|
+
siteSizeMapWT[tmpSiteName] += tmpFileSpec.fsize
|
|
1559
|
+
siteFilesMapWT[tmpSiteName].add(tmpFileSpec.lfn)
|
|
1560
|
+
# get max total size
|
|
1561
|
+
allLFNs = set()
|
|
1562
|
+
totalSize = 0
|
|
1563
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
1564
|
+
for fileSpec in datasetSpec.Files:
|
|
1565
|
+
if fileSpec.lfn not in allLFNs:
|
|
1566
|
+
allLFNs.add(fileSpec.lfn)
|
|
1567
|
+
try:
|
|
1568
|
+
totalSize += fileSpec.fsize
|
|
1569
|
+
except Exception:
|
|
1570
|
+
pass
|
|
1571
|
+
# get max num of available files
|
|
1572
|
+
maxNumFiles = len(allLFNs)
|
|
1573
|
+
|
|
1574
|
+
######################################
|
|
1575
|
+
# selection for fileSizeToMove
|
|
1576
|
+
moveSizeCutoffGB = self.taskBufferIF.getConfigValue(COMPONENT, "SIZE_CUTOFF_TO_MOVE_INPUT", APP, VO)
|
|
1577
|
+
if moveSizeCutoffGB is None:
|
|
1578
|
+
moveSizeCutoffGB = 10
|
|
1579
|
+
moveNumFilesCutoff = self.taskBufferIF.getConfigValue(COMPONENT, "NUM_CUTOFF_TO_MOVE_INPUT", APP, VO)
|
|
1580
|
+
if moveNumFilesCutoff is None:
|
|
1581
|
+
moveNumFilesCutoff = 100
|
|
1582
|
+
if (
|
|
1583
|
+
not sitePreAssigned
|
|
1584
|
+
and totalSize > 0
|
|
1585
|
+
and not inputChunk.isMerging
|
|
1586
|
+
and taskSpec.ioIntensity is not None
|
|
1587
|
+
and taskSpec.ioIntensity > self.io_intensity_cutoff
|
|
1588
|
+
and not (taskSpec.useEventService() and not taskSpec.useJobCloning())
|
|
1589
|
+
):
|
|
1590
|
+
newScanSiteList = []
|
|
1591
|
+
newScanSiteListWT = []
|
|
1592
|
+
msgList = []
|
|
1593
|
+
msgListDT = []
|
|
1594
|
+
for tmpSiteName in self.get_unified_sites(scanSiteList):
|
|
1595
|
+
# file size to move in MB
|
|
1596
|
+
mbToMove = int((totalSize - siteSizeMap[tmpSiteName]) / (1024 * 1024))
|
|
1597
|
+
nFilesToMove = maxNumFiles - len(siteFilesMap[tmpSiteName])
|
|
1598
|
+
mbToMoveWT = int((totalSize - siteSizeMapWT[tmpSiteName]) / (1024 * 1024))
|
|
1599
|
+
nFilesToMoveWT = maxNumFiles - len(siteFilesMapWT[tmpSiteName])
|
|
1600
|
+
if min(mbToMove, mbToMoveWT) > moveSizeCutoffGB * 1024 or min(nFilesToMove, nFilesToMoveWT) > moveNumFilesCutoff:
|
|
1601
|
+
tmp_msg = f" skip site={tmpSiteName} "
|
|
1602
|
+
if mbToMove > moveSizeCutoffGB * 1024:
|
|
1603
|
+
tmp_msg += f"since size of missing input is too large ({int(mbToMove / 1024)} GB > {moveSizeCutoffGB} GB) "
|
|
1604
|
+
else:
|
|
1605
|
+
tmp_msg += f"since the number of missing input files is too large ({nFilesToMove} > {moveNumFilesCutoff}) "
|
|
1606
|
+
tmp_msg += f"for IO intensive task ({taskSpec.ioIntensity} > {self.io_intensity_cutoff} kBPerS) criteria=-io"
|
|
1607
|
+
msgListDT.append(tmp_msg)
|
|
1608
|
+
continue
|
|
1609
|
+
if mbToMove > moveSizeCutoffGB * 1024 or nFilesToMove > moveNumFilesCutoff:
|
|
1610
|
+
tmp_msg = f" skip site={tmpSiteName} "
|
|
1611
|
+
if mbToMove > moveSizeCutoffGB * 1024:
|
|
1612
|
+
tmp_msg += f"since size of missing disk input is too large ({int(mbToMove / 1024)} GB > {moveSizeCutoffGB} GB) "
|
|
1613
|
+
else:
|
|
1614
|
+
tmp_msg += f"since the number of missing disk input files is too large ({nFilesToMove} > {moveNumFilesCutoff}) "
|
|
1615
|
+
tmp_msg += f"for IO intensive task ({taskSpec.ioIntensity} > {self.io_intensity_cutoff} kBPerS) criteria=-io"
|
|
1616
|
+
msgList.append(tmp_msg)
|
|
1617
|
+
newScanSiteListWT.append(tmpSiteName)
|
|
1618
|
+
else:
|
|
1619
|
+
newScanSiteList.append(tmpSiteName)
|
|
1620
|
+
if len(newScanSiteList + newScanSiteListWT) == 0:
|
|
1621
|
+
# disable if no candidate
|
|
1622
|
+
tmpLog.info("disabled IO check since no candidate passed")
|
|
1623
|
+
else:
|
|
1624
|
+
tmpLog.set_message_slot()
|
|
1625
|
+
for tmp_msg in msgListDT:
|
|
1626
|
+
tmpLog.info(tmp_msg)
|
|
1627
|
+
if len(newScanSiteList) == 0:
|
|
1628
|
+
# use candidates with TAPE replicas
|
|
1629
|
+
newScanSiteList = newScanSiteListWT
|
|
1630
|
+
else:
|
|
1631
|
+
for tmp_msg in msgList:
|
|
1632
|
+
tmpLog.info(tmp_msg)
|
|
1633
|
+
tmpLog.unset_message_slot()
|
|
1634
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1635
|
+
scanSiteList = self.get_pseudo_sites(newScanSiteList, scanSiteList)
|
|
1636
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "IO check", tmpLog, {})
|
|
1637
|
+
if scanSiteList == []:
|
|
1638
|
+
self.dump_summary(tmpLog)
|
|
1639
|
+
tmpLog.error("no candidates")
|
|
1640
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1641
|
+
return retTmpError
|
|
1642
|
+
|
|
1643
|
+
######################################
|
|
1644
|
+
# temporary problems
|
|
1645
|
+
newScanSiteList = []
|
|
1646
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
1647
|
+
msg_map = {}
|
|
1648
|
+
tmpLog.set_message_slot()
|
|
1649
|
+
for tmpSiteName in scanSiteList:
|
|
1650
|
+
if tmpSiteName in siteSkippedTmp:
|
|
1651
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpSiteName)
|
|
1652
|
+
msg_map[tmpSiteSpec.get_unified_name()] = siteSkippedTmp[tmpSiteName]
|
|
1653
|
+
else:
|
|
1654
|
+
newScanSiteList.append(tmpSiteName)
|
|
1655
|
+
tmpLog.unset_message_slot()
|
|
1656
|
+
scanSiteList = newScanSiteList
|
|
1657
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "temporary problem check", tmpLog, msg_map)
|
|
1658
|
+
if scanSiteList == []:
|
|
1659
|
+
self.dump_summary(tmpLog)
|
|
1660
|
+
tmpLog.error("no candidates")
|
|
1661
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1662
|
+
return retTmpError
|
|
1663
|
+
|
|
1664
|
+
######################################
|
|
1665
|
+
# calculate weight
|
|
1666
|
+
jobStatPrioMapGS = dict()
|
|
1667
|
+
jobStatPrioMapGSOnly = dict()
|
|
1668
|
+
if workQueue.is_global_share:
|
|
1669
|
+
tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(taskSpec.vo)
|
|
1670
|
+
else:
|
|
1671
|
+
tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsWithWorkQueue_JEDI(taskSpec.vo, taskSpec.prodSourceLabel)
|
|
1672
|
+
if tmpSt:
|
|
1673
|
+
tmpSt, jobStatPrioMapGS = self.taskBufferIF.getJobStatisticsByGlobalShare(taskSpec.vo)
|
|
1674
|
+
tmpSt, jobStatPrioMapGSOnly = self.taskBufferIF.getJobStatisticsByGlobalShare(taskSpec.vo, True)
|
|
1675
|
+
if tmpSt:
|
|
1676
|
+
# count jobs per resource type
|
|
1677
|
+
tmpSt, tmpStatMapRT = self.taskBufferIF.getJobStatisticsByResourceTypeSite(workQueue)
|
|
1678
|
+
if not tmpSt:
|
|
1679
|
+
tmpLog.error("failed to get job statistics with priority")
|
|
1680
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
1681
|
+
return retTmpError
|
|
1682
|
+
workerStat = self.taskBufferIF.ups_load_worker_stats()
|
|
1683
|
+
upsQueues = set(self.taskBufferIF.ups_get_queues())
|
|
1684
|
+
tmpLog.info(f"calculate weight and check cap for {len(scanSiteList)} candidates")
|
|
1685
|
+
cutoffName = f"NQUEUELIMITSITE_{taskSpec.gshare}"
|
|
1686
|
+
cutOffValue = self.taskBufferIF.getConfigValue(COMPONENT, cutoffName, APP, VO)
|
|
1687
|
+
if not cutOffValue:
|
|
1688
|
+
cutOffValue = 20
|
|
1689
|
+
else:
|
|
1690
|
+
tmpLog.info(f"using {cutoffName}={cutOffValue} as lower limit for nQueued")
|
|
1691
|
+
weightMapPrimary = {}
|
|
1692
|
+
weightMapSecondary = {}
|
|
1693
|
+
weightMapJumbo = {}
|
|
1694
|
+
largestNumRun = None
|
|
1695
|
+
for tmpPseudoSiteName in scanSiteList:
|
|
1696
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
1697
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
1698
|
+
if not workQueue.is_global_share and esHigh and tmpSiteSpec.getJobSeed() == "eshigh":
|
|
1699
|
+
tmp_wq_tag = wq_tag_global_share
|
|
1700
|
+
tmp_jobStatPrioMap = jobStatPrioMapGS
|
|
1701
|
+
else:
|
|
1702
|
+
tmp_wq_tag = wq_tag
|
|
1703
|
+
tmp_jobStatPrioMap = jobStatPrioMap
|
|
1704
|
+
nRunning = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "running", None, tmp_wq_tag)
|
|
1705
|
+
nRunningAll = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "running", None, None)
|
|
1706
|
+
corrNumPilotStr = ""
|
|
1707
|
+
if not workQueue.is_global_share:
|
|
1708
|
+
# correction factor for nPilot
|
|
1709
|
+
nRunningGS = AtlasBrokerUtils.getNumJobs(jobStatPrioMapGS, tmpSiteName, "running", None, wq_tag_global_share)
|
|
1710
|
+
nRunningGSOnly = AtlasBrokerUtils.getNumJobs(jobStatPrioMapGSOnly, tmpSiteName, "running", None, wq_tag_global_share)
|
|
1711
|
+
corrNumPilot = float(nRunningGS - nRunningGSOnly + 1) / float(nRunningGS + 1)
|
|
1712
|
+
corrNumPilotStr = f"*(nRunResourceQueue({nRunningGS - nRunningGSOnly})+1)/(nRunGlobalShare({nRunningGS})+1)"
|
|
1713
|
+
else:
|
|
1714
|
+
corrNumPilot = 1
|
|
1715
|
+
nDefined = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "defined", None, tmp_wq_tag) + self.getLiveCount(tmpSiteName)
|
|
1716
|
+
nAssigned = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "assigned", None, tmp_wq_tag)
|
|
1717
|
+
nActivated = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "activated", None, tmp_wq_tag)
|
|
1718
|
+
nStarting = AtlasBrokerUtils.getNumJobs(tmp_jobStatPrioMap, tmpSiteName, "starting", None, tmp_wq_tag)
|
|
1719
|
+
if tmpSiteName in nPilotMap:
|
|
1720
|
+
nPilot = nPilotMap[tmpSiteName]
|
|
1721
|
+
else:
|
|
1722
|
+
nPilot = 0
|
|
1723
|
+
# get num workers
|
|
1724
|
+
nWorkers = 0
|
|
1725
|
+
nWorkersCutoff = 20
|
|
1726
|
+
if tmpSiteName in workerStat:
|
|
1727
|
+
for tmpHarvesterID, tmpLabelStat in workerStat[tmpSiteName].items():
|
|
1728
|
+
for tmpHarvesterID, tmpResStat in tmpLabelStat.items():
|
|
1729
|
+
for tmpResType, tmpCounts in tmpResStat.items():
|
|
1730
|
+
for tmpStatus, tmpNum in tmpCounts.items():
|
|
1731
|
+
if tmpStatus in ["running", "submitted"]:
|
|
1732
|
+
nWorkers += tmpNum
|
|
1733
|
+
# cap
|
|
1734
|
+
nWorkers = min(nWorkersCutoff, nWorkers)
|
|
1735
|
+
# use nWorkers to bootstrap
|
|
1736
|
+
if (
|
|
1737
|
+
nPilot > 0
|
|
1738
|
+
and nRunning < nWorkersCutoff
|
|
1739
|
+
and nWorkers > nRunning
|
|
1740
|
+
and tmpSiteName in upsQueues
|
|
1741
|
+
and taskSpec.currentPriority <= self.max_prio_for_bootstrap
|
|
1742
|
+
):
|
|
1743
|
+
tmpLog.debug(f"using nWorkers={nWorkers} as nRunning at {tmpPseudoSiteName} since original nRunning={nRunning} is low")
|
|
1744
|
+
nRunning = nWorkers
|
|
1745
|
+
# take into account the number of standby jobs
|
|
1746
|
+
numStandby = tmpSiteSpec.getNumStandby(wq_tag, taskSpec.resource_type)
|
|
1747
|
+
if numStandby is None:
|
|
1748
|
+
pass
|
|
1749
|
+
elif taskSpec.currentPriority > self.max_prio_for_bootstrap:
|
|
1750
|
+
# don't use numSlots for high prio tasks
|
|
1751
|
+
tmpLog.debug(f"ignored numSlots at {tmpPseudoSiteName} due to prio={taskSpec.currentPriority} > {self.max_prio_for_bootstrap}")
|
|
1752
|
+
elif numStandby == 0:
|
|
1753
|
+
# use the number of starting jobs as the number of standby jobs
|
|
1754
|
+
nRunning = nStarting + nRunning
|
|
1755
|
+
tmpLog.debug(f"using nStarting+nRunning at {tmpPseudoSiteName} to set nRunning={nRunning} due to numSlot={numStandby}")
|
|
1756
|
+
else:
|
|
1757
|
+
# the number of standby jobs is defined
|
|
1758
|
+
nRunning = max(int(numStandby / tmpSiteSpec.coreCount), nRunning)
|
|
1759
|
+
tmpLog.debug(f"using numSlots={numStandby}/coreCount at {tmpPseudoSiteName} to set nRunning={nRunning}")
|
|
1760
|
+
manyAssigned = float(nAssigned + 1) / float(nActivated + 1)
|
|
1761
|
+
manyAssigned = min(2.0, manyAssigned)
|
|
1762
|
+
manyAssigned = max(1.0, manyAssigned)
|
|
1763
|
+
# take into account nAssigned when jobs need input data transfer
|
|
1764
|
+
if tmpSiteName not in siteSizeMap or siteSizeMap[tmpSiteName] >= totalSize:
|
|
1765
|
+
useAssigned = False
|
|
1766
|
+
else:
|
|
1767
|
+
useAssigned = True
|
|
1768
|
+
# stat with resource type
|
|
1769
|
+
RT_Cap = 2
|
|
1770
|
+
if tmpSiteName in tmpStatMapRT and taskSpec.resource_type in tmpStatMapRT[tmpSiteName]:
|
|
1771
|
+
useCapRT = True
|
|
1772
|
+
tmpRTrunning = tmpStatMapRT[tmpSiteName][taskSpec.resource_type].get("running", 0)
|
|
1773
|
+
tmpRTrunning = max(tmpRTrunning, nRunning)
|
|
1774
|
+
tmpRTqueue = tmpStatMapRT[tmpSiteName][taskSpec.resource_type].get("defined", 0)
|
|
1775
|
+
if useAssigned:
|
|
1776
|
+
tmpRTqueue += tmpStatMapRT[tmpSiteName][taskSpec.resource_type].get("assigned", 0)
|
|
1777
|
+
tmpRTqueue += tmpStatMapRT[tmpSiteName][taskSpec.resource_type].get("activated", 0)
|
|
1778
|
+
tmpRTqueue += tmpStatMapRT[tmpSiteName][taskSpec.resource_type].get("starting", 0)
|
|
1779
|
+
else:
|
|
1780
|
+
useCapRT = False
|
|
1781
|
+
tmpRTqueue = 0
|
|
1782
|
+
tmpRTrunning = 0
|
|
1783
|
+
if totalSize == 0 or totalSize - siteSizeMap[tmpSiteName] <= 0:
|
|
1784
|
+
weight = float(nRunning + 1) / float(nActivated + nStarting + nDefined + 10)
|
|
1785
|
+
weightStr = (
|
|
1786
|
+
f"nRun={nRunning} nAct={nActivated} nStart={nStarting} nDef={nDefined} nPilot={nPilot}{corrNumPilotStr} "
|
|
1787
|
+
f"totalSizeMB={int(totalSize / 1024 / 1024)} totalNumFiles={maxNumFiles} nRun_rt={tmpRTrunning} nQueued_rt={tmpRTqueue} "
|
|
1788
|
+
)
|
|
1789
|
+
else:
|
|
1790
|
+
weight = float(nRunning + 1) / float(nActivated + nAssigned + nStarting + nDefined + 10) / manyAssigned
|
|
1791
|
+
weightStr = (
|
|
1792
|
+
f"nRun={nRunning} nAct={nActivated} nAss={nAssigned} nStart={nStarting} nDef={nDefined} manyAss={manyAssigned} "
|
|
1793
|
+
f"nPilot={nPilot}{corrNumPilotStr} totalSizeMB={int(totalSize / 1024 / 1024)} "
|
|
1794
|
+
f"totalNumFiles={maxNumFiles} nRun_rt={tmpRTrunning} nQueued_rt={tmpRTqueue} "
|
|
1795
|
+
)
|
|
1796
|
+
|
|
1797
|
+
# reduce weights by taking data availability into account
|
|
1798
|
+
skipRemoteData = False
|
|
1799
|
+
if totalSize > 0:
|
|
1800
|
+
# file size to move in MB
|
|
1801
|
+
mbToMove = int((totalSize - siteSizeMap[tmpSiteName]) / (1024 * 1024))
|
|
1802
|
+
# number of files to move
|
|
1803
|
+
nFilesToMove = maxNumFiles - len(siteFilesMap[tmpSiteName])
|
|
1804
|
+
# consider size and # of files
|
|
1805
|
+
if tmpSiteSpec.use_only_local_data() and (mbToMove > 0 or nFilesToMove > 0):
|
|
1806
|
+
skipRemoteData = True
|
|
1807
|
+
else:
|
|
1808
|
+
weight = weight * (totalSize + siteSizeMap[tmpSiteName]) / totalSize / (nFilesToMove / 100 + 1)
|
|
1809
|
+
weightStr += f"fileSizeToMoveMB={mbToMove} nFilesToMove={nFilesToMove} "
|
|
1810
|
+
|
|
1811
|
+
# T1 weight
|
|
1812
|
+
if tmpSiteName in sites_in_nucleus + sites_sharing_output_storages_in_nucleus:
|
|
1813
|
+
weight *= t1Weight
|
|
1814
|
+
weightStr += f"t1W={t1Weight} "
|
|
1815
|
+
if useT1Weight:
|
|
1816
|
+
weightStr += f"nRunningAll={nRunningAll} "
|
|
1817
|
+
|
|
1818
|
+
# apply network metrics to weight
|
|
1819
|
+
if nucleus:
|
|
1820
|
+
tmpAtlasSiteName = None
|
|
1821
|
+
try:
|
|
1822
|
+
tmpAtlasSiteName = storageMapping[tmpSiteName]["default"]
|
|
1823
|
+
except KeyError:
|
|
1824
|
+
tmpLog.debug(f"Panda site {tmpSiteName} was not in site mapping. Default network values will be given")
|
|
1825
|
+
|
|
1826
|
+
try:
|
|
1827
|
+
closeness = networkMap[tmpAtlasSiteName][AGIS_CLOSENESS]
|
|
1828
|
+
except KeyError:
|
|
1829
|
+
tmpLog.debug(f"No {AGIS_CLOSENESS} information found in network matrix from {tmpAtlasSiteName}({tmpSiteName}) to {nucleus}")
|
|
1830
|
+
closeness = MAX_CLOSENESS * 0.7
|
|
1831
|
+
|
|
1832
|
+
try:
|
|
1833
|
+
nFilesInQueue = networkMap[tmpAtlasSiteName][queued_tag]
|
|
1834
|
+
except KeyError:
|
|
1835
|
+
tmpLog.debug(f"No {queued_tag} information found in network matrix from {tmpAtlasSiteName} ({tmpSiteName}) to {nucleus}")
|
|
1836
|
+
nFilesInQueue = 0
|
|
1837
|
+
|
|
1838
|
+
mbps = None
|
|
1839
|
+
try:
|
|
1840
|
+
mbps = networkMap[tmpAtlasSiteName][FTS_1W]
|
|
1841
|
+
mbps = networkMap[tmpAtlasSiteName][FTS_1D]
|
|
1842
|
+
mbps = networkMap[tmpAtlasSiteName][FTS_1H]
|
|
1843
|
+
except KeyError:
|
|
1844
|
+
if mbps is None:
|
|
1845
|
+
tmpLog.debug(f"No dynamic FTS mbps information found in network matrix from {tmpAtlasSiteName}({tmpSiteName}) to {nucleus}")
|
|
1846
|
+
|
|
1847
|
+
# network weight: value between 1 and 2, except when nucleus == satellite
|
|
1848
|
+
if nucleus == tmpAtlasSiteName: # 25 percent weight boost for processing in nucleus itself
|
|
1849
|
+
weightNwQueue = 2.5
|
|
1850
|
+
weightNwThroughput = 2.5
|
|
1851
|
+
else:
|
|
1852
|
+
# queue weight: the more in queue, the lower the weight
|
|
1853
|
+
weightNwQueue = 2 - (nFilesInQueue * 1.0 / self.queue_threshold)
|
|
1854
|
+
|
|
1855
|
+
# throughput weight: the higher the throughput, the higher the weight
|
|
1856
|
+
if mbps is not None:
|
|
1857
|
+
weightNwThroughput = self.convertMBpsToWeight(mbps)
|
|
1858
|
+
else:
|
|
1859
|
+
weightNwThroughput = 1 + ((MAX_CLOSENESS - closeness) * 1.0 / (MAX_CLOSENESS - MIN_CLOSENESS))
|
|
1860
|
+
|
|
1861
|
+
# combine queue and throughput weights
|
|
1862
|
+
weightNw = self.nwQueueImportance * weightNwQueue + self.nwThroughputImportance * weightNwThroughput
|
|
1863
|
+
|
|
1864
|
+
weightStr += f"weightNw={weightNw} ( closeness={closeness} nFilesQueued={nFilesInQueue} throughputMBps={mbps} Network weight:{self.nwActive} )"
|
|
1865
|
+
|
|
1866
|
+
# If network measurements in active mode, apply the weight
|
|
1867
|
+
if self.nwActive:
|
|
1868
|
+
weight *= weightNw
|
|
1869
|
+
|
|
1870
|
+
tmpLog.debug(
|
|
1871
|
+
f"subject=network_data src={tmpAtlasSiteName} dst={nucleus} weight={weight} weightNw={weightNw} "
|
|
1872
|
+
f"weightNwThroughput={weightNwThroughput} weightNwQueued={weightNwQueue} mbps={mbps} closeness={closeness} nqueued={nFilesInQueue}"
|
|
1873
|
+
)
|
|
1874
|
+
|
|
1875
|
+
# apply architecture (x86-v2, 3, 4...) preference to weight
|
|
1876
|
+
if preference_weight_map and tmpSiteName in preference_weight_map:
|
|
1877
|
+
pref_weight = preference_weight_map[tmpSiteName]
|
|
1878
|
+
weight *= pref_weight
|
|
1879
|
+
weightStr += f"prefW={pref_weight} "
|
|
1880
|
+
tmpLog.info(f"prefW={pref_weight} ")
|
|
1881
|
+
|
|
1882
|
+
# make candidate
|
|
1883
|
+
siteCandidateSpec = SiteCandidate(tmpPseudoSiteName, tmpSiteName)
|
|
1884
|
+
# override attributes
|
|
1885
|
+
siteCandidateSpec.override_attribute("maxwdir", newMaxwdir.get(tmpSiteName))
|
|
1886
|
+
platforms = resolved_platforms.get(tmpSiteName)
|
|
1887
|
+
if platforms:
|
|
1888
|
+
siteCandidateSpec.override_attribute("platforms", platforms)
|
|
1889
|
+
# set weight and params
|
|
1890
|
+
siteCandidateSpec.weight = weight
|
|
1891
|
+
siteCandidateSpec.nRunningJobs = nRunning
|
|
1892
|
+
siteCandidateSpec.nAssignedJobs = nAssigned
|
|
1893
|
+
# set available files
|
|
1894
|
+
for tmpDatasetName, availableFiles in availableFileMap.items():
|
|
1895
|
+
if tmpSiteName in availableFiles:
|
|
1896
|
+
siteCandidateSpec.add_local_disk_files(availableFiles[tmpSiteName]["localdisk"])
|
|
1897
|
+
siteCandidateSpec.add_local_tape_files(availableFiles[tmpSiteName]["localtape"])
|
|
1898
|
+
siteCandidateSpec.add_cache_files(availableFiles[tmpSiteName]["cache"])
|
|
1899
|
+
siteCandidateSpec.add_remote_files(availableFiles[tmpSiteName]["remote"])
|
|
1900
|
+
# add files as remote since WAN access is allowed
|
|
1901
|
+
if taskSpec.allowInputWAN() and tmpSiteSpec.allowWanInputAccess():
|
|
1902
|
+
siteCandidateSpec.remoteProtocol = "direct"
|
|
1903
|
+
for datasetSpec in inputChunk.getDatasets():
|
|
1904
|
+
siteCandidateSpec.add_remote_files(datasetSpec.Files)
|
|
1905
|
+
|
|
1906
|
+
# check if site is locked
|
|
1907
|
+
lockedByBrokerage = self.checkSiteLock(taskSpec.vo, taskSpec.prodSourceLabel, tmpPseudoSiteName, taskSpec.workQueue_ID, taskSpec.resource_type)
|
|
1908
|
+
|
|
1909
|
+
# check cap with nRunning
|
|
1910
|
+
nPilot *= corrNumPilot
|
|
1911
|
+
cutOffFactor = 2
|
|
1912
|
+
if tmpSiteSpec.capability == "ucore":
|
|
1913
|
+
if not inputChunk.isExpress():
|
|
1914
|
+
siteCandidateSpec.nRunningJobsCap = max(cutOffValue, cutOffFactor * tmpRTrunning)
|
|
1915
|
+
siteCandidateSpec.nQueuedJobs = tmpRTqueue
|
|
1916
|
+
else:
|
|
1917
|
+
if not inputChunk.isExpress():
|
|
1918
|
+
siteCandidateSpec.nRunningJobsCap = max(cutOffValue, cutOffFactor * nRunning)
|
|
1919
|
+
if useAssigned:
|
|
1920
|
+
siteCandidateSpec.nQueuedJobs = nActivated + nAssigned + nStarting
|
|
1921
|
+
else:
|
|
1922
|
+
siteCandidateSpec.nQueuedJobs = nActivated + nStarting
|
|
1923
|
+
if taskSpec.getNumJumboJobs() is None or not tmpSiteSpec.useJumboJobs():
|
|
1924
|
+
forJumbo = False
|
|
1925
|
+
else:
|
|
1926
|
+
forJumbo = True
|
|
1927
|
+
# OK message. Use jumbo as primary by default
|
|
1928
|
+
if not forJumbo:
|
|
1929
|
+
okMsg = f" use site={tmpPseudoSiteName} with weight={weight} {weightStr} criteria=+use"
|
|
1930
|
+
okAsPrimary = False
|
|
1931
|
+
else:
|
|
1932
|
+
okMsg = f" use site={tmpPseudoSiteName} for jumbo jobs with weight={weight} {weightStr} criteria=+usejumbo"
|
|
1933
|
+
okAsPrimary = True
|
|
1934
|
+
# checks
|
|
1935
|
+
if lockedByBrokerage:
|
|
1936
|
+
ngMsg = f" skip site={tmpPseudoSiteName} due to locked by another brokerage criteria=-lock"
|
|
1937
|
+
elif skipRemoteData:
|
|
1938
|
+
ngMsg = f" skip site={tmpPseudoSiteName} due to non-local data criteria=-non_local"
|
|
1939
|
+
elif not inputChunk.isExpress() and tmpSiteSpec.capability != "ucore" and siteCandidateSpec.nQueuedJobs > siteCandidateSpec.nRunningJobsCap:
|
|
1940
|
+
if not useAssigned:
|
|
1941
|
+
ngMsg = f" skip site={tmpPseudoSiteName} weight={weight} due to nDefined+nActivated+nStarting={siteCandidateSpec.nQueuedJobs} "
|
|
1942
|
+
ngMsg += "(nAssigned ignored due to data locally available) "
|
|
1943
|
+
else:
|
|
1944
|
+
ngMsg = f" skip site={tmpPseudoSiteName} weight={weight} due to nDefined+nActivated+nAssigned+nStarting={siteCandidateSpec.nQueuedJobs} "
|
|
1945
|
+
ngMsg += f"greater than max({cutOffValue},{cutOffFactor}*nRun) {weightStr} criteria=-cap"
|
|
1946
|
+
elif self.nwActive and inputChunk.isExpress() and weightNw < self.nw_threshold * self.nw_weight_multiplier:
|
|
1947
|
+
ngMsg = (
|
|
1948
|
+
f" skip site={tmpPseudoSiteName} due to low network weight for express task weightNw={weightNw} threshold={self.nw_threshold} "
|
|
1949
|
+
f"{weightStr} criteria=-lowNetworkWeight"
|
|
1950
|
+
)
|
|
1951
|
+
elif useCapRT and tmpRTqueue > max(cutOffValue, tmpRTrunning * RT_Cap) and not inputChunk.isExpress():
|
|
1952
|
+
ngMsg = f" skip site={tmpSiteName} since "
|
|
1953
|
+
if useAssigned:
|
|
1954
|
+
ngMsg += f"nDefined_rt+nActivated_rt+nAssigned_rt+nStarting_rt={tmpRTqueue} "
|
|
1955
|
+
else:
|
|
1956
|
+
ngMsg += f"nDefined_rt+nActivated_rt+nStarting_rt={tmpRTqueue} (nAssigned_rt ignored due to data locally available) "
|
|
1957
|
+
ngMsg += "with gshare+resource_type is greater than max({cutOffValue},{RT_Cap}*nRun_rt={RT_Cap}*{tmpRTrunning}) criteria=-cap_rt"
|
|
1958
|
+
elif (
|
|
1959
|
+
nRunning + nActivated + nAssigned + nStarting + nDefined == 0
|
|
1960
|
+
and taskSpec.currentPriority <= self.max_prio_for_bootstrap
|
|
1961
|
+
and not inputChunk.isMerging
|
|
1962
|
+
and not inputChunk.isExpress()
|
|
1963
|
+
):
|
|
1964
|
+
okMsg = f" use site={tmpPseudoSiteName} to bootstrap (no running or queued jobs) criteria=+use"
|
|
1965
|
+
ngMsg = (
|
|
1966
|
+
f" skip site={tmpPseudoSiteName} as others being bootstrapped (no running or queued jobs), "
|
|
1967
|
+
f"weight={weight} {weightStr} criteria=-others_bootstrap"
|
|
1968
|
+
)
|
|
1969
|
+
okAsPrimary = True
|
|
1970
|
+
# set weight to 0 for subsequent processing
|
|
1971
|
+
weight = 0
|
|
1972
|
+
siteCandidateSpec.weight = weight
|
|
1973
|
+
else:
|
|
1974
|
+
if min_weight > 0 and weight < min_weight:
|
|
1975
|
+
ngMsg = (
|
|
1976
|
+
f" skip site={tmpPseudoSiteName} due to weight below the minimum {min_weight_param}={min_weight}, "
|
|
1977
|
+
f"weight={weight} {weightStr} criteria=-below_min_weight"
|
|
1978
|
+
)
|
|
1979
|
+
elif useT1Weight:
|
|
1980
|
+
ngMsg = f" skip site={tmpPseudoSiteName} due to low total nRunningAll={nRunningAll} for negative T1 weight criteria=-t1_weight"
|
|
1981
|
+
if not largestNumRun or largestNumRun[-1] < nRunningAll:
|
|
1982
|
+
largestNumRun = (tmpPseudoSiteName, nRunningAll)
|
|
1983
|
+
okAsPrimary = True
|
|
1984
|
+
# copy primaries to secondary map
|
|
1985
|
+
for tmpWeight, tmpCandidates in weightMapPrimary.items():
|
|
1986
|
+
weightMapSecondary.setdefault(tmpWeight, [])
|
|
1987
|
+
weightMapSecondary[tmpWeight] += tmpCandidates
|
|
1988
|
+
weightMapPrimary = {}
|
|
1989
|
+
else:
|
|
1990
|
+
ngMsg = f" skip site={tmpPseudoSiteName} due to low weight, weight={weight} {weightStr} criteria=-low_weight"
|
|
1991
|
+
okAsPrimary = True
|
|
1992
|
+
# add to jumbo or primary or secondary
|
|
1993
|
+
if forJumbo:
|
|
1994
|
+
# only OK sites for jumbo
|
|
1995
|
+
if not okAsPrimary:
|
|
1996
|
+
continue
|
|
1997
|
+
weightMap = weightMapJumbo
|
|
1998
|
+
elif okAsPrimary:
|
|
1999
|
+
weightMap = weightMapPrimary
|
|
2000
|
+
else:
|
|
2001
|
+
weightMap = weightMapSecondary
|
|
2002
|
+
# add weight
|
|
2003
|
+
if weight not in weightMap:
|
|
2004
|
+
weightMap[weight] = []
|
|
2005
|
+
weightMap[weight].append((siteCandidateSpec, okMsg, ngMsg))
|
|
2006
|
+
# use only primary candidates
|
|
2007
|
+
weightMap = weightMapPrimary
|
|
2008
|
+
# use all weights
|
|
2009
|
+
weightRank = None
|
|
2010
|
+
# dump NG message
|
|
2011
|
+
for tmpWeight in weightMapSecondary.keys():
|
|
2012
|
+
for siteCandidateSpec, tmpOkMsg, tmpNgMsg in weightMapSecondary[tmpWeight]:
|
|
2013
|
+
tmpLog.info(tmpNgMsg)
|
|
2014
|
+
if weightMapPrimary == {}:
|
|
2015
|
+
tmpLog.info("available sites all capped")
|
|
2016
|
+
# add jumbo sites
|
|
2017
|
+
for weight, tmpList in weightMapJumbo.items():
|
|
2018
|
+
if weight not in weightMap:
|
|
2019
|
+
weightMap[weight] = []
|
|
2020
|
+
for tmpItem in tmpList:
|
|
2021
|
+
weightMap[weight].append(tmpItem)
|
|
2022
|
+
# max candidates for WORLD
|
|
2023
|
+
maxSiteCandidates = 10
|
|
2024
|
+
newScanSiteList = []
|
|
2025
|
+
weightList = sorted(weightMap.keys())
|
|
2026
|
+
weightList.reverse()
|
|
2027
|
+
# put 0 at the head of the list to give priorities bootstrap PQs
|
|
2028
|
+
if 0 in weightList:
|
|
2029
|
+
weightList.remove(0)
|
|
2030
|
+
weightList.insert(0, 0)
|
|
2031
|
+
for weightIdx, tmpWeight in enumerate(weightList):
|
|
2032
|
+
for siteCandidateSpec, tmpOkMsg, tmpNgMsg in weightMap[tmpWeight]:
|
|
2033
|
+
# candidates for jumbo jobs
|
|
2034
|
+
if taskSpec.getNumJumboJobs() is not None:
|
|
2035
|
+
tmpSiteSpec = self.siteMapper.getSite(siteCandidateSpec.siteName)
|
|
2036
|
+
if tmpSiteSpec.useJumboJobs():
|
|
2037
|
+
# use site for jumbo jobs
|
|
2038
|
+
tmpLog.info(tmpOkMsg)
|
|
2039
|
+
inputChunk.addSiteCandidateForJumbo(siteCandidateSpec)
|
|
2040
|
+
if inputChunk.useJumbo not in ["fake", "only"]:
|
|
2041
|
+
continue
|
|
2042
|
+
# candidates for normal jobs
|
|
2043
|
+
if (weightRank is None or weightIdx < weightRank) and (maxSiteCandidates is None or len(newScanSiteList) < maxSiteCandidates):
|
|
2044
|
+
# use site
|
|
2045
|
+
tmpLog.info(tmpOkMsg)
|
|
2046
|
+
newScanSiteList.append(siteCandidateSpec.siteName)
|
|
2047
|
+
inputChunk.addSiteCandidate(siteCandidateSpec)
|
|
2048
|
+
else:
|
|
2049
|
+
# dump NG message
|
|
2050
|
+
tmpLog.info(tmpNgMsg)
|
|
2051
|
+
oldScanSiteList = copy.copy(scanSiteList)
|
|
2052
|
+
scanSiteList = newScanSiteList
|
|
2053
|
+
self.add_summary_message(oldScanSiteList, scanSiteList, "final check", tmpLog, {})
|
|
2054
|
+
# final check
|
|
2055
|
+
if scanSiteList == []:
|
|
2056
|
+
self.dump_summary(tmpLog)
|
|
2057
|
+
tmpLog.error("no candidates")
|
|
2058
|
+
taskSpec.setErrDiag(tmpLog.uploadLog(taskSpec.jediTaskID))
|
|
2059
|
+
return retTmpError
|
|
2060
|
+
|
|
2061
|
+
self.dump_summary(tmpLog, scanSiteList)
|
|
2062
|
+
# return
|
|
2063
|
+
tmpLog.info("done")
|
|
2064
|
+
return self.SC_SUCCEEDED, inputChunk
|