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,180 @@
1
+ import datetime
2
+ import json
3
+ import traceback
4
+
5
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
6
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
7
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
8
+
9
+ from pandaserver.api.v1.common import generate_response, request_validation
10
+ from pandaserver.srvcore.CoreUtils import clean_user_id
11
+
12
+ try:
13
+ import idds.common.constants
14
+ import idds.common.utils
15
+ from idds.client.client import Client as iDDS_Client
16
+ from idds.client.clientmanager import ClientManager as iDDS_ClientManager
17
+ except ImportError:
18
+ pass
19
+
20
+ _logger = PandaLogger().getLogger("api_idds")
21
+
22
+
23
+ # json decoder for idds constants
24
+ def decode_idds_enum(d):
25
+ if "__idds_const__" in d:
26
+ items = d["__idds_const__"].split(".")
27
+ obj = idds.common.constants
28
+ for item in items:
29
+ obj = getattr(obj, item)
30
+ return obj
31
+ else:
32
+ return d
33
+
34
+
35
+ @request_validation(_logger, secure=True, request_method="POST")
36
+ def relay_idds_command(req, command_name: str, args: str = None, kwargs: str = None, manager: bool = False, json_outputs: bool = False):
37
+ tmp_log = LogWrapper(
38
+ _logger,
39
+ f"relay_idds_command-{naive_utcnow().isoformat('/')}",
40
+ )
41
+
42
+ try:
43
+ if "+" in command_name:
44
+ command_name, idds_host = command_name.split("+")
45
+ else:
46
+ idds_host = idds.common.utils.get_rest_host()
47
+
48
+ if manager:
49
+ c = iDDS_ClientManager(idds_host)
50
+ else:
51
+ c = iDDS_Client(idds_host)
52
+
53
+ if not hasattr(c, command_name):
54
+ tmp_str = f"{command_name} is not a command of iDDS {c.__class__.__name__}"
55
+ tmp_log.error(tmp_str)
56
+ return generate_response(False, tmp_str)
57
+
58
+ if args:
59
+ try:
60
+ args = idds.common.utils.json_loads(args)
61
+ except Exception as e:
62
+ tmp_log.warning(f"failed to load args json with {str(e)}")
63
+ args = json.loads(args, object_hook=decode_idds_enum)
64
+ else:
65
+ args = []
66
+
67
+ if kwargs:
68
+ try:
69
+ kwargs = idds.common.utils.json_loads(kwargs)
70
+ except Exception as e:
71
+ tmp_log.warning(f"failed to load kwargs json with {str(e)}")
72
+ kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
73
+ else:
74
+ kwargs = {}
75
+
76
+ # json outputs
77
+ if json_outputs and manager:
78
+ c.setup_json_outputs()
79
+
80
+ # set original username
81
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
82
+ if dn:
83
+ c.set_original_user(user_name=clean_user_id(dn))
84
+
85
+ tmp_log.debug(f"execute: class={c.__class__.__name__} com={command_name} host={idds_host} args={str(args)[:200]} kwargs={str(kwargs)[:200]}")
86
+ ret = getattr(c, command_name)(*args, **kwargs)
87
+ tmp_log.debug(f"ret: {str(ret)[:200]}")
88
+
89
+ try:
90
+ return generate_response(True, "", ret)
91
+ except Exception:
92
+ # TODO: I don't know how to handle this
93
+ return idds.common.utils.json_dumps((True, ret))
94
+
95
+ except Exception as e:
96
+ tmp_str = f"failed to execute command with {str(e)}"
97
+ tmp_log.error(f"{tmp_str} {traceback.format_exc()}")
98
+ return generate_response(False, tmp_str)
99
+
100
+
101
+ # relay iDDS workflow command with ownership check
102
+ @request_validation(_logger, secure=True, request_method="POST")
103
+ def execute_idds_workflow_command(req, command_name: str, kwargs: str = None, json_outputs: bool = False):
104
+ tmp_log = LogWrapper(
105
+ _logger,
106
+ f"execute_idds_workflow_command-{naive_utcnow().isoformat('/')}",
107
+ )
108
+ try:
109
+ if kwargs:
110
+ try:
111
+ kwargs = idds.common.utils.json_loads(kwargs)
112
+ except Exception:
113
+ kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
114
+ else:
115
+ kwargs = {}
116
+
117
+ if "+" in command_name:
118
+ command_name, idds_host = command_name.split("+")
119
+ else:
120
+ idds_host = idds.common.utils.get_rest_host()
121
+
122
+ # check permission
123
+ if command_name in ["get_status"]:
124
+ check_owner = False
125
+ elif command_name in ["abort", "suspend", "resume", "retry", "finish"]:
126
+ check_owner = True
127
+ else:
128
+ tmp_message = f"{command_name} is unsupported"
129
+ tmp_log.error(tmp_message)
130
+ return generate_response(False, tmp_message)
131
+
132
+ # check owner
133
+ c = iDDS_ClientManager(idds_host)
134
+ if json_outputs:
135
+ c.setup_json_outputs()
136
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
137
+ if check_owner:
138
+ # requester
139
+ if not dn:
140
+ tmp_message = "SSL_CLIENT_S_DN is missing in HTTP request"
141
+ tmp_log.error(tmp_message)
142
+ return generate_response(False, tmp_message)
143
+ requester = clean_user_id(dn)
144
+
145
+ # get request_id
146
+ request_id = kwargs.get("request_id")
147
+ if request_id is None:
148
+ tmp_message = "request_id is missing"
149
+ tmp_log.error(tmp_message)
150
+ return generate_response(False, tmp_message)
151
+
152
+ # get request
153
+ req = c.get_requests(request_id=request_id)
154
+ if not req:
155
+ tmp_message = f"request {request_id} is not found"
156
+ tmp_log.error(tmp_message)
157
+ return generate_response(False, tmp_message)
158
+
159
+ user_name = req[0].get("username")
160
+ if user_name and user_name != requester:
161
+ tmp_message = f"request {request_id} is not owned by {requester}"
162
+ tmp_log.error(tmp_message)
163
+ return generate_response(False, tmp_message)
164
+
165
+ # set original username
166
+ if dn:
167
+ c.set_original_user(user_name=clean_user_id(dn))
168
+
169
+ # execute command
170
+ tmp_log.debug(f"com={command_name} host={idds_host} kwargs={str(kwargs)}")
171
+ ret = getattr(c, command_name)(**kwargs)
172
+ tmp_log.debug(str(ret))
173
+
174
+ if isinstance(ret, dict) and "message" in ret:
175
+ return generate_response(True, ret["message"], ret["status"])
176
+
177
+ return generate_response(True, "", ret)
178
+ except Exception as e:
179
+ tmp_log.error(f"failed with {str(e)} {traceback.format_exc()}")
180
+ return generate_response(False, f"server failed with {str(e)}")
@@ -0,0 +1,463 @@
1
+ import sys
2
+ import traceback
3
+ from typing import Dict, List
4
+
5
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
6
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
7
+
8
+ from pandaserver.api.v1.common import (
9
+ TimedMethod,
10
+ extract_primary_production_working_group,
11
+ extract_production_working_groups,
12
+ generate_response,
13
+ get_dn,
14
+ get_email_address,
15
+ get_fqan,
16
+ has_production_role,
17
+ request_validation,
18
+ )
19
+ from pandaserver.jobdispatcher import Protocol
20
+ from pandaserver.srvcore.panda_request import PandaRequest
21
+ from pandaserver.taskbuffer import JobUtils
22
+ from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
23
+
24
+ _logger = PandaLogger().getLogger("api_job")
25
+
26
+ global_task_buffer = None
27
+
28
+
29
+ def init_task_buffer(task_buffer: TaskBuffer) -> None:
30
+ """
31
+ Initialize the task buffer. This method needs to be called before any other method in this module.
32
+ """
33
+ global global_task_buffer
34
+ global_task_buffer = task_buffer
35
+
36
+
37
+ @request_validation(_logger, secure=True, request_method="GET")
38
+ def get_status(req: PandaRequest, job_ids: List[int], timeout: int = 60) -> Dict:
39
+ """
40
+ Get status of a job.
41
+
42
+ Gets the status for a job and command to the pilot if any. Requires a secure connection.
43
+
44
+ API details:
45
+ HTTP Method: GET
46
+ Path: /v1/job/get_status
47
+
48
+ Args:
49
+ req(PandaRequest): internally generated request object containing the env variables
50
+ job_ids(list of int): list of PanDA job IDs.
51
+ timeout(int, optional): The timeout value. Defaults to 60.
52
+
53
+ Returns:
54
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list of tupes with (status, command). When unsuccessful, the message field contains the error message and data an error code.
55
+ """
56
+ tmp_logger = LogWrapper(_logger, f"get_status job_ids={job_ids} timeout={timeout}")
57
+ tmp_logger.debug("Start")
58
+
59
+ # The task buffer method expect a comma separated list of job_ids
60
+ job_ids_str = ",".join([str(job_id) for job_id in job_ids])
61
+ timed_method = TimedMethod(global_task_buffer.checkJobStatus, timeout)
62
+ timed_method.run(job_ids_str)
63
+
64
+ # Time out
65
+ if timed_method.result == Protocol.TimeOutToken:
66
+ tmp_logger.debug("Timed out")
67
+ return generate_response(False, message="time out", data={"code": Protocol.SC_TimeOut})
68
+
69
+ # No result
70
+ if not isinstance(timed_method.result, list):
71
+ tmp_logger.debug(f"Failed")
72
+ return generate_response(False, message="failed", data={"code": Protocol.SC_Failed})
73
+
74
+ # Success
75
+ data = timed_method.result
76
+ tmp_logger.debug(f"Done with data={data}")
77
+ return generate_response(True, data=data)
78
+
79
+
80
+ @request_validation(_logger, secure=True)
81
+ def get_description(self, job_ids: List[int]) -> Dict:
82
+ """
83
+ Get description of a job.
84
+
85
+ Gets the description of a job from the main/active schema. The description includes job attributes, job parameters and related file attributes. Requires a secure connection.
86
+
87
+ API details:
88
+ HTTP Method: GET
89
+ Path: /v1/job/get_description
90
+
91
+ Args:
92
+ req(PandaRequest): internally generated request object containing the env variables
93
+ job_ids (list of int): List of PanDA job IDs.
94
+ timeout (int, optional): The timeout value. Defaults to 60.
95
+
96
+ Returns:
97
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list with job descriptions. When unsuccessful, the message field contains the error message and data an error code.
98
+ """
99
+ tmp_logger = LogWrapper(_logger, f"get_description")
100
+ tmp_logger.debug("Start")
101
+
102
+ try:
103
+ tmp_logger.debug(f"Number of requested PanDA IDs: {len(job_ids)}")
104
+ max_ids = 5500
105
+ if len(job_ids) > max_ids:
106
+ tmp_logger.error(f"List of PanDA IDs is longer than {max_ids}. Truncating the list.")
107
+ job_ids = job_ids[:max_ids]
108
+ except Exception:
109
+ error_type, error_value, _ = sys.exc_info()
110
+ tmp_logger.error(f"Failed deserializing the list of PanDA IDs: {error_type} {error_value}")
111
+ job_ids = []
112
+
113
+ tmp_logger.debug(f"Retrieving data for {job_ids}")
114
+ ret = [job.to_dict_advanced() if job is not None else None for job in global_task_buffer.peekJobs(job_ids)]
115
+ _logger.debug("Done")
116
+
117
+ return generate_response(True, data=ret)
118
+
119
+
120
+ @request_validation(_logger, secure=True)
121
+ def get_description_incl_archive(req: PandaRequest, job_ids: List[int]) -> Dict:
122
+ """
123
+ Get description of a job.
124
+
125
+ Gets the description of a job, also looking into the secondary/archive schema. The description includes job attributes, job parameters and related file attributes. Requires a secure connection.
126
+
127
+ API details:
128
+ HTTP Method: GET
129
+ Path: /v1/job/get_description_incl_archive
130
+
131
+ Args:
132
+ req(PandaRequest): internally generated request object containing the env variables.
133
+ job_ids (list of int): List of PanDA job IDs.
134
+ timeout (int, optional): The timeout value. Defaults to 60.
135
+
136
+ Returns:
137
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list with job descriptions. When unsuccessful, the message field contains the error message and data an error code.
138
+ """
139
+ tmp_logger = LogWrapper(_logger, f"get_description_including_archive")
140
+ tmp_logger.debug("Start")
141
+
142
+ try:
143
+ tmp_logger.debug(f"Number of requested PanDA IDs: {len(job_ids)}")
144
+ max_ids = 5500
145
+ if len(job_ids) > max_ids:
146
+ tmp_logger.error(f"List of PanDA IDs is longer than {max_ids}. Truncating the list.")
147
+ job_ids = job_ids[:max_ids]
148
+ except Exception:
149
+ error_type, error_value, _ = sys.exc_info()
150
+ tmp_logger.error(f"Failed deserializing the list of PanDA IDs: {error_type} {error_value}")
151
+ job_ids = []
152
+
153
+ tmp_logger.debug(f"Retrieving data for {str(job_ids)}")
154
+
155
+ ret = [job.to_dict_advanced() if job is not None else None for job in global_task_buffer.getFullJobStatus(job_ids)]
156
+
157
+ tmp_logger.debug("getFullJobStatus end")
158
+ return generate_response(True, data=ret)
159
+
160
+
161
+ @request_validation(_logger, secure=False, request_method="GET")
162
+ def generate_offline_execution_script(req: PandaRequest, job_id: int, days: int = None) -> Dict:
163
+ """
164
+ Get execution script for a job.
165
+
166
+ Gets the execution script for a job, including Rucio download of input, ALRB setup, downloading transformation script and running the script. Requires a secure connection.
167
+
168
+ API details:
169
+ HTTP Method: GET
170
+ Path: /v1/job/generate_offline_execution_script
171
+
172
+ Args:
173
+ req(PandaRequest): internally generated request object containing the env variables
174
+ job_id (int): PanDA job ID
175
+ timeout (int, optional): The timeout value. Defaults to 60.
176
+
177
+ Returns:
178
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field will contain `{"script": script}`. When unsuccessful, the message field contains the error message and data an error code.
179
+ """
180
+ tmp_logger = LogWrapper(_logger, f"generate_offline_execution_script job_id={job_id} days={days}")
181
+ tmp_logger.debug("Start")
182
+ script = global_task_buffer.getScriptOfflineRunning(job_id, days)
183
+
184
+ if script.startswith("ERROR"):
185
+ tmp_logger.debug(f"Failed to generate script: {script}")
186
+ return script
187
+
188
+ tmp_logger.debug("Done")
189
+ return script
190
+
191
+
192
+ @request_validation(_logger, secure=True, request_method="GET")
193
+ def get_metadata_for_analysis_jobs(req: PandaRequest, task_id: int) -> Dict:
194
+ """
195
+ Get metadata for analysis jobs
196
+
197
+ Gets the metadata from the metatable for analysis jobs in `finished` status. Requires a secure connection.
198
+
199
+ API details:
200
+ HTTP Method: GET
201
+ Path: /v1/job/get_metadata_for_analysis_jobs
202
+
203
+ Args:
204
+ req(PandaRequest): internally generated request object containing the env variables
205
+ task_id (int): JEDI task ID
206
+
207
+ Returns:
208
+ dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the medata. When unsuccessful, the message field contains the error message and data an error code.
209
+ """
210
+
211
+ tmp_logger = LogWrapper(_logger, f"get_metadata_for_analysis_jobs task_id={task_id}")
212
+ tmp_logger.debug("Start")
213
+
214
+ metadata = global_task_buffer.getUserJobMetadata(task_id)
215
+ if not metadata:
216
+ tmp_logger.debug("No metadata found")
217
+ return generate_response(True, message="No metadata found", data={})
218
+
219
+ tmp_logger.debug("Done")
220
+ return generate_response(True, data=metadata)
221
+
222
+
223
+ @request_validation(_logger, secure=True, request_method="POST")
224
+ def kill(req, job_ids: List[int], code: int = None, use_email_as_id: bool = False, kill_options: List[str] = []):
225
+ """
226
+ Kill the jobs
227
+
228
+ Kills the jobs with the given PanDA IDs. Requires a secure connection.
229
+
230
+ API details:
231
+ HTTP Method: POST
232
+ Path: /v1/job/kill
233
+
234
+ Args:
235
+ req(PandaRequest): internally generated request object containing the env variables
236
+ job_ids (list): List of PanDA job IDs
237
+ code (int, optional): The kill code. Defaults to None.
238
+ ```
239
+ code
240
+ 2: expire
241
+ 3: aborted
242
+ 4: expire in waiting
243
+ 7: retry by server
244
+ 8: rebrokerage
245
+ 9: force kill
246
+ 10: fast rebrokerage in overloaded PQ
247
+ 50: kill by JEDI
248
+ 51: reassigned by JEDI
249
+ 52: force kill by JEDI
250
+ 55: killed since task is (almost) done
251
+ 60: workload was terminated by the pilot without actual work
252
+ 91: kill user jobs with prod role
253
+ 99: force kill user jobs with prod role
254
+ ```
255
+ use_email_as_id (bool, optional): Use the email as ID. Defaults to False.
256
+ kill_options (List, optional): Defaults to []. Possible options are: `keepUnmerged`, `jobSubStatus=xyz`
257
+
258
+ Returns:
259
+ dict: The system response `{"success": success, "message": message, "data": data}`. The data field contains a list of bools indicating the success of the kill operations.
260
+ """
261
+
262
+ # retrieve the user information
263
+ user = get_dn(req)
264
+ is_production_manager = has_production_role(req)
265
+ fqans = get_fqan(req)
266
+
267
+ tmp_logger = LogWrapper(_logger, f"kill")
268
+ tmp_logger.debug(f"Start user: {user} code: {code} is_production_manager: {is_production_manager} fqans: {fqans} job_ids: {job_ids}")
269
+
270
+ # Get the user's email address if use_email_as_id is set
271
+ if use_email_as_id:
272
+ email = get_email_address(user)
273
+ if email:
274
+ user = email
275
+
276
+ # Extract working groups with production role from FQANs
277
+ wg_prod_roles = extract_production_working_groups(fqans)
278
+
279
+ # kill jobs
280
+ ret = global_task_buffer.killJobs(job_ids, user, code, is_production_manager, wg_prod_roles, kill_options)
281
+ tmp_logger.debug(f"Done with ret: {ret}")
282
+ return generate_response(True, data=ret)
283
+
284
+
285
+ @request_validation(_logger, secure=True, request_method="POST")
286
+ def reassign(req: PandaRequest, job_ids: List[int]):
287
+ """
288
+ Reassign a list of jobs
289
+
290
+ Reassigns a list of jobs. Requires a secure connection.
291
+
292
+ API details:
293
+ HTTP Method: POST
294
+ Path: /v1/job/reassign
295
+
296
+ Args:
297
+ req(PandaRequest): internally generated request object containing the env variables
298
+ job_ids (list): List of PanDA job IDs
299
+
300
+ Returns:
301
+ dict: The system response `{"success": True}`.
302
+ """
303
+ # TODO: think about the default values for for_pending and first_submission. The resolve_true and resolve_false functions are confusing me.
304
+
305
+ tmp_logger = LogWrapper(_logger, f"reassign job_ids={job_ids}")
306
+ tmp_logger.debug("Start")
307
+ # taskBuffer.reassignJobs always returns True
308
+ global_task_buffer.reassignJobs(job_ids)
309
+ tmp_logger.debug(f"Done")
310
+ return generate_response(True)
311
+
312
+
313
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
314
+ def set_command(req: PandaRequest, job_id: int, command: str):
315
+ """
316
+ Set a pilot command
317
+
318
+ Sets a command to the pilot for a job. Requires a secure connection and production role.
319
+
320
+ API details:
321
+ HTTP Method: POST
322
+ Path: /v1/job/set_command
323
+
324
+ Args:
325
+ req(PandaRequest): internally generated request object containing the env variables
326
+ job_id (int): PanDA job ID
327
+ command (str): The command for the pilot, e.g. `tobekilled`
328
+
329
+ Returns:
330
+ dict: The system response `{"success": success, "message": message}`.
331
+ """
332
+ tmp_logger = LogWrapper(_logger, f"set_command job_id={job_id} command={command}")
333
+ tmp_logger.debug("Start")
334
+ success, message = global_task_buffer.send_command_to_job(job_id, command)
335
+ tmp_logger.debug("Done with success={success} message={message}")
336
+ return generate_response(success, message=message)
337
+
338
+
339
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
340
+ def set_debug_mode(req: PandaRequest, job_id: int, mode: bool):
341
+ """
342
+ Set the debug mode
343
+
344
+ Sets the debug mode for a job. Requires a secure connection and production role.
345
+
346
+ API details:
347
+ HTTP Method: POST
348
+ Path: /v1/job/set_debug_mode
349
+
350
+ Args:
351
+ req(PandaRequest): internally generated request object containing the env variables
352
+ job_id (int): PanDA job ID
353
+ mode (bool): True to set debug mode, False to unset debug mode
354
+
355
+ Returns:
356
+ dict: The system response `{"success": success, "message": message}`.
357
+ """
358
+
359
+ tmp_logger = LogWrapper(_logger, f"set_debug_mode job_id={job_id}")
360
+
361
+ user = get_dn(req)
362
+ is_production_manager = has_production_role(req)
363
+ fqans = get_fqan(req)
364
+
365
+ # get the primary working group with prod role
366
+ working_group = extract_primary_production_working_group(fqans)
367
+
368
+ tmp_logger.debug(f"Start user={user} mgr={is_production_manager} wg={working_group} fqans={str(fqans)}")
369
+
370
+ message = global_task_buffer.setDebugMode(user, job_id, is_production_manager, mode, working_group)
371
+
372
+ success = False
373
+ if message != "Succeeded":
374
+ success = True
375
+
376
+ return generate_response(success, message=message)
377
+
378
+
379
+ @request_validation(_logger, secure=True, request_method="POST")
380
+ def submit(req: PandaRequest, jobs: str):
381
+ """
382
+ Submit jobs
383
+
384
+ Sets the debug mode for a job. Requires a secure connection.
385
+
386
+ API details:
387
+ HTTP Method: POST
388
+ Path: /v1/job/submit
389
+
390
+ Args:
391
+ req(PandaRequest): internally generated request object containing the env variables
392
+ jobs (str): JSON string with a list of job specs
393
+
394
+ Returns:
395
+ dict: The system response `{"success": success, "message": message}`.
396
+ """
397
+ tmp_logger = LogWrapper(_logger, f"submit")
398
+ user = get_dn(req)
399
+ fqans = get_fqan(req)
400
+ is_production_role = has_production_role(req)
401
+ host = req.get_remote_host()
402
+
403
+ # deserialize job specs
404
+ try:
405
+ jobs = JobUtils.load_jobs_json(jobs)
406
+ tmp_logger.debug(f"{user} len:{len(jobs)} is_production_role={is_production_role} FQAN:{fqans}")
407
+ max_jobs = 5000
408
+ if len(jobs) > max_jobs:
409
+ _logger.error(f"Number of jobs exceeded maximum {max_jobs}. Truncating the list.")
410
+ jobs = jobs[:max_jobs]
411
+ except Exception as ex:
412
+ error_message = f"Failed to deserialize jobs: {str(ex)} {traceback.format_exc()}"
413
+ tmp_logger.error(error_message)
414
+ return generate_response(False, message=error_message)
415
+
416
+ if not jobs:
417
+ return generate_response(False, message="No jobs were submitted")
418
+
419
+ # check prodSourceLabel and job_label are correct
420
+ for tmp_job in jobs:
421
+ # check production jobs are submitted with production role
422
+ if tmp_job.prodSourceLabel in ["managed"] and not is_production_role:
423
+ return generate_response(False, message=f"{user} is missing production for prodSourceLabel={tmp_job.prodSourceLabel} submission")
424
+
425
+ # validate the job_label
426
+ if tmp_job.job_label not in [None, "", "NULL"] and tmp_job.job_label not in JobUtils.job_labels:
427
+ return generate_response(False, message=f"job_label={tmp_job.job_label} is not valid")
428
+
429
+ sample_job = jobs[0]
430
+
431
+ # get user VO
432
+ try:
433
+ user_vo = "atlas"
434
+ if sample_job.VO not in [None, "", "NULL"]:
435
+ user_vo = sample_job.VO
436
+ except (IndexError, AttributeError) as e:
437
+ tmp_logger.error(f"User VO was not found and defaulted to {user_vo}. (Exception {e})")
438
+
439
+ # atlas jobs require FQANs
440
+ if user_vo == "atlas" and not fqans:
441
+ tmp_logger.error(f"Proxy was missing FQANs for {user_vo}")
442
+
443
+ # LSST: try to overwrite with pipeline username
444
+ if user_vo.lower() == "lsst":
445
+ try:
446
+ if sample_job.prodUserName and sample_job.prodUserName.lower() != "none":
447
+ user = sample_job.prodUserName
448
+ except AttributeError:
449
+ tmp_logger.error(f"VO {user_vo} check: username not found in job parameters and defaulted to submitter ({user})")
450
+
451
+ # store jobs
452
+ ret = global_task_buffer.storeJobs(jobs, user, fqans=fqans, hostname=host, userVO=user_vo)
453
+ tmp_logger.debug(f"{user} -> {len(ret)}")
454
+
455
+ # There was no response
456
+ if not ret:
457
+ return generate_response(False, message="Failed to submit jobs")
458
+
459
+ # There was a string for response, i.e. an error message
460
+ if isinstance(ret, str):
461
+ return generate_response(False, message=ret)
462
+
463
+ return generate_response(True, data=ret)