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,1851 @@
1
+ """
2
+ This module is designed to setup datasets for the ATLAS experiment.
3
+ The setup process involves preparing the necessary data and resources for the ATLAS jobs to run.
4
+ This includes tasks such as data placement, dataset creation, and job configuration.
5
+ The 'SetupperAtlasPlugin' class in this module inherits from the 'SetupperPluginBase' class and overrides its methods to provide ATLAS-specific functionality.
6
+ The 'setupper.py' module uses this plugin when setting up datasets for ATLAS jobs.
7
+
8
+ """
9
+
10
+ import datetime
11
+ import os
12
+ import re
13
+ import sys
14
+ import time
15
+ import traceback
16
+ import uuid
17
+ from typing import Dict, List, Optional, Tuple
18
+
19
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
20
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
21
+ from rucio.common.exception import DataIdentifierNotFound
22
+
23
+ import pandaserver.brokerage.broker
24
+ from pandaserver.brokerage.SiteMapper import SiteMapper
25
+ from pandaserver.config import panda_config
26
+ from pandaserver.dataservice import DataServiceUtils, ErrorCode
27
+ from pandaserver.dataservice.DataServiceUtils import select_scope
28
+ from pandaserver.dataservice.ddm import rucioAPI
29
+ from pandaserver.dataservice.setupper_plugin_base import SetupperPluginBase
30
+ from pandaserver.taskbuffer import EventServiceUtils, JobUtils
31
+ from pandaserver.taskbuffer.DatasetSpec import DatasetSpec
32
+
33
+
34
+ class SetupperAtlasPlugin(SetupperPluginBase):
35
+ """
36
+ This class is a plugin for setting up datasets specifically for the ATLAS experiment.
37
+ It inherits from the SetupperPluginBase class and overrides its methods to provide
38
+ ATLAS-specific functionality.
39
+ """
40
+
41
+ # constructor
42
+ def __init__(self, taskBuffer, jobs: List, logger, **params: Dict) -> None:
43
+ """
44
+ Constructor for the SetupperAtlasPlugin class.
45
+
46
+ :param taskBuffer: The buffer for tasks.
47
+ :param jobs: The jobs to be processed.
48
+ :param logger: The logger to be used for logging.
49
+ :param params: Additional parameters.
50
+ """
51
+ # defaults
52
+ default_map = {
53
+ "resubmit": False,
54
+ }
55
+ SetupperPluginBase.__init__(self, taskBuffer, jobs, logger, params, default_map)
56
+ # VUIDs of dispatchDBlocks
57
+ self.vuid_map = {}
58
+ # file list for dispDS for PandaDDM
59
+ self.disp_file_list = {}
60
+ # site mapper
61
+ self.site_mapper = None
62
+ # available files at satellite sites
63
+ self.available_lfns_in_satellites = {}
64
+ # list of missing datasets
65
+ self.missing_dataset_list = {}
66
+ # lfn ds map
67
+ self.lfn_dataset_map = {}
68
+ # source label
69
+ self.prod_source_label = None
70
+ self.job_label = None
71
+
72
+ # main
73
+ def run(self) -> None:
74
+ """
75
+ Main method for running the setup process.
76
+ """
77
+ tmp_logger = LogWrapper(self.logger, "<run>")
78
+ try:
79
+ tmp_logger.debug("start")
80
+ self.memory_check()
81
+ bunch_tag = ""
82
+ tag_job = None
83
+ time_start = naive_utcnow()
84
+ if self.jobs is not None and len(self.jobs) > 0:
85
+ tag_job = self.jobs[0]
86
+ elif len(self.jumbo_jobs) > 0:
87
+ tag_job = self.jumbo_jobs[0]
88
+ if tag_job is not None:
89
+ bunch_tag = f"PandaID:{tag_job.PandaID} type:{tag_job.prodSourceLabel} taskID:{tag_job.taskID} pType={tag_job.processingType}"
90
+ tmp_logger.debug(bunch_tag)
91
+ self.prod_source_label = tag_job.prodSourceLabel
92
+ self.job_label = tag_job.job_label
93
+ # instantiate site mapper
94
+ self.site_mapper = SiteMapper(self.task_buffer)
95
+ # correctLFN
96
+ self.correct_lfn()
97
+ # run full Setupper
98
+ # invoke brokerage
99
+ tmp_logger.debug("running broker.schedule")
100
+ self.memory_check()
101
+ pandaserver.brokerage.broker.schedule(self.jobs, self.site_mapper)
102
+
103
+ # remove waiting jobs
104
+ self.remove_waiting_jobs()
105
+
106
+ # setup dispatch dataset
107
+ tmp_logger.debug("running setup_source")
108
+ self.memory_check()
109
+ self.setup_source()
110
+ self.memory_check()
111
+
112
+ # sort by site so that larger sub datasets are created in the next step
113
+ if self.jobs != [] and self.jobs[0].prodSourceLabel in [
114
+ "managed",
115
+ "test",
116
+ ]:
117
+ tmp_job_map = {}
118
+ for tmp_job in self.jobs:
119
+ # add site
120
+ if tmp_job.computingSite not in tmp_job_map:
121
+ tmp_job_map[tmp_job.computingSite] = []
122
+ # add job
123
+ tmp_job_map[tmp_job.computingSite].append(tmp_job)
124
+ # make new list
125
+ tmp_job_list = []
126
+ for jobs in tmp_job_map.values():
127
+ tmp_job_list += jobs
128
+ # set new list
129
+ self.jobs = tmp_job_list
130
+ # create dataset for outputs and assign destination
131
+ if self.jobs != [] and self.jobs[0].prodSourceLabel in [
132
+ "managed",
133
+ "test",
134
+ ]:
135
+ # count the number of jobs per _dis
136
+ i_bunch = 0
137
+ prev_dis_ds_name = None
138
+ n_jobs_per_dis_list = []
139
+ for tmp_job in self.jobs:
140
+ if prev_dis_ds_name is not None and prev_dis_ds_name != tmp_job.dispatchDBlock:
141
+ n_jobs_per_dis_list.append(i_bunch)
142
+ i_bunch = 0
143
+ # increment
144
+ i_bunch += 1
145
+ # set _dis name
146
+ prev_dis_ds_name = tmp_job.dispatchDBlock
147
+ # remaining
148
+ if i_bunch != 0:
149
+ n_jobs_per_dis_list.append(i_bunch)
150
+ # split sub datasets
151
+ i_bunch = 0
152
+ n_bunch_max = 50
153
+ tmp_index_job = 0
154
+ for n_jobs_per_dis in n_jobs_per_dis_list:
155
+ # check _dis boundary so that the same _dis doesn't contribute to many _subs
156
+ if i_bunch + n_jobs_per_dis > n_bunch_max:
157
+ if i_bunch != 0:
158
+ self.setup_destination(start_idx=tmp_index_job, n_jobs_in_loop=i_bunch)
159
+ tmp_index_job += i_bunch
160
+ i_bunch = 0
161
+ # increment
162
+ i_bunch += n_jobs_per_dis
163
+ # remaining
164
+ if i_bunch != 0:
165
+ self.setup_destination(start_idx=tmp_index_job, n_jobs_in_loop=i_bunch)
166
+ else:
167
+ # make one sub dataset per job so that each job doesn't have to wait for others to be done
168
+ # Special jobs for SW installation and HC seem to be above 6000
169
+ if self.jobs != [] and self.jobs[0].prodSourceLabel in ["user", "panda"] and self.jobs[-1].currentPriority > 6000:
170
+ for i_bunch in range(len(self.jobs)):
171
+ self.setup_destination(start_idx=i_bunch, n_jobs_in_loop=1)
172
+ else:
173
+ # at a burst
174
+ self.setup_destination()
175
+ # make dis datasets for existing files
176
+ self.memory_check()
177
+ self.make_dis_datasets_for_existing_files()
178
+ self.memory_check()
179
+ # setup jumbo jobs
180
+ self.setup_jumbo_jobs()
181
+ self.memory_check()
182
+ reg_time = naive_utcnow() - time_start
183
+ tmp_logger.debug(f"{bunch_tag} took {reg_time.seconds}sec")
184
+ tmp_logger.debug("end")
185
+ except Exception as e:
186
+ err_str = f"setupper.run failed with {str(e)}"
187
+ err_str.strip()
188
+ for job in self.jobs:
189
+ if job.jobStatus not in ["failed", "cancelled"]:
190
+ job.jobStatus = "failed"
191
+ job.ddmErrorCode = ErrorCode.EC_Setupper
192
+ job.ddmErrorDiag = err_str
193
+ err_str += traceback.format_exc()
194
+ tmp_logger.error(err_str)
195
+
196
+ # post run
197
+ def post_run(self) -> None:
198
+ """
199
+ Post run method for running the setup process.
200
+ """
201
+ tmp_logger = LogWrapper(self.logger, "<post_run>")
202
+ try:
203
+ tmp_logger.debug("start")
204
+ self.memory_check()
205
+ # subscribe sites dispatchDBlocks. this must be the last method
206
+ tmp_logger.debug("running subscribe_dispatch_data_block")
207
+ self.subscribe_dispatch_data_block()
208
+
209
+ # dynamic data placement for analysis jobs
210
+ self.memory_check()
211
+ tmp_logger.debug("running dynamic_data_placement")
212
+ self.dynamic_data_placement()
213
+ self.memory_check()
214
+ tmp_logger.debug("end")
215
+ except Exception:
216
+ error_type, error_value = sys.exc_info()[:2]
217
+ tmp_logger.error(f"{error_type} {error_value}")
218
+
219
+ # make dispatchDBlocks, insert prod/dispatchDBlock to database
220
+ def setup_source(self) -> None:
221
+ """
222
+ Make dispatchDBlocks, insert prod/dispatchDBlock to database
223
+ Transfer input to satellite
224
+ """
225
+
226
+ tmp_logger = LogWrapper(self.logger, "<setup_source>")
227
+
228
+ file_list = {}
229
+ prod_list = []
230
+ prod_error = {}
231
+ disp_error = {}
232
+ back_end_map = {}
233
+ ds_task_map = dict()
234
+ jedi_task_id = None
235
+ # special datasets in rucio where files are zipped into a file
236
+ use_zip_to_pin_map = dict()
237
+ # extract prodDBlock
238
+ for job in self.jobs:
239
+ # ignore failed jobs
240
+ if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
241
+ continue
242
+ if jedi_task_id is None and job.jediTaskID not in ["NULL", None, 0]:
243
+ jedi_task_id = job.jediTaskID
244
+ # production datablock
245
+ if job.prodDBlock != "NULL" and job.prodDBlock and (job.prodSourceLabel not in ["user", "panda"]):
246
+ # get VUID and record prodDBlock into DB
247
+ if job.prodDBlock not in prod_error:
248
+ tmp_logger.debug(f"list_datasets {job.prodDBlock}")
249
+ prod_error[job.prodDBlock] = ""
250
+ new_out = None
251
+ for _ in range(3):
252
+ new_out, err_msg = rucioAPI.list_datasets(job.prodDBlock)
253
+ if new_out is None:
254
+ time.sleep(10)
255
+ else:
256
+ break
257
+ if new_out is None:
258
+ prod_error[job.prodDBlock] = f"setupper.setup_source() could not get VUID of prodDBlock with {err_msg}"
259
+ tmp_logger.error(prod_error[job.prodDBlock])
260
+ else:
261
+ tmp_logger.debug(new_out)
262
+ try:
263
+ vuids = new_out[job.prodDBlock]["vuids"]
264
+ n_files = 0
265
+ # dataset spec
266
+ dataset = DatasetSpec()
267
+ dataset.vuid = vuids[0]
268
+ dataset.name = job.prodDBlock
269
+ dataset.type = "input"
270
+ dataset.status = "completed"
271
+ dataset.numberfiles = n_files
272
+ dataset.currentfiles = n_files
273
+ prod_list.append(dataset)
274
+ except Exception:
275
+ error_type, error_value = sys.exc_info()[:2]
276
+ tmp_logger.error(f"{error_type} {error_value}")
277
+ prod_error[job.prodDBlock] = "setupper.setup_source() could not decode VUID of prodDBlock"
278
+ # error
279
+ if prod_error[job.prodDBlock] != "":
280
+ if job.jobStatus != "failed":
281
+ job.jobStatus = "failed"
282
+ job.ddmErrorCode = ErrorCode.EC_Setupper
283
+ job.ddmErrorDiag = prod_error[job.prodDBlock]
284
+ tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
285
+ continue
286
+
287
+ # dispatch datablock
288
+ if job.dispatchDBlock != "NULL":
289
+ # useZipToPin mapping
290
+ use_zip_to_pin_map[job.dispatchDBlock] = job.useZipToPin()
291
+
292
+ # filelist
293
+ if job.dispatchDBlock not in file_list:
294
+ file_list[job.dispatchDBlock] = {
295
+ "lfns": [],
296
+ "guids": [],
297
+ "fsizes": [],
298
+ "md5sums": [],
299
+ "chksums": [],
300
+ }
301
+ ds_task_map[job.dispatchDBlock] = job.jediTaskID
302
+ # DDM backend
303
+ if job.dispatchDBlock not in back_end_map:
304
+ back_end_map[job.dispatchDBlock] = "rucio"
305
+ # collect LFN and GUID
306
+ for file in job.Files:
307
+ if file.type == "input" and file.status == "pending":
308
+ if back_end_map[job.dispatchDBlock] != "rucio":
309
+ tmp_lfn = file.lfn
310
+ else:
311
+ tmp_lfn = f"{file.scope}:{file.lfn}"
312
+ if tmp_lfn not in file_list[job.dispatchDBlock]["lfns"]:
313
+ file_list[job.dispatchDBlock]["lfns"].append(tmp_lfn)
314
+ file_list[job.dispatchDBlock]["guids"].append(file.GUID)
315
+ file_list[job.dispatchDBlock]["fsizes"].append(None if file.fsize in ["NULL", 0] else int(file.fsize))
316
+ file_list[job.dispatchDBlock]["chksums"].append(None if file.checksum in ["NULL", ""] else file.checksum)
317
+
318
+ if file.md5sum in ["NULL", ""]:
319
+ file_list[job.dispatchDBlock]["md5sums"].append(None)
320
+ elif file.md5sum.startswith("md5:"):
321
+ file_list[job.dispatchDBlock]["md5sums"].append(file.md5sum)
322
+ else:
323
+ file_list[job.dispatchDBlock]["md5sums"].append(f"md5:{file.md5sum}")
324
+
325
+ # register dispatch dataset
326
+ disp_list = self.register_dispatch_datasets(file_list, use_zip_to_pin_map, ds_task_map, tmp_logger, disp_error, jedi_task_id)
327
+ # insert datasets to DB
328
+ self.task_buffer.insertDatasets(prod_list + disp_list)
329
+ # job status
330
+ for job in self.jobs:
331
+ if job.dispatchDBlock in disp_error and disp_error[job.dispatchDBlock] != "":
332
+ if job.jobStatus != "failed":
333
+ job.jobStatus = "failed"
334
+ job.ddmErrorCode = ErrorCode.EC_Setupper
335
+ job.ddmErrorDiag = disp_error[job.dispatchDBlock]
336
+ tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
337
+ # delete explicitly some huge variables
338
+ del file_list
339
+ del prod_list
340
+ del prod_error
341
+
342
+ def register_dispatch_datasets(
343
+ self,
344
+ file_list: Dict[str, Dict[str, List[str]]],
345
+ use_zip_to_pin_map: Dict[str, bool],
346
+ ds_task_map: Dict[str, int],
347
+ tmp_logger: LogWrapper,
348
+ disp_error: Dict[str, str],
349
+ jedi_task_id: Optional[int],
350
+ ) -> List[DatasetSpec]:
351
+ """
352
+ Register dispatch datasets in Rucio.
353
+
354
+ This method registers dispatch datasets in Rucio, handles the creation of zip files if necessary,
355
+ and updates the dataset metadata. It also handles errors and retries the registration process if needed.
356
+
357
+ :param file_list: Dictionary containing file information for each dispatch dataset block.
358
+ :param use_zip_to_pin_map: Dictionary mapping dispatch dataset blocks to a boolean indicating if zip files should be used.
359
+ :param ds_task_map: Dictionary mapping dispatch dataset blocks to their corresponding JEDI task IDs.
360
+ :param tmp_logger: Logger instance for logging messages.
361
+ :param disp_error: Dictionary to store errors encountered during the registration process.
362
+ :param jedi_task_id: JEDI task ID associated with the jobs.
363
+ :return: List of DatasetSpec objects representing the registered dispatch datasets.
364
+ """
365
+ disp_list = []
366
+ for dispatch_data_block, block_data in file_list.items():
367
+ # ignore empty dataset
368
+ if len(block_data["lfns"]) == 0:
369
+ continue
370
+
371
+ # register dispatch dataset
372
+ self.disp_file_list[dispatch_data_block] = file_list[dispatch_data_block]
373
+ if not use_zip_to_pin_map[dispatch_data_block]:
374
+ dis_files = file_list[dispatch_data_block]
375
+ else:
376
+ dids = file_list[dispatch_data_block]["lfns"]
377
+ tmp_zip_stat, tmp_zip_out = rucioAPI.get_zip_files(dids, None)
378
+ if not tmp_zip_stat:
379
+ tmp_logger.debug(f"failed to get zip files : {tmp_zip_out}")
380
+ tmp_zip_out = {}
381
+ dis_files = {"lfns": [], "guids": [], "fsizes": [], "chksums": []}
382
+ for tmp_lfn, tmp_guid, tmp_file_size, tmp_checksum in zip(
383
+ file_list[dispatch_data_block]["lfns"],
384
+ file_list[dispatch_data_block]["guids"],
385
+ file_list[dispatch_data_block]["fsizes"],
386
+ file_list[dispatch_data_block]["chksums"],
387
+ ):
388
+ if tmp_lfn in tmp_zip_out:
389
+ tmp_zip_file_name = f"{tmp_zip_out[tmp_lfn]['scope']}:{tmp_zip_out[tmp_lfn]['name']}"
390
+ if tmp_zip_file_name not in dis_files["lfns"]:
391
+ dis_files["lfns"].append(tmp_zip_file_name)
392
+ dis_files["guids"].append(tmp_zip_out[tmp_lfn]["guid"])
393
+ dis_files["fsizes"].append(tmp_zip_out[tmp_lfn]["bytes"])
394
+ dis_files["chksums"].append(tmp_zip_out[tmp_lfn]["adler32"])
395
+ else:
396
+ dis_files["lfns"].append(tmp_lfn)
397
+ dis_files["guids"].append(tmp_guid)
398
+ dis_files["fsizes"].append(tmp_file_size)
399
+ dis_files["chksums"].append(tmp_checksum)
400
+
401
+ metadata = {"hidden": True, "purge_replicas": 0}
402
+ if dispatch_data_block in ds_task_map and ds_task_map[dispatch_data_block] not in ["NULL", 0]:
403
+ metadata["task_id"] = str(ds_task_map[dispatch_data_block])
404
+ tmp_logger.debug(f"register_dataset {dispatch_data_block} {str(metadata)}")
405
+ max_attempt = 3
406
+ is_ok = False
407
+ err_str = ""
408
+ for attempt in range(max_attempt):
409
+ try:
410
+ out = rucioAPI.register_dataset(
411
+ dispatch_data_block,
412
+ dis_files["lfns"],
413
+ dis_files["guids"],
414
+ dis_files["fsizes"],
415
+ dis_files["chksums"],
416
+ lifetime=7,
417
+ scope="panda",
418
+ metadata=metadata,
419
+ )
420
+ is_ok = True
421
+ break
422
+ except Exception:
423
+ error_type, error_value = sys.exc_info()[:2]
424
+ err_str = f"{error_type}:{error_value}"
425
+ tmp_logger.error(f"register_dataset : failed with {err_str}")
426
+ if attempt + 1 == max_attempt:
427
+ break
428
+ self.logger.debug(f"sleep {attempt}/{max_attempt}")
429
+ time.sleep(10)
430
+ if not is_ok:
431
+ tmp_str = err_str.split("\n")[-1]
432
+ disp_error[dispatch_data_block] = f"setupper.setup_source() could not register dispatch_data_block with {tmp_str}"
433
+ continue
434
+ tmp_logger.debug(out)
435
+ new_out = out
436
+ # freezeDataset dispatch dataset
437
+ tmp_logger.debug(f"closeDataset {dispatch_data_block}")
438
+ for attempt in range(max_attempt):
439
+ status = False
440
+ try:
441
+ rucioAPI.close_dataset(dispatch_data_block)
442
+ status = True
443
+ break
444
+ except Exception:
445
+ error_type, error_value = sys.exc_info()[:2]
446
+ out = f"failed to close : {error_type} {error_value}"
447
+ time.sleep(10)
448
+ if not status:
449
+ tmp_logger.error(out)
450
+ disp_error[dispatch_data_block] = f"setupper.setup_source() could not freeze dispatch_data_block with {out}"
451
+ continue
452
+
453
+ # get VUID
454
+ try:
455
+ vuid = new_out["vuid"]
456
+ # dataset spec. currentfiles is used to count the number of failed jobs
457
+ dataset = DatasetSpec()
458
+ dataset.vuid = vuid
459
+ dataset.name = dispatch_data_block
460
+ dataset.type = "dispatch"
461
+ dataset.status = "defined"
462
+ dataset.numberfiles = len(file_list[dispatch_data_block]["lfns"])
463
+ try:
464
+ dataset.currentfiles = int(sum(filter(None, file_list[dispatch_data_block]["fsizes"])) / 1024 / 1024)
465
+ except Exception:
466
+ dataset.currentfiles = 0
467
+ if jedi_task_id is not None:
468
+ dataset.MoverID = jedi_task_id
469
+ disp_list.append(dataset)
470
+ self.vuid_map[dataset.name] = dataset.vuid
471
+ except Exception:
472
+ error_type, error_value = sys.exc_info()[:2]
473
+ dispatch_data_block.error(f"{error_type} {error_value}")
474
+ disp_error[dispatch_data_block] = "setupper.setup_source() could not decode VUID dispatch_data_block"
475
+ return disp_list
476
+
477
+ # create dataset for outputs in the repository and assign destination
478
+ def setup_destination(self, start_idx: int = -1, n_jobs_in_loop: int = 50) -> None:
479
+ """
480
+ Create dataset for outputs in the repository and assign destination
481
+
482
+ :param start_idx: The starting index for the jobs to be processed. Defaults to -1.
483
+ :param n_jobs_in_loop: The number of jobs to be processed in a loop. Defaults to 50.
484
+ """
485
+ tmp_logger = LogWrapper(self.logger, "<setup_destination>")
486
+ tmp_logger.debug(f"idx:{start_idx} n:{n_jobs_in_loop}")
487
+
488
+ dest_error = {}
489
+ dataset_list = {}
490
+ newname_list = {}
491
+ sn_gotten_ds = []
492
+ if start_idx == -1:
493
+ jobs_list = self.jobs
494
+ else:
495
+ jobs_list = self.jobs[start_idx : start_idx + n_jobs_in_loop]
496
+ for job in jobs_list:
497
+ # ignore failed jobs
498
+ if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
499
+ continue
500
+ for file in job.Files:
501
+ # ignore input files
502
+ if file.type in ["input", "pseudo_input"]:
503
+ continue
504
+ # don't touch with outDS for unmerge jobs
505
+ if job.prodSourceLabel == "panda" and job.processingType == "unmerge" and file.type != "log":
506
+ continue
507
+ # extract destinationDBlock, destinationSE and computing_site
508
+ dest = (
509
+ file.destinationDBlock,
510
+ file.destinationSE,
511
+ job.computingSite,
512
+ file.destinationDBlockToken,
513
+ )
514
+ if dest not in dest_error:
515
+ dest_error[dest] = ""
516
+ original_name = ""
517
+ if (
518
+ (job.prodSourceLabel == "panda")
519
+ or panda_config.disable_file_aggregation
520
+ or (job.prodSourceLabel in JobUtils.list_ptest_prod_sources and job.processingType in ["pathena", "prun"])
521
+ or job.processingType.startswith("gangarobot")
522
+ ):
523
+ # keep original name
524
+ name_list = [file.destinationDBlock]
525
+ else:
526
+ # set freshness to avoid redundant DB lookup
527
+ defined_fresh_flag = None
528
+ if file.destinationDBlock in sn_gotten_ds:
529
+ # already checked
530
+ defined_fresh_flag = False
531
+ elif job.prodSourceLabel in ["user", "test", "prod_test"]:
532
+ # user or test datasets are always fresh in DB
533
+ defined_fresh_flag = True
534
+ # get serial number
535
+ serial_number, fresh_flag = self.task_buffer.getSerialNumber(file.destinationDBlock, defined_fresh_flag)
536
+ if serial_number == -1:
537
+ dest_error[dest] = f"setupper.setup_destination() could not get serial num for {file.destinationDBlock}"
538
+ break
539
+ if file.destinationDBlock not in sn_gotten_ds:
540
+ sn_gotten_ds.append(file.destinationDBlock)
541
+ # new dataset name
542
+ newname_list[dest] = self.make_sub_dataset_name(file.destinationDBlock, serial_number, job.jediTaskID)
543
+ if fresh_flag:
544
+ # register original dataset and new dataset
545
+ name_list = [file.destinationDBlock, newname_list[dest]]
546
+ original_name = file.destinationDBlock
547
+ else:
548
+ # register new dataset only
549
+ name_list = [newname_list[dest]]
550
+ # create dataset
551
+ for name in name_list:
552
+ computing_site = job.computingSite
553
+ tmp_site = self.site_mapper.getSite(computing_site)
554
+ _, scope_output = select_scope(tmp_site, job.prodSourceLabel, job.job_label)
555
+ if name == original_name and not name.startswith("panda.um."):
556
+ # for original dataset
557
+ computing_site = file.destinationSE
558
+ new_vuid = None
559
+ if job.destinationSE != "local":
560
+ # get src and dest DDM conversion is needed for unknown sites
561
+ if job.prodSourceLabel == "user" and computing_site not in self.site_mapper.siteSpecList:
562
+ # DDM ID was set by using --destSE for analysis job to transfer output
563
+ tmp_src_ddm = tmp_site.ddm_output[scope_output]
564
+ else:
565
+ tmp_src_ddm = tmp_site.ddm_output[scope_output]
566
+ if job.prodSourceLabel == "user" and file.destinationSE not in self.site_mapper.siteSpecList:
567
+ # DDM ID was set by using --destSE for analysis job to transfer output
568
+ tmp_dst_ddm = tmp_src_ddm
569
+ elif DataServiceUtils.getDestinationSE(file.destinationDBlockToken) is not None:
570
+ # destination is specified
571
+ tmp_dst_ddm = DataServiceUtils.getDestinationSE(file.destinationDBlockToken)
572
+ else:
573
+ tmp_dst_site = self.site_mapper.getSite(file.destinationSE)
574
+ _, scope_dst_site_output = select_scope(tmp_dst_site, job.prodSourceLabel, job.job_label)
575
+ tmp_dst_ddm = tmp_dst_site.ddm_output[scope_dst_site_output]
576
+ # skip registration for _sub when src=dest
577
+ if (
578
+ (
579
+ (tmp_src_ddm == tmp_dst_ddm and not EventServiceUtils.isMergeAtOS(job.specialHandling))
580
+ or DataServiceUtils.getDistributedDestination(file.destinationDBlockToken) is not None
581
+ )
582
+ and name != original_name
583
+ and DataServiceUtils.is_sub_dataset(name)
584
+ ):
585
+ # create a fake vuid
586
+ new_vuid = str(uuid.uuid4())
587
+ else:
588
+ # get list of tokens
589
+ tmp_token_list = file.destinationDBlockToken.split(",")
590
+
591
+ # get locations
592
+ using_nucleus_as_satellite = False
593
+ if job.prodSourceLabel == "user" and computing_site not in self.site_mapper.siteSpecList:
594
+ ddm_id_list = [tmp_site.ddm_output[scope_output]]
595
+ else:
596
+ if (
597
+ tmp_site.cloud != job.getCloud()
598
+ and DataServiceUtils.is_sub_dataset(name)
599
+ and (job.prodSourceLabel not in ["user", "panda"])
600
+ ):
601
+ # Nucleus used as Satellite. Use DATADISK as location
602
+ ddm_id_list = [tmp_site.ddm_output[scope_output]]
603
+ using_nucleus_as_satellite = True
604
+ else:
605
+ ddm_id_list = [tmp_site.ddm_output[scope_output]]
606
+ # use another location when token is set
607
+ if not DataServiceUtils.is_sub_dataset(name) and DataServiceUtils.getDestinationSE(file.destinationDBlockToken) is not None:
608
+ # destination is specified
609
+ ddm_id_list = [DataServiceUtils.getDestinationSE(file.destinationDBlockToken)]
610
+ elif (not using_nucleus_as_satellite) and (file.destinationDBlockToken not in ["NULL", ""]):
611
+ ddm_id_list = []
612
+ for tmp_token in tmp_token_list:
613
+ # set default
614
+ ddm_id = tmp_site.ddm_output[scope_output]
615
+ # convert token to DDM ID
616
+ if tmp_token in tmp_site.setokens_output[scope_output]:
617
+ ddm_id = tmp_site.setokens_output[scope_output][tmp_token]
618
+ # replace or append
619
+ if len(tmp_token_list) <= 1 or name != original_name:
620
+ # use location consistent with token
621
+ ddm_id_list = [ddm_id]
622
+ break
623
+ else:
624
+ # use multiple locations for _tid
625
+ if ddm_id not in ddm_id_list:
626
+ ddm_id_list.append(ddm_id)
627
+ # set hidden flag for _sub
628
+ tmp_life_time = None
629
+ tmp_metadata = None
630
+ if name != original_name and DataServiceUtils.is_sub_dataset(name):
631
+ tmp_life_time = 14
632
+ tmp_metadata = {"hidden": True, "purge_replicas": 0}
633
+
634
+ # register dataset
635
+ tmp_logger.debug(f"register_dataset {name} metadata={tmp_metadata}")
636
+ is_ok = False
637
+ for _ in range(3):
638
+ try:
639
+ out = rucioAPI.register_dataset(
640
+ name,
641
+ metadata=tmp_metadata,
642
+ lifetime=tmp_life_time,
643
+ )
644
+ tmp_logger.debug(out)
645
+ new_vuid = out["vuid"]
646
+ is_ok = True
647
+ break
648
+ except Exception:
649
+ error_type, error_value = sys.exc_info()[:2]
650
+ tmp_logger.error(f"register_dataset : failed with {error_type}:{error_value}")
651
+ time.sleep(10)
652
+ if not is_ok:
653
+ tmp_msg = f"setupper.setup_destination() could not register : {name}"
654
+ dest_error[dest] = tmp_msg
655
+ tmp_logger.error(tmp_msg)
656
+ break
657
+ # register dataset locations
658
+ if (
659
+ job.lockedby == "jedi" and job.getDdmBackEnd() == "rucio" and job.prodSourceLabel in ["panda", "user"]
660
+ ) or DataServiceUtils.getDistributedDestination(file.destinationDBlockToken, ignore_empty=False) is not None:
661
+ # skip registerDatasetLocations
662
+ status, out = True, ""
663
+ elif (
664
+ name == original_name
665
+ or tmp_src_ddm != tmp_dst_ddm
666
+ or job.prodSourceLabel == "panda"
667
+ or (
668
+ job.prodSourceLabel in JobUtils.list_ptest_prod_sources
669
+ and job.processingType in ["pathena", "prun", "gangarobot-rctest"]
670
+ )
671
+ or len(tmp_token_list) > 1
672
+ or EventServiceUtils.isMergeAtOS(job.specialHandling)
673
+ ):
674
+ # set replica lifetime to _sub
675
+ rep_life_time = None
676
+ if (name != original_name and DataServiceUtils.is_sub_dataset(name)) or (
677
+ name == original_name and name.startswith("panda.")
678
+ ):
679
+ rep_life_time = 14
680
+ elif name.startswith("hc_test") or name.startswith("panda.install.") or name.startswith("user.gangarbt."):
681
+ rep_life_time = 7
682
+ # distributed datasets for Event Service output
683
+ grouping = None
684
+ if name != original_name and DataServiceUtils.is_sub_dataset(name) and EventServiceUtils.isEventServiceJob(job):
685
+ ddm_id_list = ["type=DATADISK"]
686
+ grouping = "NONE"
687
+ # register location
688
+ is_ok = True
689
+ for ddm_id in ddm_id_list:
690
+ activity = DataServiceUtils.getActivityForOut(job.prodSourceLabel)
691
+ tmp_logger.debug(
692
+ f"register_dataset_location {name} {ddm_id} lifetime={rep_life_time} activity={activity} grouping={grouping}"
693
+ )
694
+ status = False
695
+ # invalid location
696
+ if ddm_id is None:
697
+ tmp_logger.error(f"wrong location : {ddm_id}")
698
+ break
699
+ for _ in range(3):
700
+ try:
701
+ out = rucioAPI.register_dataset_location(
702
+ name,
703
+ [ddm_id],
704
+ lifetime=rep_life_time,
705
+ activity=activity,
706
+ grouping=grouping,
707
+ )
708
+ tmp_logger.debug(out)
709
+ status = True
710
+ break
711
+ except Exception:
712
+ error_type, error_value = sys.exc_info()[:2]
713
+ out = f"{error_type}:{error_value}"
714
+ tmp_logger.error(f"register_dataset_location : failed with {out}")
715
+ time.sleep(10)
716
+ # failed
717
+ if not status:
718
+ break
719
+ else:
720
+ # skip registerDatasetLocations
721
+ status, out = True, ""
722
+ if not status:
723
+ dest_error[dest] = f"Could not register location : {name} {out.splitlines()[-1]}"
724
+ break
725
+ # already failed
726
+ if dest_error[dest] != "" and name == original_name:
727
+ break
728
+ # get vuid
729
+ if new_vuid is None:
730
+ tmp_logger.debug("listDatasets " + name)
731
+ for _ in range(3):
732
+ new_out, err_msg = rucioAPI.list_datasets(name)
733
+ if new_out is None:
734
+ time.sleep(10)
735
+ else:
736
+ break
737
+ if new_out is None:
738
+ tmp_logger.error(f"failed to get VUID for {name} with {err_msg}")
739
+ else:
740
+ tmp_logger.debug(new_out)
741
+ new_vuid = new_out[name]["vuids"][0]
742
+ try:
743
+ # dataset spec
744
+ dataset = DatasetSpec()
745
+ dataset.vuid = new_vuid
746
+ dataset.name = name
747
+ dataset.type = "output"
748
+ dataset.numberfiles = 0
749
+ dataset.currentfiles = 0
750
+ dataset.status = "defined"
751
+ # append
752
+ dataset_list[(name, file.destinationSE, computing_site)] = dataset
753
+ except Exception:
754
+ # set status
755
+ error_type, error_value = sys.exc_info()[:2]
756
+ tmp_logger.error(f"{error_type} {error_value}")
757
+ dest_error[dest] = f"setupper.setup_destination() could not get VUID : {name}"
758
+ # update job status if failed. set new destDBlock and increment number of files, otherwise
759
+ for job in jobs_list:
760
+ # ignore failed jobs
761
+ if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
762
+ continue
763
+ for file in job.Files:
764
+ dest = (
765
+ file.destinationDBlock,
766
+ file.destinationSE,
767
+ job.computingSite,
768
+ file.destinationDBlockToken,
769
+ )
770
+ # update job status if failed
771
+ if dest in dest_error and dest_error[dest] != "":
772
+ if job.jobStatus != "failed":
773
+ job.jobStatus = "failed"
774
+ job.ddmErrorCode = ErrorCode.EC_Setupper
775
+ job.ddmErrorDiag = dest_error[dest]
776
+ tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
777
+ break
778
+ else:
779
+ # set new destDBlock
780
+ if dest in newname_list:
781
+ file.destinationDBlock = newname_list[dest]
782
+ new_dest = (
783
+ file.destinationDBlock,
784
+ file.destinationSE,
785
+ job.computingSite,
786
+ )
787
+ # increment number of files
788
+ if new_dest in dataset_list:
789
+ dataset_list[new_dest].numberfiles = dataset_list[new_dest].numberfiles + 1
790
+ # dump
791
+ for dataset_name, dataset in dataset_list.items():
792
+ # Ensure dataset_name is a string
793
+ if isinstance(dataset_name, tuple):
794
+ dataset_name = dataset_name[0]
795
+ if DataServiceUtils.is_sub_dataset(dataset_name):
796
+ tmp_logger.debug(f"made sub:{dataset_name} for nFiles={dataset.numberfiles}")
797
+ # insert datasets to DB
798
+ return self.task_buffer.insertDatasets(dataset_list.values())
799
+
800
+ # subscribe sites to dispatchDBlocks
801
+ def subscribe_dispatch_data_block(self) -> None:
802
+ """
803
+ Subscribe dispatch db method for running the setup process.
804
+ """
805
+ tmp_logger = LogWrapper(self.logger, "<subscribe_dispatch_data_block>")
806
+
807
+ disp_error = {}
808
+ failed_jobs = []
809
+
810
+ for job in self.jobs:
811
+ # ignore failed jobs
812
+ if job.jobStatus in ["failed", "cancelled"] or job.isCancelled():
813
+ continue
814
+ # ignore no dispatch jobs
815
+ if job.dispatchDBlock == "NULL" or job.computingSite == "NULL":
816
+ continue
817
+
818
+ # extract dispatchDBlock and computingSite
819
+ disp = (job.dispatchDBlock, job.computingSite)
820
+ if disp not in disp_error:
821
+ disp_error[disp] = ""
822
+
823
+ site_spec = self.site_mapper.getSite(job.computingSite)
824
+
825
+ # use input RSE with read_lan/0 as destination
826
+ scope_dst_input, _ = select_scope(site_spec, job.prodSourceLabel, job.job_label)
827
+ ddm_id = site_spec.ddm_input[scope_dst_input]
828
+
829
+ # set share and activity
830
+ option_activity = "Production Input"
831
+ if job.prodSourceLabel in ["user", "panda"]:
832
+ option_activity = "Analysis Input"
833
+ elif job.processingType == "urgent" or job.currentPriority > 1000:
834
+ option_activity = "Express"
835
+
836
+ # taskID
837
+ option_comment = None
838
+ if job.jediTaskID not in ["NULL", 0]:
839
+ option_comment = f"task_id:{job.jediTaskID}"
840
+
841
+ option_owner = None
842
+
843
+ tmp_logger.debug(
844
+ f"register_dataset_subscription {job.dispatchDBlock, ddm_id} "
845
+ f"{{'activity': {option_activity}, 'lifetime': 7, 'dn': {option_owner}, 'comment': {option_comment}}}"
846
+ )
847
+ for _ in range(3):
848
+ try:
849
+ status = rucioAPI.register_dataset_subscription(
850
+ job.dispatchDBlock,
851
+ [ddm_id],
852
+ activity=option_activity,
853
+ lifetime=7,
854
+ distinguished_name=option_owner,
855
+ comment=option_comment,
856
+ )
857
+ out = "register_dataset_subscription finished correctly"
858
+ break
859
+ except Exception as error:
860
+ status = False
861
+ out = f"register_dataset_subscription failed with {str(error)} {traceback.format_exc()}"
862
+ time.sleep(10)
863
+
864
+ if not status:
865
+ tmp_logger.error(out)
866
+ disp_error[disp] = "setupper.subscribe_dispatch_data_block() could not register subscription"
867
+ else:
868
+ tmp_logger.debug(out)
869
+
870
+ # failed jobs
871
+ if disp_error[disp] != "":
872
+ if job.jobStatus != "failed":
873
+ job.jobStatus = "failed"
874
+ job.ddmErrorCode = ErrorCode.EC_Setupper
875
+ job.ddmErrorDiag = disp_error[disp]
876
+ tmp_logger.debug(f"failed PandaID={job.PandaID} with {job.ddmErrorDiag}")
877
+ failed_jobs.append(job)
878
+ # update failed jobs only. succeeded jobs should be activated by DDM callback
879
+ self.update_failed_jobs(failed_jobs)
880
+
881
+ def collect_input_lfns(self):
882
+ # collect input LFNs
883
+ input_lfns = set()
884
+ for tmp_job in self.jobs:
885
+ for tmp_file in tmp_job.Files:
886
+ if tmp_file.type == "input":
887
+ input_lfns.add(tmp_file.lfn)
888
+ # Removes attemptNr from LFN name
889
+ gen_lfn = re.sub("\.\d+$", "", tmp_file.lfn)
890
+ input_lfns.add(gen_lfn)
891
+ if tmp_file.GUID not in ["NULL", "", None]:
892
+ if tmp_file.dataset not in self.lfn_dataset_map:
893
+ self.lfn_dataset_map[tmp_file.dataset] = {}
894
+ self.lfn_dataset_map[tmp_file.dataset][tmp_file.lfn] = {
895
+ "guid": tmp_file.GUID,
896
+ "chksum": tmp_file.checksum,
897
+ "md5sum": tmp_file.md5sum,
898
+ "fsize": tmp_file.fsize,
899
+ "scope": tmp_file.scope,
900
+ }
901
+ return input_lfns
902
+
903
+ # correct LFN for attemptNr
904
+ def correct_lfn(self) -> None:
905
+ """
906
+ Correct lfn method for running the setup process.
907
+ """
908
+
909
+ tmp_logger = LogWrapper(self.logger, "<correct_lfn>")
910
+
911
+ lfn_map = {}
912
+ val_map = {}
913
+ prod_error = {}
914
+ missing_datasets = {}
915
+ jobs_waiting = []
916
+ jobs_failed = []
917
+ jobs_processed = []
918
+ all_lfns = {}
919
+ all_guids = {}
920
+ all_scopes = {}
921
+ lfn_ds_map = {}
922
+ tmp_logger.debug("start")
923
+
924
+ # collect input LFNs
925
+ input_lfns = self.collect_input_lfns()
926
+
927
+ for job in self.jobs:
928
+ # check if sitename is known
929
+ if job.computingSite != "NULL" and job.computingSite not in self.site_mapper.siteSpecList:
930
+ job.jobStatus = "failed"
931
+ job.ddmErrorCode = ErrorCode.EC_Setupper
932
+ job.ddmErrorDiag = f"computingSite:{job.computingSite} is unknown"
933
+ # append job for downstream process
934
+ jobs_processed.append(job)
935
+ continue
936
+
937
+ # ignore no prodDBlock jobs or container dataset
938
+ if job.prodDBlock == "NULL":
939
+ # append job to processed list
940
+ jobs_processed.append(job)
941
+ continue
942
+
943
+ # collect datasets
944
+ datasets = []
945
+ for file in job.Files:
946
+ # make a list of input datasets
947
+ if file.type == "input" and file.dispatchDBlock == "NULL" and (file.GUID == "NULL" or job.prodSourceLabel in ["managed", "test", "ptest"]):
948
+ if file.dataset not in datasets:
949
+ datasets.append(file.dataset)
950
+
951
+ # get file information for the LFNs
952
+ for dataset in datasets:
953
+ # make a dataset to lfn map
954
+ if dataset not in lfn_map:
955
+ prod_error[dataset] = ""
956
+ lfn_map[dataset] = {}
957
+
958
+ # get the file information (size, checksum, ...) for the selected LFNs
959
+ status, out = self.get_list_files_in_dataset(dataset, input_lfns)
960
+ # issue getting the files in dataset
961
+ if status != 0:
962
+ tmp_logger.error(out)
963
+ error_message = f"could not get file list of prodDBlock {dataset}"
964
+ prod_error[dataset] = error_message
965
+ tmp_logger.error(error_message)
966
+ # doesn't exist in DDM, record it as missing
967
+ if status == -1:
968
+ missing_datasets[dataset] = f"DS:{dataset} not found in DDM"
969
+ else:
970
+ missing_datasets[dataset] = out
971
+ # issue getting the files in dataset
972
+ else:
973
+ # make map (key: LFN w/o attemptNr, value: LFN with attemptNr)
974
+ items = out
975
+ try:
976
+ # loop over all files
977
+ for tmp_lfn in items:
978
+ vals = items[tmp_lfn]
979
+ val_map[tmp_lfn] = vals
980
+ # Removes attemptNr from LFN name
981
+ gen_lfn = re.sub("\.\d+$", "", tmp_lfn)
982
+ if gen_lfn in lfn_map[dataset]:
983
+ # get attemptNr
984
+ new_att_nr = 0
985
+ new_mat = re.search("\.(\d+)$", tmp_lfn)
986
+ if new_mat is not None:
987
+ new_att_nr = int(new_mat.group(1))
988
+ old_att_nr = 0
989
+ old_mat = re.search("\.(\d+)$", lfn_map[dataset][gen_lfn])
990
+ if old_mat is not None:
991
+ old_att_nr = int(old_mat.group(1))
992
+ # compare
993
+ if new_att_nr > old_att_nr:
994
+ lfn_map[dataset][gen_lfn] = tmp_lfn
995
+ else:
996
+ lfn_map[dataset][gen_lfn] = tmp_lfn
997
+ # mapping from LFN to DS
998
+ lfn_ds_map[lfn_map[dataset][gen_lfn]] = dataset
999
+ except Exception:
1000
+ prod_error[dataset] = f"could not convert HTTP-res to map for prodDBlock {dataset}"
1001
+ tmp_logger.error(prod_error[dataset])
1002
+ tmp_logger.error(out)
1003
+
1004
+ # mark job with a missing dataset as failed and update files as missing
1005
+ is_failed = False
1006
+ for dataset in datasets:
1007
+ if dataset in missing_datasets:
1008
+ job.jobStatus = "failed"
1009
+ job.ddmErrorCode = ErrorCode.EC_GUID
1010
+ job.ddmErrorDiag = missing_datasets[dataset]
1011
+
1012
+ # set missing
1013
+ for tmp_file in job.Files:
1014
+ if tmp_file.dataset == dataset:
1015
+ tmp_file.status = "missing"
1016
+
1017
+ # append to job_failed
1018
+ jobs_failed.append(job)
1019
+ is_failed = True
1020
+ tmp_logger.debug(f"{job.PandaID} failed with {missing_datasets[dataset]}")
1021
+ break
1022
+ # skip to the next job
1023
+ if is_failed:
1024
+ continue
1025
+
1026
+ # check for waiting jobs
1027
+ is_failed = False
1028
+ for dataset in datasets:
1029
+ if prod_error[dataset] != "":
1030
+ # append job to waiting list
1031
+ jobs_waiting.append(job)
1032
+ is_failed = True
1033
+ break
1034
+ if is_failed:
1035
+ continue
1036
+
1037
+ # replace generic LFN with real LFN
1038
+ replace_list = []
1039
+ is_failed = False
1040
+ for file in job.Files:
1041
+ if file.type == "input" and file.dispatchDBlock == "NULL":
1042
+ add_to_lfn_map = True
1043
+ if file.GUID == "NULL":
1044
+ # get LFN w/o attemptNr
1045
+ # regexp to remove a dot followed by one or more digits at the end of the file name
1046
+ # for example "data_file.123" will become "data_file"
1047
+ basename = re.sub("\.\d+$", "", file.lfn)
1048
+ if basename == file.lfn:
1049
+ # replace
1050
+ if basename in lfn_map[file.dataset]:
1051
+ file.lfn = lfn_map[file.dataset][basename]
1052
+ replace_list.append((basename, file.lfn))
1053
+ # set GUID
1054
+ if file.lfn in val_map:
1055
+ file.GUID = val_map[file.lfn]["guid"]
1056
+ file.fsize = val_map[file.lfn]["fsize"]
1057
+ file.md5sum = val_map[file.lfn]["md5sum"]
1058
+ file.checksum = val_map[file.lfn]["chksum"]
1059
+ file.scope = val_map[file.lfn]["scope"]
1060
+ # remove white space
1061
+ if file.md5sum is not None:
1062
+ file.md5sum = file.md5sum.strip()
1063
+ if file.checksum is not None:
1064
+ file.checksum = file.checksum.strip()
1065
+ else:
1066
+ if job.prodSourceLabel not in ["managed", "test"]:
1067
+ add_to_lfn_map = False
1068
+
1069
+ # check missing file
1070
+ if file.GUID == "NULL" or job.prodSourceLabel in [
1071
+ "managed",
1072
+ "test",
1073
+ ]:
1074
+ if file.lfn not in val_map:
1075
+ # append job to waiting list
1076
+ err_msg = f"GUID for {file.lfn} not found in rucio"
1077
+ tmp_logger.error(err_msg)
1078
+ file.status = "missing"
1079
+ if job not in jobs_failed:
1080
+ job.jobStatus = "failed"
1081
+ job.ddmErrorCode = ErrorCode.EC_GUID
1082
+ job.ddmErrorDiag = err_msg
1083
+ jobs_failed.append(job)
1084
+ is_failed = True
1085
+ continue
1086
+
1087
+ # add to all_lfns/all_guids
1088
+ if add_to_lfn_map:
1089
+ tmp_cloud = job.getCloud()
1090
+ all_lfns.setdefault(tmp_cloud, []).append(file.lfn)
1091
+ all_guids.setdefault(tmp_cloud, []).append(file.GUID)
1092
+ all_scopes.setdefault(tmp_cloud, []).append(file.scope)
1093
+
1094
+ # modify jobParameters
1095
+ if not is_failed:
1096
+ for patt, repl in replace_list:
1097
+ job.jobParameters = re.sub(f"{patt} ", f"{repl} ", job.jobParameters)
1098
+ # append job to processed list
1099
+ jobs_processed.append(job)
1100
+
1101
+ # set data summary fields
1102
+ for tmp_job in self.jobs:
1103
+ try:
1104
+ # set only for production/analysis/test
1105
+ if tmp_job.prodSourceLabel not in ["managed", "test", "user", "prod_test"] + JobUtils.list_ptest_prod_sources:
1106
+ continue
1107
+
1108
+ # loop over all files
1109
+ tmp_job.nInputDataFiles = 0
1110
+ tmp_job.inputFileBytes = 0
1111
+ tmp_input_file_project = None
1112
+ for tmp_file in tmp_job.Files:
1113
+ # use input files and ignore DBR/lib.tgz
1114
+ # lib.tgz is the user sandbox
1115
+ if tmp_file.type == "input" and (not tmp_file.dataset.startswith("ddo")) and not tmp_file.lfn.endswith(".lib.tgz"):
1116
+ tmp_job.nInputDataFiles += 1
1117
+ if tmp_file.fsize not in ["NULL", None, 0, "0"]:
1118
+ tmp_job.inputFileBytes += tmp_file.fsize
1119
+ # get input type and project
1120
+ if tmp_input_file_project is None:
1121
+ tmp_input_items = tmp_file.dataset.split(".")
1122
+ # input project
1123
+ tmp_input_file_project = tmp_input_items[0].split(":")[-1]
1124
+ # set input type and project
1125
+ if tmp_job.prodDBlock not in ["", None, "NULL"]:
1126
+ # input project
1127
+ if tmp_input_file_project is not None:
1128
+ tmp_job.inputFileProject = tmp_input_file_project
1129
+ # protection
1130
+ max_input_file_bytes = 10**16 - 1 # 15 digit precision in database column
1131
+ tmp_job.inputFileBytes = min(tmp_job.inputFileBytes, max_input_file_bytes)
1132
+ # set background-able flag
1133
+ tmp_job.setBackgroundableFlag()
1134
+ # set input and output file types
1135
+ tmp_job.set_input_output_file_types()
1136
+ except Exception:
1137
+ error_type, error_value = sys.exc_info()[:2]
1138
+ tmp_logger.error(f"failed to set data summary fields for PandaID={tmp_job.PandaID}: {error_type} {error_value}")
1139
+
1140
+ # send jobs to jobs_waiting
1141
+ self.task_buffer.keepJobs(jobs_waiting)
1142
+ # update failed job
1143
+ self.update_failed_jobs(jobs_failed)
1144
+ # remove waiting/failed jobs
1145
+ self.jobs = jobs_processed
1146
+
1147
+ tmp_logger.debug("done")
1148
+
1149
+ # delete huge variables
1150
+ del lfn_map
1151
+ del val_map
1152
+ del prod_error
1153
+ del jobs_waiting
1154
+ del jobs_processed
1155
+ del all_lfns
1156
+ del all_guids
1157
+
1158
+ # remove waiting jobs
1159
+ def remove_waiting_jobs(self) -> None:
1160
+ """
1161
+ Remove waiting jobs method for running the setup process.
1162
+ """
1163
+ jobs_waiting = []
1164
+ jobs_processed = []
1165
+ for tmp_job in self.jobs:
1166
+ if tmp_job.jobStatus == "waiting":
1167
+ jobs_waiting.append(tmp_job)
1168
+ else:
1169
+ jobs_processed.append(tmp_job)
1170
+ # send jobs to jobs_waiting
1171
+ self.task_buffer.keepJobs(jobs_waiting)
1172
+ # remove waiting/failed jobs
1173
+ self.jobs = jobs_processed
1174
+
1175
+ # memory checker
1176
+ def memory_check(self) -> None:
1177
+ """
1178
+ Memory check method for running the setup process.
1179
+ """
1180
+ tmp_logger = LogWrapper(self.logger, "<memory_check>")
1181
+
1182
+ try:
1183
+ proc_status = f"/proc/{os.getpid()}/status"
1184
+ proc_file = open(proc_status)
1185
+ name = ""
1186
+ vm_size = ""
1187
+ vm_rss = ""
1188
+ # extract Name,VmSize,VmRSS
1189
+ for line in proc_file:
1190
+ if line.startswith("Name:"):
1191
+ name = line.split()[-1]
1192
+ continue
1193
+ if line.startswith("VmSize:"):
1194
+ vm_size = ""
1195
+ for item in line.split()[1:]:
1196
+ vm_size += item
1197
+ continue
1198
+ if line.startswith("VmRSS:"):
1199
+ vm_rss = ""
1200
+ for item in line.split()[1:]:
1201
+ vm_rss += item
1202
+ continue
1203
+ proc_file.close()
1204
+ tmp_logger.debug(f"PID={os.getpid()} Name={name} VSZ={vm_size} RSS={vm_rss}")
1205
+ except Exception:
1206
+ error_type, error_value = sys.exc_info()[:2]
1207
+ tmp_logger.error(f"{error_type} {error_value}")
1208
+ tmp_logger.debug(f"PID={os.getpid()} unknown")
1209
+ return
1210
+
1211
+ # get list of files in dataset
1212
+ def get_list_files_in_dataset(self, dataset: str, file_list: Optional[List[str]] = None, use_cache: bool = True) -> Tuple[int, List[str]]:
1213
+ """
1214
+ Get list files in dataset method for running the setup process.
1215
+
1216
+ :param dataset: The dataset to get the list of files from.
1217
+ :param file_list: The list of files. Defaults to None.
1218
+ :param use_cache: Whether to use cache. Defaults to True.
1219
+ :return: A tuple containing the status and the list of files.
1220
+ """
1221
+
1222
+ tmp_logger = LogWrapper(self.logger, "<get_list_files_in_dataset>")
1223
+
1224
+ # use cache data
1225
+ if use_cache and dataset in self.lfn_dataset_map:
1226
+ return 0, self.lfn_dataset_map[dataset]
1227
+ status = None
1228
+ items = []
1229
+ for _ in range(3):
1230
+ try:
1231
+ tmp_logger.debug(f"list_files_in_dataset {dataset}")
1232
+ items, _ = rucioAPI.list_files_in_dataset(dataset, file_list=file_list)
1233
+ status = 0
1234
+ break
1235
+ except DataIdentifierNotFound:
1236
+ status = -1
1237
+ break
1238
+ except Exception:
1239
+ status = -2
1240
+
1241
+ if status != 0:
1242
+ error_type, error_value = sys.exc_info()[:2]
1243
+ out = f"{error_type} {error_value}"
1244
+ return status, out
1245
+ # keep to avoid redundant lookup
1246
+ self.lfn_dataset_map[dataset] = items
1247
+ return status, items
1248
+
1249
+ # get list of datasets in container
1250
+ def get_list_dataset_in_container(self, container: str) -> Tuple[bool, List[str]]:
1251
+ """
1252
+ Get list dataset in container method for running the setup process.
1253
+
1254
+ :param container: The container to get the list of datasets from.
1255
+ :return: A tuple containing a boolean indicating the status and the list of datasets.
1256
+ """
1257
+
1258
+ tmp_logger = LogWrapper(self.logger, "<get_list_dataset_in_container>")
1259
+
1260
+ # get datasets in container
1261
+ tmp_logger.debug(container)
1262
+ out = ""
1263
+ for _ in range(3):
1264
+ datasets, out = rucioAPI.list_datasets_in_container(container)
1265
+ if datasets is not None:
1266
+ return True, datasets
1267
+ time.sleep(10)
1268
+ tmp_logger.error(out)
1269
+ return False, out
1270
+
1271
+ # get datasets in container
1272
+ def get_list_dataset_replicas_in_container(self, container: str, get_map: bool = False) -> Tuple[int, str]:
1273
+ """
1274
+ Get list dataset replicas in container method for running the setup process.
1275
+
1276
+ :param container: The container to get the list of dataset replicas from.
1277
+ :param get_map: Whether to get the map. Defaults to False.
1278
+ :return: A tuple containing the status and the map of dataset replicas.
1279
+ """
1280
+ tmp_logger = LogWrapper(self.logger, "<get_list_dataset_replicas_in_container>")
1281
+ tmp_logger.debug(container)
1282
+
1283
+ datasets = None
1284
+ out = ""
1285
+ for _ in range(3):
1286
+ datasets, out = rucioAPI.list_datasets_in_container(container)
1287
+ if datasets is None:
1288
+ time.sleep(10)
1289
+ else:
1290
+ break
1291
+ if datasets is None:
1292
+ tmp_logger.error(out)
1293
+ if get_map:
1294
+ return False, out
1295
+ return 1, out
1296
+
1297
+ # loop over all datasets
1298
+ all_rep_map = {}
1299
+ for dataset in datasets:
1300
+ tmp_logger.debug(f"listDatasetReplicas {dataset}")
1301
+ status, out = self.get_list_dataset_replicas(dataset)
1302
+ tmp_logger.debug(out)
1303
+ if not status:
1304
+ if get_map:
1305
+ return False, out
1306
+ return status, out
1307
+ tmp_rep_sites = out
1308
+ # get map
1309
+ if get_map:
1310
+ all_rep_map[dataset] = tmp_rep_sites
1311
+ continue
1312
+ # otherwise get sum
1313
+ for site_id in tmp_rep_sites:
1314
+ stat_list = tmp_rep_sites[site_id]
1315
+ if site_id not in all_rep_map:
1316
+ # append
1317
+ all_rep_map[site_id] = [
1318
+ stat_list[-1],
1319
+ ]
1320
+ else:
1321
+ # add
1322
+ new_st_map = {}
1323
+ for st_name in all_rep_map[site_id][0]:
1324
+ st_num = all_rep_map[site_id][0][st_name]
1325
+ if st_name in stat_list[-1]:
1326
+ # try mainly for archived=None
1327
+ try:
1328
+ new_st_map[st_name] = st_num + stat_list[-1][st_name]
1329
+ except Exception:
1330
+ new_st_map[st_name] = st_num
1331
+ else:
1332
+ new_st_map[st_name] = st_num
1333
+ all_rep_map[site_id] = [
1334
+ new_st_map,
1335
+ ]
1336
+ # return
1337
+ tmp_logger.debug(str(all_rep_map))
1338
+ if get_map:
1339
+ return True, all_rep_map
1340
+ return 0, str(all_rep_map)
1341
+
1342
+ # get list of replicas for a dataset
1343
+ def get_list_dataset_replicas(self, dataset: str, get_map: bool = True) -> Tuple[bool, str]:
1344
+ """
1345
+ Get list dataset replicas method for running the setup process.
1346
+
1347
+ :param dataset: The dataset to get the list of replicas from.
1348
+ :param get_map: Whether to get the map. Defaults to True.
1349
+ :return: A tuple containing a boolean indicating the status and the map of dataset replicas.
1350
+ """
1351
+
1352
+ tmp_logger = LogWrapper(self.logger, "<get_list_dataset_replicas>")
1353
+
1354
+ out = ""
1355
+ for attempt in range(3):
1356
+ tmp_logger.debug(f"{attempt}/{3} listDatasetReplicas {dataset}")
1357
+ status, out = rucioAPI.list_dataset_replicas(dataset)
1358
+ if status != 0:
1359
+ time.sleep(10)
1360
+ else:
1361
+ break
1362
+ # result
1363
+ if status != 0:
1364
+ tmp_logger.error(out)
1365
+ tmp_logger.error(f"bad response for {dataset}")
1366
+ if get_map:
1367
+ return False, {}
1368
+ else:
1369
+ return 1, str({})
1370
+ try:
1371
+ ret_map = out
1372
+ tmp_logger.debug(f"list_dataset_replicas->{str(ret_map)}")
1373
+ if get_map:
1374
+ return True, ret_map
1375
+ else:
1376
+ return 0, str(ret_map)
1377
+ except Exception:
1378
+ tmp_logger.error(out)
1379
+ tmp_logger.error(f"could not convert HTTP-res to replica map for {dataset}")
1380
+ if get_map:
1381
+ return False, {}
1382
+ else:
1383
+ return 1, str({})
1384
+
1385
+ # dynamic data placement for analysis jobs
1386
+ def dynamic_data_placement(self) -> None:
1387
+ """
1388
+ Dynamic data placement method for running the setup process.
1389
+ """
1390
+ # only first submission
1391
+ if not self.first_submission:
1392
+ return
1393
+ # no jobs
1394
+ if len(self.jobs) == 0:
1395
+ return
1396
+ # only successful analysis
1397
+ if self.jobs[0].jobStatus in ["failed", "cancelled"] or self.jobs[0].isCancelled() or (not self.jobs[0].prodSourceLabel in ["user", "panda"]):
1398
+ return
1399
+ # disable for JEDI
1400
+ if self.jobs[0].lockedby == "jedi":
1401
+ return
1402
+ return
1403
+
1404
+ def collect_existing_files(self):
1405
+ """
1406
+ Collects existing files to avoid deletion when jobs are queued.
1407
+ This method iterates over all jobs and collects files that should not be deleted,
1408
+ organizing them by destination DDM endpoint and log dataset name.
1409
+
1410
+ :return: A dictionary mapping (destination DDM endpoint, log dataset name) to a list of files.
1411
+ """
1412
+ dataset_file_map = {}
1413
+ n_max_jobs = 20
1414
+ n_jobs_map = {}
1415
+ for tmp_job in self.jobs:
1416
+ # use production or test jobs only
1417
+ if tmp_job.prodSourceLabel not in ["managed", "test"]:
1418
+ continue
1419
+ # skip for prefetcher or transferType=direct
1420
+ if tmp_job.usePrefetcher() or tmp_job.transferType == "direct":
1421
+ continue
1422
+ # ignore inappropriate status
1423
+ if tmp_job.jobStatus in ["failed", "cancelled", "waiting"] or tmp_job.isCancelled():
1424
+ continue
1425
+ # check cloud
1426
+ if tmp_job.getCloud() == "ND" and self.site_mapper.getSite(tmp_job.computingSite).cloud == "ND":
1427
+ continue
1428
+ # look for log _sub dataset to be used as a key
1429
+ log_sub_ds_name = ""
1430
+ for tmp_file in tmp_job.Files:
1431
+ if tmp_file.type == "log":
1432
+ log_sub_ds_name = tmp_file.destinationDBlock
1433
+ break
1434
+ # append site
1435
+ dest_site = self.site_mapper.getSite(tmp_job.computingSite)
1436
+ scope_dest_input, _ = select_scope(dest_site, tmp_job.prodSourceLabel, tmp_job.job_label)
1437
+ dest_ddm_id = dest_site.ddm_input[scope_dest_input]
1438
+ map_key_job = (dest_ddm_id, log_sub_ds_name)
1439
+ # increment the number of jobs per key
1440
+ if map_key_job not in n_jobs_map:
1441
+ n_jobs_map[map_key_job] = 0
1442
+ map_key = (
1443
+ dest_ddm_id,
1444
+ log_sub_ds_name,
1445
+ n_jobs_map[map_key_job] // n_max_jobs,
1446
+ )
1447
+ n_jobs_map[map_key_job] += 1
1448
+ if map_key not in dataset_file_map:
1449
+ dataset_file_map[map_key] = {}
1450
+ # add files
1451
+ for tmp_file in tmp_job.Files:
1452
+ if tmp_file.type != "input":
1453
+ continue
1454
+ # if files are unavailable at the dest site normal dis datasets contain them
1455
+ # or files are cached
1456
+ if tmp_file.status not in ["ready"]:
1457
+ continue
1458
+ # if available at Satellite
1459
+ real_dest_ddm_id = (dest_ddm_id,)
1460
+ if (
1461
+ tmp_job.getCloud() in self.available_lfns_in_satellites
1462
+ and tmp_file.dataset in self.available_lfns_in_satellites[tmp_job.getCloud()]
1463
+ and tmp_job.computingSite in self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["sites"]
1464
+ and tmp_file.lfn in self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["sites"][tmp_job.computingSite]
1465
+ ):
1466
+ real_dest_ddm_id = self.available_lfns_in_satellites[tmp_job.getCloud()][tmp_file.dataset]["siteDQ2IDs"][tmp_job.computingSite]
1467
+ real_dest_ddm_id = tuple(real_dest_ddm_id)
1468
+ # append
1469
+ if real_dest_ddm_id not in dataset_file_map[map_key]:
1470
+ dataset_file_map[map_key][real_dest_ddm_id] = {
1471
+ "taskID": tmp_job.taskID,
1472
+ "PandaID": tmp_job.PandaID,
1473
+ "useZipToPin": tmp_job.useZipToPin(),
1474
+ "files": {},
1475
+ }
1476
+ if tmp_file.lfn not in dataset_file_map[map_key][real_dest_ddm_id]["files"]:
1477
+ # add scope
1478
+ tmp_lfn = f"{tmp_file.scope}:{tmp_file.lfn}"
1479
+ dataset_file_map[map_key][real_dest_ddm_id]["files"][tmp_file.lfn] = {
1480
+ "lfn": tmp_lfn,
1481
+ "guid": tmp_file.GUID,
1482
+ "fileSpecs": [],
1483
+ }
1484
+ # add file spec
1485
+ dataset_file_map[map_key][real_dest_ddm_id]["files"][tmp_file.lfn]["fileSpecs"].append(tmp_file)
1486
+ return dataset_file_map
1487
+
1488
+ def create_dispatch_datasets(self, dataset_file_map):
1489
+ """
1490
+ Creates dispatch datasets for the collected files.
1491
+ Returns a list of datasets to be inserted into the database.
1492
+ """
1493
+
1494
+ tmp_logger = LogWrapper(self.logger, "<create_dispatch_datasets>")
1495
+
1496
+ # loop over all locations
1497
+ disp_list = []
1498
+ for _, tmp_dum_val in dataset_file_map.items():
1499
+ for tmp_location_list in tmp_dum_val:
1500
+ tmp_val = tmp_dum_val[tmp_location_list]
1501
+ for tmp_location in tmp_location_list:
1502
+ tmp_file_list = tmp_val["files"]
1503
+ if tmp_file_list == {}:
1504
+ continue
1505
+ n_max_files = 500
1506
+ i_files = 0
1507
+ i_loop = 0
1508
+ while i_files < len(tmp_file_list):
1509
+ sub_file_names = list(tmp_file_list)[i_files : i_files + n_max_files]
1510
+ if len(sub_file_names) == 0:
1511
+ break
1512
+ # dis name
1513
+ dis_dispatch_block = f"panda.{tmp_val['taskID']}.{time.strftime('%m.%d')}.GEN.{str(uuid.uuid4())}_dis0{i_loop}{tmp_val['PandaID']}"
1514
+ i_files += n_max_files
1515
+ lfns = []
1516
+ guids = []
1517
+ fsizes = []
1518
+ chksums = []
1519
+ tmp_zip_out = {}
1520
+ if tmp_val["useZipToPin"]:
1521
+ dids = [tmp_file_list[tmp_sub_file_name]["lfn"] for tmp_sub_file_name in sub_file_names]
1522
+ tmp_zip_stat, tmp_zip_out = rucioAPI.get_zip_files(dids, [tmp_location])
1523
+ if not tmp_zip_stat:
1524
+ tmp_logger.debug(f"failed to get zip files : {tmp_zip_out}")
1525
+ tmp_zip_out = {}
1526
+ for tmp_sub_file_name in sub_file_names:
1527
+ tmp_lfn = tmp_file_list[tmp_sub_file_name]["lfn"]
1528
+ if tmp_lfn in tmp_zip_out:
1529
+ tmp_zip_file_name = f"{tmp_zip_out[tmp_lfn]['scope']}:{tmp_zip_out[tmp_lfn]['name']}"
1530
+ if tmp_zip_file_name not in lfns:
1531
+ lfns.append(tmp_zip_file_name)
1532
+ guids.append(tmp_zip_out[tmp_lfn]["guid"])
1533
+ fsizes.append(tmp_zip_out[tmp_lfn]["bytes"])
1534
+ chksums.append(tmp_zip_out[tmp_lfn]["adler32"])
1535
+ else:
1536
+ lfns.append(tmp_lfn)
1537
+ guids.append(tmp_file_list[tmp_sub_file_name]["guid"])
1538
+ fsizes.append(int(tmp_file_list[tmp_sub_file_name]["fileSpecs"][0].fsize))
1539
+ chksums.append(tmp_file_list[tmp_sub_file_name]["fileSpecs"][0].checksum)
1540
+ # set dis name
1541
+ for tmp_file_spec in tmp_file_list[tmp_sub_file_name]["fileSpecs"]:
1542
+ if tmp_file_spec.status in ["ready"] and tmp_file_spec.dispatchDBlock == "NULL":
1543
+ tmp_file_spec.dispatchDBlock = dis_dispatch_block
1544
+ # register datasets
1545
+ i_loop += 1
1546
+ max_attempt = 3
1547
+ is_ok = False
1548
+ metadata = {"hidden": True, "purge_replicas": 0}
1549
+ if tmp_val["taskID"] not in [None, "NULL"]:
1550
+ metadata["task_id"] = str(tmp_val["taskID"])
1551
+
1552
+ tmp_logger.debug(f"ext registerNewDataset {dis_dispatch_block} {str(lfns)} {str(guids)} {str(fsizes)} {str(chksums)} {str(metadata)}")
1553
+
1554
+ for attempt in range(max_attempt):
1555
+ try:
1556
+ out = rucioAPI.register_dataset(
1557
+ dis_dispatch_block,
1558
+ lfns,
1559
+ guids,
1560
+ fsizes,
1561
+ chksums,
1562
+ lifetime=7,
1563
+ scope="panda",
1564
+ metadata=metadata,
1565
+ )
1566
+ self.logger.debug(out)
1567
+ is_ok = True
1568
+ break
1569
+ except Exception:
1570
+ error_type, error_value = sys.exc_info()[:2]
1571
+ tmp_logger.error(f"ext registerDataset : failed with {error_type}:{error_value}" + traceback.format_exc())
1572
+ if attempt + 1 == max_attempt:
1573
+ break
1574
+ tmp_logger.debug(f"sleep {attempt}/{max_attempt}")
1575
+ time.sleep(10)
1576
+ # failure
1577
+ if not is_ok:
1578
+ continue
1579
+ # get VUID
1580
+ try:
1581
+ vuid = out["vuid"]
1582
+ # dataset spec. currentfiles is used to count the number of failed jobs
1583
+ dataset = DatasetSpec()
1584
+ dataset.vuid = vuid
1585
+ dataset.name = dis_dispatch_block
1586
+ dataset.type = "dispatch"
1587
+ dataset.status = "defined"
1588
+ dataset.numberfiles = len(lfns)
1589
+ dataset.currentfiles = 0
1590
+ if tmp_val["taskID"] not in [None, "NULL"]:
1591
+ dataset.MoverID = tmp_val["taskID"]
1592
+ disp_list.append(dataset)
1593
+ except Exception:
1594
+ error_type, error_value = sys.exc_info()[:2]
1595
+ tmp_logger.error(f"ext registerNewDataset : failed to decode VUID for {dis_dispatch_block} - {error_type} {error_value}")
1596
+ continue
1597
+ # freezeDataset dispatch dataset
1598
+ tmp_logger.debug(f"freezeDataset {dis_dispatch_block}")
1599
+
1600
+ for attempt in range(3):
1601
+ status = False
1602
+ try:
1603
+ rucioAPI.close_dataset(dis_dispatch_block)
1604
+ status = True
1605
+ break
1606
+ except Exception:
1607
+ error_type, error_value = sys.exc_info()[:2]
1608
+ out = f"failed to close : {error_type} {error_value}"
1609
+ time.sleep(10)
1610
+ if not status:
1611
+ tmp_logger.error(out)
1612
+ continue
1613
+ # register location
1614
+ is_ok = False
1615
+ tmp_logger.debug(f"ext registerDatasetLocation {dis_dispatch_block} {tmp_location} {7}days asynchronous=True")
1616
+ max_attempt = 3
1617
+ for attempt in range(max_attempt):
1618
+ try:
1619
+ out = rucioAPI.register_dataset_location(
1620
+ dis_dispatch_block,
1621
+ [tmp_location],
1622
+ 7,
1623
+ activity="Production Input",
1624
+ scope="panda",
1625
+ grouping="NONE",
1626
+ )
1627
+ tmp_logger.debug(out)
1628
+ is_ok = True
1629
+ break
1630
+ except Exception:
1631
+ error_type, error_value = sys.exc_info()[:2]
1632
+ tmp_logger.error(f"ext registerDatasetLocation : failed with {error_type}:{error_value}")
1633
+ if attempt + 1 == max_attempt:
1634
+ break
1635
+ tmp_logger.debug(f"sleep {attempt}/{max_attempt}")
1636
+ time.sleep(10)
1637
+
1638
+ # failure
1639
+ if not is_ok:
1640
+ continue
1641
+ return disp_list
1642
+
1643
+ # make dis datasets for existing files to avoid deletion when jobs are queued
1644
+ def make_dis_datasets_for_existing_files(self) -> None:
1645
+ """
1646
+ Make dis datasets for existing files method for running the setup process.
1647
+ """
1648
+ tmp_logger = LogWrapper(self.logger, "<make_dis_datasets_for_existing_files>")
1649
+ tmp_logger.debug("start")
1650
+
1651
+ # collect existing files
1652
+ dataset_file_map = self.collect_existing_files()
1653
+ # create dispatch datasets for the collected files
1654
+ disp_list = self.create_dispatch_datasets(dataset_file_map)
1655
+
1656
+ # insert datasets to DB
1657
+ self.task_buffer.insertDatasets(disp_list)
1658
+ tmp_logger.debug("finished")
1659
+ return
1660
+
1661
+ # make subscription
1662
+ def make_subscription(self, dataset: str, ddm_id: str) -> bool:
1663
+ """
1664
+ Make subscription method for running the setup process.
1665
+
1666
+ :param dataset: The dataset to make the subscription for.
1667
+ :param ddm_id: The DDM ID.
1668
+ :return: A boolean indicating whether the subscription was made successfully.
1669
+ """
1670
+ ret_failed = False
1671
+ tmp_logger = LogWrapper(self.logger, "<make_subscription>")
1672
+ tmp_logger.debug(f"register_dataset_subscription {dataset} {ddm_id}")
1673
+ for _ in range(3):
1674
+ try:
1675
+ # register subscription
1676
+ status = rucioAPI.register_dataset_subscription(dataset, [ddm_id], activity="Production Input")
1677
+ out = "OK"
1678
+ break
1679
+ except Exception:
1680
+ status = False
1681
+ error_type, error_value = sys.exc_info()[:2]
1682
+ out = f"{error_type} {error_value}"
1683
+ time.sleep(10)
1684
+
1685
+ if not status:
1686
+ tmp_logger.error(out)
1687
+ return ret_failed
1688
+
1689
+ tmp_logger.debug(f"{status} {out}")
1690
+ return True
1691
+
1692
+ # setup jumbo jobs
1693
+ def setup_jumbo_jobs(self) -> None:
1694
+ """
1695
+ Setup jumbo jobs method for running the setup process.
1696
+ """
1697
+
1698
+ tmp_logger = LogWrapper(self.logger, "<setup_jumbo_jobs>")
1699
+
1700
+ if len(self.jumbo_jobs) == 0:
1701
+ return
1702
+ tmp_logger.debug("start")
1703
+ # get files in datasets
1704
+ datasets_lfns_map = {}
1705
+ failed_ds = set()
1706
+ for jumbo_job_spec in self.jumbo_jobs:
1707
+ for tmp_file_spec in jumbo_job_spec.Files:
1708
+ # only input
1709
+ if tmp_file_spec.type not in ["input"]:
1710
+ continue
1711
+ # get files
1712
+ if tmp_file_spec.dataset not in datasets_lfns_map:
1713
+ if tmp_file_spec.dataset not in failed_ds:
1714
+ tmp_stat, tmp_map = self.get_list_files_in_dataset(tmp_file_spec.dataset, use_cache=False)
1715
+ # failed
1716
+ if tmp_stat != 0:
1717
+ failed_ds.add(tmp_file_spec.dataset)
1718
+ tmp_logger.debug(f"failed to get files in {tmp_file_spec.dataset} with {tmp_map}")
1719
+ else:
1720
+ # append
1721
+ datasets_lfns_map[tmp_file_spec.dataset] = tmp_map
1722
+ # set failed if file lookup failed
1723
+ if tmp_file_spec.dataset in failed_ds:
1724
+ jumbo_job_spec.jobStatus = "failed"
1725
+ jumbo_job_spec.ddmErrorCode = ErrorCode.EC_GUID
1726
+ jumbo_job_spec.ddmErrorDiag = f"failed to get files in {tmp_file_spec.dataset}"
1727
+ break
1728
+ # make dis datasets
1729
+ ok_jobs = []
1730
+ ng_jobs = []
1731
+ for jumbo_job_spec in self.jumbo_jobs:
1732
+ # skip failed
1733
+ if jumbo_job_spec.jobStatus == "failed":
1734
+ ng_jobs.append(jumbo_job_spec)
1735
+ continue
1736
+ # get datatype
1737
+ try:
1738
+ tmp_data_type = jumbo_job_spec.prodDBlock.split(".")[-2]
1739
+ if len(tmp_data_type) > 20:
1740
+ raise RuntimeError(f"data type is too log : {len(tmp_data_type)} chars")
1741
+ except Exception:
1742
+ # default
1743
+ tmp_data_type = "GEN"
1744
+ # files for jumbo job
1745
+ lfns_for_jumbo = self.task_buffer.getLFNsForJumbo(jumbo_job_spec.jediTaskID)
1746
+ # make dis dataset name
1747
+ dispatch_data_block = f"panda.{jumbo_job_spec.taskID}.{time.strftime('%m.%d.%H%M')}.{tmp_data_type}.jumbo_dis{jumbo_job_spec.PandaID}"
1748
+ # collect file attributes
1749
+ lfns = []
1750
+ guids = []
1751
+ sizes = []
1752
+ checksums = []
1753
+ for tmp_file_spec in jumbo_job_spec.Files:
1754
+ # only input
1755
+ if tmp_file_spec.type not in ["input"]:
1756
+ continue
1757
+ for tmp_lfn in datasets_lfns_map[tmp_file_spec.dataset]:
1758
+ tmp_var = datasets_lfns_map[tmp_file_spec.dataset][tmp_lfn]
1759
+ tmp_lfn = f"{tmp_var['scope']}:{tmp_lfn}"
1760
+ if tmp_lfn not in lfns_for_jumbo:
1761
+ continue
1762
+ lfns.append(tmp_lfn)
1763
+ guids.append(tmp_var["guid"])
1764
+ sizes.append(tmp_var["fsize"])
1765
+ checksums.append(tmp_var["chksum"])
1766
+ # set dis dataset
1767
+ tmp_file_spec.dispatchDBlock = dispatch_data_block
1768
+ # register and subscribe dis dataset
1769
+ if len(lfns) != 0:
1770
+ # set dis dataset
1771
+ jumbo_job_spec.dispatchDBlock = dispatch_data_block
1772
+ # register dis dataset
1773
+ try:
1774
+ tmp_logger.debug(f"register_dataset {dispatch_data_block} with {len(lfns)} files")
1775
+ out = rucioAPI.register_dataset(dispatch_data_block, lfns, guids, sizes, checksums, lifetime=14)
1776
+ vuid = out["vuid"]
1777
+ rucioAPI.close_dataset(dispatch_data_block)
1778
+ except Exception:
1779
+ error_type, error_value = sys.exc_info()[:2]
1780
+ tmp_logger.debug(f"failed to register jumbo dis dataset {dispatch_data_block} with {error_type}:{error_value}")
1781
+ jumbo_job_spec.jobStatus = "failed"
1782
+ jumbo_job_spec.ddmErrorCode = ErrorCode.EC_Setupper
1783
+ jumbo_job_spec.ddmErrorDiag = f"failed to register jumbo dispatch dataset {dispatch_data_block}"
1784
+ ng_jobs.append(jumbo_job_spec)
1785
+ continue
1786
+ # subscribe dis dataset
1787
+ try:
1788
+ tmp_site_spec = self.site_mapper.getSite(jumbo_job_spec.computingSite)
1789
+ scope_input, _ = select_scope(
1790
+ tmp_site_spec,
1791
+ jumbo_job_spec.prodSourceLabel,
1792
+ jumbo_job_spec.job_label,
1793
+ )
1794
+ end_point = tmp_site_spec.ddm_input[scope_input]
1795
+ tmp_logger.debug(f"register_dataset_subscription {dispatch_data_block} to {end_point}")
1796
+ rucioAPI.register_dataset_subscription(
1797
+ dispatch_data_block,
1798
+ [end_point],
1799
+ lifetime=14,
1800
+ activity="Production Input",
1801
+ )
1802
+ except Exception:
1803
+ error_type, error_value = sys.exc_info()[:2]
1804
+ tmp_logger.debug(f"failed to subscribe jumbo dis dataset {dispatch_data_block} to {end_point} with {error_type}:{error_value}")
1805
+ jumbo_job_spec.jobStatus = "failed"
1806
+ jumbo_job_spec.ddmErrorCode = ErrorCode.EC_Setupper
1807
+ jumbo_job_spec.ddmErrorDiag = f"failed to subscribe jumbo dispatch dataset {dispatch_data_block} to {end_point}"
1808
+ ng_jobs.append(jumbo_job_spec)
1809
+ continue
1810
+
1811
+ # add dataset in DB
1812
+ dataset = DatasetSpec()
1813
+ dataset.vuid = vuid
1814
+ dataset.name = dispatch_data_block
1815
+ dataset.type = "dispatch"
1816
+ dataset.status = "defined"
1817
+ dataset.numberfiles = len(lfns)
1818
+ dataset.currentfiles = 0
1819
+ dataset.MoverID = jumbo_job_spec.jediTaskID
1820
+ self.task_buffer.insertDatasets([dataset])
1821
+ # set destination
1822
+ jumbo_job_spec.destinationSE = jumbo_job_spec.computingSite
1823
+ for tmp_file_spec in jumbo_job_spec.Files:
1824
+ if tmp_file_spec.type in ["output", "log"] and DataServiceUtils.getDistributedDestination(tmp_file_spec.destinationDBlockToken) is None:
1825
+ tmp_file_spec.destinationSE = jumbo_job_spec.computingSite
1826
+ ok_jobs.append(jumbo_job_spec)
1827
+ # update failed jobs
1828
+ self.update_failed_jobs(ng_jobs)
1829
+ self.jumbo_jobs = ok_jobs
1830
+ tmp_logger.debug("done")
1831
+ return
1832
+
1833
+ # make sub dataset name
1834
+ def make_sub_dataset_name(self, original_name: str, serial_number: int, task_id: int) -> str:
1835
+ """
1836
+ Make sub dataset name method for running the setup process.
1837
+
1838
+ :param original_name: The original name of the dataset.
1839
+ :param serial_number: The serial number.
1840
+ :param task_id: The task ID.
1841
+ :return: The sub dataset name.
1842
+ """
1843
+ try:
1844
+ task_id = int(task_id)
1845
+ if original_name.startswith("user") or original_name.startswith("panda"):
1846
+ part_name = ".".join(original_name.split(".")[:3])
1847
+ else:
1848
+ part_name = f"{'.'.join(original_name.split('.')[:2])}.NA.{'.'.join(original_name.split('.')[3:5])}"
1849
+ return f"{part_name}.{task_id}_sub{serial_number}"
1850
+ except Exception:
1851
+ return f"{original_name}_sub{serial_number}"