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.
Files changed (375) hide show
  1. panda_server_source-0.8.0.dist-info/METADATA +55 -0
  2. panda_server_source-0.8.0.dist-info/RECORD +375 -0
  3. panda_server_source-0.8.0.dist-info/WHEEL +4 -0
  4. panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
  5. pandajedi/__init__.py +0 -0
  6. pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
  7. pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
  8. pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
  9. pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
  10. pandajedi/jedibrokerage/GenJobBroker.py +381 -0
  11. pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
  12. pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
  13. pandajedi/jedibrokerage/__init__.py +0 -0
  14. pandajedi/jediconfig/__init__.py +0 -0
  15. pandajedi/jediconfig/jedi_config.py +40 -0
  16. pandajedi/jedicore/FactoryBase.py +182 -0
  17. pandajedi/jedicore/Interaction.py +433 -0
  18. pandajedi/jedicore/JediDBProxy.py +56 -0
  19. pandajedi/jedicore/JediDBProxyPool.py +37 -0
  20. pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
  21. pandajedi/jedicore/JediException.py +13 -0
  22. pandajedi/jedicore/JediTaskBuffer.py +984 -0
  23. pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
  24. pandajedi/jedicore/MsgWrapper.py +124 -0
  25. pandajedi/jedicore/ProcessUtils.py +34 -0
  26. pandajedi/jedicore/SiteCandidate.py +90 -0
  27. pandajedi/jedicore/ThreadUtils.py +205 -0
  28. pandajedi/jedicore/__init__.py +0 -0
  29. pandajedi/jediddm/AtlasDDMClient.py +1708 -0
  30. pandajedi/jediddm/DDMClientBase.py +16 -0
  31. pandajedi/jediddm/DDMInterface.py +83 -0
  32. pandajedi/jediddm/GenDDMClient.py +17 -0
  33. pandajedi/jediddm/__init__.py +0 -0
  34. pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
  35. pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
  36. pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
  37. pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
  38. pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
  39. pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
  40. pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
  41. pandajedi/jedidog/GenWatchDog.py +21 -0
  42. pandajedi/jedidog/JumboWatchDog.py +147 -0
  43. pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
  44. pandajedi/jedidog/WatchDogBase.py +53 -0
  45. pandajedi/jedidog/__init__.py +0 -0
  46. pandajedi/jediexec/reniceJEDI.py +25 -0
  47. pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
  48. pandajedi/jedigen/TaskGeneratorBase.py +15 -0
  49. pandajedi/jedigen/__init__.py +0 -0
  50. pandajedi/jedimsgprocessor/__init__.py +0 -0
  51. pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
  52. pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
  53. pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
  54. pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
  55. pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
  56. pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
  57. pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
  58. pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
  59. pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
  60. pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
  61. pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
  62. pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
  63. pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
  64. pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
  65. pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
  66. pandajedi/jediorder/ContentsFeeder.py +785 -0
  67. pandajedi/jediorder/JediDaemon.py +27 -0
  68. pandajedi/jediorder/JediKnight.py +55 -0
  69. pandajedi/jediorder/JediMaster.py +238 -0
  70. pandajedi/jediorder/JediMsgProcessor.py +32 -0
  71. pandajedi/jediorder/JobBroker.py +33 -0
  72. pandajedi/jediorder/JobGenerator.py +2763 -0
  73. pandajedi/jediorder/JobSplitter.py +308 -0
  74. pandajedi/jediorder/JobThrottler.py +28 -0
  75. pandajedi/jediorder/PostProcessor.py +166 -0
  76. pandajedi/jediorder/TaskBroker.py +268 -0
  77. pandajedi/jediorder/TaskCommando.py +334 -0
  78. pandajedi/jediorder/TaskGenerator.py +17 -0
  79. pandajedi/jediorder/TaskRefiner.py +611 -0
  80. pandajedi/jediorder/TaskSetupper.py +17 -0
  81. pandajedi/jediorder/WatchDog.py +79 -0
  82. pandajedi/jediorder/__init__.py +0 -0
  83. pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
  84. pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
  85. pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
  86. pandajedi/jedipprocess/GenPostProcessor.py +35 -0
  87. pandajedi/jedipprocess/MailTemplates.py +185 -0
  88. pandajedi/jedipprocess/PostProcessorBase.py +269 -0
  89. pandajedi/jedipprocess/__init__.py +0 -0
  90. pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
  91. pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
  92. pandajedi/jedirefine/GenTaskRefiner.py +61 -0
  93. pandajedi/jedirefine/RefinerUtils.py +154 -0
  94. pandajedi/jedirefine/TaskRefinerBase.py +996 -0
  95. pandajedi/jedirefine/__init__.py +0 -0
  96. pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
  97. pandajedi/jedisetup/GenTaskSetupper.py +16 -0
  98. pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
  99. pandajedi/jedisetup/TaskSetupperBase.py +15 -0
  100. pandajedi/jedisetup/__init__.py +0 -0
  101. pandajedi/jeditest/SchemaChecker.py +41 -0
  102. pandajedi/jeditest/addContWoSoTask.py +54 -0
  103. pandajedi/jeditest/addDataCarouselRequest.py +47 -0
  104. pandajedi/jeditest/addEventServiceTask.py +101 -0
  105. pandajedi/jeditest/addHpoTask.py +81 -0
  106. pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
  107. pandajedi/jeditest/addNonAtlasTask.py +71 -0
  108. pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
  109. pandajedi/jeditest/addTestTask.py +124 -0
  110. pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
  111. pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
  112. pandajedi/jeditest/brokerTest.py +78 -0
  113. pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
  114. pandajedi/jeditest/contentsFeederTest.py +28 -0
  115. pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
  116. pandajedi/jeditest/ddmTest.py +4 -0
  117. pandajedi/jeditest/jobGenForOneTask.py +38 -0
  118. pandajedi/jeditest/jobGeneratorTest.py +43 -0
  119. pandajedi/jeditest/jobSplitterTest.py +104 -0
  120. pandajedi/jeditest/jobThrottlerTest.py +22 -0
  121. pandajedi/jeditest/msgProcessorTest.py +9 -0
  122. pandajedi/jeditest/postProcessorTest.py +18 -0
  123. pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
  124. pandajedi/jeditest/resubmitTaskParam.py +20 -0
  125. pandajedi/jeditest/taskBrokerTest.py +22 -0
  126. pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
  127. pandajedi/jeditest/taskBufferTest.py +5 -0
  128. pandajedi/jeditest/taskCommandoTest.py +28 -0
  129. pandajedi/jeditest/taskRefinerTest.py +30 -0
  130. pandajedi/jeditest/testOneTaskToRefine.py +49 -0
  131. pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
  132. pandajedi/jeditest/testPandaDDMMap.py +62 -0
  133. pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
  134. pandajedi/jeditest/watchDogTest.py +25 -0
  135. pandajedi/jeditest/workQueueTest.py +22 -0
  136. pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
  137. pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
  138. pandajedi/jedithrottle/GenJobThrottler.py +26 -0
  139. pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
  140. pandajedi/jedithrottle/__init__.py +0 -0
  141. pandaserver/__init__.py +0 -0
  142. pandaserver/api/__init__.py +0 -0
  143. pandaserver/api/v1/__init__.py +0 -0
  144. pandaserver/api/v1/common.py +339 -0
  145. pandaserver/api/v1/credential_management_api.py +372 -0
  146. pandaserver/api/v1/data_carousel_api.py +264 -0
  147. pandaserver/api/v1/event_api.py +269 -0
  148. pandaserver/api/v1/file_server_api.py +942 -0
  149. pandaserver/api/v1/harvester_api.py +445 -0
  150. pandaserver/api/v1/http_client.py +166 -0
  151. pandaserver/api/v1/idds_api.py +180 -0
  152. pandaserver/api/v1/job_api.py +463 -0
  153. pandaserver/api/v1/metaconfig_api.py +122 -0
  154. pandaserver/api/v1/pilot_api.py +895 -0
  155. pandaserver/api/v1/statistics_api.py +207 -0
  156. pandaserver/api/v1/system_api.py +186 -0
  157. pandaserver/api/v1/task_api.py +1520 -0
  158. pandaserver/api/v1/tests/__init__.py +0 -0
  159. pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
  160. pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
  161. pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
  162. pandaserver/api/v1/tests/idds_api_tests.py +45 -0
  163. pandaserver/api/v1/tests/job_api_tests.py +112 -0
  164. pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
  165. pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
  166. pandaserver/api/v1/tests/system_api_tests.py +58 -0
  167. pandaserver/api/v1/tests/task_api_tests.py +590 -0
  168. pandaserver/api/v1/version.py +2 -0
  169. pandaserver/api/v1/workflow_api.py +136 -0
  170. pandaserver/brokerage/SiteMapper.py +406 -0
  171. pandaserver/brokerage/__init__.py +0 -0
  172. pandaserver/brokerage/broker.py +272 -0
  173. pandaserver/config/__init__.py +0 -0
  174. pandaserver/config/config_utils.py +15 -0
  175. pandaserver/config/daemon_config.py +29 -0
  176. pandaserver/config/mcp_config.py +38 -0
  177. pandaserver/config/panda_config.py +233 -0
  178. pandaserver/configurator/Carbon.py +65 -0
  179. pandaserver/configurator/Configurator.py +908 -0
  180. pandaserver/configurator/__init__.py +0 -0
  181. pandaserver/configurator/aux.py +73 -0
  182. pandaserver/daemons/__init__.py +0 -0
  183. pandaserver/daemons/master.py +110 -0
  184. pandaserver/daemons/master_systemd.py +79 -0
  185. pandaserver/daemons/scripts/__init__.py +0 -0
  186. pandaserver/daemons/scripts/add_main.py +264 -0
  187. pandaserver/daemons/scripts/add_sub.py +221 -0
  188. pandaserver/daemons/scripts/cache_pilots.py +47 -0
  189. pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
  190. pandaserver/daemons/scripts/carbon.py +50 -0
  191. pandaserver/daemons/scripts/configurator.py +94 -0
  192. pandaserver/daemons/scripts/copyArchive.py +1387 -0
  193. pandaserver/daemons/scripts/datasetManager.py +1154 -0
  194. pandaserver/daemons/scripts/dummy_test.py +25 -0
  195. pandaserver/daemons/scripts/evpPD2P.py +144 -0
  196. pandaserver/daemons/scripts/hs_scrapers.py +264 -0
  197. pandaserver/daemons/scripts/metric_collector.py +927 -0
  198. pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
  199. pandaserver/daemons/scripts/pilotStreaming.py +109 -0
  200. pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
  201. pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
  202. pandaserver/daemons/scripts/task_evaluator.py +405 -0
  203. pandaserver/daemons/scripts/tmpwatch.py +70 -0
  204. pandaserver/daemons/scripts/worker_synchronization.py +97 -0
  205. pandaserver/daemons/utils.py +753 -0
  206. pandaserver/dataservice/DataServiceUtils.py +323 -0
  207. pandaserver/dataservice/ErrorCode.py +25 -0
  208. pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
  209. pandaserver/dataservice/__init__.py +0 -0
  210. pandaserver/dataservice/activator.py +79 -0
  211. pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
  212. pandaserver/dataservice/adder_dummy_plugin.py +31 -0
  213. pandaserver/dataservice/adder_gen.py +954 -0
  214. pandaserver/dataservice/adder_plugin_base.py +27 -0
  215. pandaserver/dataservice/adder_result.py +75 -0
  216. pandaserver/dataservice/adder_simple_plugin.py +160 -0
  217. pandaserver/dataservice/closer.py +247 -0
  218. pandaserver/dataservice/closer_atlas_plugin.py +90 -0
  219. pandaserver/dataservice/ddm.py +1381 -0
  220. pandaserver/dataservice/ddm_handler.py +95 -0
  221. pandaserver/dataservice/dyn_data_distributer.py +824 -0
  222. pandaserver/dataservice/event_lookup_client_ei.py +77 -0
  223. pandaserver/dataservice/event_picker.py +268 -0
  224. pandaserver/dataservice/finisher.py +194 -0
  225. pandaserver/dataservice/setupper.py +225 -0
  226. pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
  227. pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
  228. pandaserver/dataservice/setupper_plugin_base.py +67 -0
  229. pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
  230. pandaserver/jobdispatcher/ErrorCode.py +19 -0
  231. pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
  232. pandaserver/jobdispatcher/Protocol.py +443 -0
  233. pandaserver/jobdispatcher/Watcher.py +186 -0
  234. pandaserver/jobdispatcher/__init__.py +0 -0
  235. pandaserver/pandamcp/__init__.py +0 -0
  236. pandaserver/pandamcp/mcp_main.py +56 -0
  237. pandaserver/pandamcp/mcp_test_client.py +95 -0
  238. pandaserver/pandamcp/mcp_utils.py +74 -0
  239. pandaserver/proxycache/__init__.py +0 -0
  240. pandaserver/proxycache/panda_proxy_cache.py +240 -0
  241. pandaserver/proxycache/token_cache.py +141 -0
  242. pandaserver/server/.gacl +6 -0
  243. pandaserver/server/panda.py +539 -0
  244. pandaserver/srvcore/CoreUtils.py +572 -0
  245. pandaserver/srvcore/MailUtils.py +106 -0
  246. pandaserver/srvcore/__init__.py +0 -0
  247. pandaserver/srvcore/allowed_methods.py +100 -0
  248. pandaserver/srvcore/oidc_utils.py +152 -0
  249. pandaserver/srvcore/panda_request.py +196 -0
  250. pandaserver/srvcore/srv_msg_utils.py +42 -0
  251. pandaserver/taskbuffer/ConBridge.py +513 -0
  252. pandaserver/taskbuffer/DBProxyPool.py +95 -0
  253. pandaserver/taskbuffer/DataCarousel.py +3053 -0
  254. pandaserver/taskbuffer/DatasetSpec.py +137 -0
  255. pandaserver/taskbuffer/DdmSpec.py +201 -0
  256. pandaserver/taskbuffer/ErrorCode.py +87 -0
  257. pandaserver/taskbuffer/EventServiceUtils.py +396 -0
  258. pandaserver/taskbuffer/FileSpec.py +290 -0
  259. pandaserver/taskbuffer/GlobalShares.py +244 -0
  260. pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
  261. pandaserver/taskbuffer/Initializer.py +82 -0
  262. pandaserver/taskbuffer/InputChunk.py +1080 -0
  263. pandaserver/taskbuffer/JediCacheSpec.py +57 -0
  264. pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
  265. pandaserver/taskbuffer/JediFileSpec.py +242 -0
  266. pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
  267. pandaserver/taskbuffer/JobSpec.py +1006 -0
  268. pandaserver/taskbuffer/JobUtils.py +232 -0
  269. pandaserver/taskbuffer/NucleusSpec.py +116 -0
  270. pandaserver/taskbuffer/OraDBProxy.py +174 -0
  271. pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
  272. pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
  273. pandaserver/taskbuffer/ParseJobXML.py +350 -0
  274. pandaserver/taskbuffer/PickleFileSpec.py +18 -0
  275. pandaserver/taskbuffer/PickleJobSpec.py +25 -0
  276. pandaserver/taskbuffer/PrioUtil.py +40 -0
  277. pandaserver/taskbuffer/ProcessGroups.py +111 -0
  278. pandaserver/taskbuffer/ResourceSpec.py +175 -0
  279. pandaserver/taskbuffer/SQLDumper.py +37 -0
  280. pandaserver/taskbuffer/SQLManager.py +60 -0
  281. pandaserver/taskbuffer/SiteSpec.py +304 -0
  282. pandaserver/taskbuffer/SupErrors.py +10 -0
  283. pandaserver/taskbuffer/TaskBuffer.py +2882 -0
  284. pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
  285. pandaserver/taskbuffer/Utils.py +848 -0
  286. pandaserver/taskbuffer/WorkQueue.py +227 -0
  287. pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
  288. pandaserver/taskbuffer/WorkerSpec.py +143 -0
  289. pandaserver/taskbuffer/WrappedCursor.py +487 -0
  290. pandaserver/taskbuffer/WrappedPickle.py +88 -0
  291. pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
  292. pandaserver/taskbuffer/__init__.py +0 -0
  293. pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
  294. pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
  295. pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
  296. pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
  297. pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
  298. pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
  299. pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
  300. pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
  301. pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
  302. pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
  303. pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
  304. pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
  305. pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
  306. pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
  307. pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
  308. pandaserver/taskbuffer/retryModule.py +552 -0
  309. pandaserver/taskbuffer/task_split_rules.py +308 -0
  310. pandaserver/taskbuffer/workflow_processor.py +249 -0
  311. pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
  312. pandaserver/test/SchemaChecker.py +40 -0
  313. pandaserver/test/__init__.py +0 -0
  314. pandaserver/test/banUser.py +43 -0
  315. pandaserver/test/boostPrio.py +19 -0
  316. pandaserver/test/boostUser.py +35 -0
  317. pandaserver/test/callbackDDM.py +11 -0
  318. pandaserver/test/daod_on_demand.py +95 -0
  319. pandaserver/test/finishJob.py +184 -0
  320. pandaserver/test/finishTaskJEDI.py +11 -0
  321. pandaserver/test/getJobs.py +53 -0
  322. pandaserver/test/killJob.py +79 -0
  323. pandaserver/test/killJobLowPrio.py +99 -0
  324. pandaserver/test/killJobsInTask.py +56 -0
  325. pandaserver/test/killTask.py +80 -0
  326. pandaserver/test/killTaskJEDI.py +11 -0
  327. pandaserver/test/killUser.py +83 -0
  328. pandaserver/test/lsst/README.txt +7 -0
  329. pandaserver/test/lsst/lsstSubmit.py +139 -0
  330. pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
  331. pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
  332. pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
  333. pandaserver/test/reassignJobs.py +17 -0
  334. pandaserver/test/reassignSite.py +82 -0
  335. pandaserver/test/reassignTask.py +117 -0
  336. pandaserver/test/reassignWaiting.py +39 -0
  337. pandaserver/test/recoverLostFiles.py +7 -0
  338. pandaserver/test/reloadInputDS.py +51 -0
  339. pandaserver/test/sendCommandToJob.py +26 -0
  340. pandaserver/test/setDebugMode.py +38 -0
  341. pandaserver/test/setPriority.py +28 -0
  342. pandaserver/test/testEvgen.py +60 -0
  343. pandaserver/test/testEvgen17.py +58 -0
  344. pandaserver/test/testG4sim.py +81 -0
  345. pandaserver/test/testG4sim17.py +90 -0
  346. pandaserver/test/testGlobalShares.py +131 -0
  347. pandaserver/test/testReco.py +103 -0
  348. pandaserver/test/testSimulReco14.py +98 -0
  349. pandaserver/test/testSiteMap.py +26 -0
  350. pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
  351. pandaserver/userinterface/Client.py +1686 -0
  352. pandaserver/userinterface/UserIF.py +1799 -0
  353. pandaserver/userinterface/__init__.py +0 -0
  354. pandaserver/workflow/__init__.py +0 -0
  355. pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
  356. pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
  357. pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
  358. pandaserver/workflow/pcwl_test.py +122 -0
  359. pandaserver/workflow/pcwl_utils.py +384 -0
  360. pandaserver/workflow/psnakemake_container.json +68 -0
  361. pandaserver/workflow/psnakemake_task.json +62 -0
  362. pandaserver/workflow/psnakemake_test.py +60 -0
  363. pandaserver/workflow/snakeparser/__init__.py +3 -0
  364. pandaserver/workflow/snakeparser/extensions.py +74 -0
  365. pandaserver/workflow/snakeparser/log.py +53 -0
  366. pandaserver/workflow/snakeparser/names.py +3 -0
  367. pandaserver/workflow/snakeparser/parser.py +332 -0
  368. pandaserver/workflow/snakeparser/utils.py +29 -0
  369. pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
  370. pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
  371. pandaserver/workflow/workflow_base.py +531 -0
  372. pandaserver/workflow/workflow_core.py +1974 -0
  373. pandaserver/workflow/workflow_core_smoketest.py +388 -0
  374. pandaserver/workflow/workflow_parser.py +222 -0
  375. 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