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,1799 @@
1
+ """
2
+ provide web interface to users
3
+
4
+ """
5
+
6
+ import datetime
7
+ import json
8
+ import re
9
+ import sys
10
+ import time
11
+ import traceback
12
+
13
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
14
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
15
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
16
+
17
+ import pandaserver.jobdispatcher.Protocol as Protocol
18
+ from pandaserver.brokerage.SiteMapper import SiteMapper
19
+ from pandaserver.config import panda_config
20
+ from pandaserver.dataservice.ddm import rucioAPI
21
+ from pandaserver.srvcore import CoreUtils
22
+ from pandaserver.srvcore.CoreUtils import clean_user_id, resolve_bool
23
+ from pandaserver.taskbuffer import JobUtils, PrioUtil
24
+ from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec
25
+ from pandaserver.taskbuffer.WrappedPickle import WrappedPickle
26
+
27
+ try:
28
+ import idds.common.constants
29
+ import idds.common.utils
30
+ from idds.client.client import Client as iDDS_Client
31
+ from idds.client.clientmanager import ClientManager as iDDS_ClientManager
32
+ except ImportError:
33
+ pass
34
+
35
+ MESSAGE_SSL = "SSL secure connection is required"
36
+ MESSAGE_PROD_ROLE = "production or pilot role required"
37
+ MESSAGE_TASK_ID = "jediTaskID must be an integer"
38
+ MESSAGE_DATABASE = "database error in the PanDA server"
39
+ MESSAGE_JSON = "failed to load JSON"
40
+
41
+ CODE_SSL = 100
42
+ CODE_LOGIC = 101
43
+ CODE_OTHER_PARAMS = 102
44
+
45
+
46
+ _logger = PandaLogger().getLogger("UserIF")
47
+
48
+
49
+ def resolve_true(variable):
50
+ return variable == "True"
51
+
52
+
53
+ def resolve_false(variable):
54
+ return variable != "False"
55
+
56
+
57
+ # main class
58
+ class UserIF:
59
+ # constructor
60
+ def __init__(self):
61
+ self.taskBuffer = None
62
+
63
+ # initialize
64
+ def init(self, taskBuffer):
65
+ self.taskBuffer = taskBuffer
66
+
67
+ # submit jobs
68
+ def submitJobs(self, jobsStr, user, host, userFQANs, prodRole=False, toPending=False):
69
+ try:
70
+ # deserialize jobspecs
71
+ try:
72
+ jobs = WrappedPickle.loads(jobsStr)
73
+ except Exception:
74
+ jobs = JobUtils.load_jobs_json(jobsStr)
75
+ _logger.debug(f"submitJobs {user} len:{len(jobs)} prodRole={prodRole} FQAN:{str(userFQANs)}")
76
+ maxJobs = 5000
77
+ if len(jobs) > maxJobs:
78
+ _logger.error(f"submitJobs: too many jobs more than {maxJobs}")
79
+ jobs = jobs[:maxJobs]
80
+ except Exception as ex:
81
+ _logger.error(f"submitJobs : {str(ex)} {traceback.format_exc()}")
82
+ jobs = []
83
+ # check prodSourceLabel
84
+ try:
85
+ good_labels = True
86
+ for tmpJob in jobs:
87
+ # check production role
88
+ if tmpJob.prodSourceLabel in ["managed"] and not prodRole:
89
+ good_labels = False
90
+ good_labels_message = f"submitJobs {user} missing prod-role for prodSourceLabel={tmpJob.prodSourceLabel}"
91
+ break
92
+
93
+ # check the job_label is valid
94
+ if tmpJob.job_label not in [None, "", "NULL"] and tmpJob.job_label not in JobUtils.job_labels:
95
+ good_labels = False
96
+ good_labels_message = f"submitJobs {user} wrong job_label={tmpJob.job_label}"
97
+ break
98
+ except Exception:
99
+ err_type, err_value = sys.exc_info()[:2]
100
+ _logger.error(f"submitJobs : checking good_labels {err_type} {err_value}")
101
+ good_labels = False
102
+
103
+ # reject injection for error with the labels
104
+ if not good_labels:
105
+ _logger.error(good_labels_message)
106
+ return f"ERROR: {good_labels_message}"
107
+
108
+ job0 = None
109
+
110
+ # get user VO
111
+ userVO = "atlas"
112
+ try:
113
+ job0 = jobs[0]
114
+ if job0.VO not in [None, "", "NULL"]:
115
+ userVO = job0.VO
116
+ except (IndexError, AttributeError) as e:
117
+ _logger.error(f"submitJobs : checking userVO. userVO not found, defaulting to {userVO}. (Exception {e})")
118
+
119
+ # atlas jobs require FQANs
120
+ if userVO == "atlas" and userFQANs == []:
121
+ _logger.error(f"submitJobs : VOMS FQANs are missing in your proxy. They are required for {userVO}")
122
+ # return "ERROR: VOMS FQANs are missing. They are required for {0}".format(userVO)
123
+
124
+ # get LSST pipeline username
125
+ if userVO.lower() == "lsst":
126
+ try:
127
+ if job0.prodUserName and job0.prodUserName.lower() != "none":
128
+ user = job0.prodUserName
129
+ except AttributeError:
130
+ _logger.error(f"submitJobs : checking username for userVO {userVO}: username not found, defaulting to {user}.")
131
+
132
+ # store jobs
133
+ ret = self.taskBuffer.storeJobs(
134
+ jobs,
135
+ user,
136
+ fqans=userFQANs,
137
+ hostname=host,
138
+ toPending=toPending,
139
+ userVO=userVO,
140
+ )
141
+ _logger.debug(f"submitJobs {user} ->:{len(ret)}")
142
+
143
+ # serialize
144
+ return WrappedPickle.dumps(ret)
145
+
146
+ # set debug mode
147
+ def setDebugMode(self, dn, pandaID, prodManager, modeOn, workingGroup):
148
+ ret = self.taskBuffer.setDebugMode(dn, pandaID, prodManager, modeOn, workingGroup)
149
+ # return
150
+ return ret
151
+
152
+ # insert sandbox file info
153
+ def insertSandboxFileInfo(self, userName, hostName, fileName, fileSize, checkSum):
154
+ ret = self.taskBuffer.insertSandboxFileInfo(userName, hostName, fileName, fileSize, checkSum)
155
+ # return
156
+ return ret
157
+
158
+ # check duplicated sandbox file
159
+ def checkSandboxFile(self, userName, fileSize, checkSum):
160
+ ret = self.taskBuffer.checkSandboxFile(userName, fileSize, checkSum)
161
+ # return
162
+ return ret
163
+
164
+ # get job status
165
+ def getJobStatus(self, idsStr, use_json, no_pickle=False):
166
+ try:
167
+ # deserialize IDs
168
+ if use_json or no_pickle:
169
+ ids = json.loads(idsStr)
170
+ else:
171
+ ids = WrappedPickle.loads(idsStr)
172
+ _logger.debug(f"getJobStatus len : {len(ids)}")
173
+ maxIDs = 5500
174
+ if len(ids) > maxIDs:
175
+ _logger.error(f"too long ID list more than {maxIDs}")
176
+ ids = ids[:maxIDs]
177
+ except Exception:
178
+ type, value, traceBack = sys.exc_info()
179
+ _logger.error(f"getJobStatus : {type} {value}")
180
+ ids = []
181
+ _logger.debug(f"getJobStatus start : {str(ids)} json={use_json}")
182
+ # peek jobs
183
+ ret = self.taskBuffer.peekJobs(ids, use_json=use_json)
184
+ _logger.debug("getJobStatus end")
185
+ # serialize
186
+ if use_json:
187
+ return json.dumps(ret)
188
+ if no_pickle:
189
+ return JobUtils.dump_jobs_json(ret)
190
+ return WrappedPickle.dumps(ret)
191
+
192
+ # get PandaIDs with TaskID
193
+ def getPandaIDsWithTaskID(self, jediTaskID):
194
+ # get PandaIDs
195
+ ret = self.taskBuffer.getPandaIDsWithTaskID(jediTaskID)
196
+ # serialize
197
+ return WrappedPickle.dumps(ret)
198
+
199
+ # get job statistics per site
200
+ def getJobStatisticsPerSite(self):
201
+ ret = self.taskBuffer.getJobStatistics()
202
+ return WrappedPickle.dumps(ret, convert_to_safe=True)
203
+
204
+ # get job statistics per site, source label, and resource type
205
+ def get_job_statistics_per_site_label_resource(self, time_window):
206
+ ret = self.taskBuffer.get_job_statistics_per_site_label_resource(time_window)
207
+ return json.dumps(ret)
208
+
209
+ # kill jobs
210
+ def killJobs(self, idsStr, user, host, code, prodManager, useMailAsID, fqans, killOpts=[]):
211
+ # deserialize IDs
212
+ ids = WrappedPickle.loads(idsStr)
213
+ if not isinstance(ids, list):
214
+ ids = [ids]
215
+ _logger.info(f"killJob : {user} {code} {prodManager} {fqans} {ids}")
216
+ try:
217
+ if useMailAsID:
218
+ _logger.debug(f"killJob : getting mail address for {user}")
219
+ nTry = 3
220
+ for iDDMTry in range(nTry):
221
+ status, userInfo = rucioAPI.finger(user)
222
+ if status:
223
+ _logger.debug(f"killJob : {user} is converted to {userInfo['email']}")
224
+ user = userInfo["email"]
225
+ break
226
+ time.sleep(1)
227
+ except Exception:
228
+ err_type, err_value = sys.exc_info()[:2]
229
+ _logger.error(f"killJob : failed to convert email address {user} : {err_type} {err_value}")
230
+ # get working groups with prod role
231
+ wgProdRole = []
232
+ for fqan in fqans:
233
+ tmpMatch = re.search("/atlas/([^/]+)/Role=production", fqan)
234
+ if tmpMatch is not None:
235
+ # ignore usatlas since it is used as atlas prod role
236
+ tmpWG = tmpMatch.group(1)
237
+ if tmpWG not in ["", "usatlas"] + wgProdRole:
238
+ wgProdRole.append(tmpWG)
239
+ # group production
240
+ wgProdRole.append(f"gr_{tmpWG}")
241
+ # kill jobs
242
+ ret = self.taskBuffer.killJobs(ids, user, code, prodManager, wgProdRole, killOpts)
243
+ return WrappedPickle.dumps(ret)
244
+
245
+ # reassign jobs
246
+ def reassignJobs(self, idsStr, user, host, forPending, firstSubmission):
247
+ # deserialize IDs
248
+ ids = WrappedPickle.loads(idsStr)
249
+ # reassign jobs
250
+ ret = self.taskBuffer.reassignJobs(
251
+ ids,
252
+ forPending=forPending,
253
+ firstSubmission=firstSubmission,
254
+ )
255
+ return WrappedPickle.dumps(ret)
256
+
257
+ # get script for offline running
258
+ def getScriptOfflineRunning(self, pandaID, days=None):
259
+ ret = self.taskBuffer.getScriptOfflineRunning(pandaID, days)
260
+ return ret
261
+
262
+ # get ban users
263
+ def get_ban_users(self):
264
+ ret = self.taskBuffer.get_ban_users()
265
+ return json.dumps(ret)
266
+
267
+ # get active JediTasks in a time range
268
+ def getJediTasksInTimeRange(self, dn, timeRange, fullFlag, minTaskID, task_type):
269
+ ret = self.taskBuffer.getJediTasksInTimeRange(dn, timeRange, fullFlag, minTaskID, task_type)
270
+ return WrappedPickle.dumps(ret)
271
+
272
+ # get details of JediTask
273
+ def getJediTaskDetails(self, jediTaskID, fullFlag, withTaskInfo):
274
+ ret = self.taskBuffer.getJediTaskDetails(jediTaskID, fullFlag, withTaskInfo)
275
+ return WrappedPickle.dumps(ret)
276
+
277
+ # get full job status
278
+ def getFullJobStatus(self, idsStr, dn):
279
+ try:
280
+ # deserialize jobspecs
281
+ ids = WrappedPickle.loads(idsStr)
282
+ # truncate
283
+ maxIDs = 5500
284
+ if len(ids) > maxIDs:
285
+ _logger.error(f"getFullJobStatus: too long ID list more than {maxIDs}")
286
+ ids = ids[:maxIDs]
287
+ except Exception:
288
+ type, value, traceBack = sys.exc_info()
289
+ _logger.error(f"getFullJobStatus : {type} {value}")
290
+ ids = []
291
+ _logger.debug(f"getFullJobStatus start : {dn} {str(ids)}")
292
+ # peek jobs
293
+ ret = self.taskBuffer.getFullJobStatus(ids)
294
+ _logger.debug("getFullJobStatus end")
295
+ return WrappedPickle.dumps(ret)
296
+
297
+ # insert task params
298
+ def insertTaskParams(self, taskParams, user, prodRole, fqans, properErrorCode, parent_tid):
299
+ ret = self.taskBuffer.insertTaskParamsPanda(
300
+ taskParams,
301
+ user,
302
+ prodRole,
303
+ fqans,
304
+ properErrorCode=properErrorCode,
305
+ parent_tid=parent_tid,
306
+ )
307
+ return ret
308
+
309
+ # kill task
310
+ def killTask(self, jediTaskID, user, prodRole, properErrorCode, broadcast):
311
+ ret = self.taskBuffer.sendCommandTaskPanda(
312
+ jediTaskID,
313
+ user,
314
+ prodRole,
315
+ "kill",
316
+ properErrorCode=properErrorCode,
317
+ broadcast=broadcast,
318
+ )
319
+ return ret
320
+
321
+ # finish task
322
+ def finishTask(self, jediTaskID, user, prodRole, properErrorCode, qualifier, broadcast):
323
+ ret = self.taskBuffer.sendCommandTaskPanda(
324
+ jediTaskID,
325
+ user,
326
+ prodRole,
327
+ "finish",
328
+ properErrorCode=properErrorCode,
329
+ comQualifier=qualifier,
330
+ broadcast=broadcast,
331
+ )
332
+ return ret
333
+
334
+ # reload input
335
+ def reloadInput(self, jediTaskID, user, prodRole, ignore_hard_exhausted):
336
+ if not prodRole and ignore_hard_exhausted:
337
+ ignore_hard_exhausted = False
338
+ com_qualifier = JediTaskSpec.get_retry_command_qualifiers(ignore_hard_exhausted=ignore_hard_exhausted)
339
+ com_comment = json.dumps([{}, com_qualifier])
340
+ ret = self.taskBuffer.sendCommandTaskPanda(jediTaskID, user, prodRole, "incexec", comComment=com_comment, properErrorCode=True)
341
+ return ret
342
+
343
+ # retry task
344
+ def retryTask(
345
+ self,
346
+ jediTaskID,
347
+ user,
348
+ prodRole,
349
+ properErrorCode,
350
+ newParams,
351
+ noChildRetry,
352
+ discardEvents,
353
+ disable_staging_mode,
354
+ keep_gshare_priority,
355
+ ignore_hard_exhausted,
356
+ ):
357
+ # retry with new params
358
+ if newParams is not None:
359
+ try:
360
+ # convert to dict
361
+ newParams = PrioUtil.decodeJSON(newParams)
362
+ # get original params
363
+ taskParams = self.taskBuffer.getTaskParamsPanda(jediTaskID)
364
+ taskParamsJson = PrioUtil.decodeJSON(taskParams)
365
+ # replace with new values
366
+ for newKey in newParams:
367
+ newVal = newParams[newKey]
368
+ taskParamsJson[newKey] = newVal
369
+ taskParams = json.dumps(taskParamsJson)
370
+ # retry with new params
371
+ ret = self.taskBuffer.insertTaskParamsPanda(
372
+ taskParams,
373
+ user,
374
+ prodRole,
375
+ [],
376
+ properErrorCode=properErrorCode,
377
+ allowActiveTask=True,
378
+ )
379
+ except Exception:
380
+ err_type, err_value = sys.exc_info()[:2]
381
+ ret = 1, f"server error with {err_type}:{err_value}"
382
+ else:
383
+ if not prodRole and ignore_hard_exhausted:
384
+ ignore_hard_exhausted = False
385
+ com_qualifier = JediTaskSpec.get_retry_command_qualifiers(
386
+ noChildRetry, discardEvents, disable_staging_mode, keep_gshare_priority, ignore_hard_exhausted
387
+ )
388
+ com_qualifier = " ".join(com_qualifier)
389
+ # normal retry
390
+ ret = self.taskBuffer.sendCommandTaskPanda(
391
+ jediTaskID,
392
+ user,
393
+ prodRole,
394
+ "retry",
395
+ properErrorCode=properErrorCode,
396
+ comQualifier=com_qualifier,
397
+ )
398
+ if properErrorCode is True and ret[0] == 5:
399
+ # retry failed analysis jobs
400
+ jobdefList = self.taskBuffer.getJobdefIDsForFailedJob(jediTaskID)
401
+ cUID = CoreUtils.clean_user_id(user)
402
+ for jobID in jobdefList:
403
+ self.taskBuffer.finalizePendingJobs(cUID, jobID)
404
+ self.taskBuffer.increaseAttemptNrPanda(jediTaskID, 5)
405
+ return_str = f"retry has been triggered for failed jobs while the task is still {ret[1]}"
406
+ if newParams is None:
407
+ ret = 0, return_str
408
+ else:
409
+ ret = 3, return_str
410
+ return ret
411
+
412
+ # reassign task
413
+ def reassignTask(self, jediTaskID, user, prodRole, comComment):
414
+ ret = self.taskBuffer.sendCommandTaskPanda(
415
+ jediTaskID,
416
+ user,
417
+ prodRole,
418
+ "reassign",
419
+ comComment=comComment,
420
+ properErrorCode=True,
421
+ )
422
+ return ret
423
+
424
+ # pause task
425
+ def pauseTask(self, jediTaskID, user, prodRole):
426
+ ret = self.taskBuffer.sendCommandTaskPanda(jediTaskID, user, prodRole, "pause", properErrorCode=True)
427
+ return ret
428
+
429
+ # resume task
430
+ def resumeTask(self, jediTaskID, user, prodRole):
431
+ ret = self.taskBuffer.sendCommandTaskPanda(jediTaskID, user, prodRole, "resume", properErrorCode=True)
432
+ return ret
433
+
434
+ # force avalanche for task
435
+ def avalancheTask(self, jediTaskID, user, prodRole):
436
+ ret = self.taskBuffer.sendCommandTaskPanda(jediTaskID, user, prodRole, "avalanche", properErrorCode=True)
437
+ return ret
438
+
439
+ # send command to task
440
+ def send_command_to_task(self, jedi_task_id, user, prod_role, command_string):
441
+ ret = self.taskBuffer.sendCommandTaskPanda(jedi_task_id, user, prod_role, command_string, properErrorCode=True)
442
+ return ret
443
+
444
+ # change task priority
445
+ def changeTaskPriority(self, jediTaskID, newPriority):
446
+ ret = self.taskBuffer.changeTaskPriorityPanda(jediTaskID, newPriority)
447
+ return ret
448
+
449
+ # increase attempt number for unprocessed files
450
+ def increaseAttemptNrPanda(self, jediTaskID, increasedNr):
451
+ ret = self.taskBuffer.increaseAttemptNrPanda(jediTaskID, increasedNr)
452
+ return ret
453
+
454
+ # change task attribute
455
+ def changeTaskAttributePanda(self, jediTaskID, attrName, attrValue):
456
+ ret = self.taskBuffer.changeTaskAttributePanda(jediTaskID, attrName, attrValue)
457
+ return ret
458
+
459
+ # change split rule for task
460
+ def changeTaskSplitRulePanda(self, jediTaskID, attrName, attrValue):
461
+ ret = self.taskBuffer.changeTaskSplitRulePanda(jediTaskID, attrName, attrValue)
462
+ return ret
463
+
464
+ # reactivate task
465
+ def reactivateTask(self, jediTaskID, keep_attempt_nr, trigger_job_generation):
466
+ ret = self.taskBuffer.reactivateTask(jediTaskID, keep_attempt_nr, trigger_job_generation)
467
+ return ret
468
+
469
+ # get task status
470
+ def getTaskStatus(self, jediTaskID):
471
+ ret = self.taskBuffer.getTaskStatus(jediTaskID)
472
+ return ret[0]
473
+
474
+ # reassign share
475
+ def reassignShare(self, jedi_task_ids, share_dest, reassign_running):
476
+ return self.taskBuffer.reassignShare(jedi_task_ids, share_dest, reassign_running)
477
+
478
+ # get taskParamsMap
479
+ def getTaskParamsMap(self, jediTaskID):
480
+ # get taskParamsMap
481
+ ret = self.taskBuffer.getTaskParamsMap(jediTaskID)
482
+ return ret
483
+
484
+ # update workers
485
+ def updateWorkers(self, user, host, harvesterID, data):
486
+ ret = self.taskBuffer.updateWorkers(harvesterID, data)
487
+ if ret is None:
488
+ return_value = (False, MESSAGE_DATABASE)
489
+ else:
490
+ return_value = (True, ret)
491
+ return json.dumps(return_value)
492
+
493
+ # update workers
494
+ def updateServiceMetrics(self, user, host, harvesterID, data):
495
+ ret = self.taskBuffer.updateServiceMetrics(harvesterID, data)
496
+ if ret is None:
497
+ return_value = (False, MESSAGE_DATABASE)
498
+ else:
499
+ return_value = (True, ret)
500
+ return json.dumps(return_value)
501
+
502
+ # add harvester dialog messages
503
+ def addHarvesterDialogs(self, user, harvesterID, dialogs):
504
+ ret = self.taskBuffer.addHarvesterDialogs(harvesterID, dialogs)
505
+ if not ret:
506
+ return_value = (False, MESSAGE_DATABASE)
507
+ else:
508
+ return_value = (True, "")
509
+ return json.dumps(return_value)
510
+
511
+ # heartbeat for harvester
512
+ def harvesterIsAlive(self, user, host, harvesterID, data):
513
+ ret = self.taskBuffer.harvesterIsAlive(user, host, harvesterID, data)
514
+ if ret is None:
515
+ return_value = (False, MESSAGE_DATABASE)
516
+ else:
517
+ return_value = (True, ret)
518
+ return json.dumps(return_value)
519
+
520
+ # get stats of workers
521
+ def getWorkerStats(self):
522
+ return self.taskBuffer.getWorkerStats()
523
+
524
+ # report stat of workers
525
+ def reportWorkerStats_jobtype(self, harvesterID, siteName, paramsList):
526
+ return self.taskBuffer.reportWorkerStats_jobtype(harvesterID, siteName, paramsList)
527
+
528
+ # set num slots for workload provisioning
529
+ def setNumSlotsForWP(self, pandaQueueName, numSlots, gshare, resourceType, validPeriod):
530
+ return_value = self.taskBuffer.setNumSlotsForWP(pandaQueueName, numSlots, gshare, resourceType, validPeriod)
531
+ return json.dumps(return_value)
532
+
533
+ # enable jumbo jobs
534
+ def enableJumboJobs(self, jediTaskID, totalJumboJobs, nJumboPerSite):
535
+ return_value = self.taskBuffer.enableJumboJobs(jediTaskID, totalJumboJobs, nJumboPerSite)
536
+ if totalJumboJobs > 0 and return_value[0] == 0:
537
+ self.avalancheTask(jediTaskID, "panda", True)
538
+ return json.dumps(return_value)
539
+
540
+ # get user job metadata
541
+ def getUserJobMetadata(self, jediTaskID):
542
+ return_value = self.taskBuffer.getUserJobMetadata(jediTaskID)
543
+ return json.dumps(return_value)
544
+
545
+ # get jumbo job datasets
546
+ def getJumboJobDatasets(self, n_days, grace_period):
547
+ return_value = self.taskBuffer.getJumboJobDatasets(n_days, grace_period)
548
+ # serialize
549
+ return json.dumps(return_value)
550
+
551
+ # sweep panda queue
552
+ def sweepPQ(self, panda_queue, status_list, ce_list, submission_host_list):
553
+ try:
554
+ panda_queue_des = json.loads(panda_queue)
555
+ status_list_des = json.loads(status_list)
556
+ ce_list_des = json.loads(ce_list)
557
+ submission_host_list_des = json.loads(submission_host_list)
558
+ except Exception:
559
+ _logger.error("Problem deserializing variables")
560
+
561
+ ret = self.taskBuffer.sweepPQ(panda_queue_des, status_list_des, ce_list_des, submission_host_list_des)
562
+ return WrappedPickle.dumps(ret)
563
+
564
+ # send command to a job
565
+ def send_command_to_job(self, panda_id, com):
566
+ ret = self.taskBuffer.send_command_to_job(panda_id, com)
567
+ return ret
568
+
569
+ # set user secret
570
+ def set_user_secret(self, owner, key, value):
571
+ ret = self.taskBuffer.set_user_secret(owner, key, value)
572
+ return ret
573
+
574
+ # get user secrets
575
+ def get_user_secrets(self, owner, keys, get_json):
576
+ ret = self.taskBuffer.get_user_secrets(owner, keys, get_json)
577
+ return ret
578
+
579
+ # get files in datasets
580
+ def get_files_in_datasets(self, task_id, dataset_types):
581
+ ret = self.taskBuffer.get_files_in_datasets(task_id, dataset_types)
582
+ return ret
583
+
584
+
585
+ # Singleton
586
+ userIF = UserIF()
587
+ del UserIF
588
+
589
+
590
+ # get FQANs
591
+ def _getFQAN(req):
592
+ fqans = []
593
+ for tmp_key in req.subprocess_env:
594
+ tmp_value = req.subprocess_env[tmp_key]
595
+ # Scan VOMS attributes
596
+ # compact style
597
+ if tmp_key.startswith("GRST_CRED_") and tmp_value.startswith("VOMS"):
598
+ fqan = tmp_value.split()[-1]
599
+ fqans.append(fqan)
600
+
601
+ # old style
602
+ elif tmp_key.startswith("GRST_CONN_"):
603
+ tmp_items = tmp_value.split(":")
604
+ if len(tmp_items) == 2 and tmp_items[0] == "fqan":
605
+ fqans.append(tmp_items[-1])
606
+
607
+ return fqans
608
+
609
+
610
+ # get DN
611
+ def _getDN(req):
612
+ real_dn = ""
613
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
614
+ # remove redundant CN
615
+ real_dn = CoreUtils.get_bare_dn(req.subprocess_env["SSL_CLIENT_S_DN"], keep_proxy=True)
616
+ return real_dn
617
+
618
+
619
+ # check role
620
+ def _has_production_role(req):
621
+ # check DN
622
+ user = _getDN(req)
623
+ for sdn in panda_config.production_dns:
624
+ if sdn in user:
625
+ return True
626
+ # get FQANs
627
+ fqans = _getFQAN(req)
628
+ # loop over all FQANs
629
+ for fqan in fqans:
630
+ # check production role
631
+ for rolePat in [
632
+ "/atlas/usatlas/Role=production",
633
+ "/atlas/Role=production",
634
+ "^/[^/]+/Role=production",
635
+ ]:
636
+ if fqan.startswith(rolePat):
637
+ return True
638
+ if re.search(rolePat, fqan):
639
+ return True
640
+ return False
641
+
642
+
643
+ # get primary working group with prod role
644
+ def _getWGwithPR(req):
645
+ try:
646
+ fqans = _getFQAN(req)
647
+ for fqan in fqans:
648
+ tmpMatch = re.search("/[^/]+/([^/]+)/Role=production", fqan)
649
+ if tmpMatch is not None:
650
+ # ignore usatlas since it is used as atlas prod role
651
+ tmpWG = tmpMatch.group(1)
652
+ if tmpWG not in ["", "usatlas"]:
653
+ return tmpWG.split("-")[-1].lower()
654
+ except Exception:
655
+ pass
656
+ return None
657
+
658
+
659
+ # security check
660
+ def isSecure(req):
661
+ # check security
662
+ if not Protocol.isSecure(req):
663
+ return False
664
+ # disable limited proxy
665
+ if "/CN=limited proxy" in req.subprocess_env["SSL_CLIENT_S_DN"]:
666
+ _logger.warning(f"access via limited proxy : {req.subprocess_env['SSL_CLIENT_S_DN']}")
667
+ return False
668
+ return True
669
+
670
+
671
+ """
672
+ web service interface
673
+
674
+ """
675
+
676
+
677
+ # submit jobs
678
+ def submitJobs(req, jobs, toPending=None):
679
+ # check security
680
+ if not isSecure(req):
681
+ return False
682
+ # get DN
683
+ user = None
684
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
685
+ user = _getDN(req)
686
+ # get FQAN
687
+ fqans = _getFQAN(req)
688
+ # hostname
689
+ host = req.get_remote_host()
690
+ # production Role
691
+ is_production_role = _has_production_role(req)
692
+ # to pending
693
+ toPending = resolve_true(toPending)
694
+
695
+ return userIF.submitJobs(jobs, user, host, fqans, is_production_role, toPending)
696
+
697
+
698
+ # get job status
699
+ def getJobStatus(req, ids, no_pickle=None):
700
+ return userIF.getJobStatus(ids, req.acceptJson(), no_pickle)
701
+
702
+
703
+ # set debug mode
704
+ def setDebugMode(req, pandaID, modeOn):
705
+ tmp_log = LogWrapper(_logger, f"setDebugMode {pandaID} {modeOn}")
706
+ # get DN
707
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
708
+ error_str = MESSAGE_SSL
709
+ tmp_log.error(error_str)
710
+ return f"ERROR: {error_str}"
711
+ user = _getDN(req)
712
+ # check role
713
+ is_production_manager = _has_production_role(req)
714
+ fqans = _getFQAN(req)
715
+ # mode
716
+ modeOn = resolve_true(modeOn)
717
+
718
+ # get the primary working group with prod role
719
+ working_group = _getWGwithPR(req)
720
+ tmp_log.debug(f"user={user} mgr={is_production_manager} wg={working_group} fqans={str(fqans)}")
721
+ # exec
722
+ return userIF.setDebugMode(user, pandaID, is_production_manager, modeOn, working_group)
723
+
724
+
725
+ # insert sandbox file info
726
+ def insertSandboxFileInfo(req, userName, fileName, fileSize, checkSum):
727
+ tmp_log = LogWrapper(_logger, f"insertSandboxFileInfo {userName} {fileName}")
728
+ # get DN
729
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
730
+ error_str = MESSAGE_SSL
731
+ tmp_log.error(error_str)
732
+ return f"ERROR: {error_str}"
733
+
734
+ # check role
735
+ is_production_manager = _has_production_role(req)
736
+ if not is_production_manager:
737
+ tmp_log.error(MESSAGE_PROD_ROLE)
738
+ return f"ERROR: {MESSAGE_PROD_ROLE}"
739
+
740
+ # hostname
741
+ if hasattr(panda_config, "sandboxHostname") and panda_config.sandboxHostname:
742
+ hostName = panda_config.sandboxHostname
743
+ else:
744
+ hostName = req.get_remote_host()
745
+ # exec
746
+ return userIF.insertSandboxFileInfo(userName, hostName, fileName, fileSize, checkSum)
747
+
748
+
749
+ # check duplicated sandbox file
750
+ def checkSandboxFile(req, fileSize, checkSum):
751
+ # get DN
752
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
753
+ return f"ERROR: {MESSAGE_SSL}"
754
+ user = _getDN(req)
755
+ # exec
756
+ return userIF.checkSandboxFile(user, fileSize, checkSum)
757
+
758
+
759
+ # get job statistics per site and resource
760
+ def get_job_statistics_per_site_label_resource(req, time_window=None):
761
+ return userIF.get_job_statistics_per_site_label_resource(time_window)
762
+
763
+
764
+ # get job statistics per site
765
+ def getJobStatisticsPerSite(req):
766
+ return userIF.getJobStatisticsPerSite()
767
+
768
+
769
+ # kill jobs
770
+ def killJobs(req, ids, code=None, useMailAsID=None, killOpts=None):
771
+ # check security
772
+ if not isSecure(req):
773
+ return False
774
+ # get DN
775
+ user = None
776
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
777
+ user = _getDN(req)
778
+ # check role
779
+ is_production_manager = _has_production_role(req)
780
+ # get FQANs
781
+ fqans = _getFQAN(req)
782
+ # use email address as ID
783
+ useMailAsID = resolve_true(useMailAsID)
784
+
785
+ # hostname
786
+ host = req.get_remote_host()
787
+ # options
788
+ if killOpts is None:
789
+ killOpts = []
790
+ else:
791
+ killOpts = killOpts.split(",")
792
+ return userIF.killJobs(ids, user, host, code, is_production_manager, useMailAsID, fqans, killOpts)
793
+
794
+
795
+ # reassign jobs
796
+ def reassignJobs(req, ids, forPending=None, firstSubmission=None):
797
+ # check security
798
+ if not isSecure(req):
799
+ return False
800
+ # get DN
801
+ user = None
802
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
803
+ user = _getDN(req)
804
+ # hostname
805
+ host = req.get_remote_host()
806
+ # for pending
807
+ forPending = resolve_true(forPending)
808
+
809
+ # first submission
810
+ firstSubmission = resolve_false(firstSubmission)
811
+
812
+ return userIF.reassignJobs(ids, user, host, forPending, firstSubmission)
813
+
814
+
815
+ # get ban users
816
+ def get_ban_users(req):
817
+ return userIF.get_ban_users()
818
+
819
+
820
+ # get script for offline running
821
+ def getScriptOfflineRunning(req, pandaID, days=None):
822
+ try:
823
+ if days is not None:
824
+ days = int(days)
825
+ except Exception:
826
+ days = None
827
+ return userIF.getScriptOfflineRunning(pandaID, days)
828
+
829
+
830
+ # get active JediTasks in a time range
831
+ def getJediTasksInTimeRange(req, timeRange, dn=None, fullFlag=None, minTaskID=None, task_type="user"):
832
+ # check security
833
+ if not isSecure(req):
834
+ return False
835
+ # get DN
836
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
837
+ return False
838
+ if dn is None:
839
+ dn = _getDN(req)
840
+ fullFlag = resolve_true(fullFlag)
841
+
842
+ try:
843
+ minTaskID = int(minTaskID)
844
+ except Exception:
845
+ minTaskID = None
846
+ _logger.debug(f"getJediTasksInTimeRange {dn} {timeRange}")
847
+ # execute
848
+ return userIF.getJediTasksInTimeRange(dn, timeRange, fullFlag, minTaskID, task_type)
849
+
850
+
851
+ # get details of JediTask
852
+ def getJediTaskDetails(req, jediTaskID, fullFlag, withTaskInfo):
853
+ # check security
854
+ if not isSecure(req):
855
+ return False
856
+ # get DN
857
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
858
+ return False
859
+ # option
860
+ fullFlag = resolve_true(fullFlag)
861
+ withTaskInfo = resolve_true(withTaskInfo)
862
+
863
+ _logger.debug(f"getJediTaskDetails {jediTaskID} {fullFlag} {withTaskInfo}")
864
+ # execute
865
+ return userIF.getJediTaskDetails(jediTaskID, fullFlag, withTaskInfo)
866
+
867
+
868
+ # get full job status
869
+ def getFullJobStatus(req, ids):
870
+ # check security
871
+ if not isSecure(req):
872
+ return False
873
+ # get DN
874
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
875
+ return False
876
+ dn = _getDN(req)
877
+ return userIF.getFullJobStatus(ids, dn)
878
+
879
+
880
+ # insert task params
881
+ def insertTaskParams(req, taskParams=None, properErrorCode=None, parent_tid=None):
882
+ tmp_log = LogWrapper(_logger, f"insertTaskParams-{naive_utcnow().isoformat('/')}")
883
+ tmp_log.debug("start")
884
+ properErrorCode = resolve_true(properErrorCode)
885
+
886
+ # check security
887
+ if not isSecure(req):
888
+ tmp_log.debug(MESSAGE_SSL)
889
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
890
+ # get DN
891
+ user = None
892
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
893
+ user = _getDN(req)
894
+
895
+ # check format
896
+ try:
897
+ json.loads(taskParams)
898
+ except Exception:
899
+ tmp_log.debug(MESSAGE_JSON)
900
+ return WrappedPickle.dumps((False, MESSAGE_JSON))
901
+ # check role
902
+ is_production_role = _has_production_role(req)
903
+ # get FQANs
904
+ fqans = _getFQAN(req)
905
+
906
+ tmp_log.debug(f"user={user} prodRole={is_production_role} FQAN:{str(fqans)} parent_tid={parent_tid}")
907
+ ret = userIF.insertTaskParams(taskParams, user, is_production_role, fqans, properErrorCode, parent_tid)
908
+ try:
909
+ tmp_log.debug(ret[1])
910
+ except Exception:
911
+ pass
912
+ return WrappedPickle.dumps(ret)
913
+
914
+
915
+ # kill task
916
+ def killTask(req, jediTaskID=None, properErrorCode=None, broadcast=None):
917
+ properErrorCode = resolve_true(properErrorCode)
918
+ broadcast = resolve_true(broadcast)
919
+
920
+ # check security
921
+ if not isSecure(req):
922
+ error_code = CODE_SSL if properErrorCode else False
923
+ return WrappedPickle.dumps((error_code, MESSAGE_SSL))
924
+
925
+ # get DN
926
+ user = None
927
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
928
+ user = _getDN(req)
929
+ # check role
930
+ is_production_role = _has_production_role(req)
931
+ # check jediTaskID
932
+ try:
933
+ jediTaskID = int(jediTaskID)
934
+ except Exception:
935
+ error_code = CODE_LOGIC if properErrorCode else False
936
+ return WrappedPickle.dumps((error_code, MESSAGE_TASK_ID))
937
+
938
+ ret = userIF.killTask(jediTaskID, user, is_production_role, properErrorCode, broadcast)
939
+ return WrappedPickle.dumps(ret)
940
+
941
+
942
+ # retry task
943
+ def retryTask(
944
+ req,
945
+ jediTaskID,
946
+ properErrorCode=None,
947
+ newParams=None,
948
+ noChildRetry=None,
949
+ discardEvents=None,
950
+ disable_staging_mode=None,
951
+ keep_gshare_priority=None,
952
+ ignore_hard_exhausted=None,
953
+ ):
954
+ properErrorCode = resolve_true(properErrorCode)
955
+ noChildRetry = resolve_true(noChildRetry)
956
+ discardEvents = resolve_true(discardEvents)
957
+ disable_staging_mode = resolve_true(disable_staging_mode)
958
+ keep_gshare_priority = resolve_true(keep_gshare_priority)
959
+ ignore_hard_exhausted = resolve_true(ignore_hard_exhausted)
960
+
961
+ # check security
962
+ if not isSecure(req):
963
+ error_code = CODE_SSL if properErrorCode else False
964
+ return WrappedPickle.dumps((error_code, MESSAGE_SSL))
965
+
966
+ # get DN
967
+ user = None
968
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
969
+ user = _getDN(req)
970
+ # check role
971
+ is_production_role = _has_production_role(req)
972
+ # check jediTaskID
973
+ try:
974
+ jediTaskID = int(jediTaskID)
975
+ except Exception:
976
+ error_code = CODE_LOGIC if properErrorCode else False
977
+ return WrappedPickle.dumps((error_code, MESSAGE_TASK_ID))
978
+
979
+ ret = userIF.retryTask(
980
+ jediTaskID,
981
+ user,
982
+ is_production_role,
983
+ properErrorCode,
984
+ newParams,
985
+ noChildRetry,
986
+ discardEvents,
987
+ disable_staging_mode,
988
+ keep_gshare_priority,
989
+ ignore_hard_exhausted,
990
+ )
991
+ return WrappedPickle.dumps(ret)
992
+
993
+
994
+ # reassign task to site/cloud
995
+ def reassignTask(req, jediTaskID, site=None, cloud=None, nucleus=None, soft=None, mode=None):
996
+ # check security
997
+ if not isSecure(req):
998
+ return WrappedPickle.dumps((CODE_SSL, MESSAGE_SSL))
999
+ # get DN
1000
+ user = None
1001
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1002
+ user = _getDN(req)
1003
+ # check role
1004
+ is_production_role = _has_production_role(req)
1005
+ # check jediTaskID
1006
+ try:
1007
+ jediTaskID = int(jediTaskID)
1008
+ except Exception:
1009
+ return WrappedPickle.dumps((CODE_LOGIC, MESSAGE_TASK_ID))
1010
+ # site or cloud
1011
+ if site is not None:
1012
+ # set 'y' to go back to oldStatus immediately
1013
+ comComment = f"site:{site}:y"
1014
+ elif nucleus is not None:
1015
+ comComment = f"nucleus:{nucleus}:n"
1016
+ else:
1017
+ comComment = f"cloud:{cloud}:n"
1018
+ if mode == "nokill":
1019
+ comComment += ":nokill reassign"
1020
+ elif mode == "soft" or soft == "True":
1021
+ comComment += ":soft reassign"
1022
+ ret = userIF.reassignTask(jediTaskID, user, is_production_role, comComment)
1023
+ return WrappedPickle.dumps(ret)
1024
+
1025
+
1026
+ # finish task
1027
+ def finishTask(req, jediTaskID=None, properErrorCode=None, soft=None, broadcast=None):
1028
+ properErrorCode = resolve_true(properErrorCode)
1029
+ broadcast = resolve_true(broadcast)
1030
+ qualifier = None
1031
+ if soft == "True":
1032
+ qualifier = "soft"
1033
+
1034
+ # check security
1035
+ if not isSecure(req):
1036
+ error_code = CODE_SSL if properErrorCode else False
1037
+ return WrappedPickle.dumps((error_code, MESSAGE_SSL))
1038
+
1039
+ # get DN
1040
+ user = None
1041
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1042
+ user = _getDN(req)
1043
+ # check role
1044
+ is_production_role = _has_production_role(req)
1045
+ # check jediTaskID
1046
+ try:
1047
+ jediTaskID = int(jediTaskID)
1048
+ except Exception:
1049
+ error_code = CODE_LOGIC if properErrorCode else False
1050
+ return WrappedPickle.dumps((error_code, MESSAGE_TASK_ID))
1051
+
1052
+ ret = userIF.finishTask(jediTaskID, user, is_production_role, properErrorCode, qualifier, broadcast)
1053
+ return WrappedPickle.dumps(ret)
1054
+
1055
+
1056
+ # reload input
1057
+ def reloadInput(req, jediTaskID, properErrorCode=None, ignore_hard_exhausted=None):
1058
+ properErrorCode = resolve_true(properErrorCode)
1059
+ # check security
1060
+ if not isSecure(req):
1061
+ error_code = CODE_SSL if properErrorCode else False
1062
+ return WrappedPickle.dumps((error_code, MESSAGE_SSL))
1063
+
1064
+ # get DN
1065
+ user = None
1066
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1067
+ user = _getDN(req)
1068
+ # check role
1069
+ is_production_role = _has_production_role(req)
1070
+ # check jediTaskID
1071
+ try:
1072
+ jediTaskID = int(jediTaskID)
1073
+ except Exception:
1074
+ error_code = CODE_LOGIC if properErrorCode else False
1075
+ return WrappedPickle.dumps((error_code, MESSAGE_TASK_ID))
1076
+ ignore_hard_exhausted = resolve_true(ignore_hard_exhausted)
1077
+ ret = userIF.reloadInput(jediTaskID, user, is_production_role, ignore_hard_exhausted)
1078
+ return WrappedPickle.dumps(ret)
1079
+
1080
+
1081
+ # change task priority
1082
+ def changeTaskPriority(req, jediTaskID=None, newPriority=None):
1083
+ # check security
1084
+ if not isSecure(req):
1085
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1086
+ # check role
1087
+ is_production_role = _has_production_role(req)
1088
+ # only prod managers can use this method
1089
+ if not is_production_role:
1090
+ return "Failed : production or pilot role required"
1091
+ # check jediTaskID
1092
+ try:
1093
+ jediTaskID = int(jediTaskID)
1094
+ except Exception:
1095
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1096
+ # check priority
1097
+ try:
1098
+ newPriority = int(newPriority)
1099
+ except Exception:
1100
+ return WrappedPickle.dumps((False, "newPriority must be an integer"))
1101
+ ret = userIF.changeTaskPriority(jediTaskID, newPriority)
1102
+ return WrappedPickle.dumps(ret)
1103
+
1104
+
1105
+ # increase attempt number for unprocessed files
1106
+ def increaseAttemptNrPanda(req, jediTaskID, increasedNr):
1107
+ # check security
1108
+ if not isSecure(req):
1109
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1110
+
1111
+ # check role
1112
+ is_production_role = _has_production_role(req)
1113
+ # only prod managers can use this method
1114
+ if not is_production_role:
1115
+ return WrappedPickle.dumps((3, MESSAGE_PROD_ROLE))
1116
+
1117
+ # check jediTaskID
1118
+ try:
1119
+ jediTaskID = int(jediTaskID)
1120
+ except Exception:
1121
+ return WrappedPickle.dumps((4, MESSAGE_TASK_ID))
1122
+
1123
+ # check value for increase
1124
+ try:
1125
+ increasedNr = int(increasedNr)
1126
+ except Exception:
1127
+ increasedNr = -1
1128
+ if increasedNr < 0:
1129
+ return WrappedPickle.dumps((4, "increase must be a positive integer"))
1130
+
1131
+ return WrappedPickle.dumps(userIF.increaseAttemptNrPanda(jediTaskID, increasedNr))
1132
+
1133
+
1134
+ # change task attribute
1135
+ def changeTaskAttributePanda(req, jediTaskID, attrName, attrValue):
1136
+ # check security
1137
+ if not isSecure(req):
1138
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1139
+
1140
+ # check role
1141
+ is_production_role = _has_production_role(req)
1142
+ # only prod managers can use this method
1143
+ if not is_production_role:
1144
+ return WrappedPickle.dumps((False, MESSAGE_PROD_ROLE))
1145
+ # check jediTaskID
1146
+ try:
1147
+ jediTaskID = int(jediTaskID)
1148
+ except Exception:
1149
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1150
+ # check attribute
1151
+ if attrName not in ["ramCount", "wallTime", "cpuTime", "coreCount"]:
1152
+ return WrappedPickle.dumps((2, f"disallowed to update {attrName}"))
1153
+ ret = userIF.changeTaskAttributePanda(jediTaskID, attrName, attrValue)
1154
+ return WrappedPickle.dumps((ret, None))
1155
+
1156
+
1157
+ # change split rule for task
1158
+ def changeTaskSplitRulePanda(req, jediTaskID, attrName, attrValue):
1159
+ # check security
1160
+ if not isSecure(req):
1161
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1162
+
1163
+ # check role
1164
+ is_production_role = _has_production_role(req)
1165
+ # only prod managers can use this method
1166
+ if not is_production_role:
1167
+ return WrappedPickle.dumps((False, MESSAGE_PROD_ROLE))
1168
+ # check jediTaskID
1169
+ try:
1170
+ jediTaskID = int(jediTaskID)
1171
+ except Exception:
1172
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1173
+ # check attribute
1174
+ if attrName not in [
1175
+ "AI",
1176
+ "TW",
1177
+ "EC",
1178
+ "ES",
1179
+ "MF",
1180
+ "NG",
1181
+ "NI",
1182
+ "NF",
1183
+ "NJ",
1184
+ "AV",
1185
+ "IL",
1186
+ "LI",
1187
+ "LC",
1188
+ "CC",
1189
+ "OT",
1190
+ "UZ",
1191
+ ]:
1192
+ return WrappedPickle.dumps((2, f"disallowed to update {attrName}"))
1193
+ ret = userIF.changeTaskSplitRulePanda(jediTaskID, attrName, attrValue)
1194
+ return WrappedPickle.dumps((ret, None))
1195
+
1196
+
1197
+ # pause task
1198
+ def pauseTask(req, jediTaskID):
1199
+ # check security
1200
+ if not isSecure(req):
1201
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1202
+ # get DN
1203
+ user = None
1204
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1205
+ user = _getDN(req)
1206
+ # check role
1207
+ is_production_role = _has_production_role(req)
1208
+ # only prod managers can use this method
1209
+ if not is_production_role:
1210
+ return WrappedPickle.dumps((False, MESSAGE_PROD_ROLE))
1211
+ # check jediTaskID
1212
+ try:
1213
+ jediTaskID = int(jediTaskID)
1214
+ except Exception:
1215
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1216
+ ret = userIF.pauseTask(jediTaskID, user, is_production_role)
1217
+ return WrappedPickle.dumps(ret)
1218
+
1219
+
1220
+ # resume task
1221
+ def resumeTask(req, jediTaskID):
1222
+ # check security
1223
+ if not isSecure(req):
1224
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1225
+ # get DN
1226
+ user = None
1227
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1228
+ user = _getDN(req)
1229
+ # check role
1230
+ is_production_role = _has_production_role(req)
1231
+ # only prod managers can use this method
1232
+ if not is_production_role:
1233
+ return WrappedPickle.dumps((False, "production role required"))
1234
+ # check jediTaskID
1235
+ try:
1236
+ jediTaskID = int(jediTaskID)
1237
+ except Exception:
1238
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1239
+ ret = userIF.resumeTask(jediTaskID, user, is_production_role)
1240
+ return WrappedPickle.dumps(ret)
1241
+
1242
+
1243
+ # force avalanche for task
1244
+ def avalancheTask(req, jediTaskID):
1245
+ # check security
1246
+ if not isSecure(req):
1247
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1248
+ # get DN
1249
+ user = None
1250
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1251
+ user = _getDN(req)
1252
+ # check role
1253
+ is_production_role = _has_production_role(req)
1254
+ # only prod managers can use this method
1255
+ if not is_production_role:
1256
+ return WrappedPickle.dumps((False, "production role required"))
1257
+ # check jediTaskID
1258
+ try:
1259
+ jediTaskID = int(jediTaskID)
1260
+ except Exception:
1261
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1262
+ ret = userIF.avalancheTask(jediTaskID, user, is_production_role)
1263
+ return WrappedPickle.dumps(ret)
1264
+
1265
+
1266
+ # release task
1267
+ def release_task(req, jedi_task_id):
1268
+ # check security
1269
+ if not isSecure(req):
1270
+ return json.dumps((False, MESSAGE_SSL))
1271
+ # get DN
1272
+ user = None
1273
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1274
+ user = _getDN(req)
1275
+ # check role
1276
+ prod_role = _has_production_role(req)
1277
+ # only prod managers can use this method
1278
+ if not prod_role:
1279
+ return json.dumps((False, "production role required"))
1280
+ # check jediTaskID
1281
+ try:
1282
+ jedi_task_id = int(jedi_task_id)
1283
+ except Exception:
1284
+ return json.dumps((False, MESSAGE_TASK_ID))
1285
+ ret = userIF.send_command_to_task(jedi_task_id, user, prod_role, "release")
1286
+ return json.dumps(ret)
1287
+
1288
+
1289
+ # kill unfinished jobs
1290
+ def killUnfinishedJobs(req, jediTaskID, code=None, useMailAsID=None):
1291
+ # check security
1292
+ if not isSecure(req):
1293
+ return False
1294
+ # get DN
1295
+ user = None
1296
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
1297
+ user = _getDN(req)
1298
+ # check role
1299
+ is_production_manager = False
1300
+ # get FQANs
1301
+ fqans = _getFQAN(req)
1302
+ # loop over all FQANs
1303
+ for fqan in fqans:
1304
+ # check production role
1305
+ for rolePat in ["/atlas/usatlas/Role=production", "/atlas/Role=production"]:
1306
+ if fqan.startswith(rolePat):
1307
+ is_production_manager = True
1308
+ break
1309
+ # escape
1310
+ if is_production_manager:
1311
+ break
1312
+ # use email address as ID
1313
+ useMailAsID = resolve_true(useMailAsID)
1314
+ # hostname
1315
+ host = req.get_remote_host()
1316
+ # get PandaIDs
1317
+ ids = userIF.getPandaIDsWithTaskID(jediTaskID)
1318
+ # kill
1319
+ return userIF.killJobs(ids, user, host, code, is_production_manager, useMailAsID, fqans)
1320
+
1321
+
1322
+ # change modificationTime for task
1323
+ def changeTaskModTimePanda(req, jediTaskID, diffValue):
1324
+ # check security
1325
+ if not isSecure(req):
1326
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1327
+
1328
+ # check role
1329
+ is_production_role = _has_production_role(req)
1330
+ # only prod managers can use this method
1331
+ if not is_production_role:
1332
+ return WrappedPickle.dumps((False, MESSAGE_PROD_ROLE))
1333
+ # check jediTaskID
1334
+ try:
1335
+ jediTaskID = int(jediTaskID)
1336
+ except Exception:
1337
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1338
+ try:
1339
+ diffValue = int(diffValue)
1340
+ attrValue = datetime.datetime.now() + datetime.timedelta(hours=diffValue)
1341
+ except Exception:
1342
+ return WrappedPickle.dumps((False, f"failed to convert {diffValue} to time diff"))
1343
+ ret = userIF.changeTaskAttributePanda(jediTaskID, "modificationTime", attrValue)
1344
+ return WrappedPickle.dumps((ret, None))
1345
+
1346
+
1347
+ # get PandaIDs with TaskID
1348
+ def getPandaIDsWithTaskID(req, jediTaskID):
1349
+ try:
1350
+ jediTaskID = int(jediTaskID)
1351
+ except Exception:
1352
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1353
+ idsStr = userIF.getPandaIDsWithTaskID(jediTaskID)
1354
+ # deserialize
1355
+ ids = WrappedPickle.loads(idsStr)
1356
+
1357
+ return WrappedPickle.dumps(ids)
1358
+
1359
+
1360
+ # reactivate Task
1361
+ def reactivateTask(req, jediTaskID, keep_attempt_nr=None, trigger_job_generation=None):
1362
+ # check security
1363
+ if not isSecure(req):
1364
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1365
+ # check role
1366
+ is_production_manager = _has_production_role(req)
1367
+ if not is_production_manager:
1368
+ msg = "production role is required"
1369
+ _logger.error(f"reactivateTask: {msg}")
1370
+ return WrappedPickle.dumps((False, msg))
1371
+ try:
1372
+ jediTaskID = int(jediTaskID)
1373
+ except Exception:
1374
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1375
+ keep_attempt_nr = resolve_true(keep_attempt_nr)
1376
+ trigger_job_generation = resolve_true(trigger_job_generation)
1377
+
1378
+ ret = userIF.reactivateTask(jediTaskID, keep_attempt_nr, trigger_job_generation)
1379
+
1380
+ return WrappedPickle.dumps(ret)
1381
+
1382
+
1383
+ # get task status
1384
+ def getTaskStatus(req, jediTaskID):
1385
+ try:
1386
+ jediTaskID = int(jediTaskID)
1387
+ except Exception:
1388
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1389
+ ret = userIF.getTaskStatus(jediTaskID)
1390
+ return WrappedPickle.dumps(ret)
1391
+
1392
+
1393
+ # reassign share
1394
+ def reassignShare(req, jedi_task_ids_pickle, share, reassign_running):
1395
+ # check security
1396
+ if not isSecure(req):
1397
+ return WrappedPickle.dumps((False, MESSAGE_SSL))
1398
+
1399
+ # check role
1400
+ prod_role = _has_production_role(req)
1401
+ if not prod_role:
1402
+ return WrappedPickle.dumps((False, MESSAGE_PROD_ROLE))
1403
+
1404
+ jedi_task_ids = WrappedPickle.loads(jedi_task_ids_pickle)
1405
+ _logger.debug(f"reassignShare: jedi_task_ids: {jedi_task_ids}, share: {share}, reassign_running: {reassign_running} (type {type(reassign_running)})")
1406
+
1407
+ if isinstance(reassign_running, str) and "true" in reassign_running.lower():
1408
+ reassign_running = True
1409
+ else:
1410
+ reassign_running = False
1411
+
1412
+ if not ((isinstance(jedi_task_ids, list) or (isinstance(jedi_task_ids, tuple)) and isinstance(share, str))):
1413
+ return WrappedPickle.dumps((False, "jedi_task_ids must be tuple/list and share must be string"))
1414
+
1415
+ ret = userIF.reassignShare(jedi_task_ids, share, reassign_running)
1416
+ return WrappedPickle.dumps(ret)
1417
+
1418
+
1419
+ # get taskParamsMap with TaskID
1420
+ def getTaskParamsMap(req, jediTaskID):
1421
+ try:
1422
+ jediTaskID = int(jediTaskID)
1423
+ except Exception:
1424
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1425
+ ret = userIF.getTaskParamsMap(jediTaskID)
1426
+ return WrappedPickle.dumps(ret)
1427
+
1428
+
1429
+ # update workers
1430
+ def updateWorkers(req, harvesterID, workers):
1431
+ # check security
1432
+ if not isSecure(req):
1433
+ return json.dumps((False, MESSAGE_SSL))
1434
+ # get DN
1435
+ user = _getDN(req)
1436
+ # hostname
1437
+ host = req.get_remote_host()
1438
+ return_value = None
1439
+ tStart = naive_utcnow()
1440
+ # convert
1441
+ try:
1442
+ data = json.loads(workers)
1443
+ except Exception:
1444
+ return_value = json.dumps((False, MESSAGE_JSON))
1445
+ # update
1446
+ if return_value is None:
1447
+ return_value = userIF.updateWorkers(user, host, harvesterID, data)
1448
+ tDelta = naive_utcnow() - tStart
1449
+ _logger.debug(f"updateWorkers {harvesterID} took {tDelta.seconds}.{tDelta.microseconds // 1000:03d} sec")
1450
+
1451
+ return return_value
1452
+
1453
+
1454
+ # update workers
1455
+ def updateServiceMetrics(req, harvesterID, metrics):
1456
+ # check security
1457
+ if not isSecure(req):
1458
+ return json.dumps((False, MESSAGE_SSL))
1459
+
1460
+ user = _getDN(req)
1461
+
1462
+ host = req.get_remote_host()
1463
+ return_value = None
1464
+ tStart = naive_utcnow()
1465
+
1466
+ # convert
1467
+ try:
1468
+ data = json.loads(metrics)
1469
+ except Exception:
1470
+ return_value = json.dumps((False, MESSAGE_JSON))
1471
+
1472
+ # update
1473
+ if return_value is None:
1474
+ return_value = userIF.updateServiceMetrics(user, host, harvesterID, data)
1475
+
1476
+ tDelta = naive_utcnow() - tStart
1477
+ _logger.debug(f"updateServiceMetrics {harvesterID} took {tDelta.seconds}.{tDelta.microseconds // 1000:03d} sec")
1478
+
1479
+ return return_value
1480
+
1481
+
1482
+ # add harvester dialog messages
1483
+ def addHarvesterDialogs(req, harvesterID, dialogs):
1484
+ # check security
1485
+ if not isSecure(req):
1486
+ return json.dumps((False, MESSAGE_SSL))
1487
+ # get DN
1488
+ user = _getDN(req)
1489
+ # convert
1490
+ try:
1491
+ data = json.loads(dialogs)
1492
+ except Exception:
1493
+ return json.dumps((False, MESSAGE_JSON))
1494
+ # update
1495
+ return userIF.addHarvesterDialogs(user, harvesterID, data)
1496
+
1497
+
1498
+ # heartbeat for harvester
1499
+ def harvesterIsAlive(req, harvesterID, data=None):
1500
+ # check security
1501
+ if not isSecure(req):
1502
+ return json.dumps((False, MESSAGE_SSL))
1503
+ # get DN
1504
+ user = _getDN(req)
1505
+ # hostname
1506
+ host = req.get_remote_host()
1507
+ # convert
1508
+ try:
1509
+ if data is not None:
1510
+ data = json.loads(data)
1511
+ else:
1512
+ data = dict()
1513
+ except Exception:
1514
+ return json.dumps((False, MESSAGE_JSON))
1515
+ # update
1516
+ return userIF.harvesterIsAlive(user, host, harvesterID, data)
1517
+
1518
+
1519
+ # get stats of workers
1520
+ def getWorkerStats(req):
1521
+ # get
1522
+ ret = userIF.getWorkerStats()
1523
+ return json.dumps(ret)
1524
+
1525
+
1526
+ # report stat of workers
1527
+ def reportWorkerStats_jobtype(req, harvesterID, siteName, paramsList):
1528
+ # check security
1529
+ if not isSecure(req):
1530
+ return json.dumps((False, MESSAGE_SSL))
1531
+ # update
1532
+ ret = userIF.reportWorkerStats_jobtype(harvesterID, siteName, paramsList)
1533
+ return json.dumps(ret)
1534
+
1535
+
1536
+ # set num slots for workload provisioning
1537
+ def setNumSlotsForWP(req, pandaQueueName, numSlots, gshare=None, resourceType=None, validPeriod=None):
1538
+ # check security
1539
+ if not isSecure(req):
1540
+ return json.dumps((CODE_SSL, MESSAGE_SSL))
1541
+ # check role
1542
+ if not _has_production_role(req):
1543
+ return json.dumps((CODE_LOGIC, "production role is required in the certificate"))
1544
+ # convert
1545
+ try:
1546
+ numSlots = int(numSlots)
1547
+ except Exception:
1548
+ return json.dumps((CODE_OTHER_PARAMS, "numSlots must be int"))
1549
+ # execute
1550
+ return userIF.setNumSlotsForWP(pandaQueueName, numSlots, gshare, resourceType, validPeriod)
1551
+
1552
+
1553
+ # enable jumbo jobs
1554
+ def enableJumboJobs(req, jediTaskID, nJumboJobs, nJumboPerSite=None):
1555
+ # check security
1556
+ if not isSecure(req):
1557
+ return json.dumps((CODE_SSL, MESSAGE_SSL))
1558
+ # check role
1559
+ if not _has_production_role(req):
1560
+ return json.dumps((CODE_LOGIC, "production role is required in the certificate"))
1561
+ # convert
1562
+ try:
1563
+ nJumboJobs = int(nJumboJobs)
1564
+ except Exception:
1565
+ return json.dumps((CODE_OTHER_PARAMS, "nJumboJobs must be int"))
1566
+ try:
1567
+ nJumboPerSite = int(nJumboPerSite)
1568
+ except Exception:
1569
+ nJumboPerSite = nJumboJobs
1570
+ # execute
1571
+ return userIF.enableJumboJobs(jediTaskID, nJumboJobs, nJumboPerSite)
1572
+
1573
+
1574
+ # get user job metadata
1575
+ def getUserJobMetadata(req, jediTaskID):
1576
+ try:
1577
+ jediTaskID = int(jediTaskID)
1578
+ except Exception:
1579
+ return WrappedPickle.dumps((False, MESSAGE_TASK_ID))
1580
+ return userIF.getUserJobMetadata(jediTaskID)
1581
+
1582
+
1583
+ # get jumbo job datasets
1584
+ def getJumboJobDatasets(req, n_days, grace_period=0):
1585
+ try:
1586
+ n_days = int(n_days)
1587
+ except Exception:
1588
+ return WrappedPickle.dumps((False, "wrong n_days"))
1589
+ try:
1590
+ grace_period = int(grace_period)
1591
+ except Exception:
1592
+ return WrappedPickle.dumps((False, "wrong grace_period"))
1593
+ return userIF.getJumboJobDatasets(n_days, grace_period)
1594
+
1595
+
1596
+ # send Harvester the command to clean up the workers for a panda queue
1597
+ def sweepPQ(req, panda_queue, status_list, ce_list, submission_host_list):
1598
+ # check security
1599
+ if not isSecure(req):
1600
+ return json.dumps((False, MESSAGE_SSL))
1601
+ # check role
1602
+ prod_role = _has_production_role(req)
1603
+ if not prod_role:
1604
+ return json.dumps((False, MESSAGE_PROD_ROLE))
1605
+
1606
+ return json.dumps((True, userIF.sweepPQ(panda_queue, status_list, ce_list, submission_host_list)))
1607
+
1608
+
1609
+ # json decoder for idds constants
1610
+ def decode_idds_enum(d):
1611
+ if "__idds_const__" in d:
1612
+ items = d["__idds_const__"].split(".")
1613
+ obj = idds.common.constants
1614
+ for item in items:
1615
+ obj = getattr(obj, item)
1616
+ return obj
1617
+ else:
1618
+ return d
1619
+
1620
+
1621
+ # relay iDDS command
1622
+ def relay_idds_command(req, command_name, args=None, kwargs=None, manager=None, json_outputs=None):
1623
+ tmp_log = LogWrapper(
1624
+ _logger,
1625
+ f"relay_idds_command-{naive_utcnow().isoformat('/')}",
1626
+ )
1627
+ # check security
1628
+ if not isSecure(req):
1629
+ tmp_log.error(MESSAGE_SSL)
1630
+ return json.dumps((False, MESSAGE_SSL))
1631
+ try:
1632
+ manager = resolve_bool(manager)
1633
+ if not manager:
1634
+ manager = False
1635
+ if "+" in command_name:
1636
+ command_name, idds_host = command_name.split("+")
1637
+ else:
1638
+ idds_host = idds.common.utils.get_rest_host()
1639
+ if manager:
1640
+ c = iDDS_ClientManager(idds_host)
1641
+ else:
1642
+ c = iDDS_Client(idds_host)
1643
+ if not hasattr(c, command_name):
1644
+ tmp_str = f"{command_name} is not a command of iDDS {c.__class__.__name__}"
1645
+ tmp_log.error(tmp_str)
1646
+ return json.dumps((False, tmp_str))
1647
+ if args:
1648
+ try:
1649
+ args = idds.common.utils.json_loads(args)
1650
+ except Exception as e:
1651
+ tmp_log.warning(f"failed to load args json with {str(e)}")
1652
+ args = json.loads(args, object_hook=decode_idds_enum)
1653
+ else:
1654
+ args = []
1655
+ if kwargs:
1656
+ try:
1657
+ kwargs = idds.common.utils.json_loads(kwargs)
1658
+ except Exception as e:
1659
+ tmp_log.warning(f"failed to load kwargs json with {str(e)}")
1660
+ kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
1661
+ else:
1662
+ kwargs = {}
1663
+ # json outputs
1664
+ if json_outputs and manager:
1665
+ c.setup_json_outputs()
1666
+ # set original username
1667
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
1668
+ if dn:
1669
+ c.set_original_user(user_name=clean_user_id(dn))
1670
+ tmp_log.debug(f"execute: class={c.__class__.__name__} com={command_name} host={idds_host} args={str(args)[:200]} kwargs={str(kwargs)[:200]}")
1671
+ ret = getattr(c, command_name)(*args, **kwargs)
1672
+ tmp_log.debug(f"ret: {str(ret)[:200]}")
1673
+ try:
1674
+ return json.dumps((True, ret))
1675
+ except Exception:
1676
+ return idds.common.utils.json_dumps((True, ret))
1677
+ except Exception as e:
1678
+ tmp_str = f"failed to execute command with {str(e)}"
1679
+ tmp_log.error(f"{tmp_str} {traceback.format_exc()}")
1680
+ return json.dumps((False, tmp_str))
1681
+
1682
+
1683
+ # relay iDDS workflow command with ownership check
1684
+ def execute_idds_workflow_command(req, command_name, kwargs=None, json_outputs=None):
1685
+ try:
1686
+ tmp_log = LogWrapper(
1687
+ _logger,
1688
+ f"execute_idds_workflow_command-{naive_utcnow().isoformat('/')}",
1689
+ )
1690
+ if kwargs:
1691
+ try:
1692
+ kwargs = idds.common.utils.json_loads(kwargs)
1693
+ except Exception:
1694
+ kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
1695
+ else:
1696
+ kwargs = {}
1697
+ if "+" in command_name:
1698
+ command_name, idds_host = command_name.split("+")
1699
+ else:
1700
+ idds_host = idds.common.utils.get_rest_host()
1701
+ # check permission
1702
+ if command_name in ["get_status"]:
1703
+ check_owner = False
1704
+ elif command_name in ["abort", "suspend", "resume", "retry", "finish"]:
1705
+ check_owner = True
1706
+ else:
1707
+ tmp_message = f"{command_name} is unsupported"
1708
+ tmp_log.error(tmp_message)
1709
+ return json.dumps((False, tmp_message))
1710
+ # check owner
1711
+ c = iDDS_ClientManager(idds_host)
1712
+ if json_outputs:
1713
+ c.setup_json_outputs()
1714
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
1715
+ if check_owner:
1716
+ # requester
1717
+ if not dn:
1718
+ tmp_message = "SSL_CLIENT_S_DN is missing in HTTP request"
1719
+ tmp_log.error(tmp_message)
1720
+ return json.dumps((False, tmp_message))
1721
+ requester = clean_user_id(dn)
1722
+ # get request_id
1723
+ request_id = kwargs.get("request_id")
1724
+ if request_id is None:
1725
+ tmp_message = "request_id is missing"
1726
+ tmp_log.error(tmp_message)
1727
+ return json.dumps((False, tmp_message))
1728
+ # get request
1729
+ req = c.get_requests(request_id=request_id)
1730
+ if not req:
1731
+ tmp_message = f"request {request_id} is not found"
1732
+ tmp_log.error(tmp_message)
1733
+ return json.dumps((False, tmp_message))
1734
+ user_name = req[0].get("username")
1735
+ if user_name and user_name != requester:
1736
+ tmp_message = f"request {request_id} is not owned by {requester}"
1737
+ tmp_log.error(tmp_message)
1738
+ return json.dumps((False, tmp_message))
1739
+ # set original username
1740
+ if dn:
1741
+ c.set_original_user(user_name=clean_user_id(dn))
1742
+ # execute command
1743
+ tmp_log.debug(f"com={command_name} host={idds_host} kwargs={str(kwargs)}")
1744
+ ret = getattr(c, command_name)(**kwargs)
1745
+ tmp_log.debug(str(ret))
1746
+ if isinstance(ret, dict) and "message" in ret:
1747
+ return json.dumps((True, [ret["status"], ret["message"]]))
1748
+ return json.dumps((True, ret))
1749
+ except Exception as e:
1750
+ tmp_log.error(f"failed with {str(e)} {traceback.format_exc()}")
1751
+ return json.dumps((False, f"server failed with {str(e)}"))
1752
+
1753
+
1754
+ # send command to a job
1755
+ def send_command_to_job(req, panda_id, com):
1756
+ # check security
1757
+ if not isSecure(req):
1758
+ return json.dumps((False, MESSAGE_SSL))
1759
+ # check role
1760
+ prod_role = _has_production_role(req)
1761
+ if not prod_role:
1762
+ return json.dumps((False, MESSAGE_PROD_ROLE))
1763
+ return json.dumps(userIF.send_command_to_job(panda_id, com))
1764
+
1765
+
1766
+ # set user secret
1767
+ def set_user_secret(req, key=None, value=None):
1768
+ tmp_log = LogWrapper(_logger, f"set_user_secret-{naive_utcnow().isoformat('/')}")
1769
+ # get owner
1770
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
1771
+ if not dn:
1772
+ tmp_message = "SSL_CLIENT_S_DN is missing in HTTP request"
1773
+ tmp_log.error(tmp_message)
1774
+ return json.dumps((False, tmp_message))
1775
+ owner = clean_user_id(dn)
1776
+ return json.dumps(userIF.set_user_secret(owner, key, value))
1777
+
1778
+
1779
+ # get user secrets
1780
+ def get_user_secrets(req, keys=None, get_json=None):
1781
+ tmp_log = LogWrapper(_logger, f"get_user_secrets-{naive_utcnow().isoformat('/')}")
1782
+ # get owner
1783
+ dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
1784
+ get_json = resolve_true(get_json)
1785
+
1786
+ if not dn:
1787
+ tmp_message = "SSL_CLIENT_S_DN is missing in HTTP request"
1788
+ tmp_log.error(tmp_message)
1789
+ return json.dumps((False, tmp_message))
1790
+ owner = clean_user_id(dn)
1791
+ return json.dumps(userIF.get_user_secrets(owner, keys, get_json))
1792
+
1793
+
1794
+ # get files in datasets
1795
+ def get_files_in_datasets(req, task_id, dataset_types="input,pseudo_input"):
1796
+ # check security
1797
+ if not isSecure(req):
1798
+ return json.dumps((False, MESSAGE_SSL))
1799
+ return json.dumps(userIF.get_files_in_datasets(task_id, dataset_types))