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,824 @@
1
+ """
2
+ find candidate site to distribute input datasets
3
+
4
+ """
5
+
6
+ import fnmatch
7
+ import re
8
+ import sys
9
+ import time
10
+ import uuid
11
+ from typing import Dict, List, Tuple
12
+
13
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
14
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
15
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
16
+
17
+ from pandaserver.dataservice.DataServiceUtils import select_scope
18
+ from pandaserver.dataservice.ddm import rucioAPI
19
+ from pandaserver.taskbuffer import JobUtils
20
+
21
+ _logger = PandaLogger().getLogger("dyn_data_distributer")
22
+
23
+ # files in datasets
24
+ g_files_in_ds_map = {}
25
+
26
+
27
+ class DynDataDistributer:
28
+ """
29
+ Find candidate site to distribute input datasets.
30
+ """
31
+
32
+ def __init__(self, jobs, siteMapper, simul=False, token=None, dataset_lifetime=14):
33
+ self.jobs = jobs
34
+ self.site_mapper = siteMapper
35
+ if token is None:
36
+ self.token = naive_utcnow().isoformat(" ")
37
+ else:
38
+ self.token = token
39
+ # use a fixed list since some clouds don't have active T2s
40
+ self.simul = simul
41
+ self.last_message = ""
42
+ # lifetime for temporary datasets
43
+ self.dataset_lifetime = dataset_lifetime
44
+
45
+ def get_replica_locations(self, input_ds: str, check_used_file: bool) -> Tuple[bool, Dict]:
46
+ """
47
+ Get replica locations for a given dataset.
48
+
49
+ Args:
50
+ input_ds (str): The name of the input dataset.
51
+ check_used_file (bool): Flag to check used file.
52
+
53
+ Returns:
54
+ tuple: A tuple containing the status (bool) and the result (dict or str).
55
+ """
56
+ tmp_log = LogWrapper(_logger, f"get_replica_locations-{naive_utcnow().isoformat('/')}")
57
+ tmp_log.debug(f"get_replica_locations {input_ds}")
58
+
59
+ # return for failure
60
+ res_for_failure = False, {"": {"": ([], [], [], 0, False, False, 0, 0, [])}}
61
+
62
+ # get replica locations
63
+ if input_ds.endswith("/"):
64
+ # container
65
+ status, tmp_rep_maps = self.get_list_dataset_replicas_in_container(input_ds)
66
+ # get used datasets
67
+ if status and check_used_file:
68
+ status, tmp_used_dataset_list = self.get_used_datasets(tmp_rep_maps)
69
+ # remove unused datasets
70
+ new_rep_maps = {}
71
+ for tmp_key, tmp_val in tmp_rep_maps.items():
72
+ if tmp_key in tmp_used_dataset_list:
73
+ new_rep_maps[tmp_key] = tmp_val
74
+ tmp_rep_maps = new_rep_maps
75
+ else:
76
+ # normal dataset
77
+ status, tmp_rep_map = self.get_list_dataset_replicas(input_ds)
78
+ tmp_rep_maps = {input_ds: tmp_rep_map}
79
+
80
+ if not status:
81
+ # failed
82
+ tmp_log.error("failed to get replica locations for {input_ds}")
83
+ tmp_log.debug("end")
84
+ return res_for_failure
85
+
86
+ tmp_log.debug("end")
87
+ return True, tmp_rep_maps
88
+
89
+ def get_all_sites(self) -> List:
90
+ """
91
+ Retrieves all sites that meet certain conditions.
92
+
93
+ This method filters out sites based on the following conditions:
94
+ - The site should be capable of running analysis.
95
+ - The site should not be a GPU site.
96
+ - The site should not use VP.
97
+ - The status of the site should be "online".
98
+
99
+ Returns:
100
+ list: A list of SiteSpec objects that meet the above conditions.
101
+ """
102
+ all_sites = []
103
+ for site_name in self.site_mapper.siteSpecList:
104
+ site_spec = self.site_mapper.siteSpecList[site_name]
105
+ # analysis only
106
+ if not site_spec.runs_analysis():
107
+ continue
108
+ # skip GPU
109
+ if site_spec.isGPU():
110
+ continue
111
+ # skip VP
112
+ if site_spec.use_vp(JobUtils.ANALY_PS):
113
+ continue
114
+ # online
115
+ if site_spec.status not in ["online"]:
116
+ continue
117
+ all_sites.append(site_spec)
118
+ return all_sites
119
+
120
+ def get_candidate_sites(self, tmp_rep_maps: Dict, prod_source_label: str, job_label: str) -> Tuple[bool, Dict]:
121
+ """
122
+ Retrieves candidate sites for data distribution based on certain conditions.
123
+
124
+ This method filters out candidate sites based on the following condition:
125
+ - The site should have a replica of the dataset.
126
+
127
+ Args:
128
+ tmp_rep_maps (dict): A dictionary containing dataset names as keys and their replica maps as values.
129
+ prod_source_label (str): The label of the production source.
130
+ job_label (str): The label of the job.
131
+
132
+ Returns:
133
+ tuple: A tuple containing a boolean status and a dictionary. The dictionary has dataset names as keys and
134
+ another dictionary as values. The inner dictionary has cloud names as keys and a tuple of various
135
+ site-related lists and values as values.
136
+ """
137
+ all_site_map = self.get_all_sites()
138
+ return_map = {}
139
+ cloud = "WORLD"
140
+ for tmp_ds, tmp_rep_map in tmp_rep_maps.items():
141
+ cand_sites = []
142
+ sites_com_ds = []
143
+ sites_comp_pd2p = []
144
+ t1_has_replica = False
145
+ t1_has_primary = False
146
+ n_sec_replicas = 0
147
+ cand_for_mou = []
148
+ n_user_sub = 0
149
+ for tmp_site_spec in all_site_map:
150
+ tmp_scope_input, tmp_scope_output = select_scope(tmp_site_spec, prod_source_label, job_label)
151
+ if tmp_scope_input not in tmp_site_spec.ddm_endpoints_input:
152
+ continue
153
+ rses = tmp_site_spec.ddm_endpoints_input[tmp_scope_input].getLocalEndPoints()
154
+ for ddm_endpoint in tmp_rep_map:
155
+ tmp_stat_map = tmp_rep_map[ddm_endpoint]
156
+ if ddm_endpoint in rses and tmp_stat_map[0]["total"] == tmp_stat_map[0]["found"] and ddm_endpoint.endswith("DATADISK"):
157
+ sites_com_ds.append(tmp_site_spec.sitename)
158
+ break
159
+ cand_sites.append(tmp_site_spec.sitename)
160
+ return_map.setdefault(tmp_ds, {})
161
+ if sites_com_ds:
162
+ cand_sites = sites_com_ds
163
+ return_map[tmp_ds][cloud] = (
164
+ cand_sites,
165
+ sites_com_ds,
166
+ sites_comp_pd2p,
167
+ n_user_sub,
168
+ t1_has_replica,
169
+ t1_has_primary,
170
+ n_sec_replicas,
171
+ 0,
172
+ cand_for_mou,
173
+ )
174
+ return True, return_map
175
+
176
+ def get_candidates(self, input_ds: str, prod_source_label: str, job_label: str, check_used_file: bool = True) -> Tuple[bool, Dict]:
177
+ """
178
+ Get candidate sites for subscription.
179
+
180
+ Args:
181
+ input_ds (str): The name of the input dataset.
182
+ prod_source_label (str): The label of the production source.
183
+ job_label (str): The label of the job.
184
+ check_used_file (bool, optional): Flag to check used file. Defaults to True.
185
+
186
+ Returns:
187
+ tuple: A tuple containing the status (bool) and the result (dict or str).
188
+ """
189
+ # Get replica locations
190
+ status, tmp_rep_maps = self.get_replica_locations(input_ds, check_used_file)
191
+ if not status:
192
+ return status, tmp_rep_maps
193
+
194
+ # Get candidate sites
195
+ return self.get_candidate_sites(tmp_rep_maps, prod_source_label, job_label)
196
+
197
+ def get_list_dataset_replicas(self, dataset: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
198
+ """
199
+ Get the list of replicas for a given dataset.
200
+
201
+ Args:
202
+ dataset (str): The name of the dataset.
203
+ max_attempts (int, optional): The maximum number of attempts to get the replicas. Defaults to 3.
204
+
205
+ Returns:
206
+ tuple: A tuple containing the status (bool) and the result (dict or str).
207
+ """
208
+ tmp_log = LogWrapper(_logger, f"get_list_dataset_replicas-{naive_utcnow().isoformat('/')}")
209
+ tmp_log.debug(f"get_list_dataset_replicas {dataset}")
210
+
211
+ for attempt in range(max_attempts):
212
+ tmp_log.debug(f"{attempt}/{max_attempts} listDatasetReplicas {dataset}")
213
+ status, replicas = rucioAPI.list_dataset_replicas(dataset)
214
+ if status == 0:
215
+ tmp_log.debug(f"get_list_dataset_replicas->{str(replicas)}")
216
+ tmp_log.debug("end")
217
+ return True, replicas
218
+ time.sleep(10)
219
+
220
+ tmp_log.error(f"bad response for {dataset}")
221
+ tmp_log.debug("end")
222
+ return False, {}
223
+
224
+ def get_list_dataset_replicas_in_container(self, container: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
225
+ """
226
+ Get the list of replicas for a given container.
227
+
228
+ Args:
229
+ container (str): The name of the container.
230
+ max_attempts (int, optional): The maximum number of attempts to get the replicas. Defaults to 3.
231
+
232
+ Returns:
233
+ tuple: A tuple containing the status (bool) and the result (dict or str).
234
+ """
235
+ tmp_log = LogWrapper(_logger, f"get_list_dataset_replicas_in_container-{naive_utcnow().isoformat('/')}")
236
+
237
+ tmp_log.debug(f"get_list_dataset_replicas_in_container {container}")
238
+
239
+ # response for failure
240
+ res_for_failure = False, {}
241
+
242
+ # get datasets in container
243
+ for attempt in range(max_attempts):
244
+ tmp_log.debug(f"{attempt}/{max_attempts} listDatasetsInContainer {container}")
245
+ datasets, out = rucioAPI.list_datasets_in_container(container)
246
+ if datasets is not None:
247
+ break
248
+ time.sleep(60)
249
+
250
+ if datasets is None:
251
+ tmp_log.error(out)
252
+ tmp_log.error(f"bad DDM response for {container}")
253
+ tmp_log.debug("end")
254
+ return res_for_failure
255
+
256
+ # loop over all datasets
257
+ all_rep_map = {}
258
+ for dataset in datasets:
259
+ # get replicas
260
+ status, tmp_rep_sites = self.get_list_dataset_replicas(dataset)
261
+ if not status:
262
+ tmp_log.debug("end")
263
+ return res_for_failure
264
+ # append
265
+ all_rep_map[dataset] = tmp_rep_sites
266
+
267
+ # return
268
+ tmp_log.debug("end")
269
+ return True, all_rep_map
270
+
271
+ def get_used_datasets(self, dataset_map: Dict, max_attempts: int = 3) -> Tuple[bool, Dict]:
272
+ """
273
+ Get the datasets that are used by jobs.
274
+
275
+ Args:
276
+ dataset_map (dict): The map of datasets.
277
+ max_attempts (int, optional): The maximum number of attempts to get the file list. Defaults to 3.
278
+
279
+ Returns:
280
+ tuple: A tuple containing the status (bool) and the used datasets list.
281
+ """
282
+ tmp_log = LogWrapper(_logger, f"get_used_datasets-{naive_utcnow().isoformat('/')}")
283
+ tmp_log.debug(f"get_used_datasets {str(dataset_map)}")
284
+
285
+ res_for_failure = (False, [])
286
+ used_ds_list = []
287
+
288
+ # loop over all datasets
289
+ for dataset_name in dataset_map:
290
+ # get file list
291
+ for attempt in range(max_attempts):
292
+ try:
293
+ tmp_log.debug(f"{attempt}/{max_attempts} listFilesInDataset {dataset_name}")
294
+ file_items, out = rucioAPI.list_files_in_dataset(dataset_name)
295
+ status = True
296
+ break
297
+ except Exception:
298
+ status = False
299
+ err_type, err_value = sys.exc_info()[:2]
300
+ out = f"{err_type} {err_value}"
301
+ time.sleep(60)
302
+
303
+ if not status:
304
+ tmp_log.error(out)
305
+ tmp_log.error(f"bad DDM response to get size of {dataset_name}")
306
+ tmp_log.debug("end")
307
+ return res_for_failure
308
+
309
+ # check if jobs use the dataset
310
+ used_flag = False
311
+ for tmp_job in self.jobs:
312
+ for tmp_file in tmp_job.Files:
313
+ if tmp_file.type == "input" and tmp_file.lfn in file_items:
314
+ used_flag = True
315
+ break
316
+ # escape
317
+ if used_flag:
318
+ break
319
+
320
+ # used
321
+ if used_flag:
322
+ used_ds_list.append(dataset_name)
323
+
324
+ # return
325
+ tmp_log.debug(f"used datasets = {str(used_ds_list)}")
326
+ tmp_log.debug("end")
327
+ return True, used_ds_list
328
+
329
+ def get_file_from_dataset(self, dataset_name: str, guid: str, max_attempts: int = 3) -> Tuple[bool, Dict]:
330
+ """
331
+ Get file information from a dataset.
332
+
333
+ Args:
334
+ dataset_name (str): The name of the dataset.
335
+ guid (str): The GUID of the file.
336
+ max_attempts (int, optional): The maximum number of attempts to get the file list. Defaults to 3.
337
+
338
+ Returns:
339
+ tuple: A tuple containing the status (bool) and the file information (dict or None).
340
+ """
341
+ tmp_log = LogWrapper(_logger, f"get_file_from_dataset-{naive_utcnow().isoformat('/')}")
342
+ tmp_log.debug(f"get_file_from_dataset {dataset_name} {guid}")
343
+
344
+ res_for_failure = (False, None)
345
+
346
+ # get files in datasets
347
+ global g_files_in_ds_map
348
+ if dataset_name not in g_files_in_ds_map:
349
+ # get file list
350
+ for attempt in range(max_attempts):
351
+ try:
352
+ tmp_log.debug(f"{attempt}/{max_attempts} listFilesInDataset {dataset_name}")
353
+ file_items, out = rucioAPI.list_files_in_dataset(dataset_name)
354
+ status = True
355
+ break
356
+ except Exception:
357
+ status = False
358
+ err_type, err_value = sys.exc_info()[:2]
359
+ out = f"{err_type} {err_value}"
360
+ time.sleep(60)
361
+
362
+ if not status:
363
+ tmp_log.error(out)
364
+ tmp_log.error(f"bad DDM response to get size of {dataset_name}")
365
+ tmp_log.debug("end")
366
+ return res_for_failure
367
+ # append
368
+ g_files_in_ds_map[dataset_name] = file_items
369
+
370
+ # check if file is in the dataset
371
+ for tmp_lfn, tmp_val in g_files_in_ds_map[dataset_name].items():
372
+ if uuid.UUID(tmp_val["guid"]) == uuid.UUID(guid):
373
+ ret_map = tmp_val
374
+ ret_map["lfn"] = tmp_lfn
375
+ ret_map["dataset"] = dataset_name
376
+ tmp_log.debug("end")
377
+ return True, ret_map
378
+
379
+ tmp_log.debug("end")
380
+ return res_for_failure
381
+
382
+ def register_dataset_container_with_datasets(
383
+ self, container_name: str, files: List, replica_map: Dict, n_sites: int = 1, owner: str = None, max_attempts: int = 3
384
+ ) -> Tuple[bool, Dict]:
385
+ """
386
+ Register a new dataset container with datasets.
387
+
388
+ Args:
389
+ container_name (str): The name of the container.
390
+ files (list): The list of files to be included in the dataset.
391
+ replica_map (dict): The map of replicas.
392
+ n_sites (int, optional): The number of sites. Defaults to 1.
393
+ owner (str, optional): The owner of the dataset. Defaults to None.
394
+ max_attempts (int, optional): The maximum number of attempts to register the container. Defaults to 3.
395
+
396
+ Returns:
397
+ bool: The status of the registration process.
398
+ """
399
+ tmp_logger = LogWrapper(_logger, f"register_dataset_container_with_datasets-{naive_utcnow().isoformat('/')}")
400
+ tmp_logger.debug(f"register_dataset_container_with_datasets {container_name}")
401
+
402
+ # parse DN
403
+ if owner is not None:
404
+ status, user_info = rucioAPI.finger(owner)
405
+ if not status:
406
+ tmp_logger.debug(f"failed to finger: {user_info}")
407
+ else:
408
+ owner = user_info["nickname"]
409
+ tmp_logger.debug(f"parsed DN={owner}")
410
+
411
+ # sort by locations
412
+ files_map = {}
413
+ for tmp_file in files:
414
+ tmp_locations = sorted(replica_map[tmp_file["dataset"]])
415
+ new_locations = []
416
+ # skip STAGING
417
+ for tmp_location in tmp_locations:
418
+ if not tmp_location.endswith("STAGING"):
419
+ new_locations.append(tmp_location)
420
+ if not new_locations:
421
+ continue
422
+ tmp_locations = new_locations
423
+ tmp_key = tuple(tmp_locations)
424
+ files_map.setdefault(tmp_key, [])
425
+ # append file
426
+ files_map[tmp_key].append(tmp_file)
427
+
428
+ # get nfiles per dataset
429
+ n_files_per_dataset, _ = divmod(len(files), n_sites)
430
+ if n_files_per_dataset == 0:
431
+ n_files_per_dataset = 1
432
+ max_files_per_dataset = 1000
433
+ if n_files_per_dataset >= max_files_per_dataset:
434
+ n_files_per_dataset = max_files_per_dataset
435
+
436
+ # register new datasets
437
+ dataset_names = []
438
+ tmp_index = 1
439
+ for tmp_locations, tmp_files in files_map.items():
440
+ tmp_sub_index = 0
441
+ while tmp_sub_index < len(tmp_files):
442
+ tmp_dataset_name = container_name[:-1] + "_%04d" % tmp_index
443
+ tmp_ret = self.register_dataset_with_location(
444
+ tmp_dataset_name,
445
+ tmp_files[tmp_sub_index : tmp_sub_index + n_files_per_dataset],
446
+ tmp_locations,
447
+ owner=None,
448
+ )
449
+ # failed
450
+ if not tmp_ret:
451
+ tmp_logger.error(f"failed to register {tmp_dataset_name}")
452
+ tmp_logger.debug("end")
453
+ return False
454
+ # append dataset
455
+ dataset_names.append(tmp_dataset_name)
456
+ tmp_index += 1
457
+ tmp_sub_index += n_files_per_dataset
458
+
459
+ # register container
460
+ for attempt in range(max_attempts):
461
+ try:
462
+ tmp_logger.debug(f"{attempt}/{max_attempts} registerContainer {container_name}")
463
+ status = rucioAPI.register_container(container_name, dataset_names)
464
+ out = "OK"
465
+ break
466
+ except Exception:
467
+ status = False
468
+ err_type, err_value = sys.exc_info()[:2]
469
+ out = f"{err_type} {err_value}"
470
+ time.sleep(10)
471
+
472
+ if not status:
473
+ tmp_logger.error(out)
474
+ tmp_logger.error(f"bad DDM response to register {container_name}")
475
+ tmp_logger.debug("end")
476
+ return False
477
+
478
+ # return
479
+ tmp_logger.debug(out)
480
+ tmp_logger.debug("end")
481
+ return True
482
+
483
+ def register_dataset_with_location(self, dataset_name: str, files: List, locations: List, owner: str = None, max_attempts: int = 3) -> bool:
484
+ """
485
+ Register a new dataset with locations.
486
+
487
+ Args:
488
+ dataset_name (str): The name of the dataset.
489
+ files (list): The list of files to be included in the dataset.
490
+ locations (list): The list of locations where the dataset will be registered.
491
+ owner (str, optional): The owner of the dataset. Defaults to None.
492
+ max_attempts (int, optional): The maximum number of attempts to register the dataset. Defaults to 3.
493
+
494
+ Returns:
495
+ bool: The status of the registration process.
496
+ """
497
+ tmp_logger = LogWrapper(_logger, f"register_dataset_with_location-{naive_utcnow().isoformat('/')}")
498
+ tmp_logger.debug(f"register_dataset_with_location {dataset_name}")
499
+
500
+ res_for_failure = False
501
+
502
+ # get file info
503
+ guids = []
504
+ lfns = []
505
+ file_sizes = []
506
+ checksums = []
507
+ for tmp_file in files:
508
+ guids.append(tmp_file["guid"])
509
+ lfns.append(tmp_file["scope"] + ":" + tmp_file["lfn"])
510
+ file_sizes.append(int(tmp_file["filesize"]))
511
+ checksums.append(tmp_file["checksum"])
512
+
513
+ # register new dataset
514
+ for attempt in range(max_attempts):
515
+ try:
516
+ tmp_logger.debug(f"{attempt}/{max_attempts} registerNewDataset {dataset_name} len={len(files)}")
517
+ out = rucioAPI.register_dataset(dataset_name, lfns, guids, file_sizes, checksums, lifetime=self.dataset_lifetime)
518
+ tmp_logger.debug(out)
519
+ break
520
+ except Exception:
521
+ err_type, err_value = sys.exc_info()[:2]
522
+ tmp_logger.error(f"{err_type} {err_value}")
523
+ if attempt + 1 == max_attempts:
524
+ tmp_logger.error(f"failed to register {dataset_name} in rucio")
525
+ tmp_logger.debug("end")
526
+ return res_for_failure
527
+ time.sleep(10)
528
+
529
+ # freeze dataset
530
+ for attempt in range(max_attempts):
531
+ tmp_logger.debug(f"{attempt}/{max_attempts} freezeDataset {dataset_name}")
532
+ try:
533
+ rucioAPI.close_dataset(dataset_name)
534
+ status = True
535
+ except Exception:
536
+ err_type, err_value = sys.exc_info()[:2]
537
+ out = f"failed to freeze : {err_type} {err_value}"
538
+ status = False
539
+ if not status:
540
+ time.sleep(10)
541
+ else:
542
+ break
543
+ if not status:
544
+ tmp_logger.error(out)
545
+ tmp_logger.error(f"bad DDM response to freeze {dataset_name}")
546
+ tmp_logger.debug("end")
547
+ return res_for_failure
548
+
549
+ # register locations
550
+ for tmp_location in locations:
551
+ for attempt in range(max_attempts):
552
+ try:
553
+ tmp_logger.debug(f"{attempt}/{max_attempts} registerDatasetLocation {dataset_name} {tmp_location}")
554
+ out = rucioAPI.register_dataset_location(dataset_name, [tmp_location], self.dataset_lifetime, owner)
555
+ tmp_logger.debug(out)
556
+ status = True
557
+ break
558
+
559
+ except Exception:
560
+ status = False
561
+ err_type, err_value = sys.exc_info()[:2]
562
+ tmp_logger.error(f"{err_type} {err_value}")
563
+ if attempt + 1 == max_attempts:
564
+ tmp_logger.error(f"failed to register {dataset_name} in rucio")
565
+ tmp_logger.debug("end")
566
+ return res_for_failure
567
+ time.sleep(10)
568
+
569
+ if not status:
570
+ tmp_logger.error(out)
571
+ tmp_logger.error(f"bad DDM response to register location {dataset_name}")
572
+ tmp_logger.debug("end")
573
+ return res_for_failure
574
+ return True
575
+
576
+ def get_datasets_by_guids(self, out_map: Dict, guids: List[str], dataset_filters: List[str]) -> Tuple[bool, Dict]:
577
+ """
578
+ Get datasets by GUIDs.
579
+
580
+ Args:
581
+ out_map (dict): The output map.
582
+ guids (list): The list of GUIDs.
583
+ dataset_filters (list): The list of dataset filters.
584
+
585
+ Returns:
586
+ tuple: A tuple containing a boolean status and a dictionary.
587
+ """
588
+ tmp_logger = LogWrapper(_logger, f"get_datasets_by_guids-{naive_utcnow().isoformat('/')}")
589
+ tmp_logger.debug(f"get_datasets_by_guids {str(guids)}")
590
+
591
+ ret_map = {}
592
+ try:
593
+ for guid in guids:
594
+ tmp_dataset_names = []
595
+ if guid not in out_map:
596
+ tmp_logger.error(f"GUID={guid} not found")
597
+ tmp_logger.debug("end")
598
+ return False, {}
599
+
600
+ for tmp_dataset_name in out_map[guid]:
601
+ if (
602
+ tmp_dataset_name.startswith("panda")
603
+ or tmp_dataset_name.startswith("user")
604
+ or tmp_dataset_name.startswith("group")
605
+ or tmp_dataset_name.startswith("archive")
606
+ or re.search("_sub\d+$", tmp_dataset_name) is not None
607
+ or re.search("_dis\d+$", tmp_dataset_name) is not None
608
+ or re.search("_shadow$", tmp_dataset_name) is not None
609
+ ):
610
+ continue
611
+
612
+ if dataset_filters:
613
+ flag_match = False
614
+ for tmp_filter in dataset_filters:
615
+ if fnmatch.fnmatchcase(tmp_dataset_name, tmp_filter):
616
+ flag_match = True
617
+ break
618
+ if not flag_match:
619
+ continue
620
+
621
+ tmp_dataset_names.append(tmp_dataset_name)
622
+
623
+ if not tmp_dataset_names:
624
+ tmp_logger.debug(f"no datasets found for GUID={guid}")
625
+ continue
626
+
627
+ if len(tmp_dataset_names) != 1:
628
+ tmp_logger.debug(f"use the first dataset in {str(tmp_dataset_names)} for GUID:{guid}")
629
+
630
+ ret_map[guid] = tmp_dataset_names[0]
631
+ except Exception as e:
632
+ tmp_logger.error(f"failed to parse get_datasets_by_guids: {e}")
633
+ tmp_logger.debug("end")
634
+ return False, {}
635
+
636
+ return True, ret_map
637
+
638
+ def list_datasets_by_guids(self, guids: List[str], dataset_filters: List[str], max_attempts: int = 3) -> Tuple[bool, Dict]:
639
+ """
640
+ List datasets by GUIDs.
641
+
642
+ Args:
643
+ guids (list): The list of GUIDs.
644
+ dataset_filters (list): The list of dataset filters.
645
+ max_attempts (int, optional): The maximum number of attempts to list the datasets. Defaults to 3.
646
+
647
+ Returns:
648
+ tuple: A tuple containing the status (bool) and the result (dict or str).
649
+ """
650
+ tmp_logger = LogWrapper(_logger, f"list_datasets_by_guids-{naive_utcnow().isoformat('/')}")
651
+ tmp_logger.debug(f"list_datasets_by_guids {str(guids)}")
652
+
653
+ res_for_failure = (False, {})
654
+ res_for_fatal = (False, {"isFatal": True})
655
+
656
+ # get size of datasets
657
+ for attempt in range(max_attempts):
658
+ tmp_logger.debug(f"{attempt}/{max_attempts} listDatasetsByGUIDs GUIDs={str(guids)}")
659
+ try:
660
+ out = rucioAPI.list_datasets_by_guids(guids)
661
+ status = True
662
+ break
663
+ except Exception:
664
+ err_type, err_value = sys.exc_info()[:2]
665
+ out = f"failed to get datasets with GUIDs : {err_type} {err_value}"
666
+ status = False
667
+ time.sleep(10)
668
+
669
+ if not status:
670
+ tmp_logger.error(out)
671
+ tmp_logger.error(f"bad DDM response to get size of {str(guids)}")
672
+ if "DataIdentifierNotFound" in out:
673
+ tmp_logger.error("DataIdentifierNotFound in listDatasetsByGUIDs")
674
+ tmp_logger.debug("end")
675
+ return res_for_fatal
676
+ tmp_logger.debug("end")
677
+ return res_for_failure
678
+
679
+ tmp_logger.debug(out)
680
+
681
+ # get map
682
+ status, ret_map = self.get_datasets_by_guids(out, guids, dataset_filters)
683
+
684
+ if not status:
685
+ return res_for_failure
686
+
687
+ tmp_logger.debug("end")
688
+ return True, ret_map
689
+
690
+ def convert_evt_run_to_datasets(
691
+ self, event_run_list: List, dataset_type: str, stream_name: str, dataset_filters: List, ami_tag: str, run_evt_guid_map: Dict
692
+ ) -> Tuple[bool, Dict, List]:
693
+ """
694
+ Convert event/run list to datasets.
695
+
696
+ Args:
697
+ event_run_list (list): The list of run events.
698
+ dataset_type (str): The type of the dataset.
699
+ stream_name (str): The name of the stream.
700
+ dataset_filters (list): The list of dataset filters.
701
+ ami_tag (str): The AMI tag.
702
+ run_evt_guid_map (dict): The map of run events to GUIDs.
703
+
704
+ Returns:
705
+ tuple: A tuple containing the status (bool), the result (dict or str), and the list of all files.
706
+ """
707
+ tmp_logger = LogWrapper(_logger, f"convert_evt_run_to_datasets-{naive_utcnow().isoformat('/')}")
708
+ tmp_logger.debug(f"convert_evt_run_to_datasets type={dataset_type} stream={stream_name} dsPatt={str(dataset_filters)} amitag={ami_tag}")
709
+
710
+ # check data type
711
+ failed_ret = False, {}, []
712
+ fatal_ret = False, {"isFatal": True}, []
713
+ stream_ref = "Stream" + dataset_type
714
+ # import event lookup client
715
+ if run_evt_guid_map == {}:
716
+ if len(event_run_list) == 0:
717
+ tmp_logger.error("Empty list for run and events was provided")
718
+ tmp_logger.debug("end")
719
+ return failed_ret
720
+ # Hadoop EI
721
+ from .event_lookup_client_ei import EventLookupClientEI
722
+
723
+ event_lookup_if = EventLookupClientEI()
724
+ # loop over all events
725
+ n_events_per_loop = 500
726
+ i_events_total = 0
727
+ while i_events_total < len(event_run_list):
728
+ tmp_event_run_list = event_run_list[i_events_total : i_events_total + n_events_per_loop]
729
+ tmp_logger.debug(f"EI lookup for {i_events_total}/{len(event_run_list)}")
730
+ i_events_total += n_events_per_loop
731
+ reg_start = naive_utcnow()
732
+ guid_list_elssi, tmp_com, tmp_out, tmp_err = event_lookup_if.do_lookup(
733
+ tmp_event_run_list,
734
+ stream=stream_name,
735
+ tokens=stream_ref,
736
+ ami_tag=ami_tag,
737
+ )
738
+ reg_time = naive_utcnow() - reg_start
739
+ tmp_logger.debug(f"EI command: {tmp_com}")
740
+ tmp_logger.debug(f"took {reg_time.seconds}.{reg_time.microseconds / 1000:03f} sec for {len(tmp_event_run_list)} events")
741
+ # failed
742
+ if tmp_err not in [None, ""] or len(guid_list_elssi) == 0:
743
+ tmp_logger.debug(tmp_com)
744
+ tmp_logger.debug(tmp_out)
745
+ tmp_logger.debug(tmp_err)
746
+ tmp_logger.error("invalid return from EventIndex")
747
+ tmp_logger.debug("end")
748
+ return failed_ret
749
+ # check events
750
+ for run_nr, evt_nr in tmp_event_run_list:
751
+ param_str = f"Run:{run_nr} Evt:{evt_nr} Stream:{stream_name}"
752
+ tmp_logger.debug(param_str)
753
+ tmp_run_evt_key = (int(run_nr), int(evt_nr))
754
+ # not found
755
+ if tmp_run_evt_key not in guid_list_elssi or len(guid_list_elssi[tmp_run_evt_key]) == 0:
756
+ tmp_logger.debug(tmp_com)
757
+ tmp_logger.debug(tmp_out)
758
+ tmp_logger.debug(tmp_err)
759
+ tmp_logger.error(f"no GUIDs were found in EventIndex for {param_str}")
760
+ tmp_logger.debug("end")
761
+ return fatal_ret
762
+ # append
763
+ run_evt_guid_map[tmp_run_evt_key] = guid_list_elssi[tmp_run_evt_key]
764
+ # convert to datasets
765
+ all_datasets = []
766
+ all_files = []
767
+ all_locations = {}
768
+ for tmp_idx in run_evt_guid_map:
769
+ tmp_guids = run_evt_guid_map[tmp_idx]
770
+ run_nr, evt_nr = tmp_idx
771
+ tmp_ds_ret, tmp_dataset_map = self.list_datasets_by_guids(tmp_guids, dataset_filters)
772
+ # failed
773
+ if not tmp_ds_ret:
774
+ tmp_logger.error(f"failed to convert GUIDs to datasets")
775
+ if "isFatal" in tmp_dataset_map and tmp_dataset_map["isFatal"] is True:
776
+ tmp_logger.debug("end")
777
+ return fatal_ret
778
+ tmp_logger.debug("end")
779
+ return failed_ret
780
+ # empty
781
+ if not tmp_dataset_map:
782
+ tmp_logger.error(f"there is no dataset for Run:{run_nr} Evt:{evt_nr} GUIDs:{str(tmp_guids)}")
783
+ tmp_logger.debug("end")
784
+ return fatal_ret
785
+ if len(tmp_dataset_map) != 1:
786
+ tmp_logger.error(f"there are multiple datasets {str(tmp_dataset_map)} for Run:{run_nr} Evt:{evt_nr} GUIDs:{str(tmp_guids)}")
787
+ tmp_logger.debug("end")
788
+ return fatal_ret
789
+
790
+ # append
791
+ for tmp_guid, tmp_dataset_name in tmp_dataset_map.items():
792
+ # collect dataset names
793
+ if tmp_dataset_name not in all_datasets:
794
+ all_datasets.append(tmp_dataset_name)
795
+ # get location
796
+ stat_rep, replica_map = self.get_list_dataset_replicas(tmp_dataset_name)
797
+ # failed
798
+ if not stat_rep:
799
+ tmp_logger.error(f"failed to get locations for {tmp_dataset_name}")
800
+ tmp_logger.debug("end")
801
+ return failed_ret
802
+ # collect locations
803
+ tmp_location_list = []
804
+ for tmp_location in replica_map:
805
+ # use only complete replicas
806
+ ds_stat_dict = replica_map[tmp_location][0]
807
+ if ds_stat_dict["total"] is not None and ds_stat_dict["total"] == ds_stat_dict["found"]:
808
+ if tmp_location not in tmp_location_list:
809
+ tmp_location_list.append(tmp_location)
810
+ all_locations[tmp_dataset_name] = tmp_location_list
811
+
812
+ # get file info
813
+ tmp_file_ret, tmp_file_info = self.get_file_from_dataset(tmp_dataset_name, tmp_guid)
814
+ # failed
815
+ if not tmp_file_ret:
816
+ tmp_logger.error(f"failed to get fileinfo for GUID:{tmp_guid} DS:{tmp_dataset_name}")
817
+ tmp_logger.debug("end")
818
+ return failed_ret
819
+ # collect files
820
+ all_files.append(tmp_file_info)
821
+ # return
822
+ tmp_logger.debug(f"converted to {str(all_datasets)}, {str(all_locations)}, {str(all_files)}")
823
+ tmp_logger.debug("end")
824
+ return True, all_locations, all_files