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,800 @@
1
+ import copy
2
+ import datetime
3
+ import json
4
+ import os
5
+ import socket
6
+ import sys
7
+ import time
8
+ import traceback
9
+
10
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
11
+ from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
12
+
13
+ from pandajedi.jedibrokerage import AtlasBrokerUtils
14
+ from pandajedi.jediconfig import jedi_config
15
+ from pandajedi.jedicore.MsgWrapper import MsgWrapper
16
+ from pandaserver.dataservice import DataServiceUtils
17
+ from pandaserver.taskbuffer.ResourceSpec import ResourceSpecMapper
18
+
19
+ from .WatchDogBase import WatchDogBase
20
+
21
+ logger = PandaLogger().getLogger(__name__.split(".")[-1])
22
+
23
+ # dry run or not
24
+ DRY_RUN = False
25
+
26
+ # dedicated workqueue for preassigned task
27
+ magic_workqueue_id = 400
28
+ magic_workqueue_name = "wd_queuefiller"
29
+
30
+ # wait time before reassign jobs in seconds
31
+ reassign_jobs_wait_time = 300
32
+
33
+
34
+ # queue filler watchdog for ATLAS
35
+ class AtlasQueueFillerWatchDog(WatchDogBase):
36
+ # constructor
37
+ def __init__(self, taskBufferIF, ddmIF):
38
+ WatchDogBase.__init__(self, taskBufferIF, ddmIF)
39
+ self.pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}-dog"
40
+ self.vo = "atlas"
41
+ self.prodSourceLabelList = ["managed"]
42
+
43
+ # keys for cache
44
+ self.dc_main_key = "AtlasQueueFillerWatchDog"
45
+ self.dc_sub_key_pt = "PreassignedTasks"
46
+ self.dc_sub_key_bt = "BlacklistedTasks"
47
+ self.dc_sub_key_attr = "OriginalTaskAttributes"
48
+ self.dc_sub_key_ses = "SiteEmptySince"
49
+
50
+ # initialize the resource_spec_mapper object
51
+ resource_types = taskBufferIF.load_resource_types()
52
+ self.resource_spec_mapper = ResourceSpecMapper(resource_types)
53
+
54
+ self.refresh()
55
+
56
+ # refresh information stored in the instance
57
+ def refresh(self):
58
+ # work queue mapper
59
+ self.workQueueMapper = self.taskBufferIF.getWorkQueueMap()
60
+ # site mapper
61
+ self.siteMapper = self.taskBufferIF.get_site_mapper()
62
+ # all sites
63
+ allSiteList = []
64
+ for siteName, tmpSiteSpec in self.siteMapper.siteSpecList.items():
65
+ # if tmpSiteSpec.type == 'analysis' or tmpSiteSpec.is_grandly_unified():
66
+ allSiteList.append(siteName)
67
+ self.allSiteList = allSiteList
68
+ # software map
69
+ try:
70
+ self.sw_map = self.taskBufferIF.load_sw_map()
71
+ if self.sw_map is None:
72
+ logger.warning("failed to load software map: got None from taskBufferIF.load_sw_map; using empty map")
73
+ self.sw_map = {}
74
+ except Exception:
75
+ logger.error("exception while loading software map in refresh:\n%s", traceback.format_exc())
76
+ self.sw_map = {}
77
+
78
+ # update preassigned task map to cache
79
+ def _update_to_pt_cache(self, ptmap):
80
+ data_json = json.dumps(ptmap)
81
+ self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_pt, data=data_json)
82
+
83
+ # get preassigned task map from cache
84
+ def _get_from_pt_cache(self):
85
+ cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_pt)
86
+ if cache_spec is not None:
87
+ ret_map = json.loads(cache_spec.data)
88
+ return ret_map
89
+ else:
90
+ return dict()
91
+
92
+ # update blacklisted task map to cache
93
+ def _update_to_bt_cache(self, btmap):
94
+ data_json = json.dumps(btmap)
95
+ self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_bt, data=data_json)
96
+
97
+ # get blacklisted task map from cache
98
+ def _get_from_bt_cache(self):
99
+ cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_bt)
100
+ if cache_spec is not None:
101
+ ret_map = json.loads(cache_spec.data)
102
+ return ret_map
103
+ else:
104
+ return dict()
105
+
106
+ # update task original attributes map to cache
107
+ def _update_to_attr_cache(self, attrmap):
108
+ data_json = json.dumps(attrmap)
109
+ self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_attr, data=data_json)
110
+
111
+ # get task original attributes map from cache
112
+ def _get_from_attr_cache(self):
113
+ cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_attr)
114
+ if cache_spec is not None:
115
+ ret_map = json.loads(cache_spec.data)
116
+ return ret_map
117
+ else:
118
+ return dict()
119
+
120
+ # update site empty-since map to cache
121
+ def _update_to_ses_cache(self, sesmap):
122
+ data_json = json.dumps(sesmap)
123
+ self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_ses, data=data_json)
124
+
125
+ # get site empty-since map from cache
126
+ def _get_from_ses_cache(self):
127
+ cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_ses)
128
+ if cache_spec is not None:
129
+ ret_map = json.loads(cache_spec.data)
130
+ return ret_map
131
+ else:
132
+ return dict()
133
+
134
+ # get process lock to preassign
135
+ def _get_lock(self):
136
+ return self.taskBufferIF.lockProcess_JEDI(
137
+ vo=self.vo,
138
+ prodSourceLabel="managed",
139
+ cloud=None,
140
+ workqueue_id=None,
141
+ resource_name=None,
142
+ component="AtlasQueueFillerWatchDog.preassign",
143
+ pid=self.pid,
144
+ timeLimit=5,
145
+ )
146
+
147
+ # get map of site to list of RSEs
148
+ def get_site_rse_map(self, prod_source_label):
149
+ site_rse_map = {}
150
+ for tmpPseudoSiteName in self.allSiteList:
151
+ tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
152
+ tmpSiteName = tmpSiteSpec.get_unified_name()
153
+ scope_input, scope_output = DataServiceUtils.select_scope(tmpSiteSpec, prod_source_label, prod_source_label)
154
+ try:
155
+ endpoint_token_map = tmpSiteSpec.ddm_endpoints_input[scope_input].all
156
+ except KeyError:
157
+ continue
158
+ else:
159
+ # fill
160
+ site_rse_map[tmpSiteName] = [k for k, v in endpoint_token_map.items() if v.get("order_read") is not None]
161
+ # return
162
+ return site_rse_map
163
+
164
+ # get to-running rate of sites between 24 hours ago ~ 6 hours ago
165
+ def get_site_trr_map(self):
166
+ ret_val, ret_map = AtlasBrokerUtils.getSiteToRunRateStats(self.taskBufferIF, self.vo, time_window=86400, cutoff=0, cache_lifetime=600)
167
+ if ret_val:
168
+ return ret_map
169
+ else:
170
+ return None
171
+
172
+ def is_arm_only_site(self, site) -> bool:
173
+ """
174
+ Check if a site is ARM-only.
175
+
176
+ Args:
177
+ site (str): The name of the site to check.
178
+
179
+ Returns:
180
+ bool: True if the site is ARM-only, False otherwise.
181
+ """
182
+ ret = False
183
+ architecture_map = {}
184
+ if site in self.sw_map:
185
+ if architectures_list := self.sw_map[site].get("architectures"):
186
+ for architecture_dict in architectures_list:
187
+ if "type" in architecture_dict:
188
+ architecture_map[architecture_dict["type"]] = architecture_dict
189
+ # only tasks which can run with ARM if site architecture is ARM only
190
+ if cpu_architecture_dict := architecture_map.get("cpu"):
191
+ if arch_list := cpu_architecture_dict.get("arch"):
192
+ if "aarch64" in arch_list and ("excl" in arch_list or "x86_64" not in arch_list):
193
+ ret = True
194
+ return ret
195
+
196
+ def is_fat_container_site(self, site) -> bool:
197
+ """
198
+ Check if a site is a fat container site.
199
+
200
+ Args:
201
+ site (str): The name of the site to check.
202
+
203
+ Returns:
204
+ bool: True if the site is a fat container site, False otherwise.
205
+ """
206
+ ret = False
207
+ if site in self.sw_map:
208
+ containers_list = self.sw_map[site].get("containers", [])
209
+ if not ("any" in containers_list or "/cvmfs" in containers_list):
210
+ ret = True
211
+ return ret
212
+
213
+ def get_list_of_fat_container_names(self, site) -> list | None:
214
+ """
215
+ Get the list of fat container names for a site if it is a fat container site.
216
+
217
+ Args:
218
+ site (str): The name of the site to check.
219
+
220
+ Returns:
221
+ list | None: The list of fat container names if the site is a fat container site, None otherwise.
222
+ """
223
+ container_names_list = None
224
+ if self.is_fat_container_site(site):
225
+ container_names_list = []
226
+ if site in self.sw_map:
227
+ for tag_dict in self.sw_map[site].get("tags", []):
228
+ if container_name := tag_dict.get("container_name"):
229
+ container_names_list.append(container_name)
230
+ return container_names_list
231
+
232
+ # get available sites sorted list
233
+ def get_available_sites_list(self):
234
+ tmp_log = MsgWrapper(logger, "get_available_sites_list")
235
+ # initialize
236
+ available_sites_dict = {}
237
+ # get global share
238
+ tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(self.vo)
239
+ if not tmpSt:
240
+ # got nothing...
241
+ return available_sites_dict
242
+ # get to-running rate of sites
243
+ site_trr_map = self.get_site_trr_map()
244
+ if site_trr_map is None:
245
+ return available_sites_dict
246
+ # record for excluded site reasons
247
+ excluded_sites_dict = {
248
+ "not_online": set(),
249
+ "has_minrss": set(),
250
+ "es_jobseed": set(),
251
+ "low_trr": set(),
252
+ "enough_nq": set(),
253
+ }
254
+ # loop over sites
255
+ for tmpPseudoSiteName in self.allSiteList:
256
+ tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
257
+ tmpSiteName = tmpSiteSpec.get_unified_name()
258
+ # skip site already added
259
+ if tmpSiteName in available_sites_dict:
260
+ continue
261
+ # skip site if not for production
262
+ if not tmpSiteSpec.runs_production():
263
+ continue
264
+ # skip site is not online
265
+ if tmpSiteSpec.status not in ("online"):
266
+ excluded_sites_dict["not_online"].add(tmpSiteName)
267
+ continue
268
+ # skip if site has memory limitations
269
+ if tmpSiteSpec.minrss not in (0, None):
270
+ excluded_sites_dict["has_minrss"].add(tmpPseudoSiteName)
271
+ continue
272
+ # skip if site has event service jobseed
273
+ if tmpSiteSpec.getJobSeed() in ["es"]:
274
+ excluded_sites_dict["es_jobseed"].add(tmpPseudoSiteName)
275
+ continue
276
+ # tmp_num_slots as num_slots in harvester_slots
277
+ tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
278
+ tmp_num_slots = 0 if tmp_num_slots is None else tmp_num_slots
279
+ # skip if site (except ARM and fat container sites) has no harvester_slots setup and has not enough activity in the past 24 hours
280
+ site_trr = site_trr_map.get(tmpSiteName)
281
+ if (
282
+ not self.is_arm_only_site(tmpSiteName)
283
+ and not self.is_fat_container_site(tmpSiteName)
284
+ and tmp_num_slots == 0
285
+ and (site_trr is None or site_trr < 0.6)
286
+ ):
287
+ excluded_sites_dict["low_trr"].add(tmpSiteName)
288
+ continue
289
+ # get nQueue and nRunning
290
+ nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
291
+ nQueue = 0
292
+ for jobStatus in ["activated", "starting"]:
293
+ nQueue += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus)
294
+ # get nStandby; for queues that specify num_slots in harvester_slots
295
+ tmp_core_count = tmpSiteSpec.coreCount if tmpSiteSpec.coreCount else 8
296
+ nStandby = tmp_num_slots // tmp_core_count
297
+ # available sites: must be idle now
298
+ n_jobs_to_fill = max(20, max(nRunning, nStandby) * 2) * 0.25 - nQueue
299
+ if n_jobs_to_fill > 0:
300
+ available_sites_dict[tmpSiteName] = (tmpSiteName, tmpSiteSpec, n_jobs_to_fill)
301
+ else:
302
+ excluded_sites_dict["enough_nq"].add(tmpSiteName)
303
+ # list
304
+ available_sites_list = list(available_sites_dict.values())
305
+ # sort by n_jobs_to_fill
306
+ available_sites_list.sort(key=(lambda x: x[2]), reverse=True)
307
+ # log message
308
+ for reason, sites_set in excluded_sites_dict.items():
309
+ excluded_sites_str = ",".join(sorted(sites_set))
310
+ tmp_log.debug(f"excluded sites due to {reason} : {excluded_sites_str}")
311
+ included_sites_str = ",".join(sorted([x[0] for x in available_sites_list]))
312
+ tmp_log.debug("included sites : {sites}".format(reason=reason, sites=included_sites_str))
313
+ # return
314
+ return available_sites_list
315
+
316
+ # get busy sites
317
+ def get_busy_sites(self):
318
+ busy_sites_dict = {}
319
+ # get global share
320
+ tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(self.vo)
321
+ if not tmpSt:
322
+ # got nothing...
323
+ return busy_sites_dict
324
+ # get to-running rate of sites
325
+ site_trr_map = self.get_site_trr_map()
326
+ if site_trr_map is None:
327
+ return busy_sites_dict
328
+ # loop over sites
329
+ for tmpPseudoSiteName in self.allSiteList:
330
+ tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
331
+ tmpSiteName = tmpSiteSpec.get_unified_name()
332
+ # skip site already added
333
+ if tmpSiteName in busy_sites_dict:
334
+ continue
335
+ # initialize
336
+ is_busy = False
337
+ # site is not online viewed as busy
338
+ if tmpSiteSpec.status not in ("online"):
339
+ is_busy = True
340
+ # tmp_num_slots as num_slots in harvester_slots
341
+ tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
342
+ tmp_num_slots = 0 if tmp_num_slots is None else tmp_num_slots
343
+ # get nStandby; for queues that specify num_slots in harvester_slots
344
+ tmp_core_count = tmpSiteSpec.coreCount if tmpSiteSpec.coreCount else 8
345
+ nStandby = tmp_num_slots // tmp_core_count
346
+ # get nQueue and nRunning
347
+ nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
348
+ nQueue = 0
349
+ for jobStatus in ["activated", "starting"]:
350
+ nQueue += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus)
351
+ # busy sites
352
+ if nQueue > max(max(20, nRunning * 2) * 0.375, nStandby):
353
+ busy_sites_dict[tmpSiteName] = tmpSiteSpec
354
+ # return
355
+ return busy_sites_dict
356
+
357
+ # preassign tasks to site
358
+ def do_preassign(self):
359
+ tmp_log = MsgWrapper(logger, "do_preassign")
360
+ # refresh
361
+ self.refresh()
362
+ # list of resource type
363
+ resource_type_list = [rt.resource_name for rt in self.taskBufferIF.load_resource_types()]
364
+ # threshold of time duration in second that the queue keeps empty to trigger preassigning
365
+ empty_duration_threshold = 1800
366
+ # return map
367
+ ret_map = {
368
+ "to_reassign": {},
369
+ }
370
+ # loop
371
+ for prod_source_label in self.prodSourceLabelList:
372
+ # site-rse map
373
+ site_rse_map = self.get_site_rse_map(prod_source_label)
374
+ # parameters from GDP config
375
+ max_preassigned_tasks = self.taskBufferIF.getConfigValue("queue_filler", f"MAX_PREASSIGNED_TASKS_{prod_source_label}", "jedi", self.vo)
376
+ if max_preassigned_tasks is None:
377
+ max_preassigned_tasks = 3
378
+ min_files_ready = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}", "jedi", self.vo)
379
+ if min_files_ready is None:
380
+ min_files_ready = 50
381
+ min_files_ready_fc = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}_FC", "jedi", self.vo)
382
+ if min_files_ready_fc is None:
383
+ min_files_ready_fc = 10
384
+ min_files_remaining = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_REMAINING_{prod_source_label}", "jedi", self.vo)
385
+ if min_files_remaining is None:
386
+ min_files_remaining = 100
387
+ # load site empty-since map from cache
388
+ site_empty_since_map_orig = self._get_from_ses_cache()
389
+ # available sites
390
+ available_sites_list = self.get_available_sites_list()
391
+ # now timestamp
392
+ now_time = naive_utcnow()
393
+ now_time_ts = int(now_time.timestamp())
394
+ # update site empty-since map
395
+ site_empty_since_map = copy.deepcopy(site_empty_since_map_orig)
396
+ available_site_name_list = [x[0] for x in available_sites_list]
397
+ for site in site_empty_since_map_orig:
398
+ # remove sites that are no longer empty
399
+ if site not in available_site_name_list:
400
+ del site_empty_since_map[site]
401
+ for site in available_site_name_list:
402
+ # add newly found empty sites
403
+ if site not in site_empty_since_map_orig:
404
+ site_empty_since_map[site] = now_time_ts
405
+ self._update_to_ses_cache(site_empty_since_map)
406
+ # evaluate sites to preassign according to cache
407
+ # get blacklisted_tasks_map from cache
408
+ blacklisted_tasks_map = self._get_from_bt_cache()
409
+ blacklisted_tasks_set = set()
410
+ for bt_list in blacklisted_tasks_map.values():
411
+ blacklisted_tasks_set |= set(bt_list)
412
+ # loop over available sites to preassign
413
+ for site, tmpSiteSpec, n_jobs_to_fill in available_sites_list:
414
+ # rses of the available site
415
+ available_rses = set()
416
+ try:
417
+ available_rses.update(set(site_rse_map[site]))
418
+ except KeyError:
419
+ tmp_log.debug(f"skipped {site} since no good RSE")
420
+ continue
421
+ # do not consider TAPE rses
422
+ for rse in set(available_rses):
423
+ if "TAPE" in str(rse):
424
+ available_rses.remove(rse)
425
+ # skip if no rse for available site
426
+ if not available_rses:
427
+ tmp_log.debug(f"skipped {site} since no available RSE")
428
+ continue
429
+ # skip if no coreCount set
430
+ if not tmpSiteSpec.coreCount or tmpSiteSpec.coreCount <= 0:
431
+ tmp_log.debug(f"skipped {site} since coreCount is not set")
432
+ continue
433
+ # now timestamp
434
+ now_time = naive_utcnow()
435
+ now_time_ts = int(now_time.timestamp())
436
+ # skip if not empty for long enough
437
+ if site not in site_empty_since_map:
438
+ tmp_log.error(f"skipped {site} since not in empty-since map (should not happen)")
439
+ continue
440
+ empty_duration = now_time_ts - site_empty_since_map[site]
441
+ tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
442
+ if empty_duration < empty_duration_threshold and not tmp_num_slots:
443
+ tmp_log.debug(f"skipped {site} since not empty for enough time ({empty_duration}s < {empty_duration_threshold}s)")
444
+ continue
445
+ # only simul tasks if site has fairsharePolicy setup
446
+ processing_type_constraint = ""
447
+ if tmpSiteSpec.fairsharePolicy not in ("NULL", None):
448
+ if "type=simul:0%" in tmpSiteSpec.fairsharePolicy:
449
+ # skip if zero share of simul
450
+ tmp_log.debug(f"skipped {site} since with fairshare but zero for simul")
451
+ continue
452
+ else:
453
+ processing_type_constraint = "AND t.processingType='simul' "
454
+ # only tasks which can run with ARM if site architecture is ARM only
455
+ architecture_constraint = ""
456
+ if self.is_arm_only_site(site):
457
+ tmp_log.debug(f"{site} is ARM-only site")
458
+ architecture_constraint += "AND t.architecture LIKE '%aarch64%' "
459
+ # only tasks with fat container if site is fat container only
460
+ container_name_constraint = ""
461
+ container_name_var_map = {}
462
+ if container_names_list := self.get_list_of_fat_container_names(site):
463
+ tmp_log.debug(f"{site} is fat container site")
464
+ container_name_var_names_str, container_name_var_map = get_sql_IN_bind_variables(container_names_list, prefix=":container_name")
465
+ container_name_constraint += "AND t.container_name IS NOT NULL "
466
+ container_name_constraint += f"AND t.container_name IN ({container_name_var_names_str}) "
467
+ elif container_names_list == []:
468
+ # fat-container-only site with no specific container names configured; skipped the site
469
+ tmp_log.debug(f"skipped {site} since it is fat-container-only site but no specific container names configured")
470
+ continue
471
+ # site attributes
472
+ site_maxrss = tmpSiteSpec.maxrss if tmpSiteSpec.maxrss not in (0, None) else 999999
473
+ site_corecount = tmpSiteSpec.coreCount
474
+ site_capability = str(tmpSiteSpec.capability).lower()
475
+ # make sql parameters of rses
476
+ available_rses = list(available_rses)
477
+ rse_params_list = []
478
+ rse_params_map = {}
479
+ for j, rse in enumerate(available_rses):
480
+ rse_param = f":rse_{j + 1}"
481
+ rse_params_list.append(rse_param)
482
+ rse_params_map[rse_param] = rse
483
+ rse_params_str = ",".join(rse_params_list)
484
+ # sql
485
+ base_sql_query = (
486
+ "SELECT t.jediTaskID, t.workQueue_ID "
487
+ "FROM {jedi_schema}.JEDI_Tasks t "
488
+ "WHERE t.status IN ('ready','running') AND t.lockedBy IS NULL "
489
+ "AND t.prodSourceLabel=:prodSourceLabel "
490
+ "AND t.resource_type=:resource_type "
491
+ "AND site IS NULL "
492
+ "AND (COALESCE(t.baseRamCount, 0) + (CASE WHEN t.ramUnit IN ('MBPerCore','MBPerCoreFixed') THEN t.ramCount*:site_corecount ELSE t.ramCount END))*0.95 < :site_maxrss "
493
+ "AND t.eventService=0 "
494
+ "AND EXISTS ( "
495
+ "SELECT * FROM {jedi_schema}.JEDI_Dataset_Locality dl "
496
+ "WHERE dl.jediTaskID=t.jediTaskID "
497
+ "AND dl.rse IN ({rse_params_str}) "
498
+ ") "
499
+ "{processing_type_constraint} "
500
+ "{architecture_constraint} "
501
+ "{container_name_constraint} "
502
+ "AND EXISTS ( "
503
+ "SELECT d.datasetID FROM {jedi_schema}.JEDI_Datasets d "
504
+ "WHERE t.jediTaskID=d.jediTaskID AND d.type='input' "
505
+ "AND d.nFilesToBeUsed-d.nFilesUsed>=:min_files_ready "
506
+ "AND d.nFiles-d.nFilesUsed>=:min_files_remaining "
507
+ ") "
508
+ "ORDER BY t.currentPriority DESC "
509
+ ).format(
510
+ jedi_schema=jedi_config.db.schemaJEDI,
511
+ rse_params_str=rse_params_str,
512
+ processing_type_constraint=processing_type_constraint,
513
+ architecture_constraint=architecture_constraint,
514
+ container_name_constraint=container_name_constraint,
515
+ )
516
+ # loop over resource type
517
+ for resource_type in resource_type_list:
518
+ # key name for preassigned_tasks_map = site + resource_type
519
+ key_name = f"{site}|{resource_type}"
520
+
521
+ # skip if no match: site is single core and resource_type is multi core
522
+ if site_capability == "score" and self.resource_spec_mapper.is_multi_core(resource_type):
523
+ continue
524
+
525
+ # skip if no match: site is multi core and resource_type is single core
526
+ elif site_capability == "mcore" and self.resource_spec_mapper.is_single_core(resource_type):
527
+ continue
528
+
529
+ params_map = {
530
+ ":prodSourceLabel": prod_source_label,
531
+ ":resource_type": resource_type,
532
+ ":site_maxrss": site_maxrss,
533
+ ":site_corecount": site_corecount,
534
+ ":min_files_ready": min_files_ready_fc if self.is_fat_container_site(site) else min_files_ready,
535
+ ":min_files_remaining": min_files_remaining,
536
+ }
537
+ params_map.update(rse_params_map)
538
+ params_map.update(container_name_var_map)
539
+ # get preassigned_tasks_map from cache
540
+ preassigned_tasks_map = self._get_from_pt_cache()
541
+ preassigned_tasks_cached = preassigned_tasks_map.get(key_name, [])
542
+ # get task_orig_attr_map from cache
543
+ task_orig_attr_map = self._get_from_attr_cache()
544
+ # number of tasks already preassigned
545
+ n_preassigned_tasks = len(preassigned_tasks_cached)
546
+ # number of tasks to preassign
547
+ n_tasks_to_preassign = max(max_preassigned_tasks - n_preassigned_tasks, 0)
548
+ # preassign
549
+ if n_tasks_to_preassign <= 0:
550
+ tmp_log.debug(f"{key_name:<64} already has enough preassigned tasks ({n_preassigned_tasks:>3}) ; skipped ")
551
+ elif DRY_RUN:
552
+ # for dry run, just query and update cache without updating db
553
+ dry_sql_query = base_sql_query
554
+ # tmp_log.debug('[dry run] {} {}'.format(dry_sql_query, params_map))
555
+ res = self.taskBufferIF.querySQL(dry_sql_query, params_map)
556
+ n_tasks = 0 if res is None else len(res)
557
+ if n_tasks > 0:
558
+ result = [x[0] for x in res if x[0] not in preassigned_tasks_cached]
559
+ updated_tasks = result[:n_tasks_to_preassign]
560
+ tmp_log.debug(f"[dry run] {key_name:<64} {n_tasks_to_preassign:>3} tasks would be preassigned ")
561
+ # update preassigned_tasks_map into cache
562
+ preassigned_tasks_map[key_name] = list(set(updated_tasks) | set(preassigned_tasks_cached))
563
+ tmp_log.debug(f"{str(updated_tasks)} ; {str(preassigned_tasks_map[key_name])}")
564
+ self._update_to_pt_cache(preassigned_tasks_map)
565
+ else:
566
+ # for real run, query with FOR UPDATE and update db to preassign tasks
567
+ sql_query = base_sql_query + "FOR UPDATE "
568
+ updated_tasks_orig_attr = self.taskBufferIF.queryTasksToPreassign_JEDI(
569
+ sql_query, params_map, site, blacklist=blacklisted_tasks_set, limit=n_tasks_to_preassign
570
+ )
571
+ if updated_tasks_orig_attr is None:
572
+ # dbproxy method failed
573
+ tmp_log.error(f"{key_name:<64} failed to preassign tasks ")
574
+ else:
575
+ n_tasks = len(updated_tasks_orig_attr)
576
+ if n_tasks > 0:
577
+ updated_tasks = [x[0] for x in updated_tasks_orig_attr]
578
+ tmp_log.info(f"{key_name:<64} {str(n_tasks):>3} tasks preassigned : {updated_tasks}")
579
+ # update preassigned_tasks_map into cache
580
+ preassigned_tasks_map[key_name] = list(set(updated_tasks) | set(preassigned_tasks_cached))
581
+ self._update_to_pt_cache(preassigned_tasks_map)
582
+ # update task_orig_attr_map into cache and return map
583
+ for taskid, orig_attr in updated_tasks_orig_attr:
584
+ taskid_str = str(taskid)
585
+ task_orig_attr_map[taskid_str] = orig_attr
586
+ ret_map["to_reassign"][taskid] = {
587
+ "site": site,
588
+ "n_jobs_to_fill": n_jobs_to_fill,
589
+ }
590
+ self._update_to_attr_cache(task_orig_attr_map)
591
+ # Kibana log
592
+ for taskid in updated_tasks:
593
+ tmp_log.debug(f"#ATM #KV jediTaskID={taskid} action=do_preassign site={site} rtype={resource_type} preassigned ")
594
+ else:
595
+ tmp_log.debug(f"{key_name:<64} found no proper task to preassign")
596
+ # total preassigned tasks
597
+ preassigned_tasks_map = self._get_from_pt_cache()
598
+ n_pt_tot = sum([len(pt_list) for pt_list in preassigned_tasks_map.values()])
599
+ if DRY_RUN:
600
+ tmp_log.debug(f"[dry run] would have {n_pt_tot} tasks preassigned in total")
601
+ else:
602
+ tmp_log.debug(f"now {n_pt_tot} tasks preassigned in total")
603
+ # return
604
+ return ret_map
605
+
606
+ # undo preassign tasks
607
+ def undo_preassign(self):
608
+ tmp_log = MsgWrapper(logger, "undo_preassign")
609
+ # refresh
610
+ self.refresh()
611
+ # busy sites
612
+ busy_sites_dict = self.get_busy_sites()
613
+ # loop to undo preassigning
614
+ for prod_source_label in self.prodSourceLabelList:
615
+ # parameter from GDP config
616
+ max_preassigned_tasks = self.taskBufferIF.getConfigValue("queue_filler", f"MAX_PREASSIGNED_TASKS_{prod_source_label}", "jedi", self.vo)
617
+ if max_preassigned_tasks is None:
618
+ max_preassigned_tasks = 3
619
+ min_files_ready = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}", "jedi", self.vo)
620
+ if min_files_ready is None:
621
+ min_files_ready = 50
622
+ min_files_remaining = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_REMAINING_{prod_source_label}", "jedi", self.vo)
623
+ if min_files_remaining is None:
624
+ min_files_remaining = 100
625
+ # clean up outdated blacklist
626
+ blacklist_duration_hours = 12
627
+ blacklisted_tasks_map_orig = self._get_from_bt_cache()
628
+ blacklisted_tasks_map = copy.deepcopy(blacklisted_tasks_map_orig)
629
+ now_time = naive_utcnow()
630
+ min_allowed_time = now_time - datetime.timedelta(hours=blacklist_duration_hours)
631
+ min_allowed_ts = int(min_allowed_time.timestamp())
632
+ for ts_str in blacklisted_tasks_map_orig:
633
+ ts = int(ts_str)
634
+ if ts < min_allowed_ts:
635
+ del blacklisted_tasks_map[ts_str]
636
+ self._update_to_bt_cache(blacklisted_tasks_map)
637
+ n_bt_old = sum([len(bt_list) for bt_list in blacklisted_tasks_map_orig.values()])
638
+ n_bt = sum([len(bt_list) for bt_list in blacklisted_tasks_map.values()])
639
+ tmp_log.debug(f"done cleanup blacklist; before {n_bt_old} , now {n_bt} tasks in blacklist")
640
+ # get a copy of preassigned_tasks_map from cache
641
+ preassigned_tasks_map_orig = self._get_from_pt_cache()
642
+ preassigned_tasks_map = copy.deepcopy(preassigned_tasks_map_orig)
643
+ # clean up task_orig_attr_map in cache
644
+ task_orig_attr_map_orig = self._get_from_attr_cache()
645
+ task_orig_attr_map = copy.deepcopy(task_orig_attr_map_orig)
646
+ all_preassigned_taskids = set()
647
+ for taskid_list in preassigned_tasks_map_orig.values():
648
+ all_preassigned_taskids |= set(taskid_list)
649
+ for taskid_str in task_orig_attr_map_orig:
650
+ taskid = int(taskid_str)
651
+ if taskid not in all_preassigned_taskids:
652
+ del task_orig_attr_map[taskid_str]
653
+ self._update_to_attr_cache(task_orig_attr_map)
654
+ # loop on preassigned tasks in cache
655
+ for key_name in preassigned_tasks_map_orig:
656
+ # parse key name = site + resource_type
657
+ site, resource_type = key_name.split("|")
658
+ # preassigned tasks in cache
659
+ preassigned_tasks_cached = preassigned_tasks_map.get(key_name, [])
660
+ # force_undo=True for all tasks in busy sites, and force_undo=False for tasks not in status to generate jobs
661
+ force_undo = False
662
+ if site in busy_sites_dict or len(preassigned_tasks_cached) > max_preassigned_tasks:
663
+ force_undo = True
664
+ reason_str = (
665
+ "site busy or offline or with too many preassigned tasks" if force_undo else "task paused/terminated or without enough files to process"
666
+ )
667
+ # parameters for undo, kinda ugly
668
+ params_map = {
669
+ ":min_files_ready": min_files_ready,
670
+ ":min_files_remaining": min_files_remaining,
671
+ }
672
+ # undo preassign
673
+ had_undo = False
674
+ updated_tasks = []
675
+ if DRY_RUN:
676
+ if force_undo:
677
+ updated_tasks = list(preassigned_tasks_cached)
678
+ n_tasks = len(updated_tasks)
679
+ else:
680
+ preassigned_tasks_list = []
681
+ preassigned_tasks_params_map = {}
682
+ for j, taskid in enumerate(preassigned_tasks_cached):
683
+ pt_param = f":pt_{j + 1}"
684
+ preassigned_tasks_list.append(pt_param)
685
+ preassigned_tasks_params_map[pt_param] = taskid
686
+ if not preassigned_tasks_list:
687
+ continue
688
+ preassigned_tasks_params_str = ",".join(preassigned_tasks_list)
689
+ dry_sql_query = (
690
+ "SELECT t.jediTaskID "
691
+ "FROM {jedi_schema}.JEDI_Tasks t "
692
+ "WHERE t.jediTaskID IN ({preassigned_tasks_params_str}) "
693
+ "AND t.site IS NOT NULL "
694
+ "AND NOT ( "
695
+ "t.status IN ('ready','running') "
696
+ "AND EXISTS ( "
697
+ "SELECT d.datasetID FROM {0}.JEDI_Datasets d "
698
+ "WHERE t.jediTaskID=d.jediTaskID AND d.type='input' "
699
+ "AND d.nFilesToBeUsed-d.nFilesUsed>=:min_files_ready AND d.nFiles-d.nFilesUsed>=:min_files_remaining "
700
+ ") "
701
+ ") "
702
+ ).format(jedi_schema=jedi_config.db.schemaJEDI, preassigned_tasks_params_str=preassigned_tasks_params_str)
703
+ res = self.taskBufferIF.querySQL(dry_sql_query, preassigned_tasks_params_map)
704
+ n_tasks = 0 if res is None else len(res)
705
+ if n_tasks > 0:
706
+ updated_tasks = [x[0] for x in res]
707
+ # tmp_log.debug('[dry run] {} {} force={}'.format(key_name, str(updated_tasks), force_undo))
708
+ had_undo = True
709
+ if n_tasks > 0:
710
+ tmp_log.debug(f"[dry run] {key_name:<64} {n_tasks:>3} preassigned tasks would be undone ({reason_str}) ")
711
+ else:
712
+ updated_tasks = self.taskBufferIF.undoPreassignedTasks_JEDI(
713
+ preassigned_tasks_cached, task_orig_attr_map=task_orig_attr_map, params_map=params_map, force=force_undo
714
+ )
715
+ if updated_tasks is None:
716
+ # dbproxy method failed
717
+ tmp_log.error(f"{key_name:<64} failed to undo preassigned tasks (force={force_undo})")
718
+ else:
719
+ had_undo = True
720
+ n_tasks = len(updated_tasks)
721
+ if n_tasks > 0:
722
+ tmp_log.info(f"{key_name:<64} {str(n_tasks):>3} preassigned tasks undone ({reason_str}) : {updated_tasks} ")
723
+ # Kibana log
724
+ for taskid in updated_tasks:
725
+ tmp_log.debug(
726
+ f"#ATM #KV jediTaskID={taskid} action=undo_preassign site={site} rtype={resource_type} un-preassigned since {reason_str}"
727
+ )
728
+ # update preassigned_tasks_map into cache
729
+ if had_undo:
730
+ if force_undo:
731
+ del preassigned_tasks_map[key_name]
732
+ else:
733
+ tmp_tasks_set = set(preassigned_tasks_cached) - set(updated_tasks)
734
+ if not tmp_tasks_set:
735
+ del preassigned_tasks_map[key_name]
736
+ else:
737
+ preassigned_tasks_map[key_name] = list(tmp_tasks_set)
738
+ self._update_to_pt_cache(preassigned_tasks_map)
739
+ # update blacklisted_tasks_map into cache
740
+ if had_undo and not force_undo:
741
+ blacklisted_tasks_map_orig = self._get_from_bt_cache()
742
+ blacklisted_tasks_map = copy.deepcopy(blacklisted_tasks_map_orig)
743
+ now_time = naive_utcnow()
744
+ now_rounded_ts = int(now_time.replace(minute=0, second=0, microsecond=0).timestamp())
745
+ ts_str = str(now_rounded_ts)
746
+ if ts_str in blacklisted_tasks_map_orig:
747
+ tmp_bt_list = blacklisted_tasks_map[ts_str]
748
+ blacklisted_tasks_map[ts_str] = list(set(tmp_bt_list) | set(updated_tasks))
749
+ else:
750
+ blacklisted_tasks_map[ts_str] = list(updated_tasks)
751
+ self._update_to_bt_cache(blacklisted_tasks_map)
752
+
753
+ # close and reassign jobs of preassigned tasks
754
+ def reassign_jobs(self, to_reassign_map):
755
+ tmp_log = MsgWrapper(logger, "reassign_jobs")
756
+ for jedi_taskid, value_map in to_reassign_map.items():
757
+ site = value_map["site"]
758
+ n_jobs_to_fill = value_map["n_jobs_to_fill"]
759
+ # compute n_jobs_to_close from n_jobs_to_fill
760
+ n_jobs_to_close = int(n_jobs_to_fill / 3)
761
+ # reassign
762
+ n_jobs_closed = self.taskBufferIF.reassignJobsInPreassignedTask_JEDI(jedi_taskid, site, n_jobs_to_close)
763
+ if n_jobs_closed is None:
764
+ tmp_log.debug(f"jediTaskID={jedi_taskid} no longer ready/running or not assigned to {site} , skipped")
765
+ else:
766
+ tmp_log.debug(f"jediTaskID={jedi_taskid} to {site} , closed {n_jobs_closed} jobs")
767
+
768
+ # main
769
+ def doAction(self):
770
+ try:
771
+ # get logger
772
+ origTmpLog = MsgWrapper(logger)
773
+ origTmpLog.debug("start")
774
+ # lock (also in DRY_RUN to avoid races on shared caches)
775
+ got_lock = self._get_lock()
776
+ if not got_lock:
777
+ origTmpLog.debug("locked by another process. Skipped")
778
+ return self.SC_SUCCEEDED
779
+ origTmpLog.debug("got lock")
780
+ # undo preassigned tasks
781
+ self.undo_preassign()
782
+ # preassign tasks to sites
783
+ ret_map = self.do_preassign()
784
+ # in non-dry-run mode, optionally wait and reassign jobs
785
+ if not DRY_RUN:
786
+ # to-reassign map
787
+ to_reassign_map = ret_map["to_reassign"]
788
+ if to_reassign_map:
789
+ # wait some minutes so that preassigned tasks can be brokered, before reassigning jobs
790
+ origTmpLog.debug(f"wait {reassign_jobs_wait_time}s before reassigning jobs")
791
+ time.sleep(reassign_jobs_wait_time)
792
+ # reassign jobs of preassigned tasks
793
+ self.reassign_jobs(to_reassign_map)
794
+ except Exception:
795
+ errtype, errvalue = sys.exc_info()[:2]
796
+ err_str = traceback.format_exc()
797
+ origTmpLog.error(f"failed with {errtype} {errvalue} ; {err_str}")
798
+ # return
799
+ origTmpLog.debug("done")
800
+ return self.SC_SUCCEEDED