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,1686 @@
1
+ """
2
+ client methods
3
+ """
4
+
5
+ import gzip
6
+ import json
7
+ import os
8
+ import pickle
9
+ import socket
10
+ import sys
11
+ import tempfile
12
+
13
+ import requests
14
+ from pandacommon.pandautils.net_utils import replace_hostname_in_url_randomly
15
+
16
+ from pandaserver.api.v1.http_client import HttpClient as HttpClientV1
17
+ from pandaserver.api.v1.http_client import api_url_ssl as api_url_ssl_v1
18
+ from pandaserver.taskbuffer.JobUtils import dump_jobs_json
19
+
20
+ # PanDA server configuration
21
+ baseURL = os.environ.get("PANDA_URL", "http://pandaserver.cern.ch:25080/server/panda")
22
+ baseURLSSL = os.environ.get("PANDA_URL_SSL", "https://pandaserver.cern.ch:25443/server/panda")
23
+
24
+ DEFAULT_CERT_PATH = "/etc/grid-security/certificates"
25
+
26
+ # exit code
27
+ EC_Failed = 255
28
+
29
+
30
+ def is_https(url):
31
+ # check if https is used
32
+ return url.startswith("https://")
33
+
34
+
35
+ def pickle_dumps(obj):
36
+ # wrapper for pickle with python 3
37
+ return pickle.dumps(obj, protocol=0)
38
+
39
+
40
+ def pickle_loads(obj_string):
41
+ try:
42
+ return pickle.loads(obj_string.encode())
43
+ except Exception:
44
+ return pickle.loads(obj_string)
45
+
46
+
47
+ class HttpClient:
48
+ def __init__(self):
49
+ # verification of the host certificate
50
+ if "PANDA_VERIFY_HOST" in os.environ and os.environ["PANDA_VERIFY_HOST"] == "off":
51
+ self.verifyHost = False
52
+ else:
53
+ self.verifyHost = True
54
+
55
+ # request a compressed response
56
+ self.compress = True
57
+
58
+ # SSL cert/key
59
+ self.ssl_certificate = self._x509()
60
+ self.ssl_key = self._x509()
61
+
62
+ self.use_json = False
63
+
64
+ # OIDC
65
+ self.oidc = os.getenv("PANDA_AUTH") == "oidc"
66
+ self.auth_vo = os.getenv("PANDA_AUTH_VO") if self.oidc else None
67
+ self.id_token = os.getenv("PANDA_AUTH_ID_TOKEN") if self.oidc else None
68
+ if self.id_token and self.id_token.startswith("file:"):
69
+ with open(self.id_token[5:], "r") as f:
70
+ self.id_token = f.read().strip()
71
+
72
+ def _x509(self):
73
+ # retrieve the X509_USER_PROXY from the environment variables and check if it is readable
74
+ try:
75
+ if "X509_USER_PROXY" in os.environ and os.access(os.environ["X509_USER_PROXY"], os.R_OK):
76
+ return os.environ["X509_USER_PROXY"]
77
+ except Exception:
78
+ pass
79
+
80
+ # look for the default place
81
+ x509 = f"/tmp/x509up_u{os.getuid()}"
82
+ if os.access(x509, os.R_OK):
83
+ return x509
84
+
85
+ # no valid proxy certificate
86
+ print("No valid grid proxy certificate found")
87
+ return ""
88
+
89
+ def _prepare_url(self, url):
90
+ """Modify URL with HTTPS check and hostname replacement."""
91
+ use_https = is_https(url)
92
+ if "PANDA_BEHIND_REAL_LB" in os.environ:
93
+ modified_url = url
94
+ else:
95
+ modified_url = replace_hostname_in_url_randomly(url)
96
+ return modified_url, use_https
97
+
98
+ def _prepare_headers(self):
99
+ """Prepare headers based on authentication and JSON settings."""
100
+ headers = {}
101
+
102
+ if self.oidc:
103
+ headers["Authorization"] = f"Bearer {self.id_token}"
104
+ headers["Origin"] = self.auth_vo
105
+
106
+ if self.use_json:
107
+ headers["Accept"] = "application/json"
108
+
109
+ return headers
110
+
111
+ def _prepare_ssl(self, use_https):
112
+ """Prepare SSL configuration based on HTTPS usage and verification settings."""
113
+ cert = None # no certificate by default when no HTTS or using oidc headers
114
+ verify = True # validate against default system CA certificates
115
+
116
+ if use_https:
117
+ # oidc tokens are added to the headers, we don't need to provide a certificate
118
+ if not self.oidc:
119
+ cert = (self.ssl_certificate, self.ssl_key)
120
+
121
+ # the host verification has been disabled in the configuration
122
+ if not self.verifyHost:
123
+ verify = False
124
+ # there is a path to the CA certificate folder and it exists
125
+ elif "X509_CERT_DIR" in os.environ and os.path.exists(os.environ["X509_CERT_DIR"]):
126
+ verify = os.environ["X509_CERT_DIR"]
127
+ # the CA certificate folder is available in the standard location
128
+ elif os.path.exists(DEFAULT_CERT_PATH):
129
+ verify = DEFAULT_CERT_PATH
130
+
131
+ return cert, verify
132
+
133
+ def get(self, url, data):
134
+ url, use_https = self._prepare_url(url)
135
+ headers = self._prepare_headers()
136
+ cert, verify = self._prepare_ssl(use_https)
137
+
138
+ try:
139
+ response = requests.get(url, headers=headers, params=data, timeout=600, cert=cert, verify=verify)
140
+ response.raise_for_status()
141
+ return 0, response.text
142
+ except requests.RequestException as e:
143
+ return 255, str(e)
144
+
145
+ def post(self, url, data):
146
+ url, use_https = self._prepare_url(url)
147
+ headers = self._prepare_headers()
148
+ cert, verify = self._prepare_ssl(use_https)
149
+
150
+ try:
151
+ response = requests.post(url, headers=headers, data=data, timeout=600, cert=cert, verify=verify)
152
+ response.raise_for_status()
153
+ return 0, response.text
154
+ except requests.RequestException as e:
155
+ return 255, str(e)
156
+
157
+ def post_files(self, url, data):
158
+ url, use_https = self._prepare_url(url)
159
+ headers = self._prepare_headers()
160
+ cert, verify = self._prepare_ssl(use_https)
161
+
162
+ files = {}
163
+ try:
164
+ for key, value in data.items():
165
+ if type(data[key]) == str:
166
+ # we got a file to upload without specifying the destination name
167
+ files[key] = open(data[key], "rb")
168
+ else:
169
+ # we got a file to upload which specifies the destination name
170
+ files[key] = (data[key][0], open(data[key][1], "rb"))
171
+ response = requests.post(url, headers=headers, files=files, timeout=600, cert=cert, verify=verify)
172
+ response.raise_for_status()
173
+ return 0, response.text
174
+ except requests.RequestException as e:
175
+ return 255, str(e)
176
+ finally:
177
+ for file in files.values():
178
+ if type(file) == tuple:
179
+ file_handler = file[1]
180
+ else:
181
+ file_handler = file
182
+ file_handler.close()
183
+
184
+
185
+ """
186
+ Client API
187
+ """
188
+
189
+
190
+ def submit_jobs(jobs):
191
+ """
192
+ Submit jobs
193
+
194
+ args:
195
+ jobs: the list of JobSpecs
196
+ returns:
197
+ status code
198
+ 0: communication succeeded to the panda server
199
+ 255: communication failure
200
+ return code
201
+ True: request is processed
202
+ False: not processed
203
+ """
204
+ # set hostname to jobs
205
+ hostname = socket.getfqdn()
206
+ for job in jobs:
207
+ job.creationHost = hostname
208
+
209
+ # serialize the jobs to json
210
+ jobs = dump_jobs_json(jobs)
211
+
212
+ http_client = HttpClientV1()
213
+
214
+ url = f"{api_url_ssl_v1}/job/submit"
215
+ data = {"jobs": jobs}
216
+
217
+ status, output = http_client.post(url, data)
218
+ return status, output
219
+
220
+
221
+ def get_job_status(job_ids):
222
+ """
223
+ Get job status
224
+
225
+ args:
226
+ job_ids: the list of PandaIDs
227
+ returns:
228
+ status code
229
+ 0: communication succeeded to the panda server
230
+ 255: communication failure
231
+ the list of JobSpecs (or Nones for non-existing PandaIDs)
232
+ """
233
+ http_client = HttpClientV1()
234
+ url = f"{api_url_ssl_v1}/pilot/get_job_status"
235
+ data = {"job_ids": job_ids}
236
+ status, output = http_client.post(url, data)
237
+
238
+ return status, output
239
+
240
+
241
+ def kill_jobs(
242
+ job_ids,
243
+ code=None,
244
+ keep_unmerged=False,
245
+ job_sub_status=None,
246
+ ):
247
+ """
248
+ Kill jobs. Normal users can kill only their own jobs.
249
+ People with production VOMS role can kill any jobs.
250
+ Running jobs are killed when next heartbeat comes from the pilot.
251
+ Set code=9 if running jobs need to be killed immediately.
252
+
253
+ args:
254
+ job_ids: the list of PandaIDs
255
+ code: specify why the jobs are killed
256
+ 2: expire
257
+ 3: aborted
258
+ 4: expire in waiting
259
+ 7: retry by server
260
+ 8: re-brokerage
261
+ 9: force kill
262
+ 10: fast re-brokerage on overloaded PQs
263
+ 50: kill by JEDI
264
+ 91: kill user jobs with prod role
265
+ keep_unmerged: set True not to cancel unmerged jobs when pmerge is killed.
266
+ job_sub_status: set job sub status if any
267
+ returns:
268
+ status code
269
+ 0: communication succeeded to the panda server
270
+ 255: communication failure
271
+ the list of clouds (or Nones if tasks are not yet assigned)
272
+ """
273
+
274
+ http_client = HttpClientV1()
275
+
276
+ url = f"{api_url_ssl_v1}/job/kill"
277
+ data = {"job_ids": job_ids}
278
+
279
+ if code:
280
+ data["code"] = code
281
+
282
+ kill_options = []
283
+ if keep_unmerged == True:
284
+ kill_options.append("keepUnmerged")
285
+ if job_sub_status:
286
+ kill_options.append(f"jobSubStatus={job_sub_status}")
287
+ if kill_options:
288
+ data["kill_options"] = kill_options
289
+
290
+ status, output = http_client.post(url, data)
291
+ return status, output
292
+
293
+
294
+ def reassign_jobs(job_ids):
295
+ """
296
+ Triggers reassignment of jobs.
297
+
298
+ args:
299
+ ids: the list of taskIDs
300
+ returns:
301
+ status code
302
+ 0: communication succeeded to the panda server
303
+ 255: communication failure
304
+ return code
305
+ True: request is processed
306
+ False: not processed
307
+
308
+ """
309
+ http_client = HttpClientV1()
310
+
311
+ url = f"{api_url_ssl_v1}/job/reassign"
312
+ data = {"job_ids": job_ids}
313
+ status, output = http_client.post(url, data)
314
+ return status, output
315
+
316
+
317
+ def job_stats_by_cloud(job_type=None):
318
+ """
319
+ Get job statistics by cloud. Used by panglia monitor in TRIUMF
320
+
321
+ args:
322
+ job_type: string with the type of jobs to consider
323
+ analysis: analysis jobs
324
+ production: production jobs
325
+ returns:
326
+ status code
327
+ 0: communication succeeded to the panda server
328
+ 255: communication failure
329
+ map of the number jobs per job status in each site
330
+
331
+ """
332
+ if job_type not in [None, "analysis", "production"]:
333
+ print("Invalid job type, must be one of [None, 'analysis', 'production'']")
334
+ return EC_Failed, "Invalid job type"
335
+
336
+ http_client = HttpClientV1()
337
+ url = f"{api_url_ssl_v1}/statistics/job_stats_by_cloud"
338
+
339
+ data = {}
340
+ if job_type:
341
+ data["type"] = job_type
342
+
343
+ status, output = http_client.get(url, data)
344
+ if status != 0 or not output.get("success"):
345
+ print(f"Failed to retrieve job_stats_by_cloud for {job_type}. Status: {status}, Output: {output}")
346
+ return status, output
347
+
348
+ statistics = output["data"]
349
+ ret = {}
350
+ for cloud, values in statistics.items():
351
+ if cloud not in ret:
352
+ # append cloud values (make a shallow copy to avoid mutating original)
353
+ ret[cloud] = dict(values)
354
+ else:
355
+ # sum statistics per status
356
+ for job_status, count in values.items():
357
+ ret[cloud][job_status] = ret[cloud].get(job_status, 0) + count
358
+
359
+ return 0, ret
360
+
361
+
362
+ # alias the old name to the new function for backwards compatibility
363
+ def getJobStatistics(sourcetype=None):
364
+ return job_stats_by_cloud(sourcetype)
365
+
366
+
367
+ def production_job_stats_by_cloud_and_processing_type():
368
+ """
369
+ Get job statistics by cloud and processing type. Used by panglia monitor in TRIUMF
370
+
371
+ returns:
372
+ status code
373
+ 0: communication succeeded to the panda server
374
+ 255: communication failure
375
+ map of the number jobs per job status in each site
376
+
377
+ """
378
+
379
+ http_client = HttpClientV1()
380
+ url = f"{api_url_ssl_v1}/statistics/production_job_stats_by_cloud_and_processing_type"
381
+ status, output = http_client.get(url, {})
382
+
383
+ if status != 0 or not output.get("success"):
384
+ print(f"Failed to retrieve production_job_stats_by_cloud_and_processing_type. Status: {status}, Output: {output}")
385
+ return status, output
386
+
387
+ statistics = output["data"]
388
+ aggregated = {}
389
+
390
+ for cloud, processing_type_map in statistics.items():
391
+ # ensure we work with a shallow copy of the incoming mappings
392
+ processing_type_map = dict(processing_type_map)
393
+
394
+ if cloud not in aggregated:
395
+ # copy nested structures to avoid mutating the original
396
+ aggregated[cloud] = {processing_type: dict(status_map) for processing_type, status_map in processing_type_map.items()}
397
+ continue
398
+
399
+ # merge into existing cloud entry
400
+ for processing_type, status_map in processing_type_map.items():
401
+ status_map = dict(status_map)
402
+ if processing_type not in aggregated[cloud]:
403
+ aggregated[cloud][processing_type] = status_map
404
+ continue
405
+
406
+ # sum counts per status
407
+ for status, count in status_map.items():
408
+ aggregated[cloud][processing_type][status] = aggregated[cloud][processing_type].get(status, 0) + count
409
+ return 0, aggregated
410
+
411
+
412
+ # alias the old name to the new function for backwards compatibility
413
+ def getJobStatisticsForBamboo(useMorePG=False):
414
+ return production_job_stats_by_cloud_and_processing_type()
415
+
416
+
417
+ def job_stats_by_site_and_resource_type(time_window=None):
418
+ """
419
+ Get job statistics per site and resource. Used by panglia monitor in TRIUMF
420
+
421
+ args:
422
+ time_window: to count number of jobs that finish/failed/cancelled for last N minutes. 12*60 by default
423
+ returns:
424
+ status code
425
+ 0: communication succeeded to the panda server
426
+ 255: communication failure
427
+ map of the number jobs per job status in each site and resource
428
+
429
+ """
430
+
431
+ http_client = HttpClientV1()
432
+ url = f"{api_url_ssl_v1}/statistics/job_stats_by_site_and_resource_type"
433
+ data = {}
434
+ if time_window:
435
+ data["time_window"] = time_window
436
+
437
+ status, output = http_client.get(url, data)
438
+
439
+ if status != 0 or not output.get("success"):
440
+ print(f"Failed to retrieve job_stats_by_site_and_resource_type. Status: {status}, Output: {output}")
441
+ return status, output
442
+
443
+ return 0, output.get("data")
444
+
445
+
446
+ # alias the old name to the new function for backwards compatibility
447
+ def getJobStatisticsPerSiteResource(timeWindow=None):
448
+ return job_stats_by_site_and_resource_type(timeWindow)
449
+
450
+
451
+ def job_stats_by_site_share_and_resource_type(time_window=None):
452
+ """
453
+ Get job statistics per site, label, and resource
454
+
455
+ args:
456
+ timeWindow: to count number of jobs that finish/failed/cancelled for last N minutes. 12*60 by default
457
+ returns:
458
+ status code
459
+ 0: communication succeeded to the panda server
460
+ 255: communication failure
461
+ map of the number jobs per job status in each site and resource
462
+
463
+ """
464
+
465
+ http_client = HttpClientV1()
466
+ url = f"{api_url_ssl_v1}/statistics/job_stats_by_site_share_and_resource_type"
467
+ data = {}
468
+ if time_window:
469
+ data["time_window"] = time_window
470
+
471
+ status, output = http_client.get(url, data)
472
+
473
+ if status != 0 or not output.get("success"):
474
+ print(f"Failed to retrieve job_stats_by_site_share_and_resource_type. Status: {status}, Output: {output}")
475
+ return status, output
476
+
477
+ return 0, output.get("data")
478
+
479
+
480
+ # alias the old name to the new function for backwards compatibility
481
+ def get_job_statistics_per_site_label_resource(time_window=None):
482
+ return job_stats_by_site_share_and_resource_type(time_window)
483
+
484
+
485
+ def get_site_specs(site_type=None):
486
+ """
487
+ Get list of site specifications. Used by panglia monitor in TRIUMF
488
+
489
+ args:
490
+ site_type: type of sites
491
+ all: all sites
492
+ None: defaults to analysis sites
493
+ analysis: analysis sites
494
+ production: production sites
495
+ unified: unified sites
496
+ returns:
497
+ status code
498
+ 0: communication succeeded to the panda server
499
+ 255: communication failure
500
+ map of site and attributes
501
+
502
+ """
503
+ http_client = HttpClientV1()
504
+ url = f"{api_url_ssl_v1}/metaconfig/get_site_specs"
505
+ if site_type not in [None, "all", "analysis", "production", "unified"]:
506
+ return EC_Failed, "Invalid site type"
507
+
508
+ data = {}
509
+ if site_type:
510
+ data = {"type": site_type}
511
+
512
+ status, output = http_client.get(url, data)
513
+
514
+ if status != 0 or not output.get("success"):
515
+ print(f"Failed to retrieve get_site_specs. Status: {status}, Output: {output}")
516
+ return status, output
517
+
518
+ return 0, output.get("data")
519
+
520
+
521
+ # alias the old name to the new function for backwards compatibility
522
+ def getSiteSpecs(siteType=None):
523
+ return get_site_specs(siteType)
524
+
525
+
526
+ def register_cache_file(user_name: str, file_name: str, file_size: int, checksum: str):
527
+ """
528
+ Register information about the input sandbox that is being stored in PanDA cache
529
+
530
+ args:
531
+ user_name: the name of the user
532
+ file_name: the file name
533
+ file_size: the file size
534
+ checksum: md5sum of the file
535
+ returns:
536
+ status code
537
+ 0: communication succeeded to the panda server
538
+ else: communication failure
539
+
540
+ """
541
+ http_client = HttpClientV1()
542
+
543
+ url = f"{api_url_ssl_v1}/file_server/register_cache_file"
544
+
545
+ data = {
546
+ "user_name": user_name,
547
+ "file_name": file_name,
548
+ "file_size": file_size,
549
+ "checksum": str(checksum),
550
+ }
551
+ return http_client.post(url, data)
552
+
553
+
554
+ def put_file(file):
555
+ """
556
+ Upload input sandbox to PanDA cache
557
+
558
+ args:
559
+ file: the file name
560
+ returns:
561
+ status code
562
+ 0: communication succeeded to the panda server
563
+ else: communication failure
564
+
565
+ """
566
+
567
+ http_client = HttpClientV1()
568
+ url = f"{api_url_ssl_v1}/file_server/upload_cache_file"
569
+ data = {"file": file}
570
+ return http_client.post_files(url, data)
571
+
572
+
573
+ def touch_file(source_url, file_name):
574
+ http_client = HttpClientV1()
575
+ # Note the special construction of the URL here, since it is not going through the api_url_ssl_v1,
576
+ # but directly to the source_url pointing at the concrete instance provided
577
+ url = f"{source_url}/api/v1/file_server/touch_cache_file"
578
+ data = {"file_name": file_name}
579
+ return http_client.post(url, data)
580
+
581
+
582
+ def insertTaskParams(taskParams):
583
+ """
584
+ Insert task parameters
585
+
586
+ args:
587
+ taskParams: a dictionary of task parameters
588
+ returns:
589
+ status code
590
+ 0: communication succeeded to the panda server
591
+ 255: communication failure
592
+ tuple of return code and JediTaskID
593
+ True: request is processed
594
+ False: not processed
595
+ """
596
+ # serialize
597
+ taskParamsStr = json.dumps(taskParams)
598
+
599
+ http_client = HttpClient()
600
+
601
+ # execute
602
+ url = f"{baseURLSSL}/insertTaskParams"
603
+ data = {"taskParams": taskParamsStr}
604
+ status, output = http_client.post(url, data)
605
+ try:
606
+ return status, pickle_loads(output)
607
+ except Exception:
608
+ error_type, error_value = sys.exc_info()[:2]
609
+ error_str = f"ERROR insertTaskParams : {error_type} {error_value}"
610
+ return EC_Failed, f"{output}\n{error_str}"
611
+
612
+
613
+ def kill_task(task_id, broadcast=False):
614
+ """
615
+ Kill a task
616
+
617
+ args:
618
+ task_id: task ID of the task to be killed
619
+ broadcast: True to push the message to the pilot subscribing the MB
620
+ returns:
621
+ status code
622
+ 0: communication succeeded to the panda server
623
+ 255: communication failure
624
+ tuple of return code and diagnostic message
625
+ 0: request is registered
626
+ 1: server error
627
+ 2: task not found
628
+ 3: permission denied
629
+ 4: irrelevant task status
630
+ 100: non SSL connection
631
+ 101: irrelevant taskID
632
+ """
633
+
634
+ http_client = HttpClientV1()
635
+
636
+ url = f"{api_url_ssl_v1}/task/kill"
637
+ data = {"task_id": task_id, "broadcast": broadcast}
638
+
639
+ status, output = http_client.post(url, data)
640
+
641
+ return status, output
642
+
643
+
644
+ def finish_task(task_id, soft=False, broadcast=False):
645
+ """
646
+ Finish a task
647
+
648
+ args:
649
+ jediTaskID: jediTaskID of the task to be finished
650
+ soft: If True, new jobs are not generated and the task is
651
+ finished once all remaining jobs are done.
652
+ If False, all remaining jobs are killed and then the
653
+ task is finished
654
+ broadcast: True to push the message to the pilot subscribing the MB
655
+ returns:
656
+ status code
657
+ 0: communication succeeded to the panda server
658
+ 255: communication failure
659
+ tuple of return code and diagnostic message
660
+ 0: request is registered
661
+ 1: server error
662
+ 2: task not found
663
+ 3: permission denied
664
+ 4: irrelevant task status
665
+ 100: non SSL connection
666
+ 101: irrelevant taskID
667
+ """
668
+ http_client = HttpClientV1()
669
+
670
+ url = f"{api_url_ssl_v1}/task/finish"
671
+ data = {"task_id": task_id, "soft": soft, "broadcast": broadcast}
672
+
673
+ status, output = http_client.post(url, data)
674
+
675
+ return status, output
676
+
677
+
678
+ def reassignTaskToSite(jediTaskID, site, mode=None):
679
+ """
680
+ Reassign a task to a site. Existing jobs are killed and new jobs are generated at the site
681
+
682
+ args:
683
+ jediTaskID: jediTaskID of the task to be reassigned
684
+ site: the site name where the task is reassigned
685
+ mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
686
+ returns:
687
+ status code
688
+ 0: communication succeeded to the panda server
689
+ 255: communication failure
690
+ tuple of return code and diagnostic message
691
+ 0: request is registered
692
+ 1: server error
693
+ 2: task not found
694
+ 3: permission denied
695
+ 4: irrelevant task status
696
+ 100: non SSL connection
697
+ 101: irrelevant taskID
698
+ """
699
+ maxSite = 60
700
+ if site is not None and len(site) > maxSite:
701
+ return EC_Failed, f"site parameter is too long > {maxSite}chars"
702
+
703
+ http_client = HttpClient()
704
+
705
+ # execute
706
+ url = f"{baseURLSSL}/reassignTask"
707
+ data = {"jediTaskID": jediTaskID, "site": site}
708
+ if mode is not None:
709
+ data["mode"] = mode
710
+ status, output = http_client.post(url, data)
711
+ try:
712
+ return status, pickle_loads(output)
713
+ except Exception:
714
+ error_type, error_value = sys.exc_info()[:2]
715
+ error_str = f"ERROR reassignTaskToSite : {error_type} {error_value}"
716
+ return EC_Failed, f"{output}\n{error_str}"
717
+
718
+
719
+ def reassignTaskToCloud(jediTaskID, cloud, mode=None):
720
+ """
721
+ Reassign a task to a cloud. Existing jobs are killed and new jobs are generated in the cloud
722
+
723
+ args:
724
+ jediTaskID: jediTaskID of the task to be reassigned
725
+ cloud: the cloud name where the task is reassigned
726
+ mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
727
+ returns:
728
+ status code
729
+ 0: communication succeeded to the panda server
730
+ 255: communication failure
731
+ tuple of return code and diagnostic message
732
+ 0: request is registered
733
+ 1: server error
734
+ 2: task not found
735
+ 3: permission denied
736
+ 4: irrelevant task status
737
+ 100: non SSL connection
738
+ 101: irrelevant taskID
739
+ """
740
+
741
+ http_client = HttpClient()
742
+
743
+ # execute
744
+ url = f"{baseURLSSL}/reassignTask"
745
+ data = {"jediTaskID": jediTaskID, "cloud": cloud}
746
+ if mode is not None:
747
+ data["mode"] = mode
748
+ status, output = http_client.post(url, data)
749
+ try:
750
+ return status, pickle_loads(output)
751
+ except Exception:
752
+ error_type, error_value = sys.exc_info()[:2]
753
+ error_str = f"ERROR reassignTaskToCloud : {error_type} {error_value}"
754
+ return EC_Failed, f"{output}\n{error_str}"
755
+
756
+
757
+ def reassignTaskToNucleus(jediTaskID, nucleus, mode=None):
758
+ """
759
+ Reassign a task to a nucleus. Existing jobs are killed and new jobs are generated in the cloud
760
+
761
+ args:
762
+ jediTaskID: jediTaskID of the task to be reassigned
763
+ nucleus: the nucleus name where the task is reassigned
764
+ mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
765
+ returns:
766
+ status code
767
+ 0: communication succeeded to the panda server
768
+ 255: communication failure
769
+ tuple of return code and diagnostic message
770
+ 0: request is registered
771
+ 1: server error
772
+ 2: task not found
773
+ 3: permission denied
774
+ 4: irrelevant task status
775
+ 100: non SSL connection
776
+ 101: irrelevant taskID
777
+ """
778
+
779
+ http_client = HttpClient()
780
+
781
+ # execute
782
+ url = f"{baseURLSSL}/reassignTask"
783
+ data = {"jediTaskID": jediTaskID, "nucleus": nucleus}
784
+ if mode is not None:
785
+ data["mode"] = mode
786
+ status, output = http_client.post(url, data)
787
+ try:
788
+ return status, pickle_loads(output)
789
+ except Exception:
790
+ error_type, error_value = sys.exc_info()[:2]
791
+ error_str = f"ERROR reassignTaskToCloud : {error_type} {error_value}"
792
+ return EC_Failed, f"{output}\n{error_str}"
793
+
794
+
795
+ def uploadLog(log_string, log_file_name):
796
+ """
797
+ Upload log
798
+
799
+ args:
800
+ logStr: log message
801
+ log_file_name: name of log file
802
+ returns:
803
+ status code
804
+ 0: communication succeeded to the panda server
805
+ else: communication failure
806
+
807
+ """
808
+
809
+ http_client = HttpClientV1()
810
+
811
+ # write log to a tmp file
812
+ fh = tempfile.NamedTemporaryFile(delete=False)
813
+ gfh = gzip.open(fh.name, mode="wb")
814
+ if sys.version_info[0] >= 3:
815
+ log_string = log_string.encode("utf-8")
816
+ gfh.write(log_string)
817
+ gfh.close()
818
+ # execute
819
+ url = f"{api_url_ssl_v1}/file_server/upload_jedi_log"
820
+
821
+ # sometimes the destination file name (=logFileName) comes as an integer (e.g. a JEDI task ID) and it needs to be converted to a string
822
+ log_file_name = str(log_file_name)
823
+ data = {"file": (log_file_name, fh.name)}
824
+ return_value = http_client.post_files(url, data, encoding="gzip")
825
+ os.unlink(fh.name)
826
+ return return_value
827
+
828
+
829
+ def changeTaskPriority(jediTaskID, newPriority):
830
+ """
831
+ Change the task priority
832
+
833
+ args:
834
+ jediTaskID: jediTaskID of the task to change the priority
835
+ newPriority: new task priority
836
+ returns:
837
+ status code
838
+ 0: communication succeeded to the panda server
839
+ 255: communication failure
840
+ return code
841
+ 0: unknown task
842
+ 1: succeeded
843
+ None: database error
844
+ """
845
+
846
+ http_client = HttpClient()
847
+
848
+ # execute
849
+ url = f"{baseURLSSL}/changeTaskPriority"
850
+ data = {"jediTaskID": jediTaskID, "newPriority": newPriority}
851
+ status, output = http_client.post(url, data)
852
+ try:
853
+ return status, pickle_loads(output)
854
+ except Exception:
855
+ error_type, error_value = sys.exc_info()[:2]
856
+ error_str = f"ERROR changeTaskPriority : {error_type} {error_value}"
857
+ return EC_Failed, f"{output}\n{error_str}"
858
+
859
+
860
+ def set_debug_mode(job_id, mode):
861
+ """
862
+ Turn debug mode for a job on/off
863
+
864
+ args:
865
+ job_id: job_id of the job
866
+ mode: True to turn it on. Oppositely, False
867
+ returns:
868
+ status code
869
+ 0: communication succeeded to the panda server
870
+ another: communication failure
871
+ error message
872
+ """
873
+
874
+ http_client = HttpClientV1()
875
+
876
+ url = f"{api_url_ssl_v1}/job/set_debug_mode"
877
+ data = {"job_id": job_id, "mode": mode}
878
+
879
+ status, output = http_client.post(url, data)
880
+
881
+ return status, output
882
+
883
+
884
+ def retry_task(task_id, no_child_retry=False, discard_events=False, disable_staging_mode=False, keep_gshare_priority=False):
885
+ """
886
+ Retry a task
887
+
888
+ args:
889
+ task_id: jediTaskID of the task to retry
890
+ no_child_retry: True not to retry child tasks
891
+ discard_events: discard events
892
+ disable_staging_mode: disable staging mode
893
+ keep_gshare_priority: keep current gshare and priority
894
+ returns:
895
+ status code
896
+ 0: communication succeeded to the panda server
897
+ 255: communication failure
898
+ tuple of return code and diagnostic message
899
+ 0: request is registered
900
+ 1: server error
901
+ 2: task not found
902
+ 3: permission denied
903
+ 4: irrelevant task status
904
+ 100: non SSL connection
905
+ 101: irrelevant taskID
906
+ """
907
+
908
+ http_client = HttpClientV1()
909
+ url = f"{api_url_ssl_v1}/task/retry"
910
+
911
+ data = {"task_id": task_id}
912
+ if no_child_retry:
913
+ data["no_child_retry"] = True
914
+ if discard_events:
915
+ data["discard_events"] = True
916
+ if disable_staging_mode:
917
+ data["disable_staging_mode"] = True
918
+ if keep_gshare_priority:
919
+ data["keep_gshare_priority"] = True
920
+
921
+ status, output = http_client.post(url, data)
922
+ return status, output
923
+
924
+
925
+ def reload_input(task_id):
926
+ """
927
+ Reload the input for a task
928
+
929
+ args:
930
+ jediTaskID: jediTaskID of the task to retry
931
+ returns:
932
+ status code
933
+ 0: communication succeeded to the panda server
934
+ 255: communication failure
935
+ tuple of return code and diagnostic message
936
+ 0: request is registered
937
+ 1: server error
938
+ 2: task not found
939
+ 3: permission denied
940
+ 4: irrelevant task status
941
+ 100: non SSL connection
942
+ 101: irrelevant taskID
943
+ """
944
+ http_client = HttpClientV1()
945
+
946
+ url = f"{api_url_ssl_v1}/task/reload_input"
947
+ data = {"task_id": task_id}
948
+
949
+ status, output = http_client.post(url, data)
950
+
951
+ return status, output
952
+
953
+
954
+ def changeTaskWalltime(jediTaskID, wallTime):
955
+ """
956
+ Change task walltime
957
+
958
+ args:
959
+ jediTaskID: jediTaskID of the task to change the priority
960
+ wallTime: new walltime for the task
961
+ returns:
962
+ status code
963
+ 0: communication succeeded to the panda server
964
+ 255: communication failure
965
+ return code
966
+ 0: unknown task
967
+ 1: succeeded
968
+ None: database error
969
+ """
970
+
971
+ http_client = HttpClient()
972
+
973
+ # execute
974
+ url = f"{baseURLSSL}/changeTaskAttributePanda"
975
+ data = {"jediTaskID": jediTaskID, "attrName": "wallTime", "attrValue": wallTime}
976
+ status, output = http_client.post(url, data)
977
+ try:
978
+ return status, pickle_loads(output)
979
+ except Exception:
980
+ error_type, error_value = sys.exc_info()[:2]
981
+ error_str = f"ERROR changeTaskWalltime : {error_type} {error_value}"
982
+ return EC_Failed, f"{output}\n{error_str}"
983
+
984
+
985
+ def changeTaskCputime(jediTaskID, cpuTime):
986
+ """
987
+ Change task CPU time
988
+
989
+ args:
990
+ jediTaskID: jediTaskID of the task to change the priority
991
+ cpuTime: new cputime for the task
992
+ returns:
993
+ status code
994
+ 0: communication succeeded to the panda server
995
+ 255: communication failure
996
+ return code
997
+ 0: unknown task
998
+ 1: succeeded
999
+ None: database error
1000
+ """
1001
+
1002
+ http_client = HttpClient()
1003
+
1004
+ # execute
1005
+ url = f"{baseURLSSL}/changeTaskAttributePanda"
1006
+ data = {"jediTaskID": jediTaskID, "attrName": "cpuTime", "attrValue": cpuTime}
1007
+ status, output = http_client.post(url, data)
1008
+ try:
1009
+ return status, pickle_loads(output)
1010
+ except Exception:
1011
+ error_type, error_value = sys.exc_info()[:2]
1012
+ error_str = f"ERROR changeTaskCputime : {error_type} {error_value}"
1013
+ return EC_Failed, f"{output}\n{error_str}"
1014
+
1015
+
1016
+ def changeTaskRamCount(jediTaskID, ramCount):
1017
+ """
1018
+ Change task RAM count
1019
+
1020
+ args:
1021
+ jediTaskID: jediTaskID of the task to change the priority
1022
+ ramCount: new ramCount for the task
1023
+ returns:
1024
+ status code
1025
+ 0: communication succeeded to the panda server
1026
+ 255: communication failure
1027
+ return code
1028
+ 0: unknown task
1029
+ 1: succeeded
1030
+ None: database error
1031
+ """
1032
+
1033
+ http_client = HttpClient()
1034
+
1035
+ # execute
1036
+ url = f"{baseURLSSL}/changeTaskAttributePanda"
1037
+ data = {"jediTaskID": jediTaskID, "attrName": "ramCount", "attrValue": ramCount}
1038
+ status, output = http_client.post(url, data)
1039
+ try:
1040
+ return status, pickle_loads(output)
1041
+ except Exception:
1042
+ error_type, error_value = sys.exc_info()[:2]
1043
+ error_str = f"ERROR changeTaskRamCount : {error_type} {error_value}"
1044
+ return EC_Failed, f"{output}\n{error_str}"
1045
+
1046
+
1047
+ def changeTaskAttribute(jediTaskID, attrName, attrValue):
1048
+ """
1049
+ Change task attribute
1050
+
1051
+ args:
1052
+ jediTaskID: jediTaskID of the task to change the attribute
1053
+ attrName: attribute name
1054
+ attrValue: new value for the attribute
1055
+ returns:
1056
+ status code
1057
+ 0: communication succeeded to the panda server
1058
+ 255: communication failure
1059
+ return: a tuple of return code and message
1060
+ 0: unknown task
1061
+ 1: succeeded
1062
+ 2: disallowed to update the attribute
1063
+ None: database error
1064
+ """
1065
+
1066
+ http_client = HttpClient()
1067
+
1068
+ # execute
1069
+ url = f"{baseURLSSL}/changeTaskAttributePanda"
1070
+ data = {"jediTaskID": jediTaskID, "attrName": attrName, "attrValue": attrValue}
1071
+ status, output = http_client.post(url, data)
1072
+ try:
1073
+ return status, pickle_loads(output)
1074
+ except Exception:
1075
+ error_type, error_value = sys.exc_info()[:2]
1076
+ error_str = f"ERROR changeTaskAttributePanda : {error_type} {error_value}"
1077
+ return EC_Failed, f"{output}\n{error_str}"
1078
+
1079
+
1080
+ def changeTaskSplitRule(jediTaskID, ruleName, ruleValue):
1081
+ """
1082
+ Change split rule fo task
1083
+
1084
+ args:
1085
+ jediTaskID: jediTaskID of the task to change the rule
1086
+ ruleName: rule name
1087
+ ruleValue: new value for the rule
1088
+ returns:
1089
+ status code
1090
+ 0: communication succeeded to the panda server
1091
+ 255: communication failure
1092
+ return: a tuple of return code and message
1093
+ 0: unknown task
1094
+ 1: succeeded
1095
+ 2: disallowed to update the attribute
1096
+ None: database error
1097
+ """
1098
+
1099
+ http_client = HttpClient()
1100
+
1101
+ # execute
1102
+ url = f"{baseURLSSL}/changeTaskSplitRulePanda"
1103
+ data = {"jediTaskID": jediTaskID, "attrName": ruleName, "attrValue": ruleValue}
1104
+ status, output = http_client.post(url, data)
1105
+ try:
1106
+ return status, pickle_loads(output)
1107
+ except Exception:
1108
+ error_type, error_value = sys.exc_info()[:2]
1109
+ error_str = f"ERROR changeTaskSplitRule : {error_type} {error_value}"
1110
+ return EC_Failed, f"{output}\n{error_str}"
1111
+
1112
+
1113
+ def pauseTask(jediTaskID):
1114
+ """
1115
+ Pause task
1116
+
1117
+ args:
1118
+ jediTaskID: jediTaskID of the task to pause
1119
+ returns:
1120
+ status code
1121
+ 0: communication succeeded to the panda server
1122
+ 255: communication failure
1123
+ tuple of return code and diagnostic message
1124
+ 0: request is registered
1125
+ 1: server error
1126
+ 2: task not found
1127
+ 3: permission denied
1128
+ 4: irrelevant task status
1129
+ 100: non SSL connection
1130
+ 101: irrelevant taskID
1131
+ """
1132
+
1133
+ http_client = HttpClient()
1134
+
1135
+ # execute
1136
+ url = f"{baseURLSSL}/pauseTask"
1137
+ data = {"jediTaskID": jediTaskID}
1138
+ status, output = http_client.post(url, data)
1139
+ try:
1140
+ return status, pickle_loads(output)
1141
+ except Exception:
1142
+ error_type, error_value = sys.exc_info()[:2]
1143
+ error_str = f"ERROR pauseTask : {error_type} {error_value}"
1144
+ return EC_Failed, f"{output}\n{error_str}"
1145
+
1146
+
1147
+ def resumeTask(jediTaskID):
1148
+ """
1149
+ Resume task
1150
+
1151
+ args:
1152
+ jediTaskID: jediTaskID of the task to release
1153
+ returns:
1154
+ status code
1155
+ 0: communication succeeded to the panda server
1156
+ 255: communication failure
1157
+ tuple of return code and diagnostic message
1158
+ 0: request is registered
1159
+ 1: server error
1160
+ 2: task not found
1161
+ 3: permission denied
1162
+ 4: irrelevant task status
1163
+ 100: non SSL connection
1164
+ 101: irrelevant taskID
1165
+ """
1166
+
1167
+ http_client = HttpClient()
1168
+
1169
+ # execute
1170
+ url = f"{baseURLSSL}/resumeTask"
1171
+ data = {"jediTaskID": jediTaskID}
1172
+ status, output = http_client.post(url, data)
1173
+ try:
1174
+ return status, pickle_loads(output)
1175
+ except Exception:
1176
+ error_type, error_value = sys.exc_info()[:2]
1177
+ error_str = f"ERROR resumeTask : {error_type} {error_value}"
1178
+ return EC_Failed, f"{output}\n{error_str}"
1179
+
1180
+
1181
+ def avalancheTask(jediTaskID):
1182
+ """
1183
+ Force avalanche for task
1184
+
1185
+ args:
1186
+ jediTaskID: jediTaskID of the task to avalanche
1187
+ returns:
1188
+ status code
1189
+ 0: communication succeeded to the panda server
1190
+ 255: communication failure
1191
+ tuple of return code and diagnostic message
1192
+ 0: request is registered
1193
+ 1: server error
1194
+ 2: task not found
1195
+ 3: permission denied
1196
+ 4: irrelevant task status
1197
+ 100: non SSL connection
1198
+ 101: irrelevant taskID
1199
+ """
1200
+
1201
+ http_client = HttpClient()
1202
+
1203
+ # execute
1204
+ url = f"{baseURLSSL}/avalancheTask"
1205
+ data = {"jediTaskID": jediTaskID}
1206
+ status, output = http_client.post(url, data)
1207
+ try:
1208
+ return status, pickle_loads(output)
1209
+ except Exception:
1210
+ error_type, error_value = sys.exc_info()[:2]
1211
+ error_str = f"ERROR resumeTask : {error_type} {error_value}"
1212
+ return EC_Failed, f"{output}\n{error_str}"
1213
+
1214
+
1215
+ def increaseAttemptNr(jediTaskID, increase):
1216
+ """
1217
+ Change task priority
1218
+
1219
+ args:
1220
+ jediTaskID: jediTaskID of the task to increase attempt numbers
1221
+ increase: increase for attempt numbers
1222
+ returns:
1223
+ status code
1224
+ 0: communication succeeded to the panda server
1225
+ 255: communication failure
1226
+ return code
1227
+ 0: succeeded
1228
+ 1: unknown task
1229
+ 2: invalid task status
1230
+ 3: permission denied
1231
+ 4: wrong parameter
1232
+ None: database error
1233
+ """
1234
+
1235
+ http_client = HttpClient()
1236
+
1237
+ # execute
1238
+ url = f"{baseURLSSL}/increaseAttemptNrPanda"
1239
+ data = {"jediTaskID": jediTaskID, "increasedNr": increase}
1240
+ status, output = http_client.post(url, data)
1241
+ try:
1242
+ return status, pickle_loads(output)
1243
+ except Exception:
1244
+ error_type, error_value = sys.exc_info()[:2]
1245
+ error_str = f"ERROR increaseAttemptNr : {error_type} {error_value}"
1246
+ return EC_Failed, f"{output}\n{error_str}"
1247
+
1248
+
1249
+ def killUnfinishedJobs(jediTaskID, code=None, useMailAsID=False):
1250
+ """
1251
+ Kill unfinished jobs in a task. Normal users can kill only their own jobs.
1252
+ People with production VOMS role can kill any jobs.
1253
+ Running jobs are killed when next heartbeat comes from the pilot.
1254
+ Set code=9 if running jobs need to be killed immediately.
1255
+
1256
+ args:
1257
+ jediTaskID: the taskID of the task
1258
+ code: specify why the jobs are killed
1259
+ 2: expire
1260
+ 3: aborted
1261
+ 4: expire in waiting
1262
+ 7: retry by server
1263
+ 8: re-brokerage
1264
+ 9: force kill
1265
+ 50: kill by JEDI
1266
+ 91: kill user jobs with prod role
1267
+ useMailAsID: obsolete
1268
+ returns:
1269
+ status code
1270
+ 0: communication succeeded to the panda server
1271
+ 255: communication failure
1272
+ the list of clouds (or Nones if tasks are not yet assigned)
1273
+ """
1274
+
1275
+ http_client = HttpClient()
1276
+
1277
+ # execute
1278
+ url = f"{baseURLSSL}/killUnfinishedJobs"
1279
+ data = {"jediTaskID": jediTaskID, "code": code, "useMailAsID": useMailAsID}
1280
+ status, output = http_client.post(url, data)
1281
+ try:
1282
+ return status, pickle_loads(output)
1283
+ except Exception:
1284
+ error_type, error_value, _ = sys.exc_info()
1285
+ error_str = f"ERROR killUnfinishedJobs : {error_type} {error_value}"
1286
+ print(error_str)
1287
+ return EC_Failed, f"{output}\n{error_str}"
1288
+
1289
+
1290
+ def triggerTaskBrokerage(jediTaskID):
1291
+ """
1292
+ Trigger task brokerage
1293
+
1294
+ args:
1295
+ jediTaskID: jediTaskID of the task to change the attribute
1296
+ returns:
1297
+ status code
1298
+ 0: communication succeeded to the panda server
1299
+ 255: communication failure
1300
+ return: a tuple of return code and message
1301
+ 0: unknown task
1302
+ 1: succeeded
1303
+ None: database error
1304
+ """
1305
+
1306
+ http_client = HttpClient()
1307
+
1308
+ # execute
1309
+ url = f"{baseURLSSL}/changeTaskModTimePanda"
1310
+ data = {"jediTaskID": jediTaskID, "diffValue": -12}
1311
+ status, output = http_client.post(url, data)
1312
+ try:
1313
+ return status, pickle_loads(output)
1314
+ except Exception:
1315
+ error_type, error_value = sys.exc_info()[:2]
1316
+ error_str = f"ERROR triggerTaskBrokerage : {error_type} {error_value}"
1317
+ return EC_Failed, f"{output}\n{error_str}"
1318
+
1319
+
1320
+ def getPandaIDsWithTaskID(jediTaskID):
1321
+ """
1322
+ Get PanDA IDs with TaskID
1323
+
1324
+ args:
1325
+ jediTaskID: jediTaskID of the task to get lit of PanDA IDs
1326
+ returns:
1327
+ status code
1328
+ 0: communication succeeded to the panda server
1329
+ 255: communication failure
1330
+ the list of PanDA IDs
1331
+ """
1332
+
1333
+ http_client = HttpClient()
1334
+ # execute
1335
+ url = f"{baseURL}/getPandaIDsWithTaskID"
1336
+ data = {"jediTaskID": jediTaskID}
1337
+ status, output = http_client.post(url, data)
1338
+ try:
1339
+ return status, pickle_loads(output)
1340
+ except Exception:
1341
+ error_type, error_value, _ = sys.exc_info()
1342
+ error_str = f"ERROR getPandaIDsWithTaskID : {error_type} {error_value}"
1343
+ print(error_str)
1344
+ return EC_Failed, f"{output}\n{error_str}"
1345
+
1346
+
1347
+ def reactivateTask(jediTaskID, keep_attempt_nr=False, trigger_job_generation=False):
1348
+ """
1349
+ Reactivate task
1350
+
1351
+ args:
1352
+ jediTaskID: jediTaskID of the task to be reactivated
1353
+ keep_attempt_nr: not to reset attempt numbers when being reactivated
1354
+ trigger_job_generation: trigger job generation once being reactivated
1355
+ returns:
1356
+ status code
1357
+ 0: communication succeeded to the panda server
1358
+ 255: communication failure
1359
+ return: a tuple of return code and message
1360
+ 0: unknown task
1361
+ 1: succeeded
1362
+ None: database error
1363
+ """
1364
+
1365
+ http_client = HttpClient()
1366
+
1367
+ # execute
1368
+ url = f"{baseURLSSL}/reactivateTask"
1369
+ data = {"jediTaskID": jediTaskID}
1370
+ if keep_attempt_nr:
1371
+ data["keep_attempt_nr"] = True
1372
+ if trigger_job_generation:
1373
+ data["trigger_job_generation"] = True
1374
+ status, output = http_client.post(url, data)
1375
+ try:
1376
+ return status, pickle_loads(output)
1377
+ except Exception:
1378
+ error_type, error_value = sys.exc_info()[:2]
1379
+ error_str = f"ERROR reactivateTask : {error_type} {error_value}"
1380
+ return EC_Failed, f"{output}\n{error_str}"
1381
+
1382
+
1383
+ def getTaskStatus(jediTaskID):
1384
+ """
1385
+ Get task status for a particular task ID
1386
+
1387
+ args:
1388
+ jediTaskID: jediTaskID of the task to get lit of PanDA IDs
1389
+ returns:
1390
+ status code
1391
+ 0: communication succeeded to the panda server
1392
+ 255: communication failure
1393
+ the status string
1394
+ """
1395
+
1396
+ http_client = HttpClient()
1397
+ # execute
1398
+ url = f"{baseURL}/getTaskStatus"
1399
+ data = {"jediTaskID": jediTaskID}
1400
+ status, output = http_client.post(url, data)
1401
+ try:
1402
+ return status, pickle_loads(output)
1403
+ except Exception:
1404
+ error_type, error_value, _ = sys.exc_info()
1405
+ error_str = f"ERROR getTaskStatus : {error_type} {error_value}"
1406
+ print(error_str)
1407
+ return EC_Failed, f"{output}\n{error_str}"
1408
+
1409
+
1410
+ def reassignShare(jedi_task_ids, share, reassign_running=False):
1411
+ """
1412
+ Reassign specified tasks (and their jobs) to a new share
1413
+
1414
+ args:
1415
+ jedi_task_ids: task ids to act on
1416
+ share: share to be applied to jedi task ids
1417
+ returns:
1418
+ status code
1419
+ 0: communication succeeded to the panda server
1420
+ 255: communication failure
1421
+ return: a tuple of return code and message
1422
+ 1: logical error
1423
+ 0: success
1424
+ None: database error
1425
+ """
1426
+
1427
+ http_client = HttpClient()
1428
+
1429
+ jedi_task_ids_pickle = pickle_dumps(jedi_task_ids)
1430
+ change_running_pickle = pickle_dumps(reassign_running)
1431
+ # execute
1432
+ url = f"{baseURLSSL}/reassignShare"
1433
+ data = {
1434
+ "jedi_task_ids_pickle": jedi_task_ids_pickle,
1435
+ "share": share,
1436
+ "reassign_running": change_running_pickle,
1437
+ }
1438
+ status, output = http_client.post(url, data)
1439
+
1440
+ try:
1441
+ return status, pickle_loads(output)
1442
+ except Exception:
1443
+ error_type, error_value = sys.exc_info()[:2]
1444
+ error_str = f"ERROR reassignShare : {error_type} {error_value}"
1445
+ return EC_Failed, f"{output}\n{error_str}"
1446
+
1447
+
1448
+ def getTaskParamsMap(jediTaskID):
1449
+ """
1450
+ Get task parameter map for a certain task ID
1451
+
1452
+ args:
1453
+ jediTaskID: jediTaskID of the task to get taskParamsMap
1454
+ returns:
1455
+ status code
1456
+ 0: communication succeeded to the panda server
1457
+ 255: communication failure
1458
+ return: a tuple of return code and taskParamsMap
1459
+ 1: logical error
1460
+ 0: success
1461
+ None: database error
1462
+ """
1463
+
1464
+ http_client = HttpClient()
1465
+ # execute
1466
+ url = f"{baseURL}/getTaskParamsMap"
1467
+ data = {"jediTaskID": jediTaskID}
1468
+ status, output = http_client.post(url, data)
1469
+ try:
1470
+ return status, pickle_loads(output)
1471
+ except Exception:
1472
+ error_type, error_value, _ = sys.exc_info()
1473
+ error_str = f"ERROR getTaskParamsMap : {error_type} {error_value}"
1474
+ print(error_str)
1475
+ return EC_Failed, f"{output}\n{error_str}"
1476
+
1477
+
1478
+ def setNumSlotsForWP(pandaQueueName, numSlots, gshare=None, resourceType=None, validPeriod=None):
1479
+ """
1480
+ Set num slots for workload provisioning
1481
+
1482
+ args:
1483
+ pandaQueueName: Panda Queue name
1484
+ numSlots: the number of slots. 0 to dynamically set based on the number of starting jobs
1485
+ gshare: global share. None to set for any global share (default)
1486
+ resourceType: resource type. None to set for any resource type (default)
1487
+ validPeriod: How long the rule is valid in days. None if no expiration (default)
1488
+ returns:
1489
+ status code
1490
+ 0: communication succeeded to the panda server
1491
+ 255: communication failure
1492
+ tuple of return code and diagnostic message
1493
+ 0: succeeded
1494
+ 1: server error
1495
+ 100: non SSL connection
1496
+ 101: missing production role
1497
+ 102: type error for some parameters
1498
+ """
1499
+
1500
+ http_client = HttpClient()
1501
+
1502
+ # execute
1503
+ url = f"{baseURLSSL}/setNumSlotsForWP"
1504
+ data = {"pandaQueueName": pandaQueueName, "numSlots": numSlots}
1505
+ if gshare is not None:
1506
+ data["gshare"] = gshare
1507
+ if resourceType is not None:
1508
+ data["resourceType"] = resourceType
1509
+ if validPeriod is not None:
1510
+ data["validPeriod"] = validPeriod
1511
+ status, output = http_client.post(url, data)
1512
+ try:
1513
+ return status, json.loads(output)
1514
+ except Exception:
1515
+ error_type, error_value = sys.exc_info()[:2]
1516
+ error_str = f"ERROR setNumSlotsForWP : {error_type} {error_value}"
1517
+ return EC_Failed, f"{output}\n{error_str}"
1518
+
1519
+
1520
+ # enable jumbo jobs
1521
+ def enableJumboJobs(jediTaskID, totalJumboJobs=1, nJumboPerSite=1):
1522
+ """
1523
+ Enable jumbo jobs for a task
1524
+
1525
+ args:
1526
+ jediTaskID: jediTaskID of the task
1527
+ totalJumboJobs: The total number of active jumbo jobs produced for the task. Use 0 to disable jumbo jobs for the task
1528
+ nJumboPerSite: The number of active jumbo jobs per site
1529
+ returns:
1530
+ status code
1531
+ 0: communication succeeded to the panda server
1532
+ 255: communication failure
1533
+ tuple of return code and diagnostic message
1534
+ 0: succeeded
1535
+ 1: server error
1536
+ 100: non SSL connection
1537
+ 101: missing production role
1538
+ 102: type error for some parameters
1539
+ """
1540
+
1541
+ http_client = HttpClient()
1542
+
1543
+ # execute
1544
+ url = f"{baseURLSSL}/enableJumboJobs"
1545
+ data = {
1546
+ "jediTaskID": jediTaskID,
1547
+ "nJumboJobs": totalJumboJobs,
1548
+ "nJumboPerSite": nJumboPerSite,
1549
+ }
1550
+ status, output = http_client.post(url, data)
1551
+ try:
1552
+ return status, json.loads(output)
1553
+ except Exception:
1554
+ error_type, error_value = sys.exc_info()[:2]
1555
+ error_str = f"ERROR /enableJumboJobs : {error_type} {error_value}"
1556
+ return EC_Failed, f"{output}\n{error_str}"
1557
+
1558
+
1559
+ def sweepPQ(panda_queue, status_list, ce_list, submission_host_list):
1560
+ """
1561
+ Send a harvester command to panda server in order sweep a panda queue
1562
+
1563
+ args:
1564
+ panda_queue: panda queue name
1565
+ status_list: list with statuses to sweep, e.g. ['submitted']
1566
+ ce_list: list of CEs belonging to the site or 'ALL'
1567
+ submission_host_list: list of submission hosts this applies or 'ALL'
1568
+ returns:
1569
+ status code
1570
+ 0: communication succeeded to the panda server
1571
+ 255: communication failure
1572
+ return: a tuple of return code and message
1573
+ False: logical error
1574
+ True: success
1575
+ """
1576
+
1577
+ http_client = HttpClient()
1578
+
1579
+ panda_queue_json = json.dumps(panda_queue)
1580
+ status_list_json = json.dumps(status_list)
1581
+ ce_list_json = json.dumps(ce_list)
1582
+ submission_host_list_json = json.dumps(submission_host_list)
1583
+
1584
+ # execute
1585
+ url = f"{baseURLSSL}/sweepPQ"
1586
+ data = {
1587
+ "panda_queue": panda_queue_json,
1588
+ "status_list": status_list_json,
1589
+ "ce_list": ce_list_json,
1590
+ "submission_host_list": submission_host_list_json,
1591
+ }
1592
+ status, output = http_client.post(url, data)
1593
+
1594
+ try:
1595
+ return status, json.loads(output)
1596
+ except Exception:
1597
+ error_type, error_value = sys.exc_info()[:2]
1598
+ error_str = f"ERROR sweepPQ : {error_type} {error_value}"
1599
+ return EC_Failed, f"{output}\n{error_str}"
1600
+
1601
+
1602
+ def send_command_to_job(panda_id, command):
1603
+ """
1604
+ Send a command to a job
1605
+
1606
+ args:
1607
+ panda_id: PandaID of the job
1608
+ com: a command string passed to the pilot. max 250 chars
1609
+ returns:
1610
+ status code
1611
+ 0: communication succeeded to the panda server
1612
+ 255: communication failure
1613
+ return: a tuple of return code and message
1614
+ False: failed
1615
+ True: the command received
1616
+ """
1617
+
1618
+ http_client = HttpClientV1()
1619
+ url = f"{api_url_ssl_v1}/job/set_command"
1620
+ data = {"job_id": panda_id, "command": command}
1621
+
1622
+ status, output = http_client.post(url, data)
1623
+
1624
+ return status, output
1625
+
1626
+
1627
+ def get_banned_users():
1628
+ """
1629
+ Get list of banned users
1630
+
1631
+ returns:
1632
+ status code
1633
+ True: communication succeeded to the panda server
1634
+ False: communication failure
1635
+
1636
+
1637
+ """
1638
+
1639
+ http_client = HttpClientV1()
1640
+ url = f"{api_url_ssl_v1}/metaconfig/get_banned_users"
1641
+
1642
+ status, output = http_client.get(url, {})
1643
+ if status != 0:
1644
+ return False, f"bad response: {output}"
1645
+
1646
+ success = output["success"]
1647
+ message = output.get("message", "")
1648
+ data = output.get("data", None)
1649
+
1650
+ if success:
1651
+ return True, data
1652
+ else:
1653
+ return False, f"error message: {message}"
1654
+
1655
+
1656
+ def release_task(jedi_task_id):
1657
+ """
1658
+ Release task from staging
1659
+
1660
+ args:
1661
+ jedi_task_id: jediTaskID of the task to avalanche
1662
+ returns:
1663
+ status code
1664
+ 0: communication succeeded to the panda server
1665
+ 255: communication failure
1666
+ tuple of return code and diagnostic message
1667
+ 0: request is registered
1668
+ 1: server error
1669
+ 2: task not found
1670
+ 3: permission denied
1671
+ 4: irrelevant task status
1672
+ 100: non SSL connection
1673
+ 101: irrelevant taskID
1674
+ """
1675
+
1676
+ http_client = HttpClient()
1677
+
1678
+ # execute
1679
+ url = f"{baseURLSSL}/release_task"
1680
+ data = {"jedi_task_id": jedi_task_id}
1681
+ status, output = http_client.post(url, data)
1682
+ try:
1683
+ return status, json.loads(output)
1684
+ except Exception as e:
1685
+ error_str = f"ERROR release_task : failed with {str(e)}"
1686
+ return EC_Failed, f"{output}\n{error_str}"