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,942 @@
1
+ import datetime
2
+ import gc
3
+ import gzip
4
+ import json
5
+ import os
6
+ import re
7
+ import struct
8
+ import sys
9
+ import traceback
10
+ import uuid
11
+ import zlib
12
+ from pathlib import Path
13
+ from typing import Dict, List
14
+
15
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
16
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
17
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
18
+ from werkzeug.datastructures import FileStorage
19
+
20
+ from pandaserver.api.v1.common import (
21
+ generate_response,
22
+ get_dn,
23
+ get_endpoint,
24
+ has_production_role,
25
+ request_validation,
26
+ )
27
+ from pandaserver.config import panda_config
28
+ from pandaserver.jobdispatcher import Protocol
29
+ from pandaserver.srvcore import CoreUtils
30
+ from pandaserver.srvcore.panda_request import PandaRequest
31
+ from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
32
+ from pandaserver.userinterface import Client
33
+
34
+ _logger = PandaLogger().getLogger("api_file_server")
35
+
36
+ # Skip registration for files with these suffixes
37
+ IGNORED_SUFFIX = [".out"]
38
+
39
+ # File size limits
40
+ MB = 1024 * 1024
41
+ EVENT_PICKING_LIMIT = 10 * MB
42
+ LOG_LIMIT = 100 * MB
43
+ CHECKPOINT_LIMIT = 500 * MB
44
+ SANDBOX_NO_BUILD_LIMIT = 10 * MB
45
+ SANDBOX_LIMIT = 768 * MB
46
+
47
+ # Error messages
48
+ ERROR_NOT_SECURE = "ERROR : no HTTPS"
49
+ ERROR_LIMITED_PROXY = "ERROR: rejected due to the usage of limited proxy"
50
+ ERROR_OVERWRITE = "ERROR: cannot overwrite file"
51
+ ERROR_WRITE = "ERROR: cannot write file"
52
+ ERROR_SIZE_LIMIT = "ERROR: upload failure. Exceeded size limit"
53
+
54
+ global_task_buffer = None
55
+
56
+
57
+ def init_task_buffer(task_buffer: TaskBuffer) -> None:
58
+ """
59
+ Initialize the task buffer. This method needs to be called before any other method with DB access in this module.
60
+ """
61
+ global global_task_buffer
62
+ global_task_buffer = task_buffer
63
+
64
+
65
+ def _get_content_length(req: PandaRequest, tmp_logger: LogWrapper) -> int:
66
+ """
67
+ Get the content length of the request.
68
+
69
+ Args:
70
+ req(PandaRequest): internally generated request object containing the env variables
71
+ tmp_logger(LogWrapper): logger object of the calling function.
72
+
73
+ Returns:
74
+ int: content length of the request.
75
+ """
76
+ content_length = 0
77
+ try:
78
+ content_length = int(req.headers_in["content-length"])
79
+ except Exception:
80
+ if "content-length" in req.headers_in:
81
+ tmp_logger.error(f"Cannot get content_length: {req.headers_in['content-length']}")
82
+ else:
83
+ tmp_logger.error("No content_length for {method_name}")
84
+
85
+ tmp_logger.debug(f"Size: {content_length}")
86
+ return content_length
87
+
88
+
89
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
90
+ def upload_jedi_log(req: PandaRequest, file: FileStorage) -> Dict:
91
+ """
92
+ Upload a JEDI log file
93
+
94
+ Uploads a JEDI log file and returns the URL to the file. If there is already a log file for the task, it will be overwritten. Requires a secure connection and production role.
95
+
96
+ API details:
97
+ HTTP Method: POST
98
+ Path: /v1/file_server/upload_jedi_log
99
+
100
+ Args:
101
+ req(PandaRequest): internally generated request object containing the env variables
102
+ file(FileStorage): werkzeug.FileStorage object to be uploaded.
103
+
104
+ Returns:
105
+ dict: The system response `{"success": success, "message": message, "data": data}`.
106
+ When successful, the data field will contain the URL to the file. Otherwise the message field will indicate the issue.
107
+ """
108
+
109
+ tmp_logger = LogWrapper(_logger, f"upload_jedi_log <{file.filename}>")
110
+ tmp_logger.debug(f"start {req.subprocess_env['SSL_CLIENT_S_DN']}")
111
+
112
+ # get file size
113
+ content_length = _get_content_length(req, tmp_logger)
114
+
115
+ # check against the size limit for logs
116
+ if content_length > LOG_LIMIT:
117
+ error_message = ERROR_SIZE_LIMIT
118
+ tmp_logger.error(error_message)
119
+ tmp_logger.debug("Done")
120
+ return generate_response(False, error_message)
121
+
122
+ jedi_log_directory = "/jedilog"
123
+ try:
124
+ file_base_name = file.filename.split("/")[-1]
125
+ full_path = f"{panda_config.cache_dir}{jedi_log_directory}/{file_base_name}"
126
+
127
+ # delete old file
128
+ if os.path.exists(full_path):
129
+ os.remove(full_path)
130
+
131
+ # write the new file
132
+ with open(full_path, "wb") as file_object:
133
+ file_content = file.read()
134
+ file_object.write(file_content)
135
+ tmp_logger.debug(f"written to {full_path}")
136
+
137
+ # return the URL depending on the protocol
138
+ if panda_config.disableHTTP:
139
+ protocol = "https"
140
+ else:
141
+ protocol = "http"
142
+ success, server = get_endpoint(protocol)
143
+ if not success:
144
+ error_message = f"cannot get endpoint: {server}"
145
+ tmp_logger.error(error_message)
146
+ tmp_logger.debug("Done")
147
+ return generate_response(False, error_message)
148
+
149
+ file_url = f"{protocol}://{server}/cache{jedi_log_directory}/{file_base_name}"
150
+ tmp_logger.debug("Done")
151
+ return generate_response(True, data=file_url)
152
+
153
+ except Exception:
154
+ error_type, error_value = sys.exc_info()[:2]
155
+ error_message = f"failed to write log with {error_type.__name__}:{error_value}"
156
+ tmp_logger.error(error_message)
157
+ tmp_logger.debug("Done")
158
+ return generate_response(False, error_message)
159
+
160
+
161
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
162
+ def update_jedi_log(req: PandaRequest, file: FileStorage) -> Dict:
163
+ """
164
+ Update a JEDI log file
165
+
166
+ Updates a JEDI log file, appending more content at the end of the file. Requires a secure connection and production role.
167
+
168
+ API details:
169
+ HTTP Method: POST
170
+ Path: /v1/file_server/update_jedi_log
171
+
172
+ Args:
173
+ req(PandaRequest): internally generated request object containing the env variables
174
+ file(FileStorage): werkzeug.FileStorage object to be updated.
175
+
176
+ Returns:
177
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
178
+ """
179
+
180
+ tmp_logger = LogWrapper(_logger, f"update_jedi_log < {file.filename} >")
181
+ tmp_logger.debug("Start")
182
+
183
+ # write to file
184
+ try:
185
+ # expand
186
+ new_content = zlib.decompress(file.read())
187
+
188
+ # stdout name
189
+ log_name = f"{panda_config.cache_dir}/{file.filename.split('/')[-1]}"
190
+
191
+ # append to file end
192
+ with open(log_name, "a") as file_object:
193
+ file_object.write(new_content)
194
+
195
+ except Exception:
196
+ error_type, error_value, _ = sys.exc_info()
197
+ tmp_logger.error(f"{error_type} {error_value}")
198
+ return generate_response(False, f"ERROR: cannot update file with {error_type} {error_value}")
199
+
200
+ tmp_logger.debug("Done")
201
+ return generate_response(True)
202
+
203
+
204
+ @request_validation(_logger, request_method="GET")
205
+ def download_jedi_log(req: PandaRequest, log_name: str, offset: int = 0) -> str:
206
+ """
207
+ Download JEDI log file
208
+
209
+ Downloads the JEDI log file, if required at a particular offset.
210
+
211
+ API details:
212
+ HTTP Method: POST
213
+ Path: /v1/file_server/download_jedi_log
214
+
215
+ Args:
216
+ req(PandaRequest): internally generated request object containing the env variables
217
+ log_name(string): log file name
218
+ offset(int): offset in the file
219
+
220
+ Returns:
221
+ str: The content of the log file or an error message.
222
+ """
223
+
224
+ tmp_logger = LogWrapper(_logger, f"download_jedi_log <{log_name}>")
225
+ tmp_logger.debug(f"Start offset={offset}")
226
+
227
+ # put dummy char to avoid Internal Server Error
228
+ return_string = " "
229
+ try:
230
+ # stdout name
231
+ full_log_name = f"{panda_config.cache_dir}/{log_name.split('/')[-1]}"
232
+
233
+ # read at offset of the file
234
+ with open(full_log_name, "r") as file_object:
235
+ file_object.seek(int(offset))
236
+ return_string += file_object.read()
237
+
238
+ except Exception:
239
+ error_type, error_value, _ = sys.exc_info()
240
+ tmp_logger.error(f"Failed with: {error_type} {error_value}")
241
+
242
+ tmp_logger.debug(f"Read {len(return_string)} bytes")
243
+ tmp_logger.debug("Done")
244
+ return return_string
245
+
246
+
247
+ @request_validation(_logger, secure=True, request_method="POST")
248
+ def upload_cache_file(req: PandaRequest, file: FileStorage) -> Dict:
249
+ """
250
+ Upload a cache file
251
+
252
+ Uploads a file to the cache. When not touched, cache files are expired after some time.
253
+ User caches will get registered in the PanDA database and will account towards user limits.
254
+ PanDA log files will be stored in gzip format. Requires a secure connection.
255
+
256
+ API details:
257
+ HTTP Method: POST
258
+ Path: /v1/file_server/upload_cache_file
259
+
260
+ Args:
261
+ req(PandaRequest): internally generated request object containing the env variables
262
+ file(FileStorage): werkzeug.FileStorage object to be uploaded.
263
+
264
+ Returns:
265
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
266
+ """
267
+
268
+ tmp_logger = LogWrapper(_logger, f"upload_cache_file-{naive_utcnow().isoformat('/')}")
269
+ tmp_logger.debug(f"Start")
270
+
271
+ # check if using secure connection and the proxy is not limited
272
+ # we run these checks explicitly to trigger garbage collection
273
+ if not Protocol.isSecure(req):
274
+ tmp_logger.error("No HTTPS. Triggering garbage collection...")
275
+ gc.collect()
276
+ tmp_logger.debug("Done")
277
+ return generate_response(False, ERROR_NOT_SECURE)
278
+
279
+ if "/CN=limited proxy" in req.subprocess_env["SSL_CLIENT_S_DN"]:
280
+ tmp_logger.error("Limited proxy is used. Triggering garbage collection...")
281
+ gc.collect()
282
+ tmp_logger.debug("Done")
283
+ return generate_response(False, ERROR_LIMITED_PROXY)
284
+
285
+ # user name
286
+ user_name = CoreUtils.clean_user_id(req.subprocess_env["SSL_CLIENT_S_DN"])
287
+ tmp_logger.debug(f"user_name={user_name} file_path={file.filename}")
288
+
289
+ # get file size limit
290
+ # log file
291
+ if any(file.filename.endswith(suffix) for suffix in IGNORED_SUFFIX):
292
+ no_build = False
293
+ size_limit = LOG_LIMIT
294
+ # no build case
295
+ elif not file.filename.startswith("sources."):
296
+ no_build = True
297
+ size_limit = SANDBOX_NO_BUILD_LIMIT
298
+ # general sandbox limit
299
+ else:
300
+ no_build = False
301
+ size_limit = SANDBOX_LIMIT
302
+
303
+ # get actual file size
304
+ content_length = _get_content_length(req, tmp_logger)
305
+
306
+ # check if we are above the size limit
307
+ if content_length > size_limit:
308
+ error_message = f"{ERROR_SIZE_LIMIT} {content_length // MB} MB > {size_limit // MB} MB."
309
+ if no_build:
310
+ error_message += " Please submit the job without --noBuild/--libDS since those options impose a tighter size limit"
311
+ else:
312
+ error_message += " Please remove redundant files from your work area"
313
+ tmp_logger.error(error_message)
314
+ tmp_logger.debug("Triggering garbage collection...")
315
+ gc.collect()
316
+ tmp_logger.debug("Done")
317
+ return generate_response(False, error_message)
318
+
319
+ # write to file
320
+ try:
321
+ file_name = file.filename.split("/")[-1]
322
+ full_path = f"{panda_config.cache_dir}/{file_name}"
323
+
324
+ # avoid overwriting
325
+ if os.path.exists(full_path) and file.filename.split(".")[-1] != "__ow__":
326
+ # touch
327
+ os.utime(full_path, None)
328
+ # send error message
329
+ error_message = ERROR_OVERWRITE
330
+ tmp_logger.debug(f"{ERROR_OVERWRITE} {file_name}")
331
+ tmp_logger.debug("end")
332
+ return generate_response(False, error_message)
333
+
334
+ # write the file to the cache directory
335
+ with open(full_path, "wb") as file_object:
336
+ file_content = file.read()
337
+ if hasattr(panda_config, "compress_file_names") and [
338
+ True for patt in panda_config.compress_file_names.split(",") if re.search(patt, file_name) is not None
339
+ ]:
340
+ file_content = gzip.compress(file_content)
341
+ file_object.write(file_content)
342
+
343
+ except Exception:
344
+ error_message = ERROR_WRITE
345
+ tmp_logger.error(error_message)
346
+ tmp_logger.debug("Triggering garbage collection...")
347
+ gc.collect()
348
+ tmp_logger.debug("Done")
349
+ return generate_response(False, error_message)
350
+
351
+ # calculate the checksum
352
+ try:
353
+ # decode Footer
354
+ footer = file_content[-8:]
355
+ checksum, _ = struct.unpack("II", footer)
356
+ checksum = str(checksum)
357
+ tmp_logger.debug(f"CRC from gzip Footer {checksum}")
358
+ except Exception:
359
+ # use None to avoid delay for now
360
+ checksum = None
361
+ tmp_logger.debug(f"No CRC calculated {checksum}")
362
+
363
+ # calculate the file size
364
+ file_size = len(file_content)
365
+
366
+ # log the full file information
367
+ tmp_logger.debug(f"Written dn={user_name}, file={full_path}, size={file_size // MB} MB, crc={checksum}")
368
+
369
+ # record the file information to DB
370
+ if panda_config.record_sandbox_info:
371
+ # ignore some suffixes, e.g. out
372
+ to_insert = True
373
+ for patt in IGNORED_SUFFIX:
374
+ if file.filename.endswith(patt):
375
+ to_insert = False
376
+ break
377
+ if not to_insert:
378
+ tmp_logger.debug("skipped to insert to DB")
379
+ else:
380
+ status_client, output_client = Client.register_cache_file(user_name, file.filename, file_size, checksum)
381
+ if status_client != 0:
382
+ error_message = f"ERROR : failed to register sandbox to DB with {status_client} {output_client}"
383
+ tmp_logger.error(error_message)
384
+ tmp_logger.debug("Done")
385
+ return generate_response(False, error_message)
386
+
387
+ success = output_client["success"]
388
+ message = output_client["message"]
389
+ if not success:
390
+ error_message = f"ERROR : failed to register sandbox to DB with {message}"
391
+ tmp_logger.error(error_message)
392
+ tmp_logger.debug("Done")
393
+ return generate_response(False, error_message)
394
+
395
+ tmp_logger.debug(f"Registered file in database with: {output_client}")
396
+
397
+ tmp_logger.debug("Triggering garbage collection...")
398
+ gc.collect()
399
+ tmp_logger.debug("Done")
400
+
401
+ return generate_response(True)
402
+
403
+
404
+ @request_validation(_logger, secure=True, request_method="POST")
405
+ def touch_cache_file(req: PandaRequest, file_name: str) -> Dict:
406
+ """
407
+ Touch file in the cache directory.
408
+
409
+ Touches a file in the cache directory. It avoids the file to expire and being deleted by server clean up processes. Requires a secure connection.
410
+
411
+ API details:
412
+ HTTP Method: POST
413
+ Path: /v1/file_server/touch_cache_file
414
+
415
+ Args:
416
+ req(PandaRequest): internally generated request object containing the env variables
417
+ file_name(string): file name to be deleted
418
+
419
+ Returns:
420
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
421
+ """
422
+
423
+ tmp_logger = LogWrapper(_logger, f"touch_cache_file < {file_name} >")
424
+ tmp_logger.debug(f"Start")
425
+
426
+ try:
427
+ os.utime(f"{panda_config.cache_dir}/{file_name.split('/')[-1]}", None)
428
+ tmp_logger.debug(f"Done")
429
+ return generate_response(True)
430
+ except Exception:
431
+ error_type, error_value = sys.exc_info()[:2]
432
+ message = f"Failed to touch file with: {error_type} {error_value}"
433
+ _logger.error(message)
434
+ return generate_response(False, message)
435
+
436
+
437
+ @request_validation(_logger, secure=True, request_method="POST")
438
+ def delete_cache_file(req: PandaRequest, file_name: str) -> Dict:
439
+ """
440
+ Delete cache file
441
+
442
+ Deletes a file from the cache directory. Currently a dummy method. Requires a secure connection.
443
+
444
+ API details:
445
+ HTTP Method: POST
446
+ Path: /v1/file_server/delete_cache_file
447
+
448
+ Args:
449
+ req(PandaRequest): internally generated request object containing the env variables
450
+ file_name(string): file name to be deleted
451
+
452
+ Returns:
453
+ dict: The system response `{"success": success, "message": message, "data": data}`.
454
+ """
455
+
456
+ tmp_logger = LogWrapper(_logger, f"delete_cache_file <{file_name}>")
457
+ tmp_logger.debug(f"Start")
458
+
459
+ try:
460
+ # may be reused for re-brokerage
461
+ # os.remove('%s/%s' % (panda_config.cache_dir, file_name.split('/')[-1]))
462
+ return generate_response(True)
463
+ except Exception:
464
+ return generate_response(False)
465
+
466
+
467
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
468
+ def register_cache_file(req: PandaRequest, user_name: str, file_name: str, file_size: int, checksum: str) -> Dict:
469
+ """
470
+ Register cache file
471
+
472
+ Registers a file from the cache directory into the PanDA database, so that PanDA knows the server it's on. Requires a secure connection and production role.
473
+
474
+ API details:
475
+ HTTP Method: POST
476
+ Path: /v1/file_server/register_cache_file
477
+
478
+ Args:
479
+ req(PandaRequest): internally generated request object containing the env variables
480
+ user_name(string): user that uploaded the file
481
+ file_name(string): file name
482
+ file_size(int): file size
483
+ checksum(string): checksum
484
+
485
+ Returns:
486
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
487
+ """
488
+
489
+ tmp_logger = LogWrapper(_logger, f"register_cache_file {user_name} {file_name}")
490
+ tmp_logger.debug("Start")
491
+
492
+ # the files are on a particular server and not accessible through the LB endpoint
493
+ # therefore we need to register a preconfigured hostname or hostname of the caller
494
+ if hasattr(panda_config, "sandboxHostname") and panda_config.sandboxHostname:
495
+ host_name = panda_config.sandboxHostname
496
+ else:
497
+ host_name = req.get_remote_host()
498
+
499
+ message = global_task_buffer.insertSandboxFileInfo(user_name, host_name, file_name, file_size, checksum)
500
+ if message != "OK":
501
+ tmp_logger.debug("Done")
502
+ return generate_response(False, message)
503
+
504
+ tmp_logger.debug("Done")
505
+ return generate_response(True)
506
+
507
+
508
+ @request_validation(_logger, secure=True, request_method="POST")
509
+ def validate_cache_file(req: PandaRequest, file_size: int, checksum: int | str) -> Dict:
510
+ """
511
+ Validate cache file
512
+
513
+ Validates a cache file owned by the caller by checking the file metadata that was registered in the database. Requires a secure connection.
514
+
515
+ API details:
516
+ HTTP Method: POST
517
+ Path: /v1/file_server/validate_cache_file
518
+
519
+ Args:
520
+ req(PandaRequest): internally generated request object containing the env variables
521
+ file_size(int): file size
522
+ checksum(int): checksum
523
+
524
+ Returns:
525
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful the message will return the host and file name.
526
+ When unsuccessful, the message field will indicate the issue.
527
+ """
528
+ user = get_dn(req)
529
+ message = global_task_buffer.checkSandboxFile(user, file_size, checksum)
530
+
531
+ # The file was not found or there was an exception
532
+ if message and not message.startswith("FOUND"):
533
+ return generate_response(False, message)
534
+
535
+ # The file was found
536
+ return generate_response(True, message)
537
+
538
+
539
+ def _get_checkpoint_filename(task_id: str, sub_id: str) -> Dict:
540
+ """
541
+ Get the checkpoint file name.
542
+
543
+ Args:
544
+ task_id(string): task ID.
545
+ sub_id(string): sub ID.
546
+
547
+ Returns:
548
+ string: checkpoint file name.
549
+ """
550
+ return f"hpo_cp_{task_id}_{sub_id}"
551
+
552
+
553
+ @request_validation(_logger, secure=True, request_method="POST")
554
+ def upload_hpo_checkpoint(req: PandaRequest, file: FileStorage) -> Dict:
555
+ """
556
+ Upload a HPO checkpoint file
557
+
558
+ Uploads a HPO checkpoint file to the server. Requires a secure connection.
559
+
560
+ API details:
561
+ HTTP Method: POST
562
+ Path: /v1/file_server/upload_hpo_checkpoint
563
+
564
+ Args:
565
+ req(PandaRequest): internally generated request object containing the env variables
566
+ file(FileStorage): werkzeug.FileStorage object to be uploaded.
567
+
568
+ Returns:
569
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
570
+ """
571
+
572
+ tmp_logger = LogWrapper(_logger, f"upload_hpo_checkpoint <jediTaskID_subID={file.filename}>")
573
+
574
+ tmp_logger.debug(f"Start {req.subprocess_env['SSL_CLIENT_S_DN']}")
575
+
576
+ # extract task ID and sub ID
577
+ try:
578
+ task_id, sub_id = file.filename.split("/")[-1].split("_")
579
+ except Exception:
580
+ error_message = "Failed to extract task and sub IDs"
581
+ tmp_logger.error(error_message)
582
+ return generate_response(False, error_message)
583
+
584
+ # get the file size
585
+ content_length = _get_content_length(req, tmp_logger)
586
+ if not content_length:
587
+ error_message = f"Cannot get content-length"
588
+ tmp_logger.error(error_message)
589
+ return generate_response(False, error_message)
590
+
591
+ # compare the size against the limit for checkpoints
592
+ if content_length > CHECKPOINT_LIMIT:
593
+ error_message = f"Exceeded size limit {content_length}>{CHECKPOINT_LIMIT}"
594
+ tmp_logger.error(error_message)
595
+ return generate_response(False, error_message)
596
+
597
+ # write the file to the cache directory
598
+ try:
599
+ full_path = os.path.join(panda_config.cache_dir, _get_checkpoint_filename(task_id, sub_id))
600
+ # write
601
+ with open(full_path, "wb") as file_object:
602
+ file_object.write(file.read())
603
+ except Exception as exc:
604
+ error_message = f"Cannot write file due to {str(exc)}"
605
+ tmp_logger.error(error_message)
606
+ return generate_response(False, error_message)
607
+
608
+ success_message = f"Successfully placed at {full_path}"
609
+ tmp_logger.debug(success_message)
610
+ tmp_logger.debug("Done")
611
+ return generate_response(True, message=success_message, data=full_path)
612
+
613
+
614
+ @request_validation(_logger, secure=True, request_method="POST")
615
+ def delete_hpo_checkpoint(req: PandaRequest, task_id: str, sub_id: str) -> Dict:
616
+ """
617
+ Delete a HPO checkpoint file.
618
+
619
+ Deletes a HPO checkpoint file from the server. Requires a secure connection.
620
+
621
+ API details:
622
+ HTTP Method: POST
623
+ Path: /v1/file_server/delete_hpo_checkpoint
624
+
625
+ Args:
626
+ req(PandaRequest): internally generated request object containing the env variables
627
+ task_id(string): JEDI task ID
628
+ sub_id(string): sub ID.
629
+
630
+ Returns:
631
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
632
+ """
633
+
634
+ tmp_logger = LogWrapper(_logger, f"delete_hpo_checkpoint <jediTaskID={task_id} ID={sub_id}>")
635
+
636
+ tmp_logger.debug(f"Start {req.subprocess_env['SSL_CLIENT_S_DN']}")
637
+
638
+ try:
639
+ full_path = os.path.join(panda_config.cache_dir, _get_checkpoint_filename(task_id, sub_id))
640
+ os.remove(full_path)
641
+ tmp_logger.debug("Done")
642
+ return generate_response(True)
643
+ except Exception as exc:
644
+ message = f"Failed to delete file due to {str(exc)}"
645
+ tmp_logger.error(message)
646
+ return generate_response(False, message=message)
647
+
648
+
649
+ @request_validation(_logger, secure=True, request_method="POST")
650
+ def upload_file_recovery_request(
651
+ req: PandaRequest,
652
+ task_id: int = None,
653
+ dry_run: bool = None,
654
+ dataset: str = None,
655
+ files: List[str] = None,
656
+ no_child_retry: bool = False,
657
+ resurrect_datasets: bool = False,
658
+ force: bool = False,
659
+ reproduce_parent: bool = False,
660
+ reproduce_upto_nth_gen: int = 0,
661
+ ) -> Dict:
662
+ """
663
+ Upload file recovery request
664
+
665
+ Upload request to recover lost files. Either task_id or dataset needs to be specified. Requires a secure connection.
666
+
667
+ API details:
668
+ HTTP Method: POST
669
+ Path: /v1/file_server/upload_file_recovery_request
670
+
671
+ Args:
672
+ req(PandaRequest): internally generated request object containing the env variables
673
+ task_id(int, optional): JEDI task ID. Either task_id or dataset must be provided.
674
+ dry_run(bool, optional): dry run flag.
675
+ dataset(string, optional): the dataset name in which to recover files. Either task_id or dataset must be provided.
676
+ files(list of str, optional): list of file names to recover.
677
+ no_child_retry(bool, optional): flag to avoid retrying child tasks. Default is False.
678
+ resurrect_datasets(bool, optional): Specifies whether to resurrect datasets when they were already deleted. Default is False.
679
+ force(bool, optional): To force recovery even if there is no lost file. Default is False.
680
+ reproduce_parent(bool, optional): Specifies whether to reproduce the parent task if the input files that originally generated the lost files have been deleted. Default: False.
681
+ reproduce_upto_nth_gen(int, optional): Defines how many generations of parent tasks should be reproduced. Default 0, meaning no parent tasks are reproduced. When this is set to N>0, reproduce_parent is set to True automatically.
682
+
683
+ Returns:
684
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
685
+ """
686
+
687
+ user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
688
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
689
+
690
+ tmp_logger = LogWrapper(_logger, f"put_file_recovery_request < task_id={task_id} >")
691
+ tmp_logger.debug(f"Start user={user_name}")
692
+
693
+ try:
694
+ # check that at least task_id or dataset is provided
695
+ if not task_id and not dataset:
696
+ error_message = "Either task_id or dataset must be provided"
697
+ tmp_logger.error(error_message)
698
+ return generate_response(False, error_message)
699
+
700
+ # generate the filename
701
+ file_name = f"{panda_config.cache_dir}/recov.{str(uuid.uuid4())}"
702
+ log_filename = f"jedilog/lost_file_recovery.{str(uuid.uuid4())}.log"
703
+ tmp_logger.debug(f"file={file_name}")
704
+
705
+ # check if the user has production manager role
706
+ is_production_manager = has_production_role(req)
707
+
708
+ # write the file content
709
+
710
+ with open(file_name, "w") as file_object:
711
+ data = {
712
+ "userName": user_name,
713
+ "creationTime": creation_time,
714
+ "logFilename": log_filename,
715
+ "isProductionManager": is_production_manager,
716
+ }
717
+ if task_id:
718
+ data["jediTaskID"] = task_id
719
+ if dry_run:
720
+ data["dryRun"] = True
721
+ if dataset:
722
+ data["ds"] = dataset
723
+ if files:
724
+ data["files"] = ",".join(files)
725
+ if no_child_retry:
726
+ data["noChildRetry"] = True
727
+ if resurrect_datasets:
728
+ data["resurrectDS"] = True
729
+ if force:
730
+ data["force"] = True
731
+ if reproduce_parent:
732
+ data["reproduceParent"] = True
733
+ if reproduce_upto_nth_gen > 0:
734
+ data["reproduceUptoNthGen"] = reproduce_upto_nth_gen
735
+
736
+ json.dump(data, file_object)
737
+ except Exception as exc:
738
+ error_message = f"cannot put request due to {str(exc)} "
739
+ tmp_logger.error(error_message + traceback.format_exc())
740
+ return generate_response(False, error_message)
741
+
742
+ # create an empty log file to be filled later
743
+ Path(os.path.join(panda_config.cache_dir, log_filename)).touch()
744
+
745
+ # return the URL depending on the protocol
746
+ protocol = "https" if panda_config.disableHTTP else "http"
747
+ _, server = get_endpoint(protocol)
748
+ log_file_url = f"{protocol}://{server}/cache/{log_filename}"
749
+
750
+ tmp_logger.debug("done")
751
+ data = {"logFileURL": log_file_url}
752
+ return generate_response(True, message="The request was accepted and will be processed in a few minutes", data=data)
753
+
754
+
755
+ @request_validation(_logger, secure=True, request_method="POST")
756
+ def upload_workflow_request(req: PandaRequest, data: str, dry_run: bool = False, sync: bool = False) -> Dict:
757
+ """
758
+ Upload workflow request to the server.
759
+
760
+ Uploads a workflow request to the server. The request can be processed synchronously or asynchronously. Requires a secure connection.
761
+
762
+ API details:
763
+ HTTP Method: POST
764
+ Path: /v1/file_server/upload_workflow_request
765
+
766
+ Args:
767
+ req(PandaRequest): internally generated request object containing the env variables
768
+ data(string): workflow request data
769
+ dry_run(bool): requests the workflow to be executed synchronously in dry_run mode
770
+ sync(bool): requests the workflow to be processed synchronously
771
+ Returns:
772
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
773
+ When the request asked to process the workflow synchronously or with the check file, the data field will contain the response.
774
+
775
+ """
776
+
777
+ user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
778
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
779
+
780
+ tmp_logger = LogWrapper(_logger, "upload_workflow_request")
781
+
782
+ tmp_logger.debug(f"Start user={user_name} dry_run={dry_run}")
783
+
784
+ try:
785
+ # Generate the filename
786
+ file_name = f"{panda_config.cache_dir}/workflow.{str(uuid.uuid4())}"
787
+ tmp_logger.debug(f"file={file_name}")
788
+
789
+ # Write out the workflow request
790
+ with open(file_name, "w") as file_object:
791
+ data_dict = {
792
+ "userName": user_name,
793
+ "creationTime": creation_time,
794
+ "data": json.loads(data),
795
+ }
796
+ json.dump(data_dict, file_object)
797
+
798
+ # Submitter requested synchronous processing
799
+ if sync or dry_run:
800
+ tmp_logger.debug("Starting synchronous processing of the workflow")
801
+ from pandaserver.taskbuffer.workflow_processor import WorkflowProcessor
802
+
803
+ processor = WorkflowProcessor(log_stream=_logger)
804
+ if dry_run:
805
+ ret = processor.process(file_name, True, True, True, True)
806
+ else:
807
+ ret = processor.process(file_name, True, False, True, False)
808
+
809
+ # Delete the file to prevent it being processed again
810
+ if os.path.exists(file_name):
811
+ try:
812
+ os.remove(file_name)
813
+ except Exception:
814
+ pass
815
+ tmp_logger.debug("Done")
816
+ return generate_response(True, data=ret)
817
+
818
+ except Exception as exc:
819
+ error_message = f"Cannot upload the workflow request: {str(exc)} "
820
+ tmp_logger.error(error_message + traceback.format_exc())
821
+ return generate_response(False, error_message)
822
+
823
+ # Submitter did not request synchronous processing
824
+ tmp_logger.debug("Done")
825
+ return generate_response(True, message="The request was accepted and will be processed in a few minutes")
826
+
827
+
828
+ @request_validation(_logger, secure=True, request_method="POST")
829
+ def upload_event_picking_request(
830
+ req: PandaRequest,
831
+ run_event_list: str = "",
832
+ data_type: str = "",
833
+ stream_name: str = "",
834
+ dataset_name: str = "",
835
+ ami_tag: str = "",
836
+ user_dataset_name: str = "",
837
+ locked_by: str = "",
838
+ parameters: str = "",
839
+ input_file_list: str = "",
840
+ n_sites: str = "",
841
+ user_task_name: str = "",
842
+ ei_api: str = "",
843
+ include_guids: bool = False,
844
+ ) -> Dict:
845
+ """
846
+ Upload event picking request to the server.
847
+
848
+ Uploads an event picking request to the server. Requires a secure connection.
849
+
850
+ API details:
851
+ HTTP Method: POST
852
+ Path: /v1/file_server/upload_event_picking_request
853
+
854
+ Args:
855
+ req(PandaRequest): internally generated request object containing the env variables
856
+ run_event_list(string): run and event list.
857
+ data_type(string): data type.
858
+ stream_name(string): stream name.
859
+ dataset_name(string): dataset name.
860
+ ami_tag(string): AMI tag.
861
+ user_dataset_name(string): user dataset name.
862
+ locked_by(string): locking agent.
863
+ parameters(string): parameters.
864
+ input_file_list(string): input file list.
865
+ n_sites(string): number of sites.
866
+ user_task_name(string): user task name.
867
+ ei_api(string): event index API.
868
+ include_guids(bool): flag to indicate if GUIDs are included with the run-event list
869
+
870
+ Returns:
871
+ dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
872
+
873
+ """
874
+
875
+ user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
876
+
877
+ tmp_logger = LogWrapper(_logger, f"upload_event_picking_request-{naive_utcnow().isoformat('/')}")
878
+ tmp_logger.debug(f"Start for {user_name}")
879
+
880
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
881
+
882
+ # get total size
883
+ content_length = _get_content_length(req, tmp_logger)
884
+ if not content_length:
885
+ error_message = "Cannot get content-length from HTTP request."
886
+ tmp_logger.error(f"{error_message}")
887
+ return generate_response(False, f"ERROR: {error_message}")
888
+
889
+ if content_length > EVENT_PICKING_LIMIT:
890
+ error_message = f"Run/event list is too large. Exceeded size limit {content_length}>{EVENT_PICKING_LIMIT}."
891
+ tmp_logger.error(f"{error_message} ")
892
+ return generate_response(False, f"ERROR: {error_message}")
893
+
894
+ try:
895
+ # generate the filename
896
+ file_name = f"{panda_config.cache_dir}/evp.{str(uuid.uuid4())}"
897
+ tmp_logger.debug(f"file: {file_name}")
898
+
899
+ # write the information to file
900
+ file_content = (
901
+ f"userName={user_name}\n"
902
+ f"creationTime={creation_time}\n"
903
+ f"eventPickDataType={data_type}\n"
904
+ f"eventPickStreamName={stream_name}\n"
905
+ f"eventPickDS={dataset_name}\n"
906
+ f"eventPickAmiTag={ami_tag}\n"
907
+ f"eventPickNumSites={n_sites}\n"
908
+ f"userTaskName={user_task_name}\n"
909
+ f"userDatasetName={user_dataset_name}\n"
910
+ f"lockedBy={locked_by}\n"
911
+ f"params={parameters}\n"
912
+ f"inputFileList={input_file_list}\n"
913
+ f"ei_api={ei_api}\n"
914
+ )
915
+
916
+ with open(file_name, "w") as file_object:
917
+ file_object.write(file_content)
918
+ run_event_guid_map = {}
919
+
920
+ valid_entry_length = 3 if include_guids else 2
921
+
922
+ for tmp_line in run_event_list.split("\n"):
923
+ tmp_items = tmp_line.split()
924
+
925
+ # Skip invalid entries
926
+ if len(tmp_items) != valid_entry_length:
927
+ continue
928
+
929
+ file_object.write(f"runEvent={tmp_items[0]},{tmp_items[1]}\n")
930
+
931
+ if include_guids:
932
+ run_event_guid_map[(tmp_items[0], tmp_items[1])] = [tmp_items[2]]
933
+
934
+ file_object.write(f"runEvtGuidMap={str(run_event_guid_map)}\n")
935
+
936
+ except Exception as e:
937
+ error_message = f"Cannot upload the Event Picking request: {str(e)}"
938
+ tmp_logger.error(error_message + traceback.format_exc())
939
+ return generate_response(False, f"ERROR: {error_message}")
940
+
941
+ tmp_logger.debug("Done")
942
+ return generate_response(True)