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,2640 @@
1
+ import datetime
2
+ import json
3
+ import random
4
+ import re
5
+ import time
6
+
7
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
8
+ from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
9
+
10
+ from pandaserver.config import panda_config
11
+ from pandaserver.srvcore import CoreUtils, srv_msg_utils
12
+ from pandaserver.taskbuffer import EventServiceUtils, JobUtils
13
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule
14
+ from pandaserver.taskbuffer.FileSpec import FileSpec
15
+ from pandaserver.taskbuffer.JobSpec import JobSpec
16
+
17
+
18
+ # Module class to define miscellaneous job-related methods that are independent of another module's methods
19
+ class JobStandaloneModule(BaseModule):
20
+ # constructor
21
+ def __init__(self, log_stream: LogWrapper):
22
+ super().__init__(log_stream)
23
+
24
+ # activate job. move job from jobsDefined to jobsActive
25
+ def activateJob(self, job):
26
+ comment = " /* DBProxy.activateJob */"
27
+ if job is None:
28
+ tmp_id = None
29
+ else:
30
+ tmp_id = job.PandaID
31
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={tmp_id}")
32
+ updatedFlag = False
33
+ if job is None:
34
+ tmp_log.debug("skip job=None")
35
+ return True
36
+ tmp_log.debug("start")
37
+ sql0 = "SELECT row_ID FROM ATLAS_PANDA.filesTable4 WHERE PandaID=:PandaID AND type=:type AND NOT status IN (:status1,:status2) "
38
+ sql1 = "DELETE FROM ATLAS_PANDA.jobsDefined4 "
39
+ sql1 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2) AND commandToPilot IS NULL"
40
+ sql2 = f"INSERT INTO ATLAS_PANDA.jobsActive4 ({JobSpec.columnNames()}) "
41
+ sql2 += JobSpec.bindValuesExpression()
42
+ # host and time information
43
+ job.modificationTime = naive_utcnow()
44
+ # set stateChangeTime for defined->activated but not for assigned->activated
45
+ if job.jobStatus in ["defined"]:
46
+ job.stateChangeTime = job.modificationTime
47
+ nTry = 3
48
+ to_push = False
49
+ for iTry in range(nTry):
50
+ try:
51
+ # check if all files are ready
52
+ allOK = True
53
+ for file in job.Files:
54
+ if file.type == "input" and file.status not in ["ready", "cached"]:
55
+ allOK = False
56
+ break
57
+ # begin transaction
58
+ self.conn.begin()
59
+ # check all inputs are ready
60
+ varMap = {}
61
+ varMap[":type"] = "input"
62
+ varMap[":status1"] = "ready"
63
+ varMap[":status2"] = "cached"
64
+ varMap[":PandaID"] = job.PandaID
65
+ self.cur.arraysize = 100
66
+ self.cur.execute(sql0 + comment, varMap)
67
+ res = self.cur.fetchall()
68
+ if len(res) == 0 or allOK:
69
+ # check resource share
70
+ job.jobStatus = "activated"
71
+
72
+ # delete
73
+ varMap = {}
74
+ varMap[":PandaID"] = job.PandaID
75
+ varMap[":oldJobStatus1"] = "assigned"
76
+ varMap[":oldJobStatus2"] = "defined"
77
+ self.cur.execute(sql1 + comment, varMap)
78
+ n = self.cur.rowcount
79
+ if n == 0:
80
+ # already killed or activated
81
+ tmp_log.debug("Job not found to activate")
82
+ else:
83
+ # insert
84
+ self.cur.execute(sql2 + comment, job.valuesMap())
85
+ # update files
86
+ for file in job.Files:
87
+ sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
88
+ varMap = file.valuesMap(onlyChanged=True)
89
+ if varMap != {}:
90
+ varMap[":row_ID"] = file.row_ID
91
+ tmp_log.debug(sqlF + comment + str(varMap))
92
+ self.cur.execute(sqlF + comment, varMap)
93
+ # job parameters
94
+ sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
95
+ varMap = {}
96
+ varMap[":PandaID"] = job.PandaID
97
+ varMap[":param"] = job.jobParameters
98
+ self.cur.execute(sqlJob + comment, varMap)
99
+ updatedFlag = True
100
+ to_push = job.is_push_job()
101
+ else:
102
+ # update job
103
+ sqlJ = (
104
+ f"UPDATE ATLAS_PANDA.jobsDefined4 SET {job.bindUpdateChangesExpression()} "
105
+ ) + "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2)"
106
+ varMap = job.valuesMap(onlyChanged=True)
107
+ varMap[":PandaID"] = job.PandaID
108
+ varMap[":oldJobStatus1"] = "assigned"
109
+ varMap[":oldJobStatus2"] = "defined"
110
+ tmp_log.debug(sqlJ + comment + str(varMap))
111
+ self.cur.execute(sqlJ + comment, varMap)
112
+ n = self.cur.rowcount
113
+ if n == 0:
114
+ # already killed or activated
115
+ tmp_log.debug("Job not found to update")
116
+ else:
117
+ # update files
118
+ for file in job.Files:
119
+ sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
120
+ varMap = file.valuesMap(onlyChanged=True)
121
+ if varMap != {}:
122
+ varMap[":row_ID"] = file.row_ID
123
+ tmp_log.debug(sqlF + comment + str(varMap))
124
+ self.cur.execute(sqlF + comment, varMap)
125
+ # job parameters
126
+ sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
127
+ varMap = {}
128
+ varMap[":PandaID"] = job.PandaID
129
+ varMap[":param"] = job.jobParameters
130
+ self.cur.execute(sqlJob + comment, varMap)
131
+ updatedFlag = True
132
+ # commit
133
+ if not self._commit():
134
+ raise RuntimeError("Commit error")
135
+ # record status change
136
+ try:
137
+ if updatedFlag:
138
+ self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
139
+ except Exception:
140
+ tmp_log.error("recordStatusChange failed")
141
+ self.push_job_status_message(job, job.PandaID, job.jobStatus)
142
+ # push job
143
+ if to_push:
144
+ mb_proxy_queue = self.get_mb_proxy("panda_pilot_queue")
145
+ mb_proxy_topic = self.get_mb_proxy("panda_pilot_topic")
146
+ if mb_proxy_queue and mb_proxy_topic:
147
+ tmp_log.debug("push job")
148
+ srv_msg_utils.send_job_message(mb_proxy_queue, mb_proxy_topic, job.jediTaskID, job.PandaID)
149
+ else:
150
+ tmp_log.debug("message queue/topic not configured")
151
+ tmp_log.debug("done")
152
+ return True
153
+ except Exception as e:
154
+ # roll back
155
+ self._rollback()
156
+ if iTry + 1 < nTry:
157
+ tmp_log.debug(f"retry: {iTry}")
158
+ time.sleep(random.randint(10, 20))
159
+ continue
160
+ self.dump_error_message(tmp_log)
161
+ return False
162
+
163
+ # send job to jobsWaiting
164
+ def keepJob(self, job):
165
+ comment = " /* DBProxy.keepJob */"
166
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={job.PandaID}")
167
+ tmp_log.debug("start")
168
+ # set status
169
+ job.jobStatus = "waiting"
170
+ sql1 = f"UPDATE ATLAS_PANDA.jobsDefined4 SET {job.bindUpdateChangesExpression()} "
171
+ sql1 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2) AND commandToPilot IS NULL"
172
+ # time information
173
+ job.modificationTime = naive_utcnow()
174
+ job.stateChangeTime = job.modificationTime
175
+ updatedFlag = False
176
+ nTry = 3
177
+ for iTry in range(nTry):
178
+ try:
179
+ # begin transaction
180
+ self.conn.begin()
181
+ # update
182
+ varMap = job.valuesMap(onlyChanged=True)
183
+ varMap[":PandaID"] = job.PandaID
184
+ varMap[":oldJobStatus1"] = "assigned"
185
+ varMap[":oldJobStatus2"] = "defined"
186
+ self.cur.execute(sql1 + comment, varMap)
187
+ n = self.cur.rowcount
188
+ if n == 0:
189
+ # already killed
190
+ tmp_log.debug(f"Not found")
191
+ else:
192
+ # update files
193
+ for file in job.Files:
194
+ sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
195
+ varMap = file.valuesMap(onlyChanged=True)
196
+ if varMap != {}:
197
+ varMap[":row_ID"] = file.row_ID
198
+ self.cur.execute(sqlF + comment, varMap)
199
+ # update parameters
200
+ sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
201
+ varMap = {}
202
+ varMap[":PandaID"] = job.PandaID
203
+ varMap[":param"] = job.jobParameters
204
+ self.cur.execute(sqlJob + comment, varMap)
205
+ updatedFlag = True
206
+ # commit
207
+ if not self._commit():
208
+ raise RuntimeError("Commit error")
209
+ # record status change
210
+ try:
211
+ if updatedFlag:
212
+ self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
213
+ self.push_job_status_message(job, job.PandaID, job.jobStatus)
214
+ except Exception:
215
+ tmp_log.error("recordStatusChange in keepJob")
216
+ return True
217
+ except Exception:
218
+ # roll back
219
+ self._rollback()
220
+ if iTry + 1 < nTry:
221
+ tmp_log.debug(f"retry : {iTry}")
222
+ time.sleep(random.randint(10, 20))
223
+ continue
224
+ # dump error
225
+ self.dump_error_message(tmp_log)
226
+ return False
227
+
228
+ # reset job in jobsActive
229
+ def resetJob(
230
+ self,
231
+ pandaID,
232
+ activeTable=True,
233
+ keepSite=False,
234
+ getOldSubs=False,
235
+ forPending=True,
236
+ ):
237
+ comment = " /* DBProxy.resetJob */"
238
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
239
+ tmp_log.debug(f"activeTable={activeTable}")
240
+ # select table
241
+ table = "ATLAS_PANDA.jobsActive4"
242
+ sql1 = f"SELECT {JobSpec.columnNames()} FROM {table} "
243
+ sql1 += "WHERE PandaID=:PandaID"
244
+ sql2 = f"DELETE FROM {table} "
245
+ sql2 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2)"
246
+ sql3 = f"INSERT INTO ATLAS_PANDA.jobsDefined4 ({JobSpec.columnNames()}) "
247
+ sql3 += JobSpec.bindValuesExpression()
248
+ try:
249
+ # transaction causes Request ndbd time-out in ATLAS_PANDA.jobsActive4
250
+ self.conn.begin()
251
+ # select
252
+ varMap = {}
253
+ varMap[":PandaID"] = pandaID
254
+ self.cur.arraysize = 10
255
+ self.cur.execute(sql1 + comment, varMap)
256
+ res = self.cur.fetchone()
257
+ # not found
258
+ if res is None:
259
+ # commit
260
+ if not self._commit():
261
+ raise RuntimeError("Commit error")
262
+ # return
263
+ return None
264
+ # instantiate Job
265
+ job = JobSpec()
266
+ job.pack(res)
267
+ # if already running
268
+ if job.jobStatus != "waiting" and job.jobStatus != "activated" and (forPending and job.jobStatus != "pending"):
269
+ # commit
270
+ if not self._commit():
271
+ raise RuntimeError("Commit error")
272
+ # return
273
+ return None
274
+ # do nothing for analysis jobs
275
+ if job.prodSourceLabel in ["user", "panda"] and not forPending and job.jobStatus != "pending":
276
+ # commit
277
+ if not self._commit():
278
+ raise RuntimeError("Commit error")
279
+ # return
280
+ return None
281
+ # delete
282
+ varMap = {}
283
+ varMap[":PandaID"] = pandaID
284
+ if not forPending:
285
+ varMap[":oldJobStatus1"] = "waiting"
286
+ else:
287
+ varMap[":oldJobStatus1"] = "pending"
288
+ varMap[":oldJobStatus2"] = "activated"
289
+ self.cur.execute(sql2 + comment, varMap)
290
+ retD = self.cur.rowcount
291
+ # delete failed
292
+ tmp_log.debug(f"retD = {retD}")
293
+ if retD != 1:
294
+ # commit
295
+ if not self._commit():
296
+ raise RuntimeError("Commit error")
297
+ return None
298
+ # delete from jobsDefined4 just in case
299
+ varMap = {}
300
+ varMap[":PandaID"] = pandaID
301
+ sqlD = "DELETE FROM ATLAS_PANDA.jobsDefined4 WHERE PandaID=:PandaID"
302
+ self.cur.execute(sqlD + comment, varMap)
303
+ # increase priority
304
+ if job.jobStatus == "activated" and job.currentPriority < 100:
305
+ job.currentPriority = 100
306
+ # reset computing site and dispatchDBlocks
307
+ job.jobStatus = "defined"
308
+ if job.prodSourceLabel not in ["user", "panda"]:
309
+ job.dispatchDBlock = None
310
+ # erase old assignment
311
+ if (not keepSite) and job.relocationFlag not in [1, 2]:
312
+ job.computingSite = None
313
+ job.computingElement = None
314
+ # host and time information
315
+ job.modificationHost = self.hostname
316
+ job.modificationTime = naive_utcnow()
317
+ job.stateChangeTime = job.modificationTime
318
+ # reset
319
+ job.brokerageErrorDiag = None
320
+ job.brokerageErrorCode = None
321
+ # insert
322
+ self.cur.execute(sql3 + comment, job.valuesMap())
323
+ # job parameters
324
+ sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
325
+ self.cur.execute(sqlJobP + comment, varMap)
326
+ for (clobJobP,) in self.cur:
327
+ try:
328
+ job.jobParameters = clobJobP.read()
329
+ except AttributeError:
330
+ job.jobParameters = str(clobJobP)
331
+ break
332
+ # Files
333
+ oldSubList = []
334
+ sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
335
+ sqlFile += "WHERE PandaID=:PandaID"
336
+ self.cur.arraysize = 10000
337
+ self.cur.execute(sqlFile + comment, varMap)
338
+ resFs = self.cur.fetchall()
339
+ for resF in resFs:
340
+ file = FileSpec()
341
+ file.pack(resF)
342
+ # reset GUID to trigger LRC/LFC scanning
343
+ if file.status == "missing":
344
+ file.GUID = None
345
+ # collect old subs
346
+ if job.prodSourceLabel in ["managed", "test"] and file.type in ["output", "log"] and re.search("_sub\d+$", file.destinationDBlock) is not None:
347
+ if file.destinationDBlock not in oldSubList:
348
+ oldSubList.append(file.destinationDBlock)
349
+ # reset status, destinationDBlock and dispatchDBlock
350
+ if job.lockedby != "jedi":
351
+ file.status = "unknown"
352
+ if job.prodSourceLabel not in ["user", "panda"]:
353
+ file.dispatchDBlock = None
354
+ file.destinationDBlock = re.sub("_sub\d+$", "", file.destinationDBlock)
355
+ # add file
356
+ job.addFile(file)
357
+ # update files
358
+ sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
359
+ varMap = file.valuesMap(onlyChanged=True)
360
+ if varMap != {}:
361
+ varMap[":row_ID"] = file.row_ID
362
+ tmp_log.debug(sqlF + comment + str(varMap))
363
+ self.cur.execute(sqlF + comment, varMap)
364
+ # commit
365
+ if not self._commit():
366
+ raise RuntimeError("Commit error")
367
+ # record status change
368
+ try:
369
+ self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
370
+ except Exception:
371
+ tmp_log.error("recordStatusChange in resetJobs")
372
+ self.push_job_status_message(job, job.PandaID, job.jobStatus)
373
+ tmp_log.debug(f"done with {job is not None}")
374
+ if getOldSubs:
375
+ return job, oldSubList
376
+ return job
377
+ except Exception:
378
+ # roll back
379
+ self._rollback()
380
+ # error report
381
+ self.dump_error_message(tmp_log)
382
+ return None
383
+
384
+ # reset jobs in jobsDefined
385
+ def resetDefinedJob(self, pandaID):
386
+ comment = " /* DBProxy.resetDefinedJob */"
387
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
388
+ tmp_log.debug("start")
389
+ sql1 = "UPDATE ATLAS_PANDA.jobsDefined4 SET "
390
+ sql1 += "jobStatus=:newJobStatus,"
391
+ sql1 += "modificationTime=CURRENT_DATE,"
392
+ sql1 += "modificationHost=:modificationHost"
393
+ sql1 += " WHERE PandaID=:PandaID AND jobStatus IN (:oldJobStatus1,:oldJobStatus2,:oldJobStatus3) "
394
+ sql2 = f"SELECT {JobSpec.columnNames()} FROM ATLAS_PANDA.jobsDefined4 "
395
+ sql2 += "WHERE PandaID=:PandaID"
396
+ try:
397
+ # begin transaction
398
+ self.conn.begin()
399
+ # update
400
+ varMap = {}
401
+ varMap[":PandaID"] = pandaID
402
+ varMap[":newJobStatus"] = "defined"
403
+ varMap[":oldJobStatus1"] = "assigned"
404
+ varMap[":oldJobStatus2"] = "defined"
405
+ varMap[":oldJobStatus3"] = "pending"
406
+ varMap[":modificationHost"] = self.hostname
407
+ self.cur.execute(sql1 + comment, varMap)
408
+ retU = self.cur.rowcount
409
+ # not found
410
+ updatedFlag = True
411
+ job = None
412
+ if retU == 0:
413
+ tmp_log.debug("Not found for UPDATE")
414
+ updatedFlag = False
415
+ else:
416
+ # select
417
+ varMap = {}
418
+ varMap[":PandaID"] = pandaID
419
+ self.cur.arraysize = 10
420
+ self.cur.execute(sql2 + comment, varMap)
421
+ res = self.cur.fetchone()
422
+ # not found
423
+ if res is None:
424
+ raise RuntimeError(f"Not found for SELECT")
425
+ # instantiate Job
426
+ job = JobSpec()
427
+ job.pack(res)
428
+ # job parameters
429
+ sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
430
+ self.cur.execute(sqlJobP + comment, varMap)
431
+ for (clobJobP,) in self.cur:
432
+ try:
433
+ job.jobParameters = clobJobP.read()
434
+ except AttributeError:
435
+ job.jobParameters = str(clobJobP)
436
+ break
437
+ # Files
438
+ sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
439
+ sqlFile += "WHERE PandaID=:PandaID"
440
+ self.cur.arraysize = 10000
441
+ self.cur.execute(sqlFile + comment, varMap)
442
+ resFs = self.cur.fetchall()
443
+ for resF in resFs:
444
+ file = FileSpec()
445
+ file.pack(resF)
446
+ # add file
447
+ job.addFile(file)
448
+ # commit
449
+ if not self._commit():
450
+ raise RuntimeError("Commit error")
451
+ # record status change
452
+ try:
453
+ if updatedFlag:
454
+ self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
455
+ self.push_job_status_message(job, job.PandaID, job.jobStatus)
456
+ except Exception:
457
+ tmp_log.error("recordStatusChange in resetDefinedJobs")
458
+ tmp_log.debug(f"done with {job is not None}")
459
+ return job
460
+ except Exception:
461
+ self.dump_error_message(tmp_log)
462
+ # roll back
463
+ self._rollback()
464
+ return None
465
+
466
+ # peek at job
467
+ def peekJob(self, pandaID, fromDefined, fromActive, fromArchived, fromWaiting, forAnal=False):
468
+ comment = " /* DBProxy.peekJob */"
469
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
470
+ # return None for NULL PandaID
471
+ if pandaID in ["NULL", "", "None", None]:
472
+ return None
473
+ # only int
474
+ try:
475
+ _ = int(pandaID)
476
+ except Exception:
477
+ tmp_log.debug(f"return None for {pandaID}:non-integer")
478
+ return None
479
+ sql1_0 = "SELECT %s FROM %s "
480
+ sql1_1 = "WHERE PandaID=:PandaID"
481
+ nTry = 3
482
+ for iTry in range(nTry):
483
+ try:
484
+ tables = []
485
+ if fromDefined or fromWaiting:
486
+ tables.append("ATLAS_PANDA.jobsDefined4")
487
+ if fromActive:
488
+ tables.append("ATLAS_PANDA.jobsActive4")
489
+ if fromArchived:
490
+ tables.append("ATLAS_PANDA.jobsArchived4")
491
+ if fromDefined:
492
+ # for jobs which are just reset
493
+ tables.append("ATLAS_PANDA.jobsDefined4")
494
+ # select
495
+ varMap = {}
496
+ varMap[":PandaID"] = pandaID
497
+ for table in tables:
498
+ # start transaction
499
+ self.conn.begin()
500
+ # select
501
+ sql = sql1_0 % (JobSpec.columnNames(), table) + sql1_1
502
+ self.cur.arraysize = 10
503
+ self.cur.execute(sql + comment, varMap)
504
+ res = self.cur.fetchall()
505
+ # commit
506
+ if not self._commit():
507
+ raise RuntimeError("Commit error")
508
+ if len(res) != 0:
509
+ # Job
510
+ job = JobSpec()
511
+ job.pack(res[0])
512
+ # Files
513
+ # start transaction
514
+ self.conn.begin()
515
+ # select
516
+ sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
517
+ sqlFile += "WHERE PandaID=:PandaID"
518
+ self.cur.arraysize = 10000
519
+ self.cur.execute(sqlFile + comment, varMap)
520
+ resFs = self.cur.fetchall()
521
+ # metadata
522
+ resMeta = None
523
+ if table == "ATLAS_PANDA.jobsArchived4" or forAnal:
524
+ # read metadata only for finished/failed production jobs
525
+ sqlMeta = "SELECT metaData FROM ATLAS_PANDA.metaTable WHERE PandaID=:PandaID"
526
+ self.cur.execute(sqlMeta + comment, varMap)
527
+ for (clobMeta,) in self.cur:
528
+ if clobMeta is not None:
529
+ try:
530
+ resMeta = clobMeta.read()
531
+ except AttributeError:
532
+ resMeta = str(clobMeta)
533
+ break
534
+ # job parameters
535
+ job.jobParameters = None
536
+ sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
537
+ varMap = {}
538
+ varMap[":PandaID"] = job.PandaID
539
+ self.cur.execute(sqlJobP + comment, varMap)
540
+ for (clobJobP,) in self.cur:
541
+ if clobJobP is not None:
542
+ try:
543
+ job.jobParameters = clobJobP.read()
544
+ except AttributeError:
545
+ job.jobParameters = str(clobJobP)
546
+ break
547
+ # commit
548
+ if not self._commit():
549
+ raise RuntimeError("Commit error")
550
+ # set files
551
+ for resF in resFs:
552
+ file = FileSpec()
553
+ file.pack(resF)
554
+ job.addFile(file)
555
+ # set metadata
556
+ job.metadata = resMeta
557
+ return job
558
+ tmp_log.debug(f"not found")
559
+ return None
560
+ except Exception:
561
+ # roll back
562
+ self._rollback()
563
+ if iTry + 1 < nTry:
564
+ tmp_log.debug(f"retry : {iTry}")
565
+ time.sleep(random.randint(10, 20))
566
+ continue
567
+ self.dump_error_message(tmp_log)
568
+ # return None for analysis
569
+ if forAnal:
570
+ return None
571
+ # return 'unknown'
572
+ job = JobSpec()
573
+ job.PandaID = pandaID
574
+ job.jobStatus = "unknown"
575
+ return job
576
+
577
+ # get express jobs
578
+ def getExpressJobs(self, dn):
579
+ comment = " /* DBProxy.getExpressJobs */"
580
+ tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
581
+ tmp_log.debug(f"start")
582
+ sqlX = "SELECT specialHandling,COUNT(*) FROM %s "
583
+ sqlX += "WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel1 "
584
+ sqlX += "AND specialHandling IS NOT NULL "
585
+ sqlXJob = "SELECT PandaID,jobStatus,prodSourceLabel,modificationTime,jobDefinitionID,jobsetID,startTime,endTime FROM %s "
586
+ sqlXJob += "WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel1 "
587
+ sqlXJob += "AND specialHandling IS NOT NULL AND specialHandling=:specialHandling "
588
+ sqlQ = sqlX
589
+ sqlQ += "GROUP BY specialHandling "
590
+ sqlQJob = sqlXJob
591
+ sqlA = sqlX
592
+ sqlA += "AND modificationTime>:modificationTime GROUP BY specialHandling "
593
+ sqlAJob = sqlXJob
594
+ sqlAJob += "AND modificationTime>:modificationTime "
595
+ try:
596
+ # get compact DN
597
+ compactDN = CoreUtils.clean_user_id(dn)
598
+ if compactDN in ["", "NULL", None]:
599
+ compactDN = dn
600
+ expressStr = "express"
601
+ activeExpressU = []
602
+ timeUsageU = datetime.timedelta(0)
603
+ executionTimeU = datetime.timedelta(hours=1)
604
+ jobCreditU = 3
605
+ timeCreditU = executionTimeU * jobCreditU
606
+ timeNow = naive_utcnow()
607
+ timeLimit = timeNow - datetime.timedelta(hours=6)
608
+ # loop over tables
609
+ for table in [
610
+ "ATLAS_PANDA.jobsDefined4",
611
+ "ATLAS_PANDA.jobsActive4",
612
+ "ATLAS_PANDA.jobsArchived4",
613
+ ]:
614
+ varMap = {}
615
+ varMap[":prodUserName"] = compactDN
616
+ varMap[":prodSourceLabel1"] = "user"
617
+ if table == "ATLAS_PANDA.jobsArchived4":
618
+ varMap[":modificationTime"] = timeLimit
619
+ sql = sqlA % table
620
+ sqlJob = sqlAJob % table
621
+ else:
622
+ sql = sqlQ % table
623
+ sqlJob = sqlQJob % table
624
+ # start transaction
625
+ self.conn.begin()
626
+ # get the number of jobs for each specialHandling
627
+ self.cur.arraysize = 10
628
+ tmp_log.debug(sql + comment + str(varMap))
629
+ self.cur.execute(sql + comment, varMap)
630
+ res = self.cur.fetchall()
631
+ tmp_log.debug(f"{str(res)}")
632
+ for specialHandling, countJobs in res:
633
+ if specialHandling is None:
634
+ continue
635
+ # look for express jobs
636
+ if expressStr in specialHandling:
637
+ varMap[":specialHandling"] = specialHandling
638
+ self.cur.arraysize = 1000
639
+ self.cur.execute(sqlJob + comment, varMap)
640
+ resJobs = self.cur.fetchall()
641
+ tmp_log.debug(f"{str(resJobs)}")
642
+ for (
643
+ tmp_PandaID,
644
+ tmp_jobStatus,
645
+ tmp_prodSourceLabel,
646
+ tmp_modificationTime,
647
+ tmp_jobDefinitionID,
648
+ tmp_jobsetID,
649
+ tmp_startTime,
650
+ tmp_endTime,
651
+ ) in resJobs:
652
+ # collect active jobs
653
+ if tmp_jobStatus not in [
654
+ "finished",
655
+ "failed",
656
+ "cancelled",
657
+ "closed",
658
+ ]:
659
+ activeExpressU.append((tmp_PandaID, tmp_jobsetID, tmp_jobDefinitionID))
660
+ # get time usage
661
+ if tmp_jobStatus not in ["defined", "activated"]:
662
+ # check only jobs which actually use or used CPU on WN
663
+ if tmp_startTime is not None:
664
+ # running or not
665
+ if tmp_endTime is None:
666
+ # job got started before/after the time limit
667
+ if timeLimit > tmp_startTime:
668
+ timeDelta = timeNow - timeLimit
669
+ else:
670
+ timeDelta = timeNow - tmp_startTime
671
+ else:
672
+ # job got started before/after the time limit
673
+ if timeLimit > tmp_startTime:
674
+ timeDelta = tmp_endTime - timeLimit
675
+ else:
676
+ timeDelta = tmp_endTime - tmp_startTime
677
+ # add
678
+ if timeDelta > datetime.timedelta(0):
679
+ timeUsageU += timeDelta
680
+ # commit
681
+ if not self._commit():
682
+ raise RuntimeError("Commit error")
683
+ # check quota
684
+ rRet = True
685
+ rRetStr = ""
686
+ rQuota = 0
687
+ if len(activeExpressU) >= jobCreditU:
688
+ rRetStr += f"The number of queued runXYZ exceeds the limit = {jobCreditU}. "
689
+ rRet = False
690
+ if timeUsageU >= timeCreditU:
691
+ rRetStr += f"The total execution time for runXYZ exceeds the limit = {timeCreditU.seconds / 60} min. "
692
+ rRet = False
693
+ # calculate available quota
694
+ if rRet:
695
+ tmpQuota = jobCreditU - len(activeExpressU) - timeUsageU.seconds / executionTimeU.seconds
696
+ if tmpQuota < 0:
697
+ rRetStr += "Quota for runXYZ exceeds. "
698
+ rRet = False
699
+ else:
700
+ rQuota = tmpQuota
701
+ # return
702
+ retVal = {
703
+ "status": rRet,
704
+ "quota": rQuota,
705
+ "output": rRetStr,
706
+ "usage": timeUsageU,
707
+ "jobs": activeExpressU,
708
+ }
709
+ tmp_log.debug(f"{str(retVal)}")
710
+ return retVal
711
+ except Exception:
712
+ # roll back
713
+ self._rollback()
714
+ self.dump_error_message(tmp_log)
715
+ return None
716
+
717
+ # get active debug jobs
718
+ def getActiveDebugJobs(self, dn=None, workingGroup=None, prodRole=False):
719
+ comment = " /* DBProxy.getActiveDebugJobs */"
720
+ tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
721
+ tmp_log.debug(f"wg={workingGroup} prodRole={prodRole}")
722
+ varMap = {}
723
+ sqlX = "SELECT PandaID,jobStatus,specialHandling FROM %s "
724
+ sqlX += "WHERE "
725
+ if prodRole:
726
+ pass
727
+ elif workingGroup is not None:
728
+ sqlX += "UPPER(workingGroup) IN (:wg1,:wg2) AND "
729
+ varMap[":wg1"] = f"AP_{workingGroup.upper()}"
730
+ varMap[":wg2"] = f"GP_{workingGroup.upper()}"
731
+ else:
732
+ sqlX += "prodUserName=:prodUserName AND "
733
+ # get compact DN
734
+ compactDN = CoreUtils.clean_user_id(dn)
735
+ if compactDN in ["", "NULL", None]:
736
+ compactDN = dn
737
+ varMap[":prodUserName"] = compactDN
738
+ sqlX += "specialHandling IS NOT NULL "
739
+ try:
740
+ debugStr = "debug"
741
+ activeDebugJobs = []
742
+ # loop over tables
743
+ for table in ["ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsActive4"]:
744
+ sql = sqlX % table
745
+ # start transaction
746
+ self.conn.begin()
747
+ # get jobs with specialHandling
748
+ self.cur.arraysize = 100000
749
+ self.cur.execute(sql + comment, varMap)
750
+ res = self.cur.fetchall()
751
+ # commit
752
+ if not self._commit():
753
+ raise RuntimeError("Commit error")
754
+ # loop over all PandaIDs
755
+ for pandaID, jobStatus, specialHandling in res:
756
+ if specialHandling is None:
757
+ continue
758
+ # only active jobs
759
+ if jobStatus not in [
760
+ "defined",
761
+ "activated",
762
+ "running",
763
+ "sent",
764
+ "starting",
765
+ ]:
766
+ continue
767
+ # look for debug jobs
768
+ if debugStr in specialHandling and pandaID not in activeDebugJobs:
769
+ activeDebugJobs.append(pandaID)
770
+ # return
771
+ activeDebugJobs.sort()
772
+ tmp_log.debug(f"{str(activeDebugJobs)}")
773
+ return activeDebugJobs
774
+ except Exception:
775
+ # roll back
776
+ self._rollback()
777
+ self.dump_error_message(tmp_log)
778
+ return None
779
+
780
+ # set debug mode
781
+ def setDebugMode(self, dn, pandaID, prodManager, modeOn, workingGroup):
782
+ comment = " /* DBProxy.setDebugMode */"
783
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
784
+ tmp_log.debug(f"dn={dn} prod={prodManager} wg={workingGroup} mode={modeOn}")
785
+ sqlX = "SELECT prodUserName,jobStatus,specialHandling,workingGroup FROM %s "
786
+ sqlX += "WHERE PandaID=:PandaID "
787
+ sqlU = "UPDATE %s SET specialHandling=:specialHandling "
788
+ sqlU += "WHERE PandaID=:PandaID "
789
+ try:
790
+ # get compact DN
791
+ compactDN = CoreUtils.clean_user_id(dn)
792
+ if compactDN in ["", "NULL", None]:
793
+ compactDN = dn
794
+ debugStr = "debug"
795
+ retStr = ""
796
+ retCode = False
797
+ # loop over tables
798
+ for table in ["ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsActive4"]:
799
+ varMap = {}
800
+ varMap[":PandaID"] = pandaID
801
+ sql = sqlX % table
802
+ # start transaction
803
+ self.conn.begin()
804
+ # get jobs with specialHandling
805
+ self.cur.arraysize = 10
806
+ self.cur.execute(sql + comment, varMap)
807
+ res = self.cur.fetchone()
808
+ # not found
809
+ if res is None:
810
+ retStr = f"PandaID={pandaID} not found in active DB"
811
+ # commit
812
+ if not self._commit():
813
+ raise RuntimeError("Commit error")
814
+ continue
815
+ prodUserName, jobStatus, specialHandling, wGroup = res
816
+ # not active
817
+ changeableState = [
818
+ "defined",
819
+ "activated",
820
+ "running",
821
+ "sent",
822
+ "starting",
823
+ "assigned",
824
+ ]
825
+ if jobStatus not in changeableState:
826
+ retStr = f"Cannot set debugMode since the job status is {jobStatus} which is not in one of {str(changeableState)}"
827
+ # commit
828
+ if not self._commit():
829
+ raise RuntimeError("Commit error")
830
+ break
831
+ # extract workingGroup
832
+ try:
833
+ wGroup = wGroup.split("_")[-1]
834
+ wGroup = wGroup.lower()
835
+ except Exception:
836
+ pass
837
+ # not owner
838
+ notOwner = False
839
+ if not prodManager:
840
+ if workingGroup is not None:
841
+ if workingGroup.lower() != wGroup:
842
+ retStr = f"Permission denied. Not the production manager for workingGroup={wGroup}"
843
+ notOwner = True
844
+ else:
845
+ if prodUserName != compactDN:
846
+ retStr = "Permission denied. Not the owner or production manager"
847
+ notOwner = True
848
+ if notOwner:
849
+ # commit
850
+ if not self._commit():
851
+ raise RuntimeError("Commit error")
852
+ break
853
+ # set specialHandling
854
+ updateSH = True
855
+ if specialHandling in [None, ""]:
856
+ if modeOn:
857
+ # set debug mode
858
+ specialHandling = debugStr
859
+ else:
860
+ # already disabled debug mode
861
+ updateSH = False
862
+ elif debugStr in specialHandling:
863
+ if modeOn:
864
+ # already in debug mode
865
+ updateSH = False
866
+ else:
867
+ # disable debug mode
868
+ specialHandling = re.sub(debugStr, "", specialHandling)
869
+ specialHandling = re.sub(",,", ",", specialHandling)
870
+ specialHandling = re.sub("^,", "", specialHandling)
871
+ specialHandling = re.sub(",$", "", specialHandling)
872
+ else:
873
+ if modeOn:
874
+ # set debug mode
875
+ specialHandling = debugStr
876
+ else:
877
+ # already disabled debug mode
878
+ updateSH = False
879
+
880
+ # no update
881
+ if not updateSH:
882
+ retStr = "Already set accordingly"
883
+ # commit
884
+ if not self._commit():
885
+ raise RuntimeError("Commit error")
886
+ break
887
+ # update
888
+ varMap = {}
889
+ varMap[":PandaID"] = pandaID
890
+ varMap[":specialHandling"] = specialHandling
891
+ self.cur.execute((sqlU + comment) % table, varMap)
892
+ retD = self.cur.rowcount
893
+ # commit
894
+ if not self._commit():
895
+ raise RuntimeError("Commit error")
896
+ if retD == 0:
897
+ retStr = "Failed to update DB"
898
+ else:
899
+ retStr = "Succeeded"
900
+ break
901
+ # return
902
+ tmp_log.debug(f"{retStr}")
903
+ return retStr
904
+ except Exception:
905
+ # roll back
906
+ self._rollback()
907
+ self.dump_error_message(tmp_log)
908
+ return None
909
+
910
+ # lock jobs for reassign
911
+ def lockJobsForReassign(
912
+ self,
913
+ tableName,
914
+ timeLimit,
915
+ statList,
916
+ labels,
917
+ processTypes,
918
+ sites,
919
+ clouds,
920
+ useJEDI=False,
921
+ onlyReassignable=False,
922
+ useStateChangeTime=False,
923
+ getEventService=False,
924
+ ):
925
+ comment = " /* DBProxy.lockJobsForReassign */"
926
+ tmp_log = self.create_tagged_logger(comment)
927
+ tmp_log.debug(f"{tableName} {timeLimit} {statList} {labels} {processTypes} {sites} {clouds} {useJEDI}")
928
+ try:
929
+ # make sql
930
+ if not useJEDI:
931
+ sql = f"SELECT PandaID FROM {tableName} "
932
+ elif getEventService:
933
+ sql = f"SELECT PandaID,lockedby,eventService,attemptNr,computingSite FROM {tableName} "
934
+ else:
935
+ sql = f"SELECT PandaID,lockedby FROM {tableName} "
936
+ if not useStateChangeTime:
937
+ sql += "WHERE modificationTime<:modificationTime "
938
+ else:
939
+ sql += "WHERE stateChangeTime<:modificationTime "
940
+ varMap = {}
941
+ varMap[":modificationTime"] = timeLimit
942
+ if statList != []:
943
+ stat_var_names_str, stat_var_map = get_sql_IN_bind_variables(statList, prefix=":stat")
944
+ sql += f"AND jobStatus IN ({stat_var_names_str}) "
945
+ varMap.update(stat_var_map)
946
+ if labels != []:
947
+ label_var_names_str, label_var_map = get_sql_IN_bind_variables(labels, prefix=":label")
948
+ sql += f"AND prodSourceLabel IN ({label_var_names_str}) "
949
+ varMap.update(label_var_map)
950
+ if processTypes != []:
951
+ ptype_var_names_str, ptype_var_map = get_sql_IN_bind_variables(processTypes, prefix=":processType")
952
+ sql += f"AND processingType IN ({ptype_var_names_str}) "
953
+ varMap.update(ptype_var_map)
954
+ if sites != []:
955
+ site_var_names_str, site_var_map = get_sql_IN_bind_variables(sites, prefix=":site")
956
+ sql += f"AND computingSite IN ({site_var_names_str}) "
957
+ varMap.update(site_var_map)
958
+ if clouds != []:
959
+ cloud_var_names_str, cloud_var_map = get_sql_IN_bind_variables(clouds, prefix=":cloud")
960
+ sql += f"AND cloud IN ({cloud_var_names_str}) "
961
+ varMap.update(cloud_var_map)
962
+ if onlyReassignable:
963
+ sql += "AND (relocationFlag IS NULL OR relocationFlag<>:relocationFlag) "
964
+ varMap[":relocationFlag"] = 2
965
+ # sql for lock
966
+ if not useStateChangeTime:
967
+ sqlLock = f"UPDATE {tableName} SET modificationTime=CURRENT_DATE WHERE PandaID=:PandaID"
968
+ else:
969
+ sqlLock = f"UPDATE {tableName} SET stateChangeTime=CURRENT_DATE WHERE PandaID=:PandaID"
970
+ # start transaction
971
+ self.conn.begin()
972
+ # select
973
+ self.cur.arraysize = 1000000
974
+ tmp_log.debug(sql + comment + str(varMap))
975
+ self.cur.execute(sql + comment, varMap)
976
+ resList = self.cur.fetchall()
977
+ tmp_log.debug(f"found {len(resList)}")
978
+ retList = []
979
+ # lock
980
+ for tmpItem in resList:
981
+ tmpID = tmpItem[0]
982
+ varLock = {":PandaID": tmpID}
983
+ self.cur.execute(sqlLock + comment, varLock)
984
+ retList.append(tmpItem)
985
+ # commit
986
+ if not self._commit():
987
+ raise RuntimeError("Commit error")
988
+ # sort
989
+ retList.sort()
990
+ tmp_log.debug(f"return {len(retList)}")
991
+ return True, retList
992
+ except Exception:
993
+ # roll back
994
+ self._rollback()
995
+ self.dump_error_message(tmp_log)
996
+ # return empty
997
+ return False, []
998
+
999
+ # lock jobs for finisher
1000
+ def lockJobsForFinisher(self, timeNow, rownum, highPrio):
1001
+ comment = " /* DBProxy.lockJobsForFinisher */"
1002
+ tmp_log = self.create_tagged_logger(comment)
1003
+ tmp_log.debug(f"{timeNow} {rownum} {highPrio}")
1004
+ try:
1005
+ varMap = {}
1006
+ varMap[":jobStatus"] = "transferring"
1007
+ varMap[":currentPriority"] = 800
1008
+ varMap[":pLabel1"] = "managed"
1009
+ varMap[":pLabel2"] = "test"
1010
+ varMap[":esJumbo"] = EventServiceUtils.jumboJobFlagNumber
1011
+ # make sql
1012
+ sql = "SELECT PandaID FROM ATLAS_PANDA.jobsActive4 "
1013
+ sql += "WHERE jobStatus=:jobStatus AND modificationTime<:modificationTime AND prodSourceLabel IN (:pLabel1,:pLabel2) "
1014
+ sql += "AND (eventService IS NULL OR eventService<>:esJumbo) "
1015
+ if highPrio:
1016
+ varMap[":modificationTime"] = timeNow - datetime.timedelta(hours=1)
1017
+ sql += f"AND currentPriority>=:currentPriority AND rownum<={rownum} "
1018
+ else:
1019
+ sql += f"AND currentPriority<:currentPriority AND rownum<={rownum} "
1020
+ varMap[":modificationTime"] = timeNow - datetime.timedelta(hours=2)
1021
+ sql += "FOR UPDATE "
1022
+ # sql for lock
1023
+ sqlLock = "UPDATE ATLAS_PANDA.jobsActive4 SET modificationTime=CURRENT_DATE WHERE PandaID=:PandaID"
1024
+ # start transaction
1025
+ self.conn.begin()
1026
+ # select
1027
+ self.cur.arraysize = 1000
1028
+ self.cur.execute(sql + comment, varMap)
1029
+ resList = self.cur.fetchall()
1030
+ retList = []
1031
+ # lock
1032
+ for (tmpID,) in resList:
1033
+ varLock = {":PandaID": tmpID}
1034
+ self.cur.execute(sqlLock + comment, varLock)
1035
+ retList.append(tmpID)
1036
+ # commit
1037
+ if not self._commit():
1038
+ raise RuntimeError("Commit error")
1039
+ # sort
1040
+ retList.sort()
1041
+ tmp_log.debug(f"{len(retList)}")
1042
+ return True, retList
1043
+ except Exception:
1044
+ # roll back
1045
+ self._rollback()
1046
+ self.dump_error_message(tmp_log)
1047
+ # return empty
1048
+ return False, []
1049
+
1050
+ # lock jobs for activator
1051
+ def lockJobsForActivator(self, timeLimit, rownum, prio):
1052
+ comment = " /* DBProxy.lockJobsForActivator */"
1053
+ tmp_log = self.create_tagged_logger(comment)
1054
+ tmp_log.debug("start")
1055
+ try:
1056
+ varMap = {}
1057
+ varMap[":jobStatus"] = "assigned"
1058
+ if prio > 0:
1059
+ varMap[":currentPriority"] = prio
1060
+ varMap[":timeLimit"] = timeLimit
1061
+ # make sql
1062
+ sql = "SELECT PandaID FROM ATLAS_PANDA.jobsDefined4 "
1063
+ sql += "WHERE jobStatus=:jobStatus AND (prodDBUpdateTime IS NULL OR prodDBUpdateTime<:timeLimit) "
1064
+ if prio > 0:
1065
+ sql += "AND currentPriority>=:currentPriority "
1066
+ sql += f"AND rownum<={rownum} "
1067
+ sql += "FOR UPDATE "
1068
+ # sql for lock
1069
+ sqlLock = "UPDATE ATLAS_PANDA.jobsDefined4 SET prodDBUpdateTime=CURRENT_DATE WHERE PandaID=:PandaID"
1070
+ # start transaction
1071
+ self.conn.begin()
1072
+ # select
1073
+ self.cur.arraysize = 1000
1074
+ self.cur.execute(sql + comment, varMap)
1075
+ resList = self.cur.fetchall()
1076
+ retList = []
1077
+ # lock
1078
+ for (tmpID,) in resList:
1079
+ varLock = {":PandaID": tmpID}
1080
+ self.cur.execute(sqlLock + comment, varLock)
1081
+ retList.append(tmpID)
1082
+ # commit
1083
+ if not self._commit():
1084
+ raise RuntimeError("Commit error")
1085
+ # sort
1086
+ retList.sort()
1087
+ tmp_log.debug(f"locked {len(retList)} jobs")
1088
+ return True, retList
1089
+ except Exception:
1090
+ # roll back
1091
+ self._rollback()
1092
+ # error
1093
+ self.dump_error_message(tmp_log)
1094
+ # return empty
1095
+ return False, []
1096
+
1097
+ # add metadata
1098
+ def addMetadata(self, pandaID, metadata, newStatus):
1099
+ comment = " /* DBProxy.addMetaData */"
1100
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
1101
+ tmp_log.debug(f"start")
1102
+ # discard metadata for failed jobs
1103
+ if newStatus == "failed":
1104
+ tmp_log.debug("skip")
1105
+ return True
1106
+ sqlJ = "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
1107
+ sqlJ += "UNION "
1108
+ sqlJ += "SELECT jobStatus FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
1109
+ sql0 = "SELECT PandaID FROM ATLAS_PANDA.metaTable WHERE PandaID=:PandaID"
1110
+ sql1 = "INSERT INTO ATLAS_PANDA.metaTable (PandaID,metaData) VALUES (:PandaID,:metaData)"
1111
+ nTry = 1
1112
+ regStart = naive_utcnow()
1113
+ for iTry in range(nTry):
1114
+ try:
1115
+ # autocommit on
1116
+ self.conn.begin()
1117
+ self.cur.arraysize = 10
1118
+ # check job status
1119
+ varMap = {}
1120
+ varMap[":PandaID"] = pandaID
1121
+ self.cur.execute(sqlJ + comment, varMap)
1122
+ resJ = self.cur.fetchone()
1123
+ if resJ is not None:
1124
+ (jobStatus,) = resJ
1125
+ else:
1126
+ jobStatus = "unknown"
1127
+ if jobStatus in ["unknown"]:
1128
+ tmp_log.debug(f"skip jobStatus={jobStatus}")
1129
+ if not self._commit():
1130
+ raise RuntimeError("Commit error")
1131
+ return False
1132
+ # skip if in final state
1133
+ if jobStatus in ["cancelled", "closed", "finished", "failed"]:
1134
+ tmp_log.debug(f"skip jobStatus={jobStatus}")
1135
+ if not self._commit():
1136
+ raise RuntimeError("Commit error")
1137
+ # return True so that subsequent procedure can keep going
1138
+ return True
1139
+ # select
1140
+ varMap = {}
1141
+ varMap[":PandaID"] = pandaID
1142
+ self.cur.arraysize = 10
1143
+ self.cur.execute(sql0 + comment, varMap)
1144
+ res = self.cur.fetchone()
1145
+ # already exist
1146
+ if res is not None:
1147
+ tmp_log.debug(f"skip duplicated during jobStatus={jobStatus}")
1148
+ if not self._commit():
1149
+ raise RuntimeError("Commit error")
1150
+ return True
1151
+ # truncate
1152
+ if metadata is not None:
1153
+ origSize = len(metadata)
1154
+ else:
1155
+ origSize = 0
1156
+ maxSize = 1024 * 1024
1157
+ if newStatus in ["failed"] and origSize > maxSize:
1158
+ metadata = metadata[:maxSize]
1159
+ # insert
1160
+ varMap = {}
1161
+ varMap[":PandaID"] = pandaID
1162
+ varMap[":metaData"] = metadata
1163
+ self.cur.execute(sql1 + comment, varMap)
1164
+ # commit
1165
+ if not self._commit():
1166
+ raise RuntimeError("Commit error")
1167
+ regTime = naive_utcnow() - regStart
1168
+ msgStr = f"done in jobStatus={jobStatus}->{newStatus} took {regTime.seconds} sec"
1169
+ if metadata is not None:
1170
+ msgStr += f" for {len(metadata)} (orig {origSize}) bytes"
1171
+ tmp_log.debug(msgStr)
1172
+ return True
1173
+ except Exception:
1174
+ # roll back
1175
+ self._rollback()
1176
+ if iTry + 1 < nTry:
1177
+ tmp_log.debug(f"retry : {iTry}")
1178
+ time.sleep(random.randint(10, 20))
1179
+ continue
1180
+ self.dump_error_message(tmp_log)
1181
+ return False
1182
+
1183
+ # add stdout
1184
+ def addStdOut(self, pandaID, stdOut):
1185
+ comment = " /* DBProxy.addStdOut */"
1186
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
1187
+ tmp_log.debug(f"start")
1188
+ sqlJ = "SELECT PandaID FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID FOR UPDATE "
1189
+ sqlC = "SELECT PandaID FROM ATLAS_PANDA.jobsDebug WHERE PandaID=:PandaID "
1190
+ sqlI = "INSERT INTO ATLAS_PANDA.jobsDebug (PandaID,stdOut) VALUES (:PandaID,:stdOut) "
1191
+ sqlU = "UPDATE ATLAS_PANDA.jobsDebug SET stdOut=:stdOut WHERE PandaID=:PandaID "
1192
+ try:
1193
+ # autocommit on
1194
+ self.conn.begin()
1195
+ # select
1196
+ varMap = {}
1197
+ varMap[":PandaID"] = pandaID
1198
+ self.cur.arraysize = 10
1199
+ # check job table
1200
+ self.cur.execute(sqlJ + comment, varMap)
1201
+ res = self.cur.fetchone()
1202
+ if res is None:
1203
+ tmp_log.debug(f"addStdOut : {pandaID} non active")
1204
+ else:
1205
+ # check debug table
1206
+ self.cur.execute(sqlC + comment, varMap)
1207
+ res = self.cur.fetchone()
1208
+ # already exist
1209
+ if res is not None:
1210
+ # update
1211
+ sql = sqlU
1212
+ else:
1213
+ # insert
1214
+ sql = sqlI
1215
+ # write stdout
1216
+ varMap = {}
1217
+ varMap[":PandaID"] = pandaID
1218
+ varMap[":stdOut"] = stdOut
1219
+ self.cur.execute(sql + comment, varMap)
1220
+ # commit
1221
+ if not self._commit():
1222
+ raise RuntimeError("Commit error")
1223
+ return True
1224
+ except Exception:
1225
+ # roll back
1226
+ self._rollback()
1227
+ self.dump_error_message(tmp_log)
1228
+ return False
1229
+
1230
+ # get job statistics
1231
+ def getJobStatistics(self):
1232
+ comment = " /* DBProxy.getJobStatistics */"
1233
+ tmp_log = self.create_tagged_logger(comment)
1234
+ tmp_log.debug("start")
1235
+
1236
+ # tables to query
1237
+ jobs_active_4_table = f"{panda_config.schemaPANDA}.jobsActive4"
1238
+ jobs_defined_4_table = f"{panda_config.schemaPANDA}.jobsDefined4"
1239
+ tables = [jobs_active_4_table, jobs_defined_4_table]
1240
+
1241
+ # states that are necessary and irrelevant states
1242
+ included_states = ["assigned", "activated", "running"]
1243
+ excluded_states = ["merging"]
1244
+
1245
+ # sql template for jobs table
1246
+ sql_template = f"SELECT computingSite, jobStatus, COUNT(*) FROM {{table_name}} GROUP BY computingSite, jobStatus"
1247
+
1248
+ # sql template for statistics table (materialized view)
1249
+ sql_mv_template = sql_template.replace("COUNT(*)", "SUM(num_of_jobs)")
1250
+ sql_mv_template = sql_mv_template.replace("SELECT ", "SELECT /*+ RESULT_CACHE */ ")
1251
+ ret = {}
1252
+ max_retries = 3
1253
+
1254
+ for retry in range(max_retries):
1255
+ try:
1256
+ for table in tables:
1257
+ # start transaction
1258
+ self.conn.begin()
1259
+ var_map = {}
1260
+ self.cur.arraysize = 10000
1261
+
1262
+ # for active jobs we will query the summarized materialized view
1263
+ if table == jobs_active_4_table:
1264
+ table_name = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
1265
+ sql = (sql_mv_template + comment).format(table_name=table_name)
1266
+ # for defined jobs we will query the actual table
1267
+ else:
1268
+ table_name = table
1269
+ sql = (sql_template + comment).format(table_name=table_name)
1270
+ tmp_log.debug(f"Will execute: {sql} {str(var_map)}")
1271
+
1272
+ self.cur.execute(sql, var_map)
1273
+ res = self.cur.fetchall()
1274
+ if not self._commit():
1275
+ raise RuntimeError("Commit error")
1276
+
1277
+ # create map
1278
+ for computing_site, job_status, n_jobs in res:
1279
+ if job_status in excluded_states: # ignore some job status since they break APF
1280
+ continue
1281
+
1282
+ ret.setdefault(computing_site, {}).setdefault(job_status, 0)
1283
+ ret[computing_site][job_status] += n_jobs
1284
+
1285
+ # fill in missing states with 0
1286
+ for site in ret:
1287
+ for state in included_states:
1288
+ ret[site].setdefault(state, 0)
1289
+
1290
+ tmp_log.debug(f"done")
1291
+ return ret
1292
+
1293
+ except Exception:
1294
+ self._rollback()
1295
+
1296
+ if retry + 1 < max_retries: # wait 2 seconds before the next retry
1297
+ tmp_log.debug(f"retry: {retry}")
1298
+ time.sleep(2)
1299
+ else: # reached max retries - leave
1300
+ self.dump_error_message(tmp_log)
1301
+ return {}
1302
+
1303
+ # get detailed job statistics with resource_type and prodsourcelabel
1304
+ def getDetailedJobStatistics(self):
1305
+ comment = " /* DBProxy.getDetailedJobStatistics */"
1306
+ tmp_log = self.create_tagged_logger(comment)
1307
+ tmp_log.debug("start")
1308
+
1309
+ # tables to query
1310
+ jobs_active_4_table = f"{panda_config.schemaPANDA}.jobsActive4"
1311
+ jobs_defined_4_table = f"{panda_config.schemaPANDA}.jobsDefined4"
1312
+ tables = [jobs_active_4_table, jobs_defined_4_table]
1313
+
1314
+ # states that are necessary and irrelevant states
1315
+ included_states = ["assigned", "activated", "running"]
1316
+ excluded_states = ["merging"]
1317
+
1318
+ # sql template for jobs table
1319
+ sql_template = f"SELECT computingSite, resource_type, prodSourceLabel, jobStatus, COUNT(*) FROM {{table_name}} GROUP BY computingSite, resource_type, prodSourceLabel, jobStatus"
1320
+ # sql template for statistics table (materialized view)
1321
+ sql_mv_template = sql_template.replace("COUNT(*)", "SUM(num_of_jobs)")
1322
+ sql_mv_template = sql_mv_template.replace("SELECT ", "SELECT /*+ RESULT_CACHE */ ")
1323
+ ret = {}
1324
+ max_retries = 3
1325
+
1326
+ for retry in range(max_retries):
1327
+ try:
1328
+ for table in tables:
1329
+ # start transaction
1330
+ self.conn.begin()
1331
+ var_map = {}
1332
+ self.cur.arraysize = 10000
1333
+
1334
+ # for active jobs we will query the summarized materialized view
1335
+ if table == jobs_active_4_table:
1336
+ table_name = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
1337
+ sql = (sql_mv_template + comment).format(table_name=table_name)
1338
+ # for defined jobs we will query the actual table
1339
+ else:
1340
+ table_name = table
1341
+ sql = (sql_template + comment).format(table_name=table_name)
1342
+ tmp_log.debug(f"Will execute: {sql} {str(var_map)}")
1343
+
1344
+ self.cur.execute(sql, var_map)
1345
+ res = self.cur.fetchall()
1346
+ if not self._commit():
1347
+ raise RuntimeError("Commit error")
1348
+
1349
+ # create map
1350
+ for computing_site, resource_type, prod_source_label, job_status, n_jobs in res:
1351
+ if job_status in excluded_states: # ignore some job status since they break APF
1352
+ continue
1353
+
1354
+ ret.setdefault(computing_site, {}).setdefault(resource_type, {}).setdefault(prod_source_label, {}).setdefault(job_status, 0)
1355
+ ret[computing_site][resource_type][prod_source_label][job_status] += n_jobs
1356
+
1357
+ # fill in missing states with 0
1358
+ for site in ret:
1359
+ for resource_type in ret[site]:
1360
+ for prod_source_label in ret[site][resource_type]:
1361
+ for state in included_states:
1362
+ ret[site][resource_type][prod_source_label].setdefault(state, 0)
1363
+
1364
+ tmp_log.debug(f"done")
1365
+ return ret
1366
+
1367
+ except Exception:
1368
+ self._rollback()
1369
+
1370
+ if retry + 1 < max_retries: # wait 2 seconds before the next retry
1371
+ tmp_log.debug(f"retry: {retry}")
1372
+ time.sleep(2)
1373
+ else: # reached max retries - leave
1374
+ self.dump_error_message(tmp_log)
1375
+ return {}
1376
+
1377
+ # get job statistics per site and resource type (SCORE, MCORE, ...)
1378
+ def getJobStatisticsPerSiteResource(self, time_window):
1379
+ comment = " /* DBProxy.getJobStatisticsPerSiteResource */"
1380
+ tmp_log = self.create_tagged_logger(comment)
1381
+ tmp_log.debug("start")
1382
+
1383
+ tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsArchived4"]
1384
+
1385
+ # basic SQL for active and defined jobs
1386
+ sql = "SELECT computingSite, jobStatus, resource_type, COUNT(*) FROM %s GROUP BY computingSite, jobStatus, resource_type "
1387
+
1388
+ # SQL for archived table including time window
1389
+ sql_archive = (
1390
+ "SELECT /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ computingSite, jobStatus, resource_type, COUNT(*) "
1391
+ "FROM ATLAS_PANDA.jobsArchived4 tab WHERE modificationTime > :modificationTime "
1392
+ "GROUP BY computingSite, jobStatus, resource_type "
1393
+ )
1394
+
1395
+ # sql for materialized view
1396
+ sql_mv = re.sub("COUNT\(\*\)", "SUM(njobs)", sql)
1397
+ sql_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_mv)
1398
+
1399
+ ret = dict()
1400
+ try:
1401
+ # calculate the time floor based on the window specified by the caller
1402
+ if time_window is None:
1403
+ time_floor = naive_utcnow() - datetime.timedelta(hours=12)
1404
+ else:
1405
+ time_floor = naive_utcnow() - datetime.timedelta(minutes=int(time_window))
1406
+
1407
+ for table in tables:
1408
+ # start transaction
1409
+ self.conn.begin()
1410
+ self.cur.arraysize = 10000
1411
+ # select
1412
+
1413
+ var_map = {}
1414
+ if table == "ATLAS_PANDA.jobsArchived4":
1415
+ var_map[":modificationTime"] = time_floor
1416
+ sql_tmp = sql_archive + comment
1417
+ elif table == "ATLAS_PANDA.jobsActive4":
1418
+ sql_tmp = (sql_mv + comment) % "ATLAS_PANDA.JOBS_SHARE_STATS"
1419
+ else:
1420
+ sql_tmp = (sql + comment) % table
1421
+
1422
+ self.cur.execute(sql_tmp, var_map)
1423
+ res = self.cur.fetchall()
1424
+
1425
+ # commit
1426
+ if not self._commit():
1427
+ raise RuntimeError("Commit error")
1428
+
1429
+ # create map
1430
+ for computing_site, job_status, resource_type, n_jobs in res:
1431
+ ret.setdefault(computing_site, dict()).setdefault(resource_type, dict()).setdefault(job_status, 0)
1432
+ ret[computing_site][resource_type][job_status] += n_jobs
1433
+
1434
+ # fill in missing states with 0
1435
+ included_states = ["assigned", "activated", "running", "finished", "failed"]
1436
+ for computing_site in ret:
1437
+ for resource_type in ret[computing_site]:
1438
+ for job_status in included_states:
1439
+ ret[computing_site][resource_type].setdefault(job_status, 0)
1440
+
1441
+ tmp_log.debug("done")
1442
+ return ret
1443
+ except Exception:
1444
+ # roll back
1445
+ self._rollback()
1446
+ # error
1447
+ self.dump_error_message(tmp_log)
1448
+ return dict()
1449
+
1450
+ # get the number of job for a user
1451
+ def getNumberJobsUser(self, dn, workingGroup=None):
1452
+ comment = " /* DBProxy.getNumberJobsUser */"
1453
+ tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
1454
+ tmp_log.debug(f"workingGroup={workingGroup})")
1455
+
1456
+ # get compact DN
1457
+ compact_dn = CoreUtils.clean_user_id(dn)
1458
+ if compact_dn in ["", "NULL", None]:
1459
+ compact_dn = dn
1460
+
1461
+ if workingGroup is not None:
1462
+ sql_count_jobs = "SELECT COUNT(*) FROM %s WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel AND workingGroup=:workingGroup "
1463
+ else:
1464
+ sql_count_jobs = "SELECT COUNT(*) FROM %s WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel AND workingGroup IS NULL "
1465
+ sql_count_jobs += "AND NOT jobStatus IN (:failed,:merging) "
1466
+
1467
+ n_try = 1
1468
+ n_jobs = 0
1469
+ for i_try in range(n_try):
1470
+ try:
1471
+ for table in ("ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsDefined4"):
1472
+ # start transaction
1473
+ self.conn.begin()
1474
+ # select
1475
+ var_map = {":prodUserName": compact_dn, ":prodSourceLabel": "user", ":failed": "failed", ":merging": "merging"}
1476
+ if workingGroup is not None:
1477
+ var_map[":workingGroup"] = workingGroup
1478
+ self.cur.arraysize = 10
1479
+ self.cur.execute((sql_count_jobs + comment) % table, var_map)
1480
+ rows = self.cur.fetchall()
1481
+ # commit
1482
+ if not self._commit():
1483
+ raise RuntimeError("Commit error")
1484
+ if len(rows) != 0:
1485
+ n_jobs += rows[0][0]
1486
+ # return
1487
+ tmp_log.debug(f"{n_jobs}")
1488
+ return n_jobs
1489
+ except Exception:
1490
+ # roll back
1491
+ self._rollback()
1492
+ if i_try + 1 < n_try:
1493
+ time.sleep(2)
1494
+ continue
1495
+ self.dump_error_message(tmp_log)
1496
+ return 0
1497
+
1498
+ # get job statistics for ExtIF. Source type is analysis or production
1499
+ def getJobStatisticsForExtIF(self, source_type=None):
1500
+ comment = " /* DBProxy.getJobStatisticsForExtIF */"
1501
+ tmp_log = self.create_tagged_logger(comment)
1502
+ tmp_log.debug(f"start source_type={source_type}")
1503
+
1504
+ time_floor = naive_utcnow() - datetime.timedelta(hours=12)
1505
+
1506
+ # analysis
1507
+ if source_type == "analysis":
1508
+ sql = "SELECT jobStatus, COUNT(*), cloud FROM %s WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) GROUP BY jobStatus, cloud"
1509
+
1510
+ sql_archived = (
1511
+ "SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
1512
+ "jobStatus, COUNT(*), tabS.data.cloud "
1513
+ "FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
1514
+ "WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) "
1515
+ "AND tab.computingSite = tabS.panda_queue "
1516
+ "AND modificationTime>:modificationTime GROUP BY tab.jobStatus,tabS.data.cloud"
1517
+ )
1518
+
1519
+ # production
1520
+ else:
1521
+ prod_source_label_string = ":prodSourceLabel1, " + ", ".join(f":prodSourceLabel_{label}" for label in JobUtils.list_ptest_prod_sources)
1522
+ sql = (
1523
+ "SELECT /* use_json_type */ tab.jobStatus, COUNT(*), tabS.data.cloud "
1524
+ "FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
1525
+ f"WHERE prodSourceLabel IN ({prod_source_label_string}) "
1526
+ "AND tab.computingSite = tabS.panda_queue "
1527
+ "GROUP BY tab.jobStatus, tabS.data.cloud"
1528
+ )
1529
+
1530
+ sql_archived = (
1531
+ "SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
1532
+ "jobStatus, COUNT(*), tabS.data.cloud "
1533
+ "FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
1534
+ f"WHERE prodSourceLabel IN ({prod_source_label_string}) "
1535
+ "AND tab.computingSite = tabS.panda_queue "
1536
+ "AND modificationTime>:modificationTime GROUP BY tab.jobStatus,tabS.data.cloud"
1537
+ )
1538
+
1539
+ # sql for materialized view
1540
+ sql_active_mv = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sql)
1541
+ sql_active_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_active_mv)
1542
+
1543
+ ret = {}
1544
+
1545
+ tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.jobsDefined4"]
1546
+ try:
1547
+ for table in tables:
1548
+ # start transaction
1549
+ self.conn.begin()
1550
+
1551
+ # select
1552
+ var_map = {}
1553
+ if source_type == "analysis":
1554
+ var_map[":prodSourceLabel1"] = "user"
1555
+ var_map[":prodSourceLabel2"] = "panda"
1556
+ else:
1557
+ var_map[":prodSourceLabel1"] = "managed"
1558
+ for tmp_label in JobUtils.list_ptest_prod_sources:
1559
+ tmp_key = f":prodSourceLabel_{tmp_label}"
1560
+ var_map[tmp_key] = tmp_label
1561
+
1562
+ if table != "ATLAS_PANDA.jobsArchived4":
1563
+ self.cur.arraysize = 10000
1564
+ # active uses materialized view
1565
+ if table == "ATLAS_PANDA.jobsActive4":
1566
+ self.cur.execute(
1567
+ (sql_active_mv + comment) % "ATLAS_PANDA.MV_JOBSACTIVE4_STATS",
1568
+ var_map,
1569
+ )
1570
+ # defined and waiting tables
1571
+ else:
1572
+ self.cur.execute((sql + comment) % table, var_map)
1573
+ else:
1574
+ var_map[":modificationTime"] = time_floor
1575
+ self.cur.arraysize = 10000
1576
+ self.cur.execute((sql_archived + comment) % table, var_map)
1577
+ res = self.cur.fetchall()
1578
+
1579
+ # commit
1580
+ if not self._commit():
1581
+ raise RuntimeError("Commit error")
1582
+
1583
+ # create map
1584
+ for job_status, count, cloud in res:
1585
+ ret.setdefault(cloud, dict())
1586
+ ret[cloud].setdefault(job_status, 0)
1587
+ ret[cloud][job_status] += count
1588
+
1589
+ # return
1590
+ tmp_log.debug(f"done")
1591
+ return ret
1592
+ except Exception:
1593
+ # roll back
1594
+ self._rollback()
1595
+ # error
1596
+ self.dump_error_message(tmp_log)
1597
+ return {}
1598
+
1599
+ # get statistics for production jobs per processingType
1600
+ def getJobStatisticsPerProcessingType(self):
1601
+ comment = " /* DBProxy.getJobStatisticsPerProcessingType */"
1602
+ tmp_log = self.create_tagged_logger(comment)
1603
+ tmp_log.debug("start")
1604
+
1605
+ time_floor = naive_utcnow() - datetime.timedelta(hours=12)
1606
+
1607
+ # Job tables we are going to query
1608
+ tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.jobsDefined4"]
1609
+
1610
+ # Define the prodSourceLabel list
1611
+ prod_source_labels = ", ".join([f":prodSourceLabel_{label}" for label in JobUtils.list_ptest_prod_sources])
1612
+
1613
+ # Construct the SQL query for active jobs
1614
+ sql_active = (
1615
+ f"SELECT /* use_json_type */ jobStatus, COUNT(*), tabS.data.cloud, processingType "
1616
+ f"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
1617
+ f"WHERE prodSourceLabel IN (:prodSourceLabelManaged, {prod_source_labels}) "
1618
+ f"AND computingSite=tabS.panda_queue "
1619
+ f"GROUP BY jobStatus, tabS.data.cloud, processingType"
1620
+ )
1621
+
1622
+ # Construct the SQL query for archived jobs
1623
+ sql_archived = (
1624
+ f"SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
1625
+ f"jobStatus, COUNT(*), tabS.data.cloud, processingType "
1626
+ f"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
1627
+ f"WHERE prodSourceLabel IN (:prodSourceLabelManaged, {prod_source_labels}) "
1628
+ f"AND modificationTime > :modificationTime "
1629
+ f"AND computingSite = tabS.panda_queue "
1630
+ f"GROUP BY jobStatus, tabS.data.cloud, processingType"
1631
+ )
1632
+
1633
+ # sql for materialized view
1634
+ sql_active_mv = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sql_active)
1635
+ sql_active_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_active_mv)
1636
+
1637
+ ret = {}
1638
+ try:
1639
+ for table in tables:
1640
+ # start transaction
1641
+ self.conn.begin()
1642
+
1643
+ # select
1644
+ self.cur.arraysize = 10000
1645
+ var_map = {":prodSourceLabelManaged": "managed"}
1646
+ var_map.update({f":prodSourceLabel_{label}": label for label in JobUtils.list_ptest_prod_sources})
1647
+
1648
+ if table == "ATLAS_PANDA.jobsArchived4":
1649
+ var_map[":modificationTime"] = time_floor
1650
+ self.cur.execute((sql_archived + comment) % table, var_map)
1651
+
1652
+ elif table == "ATLAS_PANDA.jobsActive4":
1653
+ self.cur.execute(
1654
+ (sql_active_mv + comment) % "ATLAS_PANDA.MV_JOBSACTIVE4_STATS",
1655
+ var_map,
1656
+ )
1657
+ else:
1658
+ # use real table since coreCount is unavailable in MatView
1659
+ self.cur.execute((sql_active + comment) % table, var_map)
1660
+
1661
+ results = self.cur.fetchall()
1662
+
1663
+ if not self._commit():
1664
+ raise RuntimeError("Commit error")
1665
+
1666
+ # create map
1667
+ for row in results:
1668
+ job_status, count, cloud, processing_type = row
1669
+ ret.setdefault(cloud, {}).setdefault(processing_type, {}).setdefault(job_status, 0)
1670
+ ret[cloud][processing_type][job_status] += count
1671
+
1672
+ tmp_log.debug(f"done")
1673
+ return ret
1674
+ except Exception:
1675
+ # roll back
1676
+ self._rollback()
1677
+ # error
1678
+ self.dump_error_message(tmp_log)
1679
+ return {}
1680
+
1681
+ # peek at job
1682
+ def peekJobLog(self, pandaID, days=None):
1683
+ comment = " /* DBProxy.peekJobLog */"
1684
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
1685
+ tmp_log.debug(f"days={days}")
1686
+
1687
+ # return None for NULL PandaID
1688
+ if pandaID in ["NULL", "", "None", None]:
1689
+ return None
1690
+
1691
+ sql_select = "SELECT %s FROM %s "
1692
+ sql_where = "WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-:days) "
1693
+
1694
+ # select
1695
+ var_map = {":PandaID": pandaID}
1696
+ if days is None:
1697
+ days = 30
1698
+ var_map[":days"] = days
1699
+ n_try = 1
1700
+
1701
+ for i_try in range(n_try):
1702
+ try:
1703
+ # get list of archived tables
1704
+ tables = [f"{panda_config.schemaPANDAARCH}.jobsArchived"]
1705
+ # select
1706
+ for table in tables:
1707
+ # start transaction
1708
+ self.conn.begin()
1709
+ # select
1710
+ sql = sql_select % (JobSpec.columnNames(), table) + sql_where
1711
+ self.cur.arraysize = 10
1712
+ self.cur.execute(sql + comment, var_map)
1713
+ rows = self.cur.fetchall()
1714
+ # commit
1715
+ if not self._commit():
1716
+ raise RuntimeError("Commit error")
1717
+ if len(rows) != 0:
1718
+ # Job
1719
+ job = JobSpec()
1720
+ job.pack(rows[0])
1721
+ # Files
1722
+ # start transaction
1723
+ self.conn.begin()
1724
+ # select
1725
+ file_table_name = re.sub("jobsArchived", "filesTable_ARCH", table)
1726
+ sql_get_files = (
1727
+ f"SELECT /*+ INDEX(tab FILES_ARCH_PANDAID_IDX)*/ {FileSpec.columnNames()} "
1728
+ f"FROM {file_table_name} tab "
1729
+ "WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-:days)"
1730
+ )
1731
+ self.cur.arraysize = 10000
1732
+ self.cur.execute(sql_get_files + comment, var_map)
1733
+ file_rows = self.cur.fetchall()
1734
+
1735
+ # metadata
1736
+ var_map = {}
1737
+ var_map[":PandaID"] = job.PandaID
1738
+ job.metadata = None
1739
+ meta_table_name = re.sub("jobsArchived", "metaTable_ARCH", table)
1740
+ sql_get_meta = f"SELECT metaData FROM {meta_table_name} WHERE PandaID=:PandaID"
1741
+ self.cur.execute(sql_get_meta + comment, var_map)
1742
+ for (clob_meta,) in self.cur:
1743
+ if clob_meta is not None:
1744
+ try:
1745
+ job.metadata = clob_meta.read()
1746
+ except AttributeError:
1747
+ job.metadata = str(clob_meta)
1748
+ break
1749
+
1750
+ # job parameters
1751
+ job.jobParameters = None
1752
+ job_param_table_name = re.sub("jobsArchived", "jobParamsTable_ARCH", table)
1753
+ sql_get_job_params = f"SELECT jobParameters FROM {job_param_table_name} WHERE PandaID=:PandaID"
1754
+ var_map = {}
1755
+ var_map[":PandaID"] = job.PandaID
1756
+ self.cur.execute(sql_get_job_params + comment, var_map)
1757
+ for (clob_job_params,) in self.cur:
1758
+ if clob_job_params is not None:
1759
+ try:
1760
+ job.jobParameters = clob_job_params.read()
1761
+ except AttributeError:
1762
+ job.jobParameters = str(clob_job_params)
1763
+ break
1764
+ # commit
1765
+ if not self._commit():
1766
+ raise RuntimeError("Commit error")
1767
+ # set files
1768
+ for file_row in file_rows:
1769
+ file = FileSpec()
1770
+ file.pack(file_row)
1771
+ # remove redundant white spaces
1772
+ try:
1773
+ file.md5sum = file.md5sum.strip()
1774
+ except Exception:
1775
+ pass
1776
+ try:
1777
+ file.checksum = file.checksum.strip()
1778
+ except Exception:
1779
+ pass
1780
+ job.addFile(file)
1781
+ return job
1782
+ tmp_log.debug(f"not found")
1783
+ return None
1784
+ except Exception:
1785
+ # roll back
1786
+ self._rollback()
1787
+ if i_try + 1 < n_try:
1788
+ tmp_log.error(f"retry {i_try}")
1789
+ time.sleep(random.randint(10, 20))
1790
+ continue
1791
+ self.dump_error_message(tmp_log)
1792
+ # return None
1793
+ return None
1794
+
1795
+ # throttle user jobs
1796
+ def throttleUserJobs(self, prodUserName, workingGroup, get_dict):
1797
+ comment = " /* DBProxy.throttleUserJobs */"
1798
+ tmp_log = self.create_tagged_logger(comment, f"user={prodUserName} group={workingGroup}")
1799
+ tmp_log.debug("start")
1800
+ try:
1801
+ # sql to get tasks
1802
+ sql_get_tasks = (
1803
+ "SELECT /*+ INDEX_RS_ASC(tab JOBSACTIVE4_PRODUSERNAMEST_IDX) */ DISTINCT jediTaskID "
1804
+ "FROM ATLAS_PANDA.jobsActive4 tab "
1805
+ "WHERE prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName "
1806
+ "AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
1807
+ "AND maxCpuCount>:maxTime "
1808
+ )
1809
+
1810
+ if workingGroup is not None:
1811
+ sql_get_tasks += "AND workingGroup=:workingGroup "
1812
+ else:
1813
+ sql_get_tasks += "AND workingGroup IS NULL "
1814
+
1815
+ # sql to get jobs
1816
+ sql_get_jobs = (
1817
+ "SELECT "
1818
+ "PandaID, jediTaskID, cloud, computingSite, prodSourceLabel "
1819
+ "FROM ATLAS_PANDA.jobsActive4 "
1820
+ "WHERE jediTaskID=:jediTaskID "
1821
+ "AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
1822
+ "AND maxCpuCount>:maxTime "
1823
+ )
1824
+
1825
+ # sql to update jobs
1826
+ sql_update_jobs = (
1827
+ f"UPDATE {panda_config.schemaPANDA}.jobsActive4 SET jobStatus=:newJobStatus,relocationFlag=:newRelFlag "
1828
+ f"WHERE jediTaskID=:jediTaskID AND jobStatus=:oldJobStatus AND maxCpuCount>:maxTime"
1829
+ )
1830
+
1831
+ # start transaction
1832
+ self.conn.begin()
1833
+ # select
1834
+ self.cur.arraysize = 10
1835
+ var_map = {":prodSourceLabel": "user", ":oldRelFlag": 1, ":prodUserName": prodUserName, ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
1836
+ if workingGroup is not None:
1837
+ var_map[":workingGroup"] = workingGroup
1838
+ # get tasks
1839
+ self.cur.execute(sql_get_tasks + comment, var_map)
1840
+ task_rows = self.cur.fetchall()
1841
+ # commit
1842
+ if not self._commit():
1843
+ raise RuntimeError("Commit error")
1844
+ # loop over all tasks
1845
+ task_ids = [task_id for task_id, in task_rows]
1846
+ random.shuffle(task_ids)
1847
+ total_updated = 0
1848
+ updated_per_task = {}
1849
+ for task_id in task_ids:
1850
+ tmp_log.debug(f"reset jediTaskID={task_id}")
1851
+ # start transaction
1852
+ self.conn.begin()
1853
+ # get jobs
1854
+ var_map = {":jediTaskID": task_id, ":oldRelFlag": 1, ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
1855
+ self.cur.execute(sql_get_jobs + comment, var_map)
1856
+ job_rows = self.cur.fetchall()
1857
+ job_info_map = {
1858
+ panda_id: {
1859
+ "computingSite": computing_site,
1860
+ "cloud": cloud,
1861
+ "prodSourceLabel": prod_source_label,
1862
+ }
1863
+ for panda_id, _task_id, cloud, computing_site, prod_source_label in job_rows
1864
+ }
1865
+ # update jobs
1866
+ var_map = {":jediTaskID": task_id, ":newRelFlag": 3, ":newJobStatus": "throttled", ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
1867
+ self.cur.execute(sql_update_jobs + comment, var_map)
1868
+ n_updated = self.cur.rowcount
1869
+ tmp_log.debug(f"reset {n_updated} jobs")
1870
+ if n_updated > 0:
1871
+ total_updated += n_updated
1872
+ updated_per_task[task_id] = n_updated
1873
+ for panda_id, job_info in job_info_map.items():
1874
+ self.recordStatusChange(
1875
+ panda_id,
1876
+ var_map[":newJobStatus"],
1877
+ infoMap=job_info,
1878
+ useCommit=False,
1879
+ )
1880
+ # commit
1881
+ if not self._commit():
1882
+ raise RuntimeError("Commit error")
1883
+ if get_dict:
1884
+ tmp_log.debug(f"done with {updated_per_task}")
1885
+ return updated_per_task
1886
+ tmp_log.debug(f"done with {total_updated}")
1887
+ return total_updated
1888
+ except Exception:
1889
+ # roll back
1890
+ self._rollback()
1891
+ # error
1892
+ self.dump_error_message(tmp_log)
1893
+ return None
1894
+
1895
+ # unthrottle user jobs
1896
+ def unThrottleUserJobs(self, prodUserName, workingGroup, get_dict):
1897
+ comment = " /* DBProxy.unThrottleUserJobs */"
1898
+ tmp_log = self.create_tagged_logger(comment, f"user={prodUserName} group={workingGroup}")
1899
+ tmp_log.debug("start")
1900
+ try:
1901
+ # sql to get tasks
1902
+ sql_get_tasks = (
1903
+ "SELECT /*+ INDEX_RS_ASC(tab JOBSACTIVE4_PRODUSERNAMEST_IDX) */ DISTINCT jediTaskID "
1904
+ "FROM ATLAS_PANDA.jobsActive4 tab "
1905
+ "WHERE prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName "
1906
+ "AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
1907
+ )
1908
+
1909
+ if workingGroup is not None:
1910
+ sql_get_tasks += "AND workingGroup=:workingGroup "
1911
+ else:
1912
+ sql_get_tasks += "AND workingGroup IS NULL "
1913
+
1914
+ # sql to get jobs
1915
+ sql_get_jobs = (
1916
+ "SELECT "
1917
+ "PandaID, jediTaskID, cloud, computingSite, prodSourceLabel "
1918
+ "FROM ATLAS_PANDA.jobsActive4 "
1919
+ "WHERE jediTaskID=:jediTaskID "
1920
+ "AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
1921
+ )
1922
+
1923
+ # sql to update jobs
1924
+ sql_update_jobs = (
1925
+ f"UPDATE {panda_config.schemaPANDA}.jobsActive4 SET jobStatus=:newJobStatus,relocationFlag=:newRelFlag "
1926
+ "WHERE jediTaskID=:jediTaskID AND jobStatus=:oldJobStatus "
1927
+ )
1928
+
1929
+ # start transaction
1930
+ self.conn.begin()
1931
+ # select
1932
+ self.cur.arraysize = 10
1933
+ var_map = {":prodSourceLabel": "user", ":oldRelFlag": 3, ":prodUserName": prodUserName, ":oldJobStatus": "throttled"}
1934
+ if workingGroup is not None:
1935
+ var_map[":workingGroup"] = workingGroup
1936
+ # get tasks
1937
+ self.cur.execute(sql_get_tasks + comment, var_map)
1938
+ task_rows = self.cur.fetchall()
1939
+ # commit
1940
+ if not self._commit():
1941
+ raise RuntimeError("Commit error")
1942
+ # loop over all tasks
1943
+ task_ids = [task_id for task_id, in task_rows]
1944
+ random.shuffle(task_ids)
1945
+ total_updated = 0
1946
+ updated_per_task = {}
1947
+ for task_id in task_ids:
1948
+ tmp_log.debug(f"reset jediTaskID={task_id}")
1949
+ # start transaction
1950
+ self.conn.begin()
1951
+ # get jobs
1952
+ var_map = {":jediTaskID": task_id, ":oldRelFlag": 3, ":oldJobStatus": "throttled"}
1953
+ self.cur.execute(sql_get_jobs + comment, var_map)
1954
+ job_rows = self.cur.fetchall()
1955
+ job_info_map = {
1956
+ panda_id: {
1957
+ "computingSite": computing_site,
1958
+ "cloud": cloud,
1959
+ "prodSourceLabel": prod_source_label,
1960
+ }
1961
+ for panda_id, _task_id, cloud, computing_site, prod_source_label in job_rows
1962
+ }
1963
+ # update jobs
1964
+ var_map = {":jediTaskID": task_id, ":newRelFlag": 1, ":newJobStatus": "activated", ":oldJobStatus": "throttled"}
1965
+ self.cur.execute(sql_update_jobs + comment, var_map)
1966
+ n_updated = self.cur.rowcount
1967
+ tmp_log.debug(f"reset {n_updated} jobs")
1968
+ if n_updated > 0:
1969
+ total_updated += n_updated
1970
+ updated_per_task[task_id] = n_updated
1971
+ for panda_id, job_info in job_info_map.items():
1972
+ self.recordStatusChange(
1973
+ panda_id,
1974
+ var_map[":newJobStatus"],
1975
+ infoMap=job_info,
1976
+ useCommit=False,
1977
+ )
1978
+ # commit
1979
+ if not self._commit():
1980
+ raise RuntimeError("Commit error")
1981
+ if get_dict:
1982
+ tmp_log.debug(f"done with {updated_per_task}")
1983
+ return updated_per_task
1984
+ tmp_log.debug(f"done with {total_updated}")
1985
+ return total_updated
1986
+ except Exception:
1987
+ # roll back
1988
+ self._rollback()
1989
+ # error
1990
+ self.dump_error_message(tmp_log)
1991
+ return None
1992
+
1993
+ # get the list of jobdefIDs for failed jobs in a task
1994
+ def getJobdefIDsForFailedJob(self, jediTaskID):
1995
+ comment = " /* DBProxy.getJobdefIDsForFailedJob */"
1996
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
1997
+ tmp_log.debug(f"start")
1998
+ try:
1999
+ # begin transaction
2000
+ self.conn.begin()
2001
+ # dql to get jobDefIDs
2002
+ sqlGF = "SELECT distinct jobDefinitionID FROM ATLAS_PANDA.jobsActive4 "
2003
+ sqlGF += "WHERE jediTaskID=:jediTaskID AND jobStatus=:jobStatus "
2004
+ sqlGF += "AND attemptNr<maxAttempt "
2005
+ varMap = {}
2006
+ varMap[":jediTaskID"] = jediTaskID
2007
+ varMap[":jobStatus"] = "failed"
2008
+ self.cur.execute(sqlGF + comment, varMap)
2009
+ resGF = self.cur.fetchall()
2010
+ retList = []
2011
+ for (jobDefinitionID,) in resGF:
2012
+ retList.append(jobDefinitionID)
2013
+ # commit
2014
+ if not self._commit():
2015
+ raise RuntimeError("Commit error")
2016
+ tmp_log.debug(f"{str(retList)}")
2017
+ return retList
2018
+ except Exception:
2019
+ # roll back
2020
+ self._rollback()
2021
+ # error
2022
+ self.dump_error_message(tmp_log)
2023
+ return []
2024
+
2025
+ # check validity of merge job
2026
+ def isValidMergeJob(self, pandaID, jediTaskID):
2027
+ comment = " /* DBProxy.isValidMergeJob */"
2028
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID} jediTaskID={jediTaskID}")
2029
+ tmp_log.debug("start")
2030
+ try:
2031
+ retVal = True
2032
+ retMsg = ""
2033
+ # sql to check if merge job is active
2034
+ sqlJ = "SELECT jobStatus FROM ATLAS_PANDA.jobsDefined4 WHERE PandaID=:PandaID "
2035
+ sqlJ += "UNION "
2036
+ sqlJ += "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
2037
+ # sql to get input files
2038
+ sqlF = "SELECT datasetID,fileID FROM ATLAS_PANDA.filesTable4 "
2039
+ sqlF += "WHERE PandaID=:PandaID AND type IN (:type1,:type2) "
2040
+ # sql to get PandaIDs for pre-merged jobs
2041
+ sqlP = "SELECT outPandaID FROM ATLAS_PANDA.JEDI_Dataset_Contents "
2042
+ sqlP += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID AND type<>:type1"
2043
+ # sql to check if pre-merge job is active
2044
+ sqlC = "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
2045
+ # begin transaction
2046
+ self.conn.begin()
2047
+ # check if merge job is active
2048
+ varMap = {}
2049
+ varMap[":PandaID"] = pandaID
2050
+ self.cur.execute(sqlJ + comment, varMap)
2051
+ resJ = self.cur.fetchone()
2052
+ if resJ is None:
2053
+ tmp_log.debug("merge job not found")
2054
+ else:
2055
+ # get input files
2056
+ varMap = {}
2057
+ varMap[":PandaID"] = pandaID
2058
+ varMap[":type1"] = "input"
2059
+ varMap[":type2"] = "pseudo_input"
2060
+ self.cur.execute(sqlF + comment, varMap)
2061
+ resF = self.cur.fetchall()
2062
+ firstDatasetID = None
2063
+ fileIDsMap = {}
2064
+ for datasetID, fileID in resF:
2065
+ if datasetID not in fileIDsMap:
2066
+ fileIDsMap[datasetID] = set()
2067
+ fileIDsMap[datasetID].add(fileID)
2068
+ # get PandaIDs for pre-merged jobs
2069
+ pandaIDs = set()
2070
+ for datasetID in fileIDsMap:
2071
+ fileIDs = fileIDsMap[datasetID]
2072
+ for fileID in fileIDs:
2073
+ varMap = {}
2074
+ varMap[":jediTaskID"] = jediTaskID
2075
+ varMap[":datasetID"] = datasetID
2076
+ varMap[":fileID"] = fileID
2077
+ varMap[":type1"] = "lib"
2078
+ self.cur.execute(sqlP + comment, varMap)
2079
+ resP = self.cur.fetchone()
2080
+ if resP is not None and resP[0] is not None:
2081
+ pandaIDs.add(resP[0])
2082
+ # only files in the first dataset are enough
2083
+ if len(pandaIDs) > 0:
2084
+ break
2085
+ # check pre-merge job
2086
+ for tmpPandaID in pandaIDs:
2087
+ varMap = {}
2088
+ varMap[":PandaID"] = tmpPandaID
2089
+ self.cur.execute(sqlC + comment, varMap)
2090
+ resC = self.cur.fetchone()
2091
+ if resC is None:
2092
+ # not found
2093
+ tmp_log.debug(f"pre-merge job {tmpPandaID} not found")
2094
+ retVal = False
2095
+ retMsg = tmpPandaID
2096
+ break
2097
+ elif resC[0] != "merging":
2098
+ # not in merging
2099
+ tmp_log.debug("pre-merge job in {0} != merging".format(tmpPandaID, resC[0]))
2100
+ retVal = False
2101
+ retMsg = tmpPandaID
2102
+ break
2103
+ # commit
2104
+ if not self._commit():
2105
+ raise RuntimeError("Commit error")
2106
+ tmp_log.debug(f"ret={retVal}")
2107
+ return retVal, retMsg
2108
+ except Exception:
2109
+ # roll back
2110
+ self._rollback()
2111
+ # error
2112
+ self.dump_error_message(tmp_log)
2113
+ return None, ""
2114
+
2115
+ # check Job status
2116
+ def checkJobStatus(self, pandaID):
2117
+ comment = " /* DBProxy.checkJobStatus */"
2118
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
2119
+ tmp_log.debug("start")
2120
+ retVal = {"command": None, "status": None}
2121
+ try:
2122
+ sqlC = (
2123
+ "SELECT jobStatus,commandToPilot FROM ATLAS_PANDA.jobsActive4 "
2124
+ "WHERE PandaID=:pandaID "
2125
+ "UNION "
2126
+ "SELECT /*+ INDEX_RS_ASC(JOBSARCHIVED4 PART_JOBSARCHIVED4_PK) */ "
2127
+ "jobStatus,commandToPilot FROM ATLAS_PANDA.jobsArchived4 "
2128
+ "WHERE PandaID=:pandaID AND modificationTime>:timeLimit "
2129
+ )
2130
+ varMap = dict()
2131
+ varMap[":pandaID"] = int(pandaID)
2132
+ varMap[":timeLimit"] = naive_utcnow() - datetime.timedelta(hours=1)
2133
+ # begin transaction
2134
+ self.conn.begin()
2135
+ # select
2136
+ self.cur.arraysize = 10
2137
+ self.cur.execute(sqlC + comment, varMap)
2138
+ res = self.cur.fetchone()
2139
+ if res is not None:
2140
+ retVal["status"], retVal["command"] = res
2141
+ else:
2142
+ retVal["status"], retVal["command"] = "unknown", "tobekilled"
2143
+ # commit
2144
+ if not self._commit():
2145
+ raise RuntimeError("Commit error")
2146
+ tmp_log.debug(f"done with {str(retVal)}")
2147
+ return retVal
2148
+ except Exception:
2149
+ # roll back
2150
+ self._rollback()
2151
+ self.dump_error_message(tmp_log)
2152
+ return retVal
2153
+
2154
+ # get active job attribute
2155
+ def getActiveJobAttributes(self, pandaID, attrs):
2156
+ comment = " /* DBProxy.getActiveJobAttributes */"
2157
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
2158
+ tmp_log.debug("start")
2159
+ try:
2160
+ sqlS = f"SELECT {','.join(attrs)} FROM ATLAS_PANDA.jobsActive4 "
2161
+ sqlS += "WHERE PandaID=:PandaID "
2162
+ # start transaction
2163
+ self.conn.begin()
2164
+ varMap = {}
2165
+ varMap[":PandaID"] = pandaID
2166
+ self.cur.execute(sqlS + comment, varMap)
2167
+ res = self.cur.fetchone()
2168
+ if res is not None:
2169
+ retMap = dict()
2170
+ for idx, attr in enumerate(attrs):
2171
+ retMap[attr] = res[idx]
2172
+ else:
2173
+ retMap = None
2174
+ # commit
2175
+ if not self._commit():
2176
+ raise RuntimeError("Commit error")
2177
+ tmp_log.debug(f"got {str(retMap)}")
2178
+ return retMap
2179
+ except Exception:
2180
+ # roll back
2181
+ self._rollback()
2182
+ # error
2183
+ self.dump_error_message(tmp_log)
2184
+ return None
2185
+
2186
+ # get user job metadata
2187
+ def getUserJobMetadata(self, jediTaskID):
2188
+ comment = " /* DBProxy.getUserJobMetadata */"
2189
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
2190
+ tmp_log.debug("start")
2191
+ try:
2192
+ # sql to get workers
2193
+ sqlC = "SELECT j.PandaID,m.metaData FROM {0} j,{1} m "
2194
+ sqlC += "WHERE j.jediTaskID=:jediTaskID AND j.jobStatus=:jobStatus AND m.PandaID=j.PandaID AND j.prodSourceLabel=:label "
2195
+ retMap = dict()
2196
+ for a, m in [
2197
+ ("ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.metaTable"),
2198
+ ("ATLAS_PANDAARCH.jobsArchived", "ATLAS_PANDAARCH.metaTable_ARCH"),
2199
+ ]:
2200
+ sql = sqlC.format(a, m)
2201
+ varMap = {}
2202
+ varMap[":jediTaskID"] = jediTaskID
2203
+ varMap[":label"] = "user"
2204
+ varMap[":jobStatus"] = "finished"
2205
+ # start transaction
2206
+ self.conn.begin()
2207
+ self.cur.execute(sql + comment, varMap)
2208
+ resCs = self.cur.fetchall()
2209
+ for pandaID, clobMeta in resCs:
2210
+ try:
2211
+ metadata = clobMeta.read()
2212
+ except AttributeError:
2213
+ metadata = str(clobMeta)
2214
+ try:
2215
+ retMap[pandaID] = json.loads(metadata)
2216
+ except Exception:
2217
+ pass
2218
+ # commit
2219
+ if not self._commit():
2220
+ raise RuntimeError("Commit error")
2221
+ tmp_log.debug(f"got {len(retMap)} data blocks")
2222
+ return retMap
2223
+ except Exception:
2224
+ # roll back
2225
+ self._rollback()
2226
+ # error
2227
+ self.dump_error_message(tmp_log)
2228
+ return {}
2229
+
2230
+ # insert job output report
2231
+ def insertJobOutputReport(self, panda_id, prod_source_label, job_status, attempt_nr, data):
2232
+ comment = " /* DBProxy.insertJobOutputReport */"
2233
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2234
+ tmp_log.debug("start")
2235
+ # sql to insert
2236
+ sqlI = (
2237
+ "INSERT INTO {0}.Job_Output_Report "
2238
+ "(PandaID, prodSourceLabel, jobStatus, attemptNr, data, timeStamp) "
2239
+ "VALUES(:PandaID, :prodSourceLabel, :jobStatus, :attemptNr, :data, :timeStamp) "
2240
+ ).format(panda_config.schemaPANDA)
2241
+ try:
2242
+ retVal = False
2243
+ # start transaction
2244
+ self.conn.begin()
2245
+ # insert
2246
+ varMap = {}
2247
+ varMap[":PandaID"] = panda_id
2248
+ varMap[":prodSourceLabel"] = prod_source_label
2249
+ varMap[":jobStatus"] = job_status
2250
+ varMap[":attemptNr"] = attempt_nr
2251
+ varMap[":data"] = data
2252
+ varMap[":timeStamp"] = naive_utcnow()
2253
+ self.cur.execute(sqlI + comment, varMap)
2254
+ tmp_log.debug("successfully inserted")
2255
+ retVal = True
2256
+ # commit
2257
+ if not self._commit():
2258
+ raise RuntimeError("Commit error")
2259
+ tmp_log.debug("done")
2260
+ return retVal
2261
+ except Exception:
2262
+ # roll back
2263
+ self._rollback()
2264
+ # error
2265
+ self.dump_error_message(tmp_log)
2266
+ return retVal
2267
+
2268
+ # update data of job output report
2269
+ def updateJobOutputReport(self, panda_id, attempt_nr, data):
2270
+ comment = " /* DBProxy.updateJobOutputReport */"
2271
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2272
+ tmp_log.debug("start")
2273
+ # try to lock
2274
+ try:
2275
+ retVal = False
2276
+ # sql to update
2277
+ sqlU = f"UPDATE {panda_config.schemaPANDA}.Job_Output_Report SET data=:data, timeStamp=:timeStamp WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2278
+ # start transaction
2279
+ self.conn.begin()
2280
+ # update
2281
+ varMap = {}
2282
+ varMap[":PandaID"] = panda_id
2283
+ varMap[":attemptNr"] = attempt_nr
2284
+ varMap[":data"] = data
2285
+ varMap[":timeStamp"] = naive_utcnow()
2286
+ self.cur.execute(sqlU + comment, varMap)
2287
+ nRow = self.cur.rowcount
2288
+ if nRow == 1:
2289
+ tmp_log.debug("successfully updated")
2290
+ retVal = True
2291
+ elif nRow == 0:
2292
+ tmp_log.debug("entry not found, not updated")
2293
+ else:
2294
+ tmp_log.warning(f"updated unspecific number of rows: {nRow}")
2295
+ # commit
2296
+ if not self._commit():
2297
+ raise RuntimeError("Commit error")
2298
+ tmp_log.debug("done")
2299
+ return retVal
2300
+ except Exception:
2301
+ # roll back
2302
+ self._rollback()
2303
+ # error
2304
+ self.dump_error_message(tmp_log)
2305
+ return retVal
2306
+
2307
+ # deleted job output report
2308
+ def deleteJobOutputReport(self, panda_id, attempt_nr):
2309
+ comment = " /* DBProxy.deleteJobOutputReport */"
2310
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2311
+ tmp_log.debug("start")
2312
+ # sql to delete
2313
+ sqlD = f"DELETE FROM {panda_config.schemaPANDA}.Job_Output_Report WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2314
+ try:
2315
+ retVal = False
2316
+ # start transaction
2317
+ self.conn.begin()
2318
+ # delete
2319
+ varMap = {}
2320
+ varMap[":PandaID"] = panda_id
2321
+ varMap[":attemptNr"] = attempt_nr
2322
+ self.cur.execute(sqlD + comment, varMap)
2323
+ tmp_log.debug("successfully deleted")
2324
+ retVal = True
2325
+ # commit
2326
+ if not self._commit():
2327
+ raise RuntimeError("Commit error")
2328
+ tmp_log.debug("done")
2329
+ return retVal
2330
+ except Exception:
2331
+ # roll back
2332
+ self._rollback()
2333
+ # error
2334
+ self.dump_error_message(tmp_log)
2335
+ return retVal
2336
+
2337
+ # get record of a job output report
2338
+ def getJobOutputReport(self, panda_id, attempt_nr):
2339
+ comment = " /* DBProxy.getJobOutputReport */"
2340
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2341
+ tmp_log.debug("start")
2342
+ # try to lock
2343
+ try:
2344
+ retVal = {}
2345
+ # sql to get records
2346
+ sqlGR = (
2347
+ "SELECT PandaID,prodSourceLabel,jobStatus,attemptNr,data,timeStamp,lockedBy,lockedTime "
2348
+ "FROM {0}.Job_Output_Report "
2349
+ "WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2350
+ ).format(panda_config.schemaPANDA)
2351
+ # start transaction
2352
+ self.conn.begin()
2353
+ # check
2354
+ varMap = {}
2355
+ varMap[":PandaID"] = panda_id
2356
+ varMap[":attemptNr"] = attempt_nr
2357
+ self.cur.execute(sqlGR + comment, varMap)
2358
+ resGR = self.cur.fetchall()
2359
+ if not resGR:
2360
+ tmp_log.debug("record does not exist, skipped")
2361
+ for (
2362
+ PandaID,
2363
+ prodSourceLabel,
2364
+ jobStatus,
2365
+ attemptNr,
2366
+ data,
2367
+ timeStamp,
2368
+ lockedBy,
2369
+ lockedTime,
2370
+ ) in resGR:
2371
+ # fill result
2372
+ retVal = {
2373
+ "PandaID": PandaID,
2374
+ "jobStatus": jobStatus,
2375
+ "attemptNr": attemptNr,
2376
+ "timeStamp": timeStamp,
2377
+ "data": data,
2378
+ "lockedBy": lockedBy,
2379
+ "lockedTime": lockedTime,
2380
+ }
2381
+ tmp_log.debug("got record")
2382
+ break
2383
+ # commit
2384
+ if not self._commit():
2385
+ raise RuntimeError("Commit error")
2386
+ tmp_log.debug("done")
2387
+ return retVal
2388
+ except Exception:
2389
+ # roll back
2390
+ self._rollback()
2391
+ # error
2392
+ self.dump_error_message(tmp_log)
2393
+ return retVal
2394
+
2395
+ # lock job output report
2396
+ def lockJobOutputReport(self, panda_id, attempt_nr, pid, time_limit, take_over_from=None):
2397
+ comment = " /* DBProxy.lockJobOutputReport */"
2398
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2399
+ tmp_log.debug("start")
2400
+ # try to lock
2401
+ try:
2402
+ retVal = []
2403
+ # sql to get lock
2404
+ sqlGL = (
2405
+ "SELECT PandaID,attemptNr "
2406
+ "FROM {0}.Job_Output_Report "
2407
+ "WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2408
+ "AND (lockedBy IS NULL OR lockedBy=:lockedBy OR lockedTime<:lockedTime) "
2409
+ "FOR UPDATE NOWAIT "
2410
+ ).format(panda_config.schemaPANDA)
2411
+ # sql to update lock
2412
+ sqlUL = (
2413
+ "UPDATE {0}.Job_Output_Report " "SET lockedBy=:lockedBy, lockedTime=:lockedTime " "WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2414
+ ).format(panda_config.schemaPANDA)
2415
+ # start transaction
2416
+ self.conn.begin()
2417
+ # check
2418
+ varMap = {}
2419
+ varMap[":PandaID"] = panda_id
2420
+ varMap[":attemptNr"] = attempt_nr
2421
+ if take_over_from is None:
2422
+ varMap[":lockedBy"] = pid
2423
+ else:
2424
+ varMap[":lockedBy"] = take_over_from
2425
+ varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
2426
+ utc_now = naive_utcnow()
2427
+ try:
2428
+ self.cur.execute(sqlGL + comment, varMap)
2429
+ resGL = self.cur.fetchall()
2430
+ if not resGL:
2431
+ tmp_log.debug("record already locked by other thread, skipped")
2432
+ except Exception:
2433
+ resGL = None
2434
+ tmp_log.debug("record skipped due to NOWAIT")
2435
+ if resGL:
2436
+ for panda_id, attempt_nr in resGL:
2437
+ # lock
2438
+ varMap = {}
2439
+ varMap[":PandaID"] = panda_id
2440
+ varMap[":attemptNr"] = attempt_nr
2441
+ varMap[":lockedBy"] = pid
2442
+ varMap[":lockedTime"] = utc_now
2443
+ self.cur.execute(sqlUL + comment, varMap)
2444
+ if take_over_from is None:
2445
+ tmp_log.debug(f"successfully locked record by {pid}")
2446
+ else:
2447
+ tmp_log.debug(f"successfully took over locked record from {take_over_from} by {pid}")
2448
+ retVal = True
2449
+ break
2450
+ # commit
2451
+ if not self._commit():
2452
+ raise RuntimeError("Commit error")
2453
+ tmp_log.debug("done")
2454
+ return retVal
2455
+ except Exception:
2456
+ # roll back
2457
+ self._rollback()
2458
+ # error
2459
+ self.dump_error_message(tmp_log)
2460
+ return retVal
2461
+
2462
+ # unlock job output report
2463
+ def unlockJobOutputReport(self, panda_id, attempt_nr, pid, lock_offset):
2464
+ comment = " /* DBProxy.unlockJobOutputReport */"
2465
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
2466
+ tmp_log.debug("start")
2467
+ # try to lock
2468
+ try:
2469
+ retVal = []
2470
+ # sql to get lock
2471
+ sqlGL = (
2472
+ "SELECT PandaID,attemptNr "
2473
+ "FROM {0}.Job_Output_Report "
2474
+ "WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2475
+ "AND lockedBy=:lockedBy "
2476
+ "FOR UPDATE"
2477
+ ).format(panda_config.schemaPANDA)
2478
+ # sql to update lock
2479
+ sqlUL = f"UPDATE {panda_config.schemaPANDA}.Job_Output_Report SET lockedTime=:lockedTime WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
2480
+ # start transaction
2481
+ self.conn.begin()
2482
+ # check
2483
+ varMap = {}
2484
+ varMap[":PandaID"] = panda_id
2485
+ varMap[":attemptNr"] = attempt_nr
2486
+ varMap[":lockedBy"] = pid
2487
+ self.cur.execute(sqlGL + comment, varMap)
2488
+ resGL = self.cur.fetchall()
2489
+ if not resGL:
2490
+ tmp_log.debug("record not locked by this thread, skipped")
2491
+ else:
2492
+ for panda_id, attempt_nr in resGL:
2493
+ # lock
2494
+ varMap = {}
2495
+ varMap[":PandaID"] = panda_id
2496
+ varMap[":attemptNr"] = attempt_nr
2497
+ varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=lock_offset)
2498
+ self.cur.execute(sqlUL + comment, varMap)
2499
+ tmp_log.debug("successfully unlocked record")
2500
+ retVal = True
2501
+ break
2502
+ # commit
2503
+ if not self._commit():
2504
+ raise RuntimeError("Commit error")
2505
+ tmp_log.debug("done")
2506
+ return retVal
2507
+ except Exception:
2508
+ # roll back
2509
+ self._rollback()
2510
+ # error
2511
+ self.dump_error_message(tmp_log)
2512
+ return retVal
2513
+
2514
+ # list pandaID, jobStatus, attemptNr, timeStamp of job output report
2515
+ def listJobOutputReport(self, only_unlocked, time_limit, limit, grace_period, labels, anti_labels):
2516
+ comment = " /* DBProxy.listJobOutputReport */"
2517
+ tmp_log = self.create_tagged_logger(comment)
2518
+ tmp_log.debug(f"start label={str(labels)} limit={limit} anti_label={str(anti_labels)}")
2519
+ try:
2520
+ retVal = None
2521
+ if only_unlocked:
2522
+ # try to get only records unlocked or with expired lock
2523
+ varMap = {}
2524
+ varMap[":limit"] = limit * 10
2525
+ varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
2526
+ varMap[":timeStamp"] = naive_utcnow() - datetime.timedelta(seconds=grace_period)
2527
+ # sql to get record
2528
+ sqlGR = (
2529
+ "SELECT * "
2530
+ "FROM ( "
2531
+ "SELECT PandaID,jobStatus,attemptNr,timeStamp "
2532
+ "FROM {0}.Job_Output_Report "
2533
+ "WHERE (lockedBy IS NULL OR lockedTime<:lockedTime) "
2534
+ "AND timeStamp<:timeStamp ".format(panda_config.schemaPANDA)
2535
+ )
2536
+ if labels is not None:
2537
+ label_var_names_str, label_var_map = get_sql_IN_bind_variables(labels, prefix=":l_", value_as_suffix=True)
2538
+ sqlGR += f"AND prodSourceLabel IN ({label_var_names_str}) "
2539
+ varMap.update(label_var_map)
2540
+ if anti_labels is not None:
2541
+ anti_label_var_names_str, anti_label_var_map = get_sql_IN_bind_variables(anti_labels, prefix=":al_", value_as_suffix=True)
2542
+ sqlGR += f"AND prodSourceLabel NOT IN ({anti_label_var_names_str}) "
2543
+ varMap.update(anti_label_var_map)
2544
+ sqlGR += "ORDER BY timeStamp " ") " "WHERE rownum<=:limit "
2545
+ # start transaction
2546
+ self.conn.begin()
2547
+ # check
2548
+ self.cur.execute(sqlGR + comment, varMap)
2549
+ separator = limit // 10
2550
+ retVal = self.cur.fetchall()
2551
+ # shuffle tail to avoid conflict
2552
+ ret_head = retVal[:separator]
2553
+ ret_tail = retVal[separator:]
2554
+ random.shuffle(ret_tail)
2555
+ retVal = ret_head + ret_tail
2556
+ retVal = retVal[:limit]
2557
+ tmp_log.debug(f"listed {len(retVal)} unlocked records")
2558
+ else:
2559
+ # sql to select
2560
+ sqlS = (
2561
+ "SELECT * "
2562
+ "FROM ( "
2563
+ "SELECT PandaID,jobStatus,attemptNr,timeStamp "
2564
+ "FROM {0}.Job_Output_Report "
2565
+ "ORDER BY timeStamp "
2566
+ ") "
2567
+ "WHERE rownum<=:limit "
2568
+ ).format(panda_config.schemaPANDA)
2569
+ # start transaction
2570
+ self.conn.begin()
2571
+ varMap = {}
2572
+ varMap[":limit"] = limit
2573
+ # check
2574
+ self.cur.execute(sqlS + comment, varMap)
2575
+ retVal = self.cur.fetchall()
2576
+ tmp_log.debug(f"listed {len(retVal)} records")
2577
+ # commit
2578
+ if not self._commit():
2579
+ raise RuntimeError("Commit error")
2580
+ tmp_log.debug("done")
2581
+ return retVal
2582
+ except Exception:
2583
+ # roll back
2584
+ self._rollback()
2585
+ # error
2586
+ self.dump_error_message(tmp_log)
2587
+ return retVal
2588
+
2589
+ # send command to a job
2590
+ def send_command_to_job(self, panda_id, com):
2591
+ comment = " /* DBProxy.send_command_to_job */"
2592
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
2593
+ tmp_log.debug("start")
2594
+ retVal = None
2595
+ try:
2596
+ # check length
2597
+ new_com = JobSpec.truncateStringAttr("commandToPilot", com)
2598
+ if len(new_com) != len(com):
2599
+ retVal = (
2600
+ False,
2601
+ f"command string too long. must be less than {len(new_com)} chars",
2602
+ )
2603
+ else:
2604
+ sqlR = "SELECT commandToPilot FROM ATLAS_PANDA.{} WHERE PandaID=:PandaID FOR UPDATE "
2605
+ sqlU = "UPDATE ATLAS_PANDA.{} SET commandToPilot=:commandToPilot " "WHERE PandaID=:PandaID "
2606
+ for table in ["jobsDefined4", "jobsActive4"]:
2607
+ # start transaction
2608
+ self.conn.begin()
2609
+ # read
2610
+ varMap = {}
2611
+ varMap[":PandaID"] = panda_id
2612
+ self.cur.execute(sqlR.format(table) + comment, varMap)
2613
+ data = self.cur.fetchone()
2614
+ if data is not None:
2615
+ (commandToPilot,) = data
2616
+ if commandToPilot == "tobekilled":
2617
+ retVal = (False, "job is being killed")
2618
+ else:
2619
+ varMap = {}
2620
+ varMap[":PandaID"] = panda_id
2621
+ varMap[":commandToPilot"] = com
2622
+ self.cur.execute(sqlU.format(table) + comment, varMap)
2623
+ nRow = self.cur.rowcount
2624
+ if nRow:
2625
+ retVal = (True, "command received")
2626
+ # commit
2627
+ if not self._commit():
2628
+ raise RuntimeError("Commit error")
2629
+ if retVal is not None:
2630
+ break
2631
+ if retVal is None:
2632
+ retVal = (False, f"no active job with PandaID={panda_id}")
2633
+ tmp_log.debug(f"done with {str(retVal)}")
2634
+ return retVal
2635
+ except Exception:
2636
+ # roll back
2637
+ self._rollback()
2638
+ # error
2639
+ self.dump_error_message(tmp_log)
2640
+ return False, "database error"