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,539 @@
1
+ #!/usr/bin/python
2
+
3
+ """
4
+ entry point
5
+
6
+ """
7
+
8
+ import datetime
9
+ import decimal
10
+ import gzip
11
+ import io
12
+ import json
13
+ import os
14
+ import signal
15
+ import sys
16
+ import tempfile
17
+ import traceback
18
+ from collections import defaultdict
19
+ from urllib.parse import parse_qsl
20
+
21
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
22
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
23
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
24
+ from pandacommon.pandautils.thread_utils import GenericThread
25
+ from werkzeug.datastructures import CombinedMultiDict, EnvironHeaders
26
+ from werkzeug.formparser import parse_form_data
27
+
28
+ import pandaserver.taskbuffer.ErrorCode
29
+ from pandaserver.api.v1 import credential_management_api as cred_api_v1
30
+ from pandaserver.api.v1 import data_carousel_api as data_carousel_api_v1
31
+ from pandaserver.api.v1 import event_api as event_api_v1
32
+ from pandaserver.api.v1 import file_server_api as file_server_api_v1
33
+ from pandaserver.api.v1 import harvester_api as harvester_api_v1
34
+ from pandaserver.api.v1 import idds_api as idds_api_v1
35
+ from pandaserver.api.v1 import job_api as job_api_v1
36
+ from pandaserver.api.v1 import metaconfig_api as metaconfig_api_v1
37
+ from pandaserver.api.v1 import pilot_api as pilot_api_v1
38
+ from pandaserver.api.v1 import statistics_api as statistics_api_v1
39
+ from pandaserver.api.v1 import system_api as system_api_v1
40
+ from pandaserver.api.v1 import task_api as task_api_v1
41
+ from pandaserver.api.v1 import workflow_api as workflow_api_v1
42
+ from pandaserver.api.v1.common import extract_allowed_methods
43
+ from pandaserver.config import panda_config
44
+
45
+ # pylint: disable=W0611
46
+ from pandaserver.jobdispatcher.JobDispatcher import (
47
+ ackCommands,
48
+ checkEventsAvailability,
49
+ checkJobStatus,
50
+ get_access_token,
51
+ get_events_status,
52
+ get_max_worker_id,
53
+ get_token_key,
54
+ getCommands,
55
+ getEventRanges,
56
+ getJob,
57
+ getKeyPair,
58
+ getProxy,
59
+ getResourceTypes,
60
+ getStatus,
61
+ jobDispatcher,
62
+ updateEventRange,
63
+ updateEventRanges,
64
+ updateJob,
65
+ updateJobsInBulk,
66
+ updateWorkerPilotStatus,
67
+ )
68
+ from pandaserver.srvcore import CoreUtils
69
+
70
+ # IMPORTANT: Add any new methods here to allow them to be called from the web I/F
71
+ from pandaserver.srvcore.allowed_methods import allowed_methods
72
+ from pandaserver.srvcore.panda_request import PandaRequest
73
+ from pandaserver.taskbuffer.Initializer import initializer
74
+ from pandaserver.taskbuffer.TaskBuffer import taskBuffer
75
+
76
+ # pylint: disable=W0611
77
+ from pandaserver.taskbuffer.Utils import (
78
+ delete_checkpoint,
79
+ deleteFile,
80
+ fetchLog,
81
+ getAttr,
82
+ getServer,
83
+ getVomsAttr,
84
+ isAlive,
85
+ put_checkpoint,
86
+ put_file_recovery_request,
87
+ put_workflow_request,
88
+ putEventPickingRequest,
89
+ putFile,
90
+ touchFile,
91
+ updateLog,
92
+ uploadLog,
93
+ )
94
+ from pandaserver.userinterface import Client
95
+
96
+ # pylint: disable=W0611
97
+ from pandaserver.userinterface.UserIF import (
98
+ addHarvesterDialogs,
99
+ avalancheTask,
100
+ changeTaskAttributePanda,
101
+ changeTaskModTimePanda,
102
+ changeTaskPriority,
103
+ changeTaskSplitRulePanda,
104
+ checkSandboxFile,
105
+ enableJumboJobs,
106
+ execute_idds_workflow_command,
107
+ finishTask,
108
+ get_ban_users,
109
+ get_files_in_datasets,
110
+ get_job_statistics_per_site_label_resource,
111
+ get_user_secrets,
112
+ getFullJobStatus,
113
+ getJediTaskDetails,
114
+ getJediTasksInTimeRange,
115
+ getJobStatisticsPerSite,
116
+ getJobStatus,
117
+ getJumboJobDatasets,
118
+ getPandaIDsWithTaskID,
119
+ getScriptOfflineRunning,
120
+ getTaskParamsMap,
121
+ getTaskStatus,
122
+ getUserJobMetadata,
123
+ getWorkerStats,
124
+ harvesterIsAlive,
125
+ increaseAttemptNrPanda,
126
+ insertSandboxFileInfo,
127
+ insertTaskParams,
128
+ killJobs,
129
+ killTask,
130
+ killUnfinishedJobs,
131
+ pauseTask,
132
+ reactivateTask,
133
+ reassignJobs,
134
+ reassignShare,
135
+ reassignTask,
136
+ relay_idds_command,
137
+ release_task,
138
+ reloadInput,
139
+ reportWorkerStats_jobtype,
140
+ resumeTask,
141
+ retryTask,
142
+ send_command_to_job,
143
+ set_user_secret,
144
+ setDebugMode,
145
+ setNumSlotsForWP,
146
+ submitJobs,
147
+ sweepPQ,
148
+ updateServiceMetrics,
149
+ updateWorkers,
150
+ userIF,
151
+ )
152
+
153
+ _logger = PandaLogger().getLogger("Entry")
154
+
155
+ LATEST = "1"
156
+
157
+ # generate the allowed methods dynamically with all function names present in the API modules,
158
+ # excluding functions imported from other modules or the init_task_buffer function
159
+ cred_api_v1_methods = extract_allowed_methods(cred_api_v1)
160
+ data_carousel_api_v1_methods = extract_allowed_methods(data_carousel_api_v1)
161
+ event_api_v1_methods = extract_allowed_methods(event_api_v1)
162
+ file_server_api_v1_methods = extract_allowed_methods(file_server_api_v1)
163
+ harvester_api_v1_methods = extract_allowed_methods(harvester_api_v1)
164
+ idds_api_v1_methods = extract_allowed_methods(idds_api_v1)
165
+ job_api_v1_methods = extract_allowed_methods(job_api_v1)
166
+ metaconfig_api_v1_methods = extract_allowed_methods(metaconfig_api_v1)
167
+ pilot_api_v1_methods = extract_allowed_methods(pilot_api_v1)
168
+ statistics_api_v1_methods = extract_allowed_methods(statistics_api_v1)
169
+ system_api_v1_methods = extract_allowed_methods(system_api_v1)
170
+ task_api_v1_methods = extract_allowed_methods(task_api_v1)
171
+ workflow_api_v1_methods = extract_allowed_methods(workflow_api_v1)
172
+
173
+ # initialize oracledb using dummy connection
174
+ initializer.init()
175
+
176
+ # initialize TaskBuffer
177
+ requester_id = GenericThread().get_full_id(__name__, sys.modules[__name__].__file__)
178
+ taskBuffer.init(
179
+ panda_config.dbhost,
180
+ panda_config.dbpasswd,
181
+ nDBConnection=panda_config.nDBConnection,
182
+ useTimeout=True,
183
+ requester=requester_id,
184
+ )
185
+
186
+ if panda_config.nDBConnection != 0:
187
+ # initialize all the API modules
188
+ cred_api_v1.init_task_buffer(taskBuffer)
189
+ data_carousel_api_v1.init_task_buffer(taskBuffer)
190
+ event_api_v1.init_task_buffer(taskBuffer)
191
+ file_server_api_v1.init_task_buffer(taskBuffer)
192
+ harvester_api_v1.init_task_buffer(taskBuffer)
193
+ # IDDS API does not need to be initialized. idds_server_api_v1.init_task_buffer(taskBuffer)
194
+ job_api_v1.init_task_buffer(taskBuffer)
195
+ metaconfig_api_v1.init_task_buffer(taskBuffer)
196
+ pilot_api_v1.init_task_buffer(taskBuffer)
197
+ statistics_api_v1.init_task_buffer(taskBuffer)
198
+ # System API does not need to be initialized. system_api_v1.init_task_buffer(taskBuffer)
199
+ task_api_v1.init_task_buffer(taskBuffer)
200
+ workflow_api_v1.init_task_buffer(taskBuffer)
201
+
202
+ # initialize JobDispatcher
203
+ jobDispatcher.init(taskBuffer)
204
+
205
+ # initialize UserIF
206
+ userIF.init(taskBuffer)
207
+
208
+ # ban list
209
+ if panda_config.nDBConnection != 0:
210
+ # get ban list directly from the database
211
+ ban_user_list = CoreUtils.CachedObject("ban_list", 600, taskBuffer.get_ban_users, _logger)
212
+ else:
213
+ # get ban list from remote
214
+ ban_user_list = CoreUtils.CachedObject("ban_list", 600, Client.get_banned_users, _logger)
215
+
216
+
217
+ def pre_validate_request(panda_request):
218
+ # check authentication
219
+ if not panda_request.authenticated:
220
+ error_message = f"Token authentication failed. {panda_request.message}"
221
+ return error_message
222
+
223
+ # check list of banned users
224
+ username = panda_request.subprocess_env.get("SSL_CLIENT_S_DN", None)
225
+ if username:
226
+ username = CoreUtils.clean_user_id(username)
227
+ if username in ban_user_list:
228
+ error_message = f"{username} is banned"
229
+ return error_message
230
+
231
+ return None
232
+
233
+
234
+ def read_body(environ, content_length):
235
+ # read body contents
236
+ body = b""
237
+ while content_length > 0:
238
+ chunk = environ["wsgi.input"].read(min(content_length, 1024 * 1024))
239
+ if not chunk:
240
+ break
241
+ content_length -= len(chunk)
242
+ body += chunk
243
+ if content_length > 0:
244
+ # OSError is caught in the main function and forces killing the process
245
+ raise OSError(f"partial read from client. {content_length} bytes remaining")
246
+
247
+ return body
248
+
249
+
250
+ def parse_qsl_parameters(environ, body, request_method):
251
+ # parse parameters for non-json requests
252
+ environ["wsgi.input"] = io.BytesIO(body)
253
+ environ["CONTENT_LENGTH"] = str(len(body))
254
+ environ["wsgi.headers"] = EnvironHeaders(environ)
255
+
256
+ # In the case of GET, HEAD methods we need to parse the query string list in the URL looking for parameters
257
+ if request_method in ["GET", "HEAD"]:
258
+ # Parse the query string list in the URL looking for parameters. Repeated query parameters submitted multiple times will be appended to a list
259
+ results_tmp = defaultdict(list)
260
+ parameter_list = parse_qsl(environ.get("QUERY_STRING", ""), keep_blank_values=True)
261
+ for key, value in parameter_list:
262
+ results_tmp[key].append(value)
263
+
264
+ params = {key: values[0] if len(values) == 1 else values for key, values in results_tmp.items()}
265
+
266
+ # In the case of POST, PUT methods we need to parse the form data
267
+ else:
268
+ # Parse form data. Combine the form (string fields) and the files (file uploads) into a single object
269
+ _, form, files = parse_form_data(environ)
270
+
271
+ # Combine the form and files into a single dictionary
272
+ params = dict(CombinedMultiDict([form, files]))
273
+ return params
274
+
275
+
276
+ def parse_json_parameters_legacy(body):
277
+ # parse parameters for json requests
278
+ # decompress the body, this was done without checking the content encoding
279
+ body = gzip.decompress(body)
280
+
281
+ # de-serialize the body and patch for True/False
282
+ params = json.loads(body)
283
+ for key in list(params):
284
+ if params[key] is True:
285
+ params[key] = "True"
286
+ elif params[key] is False:
287
+ params[key] = "False"
288
+ return params
289
+
290
+
291
+ def parse_json_parameters(body, content_encoding):
292
+ # parse parameters for json requests
293
+ # decompress the body if necessary
294
+ if content_encoding == "gzip":
295
+ body = gzip.decompress(body)
296
+
297
+ # de-serialize the body
298
+ params = json.loads(body)
299
+
300
+ return params
301
+
302
+
303
+ def parse_parameters(api_module, json_app, json_body, content_encoding, environ, body, request_method):
304
+ # parse parameters with the new refactored API
305
+ if is_new_api(api_module):
306
+ # the request specifies json and it's a PUT/POST request with the data in the body
307
+ if json_body:
308
+ return parse_json_parameters(body, content_encoding)
309
+ else:
310
+ return parse_qsl_parameters(environ, body, request_method)
311
+
312
+ # parse parameters conserving the legacy API logic
313
+ else:
314
+ # parse parameters for json requests with the legacy API, even for GET requests
315
+ if json_app:
316
+ return parse_json_parameters_legacy(body)
317
+ # parse parameters for non-json requests
318
+ else:
319
+ return parse_qsl_parameters(environ, body, request_method)
320
+
321
+
322
+ def is_new_api(api_module):
323
+ return api_module != "panda"
324
+
325
+
326
+ def parse_script_name(environ):
327
+ method_name = ""
328
+ api_module = ""
329
+ version = "v0"
330
+
331
+ if "SCRIPT_NAME" in environ:
332
+ script_name = environ["SCRIPT_NAME"]
333
+ fields = script_name.split("/")
334
+
335
+ # Legacy API: /server/panda/<method>
336
+ if script_name.startswith("/server/panda/") and len(fields) == 4:
337
+ api_module = "panda"
338
+ method_name = fields[-1]
339
+
340
+ # Refactored API: /api/<version>/<module>/<method>
341
+ elif script_name.startswith("/api/") and len(fields) == 5:
342
+ method_name = fields[-1]
343
+ api_module = fields[-2]
344
+ version = fields[-3]
345
+ if version == "latest":
346
+ version = LATEST
347
+
348
+ else:
349
+ _logger.error(f"Could not parse script name: {script_name}")
350
+
351
+ return method_name, api_module, version
352
+
353
+
354
+ def module_mapping(version, api_module):
355
+ mapping = {
356
+ "v0": {"panda": {"module": None, "allowed_methods": allowed_methods}}, # legacy API uses globals instead of a particular module
357
+ "v1": {
358
+ "creds": {"module": cred_api_v1, "allowed_methods": cred_api_v1_methods},
359
+ "data_carousel": {"module": data_carousel_api_v1, "allowed_methods": data_carousel_api_v1_methods},
360
+ "event": {"module": event_api_v1, "allowed_methods": event_api_v1_methods},
361
+ "file_server": {"module": file_server_api_v1, "allowed_methods": file_server_api_v1_methods},
362
+ "harvester": {"module": harvester_api_v1, "allowed_methods": harvester_api_v1_methods},
363
+ "idds": {"module": idds_api_v1, "allowed_methods": idds_api_v1_methods},
364
+ "job": {"module": job_api_v1, "allowed_methods": job_api_v1_methods},
365
+ "metaconfig": {"module": metaconfig_api_v1, "allowed_methods": metaconfig_api_v1_methods},
366
+ "pilot": {"module": pilot_api_v1, "allowed_methods": pilot_api_v1_methods},
367
+ "statistics": {"module": statistics_api_v1, "allowed_methods": statistics_api_v1_methods},
368
+ "system": {"module": system_api_v1, "allowed_methods": system_api_v1_methods},
369
+ "task": {"module": task_api_v1, "allowed_methods": task_api_v1_methods},
370
+ "workflow": {"module": workflow_api_v1, "allowed_methods": workflow_api_v1_methods},
371
+ },
372
+ }
373
+ try:
374
+ return mapping[version][api_module]
375
+ except KeyError:
376
+ _logger.error(f"Could not find module {api_module} in API version {version}")
377
+ return None
378
+
379
+
380
+ def validate_method(method_name, api_module, version):
381
+ # We are in the refactored API and the method is not in the specific allowed list
382
+ mapping = module_mapping(version, api_module)
383
+ if mapping and method_name in mapping["allowed_methods"]:
384
+ return True
385
+
386
+ return False
387
+
388
+
389
+ # Encoder: convert datetime → ISO string with a marker
390
+ def encode_special_cases(obj):
391
+ if isinstance(obj, datetime.datetime):
392
+ return {"__datetime__": obj.isoformat()}
393
+ if isinstance(obj, decimal.Decimal):
394
+ if obj == obj.to_integral_value():
395
+ return int(obj)
396
+ else:
397
+ return float(obj)
398
+ raise TypeError(f"Type not serializable for {obj} ({type(obj)})")
399
+
400
+
401
+ # This is the starting point for all WSGI requests
402
+ def application(environ, start_response):
403
+ # Parse the script name to retrieve method, module and version
404
+ method_name, api_module, version = parse_script_name(environ)
405
+
406
+ tmp_log = LogWrapper(_logger, f"PID={os.getpid()} module={api_module} method={method_name} version={version}", seeMem=True)
407
+ cont_length = int(environ.get("CONTENT_LENGTH", 0))
408
+ request_method = environ.get("REQUEST_METHOD", None) # GET, POST, PUT, DELETE
409
+
410
+ # see if we are on the new or old APIs
411
+ new_api = is_new_api(api_module)
412
+
413
+ # json app means the content type is application/json,
414
+ # while json body requires additionally to be a PUT or POST request, where the body is json encoded
415
+ if new_api:
416
+ json_app = environ.get("CONTENT_TYPE", None) == "application/json" or environ.get("HTTP_ACCEPT", None) == "application/json"
417
+ else:
418
+ json_app = environ.get("CONTENT_TYPE", None) == "application/json"
419
+ json_body = environ.get("CONTENT_TYPE", None) == "application/json" and request_method in ["PUT", "POST"]
420
+
421
+ # Content encoding specifies whether the body is compressed through gzip or others.
422
+ # No encoding usually means the body is not compressed
423
+ content_encoding = environ.get("HTTP_CONTENT_ENCODING")
424
+
425
+ tmp_log.debug(f"""start content-length={cont_length} json={json_app} origin={environ.get("HTTP_ORIGIN", None)}""")
426
+
427
+ start_time = naive_utcnow()
428
+ return_type = None
429
+
430
+ # check method name is allowed, otherwise return 403
431
+ if not validate_method(method_name, api_module, version):
432
+ error_message = f"method {method_name} is forbidden"
433
+ tmp_log.error(error_message)
434
+ start_response("403 Forbidden", [("Content-Type", "text/plain")])
435
+ return [f"ERROR : {error_message}".encode()]
436
+
437
+ # get the method object to be executed
438
+ try:
439
+ if new_api:
440
+ module = module_mapping(version, api_module)["module"]
441
+ tmp_method = getattr(module, method_name)
442
+ else:
443
+ tmp_method = globals()[method_name]
444
+ except Exception:
445
+ error_message = f"method {method_name} is undefined in {api_module} {version}"
446
+ tmp_log.error(error_message)
447
+ start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")])
448
+ return ["ERROR : {error_message}".encode()]
449
+
450
+ try:
451
+ # generate a request object with the environment and the logger
452
+ panda_request = PandaRequest(environ, tmp_log)
453
+
454
+ # pre-validate the request
455
+ error_message = pre_validate_request(panda_request)
456
+ if error_message:
457
+ tmp_log.error(error_message)
458
+ start_response("403 Forbidden", [("Content-Type", "text/plain")])
459
+ return [f"ERROR : {error_message}".encode()]
460
+
461
+ # read the body of the request
462
+ body = read_body(environ, cont_length)
463
+
464
+ # parse the parameters
465
+ params = parse_parameters(api_module, json_app, json_body, content_encoding, environ, body, request_method)
466
+
467
+ if panda_config.entryVerbose:
468
+ tmp_log.debug(f"with {str(list(params))}")
469
+
470
+ # execute the method, passing along the request and the decoded parameters
471
+ param_list = [panda_request]
472
+ exec_result = tmp_method(*param_list, **params)
473
+
474
+ # extract return type
475
+ if isinstance(exec_result, dict) and "type" in exec_result and "content" in exec_result:
476
+ return_type = exec_result["type"]
477
+ exec_result = exec_result["content"]
478
+
479
+ # convert bool to string
480
+ if exec_result in [True, False]:
481
+ exec_result = str(exec_result)
482
+
483
+ # convert the response to JSON or str depending on HTTP_ACCEPT and CONTENT_TYPE
484
+ if new_api:
485
+ if json_app:
486
+ exec_result = json.dumps(exec_result, default=encode_special_cases)
487
+ elif not isinstance(exec_result, str):
488
+ exec_result = str(exec_result)
489
+
490
+ except Exception as exc:
491
+ tmp_log.error(f"execution failure : {str(exc)}\n {traceback.format_exc()}")
492
+ if hasattr(panda_config, "dumpBadRequest") and panda_config.dumpBadRequest:
493
+ try:
494
+ with tempfile.NamedTemporaryFile(delete=False, prefix="req_dump_") as file_object:
495
+ environ["WSGI_INPUT_DUMP"] = file_object.name
496
+ file_object.write(body)
497
+ os.chmod(file_object.name, 0o775)
498
+ except Exception:
499
+ tmp_log.error(traceback.format_exc())
500
+ pass
501
+ error_string = "\n".join(f"{tmp_key} : {str(tmp_value)}" for tmp_key, tmp_value in environ.items())
502
+ tmp_log.error(error_string)
503
+
504
+ # return internal server error
505
+ start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")])
506
+ # force kill to release memory
507
+ if isinstance(exc, OSError):
508
+ tmp_log.warning("force restart due")
509
+ os.kill(os.getpid(), signal.SIGINT)
510
+
511
+ return [str(exc).encode()]
512
+
513
+ if panda_config.entryVerbose:
514
+ tmp_log.debug("done")
515
+
516
+ # log execution time and return length
517
+ duration = naive_utcnow() - start_time
518
+ tmp_log.info(
519
+ f"exec_time={duration.seconds}.{duration.microseconds // 1000:03d} sec, return_type={return_type} real_type={type(exec_result).__name__} len={len(str(exec_result))} B"
520
+ )
521
+
522
+ # start the response and return result
523
+ if exec_result == pandaserver.taskbuffer.ErrorCode.EC_NotFound:
524
+ start_response("404 Not Found", [("Content-Type", "text/plain")])
525
+ return ["not found".encode()]
526
+
527
+ if exec_result == pandaserver.taskbuffer.ErrorCode.EC_Forbidden:
528
+ start_response("403 Forbidden", [("Content-Type", "text/plain")])
529
+ return ["forbidden".encode()]
530
+
531
+ if return_type == "json":
532
+ start_response("200 OK", [("Content-Type", "application/json")])
533
+ else:
534
+ start_response("200 OK", [("Content-Type", "text/plain")])
535
+
536
+ if isinstance(exec_result, str):
537
+ exec_result = exec_result.encode()
538
+
539
+ return [exec_result]