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,4272 @@
1
+ import copy
2
+ import datetime
3
+ import glob
4
+ import json
5
+ import os
6
+ import random
7
+ import re
8
+ import time
9
+ from typing import Dict, List, Tuple
10
+
11
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
12
+ from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
13
+
14
+ from pandaserver.config import panda_config
15
+ from pandaserver.srvcore import CoreUtils, srv_msg_utils
16
+ from pandaserver.taskbuffer import EventServiceUtils, PrioUtil
17
+ from pandaserver.taskbuffer.DatasetSpec import DatasetSpec
18
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import (
19
+ BaseModule,
20
+ SQL_QUEUE_TOPIC_async_dataset_update,
21
+ memoize,
22
+ varNUMBER,
23
+ )
24
+ from pandaserver.taskbuffer.FileSpec import FileSpec
25
+ from pandaserver.taskbuffer.JediDatasetSpec import (
26
+ INPUT_TYPES_var_map,
27
+ INPUT_TYPES_var_str,
28
+ JediDatasetSpec,
29
+ )
30
+ from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec
31
+ from pandaserver.taskbuffer.JobSpec import JobSpec
32
+
33
+
34
+ # Module class to define miscellaneous standalone methods that are independent of another module's methods
35
+ class MiscStandaloneModule(BaseModule):
36
+ # constructor
37
+ def __init__(self, log_stream: LogWrapper):
38
+ super().__init__(log_stream)
39
+
40
+ # get PandaIDs with TaskID
41
+ def getPandaIDsWithTaskID(self, jediTaskID: int, scout_only: bool = False, unsuccessful_only: bool = False) -> List[int]:
42
+ """Get PanDA job IDs for a task.
43
+
44
+ Args:
45
+ jediTaskID: JEDI task ID.
46
+ scout_only: When True, return only jobs with the ``sj`` token in the
47
+ comma-separated ``specialHandling`` field.
48
+ unsuccessful_only: When True, return only jobs with status in
49
+ ``failed``, ``cancelled``, or ``closed``.
50
+
51
+ Returns:
52
+ List[int]: PanDA job IDs found in defined, active, and archived tables.
53
+ """
54
+ comment = " /* DBProxy.getPandaIDsWithTaskID */"
55
+ tmp_log = self.create_tagged_logger(comment, f"<jediTaskID={jediTaskID} scout_only={scout_only} unsuccessful_only={unsuccessful_only}>")
56
+ tmp_log.debug("start")
57
+ # SQL
58
+ scout_filter = "AND INSTR(','||NVL(specialHandling, '')||',',',sj,')>0 "
59
+ unsuccessful_filter = "AND jobStatus IN ('failed','cancelled','closed') "
60
+ sql = "SELECT PandaID FROM ATLAS_PANDA.jobsDefined4 "
61
+ sql += "WHERE jediTaskID=:jediTaskID "
62
+ if scout_only:
63
+ sql += scout_filter
64
+ if unsuccessful_only:
65
+ sql += unsuccessful_filter
66
+ sql += "UNION "
67
+ sql += "SELECT PandaID FROM ATLAS_PANDA.jobsActive4 "
68
+ sql += "WHERE jediTaskID=:jediTaskID "
69
+ if scout_only:
70
+ sql += scout_filter
71
+ if unsuccessful_only:
72
+ sql += unsuccessful_filter
73
+ sql += "UNION "
74
+ sql += "SELECT PandaID FROM ATLAS_PANDA.jobsArchived4 "
75
+ sql += "WHERE jediTaskID=:jediTaskID "
76
+ if scout_only:
77
+ sql += scout_filter
78
+ if unsuccessful_only:
79
+ sql += unsuccessful_filter
80
+ varMap = {}
81
+ varMap[":jediTaskID"] = jediTaskID
82
+ try:
83
+ # start transaction
84
+ self.conn.begin()
85
+ # select
86
+ self.cur.arraysize = 1000000
87
+ self.cur.execute(sql + comment, varMap)
88
+ res = self.cur.fetchall()
89
+ # commit
90
+ if not self._commit():
91
+ raise RuntimeError("Commit error")
92
+ retList = []
93
+ for (pandaID,) in res:
94
+ retList.append(pandaID)
95
+
96
+ if scout_only:
97
+ tmp_log.debug(f"found {len(retList)} scout IDs")
98
+ elif unsuccessful_only:
99
+ tmp_log.debug(f"found {len(retList)} unsuccessful IDs")
100
+ else:
101
+ tmp_log.debug(f"found {len(retList)} IDs")
102
+ return retList
103
+ except Exception:
104
+ # roll back
105
+ self._rollback()
106
+ # error
107
+ self.dump_error_message(tmp_log)
108
+ return []
109
+
110
+ # change task priority
111
+ def changeTaskPriorityPanda(self, jediTaskID, newPriority):
112
+ comment = " /* DBProxy.changeTaskPriorityPanda */"
113
+ tmp_log = self.create_tagged_logger(comment, f"<jediTaskID={jediTaskID}>")
114
+ tmp_log.debug(f"newPrio={newPriority}")
115
+ try:
116
+ # sql to update JEDI task table
117
+ sqlT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET currentPriority=:newPriority WHERE jediTaskID=:jediTaskID "
118
+ # sql to update DEFT task table
119
+ schemaDEFT = panda_config.schemaDEFT
120
+ sqlD = f"UPDATE {schemaDEFT}.T_TASK SET current_priority=:newPriority,timestamp=CURRENT_DATE WHERE taskid=:jediTaskID "
121
+ # update job priorities
122
+ sqlJ = "UPDATE ATLAS_PANDA.{0} SET currentPriority=:newPriority WHERE jediTaskID=:jediTaskID "
123
+ # start transaction
124
+ self.conn.begin()
125
+ # select
126
+ self.cur.arraysize = 10
127
+ varMap = {}
128
+ varMap[":jediTaskID"] = jediTaskID
129
+ varMap[":newPriority"] = newPriority
130
+ # update JEDI
131
+ self.cur.execute(sqlT + comment, varMap)
132
+ nRow = self.cur.rowcount
133
+ if nRow == 1:
134
+ # update jobs
135
+ for tableName in ["jobsActive4", "jobsDefined4"]:
136
+ self.cur.execute(sqlJ.format(tableName) + comment, varMap)
137
+ # update DEFT
138
+ self.cur.execute(sqlD + comment, varMap)
139
+ # commit
140
+ if not self._commit():
141
+ raise RuntimeError("Commit error")
142
+ tmp_log.debug(f"done with {nRow}")
143
+ return nRow
144
+ except Exception:
145
+ # roll back
146
+ self._rollback()
147
+ # error
148
+ self.dump_error_message(tmp_log)
149
+ return None
150
+
151
+ # get jediTaskID from taskName
152
+ def getTaskIDwithTaskNameJEDI(self, userName, taskName):
153
+ comment = " /* DBProxy.getTaskIDwithTaskNameJEDI */"
154
+ tmp_log = self.create_tagged_logger(comment, f"<userName={userName} taskName={taskName}")
155
+ tmp_log.debug(f"start")
156
+ try:
157
+ # begin transaction
158
+ self.conn.begin()
159
+ # sql to get jediTaskID
160
+ sqlGF = f"SELECT MAX(jediTaskID) FROM {panda_config.schemaJEDI}.JEDI_Tasks "
161
+ sqlGF += "WHERE userName=:userName AND taskName=:taskName "
162
+ varMap = {}
163
+ varMap[":userName"] = userName
164
+ varMap[":taskName"] = taskName
165
+ self.cur.execute(sqlGF + comment, varMap)
166
+ resFJ = self.cur.fetchone()
167
+ if resFJ is not None:
168
+ (jediTaskID,) = resFJ
169
+ else:
170
+ jediTaskID = None
171
+ # commit
172
+ if not self._commit():
173
+ raise RuntimeError("Commit error")
174
+ tmp_log.debug(f"jediTaskID={jediTaskID}")
175
+ return jediTaskID
176
+ except Exception:
177
+ # roll back
178
+ self._rollback()
179
+ # error
180
+ self.dump_error_message(tmp_log)
181
+ return None
182
+
183
+ # update modificationtime for a jediTaskID to trigger subsequent process
184
+ def updateTaskModTimeJEDI(self, jediTaskID, newStatus):
185
+ comment = " /* DBProxy.updateTaskErrorDialogJEDI */"
186
+ tmp_log = self.create_tagged_logger(comment, f"<jediTaskID={jediTaskID}>")
187
+ tmp_log.debug(f"start")
188
+ try:
189
+ # begin transaction
190
+ self.conn.begin()
191
+ # update mod time
192
+ varMap = {}
193
+ varMap[":jediTaskID"] = jediTaskID
194
+ if newStatus is not None:
195
+ varMap[":newStatus"] = newStatus
196
+ sqlUE = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET "
197
+ sqlUE += "modificationTime=CURRENT_DATE-1,"
198
+ if newStatus is not None:
199
+ sqlUE += "status=:newStatus,oldStatus=NULL,"
200
+ sqlUE = sqlUE[:-1]
201
+ sqlUE += " WHERE jediTaskID=:jediTaskID "
202
+ self.cur.execute(sqlUE + comment, varMap)
203
+ # commit
204
+ if not self._commit():
205
+ raise RuntimeError("Commit error")
206
+ tmp_log.debug(f"done")
207
+ return True
208
+ except Exception:
209
+ # roll back
210
+ self._rollback()
211
+ # error
212
+ self.dump_error_message(tmp_log)
213
+ return False
214
+
215
+ def initialize_cpu_time_task(self, job_id, task_id, site_id, files, active):
216
+ """
217
+ Increases the CPU time of a task
218
+ walltime = basewalltime + cpuefficiency*CPUTime*nEvents/Corepower/Corecount
219
+
220
+ CPU time: execution time per event
221
+ Walltime: time for a job
222
+ Corepower: HS06 score
223
+ Basewalltime: Setup time, time to download, etc. taken by the pilot
224
+ """
225
+ comment = " /* DBProxy.initialize_cpu_time_task */"
226
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={job_id}; jediTaskID={task_id}; siteID={site_id}")
227
+ tmp_log.debug("start")
228
+
229
+ # See if there are successful jobs for this task. If yes, skip this method
230
+ sql = (
231
+ f"SELECT 1 FROM "
232
+ f"(SELECT 1 FROM atlas_panda.jobsarchived4 "
233
+ f"WHERE jeditaskid = :jedi_task_id AND jobstatus = 'finished' AND transformation NOT LIKE '%build%' AND ROWNUM = 1 "
234
+ f"UNION ALL "
235
+ f"SELECT 1 FROM atlas_pandaarch.jobsarchived "
236
+ f"WHERE jeditaskid = :jedi_task_id AND jobstatus = 'finished' AND transformation NOT LIKE '%build%' AND ROWNUM = 1) "
237
+ f"WHERE ROWNUM = 1"
238
+ )
239
+ var_map = {":jedi_task_id": task_id}
240
+ self.cur.execute(sql + comment, var_map)
241
+ exists = False
242
+ if self.cur.fetchone():
243
+ exists = True
244
+
245
+ # if we found a successful job, we skip the CPU time increase
246
+ if exists:
247
+ tmp_log.debug(f"Task {task_id} already has successful jobs, skipping CPU time increase and leaving it up to the scouting mechanism")
248
+ return None, None
249
+
250
+ # Get the site information from schedconfig
251
+ sql = (
252
+ "SELECT /* use_json_type */ sc.data.maxtime, sc.data.corepower, NVL(TO_NUMBER(sc.data.corecount), 1) AS corecount "
253
+ "FROM ATLAS_PANDA.schedconfig_json sc "
254
+ "WHERE sc.panda_queue= :site_id "
255
+ )
256
+
257
+ var_map = {":site_id": site_id}
258
+ self.cur.execute(sql + comment, var_map)
259
+ site_parameters = self.cur.fetchone()
260
+
261
+ if not site_parameters:
262
+ tmp_log.debug(f"No site parameters retrieved for {site_id}")
263
+
264
+ (max_time_site, core_power_site, core_count_site) = site_parameters
265
+ tmp_log.debug(f"site_id {site_id} has parameters: max_time_site {max_time_site}, core_power_site {core_power_site}, core_count_site {core_count_site}")
266
+ if (not max_time_site) or (not core_power_site) or (not core_count_site):
267
+ tmp_log.debug(f"One or more site parameters are not defined for {site_id}... nothing to do")
268
+ return None, None
269
+ else:
270
+ (max_time_site, core_power_site, core_count_site) = (
271
+ int(max_time_site),
272
+ float(core_power_site),
273
+ int(core_count_site),
274
+ )
275
+
276
+ # Get the task information
277
+ sql = """
278
+ SELECT jt.cputime, jt.walltime, jt.basewalltime, jt.cpuefficiency, jt.cputimeunit
279
+ FROM ATLAS_PANDA.jedi_tasks jt
280
+ WHERE jt.jeditaskid=:jeditaskid
281
+ """
282
+ var_map = {":jeditaskid": task_id}
283
+ self.cur.execute(sql + comment, var_map)
284
+ task_parameters = self.cur.fetchone()
285
+
286
+ if not task_parameters:
287
+ tmp_log.debug(f"No task parameters retrieved for jeditaskid {task_id}... nothing to do")
288
+ return None, None
289
+
290
+ (old_cputime, walltime, basewalltime, cpuefficiency, old_cputime_unit) = task_parameters
291
+ if not cpuefficiency or not basewalltime:
292
+ tmp_log.debug(f"CPU efficiency and/or basewalltime are not defined for task {task_id}... nothing to do")
293
+ return None, None
294
+
295
+ tmp_log.debug(
296
+ f"task {task_id} has parameters: cputime {old_cputime} {old_cputime_unit}, walltime {walltime}, "
297
+ f"basewalltime {basewalltime}, cpuefficiency {cpuefficiency}"
298
+ )
299
+
300
+ old_cputime_normalized = None
301
+ if old_cputime is not None:
302
+ if old_cputime_unit == "HS06sPerEvent":
303
+ old_cputime_normalized = old_cputime
304
+ elif old_cputime_unit == "mHS06sPerEvent":
305
+ old_cputime_normalized = old_cputime / 1000 # convert to HS06sPerEvent
306
+
307
+ # Get the file information
308
+ input_types = ("input", "pseudo_input", "pp_input", "trn_log", "trn_output")
309
+ input_files = list(
310
+ filter(
311
+ lambda pandafile: pandafile.type in input_types and re.search("DBRelease", pandafile.lfn) is None,
312
+ files,
313
+ )
314
+ )
315
+ input_fileIDs = [input_file.fileID for input_file in input_files]
316
+ input_datasetIDs = [input_file.datasetID for input_file in input_files]
317
+
318
+ if input_fileIDs:
319
+ var_map = {":taskID": task_id}
320
+
321
+ # Bind the files
322
+ file_var_names_str, file_var_map = get_sql_IN_bind_variables(input_fileIDs, prefix=":file")
323
+ var_map.update(file_var_map)
324
+
325
+ # Bind the datasets
326
+ ds_var_names_str, ds_var_map = get_sql_IN_bind_variables(input_datasetIDs, prefix=":dataset")
327
+ var_map.update(ds_var_map)
328
+
329
+ sql_select = f"""
330
+ SELECT jdc.fileid, jdc.nevents, jdc.startevent, jdc.endevent
331
+ FROM ATLAS_PANDA.JEDI_Dataset_Contents jdc, ATLAS_PANDA.JEDI_Datasets jd
332
+ WHERE jdc.JEDITaskID = :taskID
333
+ AND jdc.datasetID IN ({ds_var_names_str})
334
+ AND jdc.fileID IN ({file_var_names_str})
335
+ AND jd.datasetID = jdc.datasetID
336
+ AND jd.masterID IS NULL
337
+ """
338
+ self.cur.execute(sql_select + comment, var_map)
339
+
340
+ result_list = self.cur.fetchall()
341
+ n_events_total = 0
342
+ for fileid, n_events, start_event, end_event in result_list:
343
+ tmp_log.debug(f"event information: fileid {fileid}, n_events {n_events}, start_event {start_event}, end_event {end_event}")
344
+
345
+ start_event = start_event if start_event is not None else 0
346
+ end_event = end_event if end_event is not None else 0
347
+ if end_event and start_event:
348
+ n_events_total += end_event - start_event
349
+ elif n_events:
350
+ n_events_total += n_events
351
+
352
+ if not n_events_total:
353
+ tmp_log.debug(f"n_events could not be calculated for job {job_id}... nothing to do")
354
+ return None, None
355
+ else:
356
+ tmp_log.debug(f"No input files for job {job_id}, so could not update CPU time for task {task_id}")
357
+ return None, None
358
+
359
+ # Get the corecount from the job spec
360
+ var_map = {":task_id": task_id, ":job_id": job_id}
361
+ sql_select = f"""
362
+ SELECT jact4.corecount
363
+ FROM ATLAS_PANDA.jobsactive4 jact4
364
+ WHERE jeditaskid = :task_id AND pandaid = :job_id
365
+ UNION
366
+ SELECT jarc4.corecount
367
+ FROM ATLAS_PANDA.jobsarchived4 jarc4
368
+ WHERE jeditaskid = :task_id AND pandaid = :job_id
369
+ UNION
370
+ SELECT jarch.corecount
371
+ FROM ATLAS_PANDAARCH.jobsarchived jarch
372
+ WHERE jeditaskid = :task_id AND pandaid = :job_id
373
+ """
374
+ self.cur.execute(sql_select + comment, var_map)
375
+
376
+ results = self.cur.fetchone()
377
+ try:
378
+ core_count_job = results[0]
379
+ except (IndexError, TypeError):
380
+ core_count_job = None
381
+
382
+ if not core_count_job:
383
+ core_count_job = 1 # Default to 1 if no core_count is defined in the job spec
384
+ tmp_log.debug(f"core_count_job: {core_count_job}")
385
+
386
+ # Calculate the new CPU time
387
+ try:
388
+ new_cputime_unit = "HS06sPerEvent"
389
+ new_cputime = ((max_time_site - basewalltime) * core_power_site * core_count_job * 1.1 / (cpuefficiency / 100.0) / n_events_total) * 1.5
390
+ new_cputime_normalized = new_cputime
391
+
392
+ if new_cputime and new_cputime < 10:
393
+ new_cputime = new_cputime * 1000
394
+ new_cputime_unit = "mHS06sPerEvent"
395
+
396
+ # the entry is stored without decimals in the DB
397
+ new_cputime = int(new_cputime)
398
+
399
+ tmp_log.debug(f"Old CPU time is {old_cputime} {old_cputime_unit} and possible new CPU time is {new_cputime} {new_cputime_unit}")
400
+
401
+ if old_cputime_normalized is not None and new_cputime_normalized is not None and old_cputime_normalized > new_cputime_normalized:
402
+ tmp_log.debug(
403
+ f"Skipping CPU time increase since old CPU time {old_cputime_normalized} HS06sPerEvent "
404
+ f"> new CPU time {new_cputime_normalized} HS06sPerEvent"
405
+ )
406
+ return None, None
407
+
408
+ if active: # only run the update if active mode. Otherwise return what would have been done
409
+ sql_update_cputime = """
410
+ UPDATE ATLAS_PANDA.jedi_tasks SET cputime=:new_cputime, cputimeunit=:new_cputime_unit
411
+ WHERE jeditaskid=:jeditaskid
412
+ """
413
+ var_map = {":new_cputime": new_cputime, ":new_cputime_unit": new_cputime_unit, ":jeditaskid": task_id}
414
+ self.conn.begin()
415
+ self.cur.execute(sql_update_cputime + comment, var_map)
416
+ if not self._commit():
417
+ raise RuntimeError("Commit error")
418
+
419
+ tmp_log.debug(f"Successfully updated the task CPU time from {old_cputime} to {new_cputime} {new_cputime_unit}")
420
+ else:
421
+ tmp_log.debug("Not updating the task CPU time since active mode is False.")
422
+
423
+ return new_cputime, new_cputime_unit
424
+
425
+ except (ZeroDivisionError, TypeError) as e:
426
+ tmp_log.debug(f"Exception while updating the task CPU time: {e}")
427
+ return None, None
428
+
429
+ def requestTaskParameterRecalculation(self, taskID):
430
+ """
431
+ Requests the recalculation of the CPU time of a task:
432
+ 1. set the walltimeUnit to NULL and the modificationTime to Now
433
+ 2. AtlasProdWatchDog > JediDBProxy.setScoutJobDataToTasks will pick up tasks with walltimeUnit == NULL
434
+ and modificationTime > Now - 24h. This will trigger a recalculation of the task parameters (outDiskCount,
435
+ outDiskUnit, outDiskCount, walltime, walltimeUnit, cpuTime, ioIntensity, ioIntensityUnit, ramCount, ramUnit,
436
+ workDiskCount, workDiskUnit, workDiskCount)
437
+ """
438
+ comment = " /* DBProxy.requestTaskParameterRecalculation */"
439
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={taskID}")
440
+ tmp_log.debug("start")
441
+
442
+ timeNow = naive_utcnow()
443
+ timeLimit = timeNow - datetime.timedelta(minutes=30)
444
+
445
+ # update the task if it was not already updated in the last 30 minutes (avoid continuous recalculation)
446
+ sql = """
447
+ UPDATE ATLAS_PANDA.jedi_tasks
448
+ SET walltimeUnit=NULL, modificationTime=:timeNow
449
+ WHERE jediTaskId=:taskID AND modificationTime < :timeLimit
450
+ """
451
+ varMap = {":taskID": taskID, ":timeNow": timeNow, ":timeLimit": timeLimit}
452
+ self.conn.begin()
453
+ self.cur.execute(sql, varMap)
454
+
455
+ rowcount = self.cur.rowcount
456
+
457
+ if not self._commit():
458
+ raise RuntimeError("Commit error")
459
+
460
+ tmp_log.debug("Forced recalculation of CPUTime")
461
+ return rowcount
462
+
463
+ # get task parameters
464
+ def getTaskParamsPanda(self, jediTaskID):
465
+ comment = " /* DBProxy.getTaskParamsPanda */"
466
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
467
+ tmp_log.debug("start")
468
+ try:
469
+ # sql to get task parameters
470
+ sqlRR = f"SELECT jedi_task_parameters FROM {panda_config.schemaDEFT}.T_TASK "
471
+ sqlRR += "WHERE taskid=:jediTaskID "
472
+ varMap = {}
473
+ varMap[":jediTaskID"] = jediTaskID
474
+ # start transaction
475
+ self.conn.begin()
476
+ self.cur.execute(sqlRR + comment, varMap)
477
+ # commit
478
+ if not self._commit():
479
+ raise RuntimeError("Commit error")
480
+ # read clob
481
+ taskParams = ""
482
+ for (clobJobP,) in self.cur:
483
+ if clobJobP is not None:
484
+ try:
485
+ taskParams = clobJobP.read()
486
+ except AttributeError:
487
+ taskParams = str(clobJobP)
488
+ break
489
+ tmp_log.debug("done")
490
+ return taskParams
491
+ except Exception:
492
+ # roll back
493
+ self._rollback()
494
+ # error
495
+ self.dump_error_message(tmp_log)
496
+ return ""
497
+
498
+ # get task attributes
499
+ def getTaskAttributesPanda(self, jediTaskID, attrs):
500
+ comment = " /* DBProxy.getTaskAttributesPanda */"
501
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
502
+ tmp_log.debug("start")
503
+ try:
504
+ # sql to get task attributes
505
+ sqlRR = "SELECT "
506
+ for attr in attrs:
507
+ sqlRR += f"{attr},"
508
+ sqlRR = sqlRR[:-1]
509
+ sqlRR += f" FROM {panda_config.schemaJEDI}.JEDI_Tasks "
510
+ sqlRR += "WHERE jediTaskID=:jediTaskID "
511
+ varMap = {}
512
+ varMap[":jediTaskID"] = jediTaskID
513
+ # start transaction
514
+ self.conn.begin()
515
+ self.cur.execute(sqlRR + comment, varMap)
516
+ resRR = self.cur.fetchone()
517
+ # commit
518
+ if not self._commit():
519
+ raise RuntimeError("Commit error")
520
+ retVal = {}
521
+ if resRR is not None:
522
+ for idx, attr in enumerate(attrs):
523
+ retVal[attr] = resRR[idx]
524
+ tmp_log.debug(f"done {str(retVal)}")
525
+ return retVal
526
+ except Exception:
527
+ # roll back
528
+ self._rollback()
529
+ # error
530
+ self.dump_error_message(tmp_log)
531
+ return {}
532
+
533
+ # get task status
534
+ def getTaskStatus(self, jediTaskID):
535
+ comment = " /* DBProxy.getTaskStatus */"
536
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
537
+ tmp_log.debug("start")
538
+ try:
539
+ # sql to update input file status
540
+ varMap = {}
541
+ varMap[":jediTaskID"] = jediTaskID
542
+ sql = f"SELECT status FROM {panda_config.schemaJEDI}.JEDI_Tasks "
543
+ sql += "WHERE jediTaskID=:jediTaskID "
544
+
545
+ # start transaction
546
+ self.conn.begin()
547
+ self.cur.arraysize = 1000
548
+ self.cur.execute(sql + comment, varMap)
549
+ res = self.cur.fetchone()
550
+ # commit
551
+ if not self._commit():
552
+ raise RuntimeError("Commit error")
553
+ if res:
554
+ tmp_log.debug(f"task {jediTaskID} has status: {res[0]} ")
555
+ else:
556
+ res = []
557
+ tmp_log.debug(f"task {jediTaskID} not found")
558
+ return res
559
+ except Exception:
560
+ # roll back
561
+ self._rollback()
562
+ # error
563
+ self.dump_error_message(tmp_log)
564
+ return []
565
+
566
+ # get task status and superstatus
567
+ def getTaskStatusSuperstatus(self, jediTaskID):
568
+ comment = " /* DBProxy.getTaskStatusSuperstatus */"
569
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
570
+ tmp_log.debug("start")
571
+ try:
572
+ # sql to update input file status
573
+ varMap = {}
574
+ varMap[":jediTaskID"] = jediTaskID
575
+ sql = f"SELECT status,superStatus FROM {panda_config.schemaJEDI}.JEDI_Tasks "
576
+ sql += "WHERE jediTaskID=:jediTaskID "
577
+ # start transaction
578
+ self.conn.begin()
579
+ self.cur.arraysize = 1000
580
+ self.cur.execute(sql + comment, varMap)
581
+ res = self.cur.fetchone()
582
+ # commit
583
+ if not self._commit():
584
+ raise RuntimeError("Commit error")
585
+ if res:
586
+ tmp_log.debug(f"task {jediTaskID} has status={res[0]} superstatus={res[1]}")
587
+ else:
588
+ res = []
589
+ tmp_log.debug(f"task {jediTaskID} not found")
590
+ return res
591
+ except Exception:
592
+ # roll back
593
+ self._rollback()
594
+ # error
595
+ self.dump_error_message(tmp_log)
596
+ return []
597
+
598
+ # reactivate task
599
+ def reactivateTask(self, jediTaskID, keep_attempt_nr=False, trigger_job_generation=False):
600
+ comment = " /* DBProxy.reactivateTask */"
601
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
602
+ tmp_log.debug("start")
603
+ try:
604
+ # sql to update task status
605
+ sql = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks "
606
+ sql += "SET status=:status "
607
+ sql += "WHERE jediTaskID=:jediTaskID "
608
+ # sql to get datasetIDs for master
609
+ sqlM = f"SELECT datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets "
610
+ sqlM += "WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2,:type3) "
611
+ # sql to increase attempt numbers and update status
612
+ sqlAB = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
613
+ if keep_attempt_nr:
614
+ sqlAB += "SET status=:status "
615
+ else:
616
+ sqlAB += "SET status=:status,attemptNr=0 "
617
+ sqlAB += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
618
+ # sql to update datasets
619
+ sqlD = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
620
+ sqlD += "SET status=:status,nFilesUsed=0,nFilesTobeUsed=nFiles,nFilesFinished=0,nFilesFailed=0 "
621
+ sqlD += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
622
+ # update task status
623
+ varMap = {}
624
+ varMap[":jediTaskID"] = jediTaskID
625
+ varMap[":status"] = "ready"
626
+ self.cur.execute(sql + comment, varMap)
627
+ res = self.cur.rowcount
628
+ # get datasetIDs for master
629
+ varMap = {}
630
+ varMap[":jediTaskID"] = jediTaskID
631
+ varMap[":type1"] = "input"
632
+ varMap[":type2"] = "pseudo_input"
633
+ varMap[":type3"] = "random_seed"
634
+ self.cur.execute(sqlM + comment, varMap)
635
+ resM = self.cur.fetchall()
636
+ total_nFiles = 0
637
+
638
+ for (datasetID,) in resM:
639
+ # increase attempt numbers
640
+ varMap = {}
641
+ varMap[":jediTaskID"] = jediTaskID
642
+ varMap[":datasetID"] = datasetID
643
+ varMap[":status"] = "ready"
644
+
645
+ # update status and attempt number for datasets
646
+ self.cur.execute(sqlAB + comment, varMap)
647
+ nFiles = self.cur.rowcount
648
+
649
+ # update dataset
650
+ if nFiles > 0:
651
+ varMap = {}
652
+ varMap[":jediTaskID"] = jediTaskID
653
+ varMap[":datasetID"] = datasetID
654
+ varMap[":status"] = "ready"
655
+ tmp_log.debug(sqlD + comment + str(varMap))
656
+ self.cur.execute(sqlD + comment, varMap)
657
+ total_nFiles += nFiles
658
+
659
+ tmpMsg = f"updated {total_nFiles} inputs and task {jediTaskID} was reactivated "
660
+ tmp_log.debug(tmpMsg)
661
+ tmp_log.sendMsg(tmpMsg, "jedi", "pandasrv")
662
+ retVal = 0, tmpMsg
663
+ # commit
664
+ if not self._commit():
665
+ raise RuntimeError("Commit error")
666
+ # send message
667
+ if trigger_job_generation:
668
+ # message
669
+ msg = srv_msg_utils.make_message("generate_job", taskid=jediTaskID)
670
+ mb_proxy = self.get_mb_proxy("panda_jedi")
671
+ if mb_proxy:
672
+ mb_proxy.send(msg)
673
+ tmp_log.debug(f"sent generate_job message: {msg}")
674
+ else:
675
+ tmp_log.debug("message queue is not configured")
676
+ tmp_log.debug("done")
677
+ return retVal
678
+ except Exception:
679
+ # roll back
680
+ self._rollback()
681
+ # error
682
+ self.dump_error_message(tmp_log)
683
+ return None, "DB error"
684
+
685
+ # get event statistics
686
+ def getEventStat(self, jediTaskID, PandaID):
687
+ comment = " /* DBProxy.getEventStat */"
688
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} PandaID={PandaID}")
689
+ tmp_log.debug("start")
690
+ try:
691
+ # sql to get event stats
692
+ sql = f"SELECT status,COUNT(*) FROM {panda_config.schemaJEDI}.JEDI_Events "
693
+ sql += "WHERE jediTaskID=:jediTaskID AND PandaID=:PandaID "
694
+ sql += "GROUP BY status "
695
+ # start transaction
696
+ self.conn.begin()
697
+ self.cur.arraysize = 10000
698
+ # get stats
699
+ varMap = {}
700
+ varMap[":jediTaskID"] = jediTaskID
701
+ varMap[":PandaID"] = PandaID
702
+ self.cur.execute(sql + comment, varMap)
703
+ resM = self.cur.fetchall()
704
+ retMap = {}
705
+ for eventStatus, cnt in resM:
706
+ retMap[eventStatus] = cnt
707
+ # commit
708
+ if not self._commit():
709
+ raise RuntimeError("Commit error")
710
+ tmp_log.debug(f"done with {str(retMap)}")
711
+ return retMap
712
+ except Exception:
713
+ # roll back
714
+ self._rollback()
715
+ # error
716
+ self.dump_error_message(tmp_log)
717
+ return {}
718
+
719
+ # update error dialog for a jediTaskID
720
+ def updateTaskErrorDialogJEDI(self, jediTaskID, msg):
721
+ comment = " /* DBProxy.updateTaskErrorDialogJEDI */"
722
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
723
+ tmp_log.debug(f"start")
724
+ try:
725
+ # begin transaction
726
+ self.conn.begin()
727
+ # get existing dialog
728
+ sqlGF = f"SELECT errorDialog FROM {panda_config.schemaJEDI}.JEDI_Tasks "
729
+ sqlGF += "WHERE jediTaskID=:jediTaskID "
730
+ varMap = {}
731
+ varMap[":jediTaskID"] = jediTaskID
732
+ self.cur.execute(sqlGF + comment, varMap)
733
+ resFJ = self.cur.fetchone()
734
+ if resFJ is not None:
735
+ # update existing dialog
736
+ (errorDialog,) = resFJ
737
+ errorDialog = msg
738
+ varMap = {}
739
+ varMap[":jediTaskID"] = jediTaskID
740
+ varMap[":errorDialog"] = errorDialog
741
+ sqlUE = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET errorDialog=:errorDialog,modificationTime=CURRENT_DATE "
742
+ sqlUE += "WHERE jediTaskID=:jediTaskID "
743
+ self.cur.execute(sqlUE + comment, varMap)
744
+ # commit
745
+ if not self._commit():
746
+ raise RuntimeError("Commit error")
747
+ tmp_log.debug(f"done")
748
+ return True
749
+ except Exception:
750
+ # roll back
751
+ self._rollback()
752
+ # error
753
+ self.dump_error_message(tmp_log)
754
+ return False
755
+
756
+ # increase attempt number for unprocessed files
757
+ def increaseAttemptNrPanda(self, jediTaskID, increasedNr):
758
+ comment = " /* DBProxy.increaseAttemptNrPanda */"
759
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
760
+ tmp_log.debug(f"increasedNr={increasedNr}")
761
+ try:
762
+ # sql to check task status
763
+ sqlT = f"SELECT status,oldStatus FROM {panda_config.schemaJEDI}.JEDI_Tasks "
764
+ sqlT += "WHERE jediTaskID=:jediTaskID FOR UPDATE "
765
+ # start transaction
766
+ self.conn.begin()
767
+ # select
768
+ self.cur.arraysize = 10
769
+ varMap = {}
770
+ varMap[":jediTaskID"] = jediTaskID
771
+ # get task status
772
+ self.cur.execute(sqlT + comment, varMap)
773
+ resT = self.cur.fetchone()
774
+ if resT is None:
775
+ tmpMsg = f"jediTaskID={jediTaskID} not found"
776
+ tmp_log.debug(tmpMsg)
777
+ retVal = 1, tmpMsg
778
+ else:
779
+ taskStatus, oldStatus = resT
780
+ # check task status
781
+ okStatusList = ["running", "scouting", "ready"]
782
+ if taskStatus not in okStatusList and oldStatus not in okStatusList:
783
+ tmpMsg = f"command rejected since status={taskStatus} or oldStatus={oldStatus} not in {str(okStatusList)}"
784
+ tmp_log.debug(tmpMsg)
785
+ retVal = 2, tmpMsg
786
+ else:
787
+ # sql to get datasetIDs for master
788
+ sqlM = f"SELECT datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets "
789
+ sqlM += "WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2) "
790
+ # sql to increase attempt numbers
791
+ sqlAB = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
792
+ sqlAB += "SET maxAttempt=CASE WHEN maxAttempt > attemptNr THEN maxAttempt+:increasedNr ELSE attemptNr+:increasedNr END "
793
+ sqlAB += ",proc_status=CASE WHEN maxAttempt > attemptNr AND maxFailure > failedAttempt THEN proc_status ELSE :proc_status END "
794
+ sqlAB += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND status=:status AND keepTrack=:keepTrack "
795
+ # sql to increase attempt numbers and failure counts
796
+ sqlAF = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
797
+ sqlAF += "SET maxAttempt=CASE WHEN maxAttempt > attemptNr THEN maxAttempt+:increasedNr ELSE attemptNr+:increasedNr END "
798
+ sqlAF += ",maxFailure=maxFailure+:increasedNr "
799
+ sqlAF += ",proc_status=CASE WHEN maxAttempt > attemptNr AND maxFailure > failedAttempt THEN proc_status ELSE :proc_status END "
800
+ sqlAF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND status=:status AND keepTrack=:keepTrack "
801
+ # sql to update datasets
802
+ sqlD = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
803
+ sqlD += "SET nFilesUsed=nFilesUsed-:nFilesReset,nFilesFailed=nFilesFailed-:nFilesReset "
804
+ sqlD += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
805
+ # get datasetIDs for master
806
+ varMap = {}
807
+ varMap[":jediTaskID"] = jediTaskID
808
+ varMap[":type1"] = "input"
809
+ varMap[":type2"] = "pseudo_input"
810
+ self.cur.execute(sqlM + comment, varMap)
811
+ resM = self.cur.fetchall()
812
+ total_nFilesIncreased = 0
813
+ total_nFilesReset = 0
814
+ for (datasetID,) in resM:
815
+ # increase attempt numbers
816
+ varMap = {}
817
+ varMap[":jediTaskID"] = jediTaskID
818
+ varMap[":datasetID"] = datasetID
819
+ varMap[":status"] = "ready"
820
+ varMap[":proc_status"] = "ready"
821
+ varMap[":keepTrack"] = 1
822
+ varMap[":increasedNr"] = increasedNr
823
+ nFilesIncreased = 0
824
+ nFilesReset = 0
825
+ # still active and maxFailure is undefined
826
+ sqlA = sqlAB + "AND maxAttempt>attemptNr AND maxFailure IS NULL "
827
+ self.cur.execute(sqlA + comment, varMap)
828
+ nRow = self.cur.rowcount
829
+ nFilesIncreased += nRow
830
+ # still active and maxFailure is defined
831
+ sqlA = sqlAF + "AND maxAttempt>attemptNr AND (maxFailure IS NOT NULL AND maxFailure>failedAttempt) "
832
+ self.cur.execute(sqlA + comment, varMap)
833
+ nRow = self.cur.rowcount
834
+ nFilesIncreased += nRow
835
+ # already done and maxFailure is undefined
836
+ sqlA = sqlAB + "AND maxAttempt<=attemptNr AND maxFailure IS NULL "
837
+ self.cur.execute(sqlA + comment, varMap)
838
+ nRow = self.cur.rowcount
839
+ nFilesReset += nRow
840
+ nFilesIncreased += nRow
841
+ # already done and maxFailure is defined
842
+ sqlA = sqlAF + "AND (maxAttempt<=attemptNr OR (maxFailure IS NOT NULL AND maxFailure=failedAttempt)) "
843
+ self.cur.execute(sqlA + comment, varMap)
844
+ nRow = self.cur.rowcount
845
+ nFilesReset += nRow
846
+ nFilesIncreased += nRow
847
+ # update dataset
848
+ if nFilesReset > 0:
849
+ varMap = {}
850
+ varMap[":jediTaskID"] = jediTaskID
851
+ varMap[":datasetID"] = datasetID
852
+ varMap[":nFilesReset"] = nFilesReset
853
+ tmp_log.debug(sqlD + comment + str(varMap))
854
+ self.cur.execute(sqlD + comment, varMap)
855
+ total_nFilesIncreased += nFilesIncreased
856
+ total_nFilesReset += nFilesReset
857
+ tmpMsg = f"increased attemptNr for {total_nFilesIncreased} inputs ({total_nFilesReset} reactivated)"
858
+ tmp_log.debug(tmpMsg)
859
+ tmp_log.sendMsg(tmpMsg, "jedi", "pandasrv")
860
+ retVal = 0, tmpMsg
861
+ # commit
862
+ if not self._commit():
863
+ raise RuntimeError("Commit error")
864
+ tmp_log.debug("done")
865
+ return retVal
866
+ except Exception:
867
+ # roll back
868
+ self._rollback()
869
+ # error
870
+ self.dump_error_message(tmp_log)
871
+ return None, "DB error"
872
+
873
+ # insert sandbox file info
874
+ def insertSandboxFileInfo(self, userName, hostName, fileName, fileSize, checkSum):
875
+ comment = " /* DBProxy.insertSandboxFileInfo */"
876
+ tmp_log = self.create_tagged_logger(comment, f"userName={userName}")
877
+ sqlC = "SELECT userName,fileSize,checkSum FROM ATLAS_PANDAMETA.userCacheUsage "
878
+ sqlC += "WHERE hostName=:hostName AND fileName=:fileName FOR UPDATE"
879
+
880
+ sql = "INSERT INTO ATLAS_PANDAMETA.userCacheUsage "
881
+ sql += "(userName,hostName,fileName,fileSize,checkSum,creationTime,modificationTime) "
882
+ sql += "VALUES (:userName,:hostName,:fileName,:fileSize,:checkSum,CURRENT_DATE,CURRENT_DATE) "
883
+
884
+ try:
885
+ # begin transaction
886
+ self.conn.begin()
887
+ # check if it already exists
888
+ varMap = {}
889
+ varMap[":hostName"] = hostName
890
+ varMap[":fileName"] = fileName
891
+ self.cur.arraysize = 10
892
+ self.cur.execute(sqlC + comment, varMap)
893
+ res = self.cur.fetchall()
894
+ if len(res) != 0:
895
+ tmp_log.debug(f"skip {hostName} {fileName} since already exists")
896
+ # commit
897
+ if not self._commit():
898
+ raise RuntimeError("Commit error")
899
+ return "WARNING: file exist"
900
+ # insert
901
+ varMap = {}
902
+ varMap[":userName"] = userName
903
+ varMap[":hostName"] = hostName
904
+ varMap[":fileName"] = fileName
905
+ varMap[":fileSize"] = fileSize
906
+ varMap[":checkSum"] = checkSum
907
+ self.cur.execute(sql + comment, varMap)
908
+ # commit
909
+ if not self._commit():
910
+ raise RuntimeError("Commit error")
911
+ return "OK"
912
+ except Exception:
913
+ # roll back
914
+ self._rollback()
915
+ # error
916
+ self.dump_error_message(tmp_log)
917
+ return "ERROR: DB failure"
918
+
919
+ # get and lock sandbox files
920
+ def getLockSandboxFiles(self, time_limit, n_files):
921
+ comment = " /* DBProxy.getLockSandboxFiles */"
922
+ tmp_log = self.create_tagged_logger(comment)
923
+ sqlC = (
924
+ "SELECT * FROM ("
925
+ "SELECT userName,hostName,fileName,creationTime,modificationTime FROM ATLAS_PANDAMETA.userCacheUsage "
926
+ "WHERE modificationTime<:timeLimit AND (fileName like 'sources%' OR fileName like 'jobO%') ) "
927
+ "WHERE rownum<:nRows "
928
+ )
929
+ sqlU = "UPDATE ATLAS_PANDAMETA.userCacheUsage SET modificationTime=CURRENT_DATE " "WHERE userName=:userName AND fileName=:fileName "
930
+ try:
931
+ tmp_log.debug("start")
932
+ # begin transaction
933
+ self.conn.begin()
934
+ # check if it already exists
935
+ varMap = {}
936
+ varMap[":timeLimit"] = time_limit
937
+ varMap[":nRows"] = n_files
938
+ self.cur.execute(sqlC + comment, varMap)
939
+ res = self.cur.fetchall()
940
+ retList = []
941
+ for userName, hostName, fileName, creationTime, modificationTime in res:
942
+ retList.append((userName, hostName, fileName, creationTime, modificationTime))
943
+ # lock
944
+ varMap = dict()
945
+ varMap[":userName"] = userName
946
+ varMap[":fileName"] = fileName
947
+ self.cur.execute(sqlU + comment, varMap)
948
+ # commit
949
+ if not self._commit():
950
+ raise RuntimeError("Commit error")
951
+ tmp_log.debug(f"locked {len(retList)} files")
952
+ return retList
953
+ except Exception:
954
+ # roll back
955
+ self._rollback()
956
+ # error
957
+ self.dump_error_message(tmp_log)
958
+ return None
959
+
960
+ # check duplicated sandbox file
961
+ def checkSandboxFile(self, dn, fileSize, checkSum):
962
+ comment = " /* DBProxy.checkSandboxFile */"
963
+ tmp_log = self.create_tagged_logger(comment)
964
+ tmp_log.debug(f"dn={dn} size={fileSize} checksum={checkSum}")
965
+ sqlC = "SELECT hostName,fileName FROM ATLAS_PANDAMETA.userCacheUsage "
966
+ sqlC += "WHERE userName=:userName AND fileSize=:fileSize AND checkSum=:checkSum "
967
+ sqlC += "AND hostName<>:ngHostName AND creationTime>CURRENT_DATE-3 "
968
+ sqlC += "AND creationTime>CURRENT_DATE-3 "
969
+ try:
970
+ retStr = "NOTFOUND"
971
+ # get compact DN
972
+ compactDN = CoreUtils.clean_user_id(dn)
973
+ if compactDN in ["", "NULL", None]:
974
+ compactDN = dn
975
+ # begin transaction
976
+ self.conn.begin()
977
+ # check if it already exists
978
+ varMap = {}
979
+ varMap[":userName"] = compactDN
980
+ varMap[":fileSize"] = fileSize
981
+ varMap[":checkSum"] = str(checkSum)
982
+ varMap[":ngHostName"] = "localhost.localdomain"
983
+ self.cur.arraysize = 10
984
+ self.cur.execute(sqlC + comment, varMap)
985
+ res = self.cur.fetchall()
986
+ # commit
987
+ if not self._commit():
988
+ raise RuntimeError("Commit error")
989
+ if len(res) != 0:
990
+ hostName, fileName = res[0]
991
+ retStr = f"FOUND:{hostName}:{fileName}"
992
+ tmp_log.debug(f"{retStr}")
993
+ return retStr
994
+ except Exception:
995
+ # roll back
996
+ self._rollback()
997
+ # error
998
+ self.dump_error_message(tmp_log)
999
+ return "ERROR: DB failure"
1000
+
1001
+ # insert dataset
1002
+ def insertDataset(self, dataset, tablename="ATLAS_PANDA.Datasets"):
1003
+ comment = " /* DBProxy.insertDataset */"
1004
+ tmp_log = self.create_tagged_logger(comment, f"dataset={dataset.name}")
1005
+ tmp_log.debug("start")
1006
+ sql0 = f"SELECT COUNT(*) FROM {tablename} WHERE vuid=:vuid "
1007
+ sql1 = f"INSERT INTO {tablename} "
1008
+ sql1 += f"({DatasetSpec.columnNames()}) "
1009
+ sql1 += DatasetSpec.bindValuesExpression()
1010
+ sql2 = f"SELECT name FROM {tablename} WHERE vuid=:vuid "
1011
+ # time information
1012
+ dataset.creationdate = naive_utcnow()
1013
+ dataset.modificationdate = dataset.creationdate
1014
+ try:
1015
+ # subtype
1016
+ if dataset.subType in ["", "NULL", None]:
1017
+ # define using name
1018
+ if re.search("_dis\d+$", dataset.name) is not None:
1019
+ dataset.subType = "dis"
1020
+ elif re.search("_sub\d+$", dataset.name) is not None:
1021
+ dataset.subType = "sub"
1022
+ else:
1023
+ dataset.subType = "top"
1024
+ # begin transaction
1025
+ self.conn.begin()
1026
+ # check if it already exists
1027
+ varMap = {}
1028
+ varMap[":vuid"] = dataset.vuid
1029
+ self.cur.execute(sql0 + comment, varMap)
1030
+ (nDS,) = self.cur.fetchone()
1031
+ tmp_log.debug(f"nDS={nDS} with {dataset.vuid}")
1032
+ if nDS == 0:
1033
+ # insert
1034
+ tmp_log.debug(sql1 + comment + str(dataset.valuesMap()))
1035
+ self.cur.execute(sql1 + comment, dataset.valuesMap())
1036
+ # check name in DB
1037
+ varMap = {}
1038
+ varMap[":vuid"] = dataset.vuid
1039
+ self.cur.execute(sql2 + comment, varMap)
1040
+ (nameInDB,) = self.cur.fetchone()
1041
+ tmp_log.debug(f"inDB -> {nameInDB} {dataset.name == nameInDB}")
1042
+ # commit
1043
+ if not self._commit():
1044
+ raise RuntimeError("Commit error")
1045
+ return True
1046
+ except Exception:
1047
+ # roll back
1048
+ self._rollback()
1049
+ # error
1050
+ self.dump_error_message(tmp_log)
1051
+ return False
1052
+
1053
+ # get and lock dataset with a query
1054
+ def getLockDatasets(self, sqlQuery, varMapGet, modTimeOffset="", getVersion=False):
1055
+ comment = " /* DBProxy.getLockDatasets */"
1056
+ tmp_log = self.create_tagged_logger(comment)
1057
+ tmp_log.debug(f"{sqlQuery},{str(varMapGet)},{modTimeOffset}")
1058
+ sqlGet = (
1059
+ "SELECT /*+ INDEX_RS_ASC(tab(STATUS,TYPE,MODIFICATIONDATE)) */ vuid,name,modificationdate,version,transferStatus FROM ATLAS_PANDA.Datasets tab WHERE "
1060
+ + sqlQuery
1061
+ )
1062
+ sqlLock = "UPDATE ATLAS_PANDA.Datasets SET modificationdate=CURRENT_DATE"
1063
+ if modTimeOffset != "":
1064
+ sqlLock += f"+{modTimeOffset}"
1065
+ sqlLock += ",transferStatus=MOD(transferStatus+1,10)"
1066
+ if getVersion:
1067
+ sqlLock += ",version=:version"
1068
+ sqlLock += " WHERE vuid=:vuid AND transferStatus=:transferStatus"
1069
+ retList = []
1070
+ try:
1071
+ # begin transaction
1072
+ self.conn.begin()
1073
+ # get datasets
1074
+ self.cur.arraysize = 1000000
1075
+ self.cur.execute(sqlGet + comment, varMapGet)
1076
+ res = self.cur.fetchall()
1077
+ # commit
1078
+ if not self._commit():
1079
+ raise RuntimeError("Commit error")
1080
+ # loop over all datasets
1081
+ if res is not None and len(res) != 0:
1082
+ for vuid, name, modificationdate, version, transferStatus in res:
1083
+ # lock
1084
+ varMapLock = {}
1085
+ varMapLock[":vuid"] = vuid
1086
+ varMapLock[":transferStatus"] = transferStatus
1087
+ if getVersion:
1088
+ try:
1089
+ varMapLock[":version"] = str(int(version) + 1)
1090
+ except Exception:
1091
+ varMapLock[":version"] = str(1)
1092
+ # begin transaction
1093
+ self.conn.begin()
1094
+ # update for lock
1095
+ self.cur.execute(sqlLock + comment, varMapLock)
1096
+ retU = self.cur.rowcount
1097
+ # commit
1098
+ if not self._commit():
1099
+ raise RuntimeError("Commit error")
1100
+ if retU > 0:
1101
+ # append
1102
+ if not getVersion:
1103
+ retList.append((vuid, name, modificationdate))
1104
+ else:
1105
+ retList.append((vuid, name, modificationdate, version))
1106
+ # commit
1107
+ if not self._commit():
1108
+ raise RuntimeError("Commit error")
1109
+ # return
1110
+ return retList
1111
+ except Exception:
1112
+ # roll back
1113
+ self._rollback()
1114
+ # error
1115
+ self.dump_error_message(tmp_log)
1116
+ return []
1117
+
1118
+ # query dataset with map
1119
+ def queryDatasetWithMap(self, map):
1120
+ comment = " /* DBProxy.queryDatasetWithMap */"
1121
+ tmp_log = self.create_tagged_logger(comment)
1122
+ tmp_log.debug(f"{map}")
1123
+ if "name" in map:
1124
+ sql1 = """SELECT /*+ BEGIN_OUTLINE_DATA """
1125
+ sql1 += """INDEX_RS_ASC(@"SEL$1" "TAB"@"SEL$1" ("DATASETS"."NAME")) """
1126
+ sql1 += """OUTLINE_LEAF(@"SEL$1") ALL_ROWS """
1127
+ sql1 += """IGNORE_OPTIM_EMBEDDED_HINTS """
1128
+ sql1 += """END_OUTLINE_DATA */ """
1129
+ sql1 += f"{DatasetSpec.columnNames()} FROM ATLAS_PANDA.Datasets tab"
1130
+ else:
1131
+ sql1 = f"SELECT {DatasetSpec.columnNames()} FROM ATLAS_PANDA.Datasets"
1132
+ varMap = {}
1133
+ for key in map:
1134
+ if len(varMap) == 0:
1135
+ sql1 += f" WHERE {key}=:{key}"
1136
+ else:
1137
+ sql1 += f" AND {key}=:{key}"
1138
+ varMap[f":{key}"] = map[key]
1139
+ try:
1140
+ # start transaction
1141
+ self.conn.begin()
1142
+ # select
1143
+ self.cur.arraysize = 100
1144
+ tmp_log.debug(sql1 + comment + str(varMap))
1145
+ self.cur.execute(sql1 + comment, varMap)
1146
+ res = self.cur.fetchall()
1147
+ # commit
1148
+ if not self._commit():
1149
+ raise RuntimeError("Commit error")
1150
+ # instantiate Dataset
1151
+ if res is not None and len(res) != 0:
1152
+ dataset = DatasetSpec()
1153
+ dataset.pack(res[0])
1154
+ return dataset
1155
+ tmp_log.error(f"dataset not found")
1156
+ return None
1157
+ except Exception:
1158
+ # roll back
1159
+ self._rollback()
1160
+ self.dump_error_message(tmp_log)
1161
+ return None
1162
+
1163
+ # update dataset
1164
+ def updateDataset(self, datasets, withLock, withCriteria, criteriaMap):
1165
+ comment = " /* DBProxy.updateDataset */"
1166
+ tmp_log = self.create_tagged_logger(comment)
1167
+ tmp_log.debug("start")
1168
+ sql1 = f"UPDATE ATLAS_PANDA.Datasets SET {DatasetSpec.bindUpdateExpression()} "
1169
+ sql1 += "WHERE vuid=:vuid"
1170
+ if withCriteria != "":
1171
+ sql1 += f" AND {withCriteria}"
1172
+ retList = []
1173
+ try:
1174
+ # start transaction
1175
+ self.conn.begin()
1176
+ for dataset in datasets:
1177
+ tmp_log.debug(f"dataset={dataset.name} status={dataset.status})")
1178
+ # time information
1179
+ dataset.modificationdate = naive_utcnow()
1180
+ # update
1181
+ varMap = dataset.valuesMap()
1182
+ varMap[":vuid"] = dataset.vuid
1183
+ for cKey in criteriaMap:
1184
+ varMap[cKey] = criteriaMap[cKey]
1185
+ tmp_log.debug(sql1 + comment + str(varMap))
1186
+ self.cur.execute(sql1 + comment, varMap)
1187
+ retU = self.cur.rowcount
1188
+ if retU != 0 and retU != 1:
1189
+ raise RuntimeError(f"Invalid rerun {retU}")
1190
+ retList.append(retU)
1191
+ # commit
1192
+ if not self._commit():
1193
+ raise RuntimeError("Commit error")
1194
+ tmp_log.debug(f"ret:{retList}")
1195
+ return retList
1196
+ except Exception:
1197
+ # roll back
1198
+ self._rollback()
1199
+ self.dump_error_message(tmp_log)
1200
+ return []
1201
+
1202
+ # trigger cleanup of internal datasets used by a task
1203
+ def trigger_cleanup_internal_datasets(self, task_id: int) -> bool:
1204
+ """
1205
+ Set deleting flag to dispatch datasets used by a task, which triggers deletion in datasetManager
1206
+ """
1207
+ comment = " /* DBProxy.trigger_cleanup_internal_datasets */"
1208
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={task_id}")
1209
+ tmp_log.debug("start")
1210
+ sql1 = (
1211
+ f"UPDATE {panda_config.schemaPANDA}.Datasets SET status=:newStatus,modificationdate=CURRENT_DATE "
1212
+ "WHERE type=:type AND MoverID=:taskID AND status IN (:status_d,:status_c) "
1213
+ )
1214
+ try:
1215
+ # start transaction
1216
+ self.conn.begin()
1217
+ # update
1218
+ var_map = {
1219
+ ":type": "dispatch",
1220
+ ":newStatus": "deleting",
1221
+ ":taskID": task_id,
1222
+ ":status_d": "defined",
1223
+ ":status_c": "completed",
1224
+ }
1225
+ self.cur.execute(sql1 + comment, var_map)
1226
+ ret_u = self.cur.rowcount
1227
+ # commit
1228
+ if not self._commit():
1229
+ raise RuntimeError("Commit error")
1230
+ tmp_log.debug(f"set flag to {ret_u} dispatch datasets")
1231
+ return True
1232
+ except Exception:
1233
+ # roll back
1234
+ self._rollback()
1235
+ self.dump_error_message(tmp_log)
1236
+ return False
1237
+
1238
+ # get serial number for dataset, insert dummy datasets to increment SN
1239
+ def getSerialNumber(self, datasetname, definedFreshFlag=None):
1240
+ comment = " /* DBProxy.getSerialNumber */"
1241
+ tmp_log = self.create_tagged_logger(comment, f"datasetname={datasetname}")
1242
+ try:
1243
+ tmp_log.debug(f"fresh={definedFreshFlag}")
1244
+ if isinstance(datasetname, str):
1245
+ datasetname = datasetname.encode("ascii", "ignore")
1246
+ tmp_log.debug(f"converted unicode for {datasetname}")
1247
+ # start transaction
1248
+ self.conn.begin()
1249
+ # check freshness
1250
+ if definedFreshFlag is None:
1251
+ # select
1252
+ varMap = {}
1253
+ varMap[":name"] = datasetname
1254
+ varMap[":type"] = "output"
1255
+ sql = "SELECT /*+ INDEX_RS_ASC(TAB (DATASETS.NAME)) */ COUNT(*) FROM ATLAS_PANDA.Datasets tab WHERE type=:type AND name=:name"
1256
+ self.cur.arraysize = 100
1257
+ self.cur.execute(sql + comment, varMap)
1258
+ res = self.cur.fetchone()
1259
+ # fresh dataset or not
1260
+ if res is not None and len(res) != 0 and res[0] > 0:
1261
+ freshFlag = False
1262
+ else:
1263
+ freshFlag = True
1264
+ else:
1265
+ # use predefined flag
1266
+ freshFlag = definedFreshFlag
1267
+ # get serial number
1268
+ if self.backend == "oracle":
1269
+ sql = "SELECT ATLAS_PANDA.SUBCOUNTER_SUBID_SEQ.nextval FROM dual"
1270
+ self.cur.arraysize = 100
1271
+ self.cur.execute(sql + comment, {})
1272
+ (sn,) = self.cur.fetchone()
1273
+ elif self.backend == "postgres":
1274
+ sql = f"SELECT {panda_config.schemaPANDA}.SUBCOUNTER_SUBID_SEQ.nextval"
1275
+ self.cur.arraysize = 100
1276
+ self.cur.execute(sql + comment, {})
1277
+ (sn,) = self.cur.fetchone()
1278
+ else:
1279
+ # panda_config.backend == 'mysql'
1280
+ # fake sequence
1281
+ sql = " INSERT INTO ATLAS_PANDA.SUBCOUNTER_SUBID_SEQ (col) VALUES (NULL) "
1282
+ self.cur.arraysize = 100
1283
+ self.cur.execute(sql + comment, {})
1284
+ sql2 = """ SELECT LAST_INSERT_ID() """
1285
+ self.cur.execute(sql2 + comment, {})
1286
+ (sn,) = self.cur.fetchone()
1287
+ # commit
1288
+ if not self._commit():
1289
+ raise RuntimeError("Commit error")
1290
+ tmp_log.debug(f"SN={sn} {freshFlag}")
1291
+ return (sn, freshFlag)
1292
+ except Exception:
1293
+ # roll back
1294
+ self._rollback()
1295
+ # error
1296
+ self.dump_error_message(tmp_log)
1297
+ return (-1, False)
1298
+
1299
+ # count the number of files with map
1300
+ def countFilesWithMap(self, map):
1301
+ comment = " /* DBProxy.countFilesWithMap */"
1302
+ tmp_log = self.create_tagged_logger(comment)
1303
+ sql1 = "SELECT /*+ index(tab FILESTABLE4_DESTDBLOCK_IDX) */ COUNT(*) FROM ATLAS_PANDA.filesTable4 tab"
1304
+ varMap = {}
1305
+ for key in map:
1306
+ if len(varMap) == 0:
1307
+ sql1 += f" WHERE {key}=:{key}"
1308
+ else:
1309
+ sql1 += f" AND {key}=:{key}"
1310
+ varMap[f":{key}"] = map[key]
1311
+ nTry = 3
1312
+ for iTry in range(nTry):
1313
+ try:
1314
+ # start transaction
1315
+ self.conn.begin()
1316
+ # select
1317
+ tmp_log.debug(f"{sql1} {str(map)}")
1318
+ self.cur.arraysize = 10
1319
+ retS = self.cur.execute(sql1 + comment, varMap)
1320
+ res = self.cur.fetchone()
1321
+ tmp_log.debug(f"{retS} {str(res)}")
1322
+ # commit
1323
+ if not self._commit():
1324
+ raise RuntimeError("Commit error")
1325
+ nFiles = 0
1326
+ if res is not None:
1327
+ nFiles = res[0]
1328
+ return nFiles
1329
+ except Exception:
1330
+ # roll back
1331
+ self._rollback()
1332
+ if iTry + 1 < nTry:
1333
+ tmp_log.debug(f"retry : {iTry}")
1334
+ time.sleep(random.randint(10, 20))
1335
+ continue
1336
+ self.dump_error_message(tmp_log)
1337
+ return -1
1338
+
1339
+ # update input files and return corresponding PandaIDs
1340
+ def updateInFilesReturnPandaIDs(self, dataset, status, fileLFN=""):
1341
+ comment = " /* DBProxy.updateInFilesReturnPandaIDs */"
1342
+ tmp_log = self.create_tagged_logger(comment, f"dataset={dataset}")
1343
+ tmp_log.debug(f"{fileLFN})")
1344
+ sql0 = "SELECT /*+ index(tab FILESTABLE4_DISPDBLOCK_IDX) */ row_ID,PandaID FROM ATLAS_PANDA.filesTable4 tab WHERE status<>:status AND dispatchDBlock=:dispatchDBlock"
1345
+ sql1 = "UPDATE /*+ index(tab FILESTABLE4_DISPDBLOCK_IDX) */ ATLAS_PANDA.filesTable4 tab SET status=:status WHERE status<>:status AND dispatchDBlock=:dispatchDBlock"
1346
+ varMap = {}
1347
+ varMap[":status"] = status
1348
+ varMap[":dispatchDBlock"] = dataset
1349
+ if fileLFN != "":
1350
+ sql0 += " AND lfn=:lfn"
1351
+ sql1 += " AND lfn=:lfn"
1352
+ varMap[":lfn"] = fileLFN
1353
+ for iTry in range(self.nTry):
1354
+ try:
1355
+ # start transaction
1356
+ self.conn.begin()
1357
+ # select
1358
+ self.cur.arraysize = 10000
1359
+ retS = self.cur.execute(sql0 + comment, varMap)
1360
+ resS = self.cur.fetchall()
1361
+ # update
1362
+ retU = self.cur.execute(sql1 + comment, varMap)
1363
+ # commit
1364
+ if not self._commit():
1365
+ raise RuntimeError("Commit error")
1366
+ # collect PandaIDs
1367
+ retList = []
1368
+ for tmpRowID, tmpPandaID in resS:
1369
+ # append
1370
+ if tmpPandaID not in retList:
1371
+ retList.append(tmpPandaID)
1372
+ # return
1373
+ tmp_log.debug(f"ret={str(retList)}")
1374
+ return retList
1375
+ except Exception:
1376
+ # roll back
1377
+ self._rollback()
1378
+ # error report
1379
+ if iTry + 1 < self.nTry:
1380
+ tmp_log.debug(f"retry : {iTry}")
1381
+ time.sleep(random.randint(10, 20))
1382
+ continue
1383
+ self.dump_error_message(tmp_log)
1384
+ return []
1385
+
1386
+ # update input files for jobs at certain sites and return corresponding PandaIDs
1387
+ def update_input_files_at_sites_and_get_panda_ids(self, filename: str, sites: list) -> list:
1388
+ """
1389
+ Update input files with a LFN for jobs at certain sites and return corresponding PandaIDs
1390
+
1391
+ :param filename: LFN of the input file to be updated
1392
+ :param sites: List of site names where the jobs are running
1393
+
1394
+ :return: List of PandaIDs of the jobs whose input files were updated
1395
+ """
1396
+ comment = " /* DBProxy.update_input_files_at_sites_and_get_panda_ids */"
1397
+ tmp_log = self.create_tagged_logger(comment, f"lfn={filename}")
1398
+ tmp_log.debug("start at sites: " + ",".join(sites))
1399
+ sql_to_get_ids = (
1400
+ "SELECT tabJ.PandaID FROM ATLAS_PANDA.jobsDefined4 tabJ, ATLAS_PANDA.filesTable4 tabF WHERE tabF.PandaID=tabJ.PandaID AND tabF.lfn=:lfn "
1401
+ "AND tabF.type='input' AND tabF.status IN ('defined', 'unknown', 'pending') AND tabJ.computingSite IN ("
1402
+ )
1403
+ var_map = {":lfn": filename}
1404
+ for idx, site in enumerate(sites):
1405
+ tmp_key = f":site_{idx}"
1406
+ var_map[tmp_key] = site
1407
+ sql_to_get_ids += f"{tmp_key},"
1408
+ sql_to_get_ids = sql_to_get_ids[:-1] + ") "
1409
+ return_list = []
1410
+ try:
1411
+ # start transaction
1412
+ self.conn.begin()
1413
+ # get IDs
1414
+ self.cur.arraysize = 10000
1415
+ self.cur.execute(sql_to_get_ids + comment, var_map)
1416
+ res = self.cur.fetchall()
1417
+ # commit
1418
+ if not self._commit():
1419
+ raise RuntimeError("Commit error")
1420
+ # sql to update files
1421
+ sql_to_update_files = (
1422
+ "UPDATE ATLAS_PANDA.filesTable4 tab SET status='ready' WHERE PandaID=:PandaID AND lfn=:lfn AND type='input' AND status<>'ready' "
1423
+ )
1424
+ for (tmp_pandaID,) in res:
1425
+ var_map = {":PandaID": tmp_pandaID, ":lfn": filename}
1426
+ # start transaction
1427
+ self.conn.begin()
1428
+ self.cur.execute(sql_to_update_files + comment, var_map)
1429
+ if self.cur.rowcount > 0:
1430
+ tmp_log.debug(f"updated input file status to 'ready' for PandaID={tmp_pandaID}")
1431
+ return_list.append(tmp_pandaID)
1432
+ # commit
1433
+ if not self._commit():
1434
+ raise RuntimeError("Commit error")
1435
+ # return
1436
+ tmp_log.debug(f"done")
1437
+ return return_list
1438
+ except Exception:
1439
+ # roll back
1440
+ self._rollback()
1441
+ self.dump_error_message(tmp_log)
1442
+ return []
1443
+
1444
+ # update output files and return corresponding PandaIDs
1445
+ def updateOutFilesReturnPandaIDs(self, dataset, fileLFN=""):
1446
+ comment = " /* DBProxy.updateOutFilesReturnPandaIDs */"
1447
+ tmp_log = self.create_tagged_logger(comment, f"dataset={dataset}")
1448
+ tmp_log.debug(f"{fileLFN}")
1449
+ sql0 = "SELECT /*+ index(tab FILESTABLE4_DESTDBLOCK_IDX) */ row_ID,PandaID FROM ATLAS_PANDA.filesTable4 tab WHERE destinationDBlock=:destinationDBlock AND status=:status"
1450
+ sql1 = "UPDATE /*+ index(tab FILESTABLE4_DESTDBLOCK_IDX) */ ATLAS_PANDA.filesTable4 tab SET status='ready' WHERE destinationDBlock=:destinationDBlock AND status=:status"
1451
+ varMap = {}
1452
+ varMap[":status"] = "transferring"
1453
+ varMap[":destinationDBlock"] = dataset
1454
+ if fileLFN != "":
1455
+ sql0 += " AND lfn=:lfn"
1456
+ sql1 += " AND lfn=:lfn"
1457
+ varMap[":lfn"] = fileLFN
1458
+ for iTry in range(self.nTry):
1459
+ try:
1460
+ # start transaction
1461
+ self.conn.begin()
1462
+ # select
1463
+ self.cur.arraysize = 10000
1464
+ retS = self.cur.execute(sql0 + comment, varMap)
1465
+ resS = self.cur.fetchall()
1466
+ # update
1467
+ retList = []
1468
+ retU = self.cur.execute(sql1 + comment, varMap)
1469
+ # commit
1470
+ if not self._commit():
1471
+ raise RuntimeError("Commit error")
1472
+ # collect PandaIDs
1473
+ retList = []
1474
+ for tmpRowID, tmpPandaID in resS:
1475
+ # append
1476
+ if tmpPandaID not in retList:
1477
+ retList.append(tmpPandaID)
1478
+ # return
1479
+ tmp_log.debug(f"ret={str(retList)}")
1480
+ return retList
1481
+ except Exception:
1482
+ # roll back
1483
+ self._rollback()
1484
+ # error report
1485
+ if iTry + 1 < self.nTry:
1486
+ tmp_log.debug(f"retry : {iTry}")
1487
+ time.sleep(random.randint(10, 20))
1488
+ continue
1489
+ self.dump_error_message(tmp_log)
1490
+ return []
1491
+
1492
+ # get _dis datasets associated to _sub
1493
+ def getAssociatedDisDatasets(self, subDsName):
1494
+ comment = " /* DBProxy.getAssociatedDisDatasets */"
1495
+ tmp_log = self.create_tagged_logger(comment, f"subDsName={subDsName}")
1496
+ tmp_log.debug(f"start")
1497
+ sqlF = (
1498
+ "SELECT /*+ index(tab FILESTABLE4_DESTDBLOCK_IDX) */ distinct PandaID FROM ATLAS_PANDA.filesTable4 tab WHERE destinationDBlock=:destinationDBlock"
1499
+ )
1500
+ sqlJ = "SELECT distinct dispatchDBlock FROM ATLAS_PANDA.filesTable4 WHERE PandaID=:PandaID AND type=:type"
1501
+ try:
1502
+ # start transaction
1503
+ self.conn.begin()
1504
+ # get PandaIDs
1505
+ varMap = {}
1506
+ varMap[":destinationDBlock"] = subDsName
1507
+ self.cur.arraysize = 10000
1508
+ self.cur.execute(sqlF + comment, varMap)
1509
+ resS = self.cur.fetchall()
1510
+ # commit
1511
+ if not self._commit():
1512
+ raise RuntimeError("Commit error")
1513
+ # loop over all PandaIDs
1514
+ retList = []
1515
+ for (pandaID,) in resS:
1516
+ # start transaction
1517
+ self.conn.begin()
1518
+ # get _dis name
1519
+ varMap = {}
1520
+ varMap[":type"] = "input"
1521
+ varMap[":PandaID"] = pandaID
1522
+ self.cur.arraysize = 1000
1523
+ self.cur.execute(sqlJ + comment, varMap)
1524
+ resD = self.cur.fetchall()
1525
+ # commit
1526
+ if not self._commit():
1527
+ raise RuntimeError("Commit error")
1528
+ # append
1529
+ for (disName,) in resD:
1530
+ if disName is not None and disName not in retList:
1531
+ retList.append(disName)
1532
+ # return
1533
+ tmp_log.debug(f"ret={str(retList)}")
1534
+ return retList
1535
+ except Exception:
1536
+ # roll back
1537
+ self._rollback()
1538
+ self.dump_error_message(tmp_log)
1539
+ return []
1540
+
1541
+ # set GUIDs
1542
+ def setGUIDs(self, files):
1543
+ comment = " /* DBProxy.setGUIDs */"
1544
+ tmp_log = self.create_tagged_logger(comment)
1545
+ tmp_log.debug(f"{files}")
1546
+ sql0 = "UPDATE ATLAS_PANDA.filesTable4 SET GUID=:GUID,fsize=:fsize,checksum=:checksum,scope=:scope WHERE lfn=:lfn"
1547
+ for iTry in range(self.nTry):
1548
+ try:
1549
+ # start transaction
1550
+ self.conn.begin()
1551
+ self.cur.arraysize = 1000000
1552
+ # update
1553
+ for file in files:
1554
+ varMap = {}
1555
+ varMap[":GUID"] = file["guid"]
1556
+ varMap[":lfn"] = file["lfn"]
1557
+ if file["checksum"] in ["", "NULL"]:
1558
+ varMap[":checksum"] = None
1559
+ else:
1560
+ varMap[":checksum"] = file["checksum"]
1561
+ varMap[":fsize"] = file["fsize"]
1562
+ if "scope" not in file or file["scope"] in ["", "NULL"]:
1563
+ varMap[":scope"] = None
1564
+ else:
1565
+ varMap[":scope"] = file["scope"]
1566
+ self.cur.execute(sql0 + comment, varMap)
1567
+ retU = self.cur.rowcount
1568
+ tmp_log.debug(f"retU {retU}")
1569
+ if retU < 0:
1570
+ raise RuntimeError("SQL error")
1571
+ # commit
1572
+ if not self._commit():
1573
+ raise RuntimeError("Commit error")
1574
+ return True
1575
+ except Exception:
1576
+ # roll back
1577
+ self._rollback()
1578
+ # error report
1579
+ if iTry + 1 < self.nTry:
1580
+ tmp_log.debug(f"retry : {iTry}")
1581
+ time.sleep(random.randint(10, 20))
1582
+ continue
1583
+ self.dump_error_message(tmp_log)
1584
+ return False
1585
+
1586
+ # get special dispatcher parameters
1587
+ def get_special_dispatch_params(self):
1588
+ """
1589
+ Get the following special parameters for dispatcher.Z
1590
+ Authorized name lists for proxy, key-pair, and token-key retrieval
1591
+ Key pairs
1592
+ Token keys
1593
+ """
1594
+ comment = " /* DBProxy.get_special_dispatch_params */"
1595
+ tmp_log = self.create_tagged_logger(comment)
1596
+ tmp_log.debug("start")
1597
+ try:
1598
+ return_map = {}
1599
+ # set autocommit on
1600
+ self.conn.begin()
1601
+ self.cur.arraysize = 100000
1602
+ # get token keys
1603
+ token_keys = {}
1604
+ sql = f"SELECT dn, credname FROM {panda_config.schemaMETA}.proxykey WHERE expires>:limit ORDER BY expires DESC "
1605
+ var_map = {":limit": naive_utcnow()}
1606
+ self.cur.execute(sql + comment, var_map)
1607
+ res_list = self.cur.fetchall()
1608
+ for client_name, token_key in res_list:
1609
+ token_keys.setdefault(client_name, {"fullList": [], "latest": token_key})
1610
+ token_keys[client_name]["fullList"].append(token_key)
1611
+ return_map["tokenKeys"] = token_keys
1612
+ tmp_list = [f"""{k}:{len(token_keys[k]["fullList"])}""" for k in token_keys]
1613
+ tmp_log.debug(f"""got token keys {",".join(tmp_list)}""")
1614
+ # select to get the list of authorized users
1615
+ allow_key = []
1616
+ allow_proxy = []
1617
+ allow_token = []
1618
+ sql = "SELECT DISTINCT name, gridpref FROM ATLAS_PANDAMETA.users " "WHERE (status IS NULL OR status<>:ngStatus) AND gridpref IS NOT NULL "
1619
+ var_map = {":ngStatus": "disabled"}
1620
+ self.cur.execute(sql + comment, var_map)
1621
+ res_list = self.cur.fetchall()
1622
+ # commit
1623
+ if not self._commit():
1624
+ raise RuntimeError("Commit error")
1625
+ for compactDN, gridpref in res_list:
1626
+ # users authorized for proxy retrieval
1627
+ if PrioUtil.PERMISSION_PROXY in gridpref:
1628
+ if compactDN not in allow_proxy:
1629
+ allow_proxy.append(compactDN)
1630
+ # users authorized for key-pair retrieval
1631
+ if PrioUtil.PERMISSION_KEY in gridpref:
1632
+ if compactDN not in allow_key:
1633
+ allow_key.append(compactDN)
1634
+ # users authorized for token-key retrieval
1635
+ if PrioUtil.PERMISSION_TOKEN_KEY in gridpref:
1636
+ if compactDN not in allow_token:
1637
+ allow_token.append(compactDN)
1638
+ return_map["allowKeyPair"] = allow_key
1639
+ return_map["allowProxy"] = allow_proxy
1640
+ return_map["allowTokenKey"] = allow_token
1641
+ tmp_log.debug(
1642
+ f"got authed users key-pair:{len(return_map['allowKeyPair'])}, proxy:{len(return_map['allowProxy'])}, token-key:{len(return_map['allowTokenKey'])}"
1643
+ )
1644
+ # read key pairs
1645
+ keyPair = {}
1646
+ try:
1647
+ keyFileNames = glob.glob(panda_config.keyDir + "/*")
1648
+ for keyName in keyFileNames:
1649
+ tmpF = open(keyName)
1650
+ keyPair[os.path.basename(keyName)] = tmpF.read()
1651
+ tmpF.close()
1652
+ except Exception as e:
1653
+ tmp_log.error(f"failed read key-pairs with {str(e)}")
1654
+ return_map["keyPair"] = keyPair
1655
+ tmp_log.debug(f"got {len(return_map['keyPair'])} key-pair files")
1656
+ tmp_log.debug("done")
1657
+ return return_map
1658
+ except Exception:
1659
+ # roll back
1660
+ self._rollback()
1661
+ # error
1662
+ self.dump_error_message(tmp_log)
1663
+ return {}
1664
+
1665
+ # get original consumers
1666
+ def getOriginalConsumers(self, jediTaskID, jobsetID, pandaID):
1667
+ comment = " /* DBProxy.getOriginalConsumers */"
1668
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} jobsetID={jobsetID} PandaID={pandaID}")
1669
+ tmp_log.debug("start")
1670
+ try:
1671
+ # sql to get sites where consumers are active
1672
+ sqlA = "SELECT computingSite FROM ATLAS_PANDA.jobsActive4 WHERE jediTaskID=:jediTaskID AND jobsetID=:jobsetID "
1673
+ sqlA += "UNION "
1674
+ sqlA += "SELECT computingSite FROM ATLAS_PANDA.jobsDefined4 WHERE jediTaskID=:jediTaskID AND jobsetID=:jobsetID "
1675
+ # sql to get original IDs
1676
+ sqlG = f"SELECT oldPandaID FROM {panda_config.schemaJEDI}.JEDI_Job_Retry_History "
1677
+ sqlG += "WHERE jediTaskID=:jediTaskID AND newPandaID=:jobsetID AND relationType=:relationType "
1678
+ # sql to check computingSite
1679
+ sqlC1 = "SELECT computingSite FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
1680
+ sqlC2 = "SELECT computingSite FROM ATLAS_PANDAARCH.jobsArchived WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-30) "
1681
+ # sql to get job info
1682
+ sqlJ = f"SELECT {JobSpec.columnNames()} "
1683
+ sqlJ += "FROM {0} "
1684
+ sqlJ += "WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-30) "
1685
+ sqlF = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
1686
+ sqlF += "WHERE PandaID=:PandaID "
1687
+ sqlP = "SELECT jobParameters FROM {0} WHERE PandaID=:PandaID "
1688
+ # get sites
1689
+ aSites = set()
1690
+ varMap = dict()
1691
+ varMap[":jediTaskID"] = jediTaskID
1692
+ varMap[":jobsetID"] = jobsetID
1693
+ self.cur.execute(sqlA + comment, varMap)
1694
+ resA = self.cur.fetchall()
1695
+ for (computingSite,) in resA:
1696
+ aSites.add(computingSite)
1697
+ # get original IDs
1698
+ varMap = dict()
1699
+ varMap[":jediTaskID"] = jediTaskID
1700
+ varMap[":jobsetID"] = jobsetID
1701
+ varMap[":relationType"] = EventServiceUtils.relationTypeJS_Map
1702
+ self.cur.execute(sqlG + comment, varMap)
1703
+ resG = self.cur.fetchall()
1704
+ jobList = []
1705
+ for (pandaID,) in resG:
1706
+ # check computingSite
1707
+ varMap = dict()
1708
+ varMap[":PandaID"] = pandaID
1709
+ self.cur.execute(sqlC1 + comment, varMap)
1710
+ resC = self.cur.fetchone()
1711
+ if resC is None:
1712
+ # try archived
1713
+ self.cur.execute(sqlC2 + comment, varMap)
1714
+ resC = self.cur.fetchone()
1715
+ inArchived = True
1716
+ else:
1717
+ inArchived = False
1718
+ # skip since it is not yet archived and thus is still active
1719
+ if resC is None:
1720
+ continue
1721
+ (computingSite,) = resC
1722
+ # skip since there is an active consumer at the site
1723
+ if computingSite in aSites:
1724
+ continue
1725
+ # get job
1726
+ if inArchived:
1727
+ self.cur.execute(sqlJ.format("ATLAS_PANDAARCH.jobsArchived") + comment, varMap)
1728
+ else:
1729
+ self.cur.execute(sqlJ.format("ATLAS_PANDA.jobsArchived4") + comment, varMap)
1730
+ resJ = self.cur.fetchone()
1731
+ if resJ is not None:
1732
+ jobSpec = JobSpec()
1733
+ jobSpec.pack(resJ)
1734
+ # get files
1735
+ self.cur.execute(sqlF + comment, varMap)
1736
+ resFs = self.cur.fetchall()
1737
+ if len(resFs) == 0:
1738
+ continue
1739
+ for resF in resFs:
1740
+ fileSpec = FileSpec()
1741
+ fileSpec.pack(resF)
1742
+ jobSpec.addFile(fileSpec)
1743
+ # get job params
1744
+ if inArchived:
1745
+ self.cur.execute(
1746
+ sqlP.format("ATLAS_PANDAARCH.jobParamsTable_ARCH") + comment,
1747
+ varMap,
1748
+ )
1749
+ else:
1750
+ self.cur.execute(sqlP.format("ATLAS_PANDA.jobParamsTable") + comment, varMap)
1751
+ for (clobJobP,) in self.cur:
1752
+ if clobJobP is not None:
1753
+ try:
1754
+ jobSpec.jobParameters = clobJobP.read()
1755
+ except AttributeError:
1756
+ jobSpec.jobParameters = str(clobJobP)
1757
+ break
1758
+ # add
1759
+ jobList.append(jobSpec)
1760
+ aSites.add(computingSite)
1761
+ tmp_log.debug(f"got {len(jobList)} consumers")
1762
+ return jobList
1763
+ except Exception:
1764
+ # error
1765
+ self.dump_error_message(tmp_log)
1766
+ return []
1767
+
1768
+ # update unmerged datasets to trigger merging
1769
+ def updateUnmergedDatasets(self, job, finalStatusDS, updateCompleted=False):
1770
+ comment = " /* JediDBProxy.updateUnmergedDatasets */"
1771
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={job.PandaID}")
1772
+ # get PandaID which produced unmerged files
1773
+ umPandaIDs = []
1774
+ umCheckedIDs = []
1775
+ # sql to get file counts
1776
+ sqlGFC = "SELECT status,PandaID,outPandaID FROM ATLAS_PANDA.JEDI_Dataset_Contents "
1777
+ sqlGFC += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND PandaID IS NOT NULL "
1778
+ # sql to update nFiles in JEDI datasets
1779
+ sqlUNF = "UPDATE ATLAS_PANDA.JEDI_Datasets "
1780
+ sqlUNF += "SET nFilesOnHold=0,nFiles=:nFiles,"
1781
+ sqlUNF += "nFilesUsed=:nFilesUsed,nFilesTobeUsed=:nFilesTobeUsed "
1782
+ sqlUNF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1783
+ # sql to check nFiles
1784
+ sqlUCF = "SELECT nFilesTobeUsed,nFilesUsed FROM ATLAS_PANDA.JEDI_Datasets "
1785
+ sqlUCF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1786
+ # sql to update dataset status
1787
+ sqlUDS = "UPDATE ATLAS_PANDA.JEDI_Datasets "
1788
+ sqlUDS += "SET status=:status,modificationTime=CURRENT_DATE "
1789
+ sqlUDS += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1790
+ # sql to update dataset status in panda
1791
+ sqlUDP = "UPDATE ATLAS_PANDA.Datasets "
1792
+ sqlUDP += "SET status=:status "
1793
+ sqlUDP += "WHERE vuid=:vuid AND NOT status IN (:statusR,:statusD) "
1794
+ try:
1795
+ tmp_log.debug(f"start")
1796
+ # begin transaction
1797
+ self.conn.begin()
1798
+ # update dataset in panda
1799
+ toSkip = False
1800
+ for datasetSpec in finalStatusDS:
1801
+ varMap = {}
1802
+ varMap[":vuid"] = datasetSpec.vuid
1803
+ varMap[":status"] = "tobeclosed"
1804
+ varMap[":statusR"] = "tobeclosed"
1805
+ if not updateCompleted:
1806
+ varMap[":statusD"] = "completed"
1807
+ else:
1808
+ varMap[":statusD"] = "dummy"
1809
+ tmp_log.debug(sqlUDP + comment + str(varMap))
1810
+ self.cur.execute(sqlUDP + comment, varMap)
1811
+ nRow = self.cur.rowcount
1812
+ if nRow != 1:
1813
+ toSkip = True
1814
+ tmp_log.debug(f"failed to lock {datasetSpec.name}")
1815
+ # look for unmerged files
1816
+ if not toSkip:
1817
+ updatedDS = []
1818
+ for tmpFile in job.Files:
1819
+ if tmpFile.isUnMergedOutput():
1820
+ if tmpFile.datasetID in updatedDS:
1821
+ continue
1822
+ updatedDS.append(tmpFile.datasetID)
1823
+ # get file counts
1824
+ varMap = {}
1825
+ varMap[":jediTaskID"] = tmpFile.jediTaskID
1826
+ varMap[":datasetID"] = tmpFile.datasetID
1827
+ self.cur.arraysize = 100000
1828
+ tmp_log.debug(sqlGFC + comment + str(varMap))
1829
+ self.cur.execute(sqlGFC + comment, varMap)
1830
+ resListGFC = self.cur.fetchall()
1831
+ varMap = {}
1832
+ tmpNumFiles = 0
1833
+ tmpNumReady = 0
1834
+ for tmpFileStatus, tmpPandaID, tmpOutPandaID in resListGFC:
1835
+ if tmpFileStatus in [
1836
+ "finished",
1837
+ "failed",
1838
+ "cancelled",
1839
+ "notmerged",
1840
+ "ready",
1841
+ "lost",
1842
+ "broken",
1843
+ "picked",
1844
+ "nooutput",
1845
+ ]:
1846
+ pass
1847
+ elif tmpFileStatus == "running" and tmpPandaID != tmpOutPandaID:
1848
+ pass
1849
+ else:
1850
+ continue
1851
+ tmpNumFiles += 1
1852
+ if tmpFileStatus in ["ready"]:
1853
+ tmpNumReady += 1
1854
+ # update nFiles
1855
+ varMap = {}
1856
+ varMap[":jediTaskID"] = tmpFile.jediTaskID
1857
+ varMap[":datasetID"] = tmpFile.datasetID
1858
+ varMap[":nFiles"] = tmpNumFiles
1859
+ varMap[":nFilesTobeUsed"] = tmpNumFiles
1860
+ varMap[":nFilesUsed"] = tmpNumFiles - tmpNumReady
1861
+ self.cur.arraysize = 10
1862
+ tmp_log.debug(sqlUNF + comment + str(varMap))
1863
+ self.cur.execute(sqlUNF + comment, varMap)
1864
+ nRow = self.cur.rowcount
1865
+ if nRow == 1:
1866
+ # check nFilesTobeUsed
1867
+ varMap = {}
1868
+ varMap[":jediTaskID"] = tmpFile.jediTaskID
1869
+ varMap[":datasetID"] = tmpFile.datasetID
1870
+ self.cur.execute(sqlUCF + comment, varMap)
1871
+ resUCF = self.cur.fetchone()
1872
+ if resUCF is not None:
1873
+ nFilesTobeUsed, nFilesUsed = resUCF
1874
+ varMap = {}
1875
+ varMap[":jediTaskID"] = tmpFile.jediTaskID
1876
+ varMap[":datasetID"] = tmpFile.datasetID
1877
+ if nFilesTobeUsed - nFilesUsed > 0:
1878
+ varMap[":status"] = "ready"
1879
+ else:
1880
+ varMap[":status"] = "done"
1881
+ # update dataset status
1882
+ tmp_log.debug(sqlUDS + comment + str(varMap))
1883
+ self.cur.execute(sqlUDS + comment, varMap)
1884
+ else:
1885
+ tmp_log.debug(f"skip jediTaskID={tmpFile.jediTaskID} datasetID={tmpFile.datasetID}")
1886
+ # commit
1887
+ if not self._commit():
1888
+ raise RuntimeError("Commit error")
1889
+ tmp_log.debug(f"done")
1890
+ return True
1891
+ except Exception:
1892
+ # roll back
1893
+ self._rollback()
1894
+ # error
1895
+ self.dump_error_message(tmp_log)
1896
+ return False
1897
+
1898
+ # get throttled users
1899
+ def getThrottledUsers(self):
1900
+ comment = " /* DBProxy.getThrottledUsers */"
1901
+ tmp_log = self.create_tagged_logger(comment)
1902
+ tmp_log.debug("start")
1903
+ retVal = set()
1904
+ try:
1905
+ # sql to get users
1906
+ sqlT = "SELECT distinct prodUserName,workingGroup FROM ATLAS_PANDA.jobsActive4 "
1907
+ sqlT += "WHERE prodSourceLabel=:prodSourceLabel AND jobStatus=:jobStatus AND relocationFlag=:relocationFlag "
1908
+ # start transaction
1909
+ self.conn.begin()
1910
+ # select
1911
+ self.cur.arraysize = 10
1912
+ varMap = {}
1913
+ varMap[":prodSourceLabel"] = "user"
1914
+ varMap[":relocationFlag"] = 3
1915
+ varMap[":jobStatus"] = "throttled"
1916
+ # get datasets
1917
+ self.cur.execute(sqlT + comment, varMap)
1918
+ resPs = self.cur.fetchall()
1919
+ for prodUserName, workingGroup in resPs:
1920
+ retVal.add((prodUserName, workingGroup))
1921
+ # commit
1922
+ if not self._commit():
1923
+ raise RuntimeError("Commit error")
1924
+ tmp_log.debug(f"done with {str(retVal)}")
1925
+ return retVal
1926
+ except Exception:
1927
+ # roll back
1928
+ self._rollback()
1929
+ # error
1930
+ self.dump_error_message(tmp_log)
1931
+ return []
1932
+
1933
+ # reset files in JEDI
1934
+ def resetFileStatusInJEDI(
1935
+ self, dn: str, is_prod_manager: bool, dataset_name: str, lost_files: List[str], recover_parent: bool, simul: bool
1936
+ ) -> Tuple[bool, int | None, Dict[str, List[str]] | None, str | None]:
1937
+ """
1938
+ Reset file status in JEDI for lost files
1939
+ 1) check ownership
1940
+ 2) set file status from 'finished' to 'lost' in JEDI_Dataset_Contents
1941
+ 3) cancel events in JEDI_Events
1942
+ 4) if recoverParent is True, set parent files from 'finished' to 'ready'
1943
+ 5) if simul is True, do not commit changes
1944
+ 6) return list of input files that produced the lost files
1945
+
1946
+ :param dn: DN of the requester
1947
+ :param is_prod_manager: whether the requester is a production manager
1948
+ :param dataset_name: name of the dataset
1949
+ :param lost_files: list of lost LFNs
1950
+ :param recover_parent: whether to recover parent files
1951
+ :param simul: whether to simulate without committing changes
1952
+ :return: tuple of
1953
+ success flag,
1954
+ jediTaskID (None if failed),
1955
+ dictionary of list of input files that produced the lost files (None if failed),
1956
+ error message (None if succeeded)
1957
+ """
1958
+ comment = " /* DBProxy.resetFileStatusInJEDI */"
1959
+ tmp_log = self.create_tagged_logger(comment, f"datasetName={dataset_name}")
1960
+ tmp_log.debug("start")
1961
+ error_message = None
1962
+ try:
1963
+ # list of lost input files
1964
+ lostInputFiles = {}
1965
+ # get compact DN
1966
+ compactDN = CoreUtils.clean_user_id(dn)
1967
+ if compactDN in ["", "NULL", None]:
1968
+ compactDN = dn
1969
+ tmp_log.debug(f"userName={compactDN}")
1970
+ toSkip = False
1971
+ # begin transaction
1972
+ self.conn.begin()
1973
+ # get jediTaskID
1974
+ varMap = {}
1975
+ varMap[":type1"] = "log"
1976
+ varMap[":type2"] = "output"
1977
+ varMap[":name1"] = dataset_name
1978
+ varMap[":name2"] = dataset_name.split(":")[-1]
1979
+ sqlGI = f"SELECT jediTaskID,datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets "
1980
+ sqlGI += "WHERE type IN (:type1,:type2) AND datasetName IN (:name1,:name2) "
1981
+ self.cur.execute(sqlGI + comment, varMap)
1982
+ resGI = self.cur.fetchall()
1983
+ # use the largest datasetID since broken tasks might have been retried
1984
+ jediTaskID = None
1985
+ datasetID = None
1986
+ for tmpJediTaskID, tmpDatasetID in resGI:
1987
+ if jediTaskID is None or jediTaskID < tmpJediTaskID:
1988
+ jediTaskID = tmpJediTaskID
1989
+ datasetID = tmpDatasetID
1990
+ elif datasetID < tmpDatasetID:
1991
+ datasetID = tmpDatasetID
1992
+ if jediTaskID is None:
1993
+ error_message = "jediTaskID not found"
1994
+ tmp_log.debug(error_message)
1995
+ toSkip = True
1996
+ if not toSkip:
1997
+ # get task status and owner
1998
+ tmp_log.debug(f"jediTaskID={jediTaskID} datasetID={datasetID}")
1999
+ varMap = {}
2000
+ varMap[":jediTaskID"] = jediTaskID
2001
+ sqlOW = f"SELECT status,userName,useJumbo FROM {panda_config.schemaJEDI}.JEDI_Tasks "
2002
+ sqlOW += "WHERE jediTaskID=:jediTaskID "
2003
+ self.cur.execute(sqlOW + comment, varMap)
2004
+ resOW = self.cur.fetchone()
2005
+ taskStatus, ownerName, useJumbo = resOW
2006
+ # check ownership
2007
+ if not is_prod_manager and ownerName != compactDN:
2008
+ error_message = f"Not the owner: {ownerName} != {compactDN}"
2009
+ tmp_log.debug(error_message)
2010
+ toSkip = True
2011
+ if not toSkip:
2012
+ # get affected PandaIDs
2013
+ sqlLP = f"SELECT pandaID FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2014
+ sqlLP += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND lfn=:lfn "
2015
+ # get files to update status
2016
+ sql_get_files = (
2017
+ f"SELECT datasetID,fileID FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2018
+ "WHERE jediTaskID=:jediTaskID AND type=:type AND status=:oldStatus AND PandaID=:PandaID "
2019
+ )
2020
+ # sql to update file status
2021
+ sqlUFO = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2022
+ sqlUFO += "SET status=:newStatus "
2023
+ sqlUFO += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID AND status=:oldStatus AND PandaID=:PandaID "
2024
+ # sql to cancel events
2025
+ sqlCE = "UPDATE /*+ INDEX_RS_ASC(tab JEDI_EVENTS_FILEID_IDX) NO_INDEX_FFS(tab JEDI_EVENTS_PK) NO_INDEX_SS(tab JEDI_EVENTS_PK) */ "
2026
+ sqlCE += f"{panda_config.schemaJEDI}.JEDI_Events tab "
2027
+ sqlCE += "SET status=:status "
2028
+ sqlCE += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID "
2029
+ sqlCE += "AND status IN (:esFinished,:esDone,:esMerged) "
2030
+ # get affected PandaIDs
2031
+ lostPandaIDs = set([])
2032
+ nDiff = {}
2033
+ for lostFile in lost_files:
2034
+ varMap = {}
2035
+ varMap[":jediTaskID"] = jediTaskID
2036
+ varMap[":datasetID"] = datasetID
2037
+ varMap[":lfn"] = lostFile
2038
+ self.cur.execute(sqlLP + comment, varMap)
2039
+ resLP = self.cur.fetchone()
2040
+ if resLP is not None:
2041
+ (pandaID,) = resLP
2042
+ lostPandaIDs.add(pandaID)
2043
+ # get the file and co-produced files to lost
2044
+ varMap = {}
2045
+ varMap[":jediTaskID"] = jediTaskID
2046
+ varMap[":PandaID"] = pandaID
2047
+ varMap[":type"] = "output"
2048
+ varMap[":oldStatus"] = "finished"
2049
+ self.cur.execute(sql_get_files + comment, varMap)
2050
+ res_files = self.cur.fetchall()
2051
+ if res_files:
2052
+ for tmpDatasetID, tmpFileID in res_files:
2053
+ # update file status
2054
+ varMap = {}
2055
+ varMap[":jediTaskID"] = jediTaskID
2056
+ varMap[":datasetID"] = tmpDatasetID
2057
+ varMap[":fileID"] = tmpFileID
2058
+ varMap[":PandaID"] = pandaID
2059
+ varMap[":newStatus"] = "lost"
2060
+ varMap[":oldStatus"] = "finished"
2061
+ tmp_log.debug(sqlUFO + comment + str(varMap))
2062
+ if not simul:
2063
+ self.cur.execute(sqlUFO + comment, varMap)
2064
+ nRow = self.cur.rowcount
2065
+ if nRow > 0:
2066
+ nDiff[tmpDatasetID] = nDiff.get(tmpDatasetID, 0) + 1
2067
+ else:
2068
+ nDiff[tmpDatasetID] = nDiff.get(tmpDatasetID, 0) + 1
2069
+ tmp_log.debug(f"PandaIDs produced lost files = {str(lostPandaIDs)}")
2070
+ tmp_log.debug("update output datasets")
2071
+ # get nEvents
2072
+ sqlGNE = "SELECT SUM(c.nEvents) "
2073
+ sqlGNE += "FROM {0}.JEDI_Datasets d,{0}.JEDI_Dataset_Contents c ".format(panda_config.schemaJEDI)
2074
+ sqlGNE += "WHERE c.jediTaskID=d.jediTaskID AND c.datasetID=d.datasetID "
2075
+ sqlGNE += "AND d.jediTaskID=:jediTaskID AND d.datasetID=:datasetID AND c.status=:status "
2076
+ # update output dataset statistics
2077
+ sqlUDO = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
2078
+ sqlUDO += "SET nFilesFinished=nFilesFinished-:nDiff,state=NULL,nEvents=:nEvents "
2079
+ sqlUDO += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
2080
+ for tmpDatasetID, n_diff_value in nDiff.items():
2081
+ # get nEvents
2082
+ varMap = {}
2083
+ varMap[":jediTaskID"] = jediTaskID
2084
+ varMap[":datasetID"] = tmpDatasetID
2085
+ varMap[":status"] = "finished"
2086
+ self.cur.execute(sqlGNE + comment, varMap)
2087
+ (tmp_counts,) = self.cur.fetchone()
2088
+ # update nFilesFinished
2089
+ varMap = {}
2090
+ varMap[":jediTaskID"] = jediTaskID
2091
+ varMap[":datasetID"] = tmpDatasetID
2092
+ varMap[":nDiff"] = n_diff_value
2093
+ varMap[":nEvents"] = tmp_counts
2094
+ tmp_log.debug(sqlUDO + comment + str(varMap))
2095
+ if not simul:
2096
+ self.cur.execute(sqlUDO + comment, varMap)
2097
+ # get input datasets
2098
+ tmp_log.debug("update input datasets")
2099
+ sqlID = f"SELECT datasetID,datasetName,masterID FROM {panda_config.schemaJEDI}.JEDI_Datasets "
2100
+ sqlID += "WHERE jediTaskID=:jediTaskID AND type=:type "
2101
+ varMap = {}
2102
+ varMap[":jediTaskID"] = jediTaskID
2103
+ varMap[":type"] = "input"
2104
+ self.cur.execute(sqlID + comment, varMap)
2105
+ resID = self.cur.fetchall()
2106
+ inputDatasets = {}
2107
+ masterID = None
2108
+ for tmpDatasetID, tmpDatasetName, tmpMasterID in resID:
2109
+ inputDatasets[tmpDatasetID] = tmpDatasetName
2110
+ if tmpMasterID is None:
2111
+ masterID = tmpDatasetID
2112
+ # sql to get affected inputs
2113
+ if useJumbo is None:
2114
+ sqlAI = f"SELECT fileID,datasetID,lfn,outPandaID FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2115
+ sqlAI += "WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2,:type3) AND PandaID=:PandaID "
2116
+ else:
2117
+ sqlAI = f"SELECT fileID,datasetID,lfn,NULL FROM {panda_config.schemaPANDA}.filesTable4 "
2118
+ sqlAI += "WHERE PandaID=:PandaID AND type IN (:type1,:type2) "
2119
+ sqlAI += "UNION "
2120
+ sqlAI = f"SELECT fileID,datasetID,lfn,NULL FROM {panda_config.schemaPANDA}.filesTable4 "
2121
+ sqlAI += "WHERE PandaID=:PandaID AND type IN (:type1,:type2) AND modificationTime>CURRENT_TIMESTAMP-365 "
2122
+ # sql to update input file status
2123
+ sqlUFI = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2124
+ sqlUFI += "SET status=:newStatus,attemptNr=attemptNr+1 "
2125
+ sqlUFI += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID AND status=:oldStatus "
2126
+ # get affected inputs
2127
+ datasetCountMap = {}
2128
+ for lostPandaID in lostPandaIDs:
2129
+ varMap = {}
2130
+ if useJumbo is None:
2131
+ varMap[":jediTaskID"] = jediTaskID
2132
+ varMap[":PandaID"] = lostPandaID
2133
+ varMap[":type1"] = "input"
2134
+ varMap[":type2"] = "pseudo_input"
2135
+ varMap[":type3"] = "output"
2136
+ else:
2137
+ varMap[":PandaID"] = lostPandaID
2138
+ varMap[":type1"] = "input"
2139
+ varMap[":type2"] = "pseudo_input"
2140
+ self.cur.execute(sqlAI + comment, varMap)
2141
+ resAI = self.cur.fetchall()
2142
+ newResAI = []
2143
+ for tmpItem in resAI:
2144
+ tmpFileID, tmpDatasetID, tmpLFN, tmpOutPandaID = tmpItem
2145
+ # skip output file
2146
+ if lostPandaID == tmpOutPandaID:
2147
+ continue
2148
+ # input for merged files
2149
+ if tmpOutPandaID is not None:
2150
+ varMap = {}
2151
+ varMap[":jediTaskID"] = jediTaskID
2152
+ varMap[":PandaID"] = tmpOutPandaID
2153
+ varMap[":type1"] = "input"
2154
+ varMap[":type2"] = "pseudo_input"
2155
+ varMap[":type3"] = "dummy"
2156
+ self.cur.execute(sqlAI + comment, varMap)
2157
+ resAI2 = self.cur.fetchall()
2158
+ for tmpItem in resAI2:
2159
+ newResAI.append(tmpItem)
2160
+ else:
2161
+ newResAI.append(tmpItem)
2162
+ for tmpFileID, tmpDatasetID, tmpLFN, tmpOutPandaID in newResAI:
2163
+ # collect if dataset was already deleted
2164
+ is_lost = False
2165
+ if recover_parent and tmpDatasetID == masterID:
2166
+ lostInputFiles.setdefault(inputDatasets[tmpDatasetID], [])
2167
+ lostInputFiles[inputDatasets[tmpDatasetID]].append(tmpLFN)
2168
+ is_lost = True
2169
+ # reset file status
2170
+ if tmpDatasetID not in datasetCountMap:
2171
+ datasetCountMap[tmpDatasetID] = 0
2172
+ varMap = {}
2173
+ varMap[":jediTaskID"] = jediTaskID
2174
+ varMap[":datasetID"] = tmpDatasetID
2175
+ varMap[":fileID"] = tmpFileID
2176
+ if is_lost:
2177
+ varMap[":newStatus"] = "lost"
2178
+ else:
2179
+ varMap[":newStatus"] = "ready"
2180
+ varMap[":oldStatus"] = "finished"
2181
+ if not simul:
2182
+ self.cur.execute(sqlUFI + comment, varMap)
2183
+ nRow = self.cur.rowcount
2184
+ else:
2185
+ tmp_log.debug(sqlUFI + comment + str(varMap))
2186
+ nRow = 1
2187
+ if nRow > 0:
2188
+ datasetCountMap[tmpDatasetID] += 1
2189
+ if useJumbo is not None:
2190
+ # cancel events
2191
+ varMap = {}
2192
+ varMap[":jediTaskID"] = jediTaskID
2193
+ varMap[":datasetID"] = tmpDatasetID
2194
+ varMap[":fileID"] = tmpFileID
2195
+ varMap[":status"] = EventServiceUtils.ST_cancelled
2196
+ varMap[":esFinished"] = EventServiceUtils.ST_finished
2197
+ varMap[":esDone"] = EventServiceUtils.ST_done
2198
+ varMap[":esMerged"] = EventServiceUtils.ST_merged
2199
+ if not simul:
2200
+ self.cur.execute(sqlCE + comment, varMap)
2201
+ else:
2202
+ tmp_log.debug(sqlCE + comment + str(varMap))
2203
+ # update dataset statistics
2204
+ sqlUDI = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
2205
+ sqlUDI += "SET nFilesUsed=nFilesUsed-:nDiff,nFilesFinished=nFilesFinished-:nDiff,"
2206
+ sqlUDI += "nEventsUsed=(SELECT SUM(CASE WHEN startEvent IS NULL THEN nEvents ELSE endEvent-startEvent+1 END) "
2207
+ sqlUDI += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2208
+ sqlUDI += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND status=:status) "
2209
+ sqlUDI += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
2210
+ for tmpDatasetID in datasetCountMap:
2211
+ nDiff = datasetCountMap[tmpDatasetID]
2212
+ varMap = {}
2213
+ varMap[":jediTaskID"] = jediTaskID
2214
+ varMap[":datasetID"] = tmpDatasetID
2215
+ varMap[":nDiff"] = nDiff
2216
+ varMap[":status"] = "finished"
2217
+ tmp_log.debug(sqlUDI + comment + str(varMap))
2218
+ if not simul:
2219
+ self.cur.execute(sqlUDI + comment, varMap)
2220
+ # update task status
2221
+ if taskStatus == "done":
2222
+ sqlUT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET status=:newStatus WHERE jediTaskID=:jediTaskID "
2223
+ varMap = {}
2224
+ varMap[":jediTaskID"] = jediTaskID
2225
+ varMap[":newStatus"] = "finished"
2226
+ if not simul:
2227
+ self.cur.execute(sqlUT + comment, varMap)
2228
+ # commit
2229
+ if not self._commit():
2230
+ raise RuntimeError("Commit error")
2231
+ tmp_log.debug("done")
2232
+ return True, jediTaskID, lostInputFiles, error_message
2233
+ except Exception:
2234
+ # roll back
2235
+ self._rollback()
2236
+ # error
2237
+ self.dump_error_message(tmp_log)
2238
+ return False, None, None, "database error"
2239
+
2240
+ # copy file records
2241
+ def copy_file_records(self, new_lfns, file_spec):
2242
+ comment = " /* DBProxy.copy_file_records */"
2243
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={file_spec.PandaID} oldLFN={file_spec.lfn}")
2244
+ tmp_log.debug(f"start with {len(new_lfns)} files")
2245
+ try:
2246
+ # begin transaction
2247
+ self.conn.begin()
2248
+ for idx_lfn, new_lfn in enumerate(new_lfns):
2249
+ # reset rowID
2250
+ tmpFileSpec = copy.copy(file_spec)
2251
+ tmpFileSpec.lfn = new_lfn
2252
+ if idx_lfn > 0:
2253
+ tmpFileSpec.row_ID = None
2254
+ # insert file in JEDI
2255
+ if idx_lfn > 0 and tmpFileSpec.jediTaskID not in [None, "NULL"] and tmpFileSpec.fileID not in ["", "NULL", None]:
2256
+ # get fileID
2257
+ sqlFileID = "SELECT ATLAS_PANDA.JEDI_DATASET_CONT_FILEID_SEQ.nextval FROM dual "
2258
+ self.cur.execute(sqlFileID + comment)
2259
+ (newFileID,) = self.cur.fetchone()
2260
+ # read file in JEDI
2261
+ varMap = {}
2262
+ varMap[":jediTaskID"] = tmpFileSpec.jediTaskID
2263
+ varMap[":datasetID"] = tmpFileSpec.datasetID
2264
+ varMap[":fileID"] = tmpFileSpec.fileID
2265
+ sqlGI = f"SELECT * FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2266
+ sqlGI += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID "
2267
+ self.cur.execute(sqlGI + comment, varMap)
2268
+ resGI = self.cur.fetchone()
2269
+ tmpFileSpec.fileID = newFileID
2270
+ if resGI is not None:
2271
+ # make sql and map
2272
+ sqlJI = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2273
+ sqlJI += "VALUES ("
2274
+ varMap = {}
2275
+ for columDesc, columVal in zip(self.cur.description, resGI):
2276
+ columName = columDesc[0]
2277
+ # overwrite fileID
2278
+ if columName.upper() == "FILEID":
2279
+ columVal = tmpFileSpec.fileID
2280
+ keyName = f":{columName}"
2281
+ varMap[keyName] = columVal
2282
+ sqlJI += f"{keyName},"
2283
+ sqlJI = sqlJI[:-1]
2284
+ sqlJI += ") "
2285
+ # insert file in JEDI
2286
+ self.cur.execute(sqlJI + comment, varMap)
2287
+ if idx_lfn > 0:
2288
+ # insert file in Panda
2289
+ sqlFile = f"INSERT INTO ATLAS_PANDA.filesTable4 ({FileSpec.columnNames()}) "
2290
+ sqlFile += FileSpec.bindValuesExpression(useSeq=True)
2291
+ varMap = tmpFileSpec.valuesMap(useSeq=True)
2292
+ self.cur.execute(sqlFile + comment, varMap)
2293
+ else:
2294
+ # update LFN
2295
+ sqlFSF = "UPDATE ATLAS_PANDA.filesTable4 SET lfn=:lfn "
2296
+ sqlFSF += "WHERE row_ID=:row_ID "
2297
+ varMap = {}
2298
+ varMap[":lfn"] = tmpFileSpec.lfn
2299
+ varMap[":row_ID"] = tmpFileSpec.row_ID
2300
+ self.cur.execute(sqlFSF + comment, varMap)
2301
+ # update LFN in JEDI
2302
+ if tmpFileSpec.fileID not in ["", "NULL", None]:
2303
+ sqlJF = f"UPDATE {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2304
+ sqlJF += "SET lfn=:lfn "
2305
+ sqlJF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID "
2306
+ varMap = {}
2307
+ varMap[":lfn"] = tmpFileSpec.lfn
2308
+ varMap[":jediTaskID"] = tmpFileSpec.jediTaskID
2309
+ varMap[":datasetID"] = tmpFileSpec.datasetID
2310
+ varMap[":fileID"] = tmpFileSpec.fileID
2311
+ self.cur.execute(sqlJF + comment, varMap)
2312
+ # commit
2313
+ if not self._commit():
2314
+ raise RuntimeError("Commit error")
2315
+ tmp_log.debug("done")
2316
+ return True
2317
+ except Exception:
2318
+ # roll back
2319
+ self._rollback()
2320
+ # error
2321
+ self.dump_error_message(tmp_log)
2322
+ return False
2323
+
2324
+ # get error definitions from DB (values cached for 1 hour)
2325
+ @memoize
2326
+ def getRetrialRules(self):
2327
+ # Logging
2328
+ comment = " /* DBProxy.getRetrialRules */"
2329
+ tmp_log = self.create_tagged_logger(comment)
2330
+ tmp_log.debug("start")
2331
+
2332
+ # SQL to extract the error definitions
2333
+ sql = """
2334
+ SELECT re.retryerror_id, re.errorsource, re.errorcode, re.errorDiag, re.parameters, re.architecture, re.release, re.workqueue_id, ra.retry_action, re.active, ra.active
2335
+ FROM ATLAS_PANDA.RETRYERRORS re, ATLAS_PANDA.RETRYACTIONS ra
2336
+ WHERE re.retryaction=ra.retryaction_id
2337
+ AND (CURRENT_TIMESTAMP < re.expiration_date or re.expiration_date IS NULL)
2338
+ """
2339
+ self.cur.execute(sql + comment, {})
2340
+ definitions = self.cur.fetchall() # example of output: [('pilotErrorCode', 1, None, None, None, None, 'no_retry', 'Y', 'Y'),...]
2341
+
2342
+ # commit
2343
+ if not self._commit():
2344
+ raise RuntimeError("Commit error")
2345
+
2346
+ # tmp_log.debug("definitions %s"%(definitions))
2347
+
2348
+ retrial_rules = {}
2349
+ for definition in definitions:
2350
+ (
2351
+ retryerror_id,
2352
+ error_source,
2353
+ error_code,
2354
+ error_diag,
2355
+ parameters,
2356
+ architecture,
2357
+ release,
2358
+ wqid,
2359
+ action,
2360
+ e_active,
2361
+ a_active,
2362
+ ) = definition
2363
+
2364
+ # Convert the parameter string into a dictionary
2365
+ try:
2366
+ # 1. Convert a string like "key1=value1&key2=value2" into [[key1, value1],[key2,value2]]
2367
+ params_list = map(
2368
+ lambda key_value_pair: key_value_pair.split("="),
2369
+ parameters.split("&"),
2370
+ )
2371
+ # 2. Convert a list [[key1, value1],[key2,value2]] into {key1: value1, key2: value2}
2372
+ params_dict = dict((key, value) for (key, value) in params_list)
2373
+ except AttributeError:
2374
+ params_dict = {}
2375
+ except ValueError:
2376
+ params_dict = {}
2377
+
2378
+ # Calculate if action and error combination should be active
2379
+ if e_active == "Y" and a_active == "Y":
2380
+ active = True # Apply the action for this error
2381
+ else:
2382
+ active = False # Do not apply the action for this error, only log
2383
+
2384
+ retrial_rules.setdefault(error_source, {})
2385
+ retrial_rules[error_source].setdefault(error_code, [])
2386
+ retrial_rules[error_source][error_code].append(
2387
+ {
2388
+ "error_id": retryerror_id,
2389
+ "error_diag": error_diag,
2390
+ "action": action,
2391
+ "params": params_dict,
2392
+ "architecture": architecture,
2393
+ "release": release,
2394
+ "wqid": wqid,
2395
+ "active": active,
2396
+ }
2397
+ )
2398
+ # tmp_log.debug("Loaded retrial rules from DB: %s" %retrial_rules)
2399
+ return retrial_rules
2400
+
2401
+ def setMaxAttempt(self, jobID, taskID, files, maxAttempt):
2402
+ # Logging
2403
+ comment = " /* DBProxy.setMaxAttempt */"
2404
+ tmp_log = self.create_tagged_logger(comment, f"jobID={jobID} taskID={taskID}")
2405
+ tmp_log.debug("start")
2406
+
2407
+ # Update the file entries to avoid JEDI generating new jobs
2408
+ input_types = ("input", "pseudo_input", "pp_input", "trn_log", "trn_output")
2409
+ input_files = list(
2410
+ filter(
2411
+ lambda pandafile: pandafile.type in input_types and re.search("DBRelease", pandafile.lfn) is None,
2412
+ files,
2413
+ )
2414
+ )
2415
+ input_fileIDs = [input_file.fileID for input_file in input_files]
2416
+ input_datasetIDs = [input_file.datasetID for input_file in input_files]
2417
+
2418
+ if input_fileIDs:
2419
+ try:
2420
+ # Start transaction
2421
+ self.conn.begin()
2422
+
2423
+ varMap = {}
2424
+ varMap[":taskID"] = taskID
2425
+ varMap[":pandaID"] = jobID
2426
+
2427
+ # Bind the files
2428
+ file_var_names_str, file_var_map = get_sql_IN_bind_variables(input_fileIDs, prefix=":file")
2429
+ varMap.update(file_var_map)
2430
+
2431
+ # Bind the datasets
2432
+ ds_var_names_str, ds_var_map = get_sql_IN_bind_variables(input_datasetIDs, prefix=":dataset")
2433
+ varMap.update(ds_var_map)
2434
+
2435
+ # Get the minimum maxAttempt value of the files
2436
+ sql_select = f"""
2437
+ select min(maxattempt) from ATLAS_PANDA.JEDI_Dataset_Contents
2438
+ WHERE JEDITaskID = :taskID
2439
+ AND datasetID IN ({ds_var_names_str})
2440
+ AND fileID IN ({file_var_names_str})
2441
+ AND pandaID = :pandaID
2442
+ """
2443
+ self.cur.execute(sql_select + comment, varMap)
2444
+ try:
2445
+ maxAttempt_select = self.cur.fetchone()[0]
2446
+ except (TypeError, IndexError):
2447
+ maxAttempt_select = None
2448
+
2449
+ # Don't update the maxAttempt if the new value is higher than the old value
2450
+ if maxAttempt_select and maxAttempt_select > maxAttempt:
2451
+ varMap[":maxAttempt"] = min(maxAttempt, maxAttempt_select)
2452
+
2453
+ sql_update = f"""
2454
+ UPDATE ATLAS_PANDA.JEDI_Dataset_Contents
2455
+ SET maxAttempt=:maxAttempt
2456
+ WHERE JEDITaskID = :taskID
2457
+ AND datasetID IN ({ds_var_names_str})
2458
+ AND fileID IN ({file_var_names_str})
2459
+ AND pandaID = :pandaID
2460
+ """
2461
+
2462
+ self.cur.execute(sql_update + comment, varMap)
2463
+
2464
+ # Commit updates
2465
+ if not self._commit():
2466
+ raise RuntimeError("Commit error")
2467
+ except Exception:
2468
+ # roll back
2469
+ self._rollback()
2470
+ # error
2471
+ self.dump_error_message(tmp_log)
2472
+ return False
2473
+
2474
+ tmp_log.debug("done")
2475
+ return True
2476
+
2477
+ def increase_max_failure(self, job_id, task_id, files):
2478
+ """Increase the max failure number by one for specific files."""
2479
+ comment = " /* DBProxy.increase_max_failure */"
2480
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={job_id} jediTaskID={task_id}")
2481
+ tmp_log.debug("start")
2482
+
2483
+ # Update the file entries to increase the max attempt number by one
2484
+ input_types = ("input", "pseudo_input", "pp_input", "trn_log", "trn_output")
2485
+ input_files = [pandafile for pandafile in files if pandafile.type in input_types and re.search("DBRelease", pandafile.lfn) is None]
2486
+ input_file_ids = [input_file.fileID for input_file in input_files]
2487
+ input_dataset_ids = [input_file.datasetID for input_file in input_files]
2488
+
2489
+ if input_file_ids:
2490
+ try:
2491
+ # Start transaction
2492
+ self.conn.begin()
2493
+
2494
+ var_map = {
2495
+ ":taskID": task_id,
2496
+ ":pandaID": job_id,
2497
+ }
2498
+
2499
+ # Bind the files
2500
+ file_var_names_str, file_var_map = get_sql_IN_bind_variables(input_file_ids, prefix=":file")
2501
+ var_map.update(file_var_map)
2502
+
2503
+ # Bind the datasets
2504
+ ds_var_names_str, ds_var_map = get_sql_IN_bind_variables(input_dataset_ids, prefix=":dataset")
2505
+ var_map.update(ds_var_map)
2506
+
2507
+ sql_update = f"""
2508
+ UPDATE ATLAS_PANDA.JEDI_Dataset_Contents
2509
+ SET maxFailure = maxFailure + 1
2510
+ WHERE JEDITaskID = :taskID
2511
+ AND datasetID IN ({ds_var_names_str})
2512
+ AND fileID IN ({file_var_names_str})
2513
+ AND pandaID = :pandaID
2514
+ """
2515
+
2516
+ self.cur.execute(sql_update + comment, var_map)
2517
+
2518
+ # Commit updates
2519
+ if not self._commit():
2520
+ raise RuntimeError("Commit error")
2521
+
2522
+ except Exception:
2523
+ # Roll back
2524
+ self._rollback()
2525
+ # Log error
2526
+ self.dump_error_message(tmp_log)
2527
+ return False
2528
+
2529
+ tmp_log.debug("done")
2530
+ return True
2531
+
2532
+ def setNoRetry(self, jobID, taskID, files):
2533
+ # Logging
2534
+ comment = " /* DBProxy.setNoRetry */"
2535
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={jobID} jediTaskID={taskID}")
2536
+ tmp_log.debug("start")
2537
+
2538
+ # Update the file entries to avoid JEDI generating new jobs
2539
+ input_types = ("input", "pseudo_input", "pp_input", "trn_log", "trn_output")
2540
+ input_files = list(
2541
+ filter(
2542
+ lambda pandafile: pandafile.type in input_types and re.search("DBRelease", pandafile.lfn) is None,
2543
+ files,
2544
+ )
2545
+ )
2546
+ input_fileIDs = [input_file.fileID for input_file in input_files]
2547
+ input_datasetIDs = [input_file.datasetID for input_file in input_files]
2548
+
2549
+ if input_fileIDs:
2550
+ try:
2551
+ # Start transaction
2552
+ self.conn.begin()
2553
+
2554
+ # loop over all datasets
2555
+ for datasetID in input_datasetIDs:
2556
+ varMap = {}
2557
+ varMap[":taskID"] = taskID
2558
+ varMap[":datasetID"] = datasetID
2559
+ varMap[":keepTrack"] = 1
2560
+
2561
+ # Bind the files
2562
+ file_var_names_str, file_var_map = get_sql_IN_bind_variables(input_fileIDs, prefix=":file")
2563
+ varMap.update(file_var_map)
2564
+
2565
+ sql_update = f"""
2566
+ UPDATE ATLAS_PANDA.JEDI_Dataset_Contents
2567
+ SET maxAttempt=attemptNr
2568
+ WHERE JEDITaskID = :taskID
2569
+ AND datasetID=:datasetID
2570
+ AND fileID IN ({file_var_names_str})
2571
+ AND maxAttempt IS NOT NULL AND attemptNr IS NOT NULL
2572
+ AND maxAttempt > attemptNr
2573
+ AND (maxFailure IS NULL OR failedAttempt IS NULL OR maxFailure > failedAttempt)
2574
+ AND keepTrack=:keepTrack
2575
+ AND status=:status
2576
+ """
2577
+
2578
+ # update files in 'running' status. These files do NOT need to be counted for the nFiles*
2579
+ varMap[":status"] = "running"
2580
+ self.cur.execute(sql_update + comment, varMap)
2581
+
2582
+ # update files in 'ready' status. These files need to be counted for the nFiles*
2583
+ varMap[":status"] = "ready"
2584
+ self.cur.execute(sql_update + comment, varMap)
2585
+ rowcount = self.cur.rowcount
2586
+
2587
+ # update datasets
2588
+ if rowcount > 0:
2589
+ sql_dataset = "UPDATE ATLAS_PANDA.JEDI_Datasets "
2590
+ sql_dataset += "SET nFilesUsed=nFilesUsed+:nDiff,nFilesFailed=nFilesFailed+:nDiff "
2591
+ sql_dataset += "WHERE jediTaskID=:taskID AND datasetID=:datasetID "
2592
+ varMap = dict()
2593
+ varMap[":taskID"] = taskID
2594
+ varMap[":datasetID"] = datasetID
2595
+ varMap[":nDiff"] = rowcount
2596
+ self.cur.execute(sql_dataset + comment, varMap)
2597
+
2598
+ # Commit updates
2599
+ if not self._commit():
2600
+ raise RuntimeError("Commit error")
2601
+ except Exception:
2602
+ # roll back
2603
+ self._rollback()
2604
+ # error
2605
+ self.dump_error_message(tmp_log)
2606
+ return False
2607
+
2608
+ tmp_log.debug("done")
2609
+ return True
2610
+
2611
+ # add associate sub datasets for single consumer job
2612
+ def getDestDBlocksWithSingleConsumer(self, jediTaskID, PandaID, ngDatasets):
2613
+ comment = " /* DBProxy.getDestDBlocksWithSingleConsumer */"
2614
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} PandaID={PandaID}")
2615
+ tmp_log.debug("start")
2616
+ try:
2617
+ retMap = {}
2618
+ checkedDS = set()
2619
+ # sql to get files
2620
+ sqlF = "SELECT datasetID,fileID FROM ATLAS_PANDA.JEDI_Events "
2621
+ sqlF += "WHERE jediTaskID=:jediTaskID AND PandaID=:PandaID "
2622
+ # sql to get PandaIDs
2623
+ sqlP = "SELECT distinct PandaID FROM ATLAS_PANDA.filesTable4 "
2624
+ sqlP += "WHERE jediTaskID=:jediTaskID ANd datasetID=:datasetID AND fileID=:fileID "
2625
+ # sql to get sub datasets
2626
+ sqlD = "SELECT destinationDBlock,datasetID FROM ATLAS_PANDA.filesTable4 "
2627
+ sqlD += "WHERE PandaID=:PandaID AND type IN (:type1,:type2) "
2628
+ # sql to get PandaIDs in merging
2629
+ sqlM = "SELECT distinct PandaID FROM ATLAS_PANDA.filesTable4 "
2630
+ sqlM += "WHERE jediTaskID=:jediTaskID ANd datasetID=:datasetID AND status=:status "
2631
+ varMap = {}
2632
+ varMap[":jediTaskID"] = jediTaskID
2633
+ varMap[":PandaID"] = PandaID
2634
+ # begin transaction
2635
+ self.conn.begin()
2636
+ # get files
2637
+ self.cur.execute(sqlF + comment, varMap)
2638
+ resF = self.cur.fetchall()
2639
+ for datasetID, fileID in resF:
2640
+ # get parallel jobs
2641
+ varMap = {}
2642
+ varMap[":jediTaskID"] = jediTaskID
2643
+ varMap[":datasetID"] = datasetID
2644
+ varMap[":fileID"] = fileID
2645
+ self.cur.execute(sqlP + comment, varMap)
2646
+ resP = self.cur.fetchall()
2647
+ for (sPandaID,) in resP:
2648
+ if sPandaID == PandaID:
2649
+ continue
2650
+ # get sub datasets of parallel jobs
2651
+ varMap = {}
2652
+ varMap[":PandaID"] = sPandaID
2653
+ varMap[":type1"] = "output"
2654
+ varMap[":type2"] = "log"
2655
+ self.cur.execute(sqlD + comment, varMap)
2656
+ resD = self.cur.fetchall()
2657
+ subDatasets = []
2658
+ subDatasetID = None
2659
+ for destinationDBlock, datasetID in resD:
2660
+ if destinationDBlock in ngDatasets:
2661
+ continue
2662
+ if destinationDBlock in checkedDS:
2663
+ continue
2664
+ checkedDS.add(destinationDBlock)
2665
+ subDatasets.append(destinationDBlock)
2666
+ subDatasetID = datasetID
2667
+ if subDatasets == []:
2668
+ continue
2669
+ # get merging PandaID which uses sub dataset
2670
+ varMap = {}
2671
+ varMap[":jediTaskID"] = jediTaskID
2672
+ varMap[":datasetID"] = datasetID
2673
+ varMap[":status"] = "merging"
2674
+ self.cur.execute(sqlM + comment, varMap)
2675
+ resM = self.cur.fetchone()
2676
+ if resM is not None:
2677
+ (mPandaID,) = resM
2678
+ retMap[mPandaID] = subDatasets
2679
+ # commit
2680
+ if not self._commit():
2681
+ raise RuntimeError("Commit error")
2682
+ tmp_log.debug(f"got {len(retMap)} jobs")
2683
+ return retMap
2684
+ except Exception:
2685
+ # roll back
2686
+ self._rollback()
2687
+ # error
2688
+ self.dump_error_message(tmp_log)
2689
+ return {}
2690
+
2691
+ # get dispatch datasets per user
2692
+ def getDispatchDatasetsPerUser(self, vo, prodSourceLabel, onlyActive, withSize):
2693
+ comment = " /* DBProxy.getDispatchDatasetsPerUser */"
2694
+ tmp_log = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel}")
2695
+ tmp_log.debug("start")
2696
+ # mapping for table and job status
2697
+ tableStatMap = {"jobsDefined4": ["defined", "assigned"]}
2698
+ if not onlyActive:
2699
+ tableStatMap["jobsActive4"] = None
2700
+ tableStatMap["jobsArchived4"] = None
2701
+ try:
2702
+ userDispMap = {}
2703
+ for tableName in tableStatMap:
2704
+ statusList = tableStatMap[tableName]
2705
+ # make sql to get dispatch datasets
2706
+ varMap = {}
2707
+ varMap[":vo"] = vo
2708
+ varMap[":label"] = prodSourceLabel
2709
+ varMap[":dType"] = "dispatch"
2710
+ sqlJ = "SELECT distinct prodUserName,dispatchDBlock,jediTaskID,currentFiles "
2711
+ sqlJ += "FROM {0}.{1} j, {0}.Datasets d ".format(panda_config.schemaPANDA, tableName)
2712
+ sqlJ += "WHERE vo=:vo AND prodSourceLabel=:label "
2713
+ if statusList is not None:
2714
+ jobstat_var_names_str, jobstat_var_map = get_sql_IN_bind_variables(statusList, prefix=":jobStat_", value_as_suffix=True)
2715
+ sqlJ += f"AND jobStatus IN ({jobstat_var_names_str}) "
2716
+ varMap.update(jobstat_var_map)
2717
+ sqlJ += "AND dispatchDBlock IS NOT NULL "
2718
+ sqlJ += "AND d.name=j.dispatchDBlock AND d.modificationDate>CURRENT_DATE-14 "
2719
+ sqlJ += "AND d.type=:dType "
2720
+ # begin transaction
2721
+ self.conn.begin()
2722
+ # get dispatch datasets
2723
+ self.cur.execute(sqlJ + comment, varMap)
2724
+ resJ = self.cur.fetchall()
2725
+ if not self._commit():
2726
+ raise RuntimeError("Commit error")
2727
+ # make map
2728
+ for prodUserName, dispatchDBlock, jediTaskID, dsSize in resJ:
2729
+ transferType = "transfer"
2730
+ try:
2731
+ if dispatchDBlock.split(".")[4] == "prestaging":
2732
+ transferType = "prestaging"
2733
+ except Exception:
2734
+ pass
2735
+ userDispMap.setdefault(prodUserName, {})
2736
+ userDispMap[prodUserName].setdefault(transferType, {"datasets": set(), "size": 0, "tasks": set()})
2737
+ if dispatchDBlock not in userDispMap[prodUserName][transferType]["datasets"]:
2738
+ userDispMap[prodUserName][transferType]["datasets"].add(dispatchDBlock)
2739
+ userDispMap[prodUserName][transferType]["tasks"].add(jediTaskID)
2740
+ userDispMap[prodUserName][transferType]["size"] += dsSize
2741
+ tmp_log.debug("done")
2742
+ return userDispMap
2743
+ except Exception:
2744
+ # roll back
2745
+ self._rollback()
2746
+ # error
2747
+ self.dump_error_message(tmp_log)
2748
+ return {}
2749
+
2750
+ # bulk fetch PandaIDs
2751
+ def bulk_fetch_panda_ids(self, num_ids):
2752
+ comment = " /* JediDBProxy.bulk_fetch_panda_ids */"
2753
+ tmp_log = self.create_tagged_logger(comment, f"num_ids={num_ids}")
2754
+ tmp_log.debug("start")
2755
+ try:
2756
+ new_ids = []
2757
+ var_map = {}
2758
+ var_map[":nIDs"] = num_ids
2759
+ # sql to get fileID
2760
+ sqlFID = "SELECT ATLAS_PANDA.JOBSDEFINED4_PANDAID_SEQ.nextval FROM "
2761
+ sqlFID += "(SELECT level FROM dual CONNECT BY level<=:nIDs) "
2762
+ # start transaction
2763
+ self.conn.begin()
2764
+ self.cur.arraysize = 10000
2765
+ self.cur.execute(sqlFID + comment, var_map)
2766
+ resFID = self.cur.fetchall()
2767
+ for (id,) in resFID:
2768
+ new_ids.append(id)
2769
+ # commit
2770
+ if not self._commit():
2771
+ raise RuntimeError("Commit error")
2772
+ tmp_log.debug(f"got {len(new_ids)} IDs")
2773
+ return sorted(new_ids)
2774
+ except Exception:
2775
+ # roll back
2776
+ self._rollback()
2777
+ # error
2778
+ self.dump_error_message(tmp_log)
2779
+ return []
2780
+
2781
+ # bulk fetch fileIDs
2782
+ def bulkFetchFileIDsPanda(self, nIDs):
2783
+ comment = " /* JediDBProxy.bulkFetchFileIDsPanda */"
2784
+ tmp_log = self.create_tagged_logger(comment, f"nIDs={nIDs}")
2785
+ tmp_log.debug("start")
2786
+ try:
2787
+ newFileIDs = []
2788
+ varMap = {}
2789
+ varMap[":nIDs"] = nIDs
2790
+ # sql to get fileID
2791
+ sqlFID = "SELECT ATLAS_PANDA.FILESTABLE4_ROW_ID_SEQ.nextval FROM "
2792
+ sqlFID += "(SELECT level FROM dual CONNECT BY level<=:nIDs) "
2793
+ # start transaction
2794
+ self.conn.begin()
2795
+ self.cur.arraysize = 10000
2796
+ self.cur.execute(sqlFID + comment, varMap)
2797
+ resFID = self.cur.fetchall()
2798
+ for (fileID,) in resFID:
2799
+ newFileIDs.append(fileID)
2800
+ # commit
2801
+ if not self._commit():
2802
+ raise RuntimeError("Commit error")
2803
+ tmp_log.debug(f"got {len(newFileIDs)} IDs")
2804
+ return newFileIDs
2805
+ except Exception:
2806
+ # roll back
2807
+ self._rollback()
2808
+ # error
2809
+ self.dump_error_message(tmp_log)
2810
+ return []
2811
+
2812
+ # get LNFs for jumbo job
2813
+ def getLFNsForJumbo(self, jediTaskID):
2814
+ comment = " /* DBProxy.getLFNsForJumbo */"
2815
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
2816
+ tmp_log.debug("start")
2817
+ try:
2818
+ sqlS = "SELECT lfn,scope FROM {0}.JEDI_Datasets d, {0}.JEDI_Dataset_Contents c ".format(panda_config.schemaJEDI)
2819
+ sqlS += "WHERE d.jediTaskID=c.jediTaskID AND d.datasetID=c.datasetID AND d.jediTaskID=:jediTaskID "
2820
+ sqlS += "AND d.type IN (:type1,:type2) AND d.masterID IS NULL "
2821
+ retSet = set()
2822
+ # start transaction
2823
+ self.conn.begin()
2824
+ varMap = {}
2825
+ varMap[":jediTaskID"] = jediTaskID
2826
+ varMap[":type1"] = "input"
2827
+ varMap[":type2"] = "pseudo_input"
2828
+ self.cur.execute(sqlS + comment, varMap)
2829
+ res = self.cur.fetchall()
2830
+ for tmpLFN, tmpScope in res:
2831
+ name = f"{tmpScope}:{tmpLFN}"
2832
+ retSet.add(name)
2833
+ # commit
2834
+ if not self._commit():
2835
+ raise RuntimeError("Commit error")
2836
+ tmp_log.debug(f"has {len(retSet)} LFNs")
2837
+ return retSet
2838
+ except Exception:
2839
+ # roll back
2840
+ self._rollback()
2841
+ # error
2842
+ self.dump_error_message(tmp_log)
2843
+ return []
2844
+
2845
+ # get number of started events
2846
+ def getNumStartedEvents(self, jobSpec):
2847
+ comment = " /* DBProxy.getNumStartedEvents */"
2848
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={jobSpec.PandaID}")
2849
+ tmp_log.debug("start")
2850
+ try:
2851
+ # count the number of started ranges
2852
+ sqlCDO = "SELECT /*+ INDEX_RS_ASC(tab JEDI_EVENTS_FILEID_IDX) NO_INDEX_FFS(tab JEDI_EVENTS_PK) NO_INDEX_SS(tab JEDI_EVENTS_PK) */ "
2853
+ sqlCDO += f"COUNT(*) FROM {panda_config.schemaJEDI}.JEDI_Events tab "
2854
+ sqlCDO += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID "
2855
+ sqlCDO += "AND status IN (:esSent,:esRunning,:esFinished,:esDone) "
2856
+ # start transaction
2857
+ self.conn.begin()
2858
+ nEvt = 0
2859
+ for fileSpec in jobSpec.Files:
2860
+ if fileSpec.type != "input":
2861
+ continue
2862
+ varMap = {}
2863
+ varMap[":jediTaskID"] = fileSpec.jediTaskID
2864
+ varMap[":datasetID"] = fileSpec.datasetID
2865
+ varMap[":fileID"] = fileSpec.fileID
2866
+ varMap[":esSent"] = EventServiceUtils.ST_sent
2867
+ varMap[":esRunning"] = EventServiceUtils.ST_running
2868
+ varMap[":esFinished"] = EventServiceUtils.ST_finished
2869
+ varMap[":esDone"] = EventServiceUtils.ST_done
2870
+ self.cur.execute(sqlCDO + comment, varMap)
2871
+ res = self.cur.fetchone()
2872
+ if res is not None:
2873
+ nEvt += res[0]
2874
+ # commit
2875
+ if not self._commit():
2876
+ raise RuntimeError("Commit error")
2877
+ tmp_log.debug(f"{nEvt} events started")
2878
+ return nEvt
2879
+ except Exception:
2880
+ # roll back
2881
+ self._rollback()
2882
+ # error
2883
+ self.dump_error_message(tmp_log)
2884
+ return None
2885
+
2886
+ # get JEDI file attributes
2887
+ def getJediFileAttributes(self, PandaID, jediTaskID, datasetID, fileID, attrs):
2888
+ comment = " /* DBProxy.getJediFileAttributes */"
2889
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={PandaID}")
2890
+ tmp_log.debug(f"start for jediTaskID={jediTaskID} datasetId={datasetID} fileID={fileID}")
2891
+ try:
2892
+ # sql to get task attributes
2893
+ sqlRR = "SELECT "
2894
+ for attr in attrs:
2895
+ sqlRR += f"{attr},"
2896
+ sqlRR = sqlRR[:-1]
2897
+ sqlRR += f" FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
2898
+ sqlRR += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID "
2899
+ varMap = {}
2900
+ varMap[":jediTaskID"] = jediTaskID
2901
+ varMap[":datasetID"] = datasetID
2902
+ varMap[":fileID"] = fileID
2903
+ # start transaction
2904
+ self.conn.begin()
2905
+ self.cur.execute(sqlRR + comment, varMap)
2906
+ resRR = self.cur.fetchone()
2907
+ # commit
2908
+ if not self._commit():
2909
+ raise RuntimeError("Commit error")
2910
+ retVal = {}
2911
+ if resRR is not None:
2912
+ for idx, attr in enumerate(attrs):
2913
+ retVal[attr] = resRR[idx]
2914
+ tmp_log.debug(f"done {str(retVal)}")
2915
+ return retVal
2916
+ except Exception:
2917
+ # roll back
2918
+ self._rollback()
2919
+ # error
2920
+ self.dump_error_message(tmp_log)
2921
+ return {}
2922
+
2923
+ # get jumbo job datasets
2924
+ def getJumboJobDatasets(self, n_days, grace_period):
2925
+ comment = " /* DBProxy.getJumboJobDatasets */"
2926
+ tmp_log = self.create_tagged_logger(comment, f"nDays={n_days}")
2927
+ tmp_log.debug("start")
2928
+ try:
2929
+ # sql to get workers
2930
+ sqlC = "SELECT t.jediTaskID,d.datasetName,t.status FROM ATLAS_PANDA.JEDI_Tasks t,ATLAS_PANDA.JEDI_Datasets d "
2931
+ sqlC += "WHERE t.prodSourceLabel='managed' AND t.useJumbo IS NOT NULL "
2932
+ sqlC += "AND t.modificationTime>CURRENT_DATE-:days AND t.modificationTime<CURRENT_DATE-:grace_period "
2933
+ sqlC += "AND t.status IN ('finished','done') "
2934
+ sqlC += "AND d.jediTaskID=t.jediTaskID AND d.type='output' "
2935
+ varMap = {}
2936
+ varMap[":days"] = n_days
2937
+ varMap[":grace_period"] = grace_period
2938
+ # start transaction
2939
+ self.conn.begin()
2940
+ self.cur.execute(sqlC + comment, varMap)
2941
+ resCs = self.cur.fetchall()
2942
+ retMap = dict()
2943
+ nDS = 0
2944
+ for jediTaskID, datasetName, status in resCs:
2945
+ retMap.setdefault(jediTaskID, {"status": status, "datasets": []})
2946
+ retMap[jediTaskID]["datasets"].append(datasetName)
2947
+ nDS += 1
2948
+ # commit
2949
+ if not self._commit():
2950
+ raise RuntimeError("Commit error")
2951
+ tmp_log.debug(f"got {nDS} datasets")
2952
+ return retMap
2953
+ except Exception:
2954
+ # roll back
2955
+ self._rollback()
2956
+ # error
2957
+ self.dump_error_message(tmp_log)
2958
+ return {}
2959
+
2960
+ # get output datasets
2961
+ def getOutputDatasetsJEDI(self, panda_id):
2962
+ comment = " /* DBProxy.getOutputDatasetsJEDI */"
2963
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
2964
+ tmp_log.debug("start")
2965
+ try:
2966
+ # sql to get workers
2967
+ sqlC = "SELECT d.datasetID,d.datasetName FROM ATLAS_PANDA.filesTable4 f,ATLAS_PANDA.JEDI_Datasets d "
2968
+ sqlC += "WHERE f.PandaID=:PandaID AND f.type IN (:type1,:type2) AND d.jediTaskID=f.jediTaskID AND d.datasetID=f.datasetID "
2969
+ varMap = {}
2970
+ varMap[":PandaID"] = panda_id
2971
+ varMap[":type1"] = "output"
2972
+ varMap[":type2"] = "log"
2973
+ # start transaction
2974
+ self.conn.begin()
2975
+ self.cur.execute(sqlC + comment, varMap)
2976
+ retMap = dict()
2977
+ resCs = self.cur.fetchall()
2978
+ for datasetID, datasetName in resCs:
2979
+ retMap[datasetID] = datasetName
2980
+ # commit
2981
+ if not self._commit():
2982
+ raise RuntimeError("Commit error")
2983
+ tmp_log.debug(f"got {len(retMap)}")
2984
+ return retMap
2985
+ except Exception:
2986
+ # roll back
2987
+ self._rollback()
2988
+ # error
2989
+ self.dump_error_message(tmp_log)
2990
+ return {}
2991
+
2992
+ # lock process
2993
+ def lockProcess_PANDA(self, component, pid, time_limit, force=False):
2994
+ comment = " /* DBProxy.lockProcess_PANDA */"
2995
+ tmp_log = self.create_tagged_logger(comment, f"component={component} pid={pid}")
2996
+ # defaults
2997
+ vo = "default"
2998
+ prodSourceLabel = "default"
2999
+ cloud = "default"
3000
+ workqueue_id = 0
3001
+ resource_name = "default"
3002
+ tmp_log.debug("start")
3003
+ try:
3004
+ retVal = False
3005
+ # sql to check
3006
+ sqlCT = (
3007
+ "SELECT lockedBy "
3008
+ "FROM {0}.JEDI_Process_Lock "
3009
+ "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
3010
+ "AND cloud=:cloud AND workqueue_id=:workqueue_id "
3011
+ "AND resource_type=:resource_name AND component=:component "
3012
+ "AND lockedTime>:lockedTime "
3013
+ "FOR UPDATE"
3014
+ ).format(panda_config.schemaJEDI)
3015
+ # sql to delete
3016
+ sqlCD = (
3017
+ "DELETE FROM {0}.JEDI_Process_Lock "
3018
+ "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
3019
+ "AND cloud=:cloud AND workqueue_id=:workqueue_id "
3020
+ "AND resource_type=:resource_name AND component=:component "
3021
+ ).format(panda_config.schemaJEDI)
3022
+ # sql to insert
3023
+ sqlFR = (
3024
+ "INSERT INTO {0}.JEDI_Process_Lock "
3025
+ "(vo, prodSourceLabel, cloud, workqueue_id, resource_type, component, lockedBy, lockedTime) "
3026
+ "VALUES(:vo, :prodSourceLabel, :cloud, :workqueue_id, :resource_name, :component, :lockedBy, CURRENT_DATE) "
3027
+ ).format(panda_config.schemaJEDI)
3028
+ # start transaction
3029
+ self.conn.begin()
3030
+ # check
3031
+ if not force:
3032
+ varMap = {}
3033
+ varMap[":vo"] = vo
3034
+ varMap[":prodSourceLabel"] = prodSourceLabel
3035
+ varMap[":cloud"] = cloud
3036
+ varMap[":workqueue_id"] = workqueue_id
3037
+ varMap[":resource_name"] = resource_name
3038
+ varMap[":component"] = component
3039
+ varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
3040
+ self.cur.execute(sqlCT + comment, varMap)
3041
+ resCT = self.cur.fetchone()
3042
+ else:
3043
+ resCT = None
3044
+ if resCT is not None:
3045
+ tmp_log.debug(f"skipped, locked by {resCT[0]}")
3046
+ else:
3047
+ # delete
3048
+ varMap = {}
3049
+ varMap[":vo"] = vo
3050
+ varMap[":prodSourceLabel"] = prodSourceLabel
3051
+ varMap[":cloud"] = cloud
3052
+ varMap[":workqueue_id"] = workqueue_id
3053
+ varMap[":resource_name"] = resource_name
3054
+ varMap[":component"] = component
3055
+ self.cur.execute(sqlCD + comment, varMap)
3056
+ # insert
3057
+ varMap = {}
3058
+ varMap[":vo"] = vo
3059
+ varMap[":prodSourceLabel"] = prodSourceLabel
3060
+ varMap[":cloud"] = cloud
3061
+ varMap[":workqueue_id"] = workqueue_id
3062
+ varMap[":resource_name"] = resource_name
3063
+ varMap[":component"] = component
3064
+ varMap[":lockedBy"] = pid
3065
+ self.cur.execute(sqlFR + comment, varMap)
3066
+ tmp_log.debug("successfully locked")
3067
+ retVal = True
3068
+ # commit
3069
+ if not self._commit():
3070
+ raise RuntimeError("Commit error")
3071
+ return retVal
3072
+ except Exception:
3073
+ # roll back
3074
+ self._rollback()
3075
+ # error
3076
+ self.dump_error_message(tmp_log)
3077
+ return retVal
3078
+
3079
+ # unlock process
3080
+ def unlockProcess_PANDA(self, component, pid):
3081
+ comment = " /* DBProxy.unlockProcess_PANDA */"
3082
+ tmp_log = self.create_tagged_logger(comment, f"component={component} pid={pid}")
3083
+ # defaults
3084
+ vo = "default"
3085
+ prodSourceLabel = "default"
3086
+ cloud = "default"
3087
+ workqueue_id = 0
3088
+ resource_name = "default"
3089
+ tmp_log.debug("start")
3090
+ try:
3091
+ retVal = False
3092
+ # sql to delete
3093
+ sqlCD = (
3094
+ "DELETE FROM {0}.JEDI_Process_Lock "
3095
+ "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel AND cloud=:cloud "
3096
+ "AND workqueue_id=:workqueue_id AND lockedBy=:lockedBy "
3097
+ "AND resource_type=:resource_name AND component=:component "
3098
+ ).format(panda_config.schemaJEDI)
3099
+ # start transaction
3100
+ self.conn.begin()
3101
+ # check
3102
+ varMap = {}
3103
+ varMap[":vo"] = vo
3104
+ varMap[":prodSourceLabel"] = prodSourceLabel
3105
+ varMap[":cloud"] = cloud
3106
+ varMap[":workqueue_id"] = workqueue_id
3107
+ varMap[":resource_name"] = resource_name
3108
+ varMap[":component"] = component
3109
+ varMap[":lockedBy"] = pid
3110
+ self.cur.execute(sqlCD + comment, varMap)
3111
+ # commit
3112
+ if not self._commit():
3113
+ raise RuntimeError("Commit error")
3114
+ tmp_log.debug("done")
3115
+ retVal = True
3116
+ return retVal
3117
+ except Exception:
3118
+ # roll back
3119
+ self._rollback()
3120
+ # error
3121
+ self.dump_error_message(tmp_log)
3122
+ return retVal
3123
+
3124
+ # check process lock
3125
+ def checkProcessLock_PANDA(self, component, pid, time_limit, check_base=False):
3126
+ comment = " /* DBProxy.checkProcessLock_PANDA */"
3127
+ tmp_log = self.create_tagged_logger(comment, f"component={component} pid={pid}")
3128
+ # defaults
3129
+ vo = "default"
3130
+ prodSourceLabel = "default"
3131
+ cloud = "default"
3132
+ workqueue_id = 0
3133
+ resource_name = "default"
3134
+ tmp_log.debug("start")
3135
+ try:
3136
+ retVal = False, None
3137
+ # sql to check
3138
+ sqlCT = (
3139
+ "SELECT lockedBy, lockedTime "
3140
+ "FROM {0}.JEDI_Process_Lock "
3141
+ "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
3142
+ "AND cloud=:cloud AND workqueue_id=:workqueue_id "
3143
+ "AND resource_type=:resource_name AND component=:component "
3144
+ "AND lockedTime>:lockedTime "
3145
+ ).format(panda_config.schemaJEDI)
3146
+ # start transaction
3147
+ self.conn.begin()
3148
+ # check
3149
+ varMap = {}
3150
+ varMap[":vo"] = vo
3151
+ varMap[":prodSourceLabel"] = prodSourceLabel
3152
+ varMap[":cloud"] = cloud
3153
+ varMap[":workqueue_id"] = workqueue_id
3154
+ varMap[":resource_name"] = resource_name
3155
+ varMap[":component"] = component
3156
+ varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
3157
+ self.cur.execute(sqlCT + comment, varMap)
3158
+ resCT = self.cur.fetchone()
3159
+ if resCT is not None:
3160
+ lockedBy, lockedTime = resCT
3161
+ if check_base:
3162
+ # check only base part
3163
+ if not lockedBy.startswith(pid):
3164
+ retVal = True, lockedTime
3165
+ else:
3166
+ # check whole string
3167
+ if lockedBy != pid:
3168
+ retVal = True, lockedTime
3169
+ if retVal[0]:
3170
+ tmp_log.debug(f"found locked by {lockedBy} at {lockedTime.strftime('%Y-%m-%d_%H:%M:%S')}")
3171
+ else:
3172
+ tmp_log.debug("found unlocked")
3173
+ # commit
3174
+ if not self._commit():
3175
+ raise RuntimeError("Commit error")
3176
+ tmp_log.debug("done")
3177
+ return retVal
3178
+ except Exception:
3179
+ # roll back
3180
+ self._rollback()
3181
+ # error
3182
+ self.dump_error_message(tmp_log)
3183
+ return retVal
3184
+
3185
+ # update problematic resource info for user
3186
+ def update_problematic_resource_info(self, user_name, jedi_task_id, resource, problem_type):
3187
+ comment = " /* DBProxy.update_problematic_resource_info */"
3188
+ tmp_log = self.create_tagged_logger(comment, f"user={user_name} jediTaskID={jedi_task_id}")
3189
+ tmp_log.debug("start")
3190
+ retVal = False
3191
+ try:
3192
+ if problem_type not in ["dest", None]:
3193
+ tmp_log.debug(f"unknown problem type: {problem_type}")
3194
+ return None
3195
+ sqlR = "SELECT pagecache FROM ATLAS_PANDAMETA.users " "WHERE name=:name "
3196
+ sqlW = "UPDATE ATLAS_PANDAMETA.users SET pagecache=:data " "WHERE name=:name "
3197
+ # string to use a dict key
3198
+ jedi_task_id = str(jedi_task_id)
3199
+ # start transaction
3200
+ self.conn.begin()
3201
+ # read
3202
+ varMap = {}
3203
+ varMap[":name"] = user_name
3204
+ self.cur.execute(sqlR + comment, varMap)
3205
+ data = self.cur.fetchone()
3206
+ if data is None:
3207
+ tmp_log.debug("user not found")
3208
+ else:
3209
+ try:
3210
+ data = json.loads(data[0])
3211
+ except Exception:
3212
+ data = {}
3213
+ if problem_type is not None:
3214
+ data.setdefault(problem_type, {})
3215
+ data[problem_type].setdefault(jedi_task_id, {})
3216
+ data[problem_type][jedi_task_id].setdefault(resource, None)
3217
+ old = data[problem_type][jedi_task_id][resource]
3218
+ if old is None or datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromtimestamp(
3219
+ old, datetime.timezone.utc
3220
+ ) > datetime.timedelta(days=1):
3221
+ retVal = True
3222
+ data[problem_type][jedi_task_id][resource] = time.time()
3223
+ # delete old data
3224
+ for p in list(data):
3225
+ for t in list(data[p]):
3226
+ for r in list(data[p][t]):
3227
+ ts = data[p][t][r]
3228
+ if datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromtimestamp(ts, datetime.timezone.utc) > datetime.timedelta(
3229
+ days=7
3230
+ ):
3231
+ del data[p][t][r]
3232
+ if not data[p][t]:
3233
+ del data[p][t]
3234
+ if not data[p]:
3235
+ del data[p]
3236
+ # update
3237
+ varMap = {}
3238
+ varMap[":name"] = user_name
3239
+ varMap[":data"] = json.dumps(data)
3240
+ self.cur.execute(sqlW + comment, varMap)
3241
+ # commit
3242
+ if not self._commit():
3243
+ raise RuntimeError("Commit error")
3244
+ tmp_log.debug(f"done with {retVal} : {str(data)}")
3245
+ return retVal
3246
+ except Exception:
3247
+ # roll back
3248
+ self._rollback()
3249
+ # error
3250
+ self.dump_error_message(tmp_log)
3251
+ return None
3252
+
3253
+ # get LFNs in datasets
3254
+ def get_files_in_datasets(self, task_id, dataset_types):
3255
+ comment = " /* DBProxy.get_lfns_in_datasets */"
3256
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={task_id}")
3257
+ tmp_log.debug("start")
3258
+ try:
3259
+ varMap = {}
3260
+ varMap[":jediTaskID"] = task_id
3261
+ sqlD = f"SELECT datasetName,datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID "
3262
+ # Old API expects comma separated types, while new API is taking directly a tuple of dataset types
3263
+ if type(dataset_types) == str:
3264
+ dataset_types = dataset_types.split(",")
3265
+ dstype_var_names_str, dstype_var_map = get_sql_IN_bind_variables(dataset_types, prefix=":", value_as_suffix=True)
3266
+ sqlD += f"AND type IN ({dstype_var_names_str}) "
3267
+ varMap.update(dstype_var_map)
3268
+ sqlS = f"SELECT lfn,scope,fileID,status FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
3269
+ sqlS += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID ORDER BY fileID "
3270
+ retVal = []
3271
+ # start transaction
3272
+ self.conn.begin()
3273
+ self.cur.execute(sqlD + comment, varMap)
3274
+ res = self.cur.fetchall()
3275
+ for datasetName, datasetID in res:
3276
+ datasetDict = {}
3277
+ datasetDict["name"] = datasetName
3278
+ datasetDict["id"] = datasetID
3279
+ # read files
3280
+ varMap = {}
3281
+ varMap[":jediTaskID"] = task_id
3282
+ varMap[":datasetID"] = datasetID
3283
+ self.cur.execute(sqlS + comment, varMap)
3284
+ resF = self.cur.fetchall()
3285
+ fileList = []
3286
+ for lfn, fileScope, fileID, status in resF:
3287
+ fileDict = {
3288
+ "lfn": lfn,
3289
+ "scope": fileScope,
3290
+ "id": fileID,
3291
+ "status": status,
3292
+ }
3293
+ fileList.append(fileDict)
3294
+ retVal.append({"dataset": datasetDict, "files": fileList})
3295
+ # commit
3296
+ if not self._commit():
3297
+ raise RuntimeError("Commit error")
3298
+ tmp_log.debug("done")
3299
+ return retVal
3300
+ except Exception:
3301
+ # roll back
3302
+ self._rollback()
3303
+ # error
3304
+ self.dump_error_message(tmp_log)
3305
+ return None
3306
+
3307
+ # update datasets asynchronously outside propagateResultToJEDI to avoid row contentions
3308
+ def async_update_datasets(self, panda_id):
3309
+ comment = " /* DBProxy.async_update_datasets */"
3310
+ g_tmp_log = self.create_tagged_logger(comment)
3311
+ g_tmp_log.debug("start")
3312
+ try:
3313
+ if panda_id is not None:
3314
+ panda_id_list = [panda_id]
3315
+ else:
3316
+ # get PandaIDs
3317
+ sql = f"SELECT DISTINCT PandaID FROM {panda_config.schemaPANDA}.SQL_QUEUE WHERE topic=:topic AND creationTime<:timeLimit"
3318
+ varMap = {
3319
+ ":topic": SQL_QUEUE_TOPIC_async_dataset_update,
3320
+ ":timeLimit": naive_utcnow() - datetime.timedelta(minutes=1),
3321
+ }
3322
+ # start transaction
3323
+ self.conn.begin()
3324
+ self.cur.arraysize = 10000
3325
+ self.cur.execute(sql + comment, varMap)
3326
+ panda_id_list = [i[0] for i in self.cur.fetchall()]
3327
+ # commit
3328
+ if not self._commit():
3329
+ raise RuntimeError("Commit error")
3330
+ if not panda_id_list:
3331
+ g_tmp_log.debug("done since no IDs are available")
3332
+ return None
3333
+ # loop over all IDs
3334
+ for tmp_id in panda_id_list:
3335
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={tmp_id}")
3336
+ sqlL = "SELECT data FROM {0}.SQL_QUEUE WHERE topic=:topic AND PandaID=:PandaID ORDER BY " "execution_order FOR UPDATE NOWAIT ".format(
3337
+ panda_config.schemaPANDA
3338
+ )
3339
+ sqlD = f"DELETE FROM {panda_config.schemaPANDA}.SQL_QUEUE WHERE PandaID=:PandaID "
3340
+ n_try = 5
3341
+ all_ok = True
3342
+ for i_try in range(n_try):
3343
+ all_ok = True
3344
+ query_list = []
3345
+ tmp_log.debug(f"Trying PandaID={tmp_id} {i_try+1}/{n_try}")
3346
+ tmp_data_list = None
3347
+ # start transaction
3348
+ self.conn.begin()
3349
+ # lock queries
3350
+ try:
3351
+ var_map = {
3352
+ ":topic": SQL_QUEUE_TOPIC_async_dataset_update,
3353
+ ":PandaID": tmp_id,
3354
+ }
3355
+ self.cur.execute(sqlL + comment, var_map)
3356
+ tmp_data_list = self.cur.fetchall()
3357
+ except Exception:
3358
+ tmp_log.debug("cannot lock queries")
3359
+ all_ok = False
3360
+ if tmp_data_list:
3361
+ # execute queries
3362
+ if all_ok:
3363
+ for (tmp_data,) in tmp_data_list:
3364
+ sql, var_map = json.loads(tmp_data)
3365
+ query_list.append((sql, var_map))
3366
+ try:
3367
+ self.cur.execute(sql + comment, var_map)
3368
+ except Exception:
3369
+ tmp_log.error(f'failed to execute "{sql}" var={str(var_map)}')
3370
+ self.dump_error_message(tmp_log)
3371
+ all_ok = False
3372
+ break
3373
+ # delete queries
3374
+ if all_ok:
3375
+ var_map = {":PandaID": tmp_id}
3376
+ self.cur.execute(sqlD + comment, var_map)
3377
+ # commit
3378
+ if all_ok:
3379
+ if not self._commit():
3380
+ raise RuntimeError("Commit error")
3381
+ for sql, var_map in query_list:
3382
+ tmp_log.debug(sql + str(var_map))
3383
+ tmp_log.debug("done")
3384
+ break
3385
+ else:
3386
+ self._rollback()
3387
+ if i_try + 1 < n_try:
3388
+ time.sleep(1)
3389
+ if not all_ok:
3390
+ tmp_log.error("all attempts failed")
3391
+ g_tmp_log.debug(f"processed {len(panda_id_list)} IDs")
3392
+ return True
3393
+ except Exception:
3394
+ # roll back
3395
+ self._rollback()
3396
+ # error
3397
+ self.dump_error_message(g_tmp_log)
3398
+ return False
3399
+
3400
+ # get datasets of input and lib, to update data locality records
3401
+ def get_tasks_inputdatasets_JEDI(self, vo):
3402
+ comment = " /* JediDBProxy.get_tasks_inputdatasets_JEDI */"
3403
+ # last update time
3404
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo}")
3405
+ tmpLog.debug("start")
3406
+ now_ts = naive_utcnow()
3407
+ try:
3408
+ retVal = None
3409
+ # sql to get all jediTaskID and datasetID of input
3410
+ sql = (
3411
+ "SELECT tabT.jediTaskID,datasetID, tabD.datasetName "
3412
+ "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD,{0}.JEDI_AUX_Status_MinTaskID tabA "
3413
+ "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID AND tabT.jediTaskID=tabD.jediTaskID "
3414
+ "AND tabT.vo=:vo AND tabT.status IN ('running', 'ready', 'scouting', 'pending') "
3415
+ "AND tabD.type IN ('input') AND tabD.masterID IS NULL "
3416
+ ).format(panda_config.schemaJEDI)
3417
+ # start transaction
3418
+ self.conn.begin()
3419
+ # get
3420
+ varMap = {}
3421
+ varMap[":vo"] = vo
3422
+ self.cur.execute(sql + comment, varMap)
3423
+ res = self.cur.fetchall()
3424
+ nRows = self.cur.rowcount
3425
+ # commit
3426
+ if not self._commit():
3427
+ raise RuntimeError("Commit error")
3428
+ # return
3429
+ retVal = res
3430
+ tmpLog.debug(f"done with {nRows} rows")
3431
+ return retVal
3432
+ except Exception:
3433
+ # roll back
3434
+ self._rollback()
3435
+ # error
3436
+ self.dump_error_message(tmpLog)
3437
+ return retVal
3438
+
3439
+ # update dataset locality
3440
+ def updateDatasetLocality_JEDI(self, jedi_taskid, datasetid, rse):
3441
+ comment = " /* JediDBProxy.updateDatasetLocality_JEDI */"
3442
+ # last update time
3443
+ timestamp = naive_utcnow()
3444
+ timestamp_str = timestamp.strftime("%Y-%m-%d_%H:%M:%S")
3445
+ tmpLog = self.create_tagged_logger(comment, f"taskID={jedi_taskid} datasetID={datasetid} rse={rse} timestamp={timestamp_str}")
3446
+ # tmpLog.debug('start')
3447
+ try:
3448
+ retVal = False
3449
+ # sql to check
3450
+ sqlC = f"SELECT timestamp FROM {panda_config.schemaJEDI}.JEDI_Dataset_Locality WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND rse=:rse "
3451
+ # sql to insert
3452
+ sqlI = (
3453
+ "INSERT INTO {0}.JEDI_Dataset_Locality " "(jediTaskID, datasetID, rse, timestamp) " "VALUES (:jediTaskID, :datasetID, :rse, :timestamp)"
3454
+ ).format(panda_config.schemaJEDI)
3455
+ # sql to update
3456
+ sqlU = (
3457
+ "UPDATE {0}.JEDI_Dataset_Locality " "SET timestamp=:timestamp " "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND rse=:rse "
3458
+ ).format(panda_config.schemaJEDI)
3459
+ # start transaction
3460
+ self.conn.begin()
3461
+ # check
3462
+ varMap = {}
3463
+ varMap[":jediTaskID"] = jedi_taskid
3464
+ varMap[":datasetID"] = datasetid
3465
+ varMap[":rse"] = rse
3466
+ self.cur.execute(sqlC + comment, varMap)
3467
+ resC = self.cur.fetchone()
3468
+ varMap[":timestamp"] = timestamp
3469
+ if resC is None:
3470
+ # insert if missing
3471
+ tmpLog.debug("insert")
3472
+ self.cur.execute(sqlI + comment, varMap)
3473
+ else:
3474
+ # update
3475
+ tmpLog.debug("update")
3476
+ self.cur.execute(sqlU + comment, varMap)
3477
+ # commit
3478
+ if not self._commit():
3479
+ raise RuntimeError("Commit error")
3480
+ # return
3481
+ retVal = True
3482
+ # tmpLog.debug('done')
3483
+ return retVal
3484
+ except Exception:
3485
+ # roll back
3486
+ self._rollback()
3487
+ # error
3488
+ self.dump_error_message(tmpLog)
3489
+ return retVal
3490
+
3491
+ # delete outdated dataset locality records
3492
+ def deleteOutdatedDatasetLocality_JEDI(self, before_timestamp):
3493
+ comment = " /* JediDBProxy.deleteOutdatedDatasetLocality_JEDI */"
3494
+ # last update time
3495
+ before_timestamp_str = before_timestamp.strftime("%Y-%m-%d_%H:%M:%S")
3496
+ tmpLog = self.create_tagged_logger(comment, f"before_timestamp={before_timestamp_str}")
3497
+ tmpLog.debug("start")
3498
+ try:
3499
+ retVal = 0
3500
+ # sql to delete
3501
+ sqlD = f"DELETE FROM {panda_config.schemaJEDI}.Jedi_Dataset_Locality WHERE timestamp<=:timestamp "
3502
+ # start transaction
3503
+ self.conn.begin()
3504
+ # check
3505
+ varMap = {}
3506
+ varMap[":timestamp"] = before_timestamp
3507
+ # delete
3508
+ self.cur.execute(sqlD + comment, varMap)
3509
+ retVal = self.cur.rowcount
3510
+ # commit
3511
+ if not self._commit():
3512
+ raise RuntimeError("Commit error")
3513
+ # return
3514
+ tmpLog.debug(f"done, deleted {retVal} records")
3515
+ return retVal
3516
+ except Exception:
3517
+ # roll back
3518
+ self._rollback()
3519
+ # error
3520
+ self.dump_error_message(tmpLog)
3521
+ return retVal
3522
+
3523
+ # append input datasets for incremental execution
3524
+ def appendDatasets_JEDI(self, jediTaskID, inMasterDatasetSpecList, inSecDatasetSpecList):
3525
+ comment = " /* JediDBProxy.appendDatasets_JEDI */"
3526
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
3527
+ tmpLog.debug("start")
3528
+ goDefined = False
3529
+ refreshContents = False
3530
+ commandStr = "incexec"
3531
+ try:
3532
+ # start transaction
3533
+ self.conn.begin()
3534
+ self.cur.arraysize = 100000
3535
+ # check task status
3536
+ varMap = {}
3537
+ varMap[":jediTaskID"] = jediTaskID
3538
+ sqlTK = f"SELECT status FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID FOR UPDATE "
3539
+ self.cur.execute(sqlTK + comment, varMap)
3540
+ resTK = self.cur.fetchone()
3541
+ if resTK is None:
3542
+ # task not found
3543
+ msgStr = "task not found"
3544
+ tmpLog.debug(msgStr)
3545
+ else:
3546
+ (taskStatus,) = resTK
3547
+ # invalid status
3548
+ if taskStatus != JediTaskSpec.commandStatusMap()[commandStr]["done"]:
3549
+ msgStr = f"invalid status={taskStatus} for dataset appending"
3550
+ tmpLog.debug(msgStr)
3551
+ else:
3552
+ timeNow = naive_utcnow()
3553
+ # list of master dataset names
3554
+ master_dataset_names = [datasetSpec.datasetName for datasetSpec in inMasterDatasetSpecList]
3555
+ # get existing input datasets
3556
+ varMap = {}
3557
+ varMap[":jediTaskID"] = jediTaskID
3558
+ sqlDS = "SELECT datasetName,datasetID,status,nFilesTobeUsed,nFilesUsed,masterID "
3559
+ sqlDS += f"FROM {panda_config.schemaJEDI}.JEDI_Datasets "
3560
+ sqlDS += f"WHERE jediTaskID=:jediTaskID AND type IN ({INPUT_TYPES_var_str}) "
3561
+ varMap.update(INPUT_TYPES_var_map)
3562
+ self.cur.execute(sqlDS + comment, varMap)
3563
+ resDS = self.cur.fetchall()
3564
+ # check if existing datasets are available, and update status if necessary
3565
+ sql_ex = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets SET status=:status WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
3566
+ existingDatasets = {}
3567
+ for datasetName, dataset_id, datasetStatus, nFilesTobeUsed, nFilesUsed, masterID in resDS:
3568
+ # only master datasets with remaining files
3569
+ try:
3570
+ if masterID is None and (nFilesTobeUsed - nFilesUsed > 0 or datasetStatus in JediDatasetSpec.statusToUpdateContents()):
3571
+ # the dataset was removed before and then added to the container again
3572
+ to_update_status = False
3573
+ if datasetStatus == "removed" and datasetName in master_dataset_names:
3574
+ to_update_status = True
3575
+ var_map = {":status": "defined", ":jediTaskID": jediTaskID, ":datasetID": dataset_id}
3576
+ # the dataset was removed
3577
+ elif datasetStatus != "removed" and datasetName not in master_dataset_names:
3578
+ to_update_status = True
3579
+ var_map = {":status": "removed", ":jediTaskID": jediTaskID, ":datasetID": dataset_id}
3580
+ # update status for removed/recovered dataset
3581
+ if to_update_status:
3582
+ tmpLog.debug(f"""set status={var_map[":status"]} from {datasetStatus} for {datasetName}""")
3583
+ self.cur.execute(sql_ex + comment, var_map)
3584
+ datasetStatus = var_map[":status"]
3585
+ if datasetStatus != "removed":
3586
+ goDefined = True
3587
+ if datasetStatus in JediDatasetSpec.statusToUpdateContents():
3588
+ refreshContents = True
3589
+ except Exception:
3590
+ pass
3591
+ existingDatasets[datasetName] = datasetStatus
3592
+ # insert datasets
3593
+ sqlID = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Datasets ({JediDatasetSpec.columnNames()}) "
3594
+ sqlID += JediDatasetSpec.bindValuesExpression()
3595
+ sqlID += " RETURNING datasetID INTO :newDatasetID"
3596
+ for datasetSpec in inMasterDatasetSpecList:
3597
+ # skip existing datasets
3598
+ if datasetSpec.datasetName in existingDatasets:
3599
+ # check dataset status and remaining files
3600
+ if existingDatasets[datasetSpec.datasetName] in JediDatasetSpec.statusToUpdateContents():
3601
+ goDefined = True
3602
+ continue
3603
+ datasetSpec.creationTime = timeNow
3604
+ datasetSpec.modificationTime = timeNow
3605
+ varMap = datasetSpec.valuesMap(useSeq=True)
3606
+ varMap[":newDatasetID"] = self.cur.var(varNUMBER)
3607
+ # insert dataset
3608
+ tmpLog.debug(f"append {datasetSpec.datasetName}")
3609
+ self.cur.execute(sqlID + comment, varMap)
3610
+ val = self.getvalue_corrector(self.cur.getvalue(varMap[":newDatasetID"]))
3611
+ datasetID = int(val)
3612
+ masterID = datasetID
3613
+ datasetSpec.datasetID = datasetID
3614
+ # insert secondary datasets
3615
+ for datasetSpec in inSecDatasetSpecList:
3616
+ datasetSpec.creationTime = timeNow
3617
+ datasetSpec.modificationTime = timeNow
3618
+ datasetSpec.masterID = masterID
3619
+ varMap = datasetSpec.valuesMap(useSeq=True)
3620
+ varMap[":newDatasetID"] = self.cur.var(varNUMBER)
3621
+ # insert dataset
3622
+ self.cur.execute(sqlID + comment, varMap)
3623
+ val = self.getvalue_corrector(self.cur.getvalue(varMap[":newDatasetID"]))
3624
+ datasetID = int(val)
3625
+ datasetSpec.datasetID = datasetID
3626
+ goDefined = True
3627
+ # update task
3628
+ deft_staus = None
3629
+ sqlUT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks "
3630
+ sqlUT += "SET status=:status,lockedBy=NULL,lockedTime=NULL,modificationtime=:updateTime,stateChangeTime=CURRENT_DATE "
3631
+ sqlUT += "WHERE jediTaskID=:jediTaskID "
3632
+ varMap = {}
3633
+ varMap[":jediTaskID"] = jediTaskID
3634
+ if goDefined:
3635
+ # no new datasets
3636
+ if inMasterDatasetSpecList == [] and not refreshContents:
3637
+ # pass to JobGenerator
3638
+ varMap[":status"] = "ready"
3639
+ deft_staus = "ready"
3640
+ else:
3641
+ # pass to ContentsFeeder
3642
+ varMap[":status"] = "defined"
3643
+ deft_staus = "registered"
3644
+ else:
3645
+ # go to finalization since no datasets are appended
3646
+ varMap[":status"] = "prepared"
3647
+ # set old update time to trigger subsequent process
3648
+ varMap[":updateTime"] = naive_utcnow() - datetime.timedelta(hours=6)
3649
+ tmpLog.debug(f"set taskStatus={varMap[':status']}")
3650
+ self.cur.execute(sqlUT + comment, varMap)
3651
+ # update DEFT status
3652
+ if deft_staus is not None:
3653
+ self.setDeftStatus_JEDI(jediTaskID, deft_staus)
3654
+ self.setSuperStatus_JEDI(jediTaskID, deft_staus)
3655
+ # add missing record_task_status_change and push_task_status_message updates
3656
+ self.record_task_status_change(jediTaskID)
3657
+ self.push_task_status_message(None, jediTaskID, varMap[":status"])
3658
+
3659
+ # commit
3660
+ if not self._commit():
3661
+ raise RuntimeError("Commit error")
3662
+ # return
3663
+ tmpLog.debug("done")
3664
+ return True
3665
+ except Exception:
3666
+ # roll back
3667
+ self._rollback()
3668
+ # error
3669
+ self.dump_error_message(tmpLog)
3670
+ return False
3671
+
3672
+ # insert dataset to the JEDI datasets table
3673
+ def insertDataset_JEDI(self, datasetSpec):
3674
+ comment = " /* JediDBProxy.insertDataset_JEDI */"
3675
+ tmpLog = self.create_tagged_logger(comment)
3676
+ tmpLog.debug("start")
3677
+ try:
3678
+ # set attributes
3679
+ timeNow = naive_utcnow()
3680
+ datasetSpec.creationTime = timeNow
3681
+ datasetSpec.modificationTime = timeNow
3682
+ # sql
3683
+ sql = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Datasets ({JediDatasetSpec.columnNames()}) "
3684
+ sql += JediDatasetSpec.bindValuesExpression()
3685
+ sql += " RETURNING datasetID INTO :newDatasetID"
3686
+ varMap = datasetSpec.valuesMap(useSeq=True)
3687
+ varMap[":newDatasetID"] = self.cur.var(varNUMBER)
3688
+ # begin transaction
3689
+ self.conn.begin()
3690
+ # insert dataset
3691
+ self.cur.execute(sql + comment, varMap)
3692
+ # commit
3693
+ if not self._commit():
3694
+ raise RuntimeError("Commit error")
3695
+ tmpLog.debug("done")
3696
+ val = self.getvalue_corrector(self.cur.getvalue(varMap[":newDatasetID"]))
3697
+ return True, int(val)
3698
+ except Exception:
3699
+ # roll back
3700
+ self._rollback()
3701
+ # error
3702
+ self.dump_error_message(tmpLog)
3703
+ return False, None
3704
+
3705
+ # update JEDI dataset
3706
+ def updateDataset_JEDI(self, datasetSpec, criteria, lockTask):
3707
+ comment = " /* JediDBProxy.updateDataset_JEDI */"
3708
+ tmpLog = self.create_tagged_logger(comment, f"datasetID={datasetSpec.datasetID}")
3709
+ tmpLog.debug("start")
3710
+ # return value for failure
3711
+ failedRet = False, 0
3712
+ # no criteria
3713
+ if criteria == {}:
3714
+ tmpLog.error("no selection criteria")
3715
+ return failedRet
3716
+ # check criteria
3717
+ for tmpKey in criteria.keys():
3718
+ if not hasattr(datasetSpec, tmpKey):
3719
+ tmpLog.error(f"unknown attribute {tmpKey} is used in criteria")
3720
+ return failedRet
3721
+ try:
3722
+ # set attributes
3723
+ timeNow = naive_utcnow()
3724
+ datasetSpec.modificationTime = timeNow
3725
+ # values for UPDATE
3726
+ varMap = datasetSpec.valuesMap(useSeq=False, onlyChanged=True)
3727
+ # sql for update
3728
+ sql = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets SET {datasetSpec.bindUpdateChangesExpression()} WHERE "
3729
+ useAND = False
3730
+ for tmpKey, tmpVal in criteria.items():
3731
+ crKey = f":cr_{tmpKey}"
3732
+ if useAND:
3733
+ sql += " AND"
3734
+ else:
3735
+ useAND = True
3736
+ sql += f" {tmpKey}={crKey}"
3737
+ varMap[crKey] = tmpVal
3738
+
3739
+ # sql for loc
3740
+ varMapLock = {}
3741
+ varMapLock[":jediTaskID"] = datasetSpec.jediTaskID
3742
+ sqlLock = f"SELECT 1 FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID FOR UPDATE"
3743
+ # begin transaction
3744
+ self.conn.begin()
3745
+ # lock task
3746
+ if lockTask:
3747
+ self.cur.execute(sqlLock + comment, varMapLock)
3748
+ # update dataset
3749
+ tmpLog.debug(sql + comment + str(varMap))
3750
+ self.cur.execute(sql + comment, varMap)
3751
+ # the number of updated rows
3752
+ nRows = self.cur.rowcount
3753
+ # commit
3754
+ if not self._commit():
3755
+ raise RuntimeError("Commit error")
3756
+ tmpLog.debug(f"updated {nRows} rows")
3757
+ return True, nRows
3758
+ except Exception:
3759
+ # roll back
3760
+ self._rollback()
3761
+ # error
3762
+ self.dump_error_message(tmpLog)
3763
+ return failedRet
3764
+
3765
+ # update JEDI dataset attributes
3766
+ def updateDatasetAttributes_JEDI(self, jediTaskID, datasetID, attributes):
3767
+ comment = " /* JediDBProxy.updateDatasetAttributes_JEDI */"
3768
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} datasetID={datasetID}")
3769
+ tmpLog.debug("start")
3770
+ # return value for failure
3771
+ failedRet = False
3772
+ try:
3773
+ # sql for update
3774
+ sql = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets SET "
3775
+ # values for UPDATE
3776
+ varMap = {}
3777
+ varMap[":jediTaskID"] = jediTaskID
3778
+ varMap[":datasetID"] = datasetID
3779
+ for tmpKey, tmpVal in attributes.items():
3780
+ crKey = f":{tmpKey}"
3781
+ sql += f"{tmpKey}={crKey},"
3782
+ varMap[crKey] = tmpVal
3783
+ sql = sql[:-1]
3784
+ sql += " "
3785
+ sql += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
3786
+ # begin transaction
3787
+ self.conn.begin()
3788
+ # update dataset
3789
+ tmpLog.debug(sql + comment + str(varMap))
3790
+ self.cur.execute(sql + comment, varMap)
3791
+ # the number of updated rows
3792
+ nRows = self.cur.rowcount
3793
+ # commit
3794
+ if not self._commit():
3795
+ raise RuntimeError("Commit error")
3796
+ tmpLog.debug(f"updated {nRows} rows")
3797
+ return True, nRows
3798
+ except Exception:
3799
+ # roll back
3800
+ self._rollback()
3801
+ # error
3802
+ self.dump_error_message(tmpLog)
3803
+ return failedRet
3804
+
3805
+ # get JEDI dataset attributes
3806
+ def getDatasetAttributes_JEDI(self, jediTaskID, datasetID, attributes):
3807
+ comment = " /* JediDBProxy.getDatasetAttributes_JEDI */"
3808
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} datasetID={datasetID}")
3809
+ tmpLog.debug("start")
3810
+ # return value for failure
3811
+ failedRet = {}
3812
+ try:
3813
+ # sql for get attributes
3814
+ sql = "SELECT "
3815
+ for tmpKey in attributes:
3816
+ sql += f"{tmpKey},"
3817
+ sql = sql[:-1] + " "
3818
+ sql += f"FROM {panda_config.schemaJEDI}.JEDI_Datasets "
3819
+ sql += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
3820
+ # values for UPDATE
3821
+ varMap = {}
3822
+ varMap[":jediTaskID"] = jediTaskID
3823
+ varMap[":datasetID"] = datasetID
3824
+ # begin transaction
3825
+ self.conn.begin()
3826
+ # select
3827
+ self.cur.execute(sql + comment, varMap)
3828
+ res = self.cur.fetchone()
3829
+ # commit
3830
+ if not self._commit():
3831
+ raise RuntimeError("Commit error")
3832
+ # make return
3833
+ retMap = {}
3834
+ if res is not None:
3835
+ for tmpIdx, tmpKey in enumerate(attributes):
3836
+ retMap[tmpKey] = res[tmpIdx]
3837
+ tmpLog.debug(f"got {str(retMap)}")
3838
+ return retMap
3839
+ except Exception:
3840
+ # roll back
3841
+ self._rollback()
3842
+ # error
3843
+ self.dump_error_message(tmpLog)
3844
+ return failedRet
3845
+
3846
+ # get JEDI dataset attributes with map
3847
+ def getDatasetAttributesWithMap_JEDI(self, jediTaskID, criteria, attributes):
3848
+ comment = " /* JediDBProxy.getDatasetAttributesWithMap_JEDI */"
3849
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} criteria={str(criteria)}")
3850
+ tmpLog.debug("start")
3851
+ # return value for failure
3852
+ failedRet = {}
3853
+ try:
3854
+ varMap = {}
3855
+ varMap[":jediTaskID"] = jediTaskID
3856
+ # sql for get attributes
3857
+ sql = "SELECT "
3858
+ for tmpKey in attributes:
3859
+ sql += f"{tmpKey},"
3860
+ sql = sql[:-1] + " "
3861
+ sql += f"FROM {panda_config.schemaJEDI}.JEDI_Datasets "
3862
+ sql += "WHERE jediTaskID=:jediTaskID "
3863
+ for crKey, crVal in criteria.items():
3864
+ sql += "AND {0}=:{0} ".format(crKey)
3865
+ varMap[f":{crKey}"] = crVal
3866
+ # begin transaction
3867
+ self.conn.begin()
3868
+ # select
3869
+ self.cur.execute(sql + comment, varMap)
3870
+ res = self.cur.fetchone()
3871
+ # commit
3872
+ if not self._commit():
3873
+ raise RuntimeError("Commit error")
3874
+ # make return
3875
+ retMap = {}
3876
+ if res is not None:
3877
+ for tmpIdx, tmpKey in enumerate(attributes):
3878
+ retMap[tmpKey] = res[tmpIdx]
3879
+ tmpLog.debug(f"got {str(retMap)}")
3880
+ return retMap
3881
+ except Exception:
3882
+ # roll back
3883
+ self._rollback()
3884
+ # error
3885
+ self.dump_error_message(tmpLog)
3886
+ return failedRet
3887
+
3888
+ # get JEDI dataset with datasetID
3889
+ def getDatasetWithID_JEDI(self, jediTaskID, datasetID):
3890
+ comment = " /* JediDBProxy.getDatasetWithID_JEDI */"
3891
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} datasetID={datasetID}")
3892
+ tmpLog.debug("start")
3893
+ # return value for failure
3894
+ failedRet = False, None
3895
+ try:
3896
+ # sql
3897
+ sql = f"SELECT {JediDatasetSpec.columnNames()} "
3898
+ sql += f"FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
3899
+ varMap = {}
3900
+ varMap[":jediTaskID"] = jediTaskID
3901
+ varMap[":datasetID"] = datasetID
3902
+ # begin transaction
3903
+ self.conn.begin()
3904
+ # select
3905
+ self.cur.execute(sql + comment, varMap)
3906
+ res = self.cur.fetchone()
3907
+ # commit
3908
+ if not self._commit():
3909
+ raise RuntimeError("Commit error")
3910
+ if res is not None:
3911
+ datasetSpec = JediDatasetSpec()
3912
+ datasetSpec.pack(res)
3913
+ else:
3914
+ datasetSpec = None
3915
+ tmpLog.debug("done")
3916
+ return True, datasetSpec
3917
+ except Exception:
3918
+ # roll back
3919
+ self._rollback()
3920
+ # error
3921
+ self.dump_error_message(tmpLog)
3922
+ return failedRet
3923
+
3924
+ # get JEDI datasets with jediTaskID
3925
+ def getDatasetsWithJediTaskID_JEDI(self, jediTaskID, datasetTypes=None):
3926
+ comment = " /* JediDBProxy.getDatasetsWithJediTaskID_JEDI */"
3927
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} datasetTypes={datasetTypes}")
3928
+ tmpLog.debug("start")
3929
+ # return value for failure
3930
+ failedRet = False, None
3931
+ try:
3932
+ # sql
3933
+ varMap = {}
3934
+ varMap[":jediTaskID"] = jediTaskID
3935
+ sql = f"SELECT {JediDatasetSpec.columnNames()} "
3936
+ sql += f"FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID "
3937
+ if datasetTypes is not None:
3938
+ dstype_var_names_str, dstype_var_map = get_sql_IN_bind_variables(datasetTypes, prefix=":type_", value_as_suffix=True)
3939
+ sql += f"AND type IN ({dstype_var_names_str}) "
3940
+ varMap.update(dstype_var_map)
3941
+ # begin transaction
3942
+ self.conn.begin()
3943
+ self.cur.arraysize = 10000
3944
+ # select
3945
+ self.cur.execute(sql + comment, varMap)
3946
+ tmpResList = self.cur.fetchall()
3947
+ # commit
3948
+ if not self._commit():
3949
+ raise RuntimeError("Commit error")
3950
+ # make file specs
3951
+ datasetSpecList = []
3952
+ for tmpRes in tmpResList:
3953
+ datasetSpec = JediDatasetSpec()
3954
+ datasetSpec.pack(tmpRes)
3955
+ datasetSpecList.append(datasetSpec)
3956
+ tmpLog.debug(f"done with {len(datasetSpecList)} datasets")
3957
+ return True, datasetSpecList
3958
+ except Exception:
3959
+ # roll back
3960
+ self._rollback()
3961
+ # error
3962
+ self.dump_error_message(tmpLog)
3963
+ return failedRet
3964
+
3965
+ # get jediTaskIDs with dataset attributes
3966
+ def get_task_ids_with_dataset_attributes(self, dataset_attributes: dict, only_active_tasks: bool = True) -> tuple[bool, list[int] | None]:
3967
+ """Get jediTaskIDs with dataset attributes.
3968
+ Args:
3969
+ dataset_attributes (dict): A dictionary of dataset attributes to filter on.
3970
+ only_active_tasks (bool): If True, only consider active tasks.
3971
+ Returns:
3972
+ tuple: A tuple containing a boolean indicating success, and a list of jediTaskIDs or None.
3973
+ """
3974
+ comment = " /* JediDBProxy.get_task_ids_with_dataset_attributes */"
3975
+ tmp_log = self.create_tagged_logger(comment, f"dataset_attributes={dataset_attributes} only_active_tasks={only_active_tasks}")
3976
+ tmp_log.debug("start")
3977
+ try:
3978
+ # sql
3979
+ sql = (
3980
+ f"SELECT DISTINCT tabT.jediTaskID FROM {panda_config.schemaJEDI}.JEDI_Tasks tabT,{panda_config.schemaJEDI}.JEDI_AUX_Status_MinTaskID tabA,{panda_config.schemaJEDI}.JEDI_Datasets tabD "
3981
+ "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID AND tabT.jediTaskID=tabD.jediTaskID "
3982
+ )
3983
+ for k, v in dataset_attributes.items():
3984
+ sql += f"AND tabD.{k}=:{k} "
3985
+ var_map = copy.copy(dataset_attributes)
3986
+ if only_active_tasks:
3987
+ task_var_names_str, task_var_map = get_sql_IN_bind_variables(
3988
+ JediTaskSpec.statusToRejectExtChange(), prefix=":task_status_", value_as_suffix=True
3989
+ )
3990
+ sql += f"AND tabT.status NOT IN ({task_var_names_str}) "
3991
+ var_map.update(task_var_map)
3992
+ # begin transaction
3993
+ self.conn.begin()
3994
+ self.cur.arraysize = 10000
3995
+ # select
3996
+ print(sql, str(var_map))
3997
+ self.cur.execute(sql + comment, var_map)
3998
+ tmp_res = self.cur.fetchall()
3999
+ # commit
4000
+ if not self._commit():
4001
+ raise RuntimeError("Commit error")
4002
+ task_id_list = [row[0] for row in tmp_res]
4003
+ tmp_log.debug(f"done with {len(task_id_list)} tasks")
4004
+ return True, task_id_list
4005
+ except Exception:
4006
+ # roll back
4007
+ self._rollback()
4008
+ # error
4009
+ self.dump_error_message(tmp_log)
4010
+ return False, None
4011
+
4012
+ # extend lifetime of sandbox file
4013
+ def extendSandboxLifetime_JEDI(self, jedi_taskid, file_name):
4014
+ comment = " /* JediDBProxy.extendSandboxLifetime_JEDI */"
4015
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jedi_taskid}")
4016
+ try:
4017
+ self.conn.begin()
4018
+ retVal = False
4019
+ # sql to update
4020
+ sqlC = f"UPDATE {panda_config.schemaMETA}.userCacheUsage SET creationTime=CURRENT_DATE WHERE fileName=:fileName "
4021
+ varMap = {}
4022
+ varMap[":fileName"] = file_name
4023
+ self.cur.execute(sqlC + comment, varMap)
4024
+ nRows = self.cur.rowcount
4025
+ if not self._commit():
4026
+ raise RuntimeError("Commit error")
4027
+ tmpLog.debug(f"done {file_name} with {nRows}")
4028
+ # return
4029
+ return nRows
4030
+ except Exception:
4031
+ # roll back
4032
+ self._rollback()
4033
+ # error
4034
+ self.dump_error_message(tmpLog)
4035
+ return None
4036
+
4037
+ # lock process
4038
+ def lockProcess_JEDI(self, vo, prodSourceLabel, cloud, workqueue_id, resource_name, component, pid, forceOption, timeLimit):
4039
+ comment = " /* JediDBProxy.lockProcess_JEDI */"
4040
+ # defaults
4041
+ if cloud is None:
4042
+ cloud = "default"
4043
+ if workqueue_id is None:
4044
+ workqueue_id = 0
4045
+ if resource_name is None:
4046
+ resource_name = "default"
4047
+ if component is None:
4048
+ component = "default"
4049
+ tmpLog = self.create_tagged_logger(
4050
+ comment, f"vo={vo} label={prodSourceLabel} cloud={cloud} queue={workqueue_id} resource_type={resource_name} component={component} pid={pid}"
4051
+ )
4052
+ tmpLog.debug("start")
4053
+ try:
4054
+ retVal = False
4055
+ # sql to check
4056
+ sqlCT = "SELECT lockedBy "
4057
+ sqlCT += f"FROM {panda_config.schemaJEDI}.JEDI_Process_Lock "
4058
+ sqlCT += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel AND cloud=:cloud AND workqueue_id=:workqueue_id "
4059
+ sqlCT += "AND resource_type=:resource_name AND component=:component "
4060
+ sqlCT += "AND lockedTime>:timeLimit "
4061
+ sqlCT += "FOR UPDATE"
4062
+ # sql to delete
4063
+ sqlCD = f"DELETE FROM {panda_config.schemaJEDI}.JEDI_Process_Lock "
4064
+ sqlCD += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel AND cloud=:cloud AND workqueue_id=:workqueue_id "
4065
+ sqlCD += "AND resource_type=:resource_name AND component=:component "
4066
+ # sql to insert
4067
+ sqlFR = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Process_Lock "
4068
+ sqlFR += "(vo, prodSourceLabel, cloud, workqueue_id, resource_type, component, lockedBy, lockedTime) "
4069
+ sqlFR += "VALUES(:vo, :prodSourceLabel, :cloud, :workqueue_id, :resource_name, :component, :lockedBy, CURRENT_DATE) "
4070
+ # start transaction
4071
+ self.conn.begin()
4072
+ # check
4073
+ if not forceOption:
4074
+ varMap = {}
4075
+ varMap[":vo"] = vo
4076
+ varMap[":prodSourceLabel"] = prodSourceLabel
4077
+ varMap[":cloud"] = cloud
4078
+ varMap[":workqueue_id"] = workqueue_id
4079
+ varMap[":resource_name"] = resource_name
4080
+ varMap[":component"] = component
4081
+ varMap[":timeLimit"] = naive_utcnow() - datetime.timedelta(minutes=timeLimit)
4082
+ self.cur.execute(sqlCT + comment, varMap)
4083
+ resCT = self.cur.fetchone()
4084
+ else:
4085
+ resCT = None
4086
+ if resCT is not None:
4087
+ tmpLog.debug(f"skipped, locked by {resCT[0]}")
4088
+ else:
4089
+ # delete
4090
+ varMap = {}
4091
+ varMap[":vo"] = vo
4092
+ varMap[":prodSourceLabel"] = prodSourceLabel
4093
+ varMap[":cloud"] = cloud
4094
+ varMap[":workqueue_id"] = workqueue_id
4095
+ varMap[":resource_name"] = resource_name
4096
+ varMap[":component"] = component
4097
+ self.cur.execute(sqlCD + comment, varMap)
4098
+ # insert
4099
+ varMap = {}
4100
+ varMap[":vo"] = vo
4101
+ varMap[":prodSourceLabel"] = prodSourceLabel
4102
+ varMap[":cloud"] = cloud
4103
+ varMap[":workqueue_id"] = workqueue_id
4104
+ varMap[":resource_name"] = resource_name
4105
+ varMap[":component"] = component
4106
+ varMap[":lockedBy"] = pid
4107
+ self.cur.execute(sqlFR + comment, varMap)
4108
+ tmpLog.debug("successfully locked")
4109
+ retVal = True
4110
+ # commit
4111
+ if not self._commit():
4112
+ raise RuntimeError("Commit error")
4113
+ return retVal
4114
+ except Exception:
4115
+ # roll back
4116
+ self._rollback()
4117
+ # error
4118
+ self.dump_error_message(tmpLog)
4119
+ return retVal
4120
+
4121
+ # unlock process
4122
+ def unlockProcess_JEDI(self, vo, prodSourceLabel, cloud, workqueue_id, resource_name, component, pid):
4123
+ comment = " /* JediDBProxy.unlockProcess_JEDI */"
4124
+ # defaults
4125
+ if cloud is None:
4126
+ cloud = "default"
4127
+ if workqueue_id is None:
4128
+ workqueue_id = 0
4129
+ if resource_name is None:
4130
+ resource_name = "default"
4131
+ if component is None:
4132
+ component = "default"
4133
+ tmpLog = self.create_tagged_logger(
4134
+ comment, f"vo={vo} label={prodSourceLabel} cloud={cloud} queue={workqueue_id} resource_type={resource_name} component={component} pid={pid}"
4135
+ )
4136
+ tmpLog.debug("start")
4137
+ try:
4138
+ retVal = False
4139
+ # sql to delete
4140
+ sqlCD = f"DELETE FROM {panda_config.schemaJEDI}.JEDI_Process_Lock "
4141
+ sqlCD += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel AND cloud=:cloud "
4142
+ sqlCD += "AND workqueue_id=:workqueue_id AND lockedBy=:lockedBy "
4143
+ sqlCD += "AND resource_type=:resource_name AND component=:component "
4144
+ # start transaction
4145
+ self.conn.begin()
4146
+ # check
4147
+ varMap = {}
4148
+ varMap[":vo"] = vo
4149
+ varMap[":prodSourceLabel"] = prodSourceLabel
4150
+ varMap[":cloud"] = cloud
4151
+ varMap[":workqueue_id"] = workqueue_id
4152
+ varMap[":resource_name"] = resource_name
4153
+ varMap[":component"] = component
4154
+ varMap[":lockedBy"] = pid
4155
+ self.cur.execute(sqlCD + comment, varMap)
4156
+ # commit
4157
+ if not self._commit():
4158
+ raise RuntimeError("Commit error")
4159
+ tmpLog.debug("done")
4160
+ retVal = True
4161
+ return retVal
4162
+ except Exception:
4163
+ # roll back
4164
+ self._rollback()
4165
+ # error
4166
+ self.dump_error_message(tmpLog)
4167
+ return retVal
4168
+
4169
+ # unlock process with PID
4170
+ def unlockProcessWithPID_JEDI(self, vo, prodSourceLabel, workqueue_id, resource_name, pid, useBase):
4171
+ comment = " /* JediDBProxy.unlockProcessWithPID_JEDI */"
4172
+ tmpLog = self.create_tagged_logger(
4173
+ comment, f"vo={vo} label={prodSourceLabel} queue={workqueue_id} resource_type={resource_name} pid={pid} useBase={useBase}"
4174
+ )
4175
+ tmpLog.debug("start")
4176
+ try:
4177
+ retVal = False
4178
+ # sql to delete
4179
+ sqlCD = f"DELETE FROM {panda_config.schemaJEDI}.JEDI_Process_Lock "
4180
+ sqlCD += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
4181
+ sqlCD += "AND workqueue_id=:workqueue_id "
4182
+ sqlCD += "AND resource_name=:resource_name "
4183
+ if useBase:
4184
+ sqlCD += "AND lockedBy LIKE :lockedBy "
4185
+ else:
4186
+ sqlCD += "AND lockedBy=:lockedBy "
4187
+ # start transaction
4188
+ self.conn.begin()
4189
+ # delete
4190
+ varMap = {}
4191
+ varMap[":vo"] = vo
4192
+ varMap[":prodSourceLabel"] = prodSourceLabel
4193
+ varMap[":workqueue_id"] = workqueue_id
4194
+ varMap[":resource_name"] = resource_name
4195
+ if useBase:
4196
+ varMap[":lockedBy"] = pid + "%"
4197
+ else:
4198
+ varMap[":lockedBy"] = pid
4199
+ self.cur.execute(sqlCD + comment, varMap)
4200
+ # commit
4201
+ if not self._commit():
4202
+ raise RuntimeError("Commit error")
4203
+ tmpLog.debug("done")
4204
+ retVal = True
4205
+ return retVal
4206
+ except Exception:
4207
+ # roll back
4208
+ self._rollback()
4209
+ # error
4210
+ self.dump_error_message(tmpLog)
4211
+ return retVal
4212
+
4213
+ # check process lock
4214
+ def checkProcessLock_JEDI(self, vo, prodSourceLabel, cloud, workqueue_id, resource_name, component, pid, checkBase):
4215
+ comment = " /* JediDBProxy.checkProcessLock_JEDI */"
4216
+ # defaults
4217
+ if cloud is None:
4218
+ cloud = "default"
4219
+ if workqueue_id is None:
4220
+ workqueue_id = 0
4221
+ if resource_name is None:
4222
+ resource_name = "default"
4223
+ if component is None:
4224
+ component = "default"
4225
+ tmpLog = self.create_tagged_logger(
4226
+ comment, f"vo={vo} label={prodSourceLabel} cloud={cloud} queue={workqueue_id} resource_type={resource_name} component={component} pid={pid}"
4227
+ )
4228
+ tmpLog.debug("start")
4229
+ try:
4230
+ retVal = False
4231
+ # sql to check
4232
+ sqlCT = "SELECT lockedBy "
4233
+ sqlCT += f"FROM {panda_config.schemaJEDI}.JEDI_Process_Lock "
4234
+ sqlCT += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel AND cloud=:cloud AND workqueue_id=:workqueue_id "
4235
+ sqlCT += "AND resource_type=:resource_name AND component=:component "
4236
+ sqlCT += "AND lockedTime>:timeLimit "
4237
+ # start transaction
4238
+ self.conn.begin()
4239
+ # check
4240
+ varMap = {}
4241
+ varMap[":vo"] = vo
4242
+ varMap[":prodSourceLabel"] = prodSourceLabel
4243
+ varMap[":cloud"] = cloud
4244
+ varMap[":workqueue_id"] = workqueue_id
4245
+ varMap[":resource_name"] = resource_name
4246
+ varMap[":component"] = component
4247
+ varMap[":timeLimit"] = naive_utcnow() - datetime.timedelta(minutes=5)
4248
+ self.cur.execute(sqlCT + comment, varMap)
4249
+ resCT = self.cur.fetchone()
4250
+ if resCT is not None:
4251
+ (lockedBy,) = resCT
4252
+ if checkBase:
4253
+ # check only base part
4254
+ if not lockedBy.startswith(pid):
4255
+ retVal = True
4256
+ else:
4257
+ # check whole string
4258
+ if lockedBy != pid:
4259
+ retVal = True
4260
+ if retVal is True:
4261
+ tmpLog.debug(f"skipped locked by {lockedBy}")
4262
+ # commit
4263
+ if not self._commit():
4264
+ raise RuntimeError("Commit error")
4265
+ tmpLog.debug(f"done with {retVal}")
4266
+ return retVal
4267
+ except Exception:
4268
+ # roll back
4269
+ self._rollback()
4270
+ # error
4271
+ self.dump_error_message(tmpLog)
4272
+ return retVal