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,908 @@
1
+ import sys
2
+ import threading
3
+ import traceback
4
+ from datetime import datetime, timedelta, timezone
5
+
6
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
7
+
8
+ from pandaserver.config import panda_config
9
+ from pandaserver.configurator import aux
10
+ from pandaserver.configurator.aux import *
11
+
12
+ _logger = PandaLogger().getLogger("configurator")
13
+
14
+ # Definitions of roles
15
+ WRITE_LAN = "write_lan"
16
+ READ_LAN = "read_lan"
17
+ DEFAULT = "default"
18
+
19
+
20
+ class Configurator(threading.Thread):
21
+ def __init__(self, taskBuffer, log_stream=None):
22
+ threading.Thread.__init__(self)
23
+
24
+ self.taskBuffer = taskBuffer
25
+ if log_stream:
26
+ self.log_stream = log_stream
27
+ else:
28
+ self.log_stream = _logger
29
+
30
+ if hasattr(panda_config, "CRIC_URL_SITES"):
31
+ self.CRIC_URL_SITES = panda_config.CRIC_URL_SITES
32
+ else:
33
+ self.CRIC_URL_SITES = "https://atlas-cric.cern.ch/api/atlas/site/query/?json"
34
+
35
+ if hasattr(panda_config, "CRIC_URL_DDMENDPOINTS"):
36
+ self.CRIC_URL_DDMENDPOINTS = panda_config.CRIC_URL_DDMENDPOINTS
37
+ else:
38
+ self.CRIC_URL_DDMENDPOINTS = "https://atlas-cric.cern.ch/api/atlas/ddmendpoint/query/?json"
39
+
40
+ if hasattr(panda_config, "CRIC_URL_SCHEDCONFIG"):
41
+ self.CRIC_URL_SCHEDCONFIG = panda_config.CRIC_URL_SCHEDCONFIG
42
+ else:
43
+ self.CRIC_URL_SCHEDCONFIG = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json"
44
+
45
+ if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST"):
46
+ self.CRIC_URL_DDMBLACKLIST = panda_config.CRIC_URL_DDMBLACKLIST
47
+ else:
48
+ self.CRIC_URL_DDMBLACKLIST = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json&activity=write_wan&fstate=OFF"
49
+
50
+ if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST_READ"):
51
+ self.CRIC_URL_DDMBLACKLIST_READ = panda_config.CRIC_URL_DDMBLACKLIST_READ
52
+ else:
53
+ self.CRIC_URL_DDMBLACKLIST_READ = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json&activity=read_wan&fstate=OFF"
54
+
55
+ if hasattr(panda_config, "CRIC_URL_DDMBLACKLIST_FULL"):
56
+ self.CRIC_URL_DDMBLACKLIST_FULL = panda_config.CRIC_URL_DDMBLACKLIST_FULL
57
+ else:
58
+ self.CRIC_URL_DDMBLACKLIST_FULL = "https://atlas-cric.cern.ch/api/atlas/ddmendpointstatus/query/?json"
59
+
60
+ if hasattr(panda_config, "RUCIO_RSE_USAGE"):
61
+ self.RUCIO_RSE_USAGE = panda_config.RUCIO_RSE_USAGE
62
+ else:
63
+ self.RUCIO_RSE_USAGE = "https://rucio-hadoop.cern.ch/dumps/rse_usage/current.json"
64
+
65
+ def retrieve_data(self):
66
+ self.log_stream.debug("Getting site dump...")
67
+ self.site_dump = aux.get_dump(self.CRIC_URL_SITES)
68
+ if not self.site_dump:
69
+ self.log_stream.error("The site dump was not retrieved correctly")
70
+ return False
71
+ self.log_stream.debug("Done")
72
+
73
+ self.log_stream.debug("Getting DDM endpoints dump...")
74
+ self.endpoint_dump = aux.get_dump(self.CRIC_URL_DDMENDPOINTS)
75
+ if not self.endpoint_dump:
76
+ self.log_stream.error("The endpoint dump was not retrieved correctly")
77
+ return False
78
+ self.log_stream.debug("Done")
79
+
80
+ self.log_stream.debug("Parsing endpoints...")
81
+ self.endpoint_token_dict = self.parse_endpoints()
82
+ self.log_stream.debug("Done")
83
+
84
+ self.log_stream.debug("Getting schedconfig dump...")
85
+ self.schedconfig_dump = aux.get_dump(self.CRIC_URL_SCHEDCONFIG)
86
+ if not self.schedconfig_dump:
87
+ self.log_stream.error("The schedconfig dump was not retrieved correctly")
88
+ return False
89
+ self.log_stream.debug("Done")
90
+
91
+ # --- WRITE ---
92
+ self.log_stream.debug("Getting ddmblacklist dump...")
93
+ try:
94
+ if self.CRIC_URL_DDMBLACKLIST:
95
+ dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST)
96
+ if dump is None:
97
+ # True failure (e.g., fetch/parse problem)
98
+ self.log_stream.error("The blacklisted endpoint dump could not be retrieved (None)")
99
+ return False
100
+ # Treat {} as valid/empty
101
+ self.blacklisted_endpoints = list(dump) if isinstance(dump, (dict, list, tuple, set)) else list(
102
+ dump or [])
103
+ self.blacklisted_endpoints_write = self.blacklisted_endpoints
104
+ self.log_stream.debug(f"ddmblacklist(write) retrieved {len(self.blacklisted_endpoints)} endpoints")
105
+ else:
106
+ self.blacklisted_endpoints = []
107
+ self.blacklisted_endpoints_write = []
108
+ except Exception as e:
109
+ self.log_stream.warning(f"Failed to retrieve ddmblacklist(write); defaulting to empty. Error: {e}")
110
+ self.blacklisted_endpoints = []
111
+ self.blacklisted_endpoints_write = []
112
+
113
+ self.log_stream.debug(f"Blacklisted endpoints {self.blacklisted_endpoints}")
114
+ self.log_stream.debug(f"Blacklisted endpoints write {self.blacklisted_endpoints_write}")
115
+ self.log_stream.debug("Done")
116
+
117
+ # --- READ ---
118
+ self.log_stream.debug("Getting ddmblacklist read dump...")
119
+ try:
120
+ if self.CRIC_URL_DDMBLACKLIST_READ:
121
+ dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST_READ)
122
+ # Accept {} as empty; only treat None as failure
123
+ if dump is None:
124
+ self.log_stream.warning("ddmblacklist(read) returned None; defaulting to empty")
125
+ self.blacklisted_endpoints_read = []
126
+ else:
127
+ self.blacklisted_endpoints_read = list(dump) if isinstance(dump,
128
+ (dict, list, tuple, set)) else list(
129
+ dump or [])
130
+ self.log_stream.debug(f"ddmblacklist(read) retrieved {len(self.blacklisted_endpoints_read)} endpoints")
131
+ else:
132
+ self.blacklisted_endpoints_read = []
133
+ except Exception as e:
134
+ self.log_stream.warning(f"Failed to retrieve ddmblacklist(read); defaulting to empty. Error: {e}")
135
+ self.blacklisted_endpoints_read = []
136
+
137
+ self.log_stream.debug(f"Blacklisted endpoints read {self.blacklisted_endpoints_read}")
138
+ self.log_stream.debug("Done")
139
+
140
+ # --- FULL / DETAILED ---
141
+ self.log_stream.debug("Getting DDM detailed status...")
142
+ try:
143
+ if self.CRIC_URL_DDMBLACKLIST_FULL:
144
+ dump = aux.get_dump(self.CRIC_URL_DDMBLACKLIST_FULL)
145
+ if dump is None:
146
+ self.log_stream.warning("ddmblacklist(full) returned None; defaulting to empty")
147
+ self.ddm_detailed_exclusions = {}
148
+ else:
149
+ self.ddm_detailed_exclusions = dump if isinstance(dump, dict) else dict(dump or {})
150
+ # Expecting a dict here; {} is valid/empty
151
+ if isinstance(dump, dict):
152
+ self.ddm_detailed_exclusions = dump
153
+ else:
154
+ # Be tolerant but safe: coerce to dict if possible, else empty
155
+ self.ddm_detailed_exclusions = dict(dump) if hasattr(dump, "items") else {}
156
+ self.log_stream.debug(
157
+ f"ddmblacklist(full) retrieved {len(self.ddm_detailed_exclusions)} entries"
158
+ )
159
+ else:
160
+ self.ddm_detailed_exclusions = {}
161
+ except Exception as e:
162
+ self.log_stream.warning(f"Failed to retrieve ddmblacklist(full); defaulting to empty. Error: {e}")
163
+ self.ddm_detailed_exclusions = {}
164
+
165
+ self.log_stream.debug(f"Blacklisted endpoints full {self.ddm_detailed_exclusions}")
166
+ self.log_stream.debug("Done")
167
+
168
+ if self.RUCIO_RSE_USAGE:
169
+ self.log_stream.debug("Getting Rucio RSE usage dump...")
170
+ self.rse_usage = aux.get_dump(self.RUCIO_RSE_USAGE)
171
+ if not self.rse_usage:
172
+ self.log_stream.error("The RSE usage dump was not retrieved correctly")
173
+ return False
174
+ self.log_stream.debug("Done")
175
+ else:
176
+ self.rse_usage = {}
177
+ return True
178
+
179
+ def get_site_info(self, site):
180
+ """
181
+ Gets the relevant information from a site
182
+ """
183
+ state = site["state"]
184
+ tier_level = site["tier_level"]
185
+
186
+ if "Nucleus" in site["datapolicies"]: # or site['tier_level'] <= 1:
187
+ role = "nucleus"
188
+ else:
189
+ role = "satellite"
190
+
191
+ return role, state, tier_level
192
+
193
+ def parse_endpoints(self):
194
+ """
195
+ Puts the relevant information from endpoint_dump into a more usable format
196
+ """
197
+ endpoint_token_dict = {}
198
+ for endpoint, endpoint_config in self.endpoint_dump.items():
199
+ # Filter out testing and inactive endpoints
200
+ if endpoint_config["state"] == "ACTIVE": # and endpoint['type'] != 'TEST'
201
+ endpoint_token_dict[endpoint] = {}
202
+ endpoint_token_dict[endpoint]["token"] = endpoint_config["token"]
203
+ endpoint_token_dict[endpoint]["site_name"] = endpoint_config["site"]
204
+ endpoint_token_dict[endpoint]["type"] = endpoint_config["type"]
205
+ if endpoint_config["is_tape"]:
206
+ endpoint_token_dict[endpoint]["is_tape"] = "Y"
207
+ else:
208
+ endpoint_token_dict[endpoint]["is_tape"] = "N"
209
+ else:
210
+ self.log_stream.debug(f"parse_endpoints: skipped endpoint {endpoint} (type: {endpoint_config['type']}, state: {endpoint_config['state']})")
211
+
212
+ return endpoint_token_dict
213
+
214
+ def process_site_dumps(self):
215
+ """
216
+ Parses the CRIC site and endpoint dumps and prepares a format loadable to the DB
217
+ """
218
+ # Variables that will contain only the relevant information
219
+ sites_list = []
220
+ included_sites = []
221
+ ddm_endpoints_list = []
222
+ panda_sites_list = []
223
+
224
+ # New relationship information based on astorages field in CRIC.
225
+ # Used to fill atlas_panda.panda_ddm_relation table
226
+ try:
227
+ panda_ddm_relation_list = self.get_panda_ddm_relations()
228
+ except Exception:
229
+ # Temporary protection to prevent issues
230
+ self.log_stream.error(f"get_panda_ddm_relations excepted with {traceback.print_exc()}")
231
+ panda_ddm_relation_list = []
232
+
233
+ # Iterate the site dump
234
+ for site_name, site_config in self.site_dump.items():
235
+ # Add the site info to a list
236
+ (site_role, site_state, tier_level) = self.get_site_info(site_config)
237
+ if site_state == "ACTIVE" and site_name not in included_sites:
238
+ sites_list.append(
239
+ {
240
+ "site_name": site_name,
241
+ "role": site_role,
242
+ "tier_level": tier_level,
243
+ }
244
+ )
245
+ included_sites.append(site_name)
246
+ else:
247
+ self.log_stream.debug(f"process_site_dumps: skipped site {site_name} (state: {site_state})")
248
+
249
+ # Get the DDM endpoints for the site we are inspecting
250
+ for ddm_endpoint_name in site_config["ddmendpoints"]:
251
+ try:
252
+ ddm_spacetoken_name = self.endpoint_token_dict[ddm_endpoint_name]["token"]
253
+ ddm_endpoint_type = self.endpoint_token_dict[ddm_endpoint_name]["type"]
254
+ ddm_endpoint_is_tape = self.endpoint_token_dict[ddm_endpoint_name]["is_tape"]
255
+ if ddm_endpoint_name in self.blacklisted_endpoints:
256
+ ddm_endpoint_blacklisted_write = "Y"
257
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is blacklisted for write")
258
+ else:
259
+ ddm_endpoint_blacklisted_write = "N"
260
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is NOT blacklisted for write")
261
+
262
+ if ddm_endpoint_name in self.blacklisted_endpoints_read:
263
+ ddm_endpoint_blacklisted_read = "Y"
264
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is blacklisted for read")
265
+ else:
266
+ ddm_endpoint_blacklisted_read = "N"
267
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} is NOT blacklisted for read")
268
+
269
+ detailed_status = {}
270
+ if ddm_endpoint_name in self.ddm_detailed_exclusions:
271
+ for activity in self.ddm_detailed_exclusions[ddm_endpoint_name]:
272
+ detailed_status[activity] = self.ddm_detailed_exclusions[ddm_endpoint_name][activity]["status"]["value"]
273
+
274
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has detailed exclusions {detailed_status}")
275
+ except KeyError:
276
+ continue
277
+
278
+ # Get the storage space
279
+ try:
280
+ space_used = self.rse_usage[ddm_endpoint_name]["storage"]["used"] / GB
281
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has used space {space_used}GB")
282
+ space_free = self.rse_usage[ddm_endpoint_name]["storage"]["free"] / GB
283
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has free space {space_free}GB")
284
+ space_total = space_used + space_free
285
+ space_timestamp = datetime.strptime(
286
+ self.rse_usage[ddm_endpoint_name]["storage"]["updated_at"],
287
+ "%Y-%m-%d %H:%M:%S",
288
+ )
289
+ self.log_stream.debug(f"process_site_dumps: endpoint {ddm_endpoint_name} has space timestamp {space_timestamp}")
290
+
291
+ except (KeyError, ValueError):
292
+ space_used, space_free, space_total, space_timestamp = (
293
+ None,
294
+ None,
295
+ None,
296
+ None,
297
+ )
298
+ self.log_stream.warning(f"process_site_dumps: no rse storage usage information for {ddm_endpoint_name}")
299
+
300
+ # Get the Expired space
301
+ try:
302
+ space_expired = self.rse_usage[ddm_endpoint_name]["expired"]["used"] / GB
303
+ except KeyError:
304
+ space_expired = 0
305
+ self.log_stream.warning(f"process_site_dumps: no rse EXPIRED usage information for {ddm_endpoint_name}")
306
+
307
+ ddm_spacetoken_state = site_config["ddmendpoints"][ddm_endpoint_name]["state"]
308
+ if ddm_spacetoken_state == "ACTIVE":
309
+ ddm_endpoints_list.append(
310
+ {
311
+ "ddm_endpoint_name": ddm_endpoint_name,
312
+ "site_name": site_name,
313
+ "ddm_spacetoken_name": ddm_spacetoken_name,
314
+ "type": ddm_endpoint_type,
315
+ "is_tape": ddm_endpoint_is_tape,
316
+ "blacklisted": ddm_endpoint_blacklisted_write,
317
+ "blacklisted_write": ddm_endpoint_blacklisted_write,
318
+ "blacklisted_read": ddm_endpoint_blacklisted_read,
319
+ "detailed_status": json.dumps(detailed_status),
320
+ "space_used": space_used,
321
+ "space_free": space_free,
322
+ "space_total": space_total,
323
+ "space_expired": space_expired,
324
+ "space_timestamp": space_timestamp,
325
+ }
326
+ )
327
+ self.log_stream.debug(f"process_site_dumps: added DDM endpoint {ddm_endpoint_name}")
328
+ else:
329
+ self.log_stream.debug(f"process_site_dumps: skipped DDM endpoint {ddm_endpoint_name} because of state {ddm_spacetoken_state}")
330
+
331
+ # Get the PanDA resources
332
+ for panda_resource in site_config["presources"]:
333
+ for panda_site in site_config["presources"][panda_resource]:
334
+ panda_site_state = site_config["presources"][panda_resource][panda_site]["state"]
335
+ if panda_site_state != "ACTIVE":
336
+ self.log_stream.debug(f"process_site_dumps: skipped PanDA site {panda_site} (state: {panda_site_state})")
337
+ continue
338
+ panda_site_name = panda_site
339
+ panda_sites_list.append({"panda_site_name": panda_site_name, "site_name": site_name})
340
+
341
+ return sites_list, panda_sites_list, ddm_endpoints_list, panda_ddm_relation_list
342
+
343
+ def parse_role(self, role):
344
+ """
345
+ Traditionally roles have been "read_lan" or "write_lan". We will consider them the default roles.
346
+ If you want to overwrite the role for specific jobs in CRIC, you can define e.g. "read_lan_analysis". Here we
347
+ want to strip the role to the "analysis" tag and return role "read_lan" and scope "analysis"
348
+
349
+ Examples:
350
+ "read_lan_analysis" --> "read_lan" and "analysis"
351
+ "read_lan" --> "read_lan" and "default"
352
+ """
353
+ # default roles: "read_lan" or "write_lan"
354
+ if role == READ_LAN or role == WRITE_LAN:
355
+ return role, DEFAULT
356
+
357
+ # special read_lan roles, e.g. "read_lan_analysis"
358
+ elif role.startswith(READ_LAN):
359
+ role_clean = READ_LAN
360
+ scope = role.replace(f"{READ_LAN}_", "")
361
+ return role_clean, scope
362
+
363
+ # special write_lan roles, e.g. "write_lan_analysis"
364
+ elif role.startswith(WRITE_LAN):
365
+ role_clean = WRITE_LAN
366
+ scope = role.replace(f"{WRITE_LAN}_", "")
367
+ return role_clean, scope
368
+
369
+ # roles we are currently not expecting
370
+ else:
371
+ return role, DEFAULT
372
+
373
+ def get_panda_ddm_relations(self):
374
+ """
375
+ Gets the DDM endpoints assigned to a panda queue, based on the CRIC astorages field of the panda queue definition
376
+ """
377
+ relation_list = []
378
+
379
+ # iterate on panda queues
380
+ for long_panda_site_name in self.schedconfig_dump:
381
+ panda_site_name = self.schedconfig_dump[long_panda_site_name]["panda_resource"]
382
+ cpu_site_name = self.schedconfig_dump[long_panda_site_name]["atlas_site"]
383
+ dict_ddm_endpoint = {}
384
+
385
+ # get the astorages field
386
+ if self.schedconfig_dump[long_panda_site_name]["astorages"]:
387
+ astorages = self.schedconfig_dump[long_panda_site_name]["astorages"]
388
+
389
+ # iterate the storages to establish their roles and orders
390
+ order_read = {DEFAULT: 1}
391
+ order_write = {DEFAULT: 1}
392
+ for role in astorages:
393
+ # ignore old roles (pr, pw) that we are not supposed to use
394
+ if not role.startswith(READ_LAN) and not role.startswith(WRITE_LAN):
395
+ continue
396
+
397
+ for ddm_endpoint_name in astorages[role]:
398
+ # set the read/write order and increment the respective counter
399
+ role_clean, scope = self.parse_role(role)
400
+ order_read.setdefault(scope, 1)
401
+ order_write.setdefault(scope, 1)
402
+
403
+ # initialize DDM endpoint and scope if it does not exist
404
+ dict_ddm_endpoint.setdefault(ddm_endpoint_name, {})
405
+ dict_ddm_endpoint[ddm_endpoint_name].setdefault(scope, {"order_write": None, "order_read": None, "role": []})
406
+
407
+ if role.startswith(WRITE_LAN):
408
+ dict_ddm_endpoint[ddm_endpoint_name][scope]["order_write"] = order_write[scope]
409
+ order_write[scope] += 1
410
+ elif role.startswith(READ_LAN):
411
+ dict_ddm_endpoint[ddm_endpoint_name][scope]["order_read"] = order_read[scope]
412
+ order_read[scope] += 1
413
+ # append the roles
414
+ dict_ddm_endpoint[ddm_endpoint_name][scope]["role"].append(role_clean)
415
+
416
+ for ddm_endpoint_name, ddm_endpoint_values in dict_ddm_endpoint.items():
417
+ for scope, scope_values in ddm_endpoint_values.items():
418
+ # unpack the values
419
+ roles = scope_values["role"]
420
+ order_write = scope_values["order_write"]
421
+ order_read = scope_values["order_read"]
422
+
423
+ # figure out the ATLAS site the DDM endpoint belongs to
424
+ try:
425
+ storage_site_name = self.endpoint_token_dict[ddm_endpoint_name]["site_name"]
426
+ except KeyError:
427
+ self.log_stream.warning(
428
+ f"Skipped {long_panda_site_name}, because primary associated DDM endpoint {ddm_endpoint_name} not found (e.g.DISABLED)"
429
+ )
430
+ continue
431
+
432
+ # figure out if the storage is local to the cpu
433
+ if storage_site_name == cpu_site_name:
434
+ is_local = "Y"
435
+ else:
436
+ is_local = "N"
437
+
438
+ # endpoints with order 1 will be considered default
439
+ if order_read == 1:
440
+ default_read = "Y"
441
+ else:
442
+ default_read = "N"
443
+ if order_write == 1:
444
+ default_write = "Y"
445
+ else:
446
+ default_write = "N"
447
+
448
+ # add the values to the list of relations if the ddm endpoint is valid
449
+ if ddm_endpoint_name in self.endpoint_token_dict:
450
+ relation_list.append(
451
+ {
452
+ "panda_site_name": panda_site_name,
453
+ "ddm_endpoint_name": ddm_endpoint_name,
454
+ "roles": ",".join(roles),
455
+ "default_read": default_read,
456
+ "default_write": default_write,
457
+ "is_local": is_local,
458
+ "order_read": order_read,
459
+ "order_write": order_write,
460
+ "scope": scope,
461
+ }
462
+ )
463
+
464
+ return relation_list
465
+
466
+ def consistency_check(self):
467
+ """
468
+ Point out sites, panda sites and DDM endpoints that are missing in one of the sources
469
+ """
470
+ # Check for site inconsistencies
471
+ CRIC_sites = set([site_name for site_name, site_config in self.site_dump.items() if site_config["state"] == "ACTIVE"])
472
+ self.log_stream.debug(f"Sites in CRIC {CRIC_sites}")
473
+ configurator_sites = self.taskBuffer.configurator_read_sites()
474
+ self.log_stream.debug(f"Sites in Configurator {configurator_sites}")
475
+ schedconfig_sites = self.taskBuffer.configurator_read_cric_sites()
476
+ self.log_stream.debug(f"Sites in Schedconfig {schedconfig_sites}")
477
+
478
+ all_sites = sorted(filter(None, CRIC_sites | configurator_sites | schedconfig_sites))
479
+
480
+ for site in all_sites:
481
+ missing = []
482
+ if site not in CRIC_sites:
483
+ missing.append("CRIC")
484
+ if site not in configurator_sites:
485
+ missing.append("Configurator")
486
+ if site not in schedconfig_sites:
487
+ missing.append("Schedconfig")
488
+ if missing:
489
+ self.log_stream.warning(f"SITE inconsistency: {site} was not found in {missing}")
490
+
491
+ # Check for panda-site inconsistencies
492
+ CRIC_panda_sites = set([self.schedconfig_dump[long_panda_site_name]["panda_resource"] for long_panda_site_name in self.schedconfig_dump])
493
+ self.log_stream.debug(f"PanDA sites in CRIC {CRIC_panda_sites}")
494
+ configurator_panda_sites = self.taskBuffer.configurator_read_panda_sites()
495
+ self.log_stream.debug(f"PanDA sites in Configurator {configurator_panda_sites}")
496
+ schedconfig_panda_sites = self.taskBuffer.configurator_read_cric_panda_sites()
497
+ self.log_stream.debug(f"PanDA sites in Schedconfig {schedconfig_panda_sites}")
498
+
499
+ all_panda_sites = sorted(CRIC_panda_sites | configurator_panda_sites | schedconfig_panda_sites)
500
+
501
+ for site in all_panda_sites:
502
+ missing = []
503
+ if site not in CRIC_panda_sites:
504
+ missing.append("CRIC")
505
+ if site not in configurator_panda_sites:
506
+ missing.append("Configurator")
507
+ if site not in schedconfig_panda_sites:
508
+ missing.append("Schedconfig")
509
+ if missing:
510
+ self.log_stream.warning(f"PanDA SITE inconsistency: {site} was not found in {missing}")
511
+
512
+ # Check for DDM endpoint inconsistencies
513
+ CRIC_ddm_endpoints = set([ddm_endpoint_name for ddm_endpoint_name in self.endpoint_token_dict])
514
+ self.log_stream.debug(f"DDM endpoints in CRIC {CRIC_ddm_endpoints}")
515
+ configurator_ddm_endpoints = self.taskBuffer.configurator_read_ddm_endpoints()
516
+ self.log_stream.debug(f"DDM endpoints in Configurator {configurator_ddm_endpoints}")
517
+
518
+ all_ddm_endpoints = sorted(CRIC_ddm_endpoints | configurator_ddm_endpoints)
519
+
520
+ for site in all_ddm_endpoints:
521
+ missing = []
522
+ if site not in CRIC_ddm_endpoints:
523
+ missing.append("CRIC")
524
+ if site not in configurator_ddm_endpoints:
525
+ missing.append("Configurator")
526
+ if missing:
527
+ self.log_stream.warning(f"DDM ENDPOINT inconsistency: {site} was not found in {missing}")
528
+
529
+ self.cleanup_configurator(
530
+ CRIC_sites,
531
+ CRIC_panda_sites,
532
+ CRIC_ddm_endpoints,
533
+ configurator_sites,
534
+ configurator_panda_sites,
535
+ configurator_ddm_endpoints,
536
+ )
537
+
538
+ def cleanup_configurator(
539
+ self,
540
+ CRIC_sites,
541
+ CRIC_panda_sites,
542
+ CRIC_ddm_endpoints,
543
+ configurator_sites,
544
+ configurator_panda_sites,
545
+ configurator_ddm_endpoints,
546
+ ):
547
+ """
548
+ Cleans up information from configurator that is not in CRIC
549
+ """
550
+ if not CRIC_sites or not CRIC_panda_sites or not CRIC_ddm_endpoints:
551
+ self.log_stream.warning("Exiting cleanup because one of CRIC sets was empty")
552
+
553
+ # Clean up sites
554
+ sites_to_delete = configurator_sites - CRIC_sites
555
+ self.taskBuffer.configurator_delete_sites(sites_to_delete)
556
+
557
+ # Clean up panda sites
558
+ panda_sites_to_delete = configurator_panda_sites - CRIC_panda_sites
559
+ self.taskBuffer.configurator_delete_panda_sites(panda_sites_to_delete)
560
+
561
+ # Clean up DDM endpoints
562
+ ddm_endpoints_to_delete = configurator_ddm_endpoints - CRIC_ddm_endpoints
563
+ self.taskBuffer.configurator_delete_ddm_endpoints(ddm_endpoints_to_delete)
564
+
565
+ def run(self):
566
+ """
567
+ Principal function
568
+ """
569
+
570
+ """
571
+ site_dump
572
+ endpoint_dump
573
+ schedconfig_dump
574
+ blacklisted_endpoints
575
+ blacklisted_endpoints_read
576
+ rse_usage
577
+ """
578
+
579
+ if self.schedconfig_dump is None:
580
+ self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SCHEDCONFIG}")
581
+ return False
582
+
583
+ if self.endpoint_dump is None:
584
+ self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_DDMENDPOINTS}")
585
+ return False
586
+
587
+ if self.site_dump is None:
588
+ self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SITES}")
589
+ return False
590
+
591
+ # Get pre-processed CRIC dumps
592
+ (
593
+ sites_list,
594
+ panda_sites_list,
595
+ ddm_endpoints_list,
596
+ panda_ddm_relation_dict,
597
+ ) = self.process_site_dumps()
598
+
599
+ # Persist the information to the PanDA DB
600
+ self.taskBuffer.configurator_write_sites(sites_list)
601
+ self.taskBuffer.configurator_write_panda_sites(panda_sites_list)
602
+ self.taskBuffer.configurator_write_ddm_endpoints(ddm_endpoints_list)
603
+ self.taskBuffer.configurator_write_panda_ddm_relations(panda_ddm_relation_dict)
604
+
605
+ # Do a data quality check
606
+ self.consistency_check()
607
+
608
+ return True
609
+
610
+
611
+ class NetworkConfigurator(threading.Thread):
612
+ def __init__(self, taskBuffer, log_stream=None):
613
+ threading.Thread.__init__(self)
614
+
615
+ self.taskBuffer = taskBuffer
616
+ if log_stream:
617
+ self.log_stream = log_stream
618
+ else:
619
+ self.log_stream = _logger
620
+
621
+ if hasattr(panda_config, "NWS_URL"):
622
+ self.NWS_URL = panda_config.NWS_URL
623
+ else:
624
+ self.NWS_URL = "https://atlas-rucio-network-metrics.cern.ch/metrics.json"
625
+
626
+ if hasattr(panda_config, "CRIC_URL_CM"):
627
+ self.CRIC_URL_CM = panda_config.CRIC_URL_CM
628
+ else:
629
+ self.CRIC_URL_CM = "https://atlas-cric.cern.ch/api/core/sitematrix/query/?json&json_pretty=0"
630
+
631
+ def retrieve_data(self):
632
+ self.log_stream.debug("Getting NWS dump...")
633
+ self.nws_dump = aux.get_dump(self.NWS_URL)
634
+ if not self.nws_dump:
635
+ self.log_stream.error("Could not retrieve the NWS data")
636
+ return False
637
+ self.log_stream.debug("Done")
638
+
639
+ self.log_stream.debug("Getting CRIC cost matrix dump...")
640
+ self.CRIC_cm_dump = aux.get_dump(self.CRIC_URL_CM)
641
+ if not self.CRIC_cm_dump:
642
+ self.log_stream.error("Could not retrieve the cost matrix data")
643
+ return False
644
+ self.log_stream.debug("Done")
645
+
646
+ return True
647
+
648
+ def process_nws_dump(self):
649
+ """
650
+ Gets the second generation NWS information dump, filters out irrelevant information
651
+ and prepares it for insertion into the PanDA DB
652
+ """
653
+
654
+ data = []
655
+ sites_list = self.taskBuffer.configurator_read_sites()
656
+ # source or destination shown as UNKNOWN before the rule is processed
657
+ sites_list.add("UNKNOWN")
658
+
659
+ # Ignore outdated values
660
+ latest_validity = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(minutes=30)
661
+
662
+ for src_dst in self.nws_dump:
663
+ try:
664
+ source, destination = src_dst.split(":")
665
+ skip_sites = []
666
+
667
+ # Skip entries with sites not recognized by configurator
668
+ if source not in sites_list:
669
+ skip_sites.append(source)
670
+ if destination not in sites_list:
671
+ skip_sites.append(destination)
672
+ if skip_sites:
673
+ self.log_stream.warning(f"Could not find site(s) {skip_sites} in configurator sites")
674
+ continue
675
+
676
+ except ValueError:
677
+ self.log_stream.error(f"Json wrongly formatted. Expected key with format src:dst, but found key {src_dst}")
678
+ continue
679
+
680
+ # Transferred files
681
+ try:
682
+ done = self.nws_dump[src_dst][FILES][DONE]
683
+ for activity in [PROD_INPUT, PROD_OUTPUT, EXPRESS]:
684
+ if activity not in done:
685
+ continue
686
+ try:
687
+ updated_at = datetime.strptime(done[activity][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
688
+ if updated_at > latest_validity:
689
+ done_1h = done[activity][H1]
690
+ done_6h = done[activity][H6]
691
+ data.append(
692
+ (
693
+ source,
694
+ destination,
695
+ activity + "_done_1h",
696
+ done_1h,
697
+ updated_at,
698
+ )
699
+ )
700
+ data.append(
701
+ (
702
+ source,
703
+ destination,
704
+ activity + "_done_6h",
705
+ done_6h,
706
+ updated_at,
707
+ )
708
+ )
709
+ except (KeyError, ValueError):
710
+ self.log_stream.debug(f"Entry {done} ({source}->{destination}) key {activity} does not follow standards")
711
+ continue
712
+ except KeyError:
713
+ pass
714
+
715
+ # Queued files
716
+ try:
717
+ queued = self.nws_dump[src_dst][FILES][QUEUED]
718
+ for activity in [PROD_INPUT, PROD_OUTPUT, EXPRESS]:
719
+ if activity not in queued:
720
+ continue
721
+ try:
722
+ updated_at = datetime.strptime(queued[activity][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
723
+ if updated_at > latest_validity:
724
+ nqueued = queued[activity][LATEST]
725
+ data.append(
726
+ (
727
+ source,
728
+ destination,
729
+ activity + "_queued",
730
+ nqueued,
731
+ updated_at,
732
+ )
733
+ )
734
+ except (KeyError, ValueError):
735
+ self.log_stream.error(f"Entry {queued} ({source}->{destination}) key {activity} does not follow standards")
736
+ continue
737
+ except KeyError:
738
+ pass
739
+
740
+ # MBps for Rucio, FAX, PerfSonar
741
+ try:
742
+ mbps = self.nws_dump[src_dst][MBPS]
743
+ for system in mbps:
744
+ try:
745
+ updated_at = datetime.strptime(mbps[system][TIMESTAMP], "%Y-%m-%dT%H:%M:%S")
746
+ except ValueError:
747
+ self.log_stream.debug(f"Entry {mbps} has wrong timestamp for system {system}")
748
+ if updated_at > latest_validity:
749
+ for duration in [H1, D1, W1]:
750
+ try:
751
+ mbps_entry = mbps[system][duration]
752
+ data.append(
753
+ (
754
+ source,
755
+ destination,
756
+ f"{system}_mbps_{duration}",
757
+ mbps_entry,
758
+ updated_at,
759
+ )
760
+ )
761
+ except KeyError:
762
+ self.log_stream.debug(
763
+ f"Entry {mbps} ({source}->{destination}) system {system} duration {duration} not available or wrongly formatted"
764
+ )
765
+ self.log_stream.debug(sys.exc_info())
766
+ except KeyError:
767
+ pass
768
+
769
+ return data
770
+
771
+ def process_CRIC_cm_dump(self):
772
+ """
773
+ Gets the CRIC CM information dump, filters out irrelevant information
774
+ and prepares it for insertion into the PanDA DB
775
+ """
776
+ data = []
777
+ sites_list = self.taskBuffer.configurator_read_sites()
778
+
779
+ for entry in self.CRIC_cm_dump:
780
+ self.log_stream.debug(f"Processing CRIC CM entry {entry}")
781
+
782
+ try:
783
+ src = entry["src"]
784
+ dst = entry["dst"]
785
+ closeness = entry["closeness"]
786
+ ts = datetime.now()
787
+
788
+ # filter out sites that are not in CRIC
789
+ skip_sites = []
790
+ if src not in sites_list:
791
+ skip_sites.append(src)
792
+ if dst not in sites_list:
793
+ skip_sites.append(dst)
794
+ if skip_sites:
795
+ self.log_stream.warning(f"Could not find site(s) {skip_sites} in configurator sites")
796
+ continue
797
+
798
+ # Skip broken entries (protection against errors in CRIC)
799
+ if not src or not dst:
800
+ continue
801
+
802
+ # Prepare data for bulk upserts
803
+ data.append((src, dst, "AGIS_closeness", closeness, ts))
804
+
805
+ except KeyError:
806
+ self.log_stream.warning(f"CRIC CM entry {entry} does not contain one or more of the keys src/dst/closeness")
807
+ continue
808
+
809
+ return data
810
+
811
+ def run(self):
812
+ """
813
+ Principal function
814
+ """
815
+
816
+ # Process and store the NWS information (NWS=Network Weather Service)
817
+ data_nws = self.process_nws_dump()
818
+ if not data_nws:
819
+ self.log_stream.error("Could not retrieve any data from the NWS!")
820
+
821
+ # Process and store the CRIC connectivity information
822
+ data_CRIC_cm = self.process_CRIC_cm_dump()
823
+ if not data_CRIC_cm:
824
+ self.log_stream.error("Could not retrieve any data from the CRIC Cost Matrix!")
825
+
826
+ data_combined = data_nws + data_CRIC_cm
827
+ if data_combined:
828
+ # Insert the new data
829
+ self.taskBuffer.insertNetworkMatrixData(data_combined)
830
+ # Do some cleanup of old data
831
+ self.taskBuffer.deleteOldNetworkData()
832
+ return True
833
+ else:
834
+ return False
835
+
836
+
837
+ class SchedconfigJsonDumper(threading.Thread):
838
+ """
839
+ Downloads the CRIC schedconfig dump and stores it in the DB, one row per queue
840
+ """
841
+
842
+ def __init__(self, taskBuffer, log_stream=None):
843
+ """
844
+ Initialization and configuration
845
+ """
846
+ threading.Thread.__init__(self)
847
+
848
+ self.taskBuffer = taskBuffer
849
+ if log_stream:
850
+ self.log_stream = log_stream
851
+ else:
852
+ self.log_stream = _logger
853
+
854
+ if hasattr(panda_config, "CRIC_URL_SCHEDCONFIG"):
855
+ self.CRIC_URL_SCHEDCONFIG = panda_config.CRIC_URL_SCHEDCONFIG
856
+ else:
857
+ self.CRIC_URL_SCHEDCONFIG = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json"
858
+
859
+ self.log_stream.debug("Getting schedconfig dump...")
860
+ self.schedconfig_dump = aux.get_dump(self.CRIC_URL_SCHEDCONFIG)
861
+ self.log_stream.debug("Done")
862
+
863
+ def run(self):
864
+ """
865
+ Principal function
866
+ """
867
+ if self.schedconfig_dump is None:
868
+ self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_SCHEDCONFIG}")
869
+ return False
870
+
871
+ return self.taskBuffer.upsertQueuesInJSONSchedconfig(self.schedconfig_dump)
872
+
873
+
874
+ class SWTagsDumper(threading.Thread):
875
+ """
876
+ Downloads the CRIC tags dump, flattens it out and stores it in the DB, one row per queue
877
+ """
878
+
879
+ def __init__(self, taskBuffer, log_stream=None):
880
+ """
881
+ Initialization and configuration
882
+ """
883
+ threading.Thread.__init__(self)
884
+
885
+ self.taskBuffer = taskBuffer
886
+ if log_stream:
887
+ self.log_stream = log_stream
888
+ else:
889
+ self.log_stream = _logger
890
+
891
+ if hasattr(panda_config, "CRIC_URL_TAGS"):
892
+ self.CRIC_URL_TAGS = panda_config.CRIC_URL_TAGS
893
+ else:
894
+ self.CRIC_URL_TAGS = "https://atlas-cric.cern.ch/api/atlas/pandaqueue/query/?json&preset=tags"
895
+
896
+ self.log_stream.debug("Getting tags dump...")
897
+ self.tags_dump = aux.get_dump(self.CRIC_URL_TAGS)
898
+ self.log_stream.debug("Done")
899
+
900
+ def run(self):
901
+ """
902
+ Principal function
903
+ """
904
+ if self.tags_dump is None:
905
+ self.log_stream.error(f"SKIPPING RUN. Failed to download {self.CRIC_URL_TAGS}")
906
+ return False
907
+
908
+ return self.taskBuffer.loadSWTags(self.tags_dump)