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,3804 @@
1
+ import datetime
2
+ import json
3
+ import os
4
+ import random
5
+ import re
6
+ import time
7
+ import traceback
8
+ import uuid
9
+
10
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
11
+ from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
12
+
13
+ from pandaserver.config import panda_config
14
+ from pandaserver.srvcore import CoreUtils
15
+ from pandaserver.taskbuffer import (
16
+ EventServiceUtils,
17
+ GlobalShares,
18
+ JobUtils,
19
+ PrioUtil,
20
+ SiteSpec,
21
+ )
22
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import (
23
+ BaseModule,
24
+ convert_dict_to_bind_vars,
25
+ memoize,
26
+ )
27
+ from pandaserver.taskbuffer.DdmSpec import DdmSpec
28
+ from pandaserver.taskbuffer.JediDatasetSpec import (
29
+ INPUT_TYPES_var_map,
30
+ INPUT_TYPES_var_str,
31
+ )
32
+ from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec
33
+ from pandaserver.taskbuffer.ResourceSpec import ResourceSpec, ResourceSpecMapper
34
+ from pandaserver.taskbuffer.Utils import create_shards
35
+ from pandaserver.taskbuffer.WorkQueueMapper import WorkQueueMapper
36
+
37
+
38
+ # Module class to define methods related to fundamental entities like GlobalShares, Resource Types, CO2, etc
39
+ class EntityModule(BaseModule):
40
+ # constructor
41
+ def __init__(self, log_stream: LogWrapper):
42
+ super().__init__(log_stream)
43
+ # global share variables
44
+ self.tree = None # Pointer to the root of the global shares tree
45
+ self.leave_shares = None # Pointer to the list with leave shares
46
+ self.__t_update_shares = None # Timestamp when the shares were last updated
47
+ self.__hs_distribution = None # HS06s distribution of sites
48
+ self.__t_update_distribution = None # Timestamp when the HS06s distribution was last updated
49
+
50
+ # resource type mapper
51
+ # if you want to use it, you need to call reload_resource_spec_mapper first
52
+ self.resource_spec_mapper = None
53
+ self.__t_update_resource_type_mapper = None
54
+
55
+ # priority boost
56
+ self.job_prio_boost_dict = None
57
+ self.job_prio_boost_dict_update_time = None
58
+
59
+ def __get_hs_leave_distribution(self, leave_shares):
60
+ """
61
+ Get the current HS06 distribution for running and queued jobs
62
+ """
63
+ comment = " /* DBProxy.get_hs_leave_distribution */"
64
+
65
+ sql_hs_distribution = """
66
+ SELECT gshare, jobstatus_grouped, SUM(HS)
67
+ FROM
68
+ (SELECT gshare, HS,
69
+ CASE
70
+ WHEN jobstatus IN('activated') THEN 'queued'
71
+ WHEN jobstatus IN('sent', 'running') THEN 'executing'
72
+ ELSE 'ignore'
73
+ END jobstatus_grouped
74
+ FROM ATLAS_PANDA.JOBS_SHARE_STATS JSS) a
75
+ GROUP BY gshare, jobstatus_grouped
76
+ """
77
+
78
+ self.cur.execute(sql_hs_distribution + comment)
79
+ hs_distribution_raw = self.cur.fetchall()
80
+
81
+ # get the hs distribution data into a dictionary structure
82
+ hs_distribution_dict = {}
83
+ hs_queued_total = 0
84
+ hs_executing_total = 0
85
+ hs_ignore_total = 0
86
+ for hs_entry in hs_distribution_raw:
87
+ gshare, status_group, hs = hs_entry
88
+ if hs is None:
89
+ continue
90
+ hs = float(hs)
91
+ hs_distribution_dict.setdefault(
92
+ gshare,
93
+ {
94
+ GlobalShares.PLEDGED: 0,
95
+ GlobalShares.QUEUED: 0,
96
+ GlobalShares.EXECUTING: 0,
97
+ },
98
+ )
99
+ hs_distribution_dict[gshare][status_group] = hs
100
+ # calculate totals
101
+ if status_group == GlobalShares.QUEUED:
102
+ hs_queued_total += hs
103
+ elif status_group == GlobalShares.EXECUTING:
104
+ hs_executing_total += hs
105
+ else:
106
+ hs_ignore_total += hs
107
+
108
+ # Calculate the ideal HS06 distribution based on shares.
109
+ for share_node in leave_shares:
110
+ share_name, share_value = share_node.name, share_node.value
111
+ hs_pledged_share = hs_executing_total * share_value / 100.0
112
+
113
+ hs_distribution_dict.setdefault(
114
+ share_name,
115
+ {
116
+ GlobalShares.PLEDGED: 0,
117
+ GlobalShares.QUEUED: 0,
118
+ GlobalShares.EXECUTING: 0,
119
+ },
120
+ )
121
+ # Pledged HS according to global share definitions
122
+ hs_distribution_dict[share_name]["pledged"] = hs_pledged_share
123
+ return hs_distribution_dict
124
+
125
+ # retrieve global shares
126
+ def get_shares(self, parents=""):
127
+ comment = " /* DBProxy.get_shares */"
128
+ tmp_log = self.create_tagged_logger(comment)
129
+ tmp_log.debug("start")
130
+
131
+ sql = """
132
+ SELECT NAME, VALUE, PARENT, PRODSOURCELABEL, WORKINGGROUP, CAMPAIGN, PROCESSINGTYPE, TRANSPATH, RTYPE,
133
+ VO, QUEUE_ID, THROTTLED
134
+ FROM ATLAS_PANDA.GLOBAL_SHARES
135
+ """
136
+ var_map = None
137
+
138
+ if parents == "":
139
+ # Get all shares
140
+ pass
141
+ elif parents is None:
142
+ # Get top level shares
143
+ sql += "WHERE parent IS NULL"
144
+
145
+ elif isinstance(parents, str):
146
+ # Get the children of a specific share
147
+ var_map = {":parent": parents}
148
+ sql += "WHERE parent = :parent"
149
+
150
+ elif type(parents) in (list, tuple):
151
+ # Get the children of a list of shares
152
+ var_map = {}
153
+ parent_var_names_str, parent_var_map = get_sql_IN_bind_variables(parents, prefix=":parent")
154
+ sql += f"WHERE parent IN ({parent_var_names_str})"
155
+ var_map.update(parent_var_map)
156
+
157
+ self.cur.execute(sql + comment, var_map)
158
+ resList = self.cur.fetchall()
159
+
160
+ tmp_log.debug("done")
161
+ return resList
162
+
163
+ def reload_shares(self, force=False):
164
+ """
165
+ Reloads the shares from the DB and recalculates distributions
166
+ """
167
+ comment = " /* DBProxy.reload_shares */"
168
+ # Don't reload shares every time
169
+ if (self.__t_update_shares is not None and self.__t_update_shares > datetime.datetime.now() - datetime.timedelta(hours=1)) or force:
170
+ return
171
+
172
+ tmp_log = self.create_tagged_logger(comment)
173
+
174
+ # Root dummy node
175
+ t_before = time.time()
176
+ tree = GlobalShares.Share("root", 100, None, None, None, None, None, None, None, None, None, None)
177
+ t_after = time.time()
178
+ total = t_after - t_before
179
+ tmp_log.debug(f"Root dummy tree took {total}s")
180
+
181
+ # Get top level shares from DB
182
+ t_before = time.time()
183
+ shares_top_level = self.get_shares(parents=None)
184
+ t_after = time.time()
185
+ total = t_after - t_before
186
+ tmp_log.debug(f"Getting shares took {total}s")
187
+
188
+ # Load branches
189
+ t_before = time.time()
190
+ for (
191
+ name,
192
+ value,
193
+ parent,
194
+ prodsourcelabel,
195
+ workinggroup,
196
+ campaign,
197
+ processingtype,
198
+ transpath,
199
+ rtype,
200
+ vo,
201
+ queue_id,
202
+ throttled,
203
+ ) in shares_top_level:
204
+ share = GlobalShares.Share(
205
+ name,
206
+ value,
207
+ parent,
208
+ prodsourcelabel,
209
+ workinggroup,
210
+ campaign,
211
+ processingtype,
212
+ transpath,
213
+ rtype,
214
+ vo,
215
+ queue_id,
216
+ throttled,
217
+ )
218
+ tree.children.append(self.__load_branch(share))
219
+ t_after = time.time()
220
+ total = t_after - t_before
221
+ tmp_log.debug(f"Loading the branches took {total}s")
222
+
223
+ # Normalize the values in the database
224
+ t_before = time.time()
225
+ tree.normalize()
226
+ t_after = time.time()
227
+ total = t_after - t_before
228
+ tmp_log.debug(f"Normalizing the values took {total}s")
229
+
230
+ # get the leave shares (the ones not having more children)
231
+ t_before = time.time()
232
+ leave_shares = tree.get_leaves()
233
+ t_after = time.time()
234
+ total = t_after - t_before
235
+ tmp_log.debug(f"Getting the leaves took {total}s")
236
+
237
+ self.leave_shares = leave_shares
238
+ self.__t_update_shares = datetime.datetime.now()
239
+
240
+ # get the distribution of shares
241
+ t_before = time.time()
242
+ # Retrieve the current HS06 distribution of jobs from the database and then aggregate recursively up to the root
243
+ hs_distribution = self.__get_hs_leave_distribution(leave_shares)
244
+ tree.aggregate_hs_distribution(hs_distribution)
245
+ t_after = time.time()
246
+ total = t_after - t_before
247
+ tmp_log.debug(f"Aggregating the hs distribution took {total}s")
248
+
249
+ self.tree = tree
250
+ self.__hs_distribution = hs_distribution
251
+ self.__t_update_distribution = datetime.datetime.now()
252
+ return
253
+
254
+ def __reload_hs_distribution(self):
255
+ """
256
+ Reloads the HS distribution
257
+ """
258
+ comment = " /* DBProxy.__reload_hs_distribution */"
259
+ tmp_log = self.create_tagged_logger(comment)
260
+ tmp_log.debug(self.__t_update_distribution)
261
+ tmp_log.debug(self.__hs_distribution)
262
+ # Reload HS06s distribution every 10 seconds
263
+ if self.__t_update_distribution is not None and self.__t_update_distribution > datetime.datetime.now() - datetime.timedelta(seconds=10):
264
+ tmp_log.debug("release")
265
+ return
266
+
267
+ # Retrieve the current HS06 distribution of jobs from the database and then aggregate recursively up to the root
268
+ tmp_log.debug("get dist")
269
+ t_before = time.time()
270
+ hs_distribution = self.__get_hs_leave_distribution(self.leave_shares)
271
+ tmp_log.debug("aggr dist")
272
+ self.tree.aggregate_hs_distribution(hs_distribution)
273
+ t_after = time.time()
274
+ total = t_after - t_before
275
+ tmp_log.debug(f"Reloading the hs distribution took {total}s")
276
+
277
+ self.__hs_distribution = hs_distribution
278
+ self.__t_update_distribution = datetime.datetime.now()
279
+
280
+ # log the distribution for debugging purposes
281
+ tmp_log.debug(f"Current HS06 distribution is {hs_distribution}")
282
+
283
+ return
284
+
285
+ def get_sorted_leaves(self):
286
+ """
287
+ Re-loads the shares, then returns the leaves sorted by under usage
288
+ """
289
+ self.reload_shares()
290
+ self.__reload_hs_distribution()
291
+ return self.tree.sort_branch_by_current_hs_distribution(self.__hs_distribution)
292
+
293
+ def get_tree_of_gshare_names(self):
294
+ """
295
+ get nested dict of gshare names implying the tree structure
296
+ """
297
+
298
+ def get_nested_gshare(share):
299
+ val = None
300
+ if not share.children:
301
+ # leaf
302
+ pass
303
+ else:
304
+ # branch
305
+ val = {}
306
+ for child in share.children:
307
+ val[child.name] = get_nested_gshare(child)
308
+ return val
309
+
310
+ ret_dict = get_nested_gshare(self.tree)
311
+ return ret_dict
312
+
313
+ def __load_branch(self, share):
314
+ """
315
+ Recursively load a branch
316
+ """
317
+ node = GlobalShares.Share(
318
+ share.name,
319
+ share.value,
320
+ share.parent,
321
+ share.prodsourcelabel,
322
+ share.workinggroup,
323
+ share.campaign,
324
+ share.processingtype,
325
+ share.transpath,
326
+ share.rtype,
327
+ share.vo,
328
+ share.queue_id,
329
+ share.throttled,
330
+ )
331
+
332
+ children = self.get_shares(parents=share.name)
333
+ if not children:
334
+ return node
335
+
336
+ for (
337
+ name,
338
+ value,
339
+ parent,
340
+ prodsourcelabel,
341
+ workinggroup,
342
+ campaign,
343
+ processingtype,
344
+ transpath,
345
+ rtype,
346
+ vo,
347
+ queue_id,
348
+ throttled,
349
+ ) in children:
350
+ child = GlobalShares.Share(
351
+ name,
352
+ value,
353
+ parent,
354
+ prodsourcelabel,
355
+ workinggroup,
356
+ campaign,
357
+ processingtype,
358
+ transpath,
359
+ rtype,
360
+ vo,
361
+ queue_id,
362
+ throttled,
363
+ )
364
+ node.children.append(self.__load_branch(child))
365
+
366
+ return node
367
+
368
+ def getGShareStatus(self):
369
+ """
370
+ Generates a list with sorted leave branches
371
+ """
372
+
373
+ comment = " /* DBProxy.getGShareStatus */"
374
+ tmp_log = self.create_tagged_logger(comment)
375
+ tmp_log.debug("start")
376
+
377
+ self.reload_shares()
378
+ self.__reload_hs_distribution()
379
+ sorted_shares = self.tree.sort_branch_by_current_hs_distribution(self.__hs_distribution)
380
+
381
+ sorted_shares_export = []
382
+ for share in sorted_shares:
383
+ sorted_shares_export.append(
384
+ {
385
+ "name": share.name,
386
+ "running": self.__hs_distribution[share.name]["executing"],
387
+ "target": self.__hs_distribution[share.name]["pledged"],
388
+ "queuing": self.__hs_distribution[share.name]["queued"],
389
+ }
390
+ )
391
+ return sorted_shares_export
392
+
393
+ def is_valid_share(self, share_name):
394
+ """
395
+ Checks whether the share is a valid leave share
396
+ """
397
+ self.reload_shares()
398
+ for share in self.leave_shares:
399
+ if share_name == share.name:
400
+ # Share found
401
+ return True
402
+
403
+ # Share not found
404
+ return False
405
+
406
+ def reassignShare(self, jedi_task_ids, gshare, reassign_running):
407
+ """
408
+ Will reassign all tasks and their jobs that have not yet completed to specified share
409
+ @param jedi_task_ids: task ids
410
+ @param gshare: dest share
411
+ @param reassign_running: whether to reassign running jobs
412
+ """
413
+
414
+ comment = " /* DBProxy.reassignShare */"
415
+ tmp_log = self.create_tagged_logger(comment)
416
+ tmp_log.debug(f"Start reassigning {jedi_task_ids} to gshare={gshare} and reassign_running={reassign_running}")
417
+
418
+ try:
419
+ if not self.is_valid_share(gshare):
420
+ error_msg = f"Share {gshare} is not a leave share "
421
+ tmp_log.debug(error_msg)
422
+ ret_val = 1, error_msg
423
+ tmp_log.debug(error_msg)
424
+ return ret_val
425
+
426
+ # begin transaction
427
+ self.conn.begin()
428
+
429
+ # update in shards of 100 task ids
430
+ for shard in create_shards(jedi_task_ids, 100):
431
+ # Prepare the bindings
432
+ var_map = {}
433
+ shard_taskid_set = set()
434
+ i = 0
435
+ for _task_id in shard:
436
+ jedi_task_id = int(_task_id)
437
+ var_map[f":jtid{i}"] = jedi_task_id
438
+ i += 1
439
+ shard_taskid_set.add(jedi_task_id)
440
+ jtid_bindings = ",".join(f":jtid{i}" for i in range(len(shard_taskid_set)))
441
+
442
+ # select only tasks without lock
443
+ sql_tasks_not_locked = f"""
444
+ SELECT jediTaskID FROM ATLAS_PANDA.JEDI_Tasks
445
+ WHERE jediTaskID IN ({jtid_bindings}) AND lockedBy IS NULL
446
+ """
447
+
448
+ self.cur.execute(sql_tasks_not_locked + comment, var_map)
449
+ res = self.cur.fetchall()
450
+
451
+ # Update the bindings and prepare var map
452
+ var_map = {":gshare": gshare}
453
+ good_taskid_set = set()
454
+ i = 0
455
+ for (_task_id,) in res:
456
+ jedi_task_id = int(_task_id)
457
+ var_map[f":jtid{i}"] = jedi_task_id
458
+ i += 1
459
+ good_taskid_set.add(jedi_task_id)
460
+ jtid_bindings = ",".join(f":jtid{i}" for i in range(len(good_taskid_set)))
461
+ locked_taskid_set = shard_taskid_set - good_taskid_set
462
+ if locked_taskid_set:
463
+ tmp_log.debug(f"skip locked tasks: {','.join([str(i) for i in locked_taskid_set])}")
464
+
465
+ # Skip if there are no tasks to update
466
+ if not jtid_bindings:
467
+ continue
468
+
469
+ # update the task
470
+ sql_task = f"""
471
+ UPDATE ATLAS_PANDA.jedi_tasks set gshare=:gshare
472
+ WHERE jeditaskid IN ({jtid_bindings})
473
+ """
474
+
475
+ self.cur.execute(sql_task + comment, var_map)
476
+ tmp_log.debug(f"""set tasks {",".join([str(i) for i in good_taskid_set])} to gshare={gshare}""")
477
+
478
+ var_map[":pending"] = "pending"
479
+ var_map[":defined"] = "defined"
480
+ var_map[":assigned"] = "assigned"
481
+ var_map[":waiting"] = "waiting"
482
+ var_map[":activated"] = "activated"
483
+ jobstatus = ":pending, :defined, :assigned, :waiting, :activated"
484
+
485
+ # add running status in case these jobs also need to be reassigned
486
+ if reassign_running:
487
+ jobstatus = f"{jobstatus}, :running, :starting"
488
+ var_map[":running"] = "running"
489
+ var_map[":starting"] = "starting"
490
+
491
+ # update the jobs
492
+ sql_jobs = """
493
+ UPDATE ATLAS_PANDA.{0} set gshare=:gshare
494
+ WHERE jeditaskid IN ({1})
495
+ AND jobstatus IN ({2})
496
+ """
497
+
498
+ for table in ["jobsactive4", "jobsdefined4"]:
499
+ self.cur.execute(
500
+ sql_jobs.format(table, jtid_bindings, jobstatus) + comment,
501
+ var_map,
502
+ )
503
+
504
+ # commit
505
+ if not self._commit():
506
+ raise RuntimeError("Commit error")
507
+
508
+ tmp_log.debug("done")
509
+ return 0, None
510
+
511
+ except Exception:
512
+ # roll back
513
+ self._rollback()
514
+ # dump error
515
+ self.dump_error_message(tmp_log)
516
+ return -1, None
517
+
518
+ def reload_resource_spec_mapper(self):
519
+ # update once per hour only
520
+ if self.__t_update_resource_type_mapper and self.__t_update_resource_type_mapper > datetime.datetime.now() - datetime.timedelta(hours=1):
521
+ return
522
+
523
+ # get the resource types from the DB and make the ResourceSpecMapper object
524
+ resource_types = self.load_resource_types(use_commit=False)
525
+ if resource_types:
526
+ self.resource_spec_mapper = ResourceSpecMapper(resource_types)
527
+ self.__t_update_resource_type_mapper = datetime.datetime.now()
528
+ return
529
+
530
+ def load_resource_types(self, formatting="spec", use_commit=True):
531
+ """
532
+ Load the resource type table to memory
533
+ """
534
+ comment = " /* JediDBProxy.load_resource_types */"
535
+ tmp_log = self.create_tagged_logger(comment)
536
+ tmp_log.debug("start")
537
+ try:
538
+ sql = f"SELECT {ResourceSpec.column_names()} FROM {panda_config.schemaJEDI}.resource_types "
539
+ self.cur.execute(sql + comment)
540
+ resource_list = self.cur.fetchall()
541
+ resource_spec_list = []
542
+ for row in resource_list:
543
+ resource_name, mincore, maxcore, minrampercore, maxrampercore = row
544
+ if formatting == "dict":
545
+ resource_dict = {
546
+ "resource_name": resource_name,
547
+ "mincore": mincore,
548
+ "maxcore": maxcore,
549
+ "minrampercore": minrampercore,
550
+ "maxrampercore": maxrampercore,
551
+ }
552
+ resource_spec_list.append(resource_dict)
553
+ else:
554
+ resource_spec_list.append(
555
+ ResourceSpec(
556
+ resource_name,
557
+ mincore,
558
+ maxcore,
559
+ minrampercore,
560
+ maxrampercore,
561
+ )
562
+ )
563
+ # commit for postgres to avoid idle transactions
564
+ if use_commit:
565
+ if not self._commit():
566
+ raise RuntimeError("Commit error")
567
+ tmp_log.debug("done")
568
+ return resource_spec_list
569
+ except Exception:
570
+ if use_commit:
571
+ self._rollback()
572
+ self.dump_error_message(tmp_log)
573
+ return []
574
+
575
+ def get_resource_type_task(self, task_spec):
576
+ """
577
+ Identify the resource type of the task based on the resource type map.
578
+ Return the name of the resource type
579
+ """
580
+ comment = " /* JediDBProxy.get_resource_type_task */"
581
+ tmp_log = self.create_tagged_logger(comment)
582
+ tmp_log.debug("start")
583
+
584
+ resource_map = self.load_resource_types()
585
+
586
+ for resource_spec in resource_map:
587
+ if resource_spec.match_task(task_spec):
588
+ tmp_log.debug(f"done. resource_type is {resource_spec.resource_name}")
589
+ return resource_spec.resource_name
590
+
591
+ tmp_log.debug("done. resource_type is Undefined")
592
+ return "Undefined"
593
+
594
+ def reset_resource_type_task(self, jedi_task_id, use_commit=True):
595
+ """
596
+ Retrieve the relevant task parameters and reset the resource type
597
+ """
598
+ comment = " /* JediDBProxy.reset_resource_type */"
599
+ tmp_log = self.create_tagged_logger(comment)
600
+ tmp_log.debug("start")
601
+
602
+ # 1. Get the task parameters
603
+ var_map = {":jedi_task_id": jedi_task_id}
604
+ sql = f"SELECT corecount, ramcount, baseramcount, ramunit FROM {panda_config.schemaJEDI}.jedi_tasks WHERE jeditaskid = :jedi_task_id "
605
+ self.cur.execute(sql + comment, var_map)
606
+ corecount, ramcount, baseramcount, ramunit = self.cur.fetchone()
607
+ tmp_log.debug(
608
+ "retrieved following values for jediTaskid {0}: corecount {1}, ramcount {2}, baseramcount {3}, ramunit {4}".format(
609
+ jedi_task_id, corecount, ramcount, baseramcount, ramunit
610
+ )
611
+ )
612
+
613
+ # 2. Load the resource types and figure out the matching one
614
+ resource_map = self.load_resource_types(use_commit=False)
615
+ resource_name = "Undefined"
616
+ for resource_spec in resource_map:
617
+ if resource_spec.match_task_basic(corecount, ramcount, baseramcount, ramunit):
618
+ resource_name = resource_spec.resource_name
619
+ break
620
+
621
+ tmp_log.debug(f"decided resource_type {resource_name} jediTaskid {jedi_task_id}")
622
+
623
+ # 3. Update the task
624
+ try:
625
+ var_map = {":jedi_task_id": jedi_task_id, ":resource_type": resource_name}
626
+ sql = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET resource_type = :resource_type WHERE jeditaskid = :jedi_task_id "
627
+ tmp_log.debug("conn begin...")
628
+ if use_commit:
629
+ self.conn.begin()
630
+ tmp_log.debug("execute...")
631
+ self.cur.execute(sql + comment, var_map)
632
+ tmp_log.debug("commit...")
633
+ if use_commit:
634
+ if not self._commit():
635
+ raise RuntimeError("Commit error")
636
+ tmp_log.debug("committed...")
637
+ except Exception:
638
+ # roll back
639
+ if use_commit:
640
+ tmp_log.debug("rolling back...")
641
+ self._rollback()
642
+ self.dump_error_message(tmp_log)
643
+ tmp_log.error(f"{comment}: {sql} {var_map}")
644
+ return False
645
+
646
+ tmp_log.debug("done")
647
+ return True
648
+
649
+ def get_resource_type_job(self, job_spec):
650
+ """
651
+ Identify the resource type of the job based on the resource type map.
652
+ Return the name of the resource type
653
+ """
654
+ comment = " /* JediDBProxy.get_resource_type_job */"
655
+ tmp_log = self.create_tagged_logger(comment)
656
+ tmp_log.debug("start")
657
+
658
+ resource_map = self.load_resource_types()
659
+ tmp_log.debug(
660
+ "going to call match_job for pandaid {0} with minRamCount {1} (type{2}) and coreCount {3} (type{4})".format(
661
+ job_spec.PandaID,
662
+ job_spec.minRamCount,
663
+ type(job_spec.minRamCount),
664
+ job_spec.coreCount,
665
+ type(job_spec.coreCount),
666
+ )
667
+ )
668
+ resource_type = JobUtils.get_resource_type_job(resource_map, job_spec)
669
+ tmp_log.debug(f"done. resource_type is {resource_type}")
670
+ return resource_type
671
+
672
+ # get the resource type of a site
673
+ def get_rtype_site(self, site):
674
+ comment = " /* DBProxy.get_rtype_site */"
675
+ tmp_log = self.create_tagged_logger(comment)
676
+ tmp_log.debug("start")
677
+ try:
678
+ var_map = {":site": site}
679
+ sql = "SELECT /* use_json_type */ scj.data.resource_type FROM ATLAS_PANDA.schedconfig_json scj WHERE panda_queue=:site "
680
+
681
+ # start transaction
682
+ self.conn.begin()
683
+ self.cur.arraysize = 1
684
+ self.cur.execute(sql + comment, var_map)
685
+ rtype = self.cur.fetchone()[0]
686
+ # commit
687
+ if not self._commit():
688
+ raise RuntimeError("Commit error")
689
+ tmp_log.debug(f"site {site} has rtype: {rtype} ")
690
+ return rtype
691
+ except Exception:
692
+ # roll back
693
+ self._rollback()
694
+ # error
695
+ self.dump_error_message(tmp_log)
696
+ return None
697
+
698
+ def compare_share_task(self, share, task):
699
+ """
700
+ Logic to compare the relevant fields of share and task.
701
+ Return: False if some condition does NOT match. True if all conditions match.
702
+ """
703
+ if share.prodsourcelabel is not None and task.prodSourceLabel is not None and re.match(share.prodsourcelabel, task.prodSourceLabel) is None:
704
+ return False
705
+
706
+ working_group = task.workingGroup
707
+ if working_group is None:
708
+ working_group = " "
709
+
710
+ if share.workinggroup is not None and working_group is not None and re.match(share.workinggroup, working_group) is None:
711
+ return False
712
+
713
+ if share.campaign is not None and task.campaign and re.match(share.campaign, task.campaign) is None:
714
+ return False
715
+
716
+ if share.processingtype is not None and task.processingType is not None and re.match(share.processingtype, task.processingType) is None:
717
+ return False
718
+
719
+ if share.transpath is not None and task.transPath is not None and re.match(share.transpath, task.transPath) is None:
720
+ return False
721
+
722
+ if share.rtype is not None and task.site is not None:
723
+ try:
724
+ site = task.site.split(",")[0] # if task assigned to more than one site, take the first one
725
+ rtype_site = self.get_rtype_site(site)
726
+ if rtype_site and re.match(share.rtype, rtype_site) is None:
727
+ return False
728
+ except Exception:
729
+ return False
730
+
731
+ return True
732
+
733
+ def compare_share_job(self, share, job):
734
+ """
735
+ Logic to compare the relevant fields of share and job. It's basically the same as compare_share_task, but
736
+ does not check for the campaign field, which is not part of the job
737
+ """
738
+
739
+ if share.prodsourcelabel is not None and re.match(share.prodsourcelabel, job.prodSourceLabel) is None:
740
+ return False
741
+
742
+ if share.workinggroup is not None and re.match(share.workinggroup, job.workingGroup) is None:
743
+ return False
744
+
745
+ if share.processingtype is not None and re.match(share.processingtype, job.processingType) is None:
746
+ return False
747
+
748
+ if share.rtype is not None and job.computingSite is not None:
749
+ try:
750
+ site = job.computingSite.split(",")[0]
751
+ rtype_site = self.get_rtype_site(site)
752
+ if re.match(share.rtype, rtype_site) is None:
753
+ return False
754
+ except Exception:
755
+ return False
756
+
757
+ return True
758
+
759
+ def get_share_for_task(self, task):
760
+ """
761
+ Return the share based on a task specification
762
+ """
763
+ self.reload_shares()
764
+ selected_share_name = "Undefined"
765
+
766
+ for share in self.leave_shares:
767
+ if self.compare_share_task(share, task):
768
+ selected_share_name = share.name
769
+ break
770
+
771
+ if selected_share_name == "Undefined":
772
+ self._log_stream.warning(
773
+ "No share matching jediTaskId={0} (prodSourceLabel={1} workingGroup={2} campaign={3} transpath={4} site={5})".format(
774
+ task.jediTaskID,
775
+ task.prodSourceLabel,
776
+ task.workingGroup,
777
+ task.campaign,
778
+ task.transPath,
779
+ task.site,
780
+ )
781
+ )
782
+
783
+ return selected_share_name
784
+
785
+ def get_share_for_job(self, job):
786
+ """
787
+ Return the share based on a job specification
788
+ """
789
+ # special case: esmerge jobs go to Express share
790
+ if job.eventService == EventServiceUtils.esMergeJobFlagNumber:
791
+ return "Express"
792
+
793
+ self.reload_shares()
794
+ selected_share_name = "Undefined"
795
+
796
+ for share in self.leave_shares:
797
+ if self.compare_share_job(share, job):
798
+ selected_share_name = share.name
799
+ break
800
+
801
+ if selected_share_name == "Undefined":
802
+ self._log_stream.warning(f"No share matching PandaID={job.PandaID} (prodSourceLabel={job.prodSourceLabel} workingGroup={job.workingGroup})")
803
+
804
+ return selected_share_name
805
+
806
+ # get dispatch sorting criteria
807
+ def getSortingCriteria(self, site_name, max_jobs):
808
+ comment = " /* DBProxy.getSortingCriteria */"
809
+ tmp_log = self.create_tagged_logger(comment)
810
+ # throw the dice to decide the algorithm
811
+ random_number = random.randrange(100)
812
+
813
+ sloppy_ratio = self.getConfigValue("jobdispatch", "SLOPPY_DISPATCH_RATIO")
814
+ if not sloppy_ratio:
815
+ sloppy_ratio = 10
816
+
817
+ tmp_log.debug(f"random_number: {random_number} sloppy_ratio: {sloppy_ratio}")
818
+
819
+ if random_number <= sloppy_ratio:
820
+ # generate the age sorting
821
+ tmp_log.debug(f"sorting by age")
822
+ return self.getCriteriaByAge(site_name, max_jobs)
823
+ else:
824
+ # generate the global share sorting
825
+ tmp_log.debug(f"sorting by gshare")
826
+ return self.getCriteriaForGlobalShares(site_name, max_jobs)
827
+
828
+ # get selection criteria for share of production activities
829
+ def getCriteriaForGlobalShares(self, site_name, max_jobs):
830
+ comment = " /* DBProxy.getCriteriaForGlobalShare */"
831
+ tmp_log = self.create_tagged_logger(comment)
832
+ # return for no criteria
833
+ var_map = {}
834
+ ret_empty = "", {}
835
+
836
+ try:
837
+ # Get the share leaves sorted by order of under-pledging
838
+ tmp_log.debug(f"Going to call get sorted leaves")
839
+ t_before = time.time()
840
+ sorted_leaves = self.get_sorted_leaves()
841
+ t_after = time.time()
842
+ total = t_after - t_before
843
+ tmp_log.debug(f"Sorting leaves took {total}s")
844
+
845
+ i = 0
846
+ tmp_list = []
847
+ for leave in sorted_leaves:
848
+ var_map[f":leave{i}"] = leave.name
849
+ if leave.name == "Test":
850
+ # Test share will bypass others for the moment
851
+ tmp_list.append(f"WHEN gshare=:leave{i} THEN 0")
852
+ else:
853
+ tmp_list.append("WHEN gshare=:leave{0} THEN {0}".format(i))
854
+ i += 1
855
+
856
+ # Only get max_jobs, to avoid getting all activated jobs from the table
857
+ var_map[":njobs"] = max_jobs
858
+
859
+ # We want to sort by global share, highest priority and lowest pandaid
860
+ leave_bindings = " ".join(tmp_list)
861
+ ret_sql = f"""
862
+ ORDER BY (CASE {leave_bindings} ELSE {len(sorted_leaves)} END), currentpriority desc, pandaid asc)
863
+ WHERE ROWNUM <= :njobs
864
+ """
865
+
866
+ tmp_log.debug(f"ret_sql: {ret_sql}. var_map: {var_map}")
867
+ return ret_sql, var_map
868
+
869
+ except Exception:
870
+ # roll back
871
+ self._rollback()
872
+ self.dump_error_message(tmp_log)
873
+ return ret_empty
874
+
875
+ # get selection criteria for share of production activities
876
+ def getCriteriaByAge(self, site_name, max_jobs):
877
+ comment = " /* DBProxy.getCriteriaByAge */"
878
+ tmp_log = self.create_tagged_logger(comment)
879
+ # return for no criteria
880
+ ret_sql = ""
881
+ var_map = {}
882
+ ret_empty = "", {}
883
+
884
+ try:
885
+ # Only get max_jobs, to avoid getting all activated jobs from the table
886
+ var_map[":njobs"] = max_jobs
887
+
888
+ # We want to ignore global share and just take the oldest pandaid
889
+ ret_sql = """
890
+ ORDER BY pandaid asc)
891
+ WHERE ROWNUM <= :njobs
892
+ """
893
+
894
+ tmp_log.debug(f"ret_sql: {ret_sql}. var_map: {var_map}")
895
+ return ret_sql, var_map
896
+
897
+ except Exception:
898
+ # roll back
899
+ self._rollback()
900
+ self.dump_error_message(tmp_log)
901
+ return ret_empty
902
+
903
+ # set HS06sec
904
+ def setHS06sec(self, pandaID, inActive=False):
905
+ comment = " /* DBProxy.setHS06sec */"
906
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
907
+ tmp_log.debug("start")
908
+ hs06sec = None
909
+
910
+ # sql to get job attributes
911
+ sqlJ = "SELECT jediTaskID,startTime,endTime,actualCoreCount,coreCount,jobMetrics,computingSite "
912
+ if inActive:
913
+ sqlJ += "FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
914
+ else:
915
+ sqlJ += "FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
916
+
917
+ # sql to update HS06sec
918
+ if inActive:
919
+ sqlU = "UPDATE ATLAS_PANDA.jobsActive4 "
920
+ else:
921
+ sqlU = "UPDATE ATLAS_PANDA.jobsArchived4 "
922
+ sqlU += "SET hs06sec=:hs06sec WHERE PandaID=:PandaID "
923
+
924
+ # get job attributes
925
+ varMap = {}
926
+ varMap[":PandaID"] = pandaID
927
+ self.cur.execute(sqlJ + comment, varMap)
928
+ resJ = self.cur.fetchone()
929
+ if resJ is None:
930
+ tmp_log.debug("skip since job not found")
931
+ else:
932
+ (
933
+ jediTaskID,
934
+ startTime,
935
+ endTime,
936
+ actualCoreCount,
937
+ defCoreCount,
938
+ jobMetrics,
939
+ computingSite,
940
+ ) = resJ
941
+ # get corePower
942
+ corePower, tmpMsg = self.get_core_power(computingSite)
943
+ if corePower is None:
944
+ tmp_log.debug(f"skip since corePower is undefined for site={computingSite}")
945
+ else:
946
+ # get core count
947
+ coreCount = JobUtils.getCoreCount(actualCoreCount, defCoreCount, jobMetrics)
948
+ # get HS06sec
949
+ hs06sec = JobUtils.getHS06sec(startTime, endTime, corePower, coreCount)
950
+ if hs06sec is None:
951
+ tmp_log.debug("skip since HS06sec is None")
952
+ else:
953
+ # cap
954
+ hs06sec = int(hs06sec)
955
+ maxHS06sec = 999999999
956
+ if hs06sec > maxHS06sec:
957
+ hs06sec = maxHS06sec
958
+ # update HS06sec
959
+ varMap = {}
960
+ varMap[":PandaID"] = pandaID
961
+ varMap[":hs06sec"] = hs06sec
962
+ self.cur.execute(sqlU + comment, varMap)
963
+ tmp_log.debug(f"set HS06sec={hs06sec}")
964
+ # return
965
+ return hs06sec
966
+
967
+ def convert_computingsite_to_region(self, computing_site):
968
+ comment = " /* DBProxy.convert_computingsite_to_region */"
969
+
970
+ var_map = {":panda_queue": computing_site}
971
+ sql = "SELECT /* use_json_type */ scj.data.region FROM ATLAS_PANDA.schedconfig_json scj WHERE scj.panda_queue=:panda_queue"
972
+ self.cur.arraysize = 100
973
+ self.cur.execute(sql + comment, var_map)
974
+ res_region = self.cur.fetchone()
975
+ region = "GRID" # when region is not defined, take average values
976
+ if res_region:
977
+ region = res_region[0]
978
+
979
+ return region
980
+
981
+ def get_co2_emissions_site(self, computing_site):
982
+ comment = " /* DBProxy.get_co2_emissions_site */"
983
+ region = self.convert_computingsite_to_region(computing_site)
984
+ if not region:
985
+ return None
986
+
987
+ var_map = {":region": region}
988
+ sql = "SELECT timestamp, region, value FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS WHERE region=:region"
989
+ self.cur.execute(sql + comment, var_map)
990
+ results = self.cur.fetchall()
991
+ return results
992
+
993
+ def get_co2_emissions_grid(self):
994
+ comment = " /* DBProxy.get_co2_emissions_grid */"
995
+
996
+ sql = "SELECT timestamp, region, value FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS WHERE region='GRID'"
997
+ self.cur.execute(sql + comment)
998
+ results = self.cur.fetchall()
999
+ return results
1000
+
1001
+ # set CO2 emissions
1002
+ def set_co2_emissions(self, panda_id, in_active=False):
1003
+ comment = " /* DBProxy.set_co2_emissions */"
1004
+ tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
1005
+ tmp_log.debug("start")
1006
+ gco2_regional, gco2_global = None, None
1007
+
1008
+ # sql to get job attributes
1009
+ sql_read = "SELECT jediTaskID, startTime, endTime, actualCoreCount, coreCount, jobMetrics, computingSite "
1010
+ if in_active:
1011
+ sql_read += "FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
1012
+ else:
1013
+ sql_read += "FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
1014
+
1015
+ # sql to update CO2 emissions
1016
+ if in_active:
1017
+ sql_update = "UPDATE ATLAS_PANDA.jobsActive4 "
1018
+ else:
1019
+ sql_update = "UPDATE ATLAS_PANDA.jobsArchived4 "
1020
+ sql_update += "SET gCO2_global=:gco2_global, gCO2_regional=:gco2_regional WHERE PandaID=:PandaID "
1021
+
1022
+ # get job attributes
1023
+ var_map = {":PandaID": panda_id}
1024
+ self.cur.execute(sql_read + comment, var_map)
1025
+ res_read = self.cur.fetchone()
1026
+ if res_read is None:
1027
+ tmp_log.debug("skip since job not found")
1028
+ else:
1029
+ (
1030
+ task_id,
1031
+ start_time,
1032
+ end_time,
1033
+ actual_cores,
1034
+ defined_cores,
1035
+ job_metrics,
1036
+ computing_site,
1037
+ ) = res_read
1038
+
1039
+ # get core count
1040
+ core_count = JobUtils.getCoreCount(actual_cores, defined_cores, job_metrics)
1041
+
1042
+ # get the queues watts per core value
1043
+ var_map = {":panda_queue": computing_site}
1044
+ sql_wpc = "SELECT /* use_json_type */ scj.data.coreenergy FROM ATLAS_PANDA.schedconfig_json scj WHERE scj.panda_queue=:panda_queue"
1045
+ self.cur.arraysize = 100
1046
+ self.cur.execute(sql_wpc + comment, var_map)
1047
+ res_wpc = self.cur.fetchone()
1048
+ try:
1049
+ watts_per_core = float(res_wpc[0])
1050
+ except Exception:
1051
+ watts_per_core = 10
1052
+ tmp_log.debug(f"using watts_per_core={watts_per_core} for computing_site={computing_site}")
1053
+
1054
+ # get regional CO2 emissions
1055
+ co2_emissions = self.get_co2_emissions_site(computing_site)
1056
+ if not co2_emissions:
1057
+ tmp_log.debug(f"skip since co2_emissions are undefined for site={computing_site}")
1058
+ else:
1059
+ # get emitted CO2 for the job
1060
+ gco2_regional = JobUtils.get_job_co2(start_time, end_time, core_count, co2_emissions, watts_per_core)
1061
+ if gco2_regional is None:
1062
+ tmp_log.debug("skip since the co2 emissions could not be calculated")
1063
+ else:
1064
+ max_gco2 = 999999999
1065
+ gco2_regional = min(gco2_regional, max_gco2)
1066
+ tmp_log.debug(f"set gco2_regional={gco2_regional}")
1067
+
1068
+ # get globally averaged CO2 emissions
1069
+ co2_emissions = self.get_co2_emissions_grid()
1070
+ if not co2_emissions:
1071
+ tmp_log.debug("skip since co2_emissions are undefined for the grid")
1072
+ else:
1073
+ # get emitted CO2 for the job
1074
+ gco2_global = JobUtils.get_job_co2(start_time, end_time, core_count, co2_emissions, watts_per_core)
1075
+ if gco2_global is None:
1076
+ tmp_log.debug("skip since the co2 emissions could not be calculated")
1077
+ else:
1078
+ max_gco2 = 999999999
1079
+ gco2_global = min(gco2_global, max_gco2)
1080
+
1081
+ tmp_log.debug(f"set gco2_global={gco2_global}")
1082
+
1083
+ var_map = {
1084
+ ":PandaID": panda_id,
1085
+ ":gco2_regional": gco2_regional,
1086
+ ":gco2_global": gco2_global,
1087
+ }
1088
+ self.cur.execute(sql_update + comment, var_map)
1089
+
1090
+ tmp_log.debug("done")
1091
+ # return
1092
+ return gco2_regional, gco2_global
1093
+
1094
+ # get core power
1095
+ @memoize
1096
+ def get_core_power(self, site_id):
1097
+ comment = " /* DBProxy.get_core_power */"
1098
+ tmp_log = self.create_tagged_logger(comment, f"siteid={site_id}")
1099
+ tmp_log.debug("start")
1100
+
1101
+ sqlS = "SELECT /* use_json_type */ scj.data.corepower FROM ATLAS_PANDA.schedconfig_json scj "
1102
+ sqlS += "WHERE panda_queue=:siteid "
1103
+
1104
+ varMap = {":siteid": site_id}
1105
+
1106
+ try:
1107
+ self.cur.arraysize = 100
1108
+ self.cur.execute(sqlS + comment, varMap)
1109
+ resS = self.cur.fetchone()
1110
+ core_power = None
1111
+ if resS is not None:
1112
+ (core_power,) = resS
1113
+ core_power = float(core_power)
1114
+ tmp_log.debug(f"got {core_power}")
1115
+ return core_power, None
1116
+
1117
+ except Exception:
1118
+ # error
1119
+ self.dump_error_message(tmp_log)
1120
+ return None, "failed to get corePower"
1121
+
1122
+ # convert ObjID to endpoint
1123
+ @memoize
1124
+ def convertObjIDtoEndPoint(self, srcFileName, objID):
1125
+ comment = " /* DBProxy.convertObjIDtoEndPoint */"
1126
+ tmp_log = self.create_tagged_logger(comment, f"ID={objID}")
1127
+ tmp_log.debug("start")
1128
+ try:
1129
+ for srcFile in srcFileName.split(","):
1130
+ if not os.path.exists(srcFile):
1131
+ continue
1132
+ with open(srcFile) as f:
1133
+ data = json.load(f)
1134
+ for rseName in data:
1135
+ rseData = data[rseName]
1136
+ if objID in [rseData["id"], rseName]:
1137
+ retMap = {
1138
+ "name": rseName,
1139
+ "is_deterministic": rseData["is_deterministic"],
1140
+ "type": rseData["type"],
1141
+ }
1142
+ tmp_log.debug(f"got {str(retMap)}")
1143
+ return retMap
1144
+ tmp_log.debug("not found")
1145
+ except Exception:
1146
+ # error
1147
+ self.dump_error_message(tmp_log)
1148
+ return None
1149
+
1150
+ def get_config_for_pq(self, pq_name):
1151
+ """
1152
+ Get the CRIC json configuration for a particular queue
1153
+ """
1154
+
1155
+ comment = " /* DBProxy.get_config_for_pq */"
1156
+ tmp_log = self.create_tagged_logger(comment, pq_name)
1157
+ tmp_log.debug("start")
1158
+
1159
+ var_map = {":pq": pq_name}
1160
+ sql_get_queue_config = """
1161
+ SELECT data FROM ATLAS_PANDA.SCHEDCONFIG_JSON
1162
+ WHERE panda_queue = :pq
1163
+ """
1164
+ tmp_v, pq_data = self.getClobObj(sql_get_queue_config + comment, var_map)
1165
+ if pq_data is None:
1166
+ tmp_log.error("Could not find queue configuration")
1167
+ return None
1168
+
1169
+ try:
1170
+ pq_data_des = pq_data[0][0]
1171
+ if not isinstance(pq_data_des, dict):
1172
+ pq_data_des = json.loads(pq_data_des)
1173
+ except Exception:
1174
+ tmp_log.error("Could not find queue configuration")
1175
+ return None
1176
+
1177
+ tmp_log.debug("done")
1178
+ return pq_data_des
1179
+
1180
+ def getQueuesInJSONSchedconfig(self):
1181
+ comment = " /* DBProxy.getQueuesInJSONSchedconfig */"
1182
+ tmp_log = self.create_tagged_logger(comment)
1183
+ tmp_log.debug("start")
1184
+ try:
1185
+ # sql to get workers
1186
+ sqlC = "SELECT /* use_json_type */ panda_queue FROM ATLAS_PANDA.schedconfig_json"
1187
+ # start transaction
1188
+ self.conn.begin()
1189
+ self.cur.execute(sqlC + comment)
1190
+ panda_queues = [row[0] for row in self.cur.fetchall()]
1191
+ # commit
1192
+ if not self._commit():
1193
+ raise RuntimeError("Commit error")
1194
+ tmp_log.debug(f"got {len(panda_queues)} queues")
1195
+ return panda_queues
1196
+ except Exception:
1197
+ # roll back
1198
+ self._rollback()
1199
+ # error
1200
+ self.dump_error_message(tmp_log)
1201
+ return None
1202
+
1203
+ # update queues
1204
+ def upsertQueuesInJSONSchedconfig(self, schedconfig_dump):
1205
+ comment = " /* DBProxy.upsertQueuesInJSONSchedconfig */"
1206
+ tmp_log = self.create_tagged_logger(comment)
1207
+ tmp_log.debug("start")
1208
+
1209
+ if not schedconfig_dump:
1210
+ tmp_log.error("empty schedconfig dump")
1211
+ return "ERROR"
1212
+
1213
+ try:
1214
+ existing_queues = self.getQueuesInJSONSchedconfig()
1215
+ if existing_queues is None:
1216
+ tmp_log.error("Could not retrieve already existing queues")
1217
+ return None
1218
+
1219
+ # separate the queues to the ones we have to update (existing) and the ones we have to insert (new)
1220
+ var_map_insert = []
1221
+ var_map_update = []
1222
+ utc_now = naive_utcnow()
1223
+ for pq in schedconfig_dump:
1224
+ data = json.dumps(schedconfig_dump[pq])
1225
+ if not data:
1226
+ tmp_log.error(f"no data for {pq}")
1227
+ continue
1228
+
1229
+ if pq in existing_queues:
1230
+ tmp_log.debug(f"pq {pq} present")
1231
+ var_map_update.append({":pq": pq, ":data": data, ":last_update": utc_now})
1232
+ else:
1233
+ tmp_log.debug(f"pq {pq} is new")
1234
+ var_map_insert.append({":pq": pq, ":data": data, ":last_update": utc_now})
1235
+
1236
+ # start transaction
1237
+ self.conn.begin()
1238
+
1239
+ # run the updates
1240
+ if var_map_update:
1241
+ sql_update = """
1242
+ UPDATE ATLAS_PANDA.SCHEDCONFIG_JSON SET data = :data, last_update = :last_update
1243
+ WHERE panda_queue = :pq
1244
+ """
1245
+ tmp_log.debug("start updates")
1246
+ self.cur.executemany(sql_update + comment, var_map_update)
1247
+ tmp_log.debug("finished updates")
1248
+
1249
+ # run the inserts
1250
+ if var_map_insert:
1251
+ sql_insert = """
1252
+ INSERT INTO ATLAS_PANDA.SCHEDCONFIG_JSON (panda_queue, data, last_update)
1253
+ VALUES (:pq, :data, :last_update)
1254
+ """
1255
+ tmp_log.debug("start inserts")
1256
+ self.cur.executemany(sql_insert + comment, var_map_insert)
1257
+ tmp_log.debug("finished inserts")
1258
+
1259
+ # delete inactive queues
1260
+ tmp_log.debug("Going to delete obsoleted queues")
1261
+ sql_delete = """
1262
+ DELETE FROM ATLAS_PANDA.SCHEDCONFIG_JSON WHERE last_update < current_date - INTERVAL '7' DAY
1263
+ """
1264
+ self.cur.execute(sql_delete + comment)
1265
+ tmp_log.debug("deleted old queues")
1266
+
1267
+ if not self._commit():
1268
+ raise RuntimeError("Commit error")
1269
+
1270
+ tmp_log.debug("done")
1271
+ return "OK"
1272
+
1273
+ except Exception:
1274
+ # roll back
1275
+ self._rollback()
1276
+ self.dump_error_message(tmp_log)
1277
+ return "ERROR"
1278
+
1279
+ # update queues
1280
+ def loadSWTags(self, sw_tags):
1281
+ comment = " /* DBProxy.loadSWTags */"
1282
+ tmp_log = self.create_tagged_logger(comment)
1283
+ tmp_log.debug("start")
1284
+
1285
+ if not sw_tags:
1286
+ tmp_log.error("empty sw tag dump")
1287
+ return "ERROR"
1288
+
1289
+ try:
1290
+ var_map_tags = []
1291
+
1292
+ utc_now = naive_utcnow()
1293
+ for pq in sw_tags:
1294
+ data = sw_tags[pq]
1295
+ var_map_tags.append({":pq": pq, ":data": json.dumps(data), ":last_update": utc_now})
1296
+
1297
+ # start transaction on SW_TAGS table
1298
+ # delete everything in the table to start every time from a clean table
1299
+ # cleaning and filling needs to be done within the same transaction
1300
+ self.conn.begin()
1301
+
1302
+ sql_delete = "DELETE FROM ATLAS_PANDA.SW_TAGS"
1303
+ tmp_log.debug("start cleaning up SW_TAGS table")
1304
+ self.cur.execute(sql_delete + comment)
1305
+ tmp_log.debug("done cleaning up SW_TAGS table")
1306
+
1307
+ sql_insert = "INSERT INTO ATLAS_PANDA.SW_TAGS (panda_queue, data, last_update) VALUES (:pq, :data, :last_update)"
1308
+ tmp_log.debug("start filling up SW_TAGS table")
1309
+ for shard in create_shards(var_map_tags, 100): # insert in batches of 100 rows
1310
+ self.cur.executemany(sql_insert + comment, shard)
1311
+ tmp_log.debug("done filling up table")
1312
+ if not self._commit():
1313
+ raise RuntimeError("Commit error")
1314
+
1315
+ tmp_log.debug("done")
1316
+ return "OK"
1317
+
1318
+ except Exception:
1319
+ # roll back
1320
+ self._rollback()
1321
+ self.dump_error_message(tmp_log)
1322
+ return "ERROR"
1323
+
1324
+ # get working group with production role
1325
+ def getWorkingGroup(self, fqans):
1326
+ for fqan in fqans:
1327
+ # check production role
1328
+ match = re.search("/[^/]+/([^/]+)/Role=production", fqan)
1329
+ if match is not None:
1330
+ return match.group(1)
1331
+ return None
1332
+
1333
+ # update site data
1334
+ def updateSiteData(self, hostID, pilotRequests, interval):
1335
+ comment = " /* DBProxy.updateSiteData */"
1336
+ tmp_log = self.create_tagged_logger(comment)
1337
+ tmp_log.debug("start")
1338
+
1339
+ sqlDel = "DELETE FROM ATLAS_PANDAMETA.SiteData WHERE LASTMOD < :LASTMOD"
1340
+
1341
+ sqlRst = (
1342
+ "UPDATE ATLAS_PANDAMETA.SiteData "
1343
+ "SET GETJOB = :GETJOB, UPDATEJOB = :UPDATEJOB, NOJOB = :NOJOB, "
1344
+ "GETJOBABS = :GETJOBABS, UPDATEJOBABS = :UPDATEJOBABS, NOJOBABS = :NOJOBABS "
1345
+ "WHERE HOURS = :HOURS AND LASTMOD < :LASTMOD"
1346
+ )
1347
+
1348
+ sqlCh = "SELECT * FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE FOR UPDATE NOWAIT "
1349
+
1350
+ sqlIn = (
1351
+ "INSERT INTO ATLAS_PANDAMETA.SiteData "
1352
+ "(SITE, FLAG, HOURS, GETJOB, UPDATEJOB, NOJOB, GETJOBABS, UPDATEJOBABS, NOJOBABS, "
1353
+ "LASTMOD, NSTART, FINISHED, FAILED, DEFINED, ASSIGNED, WAITING, ACTIVATED, HOLDING, RUNNING, TRANSFERRING) "
1354
+ "VALUES (:SITE, :FLAG, :HOURS, :GETJOB, :UPDATEJOB, :NOJOB, :GETJOBABS, :UPDATEJOBABS, :NOJOBABS, CURRENT_DATE, "
1355
+ "0, 0, 0, 0, 0, 0, 0, 0, 0, 0)"
1356
+ )
1357
+
1358
+ sqlUp = (
1359
+ "UPDATE ATLAS_PANDAMETA.SiteData "
1360
+ "SET GETJOB = :GETJOB, UPDATEJOB = :UPDATEJOB, NOJOB = :NOJOB, "
1361
+ "GETJOBABS = :GETJOBABS, UPDATEJOBABS = :UPDATEJOBABS, NOJOBABS = :NOJOBABS, LASTMOD = CURRENT_DATE "
1362
+ "WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
1363
+ )
1364
+
1365
+ sqlAll = "SELECT GETJOB, UPDATEJOB, NOJOB, GETJOBABS, UPDATEJOBABS, NOJOBABS, FLAG FROM ATLAS_PANDAMETA.SiteData WHERE HOURS = :HOURS AND SITE = :SITE"
1366
+
1367
+ try:
1368
+ timeNow = naive_utcnow()
1369
+ self.conn.begin()
1370
+ # delete old records
1371
+ varMap = {}
1372
+ varMap[":LASTMOD"] = timeNow - datetime.timedelta(hours=48)
1373
+ self.cur.execute(sqlDel + comment, varMap)
1374
+ # set 0 to old records
1375
+ varMap = {}
1376
+ varMap[":HOURS"] = interval
1377
+ varMap[":GETJOB"] = 0
1378
+ varMap[":UPDATEJOB"] = 0
1379
+ varMap[":NOJOB"] = 0
1380
+ varMap[":GETJOBABS"] = 0
1381
+ varMap[":UPDATEJOBABS"] = 0
1382
+ varMap[":NOJOBABS"] = 0
1383
+ varMap[":LASTMOD"] = timeNow - datetime.timedelta(hours=interval)
1384
+ self.cur.execute(sqlRst + comment, varMap)
1385
+ # commit
1386
+ if not self._commit():
1387
+ raise RuntimeError("Commit error")
1388
+ # shuffle to avoid concatenation
1389
+ tmpSiteList = list(pilotRequests)
1390
+ random.shuffle(tmpSiteList)
1391
+ # loop over all sites
1392
+ for tmpSite in tmpSiteList:
1393
+ tmpVal = pilotRequests[tmpSite]
1394
+ # start transaction
1395
+ self.conn.begin()
1396
+ # check individual host info first
1397
+ varMap = {}
1398
+ varMap[":FLAG"] = hostID
1399
+ varMap[":SITE"] = tmpSite
1400
+ varMap[":HOURS"] = interval
1401
+ self.cur.arraysize = 10
1402
+ locked = True
1403
+ try:
1404
+ # lock individual row
1405
+ self.cur.execute(sqlCh + comment, varMap)
1406
+ except Exception as e:
1407
+ # skip since it is being locked by another
1408
+ tmp_log.debug(f"skip to update {str(varMap)} due to {str(e)}")
1409
+ locked = False
1410
+ if locked:
1411
+ res = self.cur.fetchone()
1412
+ # row exists or not
1413
+ if res is None:
1414
+ sql = sqlIn
1415
+ else:
1416
+ sql = sqlUp
1417
+
1418
+ # getJob, updateJob and noJob entries contain the number of slots/nodes that submitted the request
1419
+ # getJobAbs, updateJobAbs and noJobAbs entries contain the absolute number of requests
1420
+ if "getJob" in tmpVal:
1421
+ varMap[":GETJOB"] = len(tmpVal["getJob"])
1422
+ getJobAbs = 0
1423
+ for node in tmpVal["getJob"]:
1424
+ getJobAbs += tmpVal["getJob"][node]
1425
+ varMap[":GETJOBABS"] = getJobAbs
1426
+ else:
1427
+ varMap[":GETJOB"] = 0
1428
+ varMap[":GETJOBABS"] = 0
1429
+
1430
+ if "updateJob" in tmpVal:
1431
+ varMap[":UPDATEJOB"] = len(tmpVal["updateJob"])
1432
+ updateJobAbs = 0
1433
+ for node in tmpVal["updateJob"]:
1434
+ updateJobAbs += tmpVal["updateJob"][node]
1435
+ varMap[":UPDATEJOBABS"] = updateJobAbs
1436
+ else:
1437
+ varMap[":UPDATEJOB"] = 0
1438
+ varMap[":UPDATEJOBABS"] = 0
1439
+
1440
+ if "noJob" in tmpVal:
1441
+ varMap[":NOJOB"] = len(tmpVal["noJob"])
1442
+ noJobAbs = 0
1443
+ for node in tmpVal["noJob"]:
1444
+ noJobAbs += tmpVal["noJob"][node]
1445
+ varMap[":NOJOBABS"] = noJobAbs
1446
+ else:
1447
+ varMap[":NOJOB"] = 0
1448
+ varMap[":NOJOBABS"] = 0
1449
+
1450
+ # update
1451
+ self.cur.execute(sql + comment, varMap)
1452
+
1453
+ # commit
1454
+ if not self._commit():
1455
+ raise RuntimeError("Commit error")
1456
+
1457
+ if locked:
1458
+ # start transaction
1459
+ self.conn.begin()
1460
+ # get all info
1461
+ sumExist = False
1462
+ varMap = {}
1463
+ varMap[":SITE"] = tmpSite
1464
+ varMap[":HOURS"] = interval
1465
+ self.cur.arraysize = 100
1466
+ self.cur.execute(sqlAll + comment, varMap)
1467
+ res = self.cur.fetchall()
1468
+ # get total getJob/updateJob
1469
+ varMap[":GETJOB"] = 0
1470
+ varMap[":UPDATEJOB"] = 0
1471
+ varMap[":NOJOB"] = 0
1472
+ varMap[":GETJOBABS"] = 0
1473
+ varMap[":UPDATEJOBABS"] = 0
1474
+ varMap[":NOJOBABS"] = 0
1475
+ nCol = 0
1476
+ for (
1477
+ tmpGetJob,
1478
+ tmpUpdateJob,
1479
+ tmpNoJob,
1480
+ tmpGetJobAbs,
1481
+ tmpUpdateJobAbs,
1482
+ tmpNoJobAbs,
1483
+ tmpFlag,
1484
+ ) in res:
1485
+ # don't use summed info
1486
+ if tmpFlag == "production":
1487
+ sumExist = True
1488
+ continue
1489
+ if tmpFlag == "analysis":
1490
+ if tmpSite.startswith("ANALY_"):
1491
+ sumExist = True
1492
+ continue
1493
+ if tmpFlag in ["test"]:
1494
+ continue
1495
+
1496
+ if tmpGetJob is None:
1497
+ tmpGetJob = 0
1498
+ if tmpUpdateJob is None:
1499
+ tmpUpdateJob = 0
1500
+ if tmpNoJob is None:
1501
+ tmpNoJob = 0
1502
+ if tmpGetJobAbs is None:
1503
+ tmpGetJobAbs = 0
1504
+ if tmpUpdateJobAbs is None:
1505
+ tmpUpdateJobAbs = 0
1506
+ if tmpNoJobAbs is None:
1507
+ tmpNoJobAbs = 0
1508
+
1509
+ # sum
1510
+ varMap[":GETJOB"] += tmpGetJob
1511
+ varMap[":UPDATEJOB"] += tmpUpdateJob
1512
+ varMap[":NOJOB"] += tmpNoJob
1513
+ varMap[":GETJOBABS"] += tmpGetJobAbs
1514
+ varMap[":UPDATEJOBABS"] += tmpUpdateJobAbs
1515
+ varMap[":NOJOBABS"] += tmpNoJobAbs
1516
+ nCol += 1
1517
+ # get average
1518
+ if nCol != 0:
1519
+ if varMap[":GETJOB"] >= nCol:
1520
+ varMap[":GETJOB"] /= nCol
1521
+ if varMap[":UPDATEJOB"] >= nCol:
1522
+ varMap[":UPDATEJOB"] /= nCol
1523
+ if varMap[":NOJOB"] >= nCol:
1524
+ varMap[":NOJOB"] /= nCol
1525
+ if varMap[":GETJOBABS"] >= nCol:
1526
+ varMap[":GETJOBABS"] /= nCol
1527
+ if varMap[":UPDATEJOBABS"] >= nCol:
1528
+ varMap[":UPDATEJOBABS"] /= nCol
1529
+ if varMap[":NOJOBABS"] >= nCol:
1530
+ varMap[":NOJOBABS"] /= nCol
1531
+
1532
+ if tmpSite.startswith("ANALY_"):
1533
+ varMap[":FLAG"] = "analysis"
1534
+ else:
1535
+ varMap[":FLAG"] = "production"
1536
+ # row exists or not
1537
+ locked_sum = True
1538
+ if sumExist:
1539
+ sql = sqlUp
1540
+ else:
1541
+ sql = sqlIn
1542
+ # lock the summary row
1543
+ var_map = {k: varMap[k] for k in [":FLAG", ":SITE", ":HOURS"]}
1544
+ try:
1545
+ # lock it
1546
+ self.cur.execute(sqlCh + comment, var_map)
1547
+ except Exception as e:
1548
+ # skip since it is being locked by another
1549
+ tmp_log.debug(f"skip to update {str(var_map)} due to {str(e)}")
1550
+ locked_sum = False
1551
+ # update
1552
+ if locked_sum:
1553
+ self.cur.execute(sql + comment, varMap)
1554
+ tmp_log.debug(
1555
+ " %s hours=%s getJob=%s updateJob=%s, noJob=%s, getJobAbs=%s updateJobAbs=%s, noJobAbs=%s"
1556
+ % (
1557
+ tmpSite,
1558
+ interval,
1559
+ varMap[":GETJOB"],
1560
+ varMap[":UPDATEJOB"],
1561
+ varMap[":NOJOB"],
1562
+ varMap[":GETJOBABS"],
1563
+ varMap[":UPDATEJOBABS"],
1564
+ varMap[":NOJOBABS"],
1565
+ )
1566
+ )
1567
+ # commit
1568
+ if not self._commit():
1569
+ raise RuntimeError("Commit error")
1570
+ tmp_log.debug("done")
1571
+ return True
1572
+ except Exception:
1573
+ # roll back
1574
+ self._rollback()
1575
+ self.dump_error_message(tmp_log)
1576
+ return False
1577
+
1578
+ # get site data
1579
+ def getCurrentSiteData(self):
1580
+ comment = " /* DBProxy.getCurrentSiteData */"
1581
+ tmp_log = self.create_tagged_logger(comment)
1582
+ sql = "SELECT SITE,getJob,updateJob,FLAG FROM ATLAS_PANDAMETA.SiteData WHERE FLAG IN (:FLAG1,:FLAG2) and HOURS=3"
1583
+ varMap = {}
1584
+ varMap[":FLAG1"] = "production"
1585
+ varMap[":FLAG2"] = "analysis"
1586
+ try:
1587
+ # set autocommit on
1588
+ self.conn.begin()
1589
+ # select
1590
+ self.cur.arraysize = 10000
1591
+ self.cur.execute(sql + comment, varMap)
1592
+ res = self.cur.fetchall()
1593
+ # commit
1594
+ if not self._commit():
1595
+ raise RuntimeError("Commit error")
1596
+ ret = {}
1597
+ for site, getJob, updateJob, flag in res:
1598
+ if site.startswith("ANALY_"):
1599
+ if flag != "analysis":
1600
+ continue
1601
+ else:
1602
+ if flag != "production":
1603
+ continue
1604
+ ret[site] = {"getJob": getJob, "updateJob": updateJob}
1605
+ return ret
1606
+ except Exception:
1607
+ self.dump_error_message(tmp_log)
1608
+ # roll back
1609
+ self._rollback()
1610
+ return {}
1611
+
1612
+ # insert nRunning in site data
1613
+ def insertnRunningInSiteData(self):
1614
+ comment = " /* DBProxy.insertnRunningInSiteData */"
1615
+ tmp_log = self.create_tagged_logger(comment)
1616
+ tmp_log.debug("start")
1617
+
1618
+ sqlDel = "DELETE FROM ATLAS_PANDAMETA.SiteData WHERE FLAG IN (:FLAG1, :FLAG2) AND LASTMOD < CURRENT_DATE - 1"
1619
+
1620
+ sqlRun = (
1621
+ "SELECT COUNT(*), computingSite "
1622
+ "FROM ATLAS_PANDA.jobsActive4 "
1623
+ "WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) "
1624
+ "AND jobStatus = :jobStatus "
1625
+ "GROUP BY computingSite"
1626
+ )
1627
+
1628
+ sqlCh = "SELECT COUNT(*) FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
1629
+
1630
+ sqlIn = (
1631
+ "INSERT INTO ATLAS_PANDAMETA.SiteData "
1632
+ "(SITE, FLAG, HOURS, GETJOB, UPDATEJOB, LASTMOD, "
1633
+ "NSTART, FINISHED, FAILED, DEFINED, ASSIGNED, WAITING, "
1634
+ "ACTIVATED, HOLDING, RUNNING, TRANSFERRING) "
1635
+ "VALUES (:SITE, :FLAG, :HOURS, 0, 0, CURRENT_DATE, "
1636
+ "0, 0, 0, 0, 0, 0, 0, 0, :RUNNING, 0)"
1637
+ )
1638
+
1639
+ sqlUp = "UPDATE ATLAS_PANDAMETA.SiteData SET RUNNING = :RUNNING, LASTMOD = CURRENT_DATE WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
1640
+
1641
+ sqlMax = "SELECT SITE, MAX(RUNNING) FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG GROUP BY SITE"
1642
+
1643
+ try:
1644
+ # use offset(1000)+minutes for :HOURS
1645
+ timeNow = naive_utcnow()
1646
+ nHours = 1000 + timeNow.hour * 60 + timeNow.minute
1647
+ # delete old records
1648
+ varMap = {}
1649
+ varMap[":FLAG1"] = "max"
1650
+ varMap[":FLAG2"] = "snapshot"
1651
+ self.conn.begin()
1652
+ self.cur.execute(sqlDel + comment, varMap)
1653
+ # commit
1654
+ if not self._commit():
1655
+ raise RuntimeError("Commit error")
1656
+ # get nRunning
1657
+ varMap = {}
1658
+ varMap[":jobStatus"] = "running"
1659
+ varMap[":prodSourceLabel1"] = "user"
1660
+ varMap[":prodSourceLabel2"] = "panda"
1661
+ self.conn.begin()
1662
+ self.cur.arraysize = 10000
1663
+ self.cur.execute(sqlRun + comment, varMap)
1664
+ res = self.cur.fetchall()
1665
+ # commit
1666
+ if not self._commit():
1667
+ raise RuntimeError("Commit error")
1668
+ # loop over all sites
1669
+ for nRunning, computingSite in res:
1670
+ # only ANALY_ sites
1671
+ if not computingSite.startswith("ANALY_"):
1672
+ continue
1673
+ # check if the row is already there
1674
+ varMap = {}
1675
+ varMap[":FLAG"] = "snapshot"
1676
+ varMap[":SITE"] = computingSite
1677
+ varMap[":HOURS"] = nHours
1678
+ # start transaction
1679
+ self.conn.begin()
1680
+ self.cur.arraysize = 10
1681
+ self.cur.execute(sqlCh + comment, varMap)
1682
+ res = self.cur.fetchone()
1683
+ # row exists or not
1684
+ if res[0] == 0:
1685
+ sql = sqlIn
1686
+ else:
1687
+ sql = sqlUp
1688
+ # set current nRunning
1689
+ varMap = {}
1690
+ varMap[":FLAG"] = "snapshot"
1691
+ varMap[":SITE"] = computingSite
1692
+ varMap[":HOURS"] = nHours
1693
+ varMap[":RUNNING"] = nRunning
1694
+ # insert or update
1695
+ self.cur.execute(sql + comment, varMap)
1696
+ # commit
1697
+ if not self._commit():
1698
+ raise RuntimeError("Commit error")
1699
+ # get max nRunning
1700
+ varMap = {}
1701
+ varMap[":FLAG"] = "snapshot"
1702
+ self.conn.begin()
1703
+ self.cur.arraysize = 10000
1704
+ self.cur.execute(sqlMax + comment, varMap)
1705
+ res = self.cur.fetchall()
1706
+ # commit
1707
+ if not self._commit():
1708
+ raise RuntimeError("Commit error")
1709
+ # loop over all sites
1710
+ for computingSite, maxnRunning in res:
1711
+ # start transaction
1712
+ self.conn.begin()
1713
+ # check if the row is already there
1714
+ varMap = {}
1715
+ varMap[":FLAG"] = "max"
1716
+ varMap[":SITE"] = computingSite
1717
+ varMap[":HOURS"] = 0
1718
+ self.cur.arraysize = 10
1719
+ self.cur.execute(sqlCh + comment, varMap)
1720
+ res = self.cur.fetchone()
1721
+ # row exists or not
1722
+ if res[0] == 0:
1723
+ sql = sqlIn
1724
+ else:
1725
+ sql = sqlUp
1726
+ # set max nRunning
1727
+ varMap = {}
1728
+ varMap[":FLAG"] = "max"
1729
+ varMap[":SITE"] = computingSite
1730
+ varMap[":HOURS"] = 0
1731
+ varMap[":RUNNING"] = maxnRunning
1732
+ self.cur.execute(sql + comment, varMap)
1733
+ # commit
1734
+ if not self._commit():
1735
+ raise RuntimeError("Commit error")
1736
+ tmp_log.debug("done")
1737
+ return True
1738
+ except Exception:
1739
+ # roll back
1740
+ self._rollback()
1741
+ self.dump_error_message(tmp_log)
1742
+ return False
1743
+
1744
+ # get site info
1745
+ def getSiteInfo(self):
1746
+ comment = " /* DBProxy.getSiteInfo */"
1747
+ tmp_log = self.create_tagged_logger(comment)
1748
+ tmp_log.debug("start")
1749
+ try:
1750
+ # get DDM endpoints
1751
+ pandaEndpointMap, endpoint_detailed_status_summary = self.getDdmEndpoints()
1752
+
1753
+ # sql to get site spec
1754
+ sql = """
1755
+ SELECT /* use_json_type */ panda_queue, data, b.site_name, c.role
1756
+ FROM (ATLAS_PANDA.schedconfig_json a
1757
+ LEFT JOIN ATLAS_PANDA.panda_site b ON a.panda_queue = b.panda_site_name)
1758
+ LEFT JOIN ATLAS_PANDA.site c ON b.site_name = c.site_name
1759
+ WHERE panda_queue IS NOT NULL
1760
+ """
1761
+ self.cur.arraysize = 10000
1762
+ # self.cur.execute(sql+comment)
1763
+ # resList = self.cur.fetchall()
1764
+ ret, resList = self.getClobObj(sql, {})
1765
+ if not resList:
1766
+ tmp_log.error("Empty site list!")
1767
+
1768
+ # set autocommit on
1769
+ self.conn.begin()
1770
+ # sql to get num slots
1771
+ sqlSL = "SELECT pandaQueueName, gshare, resourcetype, numslots FROM ATLAS_PANDA.Harvester_Slots "
1772
+ sqlSL += "WHERE (expirationTime IS NULL OR expirationTime>CURRENT_DATE) "
1773
+
1774
+ num_slots_by_site = {}
1775
+ self.cur.execute(sqlSL + comment)
1776
+ resSL = self.cur.fetchall()
1777
+
1778
+ for sl_queuename, sl_gshare, sl_resourcetype, sl_numslots in resSL:
1779
+ if sl_numslots < 0:
1780
+ continue
1781
+ num_slots_by_site.setdefault(sl_queuename, {})
1782
+ num_slots_by_site[sl_queuename].setdefault(sl_gshare, {})
1783
+ num_slots_by_site[sl_queuename][sl_gshare][sl_resourcetype] = sl_numslots
1784
+
1785
+ retList = {}
1786
+ if resList is not None:
1787
+ # loop over all results
1788
+ for res in resList:
1789
+ try: # don't let a problem with one queue break the whole map
1790
+ # change None to ''
1791
+ resTmp = []
1792
+ for tmpItem in res:
1793
+ if tmpItem is None:
1794
+ tmpItem = ""
1795
+ resTmp.append(tmpItem)
1796
+
1797
+ siteid, queue_data_json, pandasite, role = resTmp
1798
+ try:
1799
+ if isinstance(queue_data_json, dict):
1800
+ queue_data = queue_data_json
1801
+ else:
1802
+ queue_data = json.loads(queue_data_json)
1803
+ except Exception:
1804
+ tmp_log.error(f"loading json for queue {siteid} excepted. json was: {queue_data_json}")
1805
+ continue
1806
+
1807
+ # skip invalid siteid
1808
+ if siteid in [None, "", "ALL"] or not queue_data:
1809
+ if siteid != "ALL": # skip noisy error message for ALL
1810
+ tmp_log.error(f"siteid {siteid} had no queue_data {queue_data}")
1811
+ continue
1812
+
1813
+ tmp_log.debug(f"processing queue {siteid}")
1814
+
1815
+ # instantiate SiteSpec
1816
+ ret = SiteSpec.SiteSpec()
1817
+ ret.sitename = siteid
1818
+ ret.pandasite = pandasite
1819
+ ret.role = role
1820
+
1821
+ ret.type = queue_data.get("type", "production")
1822
+ ret.nickname = queue_data.get("nickname")
1823
+ try:
1824
+ ret.ddm = queue_data.get("ddm", "").split(",")[0]
1825
+ except AttributeError:
1826
+ ret.ddm = ""
1827
+ try:
1828
+ ret.cloud = queue_data.get("cloud", "").split(",")[0]
1829
+ except AttributeError:
1830
+ ret.cloud = ""
1831
+ ret.memory = queue_data.get("memory")
1832
+ ret.maxrss = queue_data.get("maxrss")
1833
+ ret.minrss = queue_data.get("minrss")
1834
+ ret.maxtime = queue_data.get("maxtime")
1835
+ ret.status = queue_data.get("status")
1836
+ ret.space = queue_data.get("space")
1837
+ ret.maxinputsize = queue_data.get("maxinputsize")
1838
+ ret.comment = queue_data.get("comment_")
1839
+ ret.statusmodtime = queue_data.get("lastmod")
1840
+ ret.catchall = queue_data.get("catchall")
1841
+ ret.tier = queue_data.get("tier")
1842
+ ret.jobseed = queue_data.get("jobseed")
1843
+ ret.capability = queue_data.get("capability")
1844
+ ret.workflow = queue_data.get("workflow")
1845
+ ret.maxDiskio = queue_data.get("maxdiskio")
1846
+ ret.pandasite_state = "ACTIVE"
1847
+ ret.fairsharePolicy = queue_data.get("fairsharepolicy")
1848
+ ret.defaulttoken = queue_data.get("defaulttoken")
1849
+
1850
+ ret.direct_access_lan = queue_data.get("direct_access_lan") is True
1851
+ ret.direct_access_wan = queue_data.get("direct_access_wan") is True
1852
+
1853
+ ret.iscvmfs = queue_data.get("is_cvmfs") is True
1854
+
1855
+ if queue_data.get("corepower") is None:
1856
+ ret.corepower = 0
1857
+ else:
1858
+ ret.corepower = queue_data.get("corepower")
1859
+
1860
+ ret.wnconnectivity = queue_data.get("wnconnectivity")
1861
+ if ret.wnconnectivity == "":
1862
+ ret.wnconnectivity = None
1863
+
1864
+ # maxwdir
1865
+ try:
1866
+ if queue_data.get("maxwdir") is None:
1867
+ ret.maxwdir = 0
1868
+ else:
1869
+ ret.maxwdir = int(queue_data["maxwdir"])
1870
+ except Exception:
1871
+ if ret.maxinputsize in [0, None]:
1872
+ ret.maxwdir = 0
1873
+ else:
1874
+ try:
1875
+ ret.maxwdir = ret.maxinputsize + 2000
1876
+ except Exception:
1877
+ ret.maxwdir = 16336
1878
+
1879
+ # mintime
1880
+ if queue_data.get("mintime") is not None:
1881
+ ret.mintime = queue_data["mintime"]
1882
+ else:
1883
+ ret.mintime = 0
1884
+
1885
+ # reliability
1886
+ ret.reliabilityLevel = None
1887
+
1888
+ # pledged CPUs
1889
+ ret.pledgedCPU = 0
1890
+ if queue_data.get("pledgedcpu") not in ["", None]:
1891
+ try:
1892
+ ret.pledgedCPU = int(queue_data["pledgedcpu"])
1893
+ except Exception:
1894
+ pass
1895
+
1896
+ # core count
1897
+ ret.coreCount = 0
1898
+ if queue_data.get("corecount") not in ["", None]:
1899
+ try:
1900
+ ret.coreCount = int(queue_data["corecount"])
1901
+ except Exception:
1902
+ pass
1903
+
1904
+ # convert releases to list
1905
+ ret.releases = []
1906
+ if queue_data.get("releases"):
1907
+ ret.releases = queue_data["releases"]
1908
+
1909
+ # convert validatedreleases to list
1910
+ ret.validatedreleases = []
1911
+ if queue_data.get("validatedreleases"):
1912
+ for tmpRel in queue_data["validatedreleases"].split("|"):
1913
+ # remove white space
1914
+ tmpRel = tmpRel.strip()
1915
+ if tmpRel != "":
1916
+ ret.validatedreleases.append(tmpRel)
1917
+
1918
+ # limit of the number of transferring jobs
1919
+ ret.transferringlimit = 0
1920
+ if queue_data.get("transferringlimit") not in ["", None]:
1921
+ try:
1922
+ ret.transferringlimit = int(queue_data["transferringlimit"])
1923
+ except Exception:
1924
+ pass
1925
+
1926
+ # FAX
1927
+ ret.allowfax = False
1928
+ try:
1929
+ if queue_data.get("catchall") is not None and "allowfax" in queue_data["catchall"]:
1930
+ ret.allowfax = True
1931
+ if queue_data.get("allowfax") is True:
1932
+ ret.allowfax = True
1933
+ except Exception:
1934
+ pass
1935
+
1936
+ # DDM endpoints
1937
+ ret.ddm_endpoints_input = {}
1938
+ ret.ddm_endpoints_output = {}
1939
+ if siteid in pandaEndpointMap:
1940
+ for scope in pandaEndpointMap[siteid]:
1941
+ if "input" in pandaEndpointMap[siteid][scope]:
1942
+ ret.ddm_endpoints_input[scope] = pandaEndpointMap[siteid][scope]["input"]
1943
+ if "output" in pandaEndpointMap[siteid][scope]:
1944
+ ret.ddm_endpoints_output[scope] = pandaEndpointMap[siteid][scope]["output"]
1945
+ else:
1946
+ # empty
1947
+ ret.ddm_endpoints_input["default"] = DdmSpec()
1948
+ ret.ddm_endpoints_output["default"] = DdmSpec()
1949
+
1950
+ # initialize dictionary fields
1951
+ ret.setokens_input = {}
1952
+ ret.setokens_output = {}
1953
+ ret.ddm_input = {}
1954
+ for scope in ret.ddm_endpoints_input:
1955
+ # mapping between token and endpoints
1956
+ ret.setokens_input[scope] = ret.ddm_endpoints_input[scope].getTokenMap("input")
1957
+ # set DDM to the default endpoint
1958
+ ret.ddm_input[scope] = ret.ddm_endpoints_input[scope].getDefaultRead()
1959
+
1960
+ ret.ddm_output = {}
1961
+ for scope in ret.ddm_endpoints_output:
1962
+ # mapping between token and endpoints
1963
+ ret.setokens_output[scope] = ret.ddm_endpoints_output[scope].getTokenMap("output")
1964
+ # set DDM to the default endpoint
1965
+ ret.ddm_output[scope] = ret.ddm_endpoints_output[scope].getDefaultWrite()
1966
+
1967
+ # object stores
1968
+ try:
1969
+ ret.objectstores = queue_data["objectstores"]
1970
+ except Exception:
1971
+ ret.objectstores = []
1972
+
1973
+ # default unified flag
1974
+ ret.is_unified = False
1975
+
1976
+ # num slots
1977
+ ret.num_slots_map = num_slots_by_site.get(siteid, {})
1978
+
1979
+ # scale with core count
1980
+ if ret.use_per_core_attr() and ret.coreCount > 0:
1981
+ if ret.maxrss:
1982
+ ret.maxrss = ret.maxrss * ret.coreCount
1983
+ if ret.minrss:
1984
+ ret.minrss = ret.minrss * ret.coreCount
1985
+ if ret.maxwdir:
1986
+ ret.maxwdir = ret.maxwdir * ret.coreCount
1987
+ if ret.maxinputsize:
1988
+ ret.maxinputsize = ret.maxinputsize
1989
+
1990
+ # append
1991
+ retList[ret.nickname] = ret
1992
+ except Exception:
1993
+ tmp_log.error(f"exception in queue: {traceback.format_exc()}")
1994
+ continue
1995
+ # commit
1996
+ if not self._commit():
1997
+ raise RuntimeError("Commit error")
1998
+ tmp_log.debug("done")
1999
+ return retList, endpoint_detailed_status_summary
2000
+ except Exception as e:
2001
+ # roll back
2002
+ self._rollback()
2003
+ # error
2004
+ self.dump_error_message(tmp_log)
2005
+ return {}, {}
2006
+
2007
+ def getDdmEndpoints(self):
2008
+ """
2009
+ get list of ddm input endpoints
2010
+ """
2011
+ comment = " /* DBProxy.getDdmEndpoints */"
2012
+ tmp_log = self.create_tagged_logger(comment)
2013
+ tmp_log.debug(f"start")
2014
+
2015
+ # get all ddm endpoints
2016
+ sql_ddm = "SELECT * FROM ATLAS_PANDA.ddm_endpoint "
2017
+ self.cur.arraysize = 10000
2018
+ self.cur.execute(f"{sql_ddm}{comment}")
2019
+ results_ddm = self.cur.fetchall()
2020
+
2021
+ # extract the column names from the query
2022
+ column_names = [i[0].lower() for i in self.cur.description]
2023
+
2024
+ # save the endpoints into a dictionary
2025
+ endpoint_dict = {}
2026
+ detailed_status_summary = {}
2027
+ for ddm_endpoint_row in results_ddm:
2028
+ tmp_endpoint = {}
2029
+ # unzip the ddm_endpoint row into a dictionary
2030
+ for column_name, column_val in zip(column_names, ddm_endpoint_row):
2031
+ tmp_endpoint[column_name] = column_val
2032
+
2033
+ ddm_endpoint_name = tmp_endpoint["ddm_endpoint_name"]
2034
+ try:
2035
+ tmp_detailed_status = tmp_endpoint["detailed_status"]
2036
+ if not isinstance(tmp_detailed_status, dict):
2037
+ tmp_detailed_status = json.loads(tmp_detailed_status)
2038
+ # make a summary of detailed status of all endpoints
2039
+ if tmp_detailed_status:
2040
+ for tmp_activity, tmp_status in tmp_detailed_status.items():
2041
+ detailed_status_summary.setdefault(tmp_activity, {})
2042
+ detailed_status_summary[tmp_activity].setdefault(tmp_status, [])
2043
+ detailed_status_summary[tmp_activity][tmp_status].append(ddm_endpoint_name)
2044
+ tmp_endpoint["detailed_status"] = tmp_detailed_status
2045
+ except Exception as e:
2046
+ tmp_log.error(f"exception when decoding detailed_status for {ddm_endpoint_name}: {str(e)}")
2047
+ tmp_endpoint["detailed_status"] = {}
2048
+ endpoint_dict[ddm_endpoint_name] = tmp_endpoint
2049
+
2050
+ # get relationship between panda sites and ddm endpoints
2051
+ sql_panda_ddm = """
2052
+ SELECT pdr.panda_site_name, pdr.ddm_endpoint_name, pdr.is_local, de.ddm_spacetoken_name,
2053
+ de.is_tape, pdr.default_read, pdr.default_write, pdr.roles, pdr.order_read, pdr.order_write,
2054
+ nvl(pdr.scope, 'default') as scope, de.blacklisted_read
2055
+ FROM ATLAS_PANDA.panda_ddm_relation pdr, ATLAS_PANDA.ddm_endpoint de
2056
+ WHERE pdr.ddm_endpoint_name = de.ddm_endpoint_name
2057
+ """
2058
+ if self.backend == "mysql":
2059
+ sql_panda_ddm = """
2060
+ SELECT pdr.panda_site_name, pdr.ddm_endpoint_name, pdr.is_local, de.ddm_spacetoken_name,
2061
+ de.is_tape, pdr.default_read, pdr.default_write, pdr.roles, pdr.order_read, pdr.order_write,
2062
+ ifnull(pdr.scope, 'default') as scope, de.blacklisted
2063
+ FROM ATLAS_PANDA.panda_ddm_relation pdr, ATLAS_PANDA.ddm_endpoint de
2064
+ WHERE pdr.ddm_endpoint_name = de.ddm_endpoint_name
2065
+ """
2066
+
2067
+ self.cur.execute(f"{sql_panda_ddm}{comment}")
2068
+ results_panda_ddm = self.cur.fetchall()
2069
+ column_names = [i[0].lower() for i in self.cur.description]
2070
+
2071
+ # save the panda ddm relations into a dictionary
2072
+ panda_endpoint_map = {}
2073
+ for panda_ddm_row in results_panda_ddm:
2074
+ tmp_relation = {}
2075
+ for column_name, column_val in zip(column_names, panda_ddm_row):
2076
+ # Default unavailable endpoint space to 0
2077
+ if column_name.startswith("space_") and column_val is None:
2078
+ column_val = 0
2079
+ tmp_relation[column_name] = column_val
2080
+
2081
+ # add the relations to the panda endpoint map
2082
+ panda_site_name = tmp_relation["panda_site_name"]
2083
+ scope = tmp_relation["scope"]
2084
+ panda_endpoint_map.setdefault(panda_site_name, {})
2085
+ panda_endpoint_map[panda_site_name].setdefault(scope, {})
2086
+
2087
+ if panda_site_name not in panda_endpoint_map:
2088
+ panda_endpoint_map[panda_site_name] = {
2089
+ "input": DdmSpec(),
2090
+ "output": DdmSpec(),
2091
+ }
2092
+ if "read_lan" in tmp_relation["roles"] and tmp_relation["blacklisted_read"] != "Y":
2093
+ panda_endpoint_map[panda_site_name][scope].setdefault("input", DdmSpec())
2094
+ panda_endpoint_map[panda_site_name][scope]["input"].add(tmp_relation, endpoint_dict)
2095
+ if "write_lan" in tmp_relation["roles"]:
2096
+ panda_endpoint_map[panda_site_name][scope].setdefault("output", DdmSpec())
2097
+ panda_endpoint_map[panda_site_name][scope]["output"].add(tmp_relation, endpoint_dict)
2098
+
2099
+ tmp_log.debug(f"done")
2100
+ return panda_endpoint_map, detailed_status_summary
2101
+
2102
+ def get_cloud_list(self):
2103
+ """
2104
+ Get a list of distinct cloud names from the database.
2105
+ """
2106
+ comment = " /* DBProxy.get_cloud_list */"
2107
+ tmp_log = self.create_tagged_logger(comment)
2108
+ tmp_log.debug("start")
2109
+ try:
2110
+ with self.conn:
2111
+ sql = (
2112
+ f"SELECT /* use_json_type */ DISTINCT sj.data.cloud AS cloud "
2113
+ f"FROM {panda_config.schemaPANDA}.schedconfig_json sj "
2114
+ f"UNION "
2115
+ f"SELECT 'WORLD' AS cloud "
2116
+ f"FROM dual "
2117
+ f"ORDER BY cloud"
2118
+ )
2119
+ self.cur.arraysize = 100
2120
+ self.cur.execute(sql + comment)
2121
+ results = self.cur.fetchall()
2122
+ clouds = [result[0] for result in results]
2123
+
2124
+ tmp_log.debug("done")
2125
+ return clouds
2126
+ except Exception:
2127
+ self.dump_error_message(tmp_log)
2128
+ self._rollback()
2129
+ return []
2130
+
2131
+ # get users and groups to boost job priorities
2132
+ def get_dict_to_boost_job_prio(self, vo):
2133
+ comment = " /* DBProxy.get_dict_to_boost_job_prio */"
2134
+ tmp_log = self.create_tagged_logger(comment)
2135
+
2136
+ if self.job_prio_boost_dict_update_time and datetime.datetime.now(datetime.timezone.utc).replace(
2137
+ tzinfo=None
2138
+ ) - self.job_prio_boost_dict_update_time < datetime.timedelta(minutes=15):
2139
+ return self.job_prio_boost_dict
2140
+ try:
2141
+ self.job_prio_boost_dict_update_time = naive_utcnow()
2142
+ self.job_prio_boost_dict = {}
2143
+ # get configs
2144
+ tmp_log = self.create_tagged_logger(comment)
2145
+ # get dicts
2146
+ res_dicts = self.getConfigValue("dbproxy", "USER_JOB_PRIO_BOOST_DICTS", "pandaserver")
2147
+ # parse list
2148
+ if res_dicts:
2149
+ for tmp_item in res_dicts:
2150
+ try:
2151
+ tmp_name = tmp_item["name"]
2152
+ tmp_type = tmp_item["type"]
2153
+ tmp_prio = tmp_item["prio"]
2154
+ tmp_expire = tmp_item.get("expire", None)
2155
+ # check expiration
2156
+ if tmp_expire:
2157
+ tmp_expire = datetime.datetime.strptime(tmp_expire, "%Y%m%d")
2158
+ if tmp_expire < naive_utcnow():
2159
+ continue
2160
+ self.job_prio_boost_dict.setdefault(tmp_type, {})
2161
+ self.job_prio_boost_dict[tmp_type][tmp_name] = int(tmp_prio)
2162
+ except Exception as e:
2163
+ tmp_log.error(str(e))
2164
+ tmp_log.debug(f"got {self.job_prio_boost_dict}")
2165
+ return self.job_prio_boost_dict
2166
+ except Exception:
2167
+ # roll back
2168
+ self._rollback()
2169
+ # error
2170
+ self.dump_error_message(tmp_log)
2171
+ return {}
2172
+
2173
+ # set user secret
2174
+ def set_user_secret(self, owner, key, value):
2175
+ comment = " /* DBProxy.set_user_secret */"
2176
+ tmp_log = self.create_tagged_logger(comment, f"owner={owner} key={key}")
2177
+ try:
2178
+ # sql to check data
2179
+ sqlC = "SELECT data FROM ATLAS_PANDA.Secrets WHERE owner=:owner "
2180
+ # sql to insert dummy
2181
+ sqlI = "INSERT INTO ATLAS_PANDA.Secrets (owner, updated_at) " "VALUES(:owner,CURRENT_TIMESTAMP) "
2182
+ # sql to update data
2183
+ sqlU = "UPDATE ATLAS_PANDA.Secrets SET updated_at=CURRENT_TIMESTAMP,data=:data " "WHERE owner=:owner "
2184
+ # start transaction
2185
+ self.conn.begin()
2186
+ # check
2187
+ varMap = {}
2188
+ varMap[":owner"] = owner
2189
+ tmpS, tmpR = self.getClobObj(sqlC, varMap, use_commit=False)
2190
+ if not tmpR:
2191
+ # insert dummy for new entry
2192
+ self.cur.execute(sqlI + comment, varMap)
2193
+ data = {}
2194
+ else:
2195
+ data = json.loads(tmpR[0][0])
2196
+ # update
2197
+ if key is None:
2198
+ # delete all
2199
+ data = {}
2200
+ elif value is None:
2201
+ # delete key
2202
+ if key in data:
2203
+ del data[key]
2204
+ else:
2205
+ file_key = f"___file___:{key}"
2206
+ if file_key in data:
2207
+ del data[file_key]
2208
+ else:
2209
+ data[key] = value
2210
+ varMap = {}
2211
+ varMap[":owner"] = owner
2212
+ varMap[":data"] = json.dumps(data)
2213
+ self.cur.execute(sqlU + comment, varMap)
2214
+ # commit
2215
+ if not self._commit():
2216
+ raise RuntimeError("Commit error")
2217
+ tmp_log.debug("done")
2218
+ return True, "OK"
2219
+ except Exception:
2220
+ # roll back
2221
+ self._rollback()
2222
+ # error
2223
+ self.dump_error_message(tmp_log)
2224
+ return False, "database error"
2225
+
2226
+ # get user secrets
2227
+ def get_user_secrets(self, owner, keys=None, get_json=False, use_commit=True):
2228
+ comment = " /* DBProxy.get_user_secrets */"
2229
+ tmp_log = self.create_tagged_logger(comment, f"owner={owner} keys={keys}")
2230
+ try:
2231
+ # sql to get data
2232
+ sqlC = "SELECT data FROM ATLAS_PANDA.Secrets WHERE owner=:owner "
2233
+ # check
2234
+ varMap = {}
2235
+ varMap[":owner"] = owner
2236
+ tmpS, tmpR = self.getClobObj(sqlC, varMap, use_commit=use_commit)
2237
+ if not tmpR:
2238
+ data = {}
2239
+ if not get_json:
2240
+ data = json.dumps({})
2241
+ else:
2242
+ data = tmpR[0][0]
2243
+ # return only interesting keys
2244
+ if keys:
2245
+ keys = set(keys.split(","))
2246
+ data = json.loads(data)
2247
+ for k in list(data):
2248
+ if k not in keys:
2249
+ data.pop(k)
2250
+ if not get_json:
2251
+ data = json.dumps(data)
2252
+ else:
2253
+ if get_json:
2254
+ data = json.loads(data)
2255
+ tmp_log.debug(f"got data with length={len(data)}")
2256
+ return True, data
2257
+ except Exception:
2258
+ # error
2259
+ self.dump_error_message(tmp_log)
2260
+ return False, "database error"
2261
+
2262
+ def configurator_write_sites(self, site_list):
2263
+ """
2264
+ Cache the CRIC site information in the PanDA database
2265
+ """
2266
+ comment = " /* DBProxy.configurator_write_sites */"
2267
+ tmp_log = self.create_tagged_logger(comment)
2268
+ tmp_log.debug("start")
2269
+
2270
+ try:
2271
+ # begin transaction
2272
+ self.conn.begin()
2273
+
2274
+ # get existing sites
2275
+ tmp_log.debug("getting existing sites")
2276
+ sql_get = "SELECT site_name FROM ATLAS_PANDA.site"
2277
+ self.cur.execute(sql_get + comment)
2278
+ results = self.cur.fetchall()
2279
+ site_name_list = list(map(lambda result: result[0], results))
2280
+ tmp_log.debug("finished getting existing sites")
2281
+
2282
+ # see which sites need an update and which need to be inserted new
2283
+ var_map_insert = []
2284
+ var_map_update = []
2285
+ for site in site_list:
2286
+ if site["site_name"] in site_name_list:
2287
+ var_map_update.append(convert_dict_to_bind_vars(site))
2288
+ else:
2289
+ var_map_insert.append(convert_dict_to_bind_vars(site))
2290
+
2291
+ tmp_log.debug("Updating sites")
2292
+ sql_update = "UPDATE ATLAS_PANDA.site set role=:role, tier_level=:tier_level WHERE site_name=:site_name"
2293
+ for shard in create_shards(var_map_update, 100):
2294
+ self.cur.executemany(sql_update + comment, shard)
2295
+
2296
+ tmp_log.debug("Inserting sites")
2297
+ sql_insert = "INSERT INTO ATLAS_PANDA.site (site_name, role, tier_level) " "VALUES(:site_name, :role, :tier_level)"
2298
+ for shard in create_shards(var_map_insert, 100):
2299
+ self.cur.executemany(sql_insert + comment, shard)
2300
+
2301
+ # commit
2302
+ if not self._commit():
2303
+ raise RuntimeError("Commit error")
2304
+
2305
+ tmp_log.debug("Done")
2306
+ return 0, None
2307
+
2308
+ except Exception:
2309
+ self._rollback()
2310
+ self.dump_error_message(tmp_log)
2311
+ return -1, None
2312
+
2313
+ def configurator_write_panda_sites(self, panda_site_list):
2314
+ comment = " /* DBProxy.configurator_write_panda_sites */"
2315
+ tmp_log = self.create_tagged_logger(comment)
2316
+ tmp_log.debug("start")
2317
+
2318
+ try:
2319
+ # begin transaction
2320
+ self.conn.begin()
2321
+
2322
+ # get existing panda sites
2323
+ tmp_log.debug("getting existing panda sites")
2324
+ sql_get = "SELECT panda_site_name FROM ATLAS_PANDA.panda_site"
2325
+ self.cur.execute(sql_get + comment)
2326
+ results = self.cur.fetchall()
2327
+ panda_site_name_list = list(map(lambda result: result[0], results))
2328
+ tmp_log.debug("finished getting existing panda sites")
2329
+
2330
+ # see which sites need an update and which need to be inserted new
2331
+ var_map_insert = []
2332
+ var_map_update = []
2333
+ for panda_site in panda_site_list:
2334
+ if panda_site["panda_site_name"] in panda_site_name_list:
2335
+ var_map_update.append(convert_dict_to_bind_vars(panda_site))
2336
+ else:
2337
+ var_map_insert.append(convert_dict_to_bind_vars(panda_site))
2338
+
2339
+ tmp_log.debug("Updating panda sites")
2340
+ sql_update = "UPDATE ATLAS_PANDA.panda_site set site_name=:site_name WHERE panda_site_name=:panda_site_name "
2341
+ for shard in create_shards(var_map_update, 100):
2342
+ self.cur.executemany(sql_update + comment, shard)
2343
+
2344
+ tmp_log.debug("Inserting panda sites")
2345
+ sql_insert = "INSERT INTO ATLAS_PANDA.panda_site (panda_site_name, site_name) " "VALUES(:panda_site_name, :site_name)"
2346
+ for shard in create_shards(var_map_insert, 100):
2347
+ self.cur.executemany(sql_insert + comment, shard)
2348
+
2349
+ # commit
2350
+ if not self._commit():
2351
+ raise RuntimeError("Commit error")
2352
+
2353
+ tmp_log.debug("Done")
2354
+ return 0, None
2355
+
2356
+ except Exception:
2357
+ self._rollback()
2358
+ self.dump_error_message(tmp_log)
2359
+ return -1, None
2360
+
2361
+ def configurator_write_ddm_endpoints(self, ddm_endpoint_list):
2362
+ comment = " /* DBProxy.configurator_write_ddm_endpoints */"
2363
+ tmp_log = self.create_tagged_logger(comment)
2364
+ tmp_log.debug("start")
2365
+
2366
+ try:
2367
+ # begin transaction
2368
+ self.conn.begin()
2369
+
2370
+ # get existing ddm endpoints
2371
+ tmp_log.debug("getting existing ddm endpoints")
2372
+ sql_get = "SELECT ddm_endpoint_name FROM ATLAS_PANDA.ddm_endpoint"
2373
+ self.cur.execute(sql_get + comment)
2374
+ results = self.cur.fetchall()
2375
+ ddm_endpoint_name_list = list(map(lambda result: result[0], results))
2376
+ tmp_log.debug("finished getting existing ddm endpoints")
2377
+
2378
+ # see which sites need an update and which need to be inserted new
2379
+ var_map_insert = []
2380
+ var_map_update = []
2381
+ for ddm_endpoint in ddm_endpoint_list:
2382
+ if ddm_endpoint["ddm_endpoint_name"] in ddm_endpoint_name_list:
2383
+ var_map_update.append(convert_dict_to_bind_vars(ddm_endpoint))
2384
+ else:
2385
+ var_map_insert.append(convert_dict_to_bind_vars(ddm_endpoint))
2386
+
2387
+ tmp_log.debug("Updating ddm endpoints")
2388
+ sql_update = (
2389
+ "UPDATE ATLAS_PANDA.ddm_endpoint set "
2390
+ "site_name=:site_name, ddm_spacetoken_name=:ddm_spacetoken_name, type=:type, is_tape=:is_tape, "
2391
+ "blacklisted=:blacklisted, blacklisted_write=:blacklisted_write, blacklisted_read=:blacklisted_read, detailed_status=:detailed_status, "
2392
+ "space_used=:space_used, space_free=:space_free, space_total=:space_total, space_expired=:space_expired, space_timestamp=:space_timestamp "
2393
+ "WHERE ddm_endpoint_name=:ddm_endpoint_name"
2394
+ )
2395
+ for shard in create_shards(var_map_update, 100):
2396
+ self.cur.executemany(sql_update + comment, shard)
2397
+
2398
+ tmp_log.debug("Inserting ddm endpoints")
2399
+ sql_insert = (
2400
+ "INSERT INTO ATLAS_PANDA.ddm_endpoint (ddm_endpoint_name, site_name, ddm_spacetoken_name, type, is_tape, "
2401
+ "blacklisted, blacklisted_write, blacklisted_read, detailed_status, "
2402
+ "space_used, space_free, space_total, space_expired, space_timestamp) "
2403
+ "VALUES(:ddm_endpoint_name, :site_name, :ddm_spacetoken_name, :type, :is_tape, "
2404
+ ":blacklisted, :blacklisted_write, :blacklisted_read, :detailed_status, "
2405
+ ":space_used, :space_free, :space_total, :space_expired, :space_timestamp)"
2406
+ )
2407
+ for shard in create_shards(var_map_insert, 100):
2408
+ self.cur.executemany(sql_insert + comment, shard)
2409
+
2410
+ # commit
2411
+ if not self._commit():
2412
+ raise RuntimeError("Commit error")
2413
+
2414
+ tmp_log.debug("Done")
2415
+ return 0, None
2416
+
2417
+ except Exception:
2418
+ self._rollback()
2419
+ self.dump_error_message(tmp_log)
2420
+ return -1, None
2421
+
2422
+ def configurator_write_panda_ddm_relations(self, relation_list):
2423
+ comment = " /* DBProxy.configurator_write_panda_ddm_relations */"
2424
+ tmp_log = self.create_tagged_logger(comment)
2425
+ tmp_log.debug("start")
2426
+
2427
+ try:
2428
+ # begin transaction
2429
+ self.conn.begin()
2430
+
2431
+ # Reset the relations. Important to do this inside the transaction
2432
+ tmp_log.debug("Deleting existing panda ddm relations")
2433
+ sql_delete = "DELETE FROM ATLAS_PANDA.panda_ddm_relation"
2434
+ self.cur.execute(sql_delete + comment)
2435
+
2436
+ var_map_insert = []
2437
+ for relation in relation_list:
2438
+ var_map_insert.append(convert_dict_to_bind_vars(relation))
2439
+
2440
+ tmp_log.debug("Inserting panda ddm relations")
2441
+ sql_insert = (
2442
+ "INSERT INTO ATLAS_PANDA.panda_ddm_relation (panda_site_name, ddm_endpoint_name, roles, "
2443
+ "is_local, order_read, order_write, default_read, default_write, scope) "
2444
+ "VALUES(:panda_site_name, :ddm_endpoint_name, :roles, "
2445
+ ":is_local, :order_read, :order_write, :default_read, :default_write, :scope)"
2446
+ )
2447
+ for shard in create_shards(var_map_insert, 100):
2448
+ self.cur.executemany(sql_insert + comment, shard)
2449
+
2450
+ # commit
2451
+ if not self._commit():
2452
+ raise RuntimeError("Commit error")
2453
+
2454
+ tmp_log.debug("Done")
2455
+ return 0, None
2456
+
2457
+ except Exception:
2458
+ self._rollback()
2459
+ self.dump_error_message(tmp_log)
2460
+ return -1, None
2461
+
2462
+ def configurator_read_sites(self):
2463
+ comment = " /* DBProxy.configurator_read_sites */"
2464
+ tmp_log = self.create_tagged_logger(comment)
2465
+ tmp_log.debug("start")
2466
+
2467
+ try:
2468
+ tmp_log.debug("getting existing panda sites")
2469
+ sql_get = "SELECT site_name FROM ATLAS_PANDA.site"
2470
+ self.cur.execute(sql_get + comment)
2471
+ results = self.cur.fetchall()
2472
+ site_names = set(map(lambda result: result[0], results))
2473
+ tmp_log.debug("finished getting site names in configurator")
2474
+
2475
+ tmp_log.debug("Done")
2476
+ return site_names
2477
+
2478
+ except Exception:
2479
+ self.dump_error_message(tmp_log)
2480
+ return set()
2481
+
2482
+ def configurator_read_panda_sites(self):
2483
+ comment = " /* DBProxy.configurator_read_sites */"
2484
+ tmp_log = self.create_tagged_logger(comment)
2485
+ tmp_log.debug("start")
2486
+
2487
+ try:
2488
+ tmp_log.debug("getting existing panda sites")
2489
+ sql_get = "SELECT panda_site_name FROM ATLAS_PANDA.panda_site"
2490
+ self.cur.execute(sql_get + comment)
2491
+ results = self.cur.fetchall()
2492
+ panda_site_names = set(map(lambda result: result[0], results))
2493
+ tmp_log.debug("finished getting panda site names in configurator")
2494
+
2495
+ tmp_log.debug("Done")
2496
+ return panda_site_names
2497
+
2498
+ except Exception:
2499
+ self.dump_error_message(tmp_log)
2500
+ return set()
2501
+
2502
+ def configurator_read_ddm_endpoints(self):
2503
+ comment = " /* DBProxy.configurator_read_ddm_endpoints */"
2504
+ tmp_log = self.create_tagged_logger(comment)
2505
+ tmp_log.debug("start")
2506
+
2507
+ try:
2508
+ tmp_log.debug("getting existing ddm endpoints")
2509
+ sql_get = "SELECT ddm_endpoint_name FROM ATLAS_PANDA.ddm_endpoint"
2510
+ self.cur.execute(sql_get + comment)
2511
+ results = self.cur.fetchall()
2512
+ ddm_endpoint_names = set(map(lambda result: result[0], results))
2513
+ tmp_log.debug("finished getting ddm endpoint names in configurator")
2514
+
2515
+ tmp_log.debug("Done")
2516
+ return ddm_endpoint_names
2517
+
2518
+ except Exception:
2519
+ self.dump_error_message(tmp_log)
2520
+ return set()
2521
+
2522
+ def configurator_read_cric_sites(self):
2523
+ comment = " /* DBProxy.configurator_read_cric_sites */"
2524
+ tmp_log = self.create_tagged_logger(comment)
2525
+ tmp_log.debug("start")
2526
+
2527
+ try:
2528
+ tmp_log.debug("getting existing CRIC sites")
2529
+ sql_get = "SELECT /* use_json_type */ distinct scj.data.atlas_site FROM ATLAS_PANDA.schedconfig_json scj"
2530
+ self.cur.arraysize = 1000
2531
+ self.cur.execute(sql_get + comment)
2532
+ results = self.cur.fetchall()
2533
+ site_names = set(map(lambda result: result[0], results))
2534
+ tmp_log.debug("finished getting CRIC sites")
2535
+
2536
+ tmp_log.debug("Done")
2537
+ return site_names
2538
+
2539
+ except Exception:
2540
+ self.dump_error_message(tmp_log)
2541
+ return set()
2542
+
2543
+ def configurator_read_cric_panda_sites(self):
2544
+ comment = " /* DBProxy.configurator_read_cric_panda_sites */"
2545
+ tmp_log = self.create_tagged_logger(comment)
2546
+ tmp_log.debug("start")
2547
+
2548
+ try:
2549
+ tmp_log.debug("getting existing CRIC panda queues")
2550
+ sql_get = "SELECT panda_queue FROM ATLAS_PANDA.schedconfig_json"
2551
+ self.cur.execute(sql_get + comment)
2552
+ results = self.cur.fetchall()
2553
+ panda_site_names = set(map(lambda result: result[0], results))
2554
+ tmp_log.debug("finished getting CRIC panda queues")
2555
+
2556
+ tmp_log.debug("Done")
2557
+ return panda_site_names
2558
+
2559
+ except Exception:
2560
+ self._rollback()
2561
+ self.dump_error_message(tmp_log)
2562
+ return set()
2563
+
2564
+ def configurator_delete_sites(self, sites_to_delete):
2565
+ """
2566
+ Delete sites and all dependent entries (panda_sites, ddm_endpoints, panda_ddm_relations).
2567
+ Deletion of dependent entries is done through cascade definition in models
2568
+ """
2569
+ comment = " /* DBProxy.configurator_delete_sites */"
2570
+ tmp_log = self.create_tagged_logger(comment)
2571
+ tmp_log.debug("start")
2572
+
2573
+ if not sites_to_delete:
2574
+ tmp_log.debug("nothing to delete")
2575
+ return
2576
+
2577
+ var_map_list = list(map(lambda site_name: {":site_name": site_name}, sites_to_delete))
2578
+
2579
+ try:
2580
+ # begin transaction
2581
+ self.conn.begin()
2582
+ tmp_log.debug(f"deleting sites: {sites_to_delete}")
2583
+ sql_update = "DELETE FROM ATLAS_PANDA.site WHERE site_name=:site_name"
2584
+ self.cur.executemany(sql_update + comment, var_map_list)
2585
+ tmp_log.debug("done deleting sites")
2586
+
2587
+ # commit
2588
+ if not self._commit():
2589
+ raise RuntimeError("Commit error")
2590
+
2591
+ tmp_log.debug("Done")
2592
+ return 0, None
2593
+
2594
+ except Exception:
2595
+ self._rollback()
2596
+ self.dump_error_message(tmp_log)
2597
+ return -1, None
2598
+
2599
+ def configurator_delete_panda_sites(self, panda_sites_to_delete):
2600
+ """
2601
+ Delete PanDA sites and dependent entries in panda_ddm_relations
2602
+ """
2603
+ comment = " /* DBProxy.configurator_delete_panda_sites */"
2604
+ tmp_log = self.create_tagged_logger(comment)
2605
+ tmp_log.debug("start")
2606
+
2607
+ if not panda_sites_to_delete:
2608
+ tmp_log.debug("nothing to delete")
2609
+ return
2610
+
2611
+ var_map_list = list(
2612
+ map(
2613
+ lambda panda_site_name: {":panda_site_name": panda_site_name},
2614
+ panda_sites_to_delete,
2615
+ )
2616
+ )
2617
+
2618
+ try:
2619
+ # begin transaction
2620
+ self.conn.begin()
2621
+ tmp_log.debug(f"deleting panda sites: {panda_sites_to_delete}")
2622
+ sql_update = "DELETE FROM ATLAS_PANDA.panda_site WHERE panda_site_name=:panda_site_name"
2623
+ self.cur.executemany(sql_update + comment, var_map_list)
2624
+ tmp_log.debug("done deleting panda sites")
2625
+
2626
+ # commit
2627
+ if not self._commit():
2628
+ raise RuntimeError("Commit error")
2629
+
2630
+ tmp_log.debug("Done")
2631
+ return 0, None
2632
+
2633
+ except Exception:
2634
+ self._rollback()
2635
+ self.dump_error_message(tmp_log)
2636
+ return -1, None
2637
+
2638
+ def configurator_delete_ddm_endpoints(self, ddm_endpoints_to_delete):
2639
+ """
2640
+ Delete DDM endpoints dependent entries in panda_ddm_relations
2641
+ """
2642
+ comment = " /* DBProxy.configurator_delete_ddm_endpoints */"
2643
+ tmp_log = self.create_tagged_logger(comment)
2644
+ tmp_log.debug("start")
2645
+
2646
+ if not ddm_endpoints_to_delete:
2647
+ tmp_log.debug("nothing to delete")
2648
+ return
2649
+
2650
+ var_map_list = list(
2651
+ map(
2652
+ lambda ddm_endpoint_name: {":ddm_endpoint_name": ddm_endpoint_name},
2653
+ ddm_endpoints_to_delete,
2654
+ )
2655
+ )
2656
+
2657
+ try:
2658
+ # begin transaction
2659
+ self.conn.begin()
2660
+ tmp_log.debug(f"deleting ddm endpoints: {ddm_endpoints_to_delete}")
2661
+ sql_update = "DELETE FROM ATLAS_PANDA.ddm_endpoint WHERE ddm_endpoint_name=:ddm_endpoint_name"
2662
+ self.cur.executemany(sql_update + comment, var_map_list)
2663
+ tmp_log.debug("done deleting ddm endpoints")
2664
+
2665
+ # commit
2666
+ if not self._commit():
2667
+ raise RuntimeError("Commit error")
2668
+
2669
+ tmp_log.debug("Done")
2670
+ return 0, None
2671
+
2672
+ except Exception:
2673
+ self._rollback()
2674
+ self.dump_error_message(tmp_log)
2675
+ return -1, None
2676
+
2677
+ def carbon_write_region_emissions(self, emissions):
2678
+ comment = " /* DBProxy.carbon_write_regional_emissions */"
2679
+ tmp_log = self.create_tagged_logger(comment)
2680
+ tmp_log.debug("start")
2681
+
2682
+ try:
2683
+ # begin transaction
2684
+ self.conn.begin()
2685
+
2686
+ tmp_log.debug("Deleting old entries")
2687
+ sql_delete = "DELETE FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS " "WHERE timestamp < sysdate - interval '10' day"
2688
+ self.cur.execute(sql_delete + comment)
2689
+
2690
+ tmp_log.debug("Inserting emissions by region")
2691
+
2692
+ sql_insert = (
2693
+ "INSERT /*+ ignore_row_on_dupkey_index (emissions(region, timestamp)) */ "
2694
+ "INTO ATLAS_PANDA.CARBON_REGION_EMISSIONS emissions (REGION, TIMESTAMP, VALUE) "
2695
+ "VALUES(:region, :timestamp, :value)"
2696
+ )
2697
+ for shard in create_shards(emissions, 100):
2698
+ self.cur.executemany(sql_insert + comment, shard)
2699
+
2700
+ # commit
2701
+ if not self._commit():
2702
+ raise RuntimeError("Commit error")
2703
+
2704
+ tmp_log.debug("Done")
2705
+ return 0, None
2706
+
2707
+ except Exception:
2708
+ self._rollback()
2709
+ self.dump_error_message(tmp_log)
2710
+ return -1, None
2711
+
2712
+ def carbon_aggregate_emissions(self):
2713
+ comment = " /* DBProxy.carbon_aggregate_emissions */"
2714
+ tmp_log = self.create_tagged_logger(comment)
2715
+ tmp_log.debug("start")
2716
+
2717
+ try:
2718
+ # begin transaction
2719
+ self.conn.begin()
2720
+
2721
+ # get the percentage each region is contributing to grid computing power
2722
+ sql_stat = (
2723
+ "WITH tmp_total(total_hs) AS "
2724
+ "(SELECT sum(hs) "
2725
+ "FROM ATLAS_PANDA.jobs_share_stats) "
2726
+ "SELECT scj.data.region, sum(jss.hs)/tmp_total.total_hs "
2727
+ "FROM ATLAS_PANDA.jobs_share_stats jss, ATLAS_PANDA.schedconfig_json scj, tmp_total "
2728
+ "WHERE jss.computingsite = scj.panda_queue "
2729
+ "AND scj.data.region IS NOT NULL AND scj.data.region != 'GRID'"
2730
+ "GROUP BY scj.data.region, tmp_total.total_hs "
2731
+ )
2732
+
2733
+ region_dic = {}
2734
+ self.cur.arraysize = 1000
2735
+ stats_raw = self.cur.execute(sql_stat + comment)
2736
+ for entry in stats_raw:
2737
+ region, per_cent = entry
2738
+ region_dic.setdefault(region, {"emissions": 0, "per_cent": 0})
2739
+ region_dic[region]["per_cent"] = per_cent
2740
+
2741
+ # get the last emission values for each region
2742
+ sql_last = (
2743
+ "WITH top_ts(timestamp, region) AS "
2744
+ "(SELECT max(timestamp), region "
2745
+ "FROM atlas_panda.carbon_region_emissions "
2746
+ "GROUP BY region) "
2747
+ "SELECT cre.region, cre.value, cre.timestamp "
2748
+ "FROM atlas_panda.carbon_region_emissions cre, top_ts "
2749
+ "WHERE cre.timestamp = top_ts.timestamp AND cre.region = top_ts.region"
2750
+ )
2751
+
2752
+ last_emission_values = self.cur.execute(sql_last + comment)
2753
+ for entry in last_emission_values:
2754
+ region, value, ts = entry
2755
+ region_dic.setdefault(region, {"emissions": 0, "per_cent": 0})
2756
+ region_dic[region]["emissions"] = value
2757
+
2758
+ # calculate the grid average emissions
2759
+ average_emissions = 0
2760
+ for region in region_dic:
2761
+ tmp_log.debug(f"Region {region} with per_cent {region_dic[region]['per_cent']} and emissions {region_dic[region]['emissions']}")
2762
+ try:
2763
+ average_emissions = average_emissions + region_dic[region]["per_cent"] * region_dic[region]["emissions"]
2764
+ except Exception:
2765
+ tmp_log.debug(f"Skipped Region {region} with per_cent {region_dic[region]['per_cent']} and emissions {region_dic[region]['emissions']}")
2766
+
2767
+ # store the average emissions
2768
+ tmp_log.debug(f"The grid co2 emissions were averaged to {average_emissions}")
2769
+ utc_now = naive_utcnow()
2770
+ var_map = {
2771
+ ":region": "GRID",
2772
+ ":timestamp": utc_now,
2773
+ ":value": average_emissions,
2774
+ }
2775
+
2776
+ sql_insert = "INSERT INTO ATLAS_PANDA.carbon_region_emissions (region, timestamp, value) " "VALUES (:region, :timestamp, :value)"
2777
+ self.cur.execute(sql_insert + comment, var_map)
2778
+
2779
+ # commit
2780
+ if not self._commit():
2781
+ raise RuntimeError("Commit error")
2782
+
2783
+ tmp_log.debug("Done")
2784
+ return 0, None
2785
+
2786
+ except Exception:
2787
+ self._rollback()
2788
+ self.dump_error_message(tmp_log)
2789
+ return -1, None
2790
+
2791
+ # check quota
2792
+ def checkQuota(self, dn):
2793
+ comment = " /* DBProxy.checkQuota */"
2794
+ tmp_log = self.create_tagged_logger(comment, f"dn={dn}")
2795
+ tmp_log.debug(f"start")
2796
+ try:
2797
+ # set autocommit on
2798
+ self.conn.begin()
2799
+ # select
2800
+ name = CoreUtils.clean_user_id(dn)
2801
+ sql = "SELECT cpua1, cpua7, cpua30, quotaa1, quotaa7, quotaa30 FROM ATLAS_PANDAMETA.users WHERE name=:name"
2802
+ varMap = {}
2803
+ varMap[":name"] = name
2804
+ self.cur.arraysize = 10
2805
+ self.cur.execute(sql + comment, varMap)
2806
+ res = self.cur.fetchall()
2807
+ # commit
2808
+ if not self._commit():
2809
+ raise RuntimeError("Commit error")
2810
+ weight = 0.0
2811
+ if res is not None and len(res) != 0:
2812
+ item = res[0]
2813
+ # cpu and quota
2814
+ cpu1 = item[0]
2815
+ cpu7 = item[1]
2816
+ cpu30 = item[2]
2817
+ if item[3] in [0, None]:
2818
+ quota1 = 0
2819
+ else:
2820
+ quota1 = item[3] * 3600
2821
+ if item[4] in [0, None]:
2822
+ quota7 = 0
2823
+ else:
2824
+ quota7 = item[4] * 3600
2825
+ if item[5] in [0, None]:
2826
+ quota30 = 0
2827
+ else:
2828
+ quota30 = item[5] * 3600
2829
+ # CPU usage
2830
+ if cpu1 is None:
2831
+ cpu1 = 0.0
2832
+ # weight
2833
+ if quota1 > 0:
2834
+ weight = float(cpu1) / float(quota1)
2835
+ # not exceeded the limit
2836
+ weight = 0.0
2837
+ tmp_log.debug(f"Weight:{weight} Quota:{quota1} CPU:{cpu1}")
2838
+ else:
2839
+ tmp_log.debug(f"cannot found")
2840
+ return weight
2841
+ except Exception:
2842
+ self.dump_error_message(tmp_log)
2843
+ # roll back
2844
+ self._rollback()
2845
+ return 0.0
2846
+
2847
+ # check if superuser
2848
+ def isSuperUser(self, userName):
2849
+ comment = " /* DBProxy.isSuperUser */"
2850
+ tmp_log = self.create_tagged_logger(comment, f"userName={userName}")
2851
+ tmp_log.debug("start")
2852
+ try:
2853
+ isSU = False
2854
+ isSG = False
2855
+ # start transaction
2856
+ self.conn.begin()
2857
+ # check gridpref
2858
+ name = CoreUtils.clean_user_id(userName)
2859
+ sql = "SELECT gridpref FROM ATLAS_PANDAMETA.users WHERE name=:name"
2860
+ varMap = {}
2861
+ varMap[":name"] = name
2862
+ self.cur.arraysize = 10
2863
+ self.cur.execute(sql + comment, varMap)
2864
+ res = self.cur.fetchone()
2865
+ # commit
2866
+ if not self._commit():
2867
+ raise RuntimeError("Commit error")
2868
+ # check if s in gridpref
2869
+ if res is not None:
2870
+ (gridpref,) = res
2871
+ if gridpref is not None:
2872
+ if PrioUtil.PERMISSION_SUPER_USER in gridpref:
2873
+ isSU = True
2874
+ if PrioUtil.PERMISSION_SUPER_GROUP in gridpref:
2875
+ isSG = True
2876
+ tmp_log.debug(f"done with superUser={isSU} superGroup={isSG}")
2877
+ return isSU, isSG
2878
+ except Exception:
2879
+ # roll back
2880
+ self._rollback()
2881
+ # error
2882
+ self.dump_error_message(tmp_log)
2883
+ return False, False
2884
+
2885
+ # get serialize JobID and status
2886
+ def getUserParameter(self, dn, jobID, jobsetID):
2887
+ comment = " /* DBProxy.getUserParameter */"
2888
+ tmp_log = self.create_tagged_logger(comment, f"dn={dn} jobID={jobID} jobsetID={jobsetID}")
2889
+ try:
2890
+ # set initial values
2891
+ retStatus = True
2892
+ if jobsetID == -1:
2893
+ # generate new jobsetID
2894
+ retJobsetID = jobID
2895
+ # new jobID = 1 + new jobsetID
2896
+ retJobID = retJobsetID + 1
2897
+ elif jobsetID in ["NULL", None, 0]:
2898
+ # no jobsetID
2899
+ retJobsetID = None
2900
+ retJobID = jobID
2901
+ else:
2902
+ # user specified jobsetID
2903
+ retJobsetID = jobsetID
2904
+ retJobID = jobID
2905
+ # set autocommit on
2906
+ self.conn.begin()
2907
+ # select
2908
+ name = CoreUtils.clean_user_id(dn)
2909
+ sql = "SELECT jobid,status FROM ATLAS_PANDAMETA.users WHERE name=:name "
2910
+ sql += "FOR UPDATE "
2911
+ sqlAdd = "INSERT INTO ATLAS_PANDAMETA.users "
2912
+ sqlAdd += "(ID,NAME,LASTMOD,FIRSTJOB,LATESTJOB,CACHETIME,NCURRENT,JOBID) "
2913
+ sqlAdd += "VALUES(ATLAS_PANDAMETA.USERS_ID_SEQ.nextval,:name,"
2914
+ sqlAdd += "CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,0,1) "
2915
+ varMap = {}
2916
+ varMap[":name"] = name
2917
+ self.cur.execute(sql + comment, varMap)
2918
+ self.cur.arraysize = 10
2919
+ res = self.cur.fetchall()
2920
+ # insert if no record
2921
+ if res is None or len(res) == 0:
2922
+ try:
2923
+ self.cur.execute(sqlAdd + comment, varMap)
2924
+ retI = self.cur.rowcount
2925
+ tmp_log.debug(f"inserted new row with {retI}")
2926
+ # emulate DB response
2927
+ res = [[1, ""]]
2928
+ except Exception:
2929
+ self.dump_error_message(tmp_log)
2930
+ if res is not None and len(res) != 0:
2931
+ item = res[0]
2932
+ # JobID in DB
2933
+ dbJobID = item[0]
2934
+ # check status
2935
+ if item[1] in ["disabled"]:
2936
+ retStatus = False
2937
+ # use larger JobID
2938
+ if dbJobID >= int(retJobID) or (jobsetID == -1 and dbJobID >= int(retJobsetID)):
2939
+ if jobsetID == -1:
2940
+ # generate new jobsetID = 1 + exsiting jobID
2941
+ retJobsetID = dbJobID + 1
2942
+ # new jobID = 1 + new jobsetID
2943
+ retJobID = retJobsetID + 1
2944
+ else:
2945
+ # new jobID = 1 + existing jobID
2946
+ retJobID = dbJobID + 1
2947
+ # update DB
2948
+ varMap = {}
2949
+ varMap[":name"] = name
2950
+ varMap[":jobid"] = retJobID
2951
+ sql = "UPDATE ATLAS_PANDAMETA.users SET jobid=:jobid WHERE name=:name"
2952
+ self.cur.execute(sql + comment, varMap)
2953
+ tmp_log.debug(f"set JobID={retJobID}")
2954
+ # commit
2955
+ if not self._commit():
2956
+ raise RuntimeError("Commit error")
2957
+ tmp_log.debug(f"return JobID={retJobID} JobsetID={retJobsetID} Status={retStatus}")
2958
+ return retJobID, retJobsetID, retStatus
2959
+ except Exception:
2960
+ self.dump_error_message(tmp_log)
2961
+ # roll back
2962
+ self._rollback()
2963
+ return retJobID, retJobsetID, retStatus
2964
+
2965
+ # check ban user
2966
+ def checkBanUser(self, dn, sourceLabel, jediCheck=False):
2967
+ comment = " /* DBProxy.checkBanUser */"
2968
+ try:
2969
+ methodName = "checkBanUser"
2970
+ # set initial values
2971
+ retStatus = True
2972
+ name = CoreUtils.clean_user_id(dn)
2973
+ tmp_log = self.create_tagged_logger(comment, f"name={name}")
2974
+ tmp_log.debug(f"start dn={dn} label={sourceLabel} jediCheck={jediCheck}")
2975
+ # set autocommit on
2976
+ self.conn.begin()
2977
+ # select
2978
+ sql = "SELECT status,dn FROM ATLAS_PANDAMETA.users WHERE name=:name"
2979
+ varMap = {}
2980
+ varMap[":name"] = name
2981
+ self.cur.execute(sql + comment, varMap)
2982
+ self.cur.arraysize = 10
2983
+ res = self.cur.fetchone()
2984
+ if res is not None:
2985
+ # check status
2986
+ tmpStatus, dnInDB = res
2987
+ if tmpStatus in ["disabled"]:
2988
+ retStatus = False
2989
+ elif jediCheck and (dnInDB in ["", None] or dnInDB != dn):
2990
+ # add DN
2991
+ sqlUp = "UPDATE ATLAS_PANDAMETA.users SET dn=:dn WHERE name=:name "
2992
+ varMap = {}
2993
+ varMap[":name"] = name
2994
+ varMap[":dn"] = dn
2995
+ self.cur.execute(sqlUp + comment, varMap)
2996
+ retI = self.cur.rowcount
2997
+ tmp_log.debug(f"update DN with Status={retI}")
2998
+ if retI != 1:
2999
+ retStatus = 1
3000
+ else:
3001
+ # new user
3002
+ if jediCheck:
3003
+ name = CoreUtils.clean_user_id(dn)
3004
+ sqlAdd = "INSERT INTO ATLAS_PANDAMETA.users "
3005
+ sqlAdd += "(ID,NAME,DN,LASTMOD,FIRSTJOB,LATESTJOB,CACHETIME,NCURRENT,JOBID) "
3006
+ sqlAdd += "VALUES(ATLAS_PANDAMETA.USERS_ID_SEQ.nextval,:name,:dn,"
3007
+ sqlAdd += "CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,0,1) "
3008
+ varMap = {}
3009
+ varMap[":name"] = name
3010
+ varMap[":dn"] = dn
3011
+ self.cur.execute(sqlAdd + comment, varMap)
3012
+ retI = self.cur.rowcount
3013
+ tmp_log.debug(f"inserted new row with Status={retI}")
3014
+ if retI != 1:
3015
+ retStatus = 2
3016
+ # commit
3017
+ if not self._commit():
3018
+ raise RuntimeError("Commit error")
3019
+ tmp_log.debug(f"done with Status={retStatus}")
3020
+ return retStatus
3021
+ except Exception:
3022
+ # roll back
3023
+ self._rollback()
3024
+ # error
3025
+ self.dump_error_message(tmp_log)
3026
+ return retStatus
3027
+
3028
+ # get email address for a user
3029
+ def getEmailAddr(self, name, withDN=False, withUpTime=False):
3030
+ comment = " /* DBProxy.getEmailAddr */"
3031
+ tmp_log = self.create_tagged_logger(comment)
3032
+ tmp_log.debug(f"get email for {name}")
3033
+ # sql
3034
+ if withDN:
3035
+ failedRet = "", "", None
3036
+ sql = "SELECT email,dn,location FROM ATLAS_PANDAMETA.users WHERE name=:name"
3037
+ elif withUpTime:
3038
+ failedRet = "", None
3039
+ sql = "SELECT email,location FROM ATLAS_PANDAMETA.users WHERE name=:name"
3040
+ else:
3041
+ failedRet = ""
3042
+ sql = "SELECT email FROM ATLAS_PANDAMETA.users WHERE name=:name"
3043
+ try:
3044
+ # set autocommit on
3045
+ self.conn.begin()
3046
+ # select
3047
+ varMap = {}
3048
+ varMap[":name"] = name
3049
+ self.cur.execute(sql + comment, varMap)
3050
+ self.cur.arraysize = 10
3051
+ res = self.cur.fetchall()
3052
+ # commit
3053
+ if not self._commit():
3054
+ raise RuntimeError("Commit error")
3055
+ if res is not None and len(res) != 0:
3056
+ if withDN or withUpTime:
3057
+ if withDN:
3058
+ email, dn, upTime = res[0]
3059
+ else:
3060
+ email, upTime = res[0]
3061
+ # convert time
3062
+ try:
3063
+ upTime = datetime.datetime.strptime(upTime, "%Y-%m-%d %H:%M:%S")
3064
+ except Exception:
3065
+ upTime = None
3066
+ if withDN:
3067
+ return email, dn, upTime
3068
+ else:
3069
+ return email, upTime
3070
+ else:
3071
+ return res[0][0]
3072
+ # return empty string
3073
+ return failedRet
3074
+ except Exception:
3075
+ self.dump_error_message(tmp_log)
3076
+ # roll back
3077
+ self._rollback()
3078
+ return failedRet
3079
+
3080
+ # set email address for a user
3081
+ def setEmailAddr(self, userName, emailAddr):
3082
+ comment = " /* DBProxy.setEmailAddr */"
3083
+ tmp_log = self.create_tagged_logger(comment)
3084
+ tmp_log.debug(f"{userName} to {emailAddr}")
3085
+ # sql
3086
+ sql = "UPDATE ATLAS_PANDAMETA.users SET email=:email,location=:uptime WHERE name=:name "
3087
+ try:
3088
+ # set autocommit on
3089
+ self.conn.begin()
3090
+ # set
3091
+ varMap = {}
3092
+ varMap[":name"] = userName
3093
+ varMap[":email"] = emailAddr
3094
+ varMap[":uptime"] = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
3095
+ self.cur.execute(sql + comment, varMap)
3096
+ # commit
3097
+ if not self._commit():
3098
+ raise RuntimeError("Commit error")
3099
+ return True
3100
+ except Exception:
3101
+ # roll back
3102
+ self._rollback()
3103
+ # error
3104
+ self.dump_error_message(tmp_log)
3105
+ return False
3106
+
3107
+ # get ban users
3108
+ def get_ban_users(self):
3109
+ comment = " /* DBProxy.get_ban_user */"
3110
+ tmp_log = self.create_tagged_logger(comment)
3111
+ tmp_log.debug("start")
3112
+ # sql
3113
+ sql = "SELECT name FROM ATLAS_PANDAMETA.users WHERE status=:status "
3114
+ try:
3115
+ # set autocommit on
3116
+ self.conn.begin()
3117
+ varMap = {}
3118
+ varMap[":status"] = "disabled"
3119
+ self.cur.execute(sql + comment, varMap)
3120
+ self.cur.arraysize = 10
3121
+ res = self.cur.fetchall()
3122
+ # commit
3123
+ if not self._commit():
3124
+ raise RuntimeError("Commit error")
3125
+ retVal = {name: False for name, in res}
3126
+ tmp_log.debug(f"got {retVal}")
3127
+ return True, retVal
3128
+ except Exception:
3129
+ # roll back
3130
+ self._rollback()
3131
+ # error
3132
+ self.dump_error_message(tmp_log)
3133
+ return False, None
3134
+
3135
+ # register token key
3136
+ def register_token_key(self, client_name: str, lifetime: int) -> bool:
3137
+ """
3138
+ Register token key for a client with a lifetime and delete expired tokens
3139
+
3140
+ :param client_name: client name who owns the token key
3141
+ :param lifetime: lifetime of the token key in hours
3142
+
3143
+ :return: True if succeeded. False otherwise
3144
+ """
3145
+ comment = " /* DBProxy.register_token_key */"
3146
+ tmp_log = self.create_tagged_logger(comment, f"client_name={client_name}")
3147
+ tmp_log.debug("start")
3148
+ try:
3149
+ # set autocommit on
3150
+ self.conn.begin()
3151
+ time_now = naive_utcnow()
3152
+ expire_at = time_now + datetime.timedelta(hours=lifetime)
3153
+ # check if a new key was registered recently
3154
+ sql = f"SELECT 1 FROM {panda_config.schemaMETA}.proxykey WHERE dn=:dn AND expires>:limit "
3155
+ var_map = {":dn": client_name, ":limit": expire_at - datetime.timedelta(hours=1)}
3156
+ self.cur.execute(sql + comment, var_map)
3157
+ res = self.cur.fetchone()
3158
+ if res:
3159
+ tmp_log.debug("skip as a new key was registered recently")
3160
+ else:
3161
+ # get max ID
3162
+ max_id = None
3163
+ sql = "SELECT MAX(ID) FROM ATLAS_PANDAMETA.proxykey "
3164
+ self.cur.execute(sql + comment, {})
3165
+ res = self.cur.fetchone()
3166
+ if res:
3167
+ (max_id,) = res
3168
+ if max_id is None:
3169
+ max_id = 0
3170
+ max_id += 1
3171
+ max_id %= 10000000
3172
+ # register a key
3173
+ sql = (
3174
+ f"INSERT INTO {panda_config.schemaMETA}.proxykey (ID,DN,CREDNAME,CREATED,EXPIRES,ORIGIN,MYPROXY) "
3175
+ "VALUES(:id,:dn,:credname,:created,:expires,:origin,:myproxy) "
3176
+ )
3177
+ var_map = {
3178
+ ":id": max_id,
3179
+ ":dn": client_name,
3180
+ ":credname": str(uuid.uuid4()),
3181
+ ":created": time_now,
3182
+ ":expires": expire_at,
3183
+ ":origin": "panda",
3184
+ ":myproxy": "NA",
3185
+ }
3186
+ try:
3187
+ self.cur.execute(sql + comment, var_map)
3188
+ tmp_log.debug(f"registered a new key with id={max_id}")
3189
+ except Exception as e:
3190
+ # ignore ID duplication error
3191
+ tmp_log.debug(f"ignoring registration failure with {str(e)}")
3192
+ # delete obsolete keys
3193
+ sql = "DELETE FROM ATLAS_PANDAMETA.proxykey WHERE expires<:limit "
3194
+ var_map = {":limit": time_now}
3195
+ self.cur.execute(sql + comment, var_map)
3196
+ # commit
3197
+ if not self._commit():
3198
+ raise RuntimeError("Commit error")
3199
+ # return True
3200
+ tmp_log.debug("done")
3201
+ return True
3202
+ except Exception:
3203
+ # roll back
3204
+ self._rollback()
3205
+ # dump error
3206
+ self.dump_error_message(tmp_log)
3207
+ return False
3208
+
3209
+ # Configurator function: inserts data into the network matrix
3210
+ def insertNetworkMatrixData(self, data):
3211
+ comment = " /* DBProxy.insertNetworkMatrixData */"
3212
+ tmp_log = self.create_tagged_logger(comment)
3213
+ tmp_log.debug("start")
3214
+
3215
+ # For performance reasons we will insert the data into a temporary table
3216
+ # and then merge this data into the permanent table.
3217
+
3218
+ sql_insert = """
3219
+ INSERT INTO ATLAS_PANDA.network_matrix_kv_temp (src, dst, key, value, ts)
3220
+ VALUES (:src, :dst, :key, :value, :ts)
3221
+ """
3222
+
3223
+ if self.backend == "postgres":
3224
+ sql_merge = (
3225
+ "INSERT INTO ATLAS_PANDA.network_matrix_kv "
3226
+ "(src, dst, key, value, ts) "
3227
+ "SELECT src, dst, key, value, ts FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP "
3228
+ "ON CONFLICT (src, dst, key) "
3229
+ "DO UPDATE SET value=EXCLUDED.value, ts=EXCLUDED.ts "
3230
+ )
3231
+ else:
3232
+ sql_merge = """
3233
+ MERGE /*+ FULL(nm_kv) */ INTO ATLAS_PANDA.network_matrix_kv nm_kv USING
3234
+ (SELECT src, dst, key, value, ts FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP) input
3235
+ ON (nm_kv.src = input.src AND nm_kv.dst= input.dst AND nm_kv.key = input.key)
3236
+ WHEN NOT MATCHED THEN
3237
+ INSERT (src, dst, key, value, ts)
3238
+ VALUES (input.src, input.dst, input.key, input.value, input.ts)
3239
+ WHEN MATCHED THEN
3240
+ UPDATE SET nm_kv.value = input.value, nm_kv.ts = input.ts
3241
+ """
3242
+ try:
3243
+ self.conn.begin()
3244
+ for shard in create_shards(data, 100):
3245
+ time1 = time.time()
3246
+ var_maps = []
3247
+ for entry in shard:
3248
+ var_map = {
3249
+ ":src": entry[0],
3250
+ ":dst": entry[1],
3251
+ ":key": entry[2],
3252
+ ":value": entry[3],
3253
+ ":ts": entry[4],
3254
+ }
3255
+ var_maps.append(var_map)
3256
+
3257
+ time2 = time.time()
3258
+ self.cur.executemany(sql_insert + comment, var_maps)
3259
+ time3 = time.time()
3260
+ tmp_log.debug(f"Processing a shard took: {time2 - time1}s of data preparation and {time3 - time2}s of insertion = {time3 - time1}")
3261
+
3262
+ time4 = time.time()
3263
+ self.cur.execute(sql_merge + comment)
3264
+ time5 = time.time()
3265
+ tmp_log.debug(f"Final merge took: {time5 - time4}s")
3266
+ if self.backend == "postgres":
3267
+ # cleanup since ON CONFLICT DO UPDATE doesn't work with duplicated entries
3268
+ self.cur.execute("DELETE FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP " + comment)
3269
+ # commit
3270
+ if not self._commit():
3271
+ raise RuntimeError("Commit error")
3272
+
3273
+ except Exception:
3274
+ # roll back
3275
+ self._rollback()
3276
+ # error
3277
+ self.dump_error_message(tmp_log)
3278
+ return None, ""
3279
+
3280
+ # Configurator function: delete old network data
3281
+ def deleteOldNetworkData(self):
3282
+ comment = " /* DBProxy.deleteOldNetworkData */"
3283
+ tmp_log = self.create_tagged_logger(comment)
3284
+ tmp_log.debug("start")
3285
+
3286
+ # delete any data older than a week
3287
+ sql_delete = """
3288
+ DELETE FROM ATLAS_PANDA.network_matrix_kv
3289
+ WHERE ts < (current_date - 7)
3290
+ """
3291
+ try:
3292
+ self.conn.begin()
3293
+ time1 = time.time()
3294
+ self.cur.execute(sql_delete + comment)
3295
+ time2 = time.time()
3296
+ tmp_log.debug(f"Deletion of old network data took: {time2 - time1}s")
3297
+
3298
+ # commit
3299
+ if not self._commit():
3300
+ raise RuntimeError("Commit error")
3301
+
3302
+ except Exception:
3303
+ # roll back
3304
+ self._rollback()
3305
+ # error
3306
+ self.dump_error_message(tmp_log)
3307
+ return None, ""
3308
+
3309
+ def ups_get_queues(self):
3310
+ """
3311
+ Identify unified pilot streaming (ups) queues: served in pull (late binding) model
3312
+ :return: list of panda queues
3313
+ """
3314
+ comment = " /* DBProxy.ups_get_queues */"
3315
+ tmp_log = self.create_tagged_logger(comment)
3316
+ tmp_log.debug("start")
3317
+
3318
+ ups_queues = []
3319
+ sql = f"""
3320
+ SELECT /* use_json_type */ scj.panda_queue FROM {panda_config.schemaPANDA}.schedconfig_json scj
3321
+ WHERE scj.data.capability='ucore' AND scj.data.workflow = 'pull_ups'
3322
+ """
3323
+
3324
+ self.cur.execute(sql + comment)
3325
+ res = self.cur.fetchall()
3326
+ for (ups_queue,) in res:
3327
+ ups_queues.append(ups_queue)
3328
+
3329
+ tmp_log.debug("done")
3330
+ return ups_queues
3331
+
3332
+ # calculate RW for tasks
3333
+ def calculateTaskRW_JEDI(self, jediTaskID):
3334
+ comment = " /* JediDBProxy.calculateTaskRW_JEDI */"
3335
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
3336
+ tmpLog.debug("start")
3337
+ try:
3338
+ # sql to get RW
3339
+ sql = "SELECT ROUND(SUM((nFiles-nFilesFinished-nFilesFailed-nFilesOnHold)*walltime)/24/3600) "
3340
+ sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD ".format(panda_config.schemaJEDI)
3341
+ sql += "WHERE tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
3342
+ sql += "AND tabT.jediTaskID=:jediTaskID "
3343
+ varMap = {}
3344
+ varMap[":jediTaskID"] = jediTaskID
3345
+ # begin transaction
3346
+ self.conn.begin()
3347
+ # get
3348
+ self.cur.execute(sql + comment, varMap)
3349
+ resRT = self.cur.fetchone()
3350
+ # commit
3351
+ if not self._commit():
3352
+ raise RuntimeError("Commit error")
3353
+ # locked by another
3354
+ if resRT is None:
3355
+ retVal = None
3356
+ else:
3357
+ retVal = resRT[0]
3358
+ tmpLog.debug(f"RW={retVal}")
3359
+ # return
3360
+ tmpLog.debug("done")
3361
+ return retVal
3362
+ except Exception:
3363
+ # roll back
3364
+ self._rollback()
3365
+ # error
3366
+ self.dump_error_message(tmpLog)
3367
+ return None
3368
+
3369
+ # calculate RW with a priority
3370
+ def calculateRWwithPrio_JEDI(self, vo, prodSourceLabel, workQueue, priority):
3371
+ comment = " /* JediDBProxy.calculateRWwithPrio_JEDI */"
3372
+ if workQueue is None:
3373
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={None} prio={priority}")
3374
+ else:
3375
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={workQueue.queue_name} prio={priority}")
3376
+ tmpLog.debug("start")
3377
+ try:
3378
+ # sql to get RW
3379
+ varMap = {}
3380
+ varMap[":vo"] = vo
3381
+ varMap[":prodSourceLabel"] = prodSourceLabel
3382
+ if priority is not None:
3383
+ varMap[":priority"] = priority
3384
+ sql = "SELECT tabT.jediTaskID,tabT.cloud,tabD.datasetID,nFiles-nFilesFinished-nFilesFailed,walltime "
3385
+ sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD,{0}.JEDI_AUX_Status_MinTaskID tabA ".format(panda_config.schemaJEDI)
3386
+ sql += "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID "
3387
+ sql += "AND tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
3388
+ sql += "AND (nFiles-nFilesFinished-nFilesFailed)>0 "
3389
+ sql += "AND tabT.vo=:vo AND prodSourceLabel=:prodSourceLabel "
3390
+
3391
+ if priority is not None:
3392
+ sql += "AND currentPriority>=:priority "
3393
+
3394
+ if workQueue is not None:
3395
+ if workQueue.is_global_share:
3396
+ sql += "AND gshare=:wq_name "
3397
+ sql += f"AND tabT.workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaJEDI}.jedi_work_queue WHERE queue_function = 'Resource') "
3398
+ varMap[":wq_name"] = workQueue.queue_name
3399
+ else:
3400
+ sql += "AND workQueue_ID=:wq_id "
3401
+ varMap[":wq_id"] = workQueue.queue_id
3402
+
3403
+ sql += "AND tabT.status IN (:status1,:status2,:status3,:status4) "
3404
+ sql += f"AND tabD.type IN ({INPUT_TYPES_var_str}) "
3405
+ varMap.update(INPUT_TYPES_var_map)
3406
+ varMap[":status1"] = "ready"
3407
+ varMap[":status2"] = "scouting"
3408
+ varMap[":status3"] = "running"
3409
+ varMap[":status4"] = "pending"
3410
+ sql += "AND tabT.cloud IS NOT NULL "
3411
+ # begin transaction
3412
+ self.conn.begin()
3413
+ # set cloud
3414
+ self.cur.execute(sql + comment, varMap)
3415
+ resList = self.cur.fetchall()
3416
+ # commit
3417
+ if not self._commit():
3418
+ raise RuntimeError("Commit error")
3419
+ # loop over all tasks
3420
+ retMap = {}
3421
+ sqlF = "SELECT fsize,startEvent,endEvent,nEvents "
3422
+ sqlF += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
3423
+ sqlF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND rownum<=1"
3424
+ for jediTaskID, cloud, datasetID, nRem, walltime in resList:
3425
+ # get effective size
3426
+ varMap = {}
3427
+ varMap[":jediTaskID"] = jediTaskID
3428
+ varMap[":datasetID"] = datasetID
3429
+ # begin transaction
3430
+ self.conn.begin()
3431
+ # get file
3432
+ self.cur.execute(sqlF + comment, varMap)
3433
+ resFile = self.cur.fetchone()
3434
+ # commit
3435
+ if not self._commit():
3436
+ raise RuntimeError("Commit error")
3437
+ if resFile is not None:
3438
+ # calculate RW using effective size
3439
+ fsize, startEvent, endEvent, nEvents = resFile
3440
+ effectiveFsize = CoreUtils.getEffectiveFileSize(fsize, startEvent, endEvent, nEvents)
3441
+ tmpRW = nRem * effectiveFsize * walltime
3442
+ if cloud not in retMap:
3443
+ retMap[cloud] = 0
3444
+ retMap[cloud] += tmpRW
3445
+ for cloudName, rwValue in retMap.items():
3446
+ retMap[cloudName] = int(rwValue / 24 / 3600)
3447
+ tmpLog.debug(f"RW={str(retMap)}")
3448
+ # return
3449
+ tmpLog.debug("done")
3450
+ return retMap
3451
+ except Exception:
3452
+ # roll back
3453
+ self._rollback()
3454
+ # error
3455
+ self.dump_error_message(tmpLog)
3456
+ return None
3457
+
3458
+ # calculate WORLD RW with a priority
3459
+ def calculateWorldRWwithPrio_JEDI(self, vo, prodSourceLabel, workQueue, priority):
3460
+ comment = " /* JediDBProxy.calculateWorldRWwithPrio_JEDI */"
3461
+ if workQueue is None:
3462
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={None} prio={priority}")
3463
+ else:
3464
+ tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={workQueue.queue_name} prio={priority}")
3465
+ tmpLog.debug("start")
3466
+ try:
3467
+ # sql to get RW
3468
+ varMap = {}
3469
+ varMap[":vo"] = vo
3470
+ varMap[":prodSourceLabel"] = prodSourceLabel
3471
+ varMap[":worldCloud"] = JediTaskSpec.worldCloudName
3472
+ if priority is not None:
3473
+ varMap[":priority"] = priority
3474
+ sql = "SELECT tabT.nucleus,SUM((nEvents-nEventsUsed)*(CASE WHEN cpuTime IS NULL THEN 300 ELSE cpuTime END)) "
3475
+ sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD,{0}.JEDI_AUX_Status_MinTaskID tabA ".format(panda_config.schemaJEDI)
3476
+ sql += "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID "
3477
+ sql += "AND tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
3478
+ sql += "AND (nFiles-nFilesFinished-nFilesFailed)>0 "
3479
+ sql += "AND tabT.vo=:vo AND prodSourceLabel=:prodSourceLabel "
3480
+ sql += "AND tabT.cloud=:worldCloud "
3481
+
3482
+ if priority is not None:
3483
+ sql += "AND currentPriority>=:priority "
3484
+
3485
+ if workQueue is not None:
3486
+ if workQueue.is_global_share:
3487
+ sql += "AND gshare=:wq_name "
3488
+ sql += f"AND tabT.workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaJEDI}.jedi_work_queue WHERE queue_function = 'Resource') "
3489
+ varMap[":wq_name"] = workQueue.queue_name
3490
+ else:
3491
+ sql += "AND workQueue_ID=:wq_id "
3492
+ varMap[":wq_id"] = workQueue.queue_id
3493
+
3494
+ sql += "AND tabT.status IN (:status1,:status2,:status3,:status4) "
3495
+ sql += f"AND tabD.type IN ({INPUT_TYPES_var_str}) "
3496
+ varMap.update(INPUT_TYPES_var_map)
3497
+ varMap[":status1"] = "ready"
3498
+ varMap[":status2"] = "scouting"
3499
+ varMap[":status3"] = "running"
3500
+ varMap[":status4"] = "pending"
3501
+ sql += "GROUP BY tabT.nucleus "
3502
+ # begin transaction
3503
+ self.conn.begin()
3504
+ # set cloud
3505
+ self.cur.execute(sql + comment, varMap)
3506
+ resList = self.cur.fetchall()
3507
+ # commit
3508
+ if not self._commit():
3509
+ raise RuntimeError("Commit error")
3510
+ # loop over all nuclei
3511
+ retMap = {}
3512
+ for nucleus, worldRW in resList:
3513
+ retMap[nucleus] = worldRW
3514
+ tmpLog.debug(f"RW={str(retMap)}")
3515
+ # return
3516
+ tmpLog.debug("done")
3517
+ return retMap
3518
+ except Exception:
3519
+ # roll back
3520
+ self._rollback()
3521
+ # error
3522
+ self.dump_error_message(tmpLog)
3523
+ return None
3524
+
3525
+ # calculate WORLD RW for tasks
3526
+ def calculateTaskWorldRW_JEDI(self, jediTaskID):
3527
+ comment = " /* JediDBProxy.calculateTaskWorldRW_JEDI */"
3528
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
3529
+ tmpLog.debug("start")
3530
+ try:
3531
+ # sql to get RW
3532
+ sql = (
3533
+ "SELECT (nEvents-nEventsUsed)*(CASE "
3534
+ "WHEN cpuTime IS NULL THEN 300 "
3535
+ "WHEN cpuTimeUnit='mHS06sPerEvent' OR cpuTimeUnit='mHS06sPerEventFixed' THEN cpuTime/1000 "
3536
+ "ELSE cpuTime END) "
3537
+ )
3538
+ sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD ".format(panda_config.schemaJEDI)
3539
+ sql += "WHERE tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
3540
+ sql += "AND tabT.jediTaskID=:jediTaskID "
3541
+ varMap = {}
3542
+ varMap[":jediTaskID"] = jediTaskID
3543
+ # begin transaction
3544
+ self.conn.begin()
3545
+ # get
3546
+ self.cur.execute(sql + comment, varMap)
3547
+ resRT = self.cur.fetchone()
3548
+ # commit
3549
+ if not self._commit():
3550
+ raise RuntimeError("Commit error")
3551
+ # locked by another
3552
+ if resRT is None:
3553
+ retVal = None
3554
+ else:
3555
+ retVal = resRT[0]
3556
+ tmpLog.debug(f"RW={retVal}")
3557
+ # return
3558
+ tmpLog.debug("done")
3559
+ return retVal
3560
+ except Exception:
3561
+ # roll back
3562
+ self._rollback()
3563
+ # error
3564
+ self.dump_error_message(tmpLog)
3565
+ return None
3566
+
3567
+ def load_sw_map(self):
3568
+ comment = " /* JediDBProxy.load_sw_map */"
3569
+ tmp_log = self.create_tagged_logger(comment)
3570
+ tmp_log.debug("start")
3571
+
3572
+ sw_map = {}
3573
+
3574
+ try:
3575
+ # sql to get size
3576
+ sql = f"SELECT PANDA_QUEUE, DATA FROM {panda_config.schemaPANDA}.SW_TAGS"
3577
+ self.cur.execute(sql + comment)
3578
+ results = self.cur.fetchall()
3579
+ for panda_queue, data in results:
3580
+ sw_map[panda_queue] = json.loads(data)
3581
+
3582
+ tmp_log.debug("done")
3583
+ return sw_map
3584
+
3585
+ except Exception:
3586
+ self._rollback()
3587
+ self.dump_error_message(tmp_log)
3588
+ return None
3589
+
3590
+ def getNetworkMetrics(self, dst, keyList):
3591
+ """
3592
+ Get the network metrics from a source to all possible destinations
3593
+ :param dst: destination site
3594
+ :param keyList: activity keys.
3595
+ :return: returns a dictionary with network values in the style
3596
+ {
3597
+ <dest>: {<key>: <value>, <key>: <value>},
3598
+ <dest>: {<key>: <value>, <key>: <value>},
3599
+ ...
3600
+ }
3601
+ """
3602
+ comment = " /* JediDBProxy.getNetworkMetrics */"
3603
+ tmpLog = self.create_tagged_logger(comment)
3604
+ tmpLog.debug("start")
3605
+
3606
+ latest_validity = naive_utcnow() - datetime.timedelta(minutes=60)
3607
+
3608
+ varMap = {":dst": dst, ":latest_validity": latest_validity}
3609
+
3610
+ key_var_names_str, key_var_map = get_sql_IN_bind_variables(keyList, prefix=":key")
3611
+
3612
+ sql = f"""
3613
+ SELECT src, key, value, ts FROM {panda_config.schemaJEDI}.network_matrix_kv
3614
+ WHERE dst = :dst AND key IN ({key_var_names_str})
3615
+ AND ts > :latest_validity
3616
+ """
3617
+
3618
+ varMap.update(key_var_map)
3619
+
3620
+ self.cur.execute(sql + comment, varMap)
3621
+ resList = self.cur.fetchall()
3622
+
3623
+ networkMap = {}
3624
+ total = {}
3625
+ for res in resList:
3626
+ src, key, value, ts = res
3627
+ networkMap.setdefault(src, {})
3628
+ networkMap[src][key] = value
3629
+ total.setdefault(key, 0)
3630
+ try:
3631
+ total[key] += value
3632
+ except Exception:
3633
+ pass
3634
+ networkMap["total"] = total
3635
+ tmpLog.debug(f"network map to nucleus {dst} is: {networkMap}")
3636
+
3637
+ return networkMap
3638
+
3639
+ def getBackloggedNuclei(self):
3640
+ """
3641
+ Return a list of nuclei, which has built up transfer backlog. We will consider a nucleus as backlogged,
3642
+ when it has over 2000 output transfers queued and there are more than 3 sites with queues over
3643
+ """
3644
+
3645
+ comment = " /* JediDBProxy.getBackloggedNuclei */"
3646
+ tmpLog = self.create_tagged_logger(comment)
3647
+ tmpLog.debug("start")
3648
+
3649
+ latest_validity = naive_utcnow() - datetime.timedelta(minutes=60)
3650
+
3651
+ nqueued_cap = self.getConfigValue("taskbrokerage", "NQUEUED_NUC_CAP", "jedi")
3652
+ if nqueued_cap is None:
3653
+ nqueued_cap = 2000
3654
+
3655
+ varMap = {":latest_validity": latest_validity, ":nqueued_cap": nqueued_cap}
3656
+
3657
+ sql = f"""
3658
+ SELECT dst
3659
+ FROM {panda_config.schemaJEDI}.network_matrix_kv
3660
+ WHERE key = 'Production Output_queued'
3661
+ AND ts > :latest_validity
3662
+ GROUP BY dst
3663
+ HAVING SUM(value) > :nqueued_cap
3664
+ """
3665
+
3666
+ self.cur.execute(sql + comment, varMap)
3667
+ try:
3668
+ backlogged_nuclei = [entry[0] for entry in self.cur.fetchall()]
3669
+ except IndexError:
3670
+ backlogged_nuclei = []
3671
+
3672
+ tmpLog.debug(f"Nuclei with a long backlog are: {backlogged_nuclei}")
3673
+
3674
+ return backlogged_nuclei
3675
+
3676
+ def getPandaSiteToOutputStorageSiteMapping(self):
3677
+ """
3678
+ Get a mapping of panda sites to their storage site. We consider the storage site of the default ddm endpoint
3679
+ :return: dictionary with panda_site_name keys and site_name values
3680
+ """
3681
+ comment = " /* JediDBProxy.getPandaSiteToOutputStorageSiteMapping */"
3682
+ tmpLog = self.create_tagged_logger(comment)
3683
+ tmpLog.debug("start")
3684
+
3685
+ sql = """
3686
+ SELECT pdr.panda_site_name, de.site_name, nvl(pdr.scope, 'default')
3687
+ FROM atlas_panda.panda_ddm_relation pdr, atlas_panda.ddm_endpoint de
3688
+ WHERE pdr.default_write = 'Y'
3689
+ AND pdr.ddm_endpoint_name = de.ddm_endpoint_name
3690
+ """
3691
+
3692
+ self.cur.execute(sql + comment)
3693
+ resList = self.cur.fetchall()
3694
+ mapping = {}
3695
+
3696
+ for res in resList:
3697
+ pandaSiteName, siteName, scope = res
3698
+ mapping.setdefault(pandaSiteName, {})
3699
+ mapping[pandaSiteName][scope] = siteName
3700
+
3701
+ # tmpLog.debug('panda site to ATLAS site mapping is: {0}'.format(mapping))
3702
+
3703
+ tmpLog.debug("done")
3704
+ return mapping
3705
+
3706
+ def get_active_gshare_rtypes(self, vo):
3707
+ """
3708
+ Gets the active gshare/resource wq combinations. Active means they have at least 1 job in (assigned, activate, starting, running, ...)
3709
+ :param vo: Virtual Organization
3710
+ """
3711
+ comment = " /* DBProxy.get_active_gshare_rtypes */"
3712
+ tmp_log = self.create_tagged_logger(comment, f"vo={vo}")
3713
+ tmp_log.debug("start")
3714
+
3715
+ # define the var map of query parameters
3716
+ var_map = {":vo": vo}
3717
+
3718
+ # sql to query on pre-cached job statistics tables, creating a single result set with active gshares and resource workqueues
3719
+ sql_get_active_combinations = f"""
3720
+ WITH gshare_results AS (
3721
+ SELECT /*+ RESULT_CACHE */ gshare AS name, resource_type
3722
+ FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS
3723
+ WHERE vo=:vo
3724
+ UNION
3725
+ SELECT /*+ RESULT_CACHE */ gshare AS name, resource_type
3726
+ FROM {panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS
3727
+ WHERE vo=:vo
3728
+ ), wq_results AS (
3729
+ SELECT jwq.QUEUE_NAME AS name, jss.resource_type
3730
+ FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS jss
3731
+ JOIN {panda_config.schemaPANDA}.JEDI_WORK_QUEUE jwq ON jss.WORKQUEUE_ID = jwq.QUEUE_ID
3732
+ WHERE jwq.QUEUE_FUNCTION = 'Resource' AND jss.vo=:vo AND jwq.vo=:vo
3733
+ UNION
3734
+ SELECT jwq.QUEUE_NAME AS name, jss.resource_type
3735
+ FROM {panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS jss
3736
+ JOIN {panda_config.schemaPANDA}.JEDI_WORK_QUEUE jwq ON jss.WORKQUEUE_ID = jwq.QUEUE_ID
3737
+ WHERE jwq.QUEUE_FUNCTION = 'Resource' AND jss.vo=:vo AND jwq.vo=:vo
3738
+ )
3739
+ SELECT name, resource_type FROM gshare_results
3740
+ UNION
3741
+ SELECT name, resource_type FROM wq_results
3742
+ GROUP BY name, resource_type
3743
+ """
3744
+
3745
+ return_map = {}
3746
+ try:
3747
+ self.cur.arraysize = 1000
3748
+ self.cur.execute(f"{sql_get_active_combinations} {comment}", var_map)
3749
+ res = self.cur.fetchall()
3750
+
3751
+ # create map
3752
+ for name, resource_type in res:
3753
+ return_map.setdefault(name, [])
3754
+ return_map[name].append(resource_type)
3755
+
3756
+ tmp_log.debug("done")
3757
+ return return_map
3758
+ except Exception:
3759
+ self.dump_error_message(tmp_log)
3760
+ return {}
3761
+
3762
+ # get work queue map
3763
+ def getWorkQueueMap(self):
3764
+ self.refreshWorkQueueMap()
3765
+ return self.workQueueMap
3766
+
3767
+ # refresh work queue map
3768
+ def refreshWorkQueueMap(self):
3769
+ # avoid frequent lookup
3770
+ if self.updateTimeForWorkQueue is not None and (naive_utcnow() - self.updateTimeForWorkQueue) < datetime.timedelta(minutes=10):
3771
+ return
3772
+ comment = " /* JediDBProxy.refreshWorkQueueMap */"
3773
+ tmpLog = self.create_tagged_logger(comment)
3774
+
3775
+ leave_shares = self.get_sorted_leaves()
3776
+
3777
+ if self.workQueueMap is None:
3778
+ self.workQueueMap = WorkQueueMapper()
3779
+ # SQL
3780
+ sql = self.workQueueMap.getSqlQuery()
3781
+ try:
3782
+ # start transaction
3783
+ self.conn.begin()
3784
+ self.cur.arraysize = 1000
3785
+ self.cur.execute(sql + comment)
3786
+ res = self.cur.fetchall()
3787
+ if not self._commit():
3788
+ raise RuntimeError("Commit error")
3789
+ # make map
3790
+ self.workQueueMap.makeMap(res, leave_shares)
3791
+ tmpLog.debug("done")
3792
+ self.updateTimeForWorkQueue = naive_utcnow()
3793
+ return True
3794
+ except Exception:
3795
+ # roll back
3796
+ self._rollback()
3797
+ # error
3798
+ self.dump_error_message(tmpLog)
3799
+ return False
3800
+
3801
+
3802
+ # get entity module
3803
+ def get_entity_module(base_mod) -> EntityModule:
3804
+ return base_mod.get_composite_module("entity")