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,848 @@
1
+ import gc
2
+ import gzip
3
+ import json
4
+ import os
5
+ import re
6
+ import struct
7
+ import sys
8
+ import traceback
9
+ import uuid
10
+ import zlib
11
+ from typing import Generator
12
+
13
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
14
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
15
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
16
+ from werkzeug.datastructures import FileStorage
17
+
18
+ from pandaserver.config import panda_config
19
+ from pandaserver.jobdispatcher import Protocol
20
+ from pandaserver.srvcore import CoreUtils
21
+ from pandaserver.srvcore.panda_request import PandaRequest
22
+ from pandaserver.userinterface import Client
23
+
24
+ _logger = PandaLogger().getLogger("Utils")
25
+
26
+ IGNORED_SUFFIX = [".out"]
27
+
28
+ # File size limits
29
+ MB = 1024 * 1024
30
+ EVENT_PICKING_LIMIT = 10 * MB
31
+ LOG_LIMIT = 100 * MB
32
+ CHECKPOINT_LIMIT = 500 * MB
33
+ SANDBOX_NO_BUILD_LIMIT = 100 * MB
34
+ SANDBOX_LIMIT = 768 * MB
35
+
36
+ # Error messages
37
+ ERROR_NOT_SECURE = "ERROR : no HTTPS"
38
+ ERROR_LIMITED_PROXY = "ERROR: rejected due to the usage of limited proxy"
39
+ ERROR_OVERWRITE = "ERROR: cannot overwrite file"
40
+ ERROR_WRITE = "ERROR: cannot write file"
41
+ ERROR_SIZE_LIMIT = "ERROR: upload failure. Exceeded size limit"
42
+
43
+
44
+ def isAlive(panda_request: PandaRequest) -> str:
45
+ """
46
+ Check if the server is alive. Basic function for the health check and used in SLS monitoring.
47
+
48
+ Args:
49
+ panda_request (PandaRequest): PanDA request object.
50
+
51
+ Returns:
52
+ str: "alive=yes"
53
+ """
54
+ return "alive=yes"
55
+
56
+
57
+ def get_content_length(panda_request: PandaRequest, tmp_log: LogWrapper) -> int:
58
+ """
59
+ Get the content length of the request.
60
+
61
+ Args:
62
+ panda_request (PandaRequest): PanDA request object.
63
+ tmp_log (LogWrapper): logger object of the calling function.
64
+
65
+ Returns:
66
+ int: content length of the request.
67
+ """
68
+ content_length = 0
69
+ try:
70
+ content_length = int(panda_request.headers_in["content-length"])
71
+ except Exception:
72
+ if "content-length" in panda_request.headers_in:
73
+ tmp_log.error(f"cannot get content_length: {panda_request.headers_in['content-length']}")
74
+ else:
75
+ tmp_log.error("no content_length for {method_name}")
76
+
77
+ tmp_log.debug(f"size {content_length}")
78
+ return content_length
79
+
80
+
81
+ # upload file
82
+ def putFile(panda_request: PandaRequest, file: FileStorage) -> str:
83
+ """
84
+ Upload a file to the server.
85
+
86
+ Args:
87
+ panda_request (PandaRequest): PanDA request object.
88
+ file (FileStorage): werkzeug.FileStorage object to be uploaded.
89
+
90
+ Returns:
91
+ string: "True" if the upload was successful, otherwise an error message.
92
+ """
93
+
94
+ tmp_log = LogWrapper(_logger, f"putFile-{naive_utcnow().isoformat('/')}")
95
+ tmp_log.debug(f"start")
96
+
97
+ # check if using secure connection and the proxy is not limited
98
+ if not Protocol.isSecure(panda_request):
99
+ tmp_log.error("no HTTPS")
100
+ tmp_log.debug("trigger garbage collection")
101
+ gc.collect()
102
+ tmp_log.debug("end")
103
+ return ERROR_NOT_SECURE
104
+ if "/CN=limited proxy" in panda_request.subprocess_env["SSL_CLIENT_S_DN"]:
105
+ tmp_log.error("limited proxy is used")
106
+ tmp_log.debug("trigger garbage collection")
107
+ gc.collect()
108
+ tmp_log.debug("end")
109
+ return ERROR_LIMITED_PROXY
110
+
111
+ # user name
112
+ user_name = CoreUtils.clean_user_id(panda_request.subprocess_env["SSL_CLIENT_S_DN"])
113
+ tmp_log.debug(f"user_name={user_name} file_path={file.filename}")
114
+
115
+ # get file size limit
116
+ if not file.filename.startswith("sources."):
117
+ no_build = True
118
+ size_limit = SANDBOX_NO_BUILD_LIMIT
119
+ else:
120
+ no_build = False
121
+ size_limit = SANDBOX_LIMIT
122
+
123
+ # get actual file size
124
+ content_length = get_content_length(panda_request, tmp_log)
125
+
126
+ # check if we are above the size limit
127
+ if content_length > size_limit:
128
+ error_message = f"{ERROR_SIZE_LIMIT} {content_length}>{size_limit}."
129
+ if no_build:
130
+ error_message += " Please submit the job without --noBuild/--libDS since those options impose a tighter size limit"
131
+ else:
132
+ error_message += " Please remove redundant files from your work area"
133
+ tmp_log.error(error_message)
134
+ tmp_log.debug("trigger garbage collection")
135
+ gc.collect()
136
+ tmp_log.debug("end")
137
+ return error_message
138
+
139
+ # write to file
140
+ try:
141
+ file_name = file.filename.split("/")[-1]
142
+ full_path = f"{panda_config.cache_dir}/{file_name}"
143
+
144
+ # avoid overwriting
145
+ if os.path.exists(full_path) and file.filename.split(".")[-1] != "__ow__":
146
+ # touch
147
+ os.utime(full_path, None)
148
+ # send error message
149
+ error_message = ERROR_OVERWRITE
150
+ tmp_log.debug(f"{ERROR_OVERWRITE} {file_name}")
151
+ tmp_log.debug("end")
152
+ return error_message
153
+
154
+ # write the file to the cache directory
155
+ with open(full_path, "wb") as file_object:
156
+ file_content = file.read()
157
+ if hasattr(panda_config, "compress_file_names") and [
158
+ True for patt in panda_config.compress_file_names.split(",") if re.search(patt, file_name) is not None
159
+ ]:
160
+ file_content = gzip.compress(file_content)
161
+ file_object.write(file_content)
162
+
163
+ except Exception:
164
+ error_message = ERROR_WRITE
165
+ tmp_log.error(error_message)
166
+ tmp_log.debug("trigger garbage collection")
167
+ gc.collect()
168
+ tmp_log.debug("end")
169
+ return error_message
170
+
171
+ # calculate the checksum
172
+ try:
173
+ # decode Footer
174
+ footer = file_content[-8:]
175
+ checksum, _ = struct.unpack("II", footer)
176
+ checksum = str(checksum)
177
+ tmp_log.debug(f"CRC from gzip Footer {checksum}")
178
+ except Exception:
179
+ # use None to avoid delay for now
180
+ checksum = ""
181
+ tmp_log.debug(f"No CRC calculated")
182
+
183
+ # calculate the file size
184
+ file_size = len(file_content)
185
+
186
+ # log the full file information
187
+ tmp_log.debug(f"written dn={user_name} file={full_path} size={file_size} crc={checksum}")
188
+
189
+ # record the file information to DB
190
+ if panda_config.record_sandbox_info:
191
+ # ignore some suffixes, e.g. out
192
+ to_insert = True
193
+ for patt in IGNORED_SUFFIX:
194
+ if file.filename.endswith(patt):
195
+ to_insert = False
196
+ break
197
+ if not to_insert:
198
+ tmp_log.debug("skipped to insert to DB")
199
+ else:
200
+ status_client, output_client = Client.register_cache_file(user_name, file.filename, file_size, checksum)
201
+ if status_client != 0:
202
+ error_message = f"ERROR : failed to register sandbox to DB with {status_client} {output_client}"
203
+ tmp_log.error(error_message)
204
+ tmp_log.debug("Done")
205
+ return error_message
206
+
207
+ success = output_client["success"]
208
+ message = output_client["message"]
209
+ if not success:
210
+ error_message = f"ERROR : failed to register sandbox to DB with {message}"
211
+ tmp_log.error(error_message)
212
+ tmp_log.debug("Done")
213
+ return error_message
214
+
215
+ tmp_log.debug(f"Inserted sandbox to DB with {output_client}")
216
+
217
+ tmp_log.debug("trigger garbage collection")
218
+ gc.collect()
219
+ tmp_log.debug("Done")
220
+
221
+ return "True"
222
+
223
+
224
+ def putEventPickingRequest(
225
+ panda_request: PandaRequest,
226
+ runEventList="",
227
+ eventPickDataType="",
228
+ eventPickStreamName="",
229
+ eventPickDS="",
230
+ eventPickAmiTag="",
231
+ userDatasetName="",
232
+ lockedBy="",
233
+ params="",
234
+ inputFileList="",
235
+ eventPickNumSites="",
236
+ userTaskName="",
237
+ ei_api="",
238
+ giveGUID=None,
239
+ ) -> str:
240
+ """
241
+ Upload event picking request to the server.
242
+
243
+ Args:
244
+ panda_request (PandaRequest): PanDA request object.
245
+ runEventList (str): run and event list.
246
+ eventPickDataType (str): data type.
247
+ eventPickStreamName (str): stream name.
248
+ eventPickDS (str): dataset name.
249
+ eventPickAmiTag (str): AMI tag.
250
+ userDatasetName (str): user dataset name.
251
+ lockedBy (str): locking agent.
252
+ params (str): parameters.
253
+ inputFileList (str): input file list.
254
+ eventPickNumSites (str): number of sites.
255
+ userTaskName (str): user task name.
256
+ ei_api (str): event index API.
257
+ giveGUID (str): give GUID.
258
+
259
+ Returns:
260
+ string: "True" if the upload was successful, otherwise an error message.
261
+
262
+ """
263
+ if not Protocol.isSecure(panda_request):
264
+ return ERROR_NOT_SECURE
265
+
266
+ user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
267
+
268
+ tmp_log = LogWrapper(_logger, f"putEventPickingRequest-{naive_utcnow().isoformat('/')}")
269
+ tmp_log.debug(f"start for {user_name}")
270
+
271
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
272
+
273
+ # get total size
274
+ try:
275
+ content_length = int(panda_request.headers_in["content-length"])
276
+ except Exception:
277
+ error_message = "cannot get content-length from HTTP request."
278
+ tmp_log.error(f"{error_message}")
279
+ tmp_log.debug("end")
280
+ return "ERROR : " + error_message
281
+ tmp_log.debug(f"size {content_length}")
282
+
283
+ if content_length > EVENT_PICKING_LIMIT:
284
+ error_message = f"Run/event list is too large. Exceeded size limit {content_length}>{EVENT_PICKING_LIMIT}."
285
+ tmp_log.error(f"{error_message} ")
286
+ tmp_log.debug("end")
287
+ return "ERROR : " + error_message
288
+
289
+ if giveGUID == "True":
290
+ giveGUID = True
291
+ else:
292
+ giveGUID = False
293
+
294
+ try:
295
+ # generate the filename
296
+ file_name = f"{panda_config.cache_dir}/evp.{str(uuid.uuid4())}"
297
+ tmp_log.debug(f"file: {file_name}")
298
+
299
+ # write the information to file
300
+ file_content = (
301
+ f"userName={user_name}\n"
302
+ f"creationTime={creation_time}\n"
303
+ f"eventPickDataType={eventPickDataType}\n"
304
+ f"eventPickStreamName={eventPickStreamName}\n"
305
+ f"eventPickDS={eventPickDS}\n"
306
+ f"eventPickAmiTag={eventPickAmiTag}\n"
307
+ f"eventPickNumSites={eventPickNumSites}\n"
308
+ f"userTaskName={userTaskName}\n"
309
+ f"userDatasetName={userDatasetName}\n"
310
+ f"lockedBy={lockedBy}\n"
311
+ f"params={params}\n"
312
+ f"inputFileList={inputFileList}\n"
313
+ f"ei_api={ei_api}\n"
314
+ )
315
+
316
+ with open(file_name, "w") as file_object:
317
+ file_object.write(file_content)
318
+ run_event_guid_map = {}
319
+ for tmp_line in runEventList.split("\n"):
320
+ tmp_items = tmp_line.split()
321
+ if (len(tmp_items) != 2 and not giveGUID) or (len(tmp_items) != 3 and giveGUID):
322
+ continue
323
+ file_object.write("runEvent=%s,%s\n" % tuple(tmp_items[:2]))
324
+ if giveGUID:
325
+ run_event_guid_map[tuple(tmp_items[:2])] = [tmp_items[2]]
326
+ file_object.write(f"runEvtGuidMap={str(run_event_guid_map)}\n")
327
+
328
+ except Exception as e:
329
+ error_message = f"cannot put request due to {str(e)}"
330
+ tmp_log.error(error_message + traceback.format_exc())
331
+ return f"ERROR : {error_message}"
332
+
333
+ tmp_log.debug("end")
334
+ return "True"
335
+
336
+
337
+ # upload lost file recovery request
338
+ def put_file_recovery_request(panda_request: PandaRequest, jediTaskID: str, dryRun: bool = None) -> str:
339
+ """
340
+ Upload lost file recovery request to the server.
341
+
342
+ Args:
343
+ panda_request (PandaRequest): PanDA request object.
344
+ jediTaskID (string): task ID.
345
+ dryRun (bool): dry run flag.
346
+
347
+ Returns:
348
+ string: String in json format with (boolean, message)
349
+ """
350
+ if not Protocol.isSecure(panda_request):
351
+ return json.dumps((False, ERROR_NOT_SECURE))
352
+ user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
353
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
354
+
355
+ tmp_log = LogWrapper(_logger, f"put_file_recovery_request < jediTaskID={jediTaskID}")
356
+ tmp_log.debug(f"start user={user_name}")
357
+ # get total size
358
+ try:
359
+ jedi_task_id = int(jediTaskID)
360
+
361
+ # generate the filename
362
+ file_name = f"{panda_config.cache_dir}/recov.{str(uuid.uuid4())}"
363
+ tmp_log.debug(f"file={file_name}")
364
+
365
+ # write the file content
366
+ with open(file_name, "w") as file_object:
367
+ data = {
368
+ "userName": user_name,
369
+ "creationTime": creation_time,
370
+ "jediTaskID": jedi_task_id,
371
+ }
372
+ if dryRun:
373
+ data["dryRun"] = True
374
+
375
+ json.dump(data, file_object)
376
+ except Exception as exc:
377
+ error_message = f"cannot put request due to {str(exc)} "
378
+ tmp_log.error(error_message + traceback.format_exc())
379
+ return json.dumps((False, error_message))
380
+
381
+ tmp_log.debug("done")
382
+ return json.dumps((True, "request was accepted and will be processed in a few minutes"))
383
+
384
+
385
+ def put_workflow_request(panda_request: PandaRequest, data: str, check: bool = False, sync: bool = False) -> str:
386
+ """
387
+ Upload workflow request to the server.
388
+ Args:
389
+ panda_request (PandaRequest): PanDA request object.
390
+ data (string): workflow request data.
391
+ check (bool): check flag.
392
+ sync (bool): synchronous processing.
393
+ Returns:
394
+ string: String in json format with (boolean, message)
395
+ """
396
+
397
+ if not Protocol.isSecure(panda_request):
398
+ return json.dumps((False, ERROR_NOT_SECURE))
399
+
400
+ user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
401
+ creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
402
+
403
+ tmp_log = LogWrapper(_logger, "put_workflow_request")
404
+
405
+ tmp_log.debug(f"start user={user_name} check={check}")
406
+
407
+ if check in ("True", True):
408
+ check = True
409
+ elif sync in ("True", True):
410
+ sync = True
411
+
412
+ try:
413
+ # generate the filename
414
+ file_name = f"{panda_config.cache_dir}/workflow.{str(uuid.uuid4())}"
415
+ tmp_log.debug(f"file={file_name}")
416
+
417
+ # write
418
+ with open(file_name, "w") as file_object:
419
+ data_dict = {
420
+ "userName": user_name,
421
+ "creationTime": creation_time,
422
+ "data": json.loads(data),
423
+ }
424
+ json.dump(data_dict, file_object)
425
+
426
+ if sync or check:
427
+ from pandaserver.taskbuffer.workflow_processor import WorkflowProcessor
428
+
429
+ processor = WorkflowProcessor(log_stream=_logger)
430
+ if check:
431
+ ret = processor.process(file_name, True, True, True, True)
432
+ else:
433
+ ret = processor.process(file_name, True, False, True, False)
434
+ if os.path.exists(file_name):
435
+ try:
436
+ os.remove(file_name)
437
+ except Exception:
438
+ pass
439
+ tmp_log.debug("done")
440
+ return json.dumps((True, ret))
441
+
442
+ except Exception as exc:
443
+ error_message = f"cannot put request due to {str(exc)} "
444
+ tmp_log.error(error_message + traceback.format_exc())
445
+ return json.dumps((False, error_message))
446
+
447
+ tmp_log.debug("done")
448
+ return json.dumps((True, "request was accepted and will be processed in a few minutes"))
449
+
450
+
451
+ # delete file
452
+ def deleteFile(panda_request: PandaRequest, file: FileStorage) -> str:
453
+ """
454
+ Delete a file from the cache directory.
455
+ Args:
456
+ panda_request (PandaRequest): PanDA request object.
457
+ file (string): file name to be deleted
458
+
459
+ Returns:
460
+ string: String with "True" or "False"
461
+ """
462
+ if not Protocol.isSecure(panda_request):
463
+ return ERROR_NOT_SECURE
464
+
465
+ try:
466
+ # may be reused for re-brokerage
467
+ # os.remove('%s/%s' % (panda_config.cache_dir,file.split('/')[-1]))
468
+ return "True"
469
+ except Exception:
470
+ return "False"
471
+
472
+
473
+ # touch file
474
+ def touchFile(panda_request: PandaRequest, filename: str) -> str:
475
+ """
476
+ Touch a file in the cache directory.
477
+ Args:
478
+ panda_request (PandaRequest): PanDA request object.
479
+ filename (string): file name to be deleted
480
+
481
+ Returns:
482
+ string: String with "True" or "False"
483
+ """
484
+ if not Protocol.isSecure(panda_request):
485
+ return "False"
486
+
487
+ try:
488
+ os.utime(f"{panda_config.cache_dir}/{filename.split('/')[-1]}", None)
489
+ return "True"
490
+ except Exception:
491
+ error_type, error_value = sys.exc_info()[:2]
492
+ _logger.error(f"touchFile : {error_type} {error_value}")
493
+ return "False"
494
+
495
+
496
+ # get server name:port for SSL
497
+ def getServer(panda_request: PandaRequest) -> str:
498
+ """
499
+ Get the server name and port for HTTPS.
500
+ Args:
501
+ panda_request (PandaRequest): PanDA request object.
502
+
503
+ Returns:
504
+ string: String with server:port
505
+ """
506
+ return f"{panda_config.pserverhost}:{panda_config.pserverport}"
507
+
508
+
509
+ # get server name:port for HTTP
510
+ def getServerHTTP(panda_request: PandaRequest) -> str:
511
+ """
512
+ Get the HTTP server name and port for HTTP.
513
+ Args:
514
+ panda_request (PandaRequest): PanDA request object.
515
+
516
+ Returns:
517
+ string: String with server:port
518
+ """
519
+ return f"{panda_config.pserverhosthttp}:{panda_config.pserverporthttp}"
520
+
521
+
522
+ def updateLog(panda_request: PandaRequest, file: FileStorage) -> str:
523
+ """
524
+ Update the log file, appending more content at the end of the file.
525
+ Args:
526
+ panda_request (PandaRequest): PanDA request object.
527
+ file (FileStorage): werkzeug.FileStorage object to be updated.
528
+
529
+ Returns:
530
+ string: String with "True" or error message
531
+ """
532
+ tmp_log = LogWrapper(_logger, f"updateLog < {file.filename} >")
533
+ tmp_log.debug("start")
534
+
535
+ # write to file
536
+ try:
537
+ # expand
538
+ new_content = zlib.decompress(file.read())
539
+
540
+ # stdout name
541
+ log_name = f"{panda_config.cache_dir}/{file.filename.split('/')[-1]}"
542
+
543
+ # append to file end
544
+ with open(log_name, "a") as file_object:
545
+ file_object.write(new_content)
546
+
547
+ except Exception:
548
+ error_type, error_value, _ = sys.exc_info()
549
+ tmp_log.error(f"{error_type} {error_value}")
550
+ return f"ERROR: cannot update file with {error_type} {error_value}"
551
+
552
+ tmp_log.debug("end")
553
+ return "True"
554
+
555
+
556
+ def fetchLog(panda_request: PandaRequest, logName: str, offset: int = 0) -> str:
557
+ """
558
+ Fetch the log file, if required at a particular offset.
559
+ Args:
560
+ panda_request (PandaRequest): PanDA request object.
561
+ logName (string): log file name
562
+ offset (int): offset in the file
563
+
564
+ Returns:
565
+ string: String with the log content
566
+ """
567
+ tmp_log = LogWrapper(_logger, f"fetchLog <{logName}>")
568
+ tmp_log.debug(f"start offset={offset}")
569
+
570
+ # put dummy char to avoid Internal Server Error
571
+ return_string = " "
572
+ try:
573
+ # stdout name
574
+ full_log_name = f"{panda_config.cache_dir}/{logName.split('/')[-1]}"
575
+
576
+ # read at offset of the file
577
+ with open(full_log_name, "r") as file_object:
578
+ file_object.seek(int(offset))
579
+ return_string += file_object.read()
580
+
581
+ except Exception:
582
+ error_type, error_value, _ = sys.exc_info()
583
+ tmp_log.error(f"{error_type} {error_value}")
584
+
585
+ tmp_log.debug(f"end read={len(return_string)}")
586
+ return return_string
587
+
588
+
589
+ def getVomsAttr(panda_request: PandaRequest) -> str:
590
+ """
591
+ Get the VOMS attributes in sorted order.
592
+ Args:
593
+ panda_request (PandaRequest): PanDA request object.
594
+
595
+ Returns:
596
+ string: String with the VOMS attributes
597
+ """
598
+ attributes = []
599
+
600
+ # Iterate over all the environment variables, keep only the ones related to GRST credentials (GRST: Grid Security Technology)
601
+ for tmp_key in panda_request.subprocess_env:
602
+ tmp_val = panda_request.subprocess_env[tmp_key]
603
+
604
+ # compact credentials
605
+ if tmp_key.startswith("GRST_CRED_"):
606
+ attributes.append(f"{tmp_key} : {tmp_val}\n")
607
+
608
+ return "".join(sorted(attributes))
609
+
610
+
611
+ def getAttr(panda_request: PandaRequest, **kv: dict) -> str:
612
+ """
613
+ Get all parameters and environment variables from the environment.
614
+ Args:
615
+ panda_request (PandaRequest): PanDA request object.
616
+ kv (dict): dictionary with key-value pairs
617
+
618
+ Returns:
619
+ string: String with the attributes
620
+ """
621
+ # add the parameters
622
+ return_string = "===== param =====\n"
623
+ for tmp_key in sorted(kv.keys()):
624
+ tmp_val = kv[tmp_key]
625
+ return_string += f"{tmp_key} = {tmp_val}\n"
626
+
627
+ # add the environment variables
628
+ attributes = []
629
+ for tmp_key in panda_request.subprocess_env:
630
+ tmp_val = panda_request.subprocess_env[tmp_key]
631
+ attributes.append(f"{tmp_key} : {tmp_val}\n")
632
+
633
+ return_string += "\n====== env ======\n"
634
+ attributes.sort()
635
+ for attribute in sorted(attributes):
636
+ return_string += attribute
637
+
638
+ return return_string
639
+
640
+
641
+ def uploadLog(panda_request: PandaRequest, file: FileStorage) -> str:
642
+ """
643
+ Upload a JEDI log file
644
+ Args:
645
+ panda_request (PandaRequest): PanDA request object.
646
+ file (FileStorage): werkzeug.FileStorage object to be uploaded.
647
+
648
+ Returns:
649
+ string: String with the URL to the file
650
+ """
651
+
652
+ if not Protocol.isSecure(panda_request):
653
+ return ERROR_NOT_SECURE
654
+ if "/CN=limited proxy" in panda_request.subprocess_env["SSL_CLIENT_S_DN"]:
655
+ return ERROR_LIMITED_PROXY
656
+
657
+ tmp_log = LogWrapper(_logger, f"uploadLog <{file.filename}>")
658
+ tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
659
+
660
+ # get file size
661
+ content_length = 0
662
+ try:
663
+ content_length = int(panda_request.headers_in["content-length"])
664
+ except Exception:
665
+ if "content-length" in panda_request.headers_in:
666
+ tmp_log.error(f"cannot get CL : {panda_request.headers_in['content-length']}")
667
+ else:
668
+ tmp_log.error("no CL")
669
+ tmp_log.debug(f"size {content_length}")
670
+
671
+ # check against the size limit for logs
672
+ if content_length > LOG_LIMIT:
673
+ error_message = ERROR_SIZE_LIMIT
674
+ tmp_log.error(error_message)
675
+ tmp_log.debug("end")
676
+ return error_message
677
+
678
+ jedi_log_directory = "/jedilog"
679
+ try:
680
+ file_base_name = file.filename.split("/")[-1]
681
+ full_path = f"{panda_config.cache_dir}{jedi_log_directory}/{file_base_name}"
682
+
683
+ # delete old file
684
+ if os.path.exists(full_path):
685
+ os.remove(full_path)
686
+
687
+ # write the new file
688
+ with open(full_path, "wb") as file_object:
689
+ file_content = file.read()
690
+ file_object.write(file_content)
691
+ tmp_log.debug(f"written to {full_path}")
692
+
693
+ # return the URL depending on the protocol
694
+ if panda_config.disableHTTP:
695
+ protocol = "https"
696
+ server = getServer(None)
697
+ else:
698
+ protocol = "http"
699
+ server = getServerHTTP(None)
700
+ return_string = f"{protocol}://{server}/cache{jedi_log_directory}/{file_base_name}"
701
+
702
+ except Exception:
703
+ error_type, error_value = sys.exc_info()[:2]
704
+ error_message = f"failed to write log with {error_type.__name__}:{error_value}"
705
+ tmp_log.error(error_message)
706
+ tmp_log.debug("end")
707
+ return error_message
708
+
709
+ tmp_log.debug("end")
710
+ return return_string
711
+
712
+
713
+ def create_shards(input_list: list, size: int) -> Generator:
714
+ """
715
+ Partitions input into shards of a given size for bulk operations.
716
+ @author: Miguel Branco in DQ2 Site Services code
717
+
718
+ Args:
719
+ input_list (list): list to be partitioned
720
+ size (int): size of the shards
721
+
722
+ Returns:
723
+ list: list of shards
724
+
725
+ """
726
+ shard, i = [], 0
727
+ for element in input_list:
728
+ shard.append(element)
729
+ i += 1
730
+ if i == size:
731
+ yield shard
732
+ shard, i = [], 0
733
+
734
+ if i > 0:
735
+ yield shard
736
+
737
+
738
+ def get_checkpoint_filename(task_id: str, sub_id: str) -> str:
739
+ """
740
+ Get the checkpoint file name.
741
+
742
+ Args:
743
+ task_id (str): task ID.
744
+ sub_id (str): sub ID.
745
+
746
+ Returns:
747
+ string: checkpoint file name.
748
+ """
749
+ return f"hpo_cp_{task_id}_{sub_id}"
750
+
751
+
752
+ def put_checkpoint(panda_request: PandaRequest, file: FileStorage) -> str:
753
+ """
754
+ Upload a HPO checkpoint file to the server.
755
+
756
+ Args:
757
+ panda_request (PandaRequest): PanDA request object.
758
+ file (FileStorage): werkzeug.FileStorage object to be uploaded.
759
+
760
+ Returns:
761
+ string: json formatted string with status and message.
762
+ """
763
+
764
+ tmp_log = LogWrapper(_logger, f"put_checkpoint <jediTaskID_subID={file.filename}>")
765
+
766
+ # operation status, will be set to True if successful
767
+ status = False
768
+
769
+ if not Protocol.isSecure(panda_request):
770
+ error_message = "insecure request"
771
+ tmp_log.error(error_message)
772
+ return json.dumps({"status": status, "message": error_message})
773
+
774
+ tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
775
+
776
+ # extract task ID and sub ID
777
+ try:
778
+ task_id, sub_id = file.filename.split("/")[-1].split("_")
779
+ except Exception:
780
+ error_message = "failed to extract ID"
781
+ tmp_log.error(error_message)
782
+ return json.dumps({"status": status, "message": error_message})
783
+
784
+ # get the file size
785
+ try:
786
+ content_length = int(panda_request.headers_in["content-length"])
787
+ except Exception as exc:
788
+ error_message = f"cannot get int(content-length) due to {str(exc)}"
789
+ tmp_log.error(error_message)
790
+ return json.dumps({"status": status, "message": error_message})
791
+ tmp_log.debug(f"size {content_length}")
792
+
793
+ # compare the size against the limit for checkpoints
794
+ if content_length > CHECKPOINT_LIMIT:
795
+ error_message = f"exceeded size limit {content_length}>{CHECKPOINT_LIMIT}"
796
+ tmp_log.error(error_message)
797
+ return json.dumps({"status": status, "message": error_message})
798
+
799
+ # write the file to the cache directory
800
+ try:
801
+ full_path = os.path.join(panda_config.cache_dir, get_checkpoint_filename(task_id, sub_id))
802
+ # write
803
+ with open(full_path, "wb") as file_object:
804
+ file_object.write(file.read())
805
+ except Exception as exc:
806
+ error_message = f"cannot write file due to {str(exc)}"
807
+ tmp_log.error(error_message)
808
+ return json.dumps({"status": status, "message": error_message})
809
+
810
+ status = True
811
+ success_message = f"successfully placed at {full_path}"
812
+ tmp_log.debug(success_message)
813
+ return json.dumps({"status": status, "message": success_message})
814
+
815
+
816
+ def delete_checkpoint(panda_request: PandaRequest, task_id: str, sub_id: str) -> str:
817
+ """
818
+ Delete a HPO checkpoint file from the server.
819
+
820
+ Args:
821
+ panda_request (PandaRequest): PanDA request object.
822
+ task_id (str): task ID.
823
+ sub_id (str): sub ID.
824
+
825
+ Returns:
826
+ string: json formatted string with status and message.
827
+ """
828
+
829
+ tmp_log = LogWrapper(_logger, f"delete_checkpoint <jediTaskID={task_id} ID={sub_id}>")
830
+
831
+ if not Protocol.isSecure(panda_request):
832
+ tmp_log.error(ERROR_NOT_SECURE)
833
+ return json.dumps({"status": False, "message": ERROR_NOT_SECURE})
834
+
835
+ tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
836
+ # operation status
837
+ status = True
838
+ try:
839
+ full_path = os.path.join(panda_config.cache_dir, get_checkpoint_filename(task_id, sub_id))
840
+ os.remove(full_path)
841
+ message = "done"
842
+ tmp_log.debug(message)
843
+ except Exception as exc:
844
+ message = f"failed to delete file due to {str(exc)}"
845
+ tmp_log.error(message)
846
+ status = False
847
+
848
+ return json.dumps({"status": status, "message": message})