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,874 @@
1
+ import datetime
2
+ import json
3
+ import re
4
+ import sys
5
+ from typing import Dict
6
+
7
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
8
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
9
+
10
+ from pandaserver.config import panda_config
11
+ from pandaserver.srvcore import CoreUtils
12
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule
13
+ from pandaserver.taskbuffer.JobSpec import JobSpec, get_task_queued_time
14
+
15
+
16
+ # Module class to define metrics related methods
17
+ class MetricsModule(BaseModule):
18
+ # constructor
19
+ def __init__(self, log_stream: LogWrapper):
20
+ super().__init__(log_stream)
21
+
22
+ # set job or task metrics
23
+ def set_workload_metrics(self, jedi_task_id: int, panda_id: int | None, metrics: dict, use_commit: bool = True) -> bool:
24
+ """
25
+ Set job or task metrics
26
+
27
+ :param jedi_task_id: jediTaskID
28
+ :param panda_id: PandaID. None to set task
29
+ :param metrics: metrics data
30
+ :param use_commit: use commit
31
+ :return: True if success
32
+ """
33
+ comment = " /* DBProxy.set_workload_metrics */"
34
+ if panda_id is not None:
35
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} PandaID={panda_id}")
36
+ else:
37
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
38
+ tmp_log.debug("start")
39
+ try:
40
+ if panda_id is not None:
41
+ table_name = "Job_Metrics"
42
+ var_map = {":jediTaskID": jedi_task_id, ":PandaID": panda_id}
43
+ else:
44
+ table_name = "Task_Metrics"
45
+ var_map = {":jediTaskID": jedi_task_id}
46
+ # check if data is already there
47
+ sql_check = f"SELECT data FROM {panda_config.schemaPANDA}.{table_name} WHERE jediTaskID=:jediTaskID "
48
+ if panda_id is not None:
49
+ sql_check += "AND PandaID=:PandaID "
50
+ # insert data
51
+ sql_insert = f"INSERT INTO {panda_config.schemaPANDA}.{table_name} "
52
+ if panda_id is not None:
53
+ sql_insert += "(jediTaskID,PandaID,creationTime,modificationTime,data) VALUES(:jediTaskID,:PandaID,CURRENT_DATE,CURRENT_DATE,:data) "
54
+ else:
55
+ sql_insert += "(jediTaskID,creationTime,modificationTime,data) VALUES(:jediTaskID,CURRENT_DATE,CURRENT_DATE,:data) "
56
+ # update data
57
+ sql_update = f"UPDATE {panda_config.schemaPANDA}.{table_name} SET modificationTime=CURRENT_DATE,data=:data WHERE jediTaskID=:jediTaskID "
58
+ if panda_id is not None:
59
+ sql_update += "AND PandaID=:PandaID "
60
+ # start transaction
61
+ if use_commit:
62
+ self.conn.begin()
63
+ # check if data is already there
64
+ self.cur.execute(sql_check + comment, var_map)
65
+ # read data
66
+ tmp_data = None
67
+ for (clob_data,) in self.cur:
68
+ try:
69
+ tmp_data = clob_data.read()
70
+ except AttributeError:
71
+ tmp_data = str(clob_data)
72
+ break
73
+ if not tmp_data:
74
+ # insert new data
75
+ var_map[":data"] = json.dumps(metrics, cls=CoreUtils.NonJsonObjectEncoder)
76
+ self.cur.execute(sql_insert + comment, var_map)
77
+ tmp_log.debug("inserted")
78
+ else:
79
+ # update existing data
80
+ tmp_data = json.loads(tmp_data, object_hook=CoreUtils.as_python_object)
81
+ tmp_data.update(metrics)
82
+ var_map[":data"] = json.dumps(tmp_data, cls=CoreUtils.NonJsonObjectEncoder)
83
+ self.cur.execute(sql_update + comment, var_map)
84
+ tmp_log.debug("updated")
85
+ if use_commit:
86
+ # commit
87
+ if not self._commit():
88
+ raise RuntimeError("Commit error")
89
+ tmp_log.debug("done")
90
+ return True
91
+ except Exception:
92
+ # roll back
93
+ self._rollback()
94
+ # error
95
+ self.dump_error_message(tmp_log)
96
+ return False
97
+
98
+ # get job or task metrics
99
+ def get_workload_metrics(self, jedi_task_id: int, panda_id: int = None) -> tuple[bool, dict | None]:
100
+ """
101
+ Get job metrics or task metrics
102
+
103
+ :param jedi_task_id: jediTaskID
104
+ :param panda_id: PandaID. None to get task metrics
105
+ :return: (False, None) if failed, otherwise (True, metrics)
106
+ """
107
+ comment = " /* DBProxy.get_workload_metrics */"
108
+ if panda_id is not None:
109
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} PandaID={panda_id}")
110
+ else:
111
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
112
+ tmp_log.debug("start")
113
+ try:
114
+ if panda_id is not None:
115
+ table_name = "Job_Metrics"
116
+ var_map = {":jediTaskID": jedi_task_id, ":PandaID": panda_id}
117
+ else:
118
+ table_name = "Task_Metrics"
119
+ var_map = {":jediTaskID": jedi_task_id}
120
+ # get data
121
+ sql_get = f"SELECT data FROM {panda_config.schemaPANDA}.{table_name} WHERE jediTaskID=:jediTaskID "
122
+ if panda_id is not None:
123
+ sql_get += "AND PandaID=:PandaID "
124
+ self.cur.execute(sql_get + comment, var_map)
125
+ # read data
126
+ metrics = None
127
+ for (clob_data,) in self.cur:
128
+ try:
129
+ metrics = clob_data.read()
130
+ except AttributeError:
131
+ metrics = str(clob_data)
132
+ break
133
+ if metrics is not None:
134
+ metrics = json.loads(metrics, object_hook=CoreUtils.as_python_object)
135
+ tmp_log.debug(f"got {sys.getsizeof(metrics)} bytes")
136
+ else:
137
+ tmp_log.debug("no data")
138
+ return True, metrics
139
+ except Exception:
140
+ # error
141
+ self.dump_error_message(tmp_log)
142
+ return False, None
143
+
144
+ # get jobs' metrics in a task
145
+ def get_jobs_metrics_in_task(self, jedi_task_id: int) -> tuple[bool, list | None]:
146
+ """
147
+ Get metrics of jobs in a task
148
+
149
+ :param jedi_task_id: jediTaskID
150
+ :return: (False, None) if failed, otherwise (True, list of [PandaID, metrics])
151
+ """
152
+ comment = " /* DBProxy.get_jobs_metrics_in_task */"
153
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
154
+ tmp_log.debug("start")
155
+ try:
156
+ var_map = {":jediTaskID": jedi_task_id}
157
+ # get data
158
+ sql_get = f"SELECT PandaID,data FROM {panda_config.schemaPANDA}.Job_Metrics WHERE jediTaskID=:jediTaskID "
159
+ self.cur.execute(sql_get + comment, var_map)
160
+ # read data
161
+ metrics_list = []
162
+ for panda_id, clob_data in self.cur:
163
+ try:
164
+ tmp_data = clob_data.read()
165
+ except AttributeError:
166
+ tmp_data = str(clob_data)
167
+ metrics_list.append([panda_id, json.loads(tmp_data, object_hook=CoreUtils.as_python_object)])
168
+ tmp_log.debug(f"got metrics for {len(metrics_list)} jobs")
169
+ return True, metrics_list
170
+ except Exception:
171
+ # error
172
+ self.dump_error_message(tmp_log)
173
+ return False, None
174
+
175
+ # update task queued and activated times
176
+ def update_task_queued_activated_times(self, jedi_task_id: int) -> None:
177
+ """
178
+ Update task queued time depending on the number of inputs to be processed. Update queued and activated times if they were None and inputs become available.
179
+ Set None if no input is available and record the queuing duration to task metrics.
180
+
181
+ :param jedi_task_id: task's jediTaskID
182
+ """
183
+ comment = " /* DBProxy.update_task_queued_activated_times */"
184
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
185
+ tmp_log.debug("start")
186
+ # check if the task is queued
187
+ sql_check = (
188
+ f"SELECT status,oldStatus,queuedTime,activatedTime,currentPriority,gshare FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
189
+ )
190
+ var_map = {":jediTaskID": jedi_task_id}
191
+ self.cur.execute(sql_check + comment, var_map)
192
+ res = self.cur.fetchone()
193
+ if not res:
194
+ # not found
195
+ tmp_log.debug("task not found")
196
+ return
197
+ (task_status, task_old_status, queued_time, activated_time, current_priority, global_share) = res
198
+ has_input = False
199
+ active_status_list = ("ready", "running", "scouting", "scouted")
200
+ if task_status in active_status_list or (task_old_status in active_status_list and task_status == "pending"):
201
+ # check if the task has unprocessed inputs
202
+ sql_input = (
203
+ f"SELECT 1 FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2) AND masterID IS NULL "
204
+ "AND nFilesToBeUsed>0 AND nFilesToBeUSed>nFilesUsed "
205
+ )
206
+ var_map = {":jediTaskID": jedi_task_id, ":type1": "input", ":type2": "pseudo_input"}
207
+ self.cur.execute(sql_input + comment, var_map)
208
+ res = self.cur.fetchone()
209
+ has_input = res is not None
210
+ # set queued time if it was None and inputs are available
211
+ if has_input:
212
+ if queued_time is None:
213
+ # update queued time
214
+ tmp_log.debug(f"set queued time when task is {task_status}")
215
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET queuedTime=CURRENT_DATE WHERE jediTaskID=:jediTaskID AND queuedTime IS NULL "
216
+ var_map = {":jediTaskID": jedi_task_id}
217
+ self.cur.execute(sql_update + comment, var_map)
218
+ n_row = self.cur.rowcount
219
+ if n_row > 0 and activated_time is None:
220
+ # set activated time
221
+ tmp_log.debug(f"set activated time when task is {task_status}")
222
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET activatedTime=CURRENT_DATE WHERE jediTaskID=:jediTaskID AND activatedTime IS NULL "
223
+ var_map = {":jediTaskID": jedi_task_id}
224
+ self.cur.execute(sql_update + comment, var_map)
225
+ else:
226
+ tmp_log.debug(f"keep current queued time {queued_time.strftime('%Y-%m-%d %H:%M:%S')}")
227
+ # record queuing duration since the task has no more input to process
228
+ if queued_time is not None and not has_input:
229
+ # unset queued time
230
+ tmp_log.debug(f"unset queued time and record duration when task is {task_status}")
231
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET queuedTime=NULL WHERE jediTaskID=:jediTaskID AND queuedTime IS NOT NULL "
232
+ var_map = {":jediTaskID": jedi_task_id}
233
+ self.cur.execute(sql_update + comment, var_map)
234
+ # get task metrics dict
235
+ tmp_success, task_metrics = self.get_workload_metrics(jedi_task_id, None)
236
+ if not tmp_success:
237
+ err_str = f"failed to get task metrics for jediTaskId={jedi_task_id}"
238
+ tmp_log.error(err_str)
239
+ return
240
+ # new
241
+ if not task_metrics:
242
+ task_metrics = {}
243
+ # add duration
244
+ task_metrics.setdefault("queuingPeriods", [])
245
+ if len(task_metrics["queuingPeriods"]) < 10000:
246
+ task_metrics["queuingPeriods"].append({"start": queued_time, "end": naive_utcnow(), "status": task_status})
247
+ tmp_success = self.set_workload_metrics(jedi_task_id, None, task_metrics, False)
248
+ if not tmp_success:
249
+ err_str = f"failed to update task metrics for jediTaskId={jedi_task_id}"
250
+ tmp_log.error(err_str)
251
+ return
252
+ else:
253
+ tmp_log.debug("skipped since queuing period list is too long")
254
+ tmp_log.debug(f"done in {task_status}")
255
+
256
+ # record job queuing period
257
+ def record_job_queuing_period(self, panda_id: int, job_spec: JobSpec = None) -> bool | None:
258
+ """
259
+ Record queuing period in job metrics. Skip if job.jobMetrics doesn't contain task queued time
260
+
261
+ :param panda_id: Job's PandaID
262
+ :param job_spec: job spec. None to get it from the database
263
+ :return: True if success. False if failed. None if skipped
264
+ """
265
+ comment = " /* DBProxy.record_job_queuing_period */"
266
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
267
+ tmp_log.debug(f"start with job spec: {job_spec is None}")
268
+ # get task queued time
269
+ if job_spec is None:
270
+ sql_check = f"SELECT jediTaskID,jobStatus,specialHandling FROM {panda_config.schemaPANDA}.jobsActive4 WHERE PandaID=:PandaID "
271
+ var_map = {":PandaID": panda_id}
272
+ self.cur.execute(sql_check + comment, var_map)
273
+ res = self.cur.fetchone()
274
+ if not res:
275
+ # not found
276
+ tmp_log.debug("job not found")
277
+ return None
278
+ jedi_task_id, job_status, tmp_str = res
279
+ else:
280
+ jedi_task_id = job_spec.jediTaskID
281
+ job_status = job_spec.jobStatus
282
+ tmp_str = job_spec.specialHandling
283
+ task_queued_time = get_task_queued_time(tmp_str)
284
+ # record queuing duration
285
+ if jedi_task_id and task_queued_time:
286
+ tmp_log.debug(f"to record queuing period")
287
+ # get job metrics dict
288
+ tmp_success, job_metrics = self.get_workload_metrics(jedi_task_id, panda_id)
289
+ if not tmp_success:
290
+ err_str = "Failed to get job metrics "
291
+ tmp_log.error(err_str)
292
+ return False
293
+ # new
294
+ if not job_metrics:
295
+ job_metrics = {}
296
+ # add duration
297
+ if "queuingPeriod" in job_metrics:
298
+ tmp_log.debug("skipped since queuing period already exists")
299
+ return None
300
+ else:
301
+ job_metrics["queuingPeriod"] = {
302
+ "start": task_queued_time,
303
+ "end": naive_utcnow(),
304
+ "status": job_status,
305
+ }
306
+ tmp_success = self.set_workload_metrics(jedi_task_id, panda_id, job_metrics, False)
307
+ if not tmp_success:
308
+ err_str = "Failed to update job metrics"
309
+ tmp_log.error(err_str)
310
+ return False
311
+ tmp_log.debug(f"done in {job_status}")
312
+ return True
313
+ else:
314
+ tmp_log.debug(f"skipped as jediTaskID={jedi_task_id} taskQueuedTime={task_queued_time}")
315
+ return None
316
+
317
+ # unset task active period
318
+ def unset_task_activated_time(self, jedi_task_id: int, task_status: str = None) -> bool | None:
319
+ """
320
+ Unset activated time and record active period in task metrics
321
+
322
+ :param jedi_task_id: task's JediTaskID
323
+ :param task_status: task status. None to get it from the database
324
+ :return: True if success. False if failed. None if skipped
325
+ """
326
+ frozen_status_list = ("done", "finished", "failed", "broken", "paused", "exhausted")
327
+ if task_status is not None and task_status not in frozen_status_list:
328
+ return None
329
+ comment = " /* DBProxy.record_task_active_period */"
330
+ tmp_log = self.create_tagged_logger(comment, f"JediTaskID={jedi_task_id}")
331
+ tmp_log.debug(f"start")
332
+ # get activated time
333
+ sql_check = f"SELECT status,activatedTime FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
334
+ var_map = {":jediTaskID": jedi_task_id}
335
+ self.cur.execute(sql_check + comment, var_map)
336
+ res = self.cur.fetchone()
337
+ if not res:
338
+ # not found
339
+ tmp_log.debug("task not found")
340
+ return None
341
+ task_status, activated_time = res
342
+ # record active duration
343
+ if task_status in frozen_status_list and activated_time:
344
+ tmp_log.debug(f"to record active period when task is {task_status}")
345
+ # get task metrics dict
346
+ tmp_success, task_metrics = self.get_workload_metrics(jedi_task_id, None)
347
+ if not tmp_success:
348
+ err_str = "Failed to get task metrics "
349
+ tmp_log.error(err_str)
350
+ return False
351
+ # new
352
+ if not task_metrics:
353
+ task_metrics = {}
354
+ # add duration
355
+ task_metrics.setdefault("activePeriod", [])
356
+ task_metrics["activePeriod"].append(
357
+ {
358
+ "start": activated_time,
359
+ "end": naive_utcnow(),
360
+ "status": task_status,
361
+ }
362
+ )
363
+ tmp_success = self.set_workload_metrics(jedi_task_id, None, task_metrics, False)
364
+ if not tmp_success:
365
+ err_str = "Failed to update task metrics"
366
+ tmp_log.error(err_str)
367
+ return False
368
+ # unset activated time
369
+ tmp_log.debug(f"unset activated time")
370
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET activatedTime=NULL WHERE jediTaskID=:jediTaskID AND activatedTime IS NOT NULL "
371
+ var_map = {":jediTaskID": jedi_task_id}
372
+ self.cur.execute(sql_update + comment, var_map)
373
+ tmp_log.debug(f"done in {task_status}")
374
+ return True
375
+
376
+ # check failure count due to corrupted files
377
+ def checkFailureCountWithCorruptedFiles(self, jediTaskID, pandaID):
378
+ comment = " /* DBProxy.checkFailureCountWithCorruptedFiles */"
379
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} pandaID={pandaID}")
380
+ # sql to failure counts
381
+ sqlBD = "SELECT f2.lfn,COUNT(*) FROM ATLAS_PANDA.filesTable4 f1, ATLAS_PANDA.filesTable4 f2 "
382
+ sqlBD += "WHERE f1.PandaID=:PandaID AND f1.type=:type AND f1.status=:status "
383
+ sqlBD += "AND f2.lfn=f1.lfn AND f2.type=:type AND f2.status=:status AND f2.jediTaskID=:jediTaskID "
384
+ sqlBD += "GROUP BY f2.lfn "
385
+ varMap = {}
386
+ varMap[":jediTaskID"] = jediTaskID
387
+ varMap[":PandaID"] = pandaID
388
+ varMap[":status"] = "corrupted"
389
+ varMap[":type"] = "zipinput"
390
+ self.cur.execute(sqlBD + comment, varMap)
391
+ resBD = self.cur.fetchall()
392
+ tooMany = False
393
+ for lfn, nFailed in resBD:
394
+ tmp_log.debug(f"{nFailed} failures with {lfn}")
395
+ if nFailed >= 3:
396
+ tooMany = True
397
+ tmp_log.debug(f"too many failures : {tooMany}")
398
+ return tooMany
399
+
400
+ # calculate failure metrics, such as single failure rate and failed HEPScore, for a task
401
+ def get_task_failure_metrics(self, task_id, use_commit=True):
402
+ comment = " /* JediDBProxy.get_task_failure_metrics */"
403
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={task_id}")
404
+ tmp_log.debug("start")
405
+ try:
406
+ # start transaction
407
+ if use_commit:
408
+ self.conn.begin()
409
+ # sql to get metrics
410
+ sql = (
411
+ f"SELECT SUM(is_finished),SUM(is_failed),SUM(HS06SEC*is_finished),SUM(HS06SEC*is_failed) "
412
+ f"FROM ("
413
+ f"SELECT PandaID, HS06SEC, CASE WHEN jobStatus='finished' THEN 1 ELSE 0 END is_finished, "
414
+ f"CASE WHEN jobStatus='failed' THEN 1 ELSE 0 END is_failed "
415
+ f"FROM {panda_config.schemaPANDA}.jobsArchived4 "
416
+ f"WHERE jediTaskID=:jediTaskID AND prodSourceLabel IN (:prodSourceLabel1,:prodSourceLabel2) "
417
+ f"UNION "
418
+ f"SELECT PandaID, HS06SEC, CASE WHEN jobStatus='finished' THEN 1 ELSE 0 END is_finished, "
419
+ f"CASE WHEN jobStatus='failed' THEN 1 ELSE 0 END is_failed "
420
+ f"FROM {panda_config.schemaPANDAARCH}.jobsArchived "
421
+ f"WHERE jediTaskID=:jediTaskID AND prodSourceLabel IN (:prodSourceLabel1,:prodSourceLabel2) "
422
+ f")"
423
+ )
424
+ var_map = {
425
+ ":jediTaskID": task_id,
426
+ ":prodSourceLabel1": "managed",
427
+ ":prodSourceLabel2": "user",
428
+ }
429
+ self.cur.execute(sql + comment, var_map)
430
+ num_finished, num_failed, good_hep_score_sec, bad_hep_score_sec = self.cur.fetchone()
431
+ ret_dict = {
432
+ "num_failed": num_failed,
433
+ "single_failure_rate": (
434
+ round(num_failed / (num_finished + num_failed), 3)
435
+ if num_finished is not None and num_failed is not None and num_finished + num_failed
436
+ else None
437
+ ),
438
+ "failed_hep_score_hour": int(bad_hep_score_sec / 60 / 60) if bad_hep_score_sec is not None else None,
439
+ "failed_hep_score_ratio": (
440
+ round(bad_hep_score_sec / (good_hep_score_sec + bad_hep_score_sec), 3)
441
+ if good_hep_score_sec is not None and bad_hep_score_sec is not None and good_hep_score_sec + bad_hep_score_sec
442
+ else None
443
+ ),
444
+ }
445
+ # commit
446
+ if use_commit:
447
+ if not self._commit():
448
+ raise RuntimeError("Commit error")
449
+ # return
450
+ tmp_log.debug(f"got {ret_dict}")
451
+ return ret_dict
452
+ except Exception:
453
+ # roll back
454
+ if use_commit:
455
+ self._rollback()
456
+ # error
457
+ self.dump_error_message(tmp_log)
458
+ return None
459
+
460
+ # get averaged disk IO
461
+ def getAvgDiskIO_JEDI(self):
462
+ comment = " /* JediDBProxy.getAvgDiskIO_JEDI */"
463
+ tmp_log = self.create_tagged_logger(comment)
464
+ tmp_log.debug("start")
465
+ try:
466
+ # sql
467
+ sql = f"SELECT sum(prorated_diskio_avg * njobs) / sum(njobs), computingSite FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS "
468
+ sql += "WHERE jobStatus=:jobStatus GROUP BY computingSite "
469
+ var_map = dict()
470
+ var_map[":jobStatus"] = "running"
471
+ # begin transaction
472
+ self.conn.begin()
473
+ self.cur.execute(sql + comment, var_map)
474
+ resFL = self.cur.fetchall()
475
+ # commit
476
+ if not self._commit():
477
+ raise RuntimeError("Commit error")
478
+ ret_map = dict()
479
+ for avg, computing_site in resFL:
480
+ if avg:
481
+ avg = float(avg)
482
+ ret_map[computing_site] = avg
483
+ tmp_log.debug("done")
484
+ return ret_map
485
+ except Exception:
486
+ # roll back
487
+ self._rollback()
488
+ # error
489
+ self.dump_error_message(tmp_log)
490
+ return {}
491
+
492
+ # get carbon footprint for a task, the level has to be 'regional' or 'global'. If misspelled, it defaults to 'global'
493
+ def get_task_carbon_footprint(self, jedi_task_id, level):
494
+ comment = " /* JediDBProxy.get_task_carbon_footprint */"
495
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} n_files={level}")
496
+ tmp_log.debug("start")
497
+
498
+ if level == "regional":
499
+ gco2_column = "GCO2_REGIONAL"
500
+ else:
501
+ gco2_column = "GCO2_GLOBAL"
502
+
503
+ try:
504
+ sql = (
505
+ "SELECT jobstatus, SUM(sum_gco2) FROM ( "
506
+ " SELECT jobstatus, SUM({gco2_column}) sum_gco2 FROM {active_schema}.jobsarchived4 "
507
+ " WHERE jeditaskid =:jeditaskid "
508
+ " GROUP BY jobstatus "
509
+ " UNION "
510
+ " SELECT jobstatus, SUM({gco2_column}) sum_gco2 FROM {archive_schema}.jobsarchived "
511
+ " WHERE jeditaskid =:jeditaskid "
512
+ " GROUP BY jobstatus)"
513
+ "GROUP BY jobstatus".format(gco2_column=gco2_column, active_schema=panda_config.schemaJEDI, archive_schema=panda_config.schemaPANDAARCH)
514
+ )
515
+ var_map = {":jeditaskid": jedi_task_id}
516
+
517
+ # start transaction
518
+ self.conn.begin()
519
+ self.cur.execute(sql + comment, var_map)
520
+ results = self.cur.fetchall()
521
+
522
+ footprint = {"total": 0}
523
+ data = False
524
+ for job_status, g_co2 in results:
525
+ if not g_co2:
526
+ g_co2 = 0
527
+ else:
528
+ data = True
529
+ footprint[job_status] = g_co2
530
+ footprint["total"] += g_co2
531
+
532
+ # commit
533
+ if not self._commit():
534
+ raise RuntimeError("Commit error")
535
+
536
+ tmp_log.debug(f"done: {footprint}")
537
+
538
+ if not data:
539
+ return None
540
+
541
+ return footprint
542
+ except Exception:
543
+ # roll back
544
+ self._rollback()
545
+ # error
546
+ self.dump_error_message(tmp_log)
547
+ return None
548
+
549
+ # get job statistics with work queue
550
+ def getJobStatisticsWithWorkQueue_JEDI(self, vo, prodSourceLabel, minPriority=None, cloud=None):
551
+ comment = " /* DBProxy.getJobStatisticsWithWorkQueue_JEDI */"
552
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} cloud={cloud}")
553
+ tmpLog.debug(f"start minPriority={minPriority}")
554
+ sql0 = "SELECT computingSite,cloud,jobStatus,workQueue_ID,COUNT(*) FROM %s "
555
+ sql0 += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
556
+ if cloud is not None:
557
+ sql0 += "AND cloud=:cloud "
558
+ tmpPrioMap = {}
559
+ if minPriority is not None:
560
+ sql0 += "AND currentPriority>=:minPriority "
561
+ tmpPrioMap[":minPriority"] = minPriority
562
+ sql0 += "GROUP BY computingSite,cloud,prodSourceLabel,jobStatus,workQueue_ID "
563
+ sqlMV = sql0
564
+ sqlMV = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sqlMV)
565
+ sqlMV = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sqlMV)
566
+ tables = [f"{panda_config.schemaPANDA}.jobsActive4", f"{panda_config.schemaPANDA}.jobsDefined4"]
567
+ if minPriority is not None:
568
+ # read the number of running jobs with prio<=MIN
569
+ tables.append(f"{panda_config.schemaPANDA}.jobsActive4")
570
+ sqlMVforRun = re.sub("currentPriority>=", "currentPriority<=", sqlMV)
571
+ varMap = {}
572
+ varMap[":vo"] = vo
573
+ varMap[":prodSourceLabel"] = prodSourceLabel
574
+ if cloud is not None:
575
+ varMap[":cloud"] = cloud
576
+ for tmpPrio in tmpPrioMap.keys():
577
+ varMap[tmpPrio] = tmpPrioMap[tmpPrio]
578
+ returnMap = {}
579
+ try:
580
+ iActive = 0
581
+ for table in tables:
582
+ # start transaction
583
+ self.conn.begin()
584
+ # select
585
+ self.cur.arraysize = 10000
586
+ useRunning = None
587
+ if table == f"{panda_config.schemaPANDA}.jobsActive4":
588
+ mvTableName = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
589
+ # first count non-running and then running if minPriority is specified
590
+ if minPriority is not None:
591
+ if iActive == 0:
592
+ useRunning = False
593
+ else:
594
+ useRunning = True
595
+ iActive += 1
596
+ if useRunning in [None, False]:
597
+ sqlExeTmp = (sqlMV + comment) % mvTableName
598
+ else:
599
+ sqlExeTmp = (sqlMVforRun + comment) % mvTableName
600
+ else:
601
+ sqlExeTmp = (sql0 + comment) % table
602
+ self.cur.execute(sqlExeTmp, varMap)
603
+ res = self.cur.fetchall()
604
+ # commit
605
+ if not self._commit():
606
+ raise RuntimeError("Commit error")
607
+ # create map
608
+ for computingSite, cloud, jobStatus, workQueue_ID, nCount in res:
609
+ # count the number of non-running with prio>=MIN
610
+ if useRunning is True and jobStatus != "running":
611
+ continue
612
+ # count the number of running with prio<=MIN
613
+ if useRunning is False and jobStatus == "running":
614
+ continue
615
+ # add site
616
+ if computingSite not in returnMap:
617
+ returnMap[computingSite] = {}
618
+ # add workQueue
619
+ if workQueue_ID not in returnMap[computingSite]:
620
+ returnMap[computingSite][workQueue_ID] = {}
621
+ # add jobstatus
622
+ if jobStatus not in returnMap[computingSite][workQueue_ID]:
623
+ returnMap[computingSite][workQueue_ID][jobStatus] = 0
624
+ # add
625
+ returnMap[computingSite][workQueue_ID][jobStatus] += nCount
626
+ # return
627
+ tmpLog.debug("done")
628
+ return True, returnMap
629
+ except Exception:
630
+ # roll back
631
+ self._rollback()
632
+ # error
633
+ self.dump_error_message(tmpLog)
634
+ return False, {}
635
+
636
+ # get core statistics with VO and prodSourceLabel
637
+ def get_core_statistics(self, vo: str, prod_source_label: str) -> [bool, dict]:
638
+ comment = " /* DBProxy.get_core_statistics */"
639
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prod_source_label}")
640
+ tmpLog.debug("start")
641
+ sql0 = f"SELECT /*+ RESULT_CACHE */ computingSite,jobStatus,SUM(num_of_cores) FROM {panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS "
642
+ sql0 += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
643
+ sql0 += "GROUP BY computingSite,cloud,prodSourceLabel,jobStatus "
644
+ var_map = {":vo": vo, ":prodSourceLabel": prod_source_label}
645
+ return_map = {}
646
+ try:
647
+ self.conn.begin()
648
+ # select
649
+ self.cur.arraysize = 10000
650
+ self.cur.execute(sql0 + comment, var_map)
651
+ res = self.cur.fetchall()
652
+ # commit
653
+ if not self._commit():
654
+ raise RuntimeError("Commit error")
655
+ # create map
656
+ for computing_site, job_status, n_core in res:
657
+ # add site
658
+ return_map.setdefault(computing_site, {})
659
+ # add status
660
+ return_map[computing_site].setdefault(job_status, 0)
661
+ # add num cores
662
+ return_map[computing_site][job_status] += n_core
663
+ # return
664
+ tmpLog.debug("done")
665
+ return True, return_map
666
+ except Exception:
667
+ # roll back
668
+ self._rollback()
669
+ # error
670
+ self.dump_error_message(tmpLog)
671
+ return False, {}
672
+
673
+ # get job statistics by global share
674
+ def getJobStatisticsByGlobalShare(self, vo, exclude_rwq):
675
+ """
676
+ :param vo: Virtual Organization
677
+ :param exclude_rwq: True/False. Indicates whether we want to indicate special workqueues from the statistics
678
+ """
679
+ comment = " /* DBProxy.getJobStatisticsByGlobalShare */"
680
+ tmpLog = self.create_tagged_logger(comment, f" vo={vo}")
681
+ tmpLog.debug("start")
682
+
683
+ # define the var map of query parameters
684
+ var_map = {":vo": vo}
685
+
686
+ # sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
687
+ sql_jt = """
688
+ SELECT /*+ RESULT_CACHE */ computingSite, jobStatus, gShare, SUM(njobs) FROM %s
689
+ WHERE vo=:vo
690
+ """
691
+
692
+ if exclude_rwq:
693
+ sql_jt += f"""
694
+ AND workqueue_id NOT IN
695
+ (SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource')
696
+ """
697
+
698
+ sql_jt += """
699
+ GROUP BY computingSite, jobStatus, gshare
700
+ """
701
+
702
+ tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
703
+
704
+ return_map = {}
705
+ try:
706
+ for table in tables:
707
+ self.cur.arraysize = 10000
708
+ sql_exe = (sql_jt + comment) % table
709
+ self.cur.execute(sql_exe, var_map)
710
+ res = self.cur.fetchall()
711
+
712
+ # create map
713
+ for panda_site, status, gshare, n_count in res:
714
+ # add site
715
+ return_map.setdefault(panda_site, {})
716
+ # add global share
717
+ return_map[panda_site].setdefault(gshare, {})
718
+ # add job status
719
+ return_map[panda_site][gshare].setdefault(status, 0)
720
+ # increase count
721
+ return_map[panda_site][gshare][status] += n_count
722
+
723
+ tmpLog.debug("done")
724
+ return True, return_map
725
+ except Exception:
726
+ self.dump_error_message(tmpLog)
727
+ return False, {}
728
+
729
+ def getJobStatisticsByResourceType(self, workqueue):
730
+ """
731
+ This function will return the job statistics for a particular workqueue, broken down by resource type
732
+ (SCORE, MCORE, etc.)
733
+ :param workqueue: workqueue object
734
+ """
735
+ comment = " /* DBProxy.getJobStatisticsByResourceType */"
736
+ tmpLog = self.create_tagged_logger(comment, f"workqueue={workqueue}")
737
+ tmpLog.debug("start")
738
+
739
+ # define the var map of query parameters
740
+ var_map = {":vo": workqueue.VO}
741
+
742
+ # sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
743
+ sql_jt = "SELECT /*+ RESULT_CACHE */ jobstatus, resource_type, SUM(njobs) FROM %s WHERE vo=:vo "
744
+
745
+ if workqueue.is_global_share:
746
+ sql_jt += "AND gshare=:gshare "
747
+ sql_jt += f"AND workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource') "
748
+ var_map[":gshare"] = workqueue.queue_name
749
+ else:
750
+ sql_jt += "AND workqueue_id=:workqueue_id "
751
+ var_map[":workqueue_id"] = workqueue.queue_id
752
+
753
+ sql_jt += "GROUP BY jobstatus, resource_type "
754
+
755
+ tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
756
+
757
+ return_map = {}
758
+ try:
759
+ for table in tables:
760
+ self.cur.arraysize = 10000
761
+ sql_exe = (sql_jt + comment) % table
762
+ self.cur.execute(sql_exe, var_map)
763
+ res = self.cur.fetchall()
764
+
765
+ # create map
766
+ for status, resource_type, n_count in res:
767
+ return_map.setdefault(status, {})
768
+ return_map[status][resource_type] = n_count
769
+
770
+ tmpLog.debug("done")
771
+ return True, return_map
772
+ except Exception:
773
+ self.dump_error_message(tmpLog)
774
+ return False, {}
775
+
776
+ def getJobStatisticsByResourceTypeSite(self, workqueue):
777
+ """
778
+ This function will return the job statistics per site for a particular workqueue, broken down by resource type
779
+ (SCORE, MCORE, etc.)
780
+ :param workqueue: workqueue object
781
+ """
782
+ comment = " /* DBProxy.getJobStatisticsByResourceTypeSite */"
783
+ tmpLog = self.create_tagged_logger(comment, f"workqueue={workqueue}")
784
+ tmpLog.debug("start")
785
+
786
+ # define the var map of query parameters
787
+ var_map = {":vo": workqueue.VO}
788
+
789
+ # sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
790
+ sql_jt = "SELECT /*+ RESULT_CACHE */ jobstatus, resource_type, computingSite, SUM(njobs) FROM %s WHERE vo=:vo "
791
+
792
+ if workqueue.is_global_share:
793
+ sql_jt += "AND gshare=:gshare "
794
+ sql_jt += f"AND workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource') "
795
+ var_map[":gshare"] = workqueue.queue_name
796
+ else:
797
+ sql_jt += "AND workqueue_id=:workqueue_id "
798
+ var_map[":workqueue_id"] = workqueue.queue_id
799
+
800
+ sql_jt += "GROUP BY jobstatus, resource_type, computingSite "
801
+
802
+ tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
803
+
804
+ return_map = {}
805
+ try:
806
+ for table in tables:
807
+ self.cur.arraysize = 10000
808
+ sql_exe = (sql_jt + comment) % table
809
+ self.cur.execute(sql_exe, var_map)
810
+ res = self.cur.fetchall()
811
+
812
+ # create map
813
+ for status, resource_type, computingSite, n_count in res:
814
+ return_map.setdefault(computingSite, {})
815
+ return_map[computingSite].setdefault(resource_type, {})
816
+ return_map[computingSite][resource_type][status] = n_count
817
+
818
+ tmpLog.debug("done")
819
+ return True, return_map
820
+ except Exception:
821
+ self.dump_error_message(tmpLog)
822
+ return False, {}
823
+
824
+ # gets statistics on the number of jobs with a specific status for each nucleus at each site
825
+ def get_num_jobs_with_status_by_nucleus(self, vo: str, job_status: str) -> [bool, Dict[str, Dict[str, int]]]:
826
+ """
827
+ This function will return the number of jobs with a specific status for each nucleus at each site.
828
+
829
+ :param vo: Virtual Organization
830
+ :param job_status: Job status
831
+ :return: True/False and Dictionary with the number of jobs with a specific status for each nucleus at each site
832
+ """
833
+ comment = " /* DBProxy.get_num_jobs_with_status_by_nucleus */"
834
+ tmp_log = self.create_tagged_logger(comment, f" vo={vo} status={job_status}")
835
+ tmp_log.debug("start")
836
+
837
+ # define the var map of query parameters
838
+ var_map = {":vo": vo, ":job_status": job_status}
839
+
840
+ # sql to query on pre-cached job statistics table
841
+ sql_jt = """
842
+ SELECT /*+ RESULT_CACHE */ computingSite, nucleus, SUM(njobs) FROM %s
843
+ WHERE vo=:vo AND jobStatus=:job_status GROUP BY computingSite, nucleus
844
+ """
845
+
846
+ if job_status in ["transferring", "running", "activated" "holding"]:
847
+ table = f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS"
848
+ else:
849
+ table = f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"
850
+
851
+ return_map = {}
852
+ try:
853
+ self.cur.arraysize = 10000
854
+ sql_exe = (sql_jt + comment) % table
855
+ self.cur.execute(sql_exe, var_map)
856
+ res = self.cur.fetchall()
857
+
858
+ # create a dict
859
+ for panda_site, nucleus, n_jobs in res:
860
+ if n_jobs:
861
+ # add site
862
+ return_map.setdefault(panda_site, {})
863
+ # add stat per nucleus
864
+ return_map[panda_site][nucleus] = n_jobs
865
+ tmp_log.debug("done")
866
+ return True, return_map
867
+ except Exception:
868
+ self.dump_error_message(tmp_log)
869
+ return False, {}
870
+
871
+
872
+ # get metrics module
873
+ def get_metrics_module(base_mod) -> MetricsModule:
874
+ return base_mod.get_composite_module("metrics")