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,832 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import sys
5
+ from datetime import datetime, timedelta
6
+
7
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
8
+ from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
9
+
10
+ from pandaserver.config import panda_config
11
+ from pandaserver.srvcore import CoreUtils
12
+ from pandaserver.taskbuffer import ErrorCode, JobUtils
13
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule, varNUMBER
14
+ from pandaserver.taskbuffer.db_proxy_mods.entity_module import get_entity_module
15
+ from pandaserver.taskbuffer.JobSpec import JobSpec
16
+ from pandaserver.workflow.workflow_base import (
17
+ WFDataSpec,
18
+ WFDataStatus,
19
+ WFStepSpec,
20
+ WFStepStatus,
21
+ WorkflowSpec,
22
+ WorkflowStatus,
23
+ )
24
+
25
+
26
+ # Module class to define methods related to workflow
27
+ class WorkflowModule(BaseModule):
28
+ # constructor
29
+ def __init__(self, log_stream: LogWrapper):
30
+ super().__init__(log_stream)
31
+
32
+ def get_workflow(self, workflow_id: int) -> WorkflowSpec | None:
33
+ """
34
+ Retrieve a workflow specification by its ID
35
+
36
+ Args:
37
+ workflow_id (int): ID of the workflow to retrieve
38
+
39
+ Returns:
40
+ WorkflowSpec | None: The workflow specification if found, otherwise None
41
+ """
42
+ comment = " /* DBProxy.get_workflow */"
43
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
44
+ sql = f"SELECT {WorkflowSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflows " f"WHERE workflow_id=:workflow_id "
45
+ var_map = {":workflow_id": workflow_id}
46
+ self.cur.execute(sql + comment, var_map)
47
+ res_list = self.cur.fetchall()
48
+ if res_list is not None:
49
+ if len(res_list) > 1:
50
+ tmp_log.error("more than one workflows; unexpected")
51
+ else:
52
+ for res in res_list:
53
+ workflow_spec = WorkflowSpec()
54
+ workflow_spec.pack(res)
55
+ return workflow_spec
56
+ else:
57
+ tmp_log.warning("no workflow found; skipped")
58
+ return None
59
+
60
+ def get_workflow_step(self, step_id: int) -> WFStepSpec | None:
61
+ """
62
+ Retrieve a workflow step specification by its ID
63
+
64
+ Args:
65
+ step_id (int): ID of the workflow step to retrieve
66
+
67
+ Returns:
68
+ WFStepSpec | None: The workflow step specification if found, otherwise None
69
+ """
70
+ comment = " /* DBProxy.get_workflow_step */"
71
+ tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}")
72
+ sql = f"SELECT {WFStepSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_steps " f"WHERE step_id=:step_id "
73
+ var_map = {":step_id": step_id}
74
+ self.cur.execute(sql + comment, var_map)
75
+ res_list = self.cur.fetchall()
76
+ if res_list is not None:
77
+ if len(res_list) > 1:
78
+ tmp_log.error("more than one steps; unexpected")
79
+ else:
80
+ for res in res_list:
81
+ step_spec = WFStepSpec()
82
+ step_spec.pack(res)
83
+ return step_spec
84
+ else:
85
+ tmp_log.warning("no step found; skipped")
86
+ return None
87
+
88
+ def get_workflow_data(self, data_id: int) -> WFDataSpec | None:
89
+ """
90
+ Retrieve a workflow data specification by its ID
91
+
92
+ Args:
93
+ data_id (int): ID of the workflow data to retrieve
94
+
95
+ Returns:
96
+ WFDataSpec | None: The workflow data specification if found, otherwise None
97
+ """
98
+ comment = " /* DBProxy.get_workflow_data */"
99
+ tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}")
100
+ sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE data_id=:data_id "
101
+ var_map = {":data_id": data_id}
102
+ self.cur.execute(sql + comment, var_map)
103
+ res_list = self.cur.fetchall()
104
+ if res_list is not None:
105
+ if len(res_list) > 1:
106
+ tmp_log.error("more than one data; unexpected")
107
+ else:
108
+ for res in res_list:
109
+ data_spec = WFDataSpec()
110
+ data_spec.pack(res)
111
+ return data_spec
112
+ else:
113
+ tmp_log.warning("no data found; skipped")
114
+ return None
115
+
116
+ def get_workflow_data_by_name(self, name: str, workflow_id: int | None) -> WFDataSpec | None:
117
+ """
118
+ Retrieve a workflow data specification by its name and workflow ID
119
+
120
+ Args:
121
+ name (str): Name of the workflow data to retrieve
122
+ workflow_id (int | None): ID of the workflow to which the data belongs (optional)
123
+
124
+ Returns:
125
+ WFDataSpec | None: The workflow data specification if found, otherwise None
126
+ """
127
+ comment = " /* DBProxy.get_workflow_data_by_name */"
128
+ tmp_log = self.create_tagged_logger(comment, f"name={name}, workflow_id={workflow_id}")
129
+ sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE name=:name "
130
+ var_map = {":name": name}
131
+ if workflow_id is not None:
132
+ sql += "AND workflow_id=:workflow_id "
133
+ var_map[":workflow_id"] = workflow_id
134
+ self.cur.execute(sql + comment, var_map)
135
+ res_list = self.cur.fetchall()
136
+ if res_list is not None:
137
+ if len(res_list) > 1:
138
+ tmp_log.error("more than one data; unexpected")
139
+ return None
140
+ else:
141
+ for res in res_list:
142
+ data_spec = WFDataSpec()
143
+ data_spec.pack(res)
144
+ return data_spec
145
+ else:
146
+ tmp_log.warning("no data found; skipped")
147
+ return None
148
+
149
+ def get_steps_of_workflow(self, workflow_id: int, status_filter_list: list | None = None, status_exclusion_list: list | None = None) -> list[WFStepSpec]:
150
+ """
151
+ Retrieve all workflow steps for a given workflow ID
152
+
153
+ Args:
154
+ workflow_id (int): ID of the workflow to retrieve steps for
155
+ status_filter_list (list | None): List of statuses to filter the steps by (optional)
156
+ status_exclusion_list (list | None): List of statuses to exclude the steps by (optional)
157
+
158
+ Returns:
159
+ list[WFStepSpec]: List of workflow step specifications
160
+ """
161
+ comment = " /* DBProxy.get_steps_of_workflow */"
162
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
163
+ sql = f"SELECT {WFStepSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_steps " f"WHERE workflow_id=:workflow_id "
164
+ var_map = {":workflow_id": workflow_id}
165
+ if status_filter_list:
166
+ status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
167
+ sql += f"AND status IN ({status_var_names_str}) "
168
+ var_map.update(status_var_map)
169
+ if status_exclusion_list:
170
+ antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
171
+ sql += f"AND status NOT IN ({antistatus_var_names_str}) "
172
+ var_map.update(antistatus_var_map)
173
+ sql += "ORDER BY step_id "
174
+ self.cur.execute(sql + comment, var_map)
175
+ res_list = self.cur.fetchall()
176
+ if res_list is not None:
177
+ step_specs = []
178
+ for res in res_list:
179
+ step_spec = WFStepSpec()
180
+ step_spec.pack(res)
181
+ step_specs.append(step_spec)
182
+ return step_specs
183
+ else:
184
+ tmp_log.warning("no steps found; skipped")
185
+ return []
186
+
187
+ def get_data_of_workflow(
188
+ self, workflow_id: int, status_filter_list: list | None = None, status_exclusion_list: list | None = None, type_filter_list: list | None = None
189
+ ) -> list[WFDataSpec]:
190
+ """
191
+ Retrieve all workflow data for a given workflow ID
192
+
193
+ Args:
194
+ workflow_id (int): ID of the workflow to retrieve data for
195
+ status_filter_list (list | None): List of statuses to filter the data by (optional)
196
+ status_exclusion_list (list | None): List of statuses to exclude the data by (optional)
197
+ type_filter_list (list | None): List of types to filter the data by (optional)
198
+
199
+ Returns:
200
+ list[WFDataSpec]: List of workflow data specifications
201
+ """
202
+ comment = " /* DBProxy.get_data_of_workflow */"
203
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
204
+ sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE workflow_id=:workflow_id "
205
+ var_map = {":workflow_id": workflow_id}
206
+ if status_filter_list:
207
+ status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
208
+ sql += f"AND status IN ({status_var_names_str}) "
209
+ var_map.update(status_var_map)
210
+ if status_exclusion_list:
211
+ antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
212
+ sql += f"AND status NOT IN ({antistatus_var_names_str}) "
213
+ var_map.update(antistatus_var_map)
214
+ if type_filter_list:
215
+ type_var_names_str, type_var_map = get_sql_IN_bind_variables(type_filter_list, prefix=":type")
216
+ sql += f"AND type IN ({type_var_names_str}) "
217
+ var_map.update(type_var_map)
218
+ sql += "ORDER BY data_id "
219
+ self.cur.execute(sql + comment, var_map)
220
+ res_list = self.cur.fetchall()
221
+ if res_list is not None:
222
+ data_specs = []
223
+ for res in res_list:
224
+ data_spec = WFDataSpec()
225
+ data_spec.pack(res)
226
+ data_specs.append(data_spec)
227
+ return data_specs
228
+ else:
229
+ tmp_log.warning("no data found; skipped")
230
+ return []
231
+
232
+ def query_workflows(
233
+ self, status_filter_list: list | None = None, status_exclusion_list: list | None = None, check_interval_sec: int = 300
234
+ ) -> list[WorkflowSpec]:
235
+ """
236
+ Retrieve list of workflows with optional status filtering
237
+
238
+ Args:
239
+ status_filter_list (list | None): List of statuses to filter the workflows by (optional)
240
+ status_exclusion_list (list | None): List of statuses to exclude the workflows by (optional)
241
+ check_interval_sec (int): Time in seconds to wait between checks (default: 300)
242
+
243
+ Returns:
244
+ list[WorkflowSpec]: List of workflow specifications
245
+ """
246
+ comment = " /* DBProxy.query_workflows */"
247
+ tmp_log = self.create_tagged_logger(comment, "query_workflows")
248
+ tmp_log.debug(f"start, status_filter_list={status_filter_list} status_exclusion_list={status_exclusion_list} check_interval_sec={check_interval_sec}")
249
+ sql = f"SELECT {WorkflowSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflows " f"WHERE (check_time IS NULL OR check_time<:check_time) "
250
+ now_time = naive_utcnow()
251
+ var_map = {":check_time": now_time - timedelta(seconds=check_interval_sec)}
252
+ if status_filter_list:
253
+ status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
254
+ sql += f"AND status IN ({status_var_names_str}) "
255
+ var_map.update(status_var_map)
256
+ if status_exclusion_list:
257
+ antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
258
+ sql += f"AND status NOT IN ({antistatus_var_names_str}) "
259
+ var_map.update(antistatus_var_map)
260
+ sql += "ORDER BY check_time, creation_time "
261
+ self.cur.execute(sql + comment, var_map)
262
+ res_list = self.cur.fetchall()
263
+ if res_list is not None:
264
+ workflow_specs = []
265
+ for res in res_list:
266
+ workflow_spec = WorkflowSpec()
267
+ workflow_spec.pack(res)
268
+ workflow_specs.append(workflow_spec)
269
+ tmp_log.debug(f"got {len(workflow_specs)} workflows")
270
+ return workflow_specs
271
+ else:
272
+ tmp_log.warning("no workflows found; skipped")
273
+ return []
274
+
275
+ def lock_workflow(self, workflow_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
276
+ """
277
+ Lock a workflow to prevent concurrent modifications
278
+
279
+ Args:
280
+ workflow_id (int): ID of the workflow to lock
281
+ locked_by (str): Identifier of the entity locking the workflow
282
+ lock_expiration_sec (int): Time in seconds after which the lock expires
283
+
284
+ Returns:
285
+ bool | None: True if the lock was acquired, False if not, None if an error occurred
286
+ """
287
+ comment = " /* DBProxy.lock_workflow */"
288
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}, locked_by={locked_by}")
289
+ tmp_log.debug("start")
290
+ try:
291
+ now_time = naive_utcnow()
292
+ sql_lock = (
293
+ f"UPDATE {panda_config.schemaJEDI}.workflows "
294
+ "SET locked_by=:locked_by, lock_time=:lock_time "
295
+ "WHERE workflow_id=:workflow_id "
296
+ "AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
297
+ )
298
+ var_map = {
299
+ ":locked_by": locked_by,
300
+ ":lock_time": now_time,
301
+ ":workflow_id": workflow_id,
302
+ ":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
303
+ }
304
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
305
+ cur.execute(sql_lock + comment, var_map)
306
+ row_count = cur.rowcount
307
+ if row_count is None:
308
+ tmp_log.error(f"failed to update DB to lock; skipped")
309
+ elif row_count > 1:
310
+ tmp_log.error(f"more than one workflow updated to lock; unexpected")
311
+ elif row_count == 0:
312
+ # no row updated; did not get the lock
313
+ tmp_log.debug(f"did not get lock; skipped")
314
+ return False
315
+ elif row_count == 1:
316
+ # successfully locked the workflow
317
+ tmp_log.debug(f"got lock")
318
+ return True
319
+ except Exception as e:
320
+ tmp_log.error(f"failed to lock workflow: {e}")
321
+
322
+ def unlock_workflow(self, workflow_id: int, locked_by: str) -> bool | None:
323
+ """
324
+ Unlock a workflow to allow modifications
325
+
326
+ Args:
327
+ workflow_id (int): ID of the workflow to unlock
328
+ locked_by (str): Identifier of the entity unlocking the workflow
329
+
330
+ Returns:
331
+ bool | None: True if the unlock was successful, False if not, None if an error occurred
332
+ """
333
+ comment = " /* DBProxy.unlock_workflow */"
334
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}, locked_by={locked_by}")
335
+ tmp_log.debug("start")
336
+ try:
337
+ sql_unlock = (
338
+ f"UPDATE {panda_config.schemaJEDI}.workflows " "SET locked_by=NULL, lock_time=NULL " "WHERE workflow_id=:workflow_id AND locked_by=:locked_by"
339
+ )
340
+ var_map = {":workflow_id": workflow_id, ":locked_by": locked_by}
341
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
342
+ cur.execute(sql_unlock + comment, var_map)
343
+ row_count = cur.rowcount
344
+ if row_count is None:
345
+ tmp_log.error(f"failed to update DB to unlock; skipped")
346
+ elif row_count > 1:
347
+ tmp_log.error(f"more than one workflow updated to unlock; unexpected")
348
+ elif row_count == 0:
349
+ # no row updated; did not get the unlock
350
+ tmp_log.debug(f"no workflow updated to unlock; skipped")
351
+ return False
352
+ elif row_count == 1:
353
+ # successfully unlocked the workflow
354
+ tmp_log.debug(f"released lock")
355
+ return True
356
+ except Exception as e:
357
+ tmp_log.error(f"failed to unlock workflow: {e}")
358
+
359
+ def lock_workflow_step(self, step_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
360
+ """
361
+ Lock a workflow step to prevent concurrent modifications
362
+
363
+ Args:
364
+ step_id (int): ID of the workflow step to lock
365
+ locked_by (str): Identifier of the entity locking the workflow step
366
+ lock_expiration_sec (int): Time in seconds after which the lock expires
367
+
368
+ Returns:
369
+ bool | None: True if the lock was acquired, False if not, None if an error occurred
370
+ """
371
+ comment = " /* DBProxy.lock_workflow_step */"
372
+ tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}, locked_by={locked_by}")
373
+ tmp_log.debug("start")
374
+ try:
375
+ now_time = naive_utcnow()
376
+ sql_lock = (
377
+ f"UPDATE {panda_config.schemaJEDI}.workflow_steps "
378
+ "SET locked_by=:locked_by, lock_time=:lock_time "
379
+ "WHERE step_id=:step_id "
380
+ "AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
381
+ )
382
+ var_map = {
383
+ ":locked_by": locked_by,
384
+ ":lock_time": now_time,
385
+ ":step_id": step_id,
386
+ ":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
387
+ }
388
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
389
+ cur.execute(sql_lock + comment, var_map)
390
+ row_count = cur.rowcount
391
+ if row_count is None:
392
+ tmp_log.error(f"failed to update DB to lock; skipped")
393
+ elif row_count > 1:
394
+ tmp_log.error(f"more than one step updated to lock; unexpected")
395
+ elif row_count == 0:
396
+ # no row updated; did not get the lock
397
+ tmp_log.debug(f"did not get lock; skipped")
398
+ return False
399
+ elif row_count == 1:
400
+ # successfully locked the workflow step
401
+ tmp_log.debug(f"got lock")
402
+ return True
403
+ except Exception as e:
404
+ tmp_log.error(f"failed to lock workflow step: {e}")
405
+
406
+ def unlock_workflow_step(self, step_id: int, locked_by: str) -> bool | None:
407
+ """
408
+ Unlock a workflow step to allow modifications
409
+
410
+ Args:
411
+ step_id (int): ID of the workflow step to unlock
412
+ locked_by (str): Identifier of the entity unlocking the workflow step
413
+
414
+ Returns:
415
+ bool | None: True if the unlock was successful, False if not, None if an error occurred
416
+ """
417
+ comment = " /* DBProxy.unlock_workflow_step */"
418
+ tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}, locked_by={locked_by}")
419
+ tmp_log.debug("start")
420
+ try:
421
+ sql_unlock = (
422
+ f"UPDATE {panda_config.schemaJEDI}.workflow_steps " "SET locked_by=NULL, lock_time=NULL " "WHERE step_id=:step_id AND locked_by=:locked_by"
423
+ )
424
+ var_map = {":step_id": step_id, ":locked_by": locked_by}
425
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
426
+ cur.execute(sql_unlock + comment, var_map)
427
+ row_count = cur.rowcount
428
+ if row_count is None:
429
+ tmp_log.error(f"failed to update DB to unlock; skipped")
430
+ elif row_count > 1:
431
+ tmp_log.error(f"more than one step updated to unlock; unexpected")
432
+ elif row_count == 0:
433
+ # no row updated; did not get the unlock
434
+ tmp_log.debug(f"no step updated to unlock; skipped")
435
+ return False
436
+ elif row_count == 1:
437
+ # successfully unlocked the workflow step
438
+ tmp_log.debug(f"released lock")
439
+ return True
440
+ except Exception as e:
441
+ tmp_log.error(f"failed to unlock workflow step: {e}")
442
+
443
+ def lock_workflow_data(self, data_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
444
+ """
445
+ Lock a workflow data to prevent concurrent modifications
446
+
447
+ Args:
448
+ data_id (int): ID of the workflow data to lock
449
+ locked_by (str): Identifier of the entity locking the workflow data
450
+ lock_expiration_sec (int): Time in seconds after which the lock expires
451
+
452
+ Returns:
453
+ bool | None: True if the lock was acquired, False if not, None if an error occurred
454
+ """
455
+ comment = " /* DBProxy.lock_workflow_data */"
456
+ tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}, locked_by={locked_by}")
457
+ tmp_log.debug("start")
458
+ try:
459
+ now_time = naive_utcnow()
460
+ sql_lock = (
461
+ f"UPDATE {panda_config.schemaJEDI}.workflow_data "
462
+ "SET locked_by=:locked_by, lock_time=:lock_time "
463
+ "WHERE data_id=:data_id "
464
+ "AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
465
+ )
466
+ var_map = {
467
+ ":locked_by": locked_by,
468
+ ":lock_time": now_time,
469
+ ":data_id": data_id,
470
+ ":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
471
+ }
472
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
473
+ cur.execute(sql_lock + comment, var_map)
474
+ row_count = cur.rowcount
475
+ if row_count is None:
476
+ tmp_log.error(f"failed to update DB to lock; skipped")
477
+ elif row_count > 1:
478
+ tmp_log.error(f"more than one data updated to lock; unexpected")
479
+ elif row_count == 0:
480
+ # no row updated; did not get the lock
481
+ tmp_log.debug(f"did not get lock; skipped")
482
+ return False
483
+ elif row_count == 1:
484
+ # successfully locked the workflow data
485
+ tmp_log.debug(f"got lock")
486
+ return True
487
+ except Exception as e:
488
+ tmp_log.error(f"failed to lock workflow data: {e}")
489
+
490
+ def unlock_workflow_data(self, data_id: int, locked_by: str) -> bool | None:
491
+ """
492
+ Unlock a workflow data to allow modifications
493
+
494
+ Args:
495
+ data_id (int): ID of the workflow data to unlock
496
+ locked_by (str): Identifier of the entity unlocking the workflow data
497
+
498
+ Returns:
499
+ bool | None: True if the unlock was successful, False if not, None if an error occurred
500
+ """
501
+ comment = " /* DBProxy.unlock_workflow_data */"
502
+ tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}, locked_by={locked_by}")
503
+ tmp_log.debug("start")
504
+ try:
505
+ sql_unlock = (
506
+ f"UPDATE {panda_config.schemaJEDI}.workflow_data " "SET locked_by=NULL, lock_time=NULL " "WHERE data_id=:data_id AND locked_by=:locked_by"
507
+ )
508
+ var_map = {":data_id": data_id, ":locked_by": locked_by}
509
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
510
+ cur.execute(sql_unlock + comment, var_map)
511
+ row_count = cur.rowcount
512
+ if row_count is None:
513
+ tmp_log.error(f"failed to update DB to unlock; skipped")
514
+ elif row_count > 1:
515
+ tmp_log.error(f"more than one data updated to unlock; unexpected")
516
+ elif row_count == 0:
517
+ # no row updated; did not get the unlock
518
+ tmp_log.debug(f"no data updated to unlock; skipped")
519
+ return False
520
+ elif row_count == 1:
521
+ # successfully unlocked the workflow data
522
+ tmp_log.debug(f"released lock")
523
+ return True
524
+ except Exception as e:
525
+ tmp_log.error(f"failed to unlock workflow data: {e}")
526
+
527
+ def insert_workflow(self, workflow_spec: WorkflowSpec) -> int | None:
528
+ """
529
+ Insert a new workflow specification into the database
530
+
531
+ Args:
532
+ workflow_spec (WorkflowSpec): The workflow specification to insert
533
+
534
+ Returns:
535
+ int | None: The ID of the inserted workflow if successful, otherwise None
536
+ """
537
+ comment = " /* DBProxy.insert_workflow */"
538
+ tmp_log = self.create_tagged_logger(comment, "")
539
+ tmp_log.debug("start")
540
+ try:
541
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
542
+ # sql to insert workflow
543
+ workflow_spec.creation_time = naive_utcnow()
544
+ sql_insert = (
545
+ f"INSERT INTO {panda_config.schemaJEDI}.workflows ({workflow_spec.columnNames()}) "
546
+ f"{workflow_spec.bindValuesExpression()} "
547
+ f"RETURNING workflow_id INTO :new_workflow_id "
548
+ )
549
+ var_map = workflow_spec.valuesMap(useSeq=True)
550
+ var_map[":new_workflow_id"] = self.cur.var(varNUMBER)
551
+ self.cur.execute(sql_insert + comment, var_map)
552
+ workflow_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_workflow_id"])))
553
+ tmp_log.debug(f"inserted workflow_id={workflow_id}")
554
+ return workflow_id
555
+ except Exception:
556
+ return None
557
+
558
+ def insert_workflow_step(self, step_spec: WFStepSpec) -> int | None:
559
+ """
560
+ Insert a new workflow step specification into the database
561
+
562
+ Args:
563
+ step_spec (WFStepSpec): The workflow step specification to insert
564
+
565
+ Returns:
566
+ int | None: The ID of the inserted workflow step if successful, otherwise None
567
+ """
568
+ comment = " /* DBProxy.insert_workflow_step */"
569
+ tmp_log = self.create_tagged_logger(comment, "")
570
+ tmp_log.debug("start")
571
+ try:
572
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
573
+ # sql to insert workflow step
574
+ step_spec.creation_time = naive_utcnow()
575
+ sql_insert = (
576
+ f"INSERT INTO {panda_config.schemaJEDI}.workflow_steps ({step_spec.columnNames()}) "
577
+ f"{step_spec.bindValuesExpression()} "
578
+ f"RETURNING step_id INTO :new_step_id "
579
+ )
580
+ var_map = step_spec.valuesMap(useSeq=True)
581
+ var_map[":new_step_id"] = self.cur.var(varNUMBER)
582
+ self.cur.execute(sql_insert + comment, var_map)
583
+ step_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_step_id"])))
584
+ tmp_log.debug(f"inserted step_id={step_id}")
585
+ return step_id
586
+ except Exception:
587
+ return None
588
+
589
+ def insert_workflow_data(self, data_spec: WFDataSpec) -> int | None:
590
+ """
591
+ Insert a new workflow data specification into the database
592
+
593
+ Args:
594
+ data_spec (WFDataSpec): The workflow data specification to insert
595
+
596
+ Returns:
597
+ int | None: The ID of the inserted workflow data if successful, otherwise None
598
+ """
599
+ comment = " /* DBProxy.insert_workflow_data */"
600
+ tmp_log = self.create_tagged_logger(comment, "")
601
+ tmp_log.debug("start")
602
+ try:
603
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
604
+ # sql to insert workflow data
605
+ data_spec.creation_time = naive_utcnow()
606
+ sql_insert = (
607
+ f"INSERT INTO {panda_config.schemaJEDI}.workflow_data ({data_spec.columnNames()}) "
608
+ f"{data_spec.bindValuesExpression()} "
609
+ f"RETURNING data_id INTO :new_data_id "
610
+ )
611
+ var_map = data_spec.valuesMap(useSeq=True)
612
+ var_map[":new_data_id"] = self.cur.var(varNUMBER)
613
+ self.cur.execute(sql_insert + comment, var_map)
614
+ data_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_data_id"])))
615
+ tmp_log.debug(f"inserted data_id={data_id}")
616
+ return data_id
617
+ except Exception:
618
+ return None
619
+
620
+ def update_workflow(self, workflow_spec: WorkflowSpec) -> WorkflowSpec | None:
621
+ """
622
+ Update a workflow specification in the database
623
+
624
+ Args:
625
+ workflow_spec (WorkflowSpec): The workflow specification to update
626
+
627
+ Returns:
628
+ WorkflowSpec | None: The updated workflow specification if successful, otherwise None
629
+ """
630
+ comment = " /* DBProxy.update_workflow */"
631
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_spec.workflow_id}")
632
+ tmp_log.debug("start")
633
+ try:
634
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
635
+ # sql to update workflow
636
+ workflow_spec.modification_time = naive_utcnow()
637
+ sql_update = (
638
+ f"UPDATE {panda_config.schemaJEDI}.workflows " f"SET {workflow_spec.bindUpdateChangesExpression()} " "WHERE workflow_id=:workflow_id "
639
+ )
640
+ var_map = workflow_spec.valuesMap(useSeq=False, onlyChanged=True)
641
+ var_map[":workflow_id"] = workflow_spec.workflow_id
642
+ cur.execute(sql_update + comment, var_map)
643
+ tmp_log.debug(f"updated {workflow_spec.bindUpdateChangesExpression()}")
644
+ return workflow_spec
645
+ except Exception:
646
+ return None
647
+
648
+ def update_workflow_step(self, step_spec: WFStepSpec) -> WFStepSpec | None:
649
+ """
650
+ Update a workflow step specification in the database
651
+
652
+ Args:
653
+ step_spec (WFStepSpec): The workflow step specification to update
654
+
655
+ Returns:
656
+ WFStepSpec | None: The updated workflow step specification if successful, otherwise None
657
+ """
658
+ comment = " /* DBProxy.update_workflow_step */"
659
+ tmp_log = self.create_tagged_logger(comment, f"step_id={step_spec.step_id}")
660
+ tmp_log.debug("start")
661
+ try:
662
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
663
+ # sql to update workflow step
664
+ step_spec.modification_time = naive_utcnow()
665
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.workflow_steps " f"SET {step_spec.bindUpdateChangesExpression()} " "WHERE step_id=:step_id "
666
+ var_map = step_spec.valuesMap(useSeq=False, onlyChanged=True)
667
+ var_map[":step_id"] = step_spec.step_id
668
+ cur.execute(sql_update + comment, var_map)
669
+ tmp_log.debug(f"updated {step_spec.bindUpdateChangesExpression()}")
670
+ return step_spec
671
+ except Exception:
672
+ return None
673
+
674
+ def update_workflow_data(self, data_spec: WFDataSpec) -> WFDataSpec | None:
675
+ """
676
+ Update a workflow data specification in the database
677
+
678
+ Args:
679
+ data_spec (WFDataSpec): The workflow data specification to update
680
+
681
+ Returns:
682
+ WFDataSpec | None: The updated workflow data specification if successful, otherwise None
683
+ """
684
+ comment = " /* DBProxy.update_workflow_data */"
685
+ tmp_log = self.create_tagged_logger(comment, f"data_id={data_spec.data_id}")
686
+ tmp_log.debug("start")
687
+ try:
688
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
689
+ # sql to update workflow data
690
+ data_spec.modification_time = naive_utcnow()
691
+ sql_update = f"UPDATE {panda_config.schemaJEDI}.workflow_data " f"SET {data_spec.bindUpdateChangesExpression()} " "WHERE data_id=:data_id "
692
+ var_map = data_spec.valuesMap(useSeq=False, onlyChanged=True)
693
+ var_map[":data_id"] = data_spec.data_id
694
+ cur.execute(sql_update + comment, var_map)
695
+ tmp_log.debug(f"updated {data_spec.bindUpdateChangesExpression()}")
696
+ return data_spec
697
+ except Exception:
698
+ return None
699
+
700
+ def upsert_workflow_entities(
701
+ self,
702
+ workflow_id: int | None,
703
+ actions_dict: dict | None = None,
704
+ workflow_spec: WorkflowSpec | None = None,
705
+ step_specs: list[WFStepSpec] | None = None,
706
+ data_specs: list[WFDataSpec] | None = None,
707
+ ) -> dict | None:
708
+ """
709
+ Update or insert (if not existing) steps and data associated with a workflow within a transaction
710
+
711
+ Args:
712
+ workflow_id (int | None): ID of the workflow to update, or None if to insert
713
+ actions_dict (dict | None): Dictionary of actions (insert, update, or None) to perform on the entities (workflow, steps, data), e.g. {"workflow": None, "steps": "insert", "data": "update"}
714
+ workflow_spec (WorkflowSpec|None): The workflow specification to update or insert
715
+ step_specs (list[WFStepSpec]|None): List of workflow step specifications to update or insert
716
+ data_specs (list[WFDataSpec]|None): List of workflow data specifications to update or insert
717
+
718
+ Returns:
719
+ dict | None: Dictionary containing the number of steps and data upserted, or None if an error occurred
720
+ """
721
+ comment = " /* DBProxy.upsert_workflow_entities */"
722
+ # Determine actions of each entity
723
+ action_of_workflow = None
724
+ action_of_steps = None
725
+ action_of_data = None
726
+ if actions_dict:
727
+ if (tmp_action_of_workflow := actions_dict.get("workflow")) and workflow_spec:
728
+ if tmp_action_of_workflow == "insert" and workflow_id is None:
729
+ action_of_workflow = "insert"
730
+ elif tmp_action_of_workflow == "update" and workflow_id is not None and workflow_spec.workflow_id == workflow_id:
731
+ action_of_workflow = "update"
732
+ action_of_steps = actions_dict.get("steps") if (workflow_id and step_specs) else None
733
+ action_of_data = actions_dict.get("data") if (workflow_id and data_specs) else None
734
+ actions_dict = {
735
+ "workflow": action_of_workflow,
736
+ "steps": action_of_steps,
737
+ "data": action_of_data,
738
+ }
739
+ # log
740
+ tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
741
+ tmp_log.debug(f"start, actions={actions_dict}")
742
+ # skip if no action specified
743
+ if not any(actions_dict.values()):
744
+ tmp_log.warning("no action specified; skipped")
745
+ return None
746
+ try:
747
+ n_steps_upserted = 0
748
+ n_data_upserted = 0
749
+ with self.transaction(tmp_log=tmp_log) as (cur, _):
750
+ # action for data
751
+ if action_of_data == "insert":
752
+ for data_spec in data_specs:
753
+ data_spec.creation_time = naive_utcnow()
754
+ sql_insert = (
755
+ f"INSERT INTO {panda_config.schemaJEDI}.workflow_data ({data_spec.columnNames()}) "
756
+ f"{data_spec.bindValuesExpression()} "
757
+ f"RETURNING data_id INTO :new_data_id "
758
+ )
759
+ var_map = data_spec.valuesMap(useSeq=True)
760
+ var_map[":new_data_id"] = self.cur.var(varNUMBER)
761
+ self.cur.execute(sql_insert + comment, var_map)
762
+ data_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_data_id"])))
763
+ data_spec.data_id = data_id
764
+ n_data_upserted += 1
765
+ tmp_log.debug(f"inserted a data workflow_id={workflow_id} data_id={data_id}")
766
+ elif action_of_data == "update":
767
+ for data_spec in data_specs:
768
+ data_spec.modification_time = naive_utcnow()
769
+ sql_update = (
770
+ f"UPDATE {panda_config.schemaJEDI}.workflow_data " f"SET {data_spec.bindUpdateChangesExpression()} " "WHERE data_id=:data_id "
771
+ )
772
+ var_map = data_spec.valuesMap(useSeq=False, onlyChanged=True)
773
+ var_map[":data_id"] = data_spec.data_id
774
+ self.cur.execute(sql_update + comment, var_map)
775
+ n_data_upserted += 1
776
+ tmp_log.debug(f"updated a data workflow_id={workflow_id} data_id={data_spec.data_id}")
777
+ # action for steps
778
+ if action_of_steps == "insert":
779
+ for step_spec in step_specs:
780
+ step_spec.creation_time = naive_utcnow()
781
+ sql_insert = (
782
+ f"INSERT INTO {panda_config.schemaJEDI}.workflow_steps ({step_spec.columnNames()}) "
783
+ f"{step_spec.bindValuesExpression()} "
784
+ f"RETURNING step_id INTO :new_step_id "
785
+ )
786
+ var_map = step_spec.valuesMap(useSeq=True)
787
+ var_map[":new_step_id"] = self.cur.var(varNUMBER)
788
+ self.cur.execute(sql_insert + comment, var_map)
789
+ step_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_step_id"])))
790
+ step_spec.step_id = step_id
791
+ n_steps_upserted += 1
792
+ tmp_log.debug(f"inserted a step workflow_id={workflow_id} step_id={step_id}")
793
+ elif action_of_steps == "update":
794
+ for step_spec in step_specs:
795
+ step_spec.modification_time = naive_utcnow()
796
+ sql_update = (
797
+ f"UPDATE {panda_config.schemaJEDI}.workflow_steps " f"SET {step_spec.bindUpdateChangesExpression()} " "WHERE step_id=:step_id "
798
+ )
799
+ var_map = step_spec.valuesMap(useSeq=False, onlyChanged=True)
800
+ var_map[":step_id"] = step_spec.step_id
801
+ self.cur.execute(sql_update + comment, var_map)
802
+ n_steps_upserted += 1
803
+ tmp_log.debug(f"updated a step workflow_id={workflow_id} step_id={step_spec.step_id}")
804
+ # action for workflow
805
+ if action_of_workflow == "insert":
806
+ workflow_spec.creation_time = naive_utcnow()
807
+ sql_insert = (
808
+ f"INSERT INTO {panda_config.schemaJEDI}.workflows ({workflow_spec.columnNames()}) "
809
+ f"{workflow_spec.bindValuesExpression()} "
810
+ f"RETURNING workflow_id INTO :new_workflow_id "
811
+ )
812
+ var_map = workflow_spec.valuesMap(useSeq=True)
813
+ var_map[":new_workflow_id"] = self.cur.var(varNUMBER)
814
+ self.cur.execute(sql_insert + comment, var_map)
815
+ workflow_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_workflow_id"])))
816
+ workflow_spec.workflow_id = workflow_id
817
+ tmp_log.debug(f"inserted a workflow workflow_id={workflow_id}")
818
+ elif action_of_workflow == "update":
819
+ workflow_spec.modification_time = naive_utcnow()
820
+ sql_update = (
821
+ f"UPDATE {panda_config.schemaJEDI}.workflows " f"SET {workflow_spec.bindUpdateChangesExpression()} " "WHERE workflow_id=:workflow_id "
822
+ )
823
+ var_map = workflow_spec.valuesMap(useSeq=False, onlyChanged=True)
824
+ var_map[":workflow_id"] = workflow_spec.workflow_id
825
+ self.cur.execute(sql_update + comment, var_map)
826
+ tmp_log.debug(f"updated a workflow workflow_id={workflow_spec.workflow_id}")
827
+ tmp_log.debug("actions completed")
828
+ # Summary
829
+ tmp_log.debug(f"done, actions={actions_dict}, upserted workflow_id={workflow_id} with {n_steps_upserted} steps and {n_data_upserted} data")
830
+ return {"workflow_id": workflow_id, "steps": n_steps_upserted, "data": n_data_upserted}
831
+ except Exception:
832
+ return None