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,895 @@
1
+ import datetime
2
+ import os
3
+ import sys
4
+ import time
5
+ import traceback
6
+ from typing import List
7
+
8
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
9
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
10
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
11
+
12
+ from pandaserver.api.v1.common import (
13
+ TimedMethod,
14
+ generate_response,
15
+ get_dn,
16
+ has_production_role,
17
+ request_validation,
18
+ )
19
+ from pandaserver.brokerage.SiteMapper import SiteMapper
20
+ from pandaserver.config import panda_config
21
+ from pandaserver.dataservice.adder_gen import AdderGen
22
+ from pandaserver.jobdispatcher import Protocol
23
+ from pandaserver.srvcore import CoreUtils
24
+ from pandaserver.srvcore.CoreUtils import normalize_cpu_model
25
+ from pandaserver.srvcore.panda_request import PandaRequest
26
+ from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
27
+
28
+ _logger = PandaLogger().getLogger("api_pilot")
29
+ pilot_logger = PandaLogger().getLogger("PilotRequests")
30
+
31
+
32
+ global_task_buffer = None
33
+ global_site_mapper_cache = None
34
+
35
+ VALID_JOB_STATES = ["running", "failed", "finished", "holding", "starting", "transferring"]
36
+
37
+
38
+ def init_task_buffer(task_buffer: TaskBuffer) -> None:
39
+ """
40
+ Initialize the task buffer. This method needs to be called before any other method in this module.
41
+ """
42
+ global global_task_buffer
43
+ global_task_buffer = task_buffer
44
+
45
+ global global_site_mapper_cache
46
+ global_site_mapper_cache = CoreUtils.CachedObject("site_mapper", 60 * 10, _get_site_mapper, _logger)
47
+
48
+
49
+ def _get_site_mapper():
50
+ return True, SiteMapper(global_task_buffer)
51
+
52
+
53
+ @request_validation(_logger, secure=True, request_method="POST")
54
+ def acquire_jobs(
55
+ req: PandaRequest,
56
+ site_name: str,
57
+ timeout: int = 60,
58
+ memory: int = None,
59
+ disk_space: int = None,
60
+ prod_source_label: str = None,
61
+ node: str = None,
62
+ computing_element: str = None,
63
+ prod_user_id: str = None,
64
+ get_proxy_key: str = None,
65
+ task_id: int = None,
66
+ n_jobs: int = 1,
67
+ background: bool = None,
68
+ resource_type: str = None,
69
+ harvester_id: str = None,
70
+ worker_id: int = None,
71
+ scheduler_id: str = None,
72
+ job_type: str = None,
73
+ via_topic: bool = None,
74
+ remaining_time=None,
75
+ ) -> dict:
76
+ """
77
+ Acquire jobs
78
+
79
+ Acquire jobs for the pilot. The jobs are reserved, the job status is updated and the jobs are returned. Requires a secure connection.
80
+
81
+ API details:
82
+ HTTP Method: POST
83
+ Path: /v1/pilot/acquire_jobs
84
+
85
+ Args:
86
+ req(PandaRequest): Internally generated request object containing the environment variables.
87
+ site_name(str): The PanDA queue name
88
+ timeout(int, optional): Request timeout in seconds. Optional and defaults to 60.
89
+ memory(int, optional): Memory limit for the job. Optional and defaults to `None`.
90
+ disk_space(int, optional): Disk space limit for the job. Optional and defaults to `None`.
91
+ prod_source_label(str, optional): Prodsourcelabel, e.g. `user`, `managed`, `unified`... Optional and defaults to `None`.
92
+ node(str, optional): Identifier of the worker node/slot. Optional and defaults to `None`.
93
+ computing_element(str, optional): Computing element. Optional and defaults to `None`.
94
+ prod_user_id(str, optional): User ID of the job. Optional and defaults to `None`.
95
+ get_proxy_key(bool, optional): Flag to request a proxy key.Optional and defaults to `None`.
96
+ task_id(int, optional): JEDI task ID of the job. Optional and defaults to `None`.
97
+ n_jobs(int, optional): Number of jobs for bulk requests. Optional and defaults to `None`.
98
+ background(bool, optional): Background flag. Optional and defaults to `None`.
99
+ resource_type(str, optional): Resource type of the job, e.g. `SCORE`, `MCORE`,.... Optional and defaults to `None`.
100
+ harvester_id(str, optional): Harvester ID, used to update the worker entry in the DB. Optional and defaults to `None`.
101
+ worker_id(int, optional): Worker ID, used to update the worker entry in the DB. Optional and defaults to `None`.
102
+ scheduler_id(str, optional): Scheduler, e.g. harvester ID. Optional and defaults to `None`.
103
+ job_type(str, optional): Job type, e.g. `user`, `unified`, ... This is necessary on top of the `prodsourcelabel`
104
+ to disambiguate the cases of test jobs that can be production or analysis. Optional and defaults to `None`.
105
+ via_topic(bool, optional): Topic for message broker. Optional and defaults to `None`.
106
+ remaining_time(int, optional): Remaining walltime. Optional and defaults to `None`.
107
+
108
+ Returns:
109
+ dict: The system response `{"success": success, "message": message, "data": data}`. The data is a list of job dictionaries.
110
+ When failed, the message contains the error message.
111
+ """
112
+
113
+ tmp_logger = LogWrapper(_logger, f"acquire_jobs {naive_utcnow().isoformat('/')}")
114
+ tmp_logger.debug(f"Start for {site_name}")
115
+
116
+ # get DN and FQANs
117
+ real_dn = get_dn(req)
118
+
119
+ # check production role
120
+ is_production_manager = has_production_role(req)
121
+
122
+ # production jobs should only be retrieved with production role
123
+ if not is_production_manager and prod_source_label not in ["user"]:
124
+ tmp_logger.warning("invalid role")
125
+ tmp_msg = "no production/pilot role in VOMS FQANs or non pilot owner"
126
+ return generate_response(False, message=tmp_msg)
127
+
128
+ # set DN for non-production user, the user can only get their own jobs
129
+ if not is_production_manager:
130
+ prod_user_id = real_dn
131
+
132
+ # allow get_proxy_key for production role
133
+ if get_proxy_key and is_production_manager:
134
+ get_proxy_key = True
135
+ else:
136
+ get_proxy_key = False
137
+
138
+ # convert memory
139
+ try:
140
+ memory = max(0, memory)
141
+ except (ValueError, TypeError):
142
+ memory = 0
143
+
144
+ # convert disk_space
145
+ try:
146
+ disk_space = max(0, disk_space)
147
+ except (ValueError, TypeError):
148
+ disk_space = 0
149
+
150
+ # convert remaining time
151
+ try:
152
+ remaining_time = max(0, remaining_time)
153
+ except (ValueError, TypeError):
154
+ remaining_time = 0
155
+
156
+ tmp_logger.debug(
157
+ f"{site_name}, n_jobs={n_jobs}, memory={memory}, disk={disk_space}, source_label={prod_source_label}, "
158
+ f"node={node}, ce={computing_element}, user={prod_user_id}, proxy={get_proxy_key}, "
159
+ f"task_id={task_id}, DN={real_dn}, role={is_production_manager}, "
160
+ f"bg={background}, rt={resource_type}, harvester_id={harvester_id}, worker_id={worker_id}, "
161
+ f"scheduler_id={scheduler_id}, job_type={job_type}, via_topic={via_topic} remaining_time={remaining_time}"
162
+ )
163
+
164
+ # log the acquire_jobs as it's used for site activity metrics
165
+ for i in range(n_jobs):
166
+ slot_suffix = f"_{i}" if n_jobs > 1 else ""
167
+ pilot_logger.info(f"method=getJob,site={site_name},node={node}{slot_suffix},type={prod_source_label}")
168
+
169
+ t_start = time.time()
170
+
171
+ global_site_mapper_cache.update()
172
+ is_grandly_unified = global_site_mapper_cache.cachedObj.getSite(site_name).is_grandly_unified()
173
+ in_test_status = global_site_mapper_cache.cachedObj.getSite(site_name).status == "test"
174
+
175
+ # change label
176
+ if in_test_status and prod_source_label in ["user", "managed", "unified"]:
177
+ new_label = "test"
178
+ tmp_logger.debug(f"prod_source_label changed from {prod_source_label} to {new_label}")
179
+ prod_source_label = new_label
180
+
181
+ # wrapper function for timeout
182
+ timed_method = TimedMethod(global_task_buffer.getJobs, timeout)
183
+ timed_method.run(
184
+ n_jobs,
185
+ site_name,
186
+ prod_source_label,
187
+ memory,
188
+ disk_space,
189
+ node,
190
+ timeout,
191
+ computing_element,
192
+ prod_user_id,
193
+ task_id,
194
+ background,
195
+ resource_type,
196
+ harvester_id,
197
+ worker_id,
198
+ scheduler_id,
199
+ job_type,
200
+ is_grandly_unified,
201
+ via_topic,
202
+ remaining_time,
203
+ )
204
+
205
+ # Time-out
206
+ if timed_method.result == Protocol.TimeOutToken:
207
+ message = "Timed out"
208
+ tmp_logger.debug(message)
209
+ return generate_response(False, message=message)
210
+
211
+ # Try to get the jobs
212
+ jobs = []
213
+ if isinstance(timed_method.result, list):
214
+ result = timed_method.result
215
+ secrets_map = result.pop()
216
+ proxy_key = result[-1]
217
+ n_sent = result[-2]
218
+ jobs = result[:-2]
219
+
220
+ if not jobs:
221
+ message = "No jobs in PanDA"
222
+ tmp_logger.debug(message)
223
+ pilot_logger.info(f"method=noJob,site={site_name},node={node},type={prod_source_label}")
224
+ return generate_response(False, message=message)
225
+
226
+ # add each job to the list
227
+ response_list = []
228
+ for tmp_job in jobs:
229
+ try:
230
+ # The response is nothing but a dictionary with the job information
231
+ response = Protocol.Response(Protocol.SC_Success)
232
+ response.appendJob(tmp_job, global_site_mapper_cache)
233
+ except Exception as e:
234
+ tmp_msg = f"failed to get jobs with {str(e)}"
235
+ tmp_logger.error(f"{tmp_msg}\n{traceback.format_exc()}")
236
+ raise
237
+
238
+ # append n_sent
239
+ response.appendNode("nSent", n_sent)
240
+
241
+ # set proxy key
242
+ if get_proxy_key:
243
+ response.setProxyKey(proxy_key)
244
+
245
+ # set user secrets
246
+ if tmp_job.use_secrets() and tmp_job.prodUserName in secrets_map and secrets_map[tmp_job.prodUserName]:
247
+ response.appendNode("secrets", secrets_map[tmp_job.prodUserName])
248
+
249
+ # set pilot secrets
250
+ pilot_secrets = secrets_map.get(panda_config.pilot_secrets, None)
251
+ if pilot_secrets:
252
+ response.appendNode("pilotSecrets", secrets_map[panda_config.pilot_secrets])
253
+
254
+ response_list.append(response.data)
255
+
256
+ # if the jobs were requested in bulk, we make a bulk response
257
+ if n_jobs is not None:
258
+ try:
259
+ response = Protocol.Response(Protocol.SC_Success)
260
+ response.appendNode("jobs", response_list)
261
+ except Exception as e:
262
+ tmp_msg = f"Failed to make response with {str(e)}"
263
+ tmp_logger.error(f"{tmp_msg}\n{traceback.format_exc()}")
264
+ raise
265
+
266
+ tmp_logger.debug(f"Done for {site_name} {node}")
267
+
268
+ t_end = time.time()
269
+ t_delta = t_end - t_start
270
+ tmp_logger.info(f"site_name={site_name} took timing={t_delta}s in_test={in_test_status}")
271
+ return generate_response(True, data=response.data)
272
+
273
+
274
+ @request_validation(_logger, secure=True, request_method="GET")
275
+ def get_job_status(req: PandaRequest, job_ids: List[int], timeout: int = 60) -> dict:
276
+ """
277
+ Get job status
278
+
279
+ Gets the status for a list of jobs. Requires a secure connection.
280
+
281
+ API details:
282
+ HTTP Method: GET
283
+ Path: /v1/pilot/get_job_status
284
+
285
+ Args:
286
+ req(PandaRequest): Internally generated request object containing the environment variables.
287
+ job_ids(list): list of job IDs.
288
+ timeout(int, optional): The timeout value. Defaults to 60.
289
+
290
+ Returns:
291
+ dict: The system response `{"success": success, "message": message, "data": data}`. The data is a list of job status dictionaries, in the format
292
+ ```
293
+ [{"job_id": <job_id_requested>, "status": "not found", "attempt_number": 0}), {"job_id": <job_id>, "status": <status>, "attempt_number": <attempt_nr>}]
294
+ ```
295
+ """
296
+
297
+ tmp_logger = LogWrapper(_logger, f"get_job_status {job_ids}")
298
+ tmp_logger.debug("Start")
299
+
300
+ # peek jobs
301
+ timed_method = TimedMethod(global_task_buffer.peekJobs, timeout)
302
+ timed_method.run(job_ids, fromDefined=False, fromActive=True, fromArchived=True, forAnal=False, use_json=False)
303
+
304
+ # make response
305
+ if timed_method.result == Protocol.TimeOutToken:
306
+ message = "Timed out"
307
+ tmp_logger.debug(message)
308
+ return generate_response(False, message=message)
309
+
310
+ if not isinstance(timed_method.result, list):
311
+ message = "Database error"
312
+ tmp_logger.debug(message)
313
+ return generate_response(False, message=message)
314
+
315
+ job_status_list = []
316
+ for job, job_id_requested in zip(timed_method.result, job_ids):
317
+ if not job:
318
+ job_status_list.append({"job_id": job_id_requested, "status": "not found", "attempt_number": 0})
319
+ else:
320
+ job_status_list.append({"job_id": job.PandaID, "status": job.jobStatus, "attempt_number": job.attemptNr})
321
+
322
+ tmp_logger.debug(f"Done: job_status_list={job_status_list}")
323
+
324
+ return generate_response(True, data=job_status_list)
325
+
326
+
327
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
328
+ def update_job(
329
+ req: PandaRequest,
330
+ job_id: int,
331
+ job_status: str,
332
+ job_output_report: str = "",
333
+ node: str = None,
334
+ cpu_consumption_time: int = None,
335
+ cpu_consumption_unit: str = None,
336
+ scheduler_id: str = None,
337
+ pilot_id: str = None,
338
+ site_name: str = None,
339
+ pilot_log: str = "",
340
+ meta_data: str = "",
341
+ cpu_conversion_factor: float | int = None,
342
+ trans_exit_code: int = None,
343
+ pilot_error_code: int = None,
344
+ pilot_error_diag: str = None,
345
+ exe_error_code: int = None,
346
+ exe_error_diag: str = None,
347
+ pilot_timing: str = None,
348
+ start_time: str = None,
349
+ end_time: str = None,
350
+ n_events: int = None,
351
+ n_input_files: int = None,
352
+ batch_id: str = None,
353
+ attempt_nr: int = None,
354
+ job_metrics: str = None,
355
+ stdout: str = "",
356
+ job_sub_status: str = None,
357
+ core_count: int = None,
358
+ max_rss: int | float = None,
359
+ max_vmem: int | float = None,
360
+ max_swap: int | float = None,
361
+ max_pss: int | float = None,
362
+ avg_rss: int | float = None,
363
+ avg_vmem: int | float = None,
364
+ avg_swap: int | float = None,
365
+ avg_pss: int | float = None,
366
+ tot_rchar: int | float = None,
367
+ tot_wchar: int | float = None,
368
+ tot_rbytes: int | float = None,
369
+ tot_wbytes: int | float = None,
370
+ rate_rchar: int | float = None,
371
+ rate_wchar: int | float = None,
372
+ rate_rbytes: int | float = None,
373
+ rate_wbytes: int | float = None,
374
+ corrupted_files: str = None,
375
+ mean_core_count: int = None,
376
+ cpu_architecture_level: str = None,
377
+ grid: str = None,
378
+ source_site: str = None,
379
+ destination_site: str = None,
380
+ timeout: int = 60,
381
+ ):
382
+ """
383
+ Update job
384
+
385
+ Updates the details for a job, stores the metadata and excerpts from the pilot log. Requires a secure connection and production role.
386
+
387
+ API details:
388
+ HTTP Method: POST
389
+ Path: /v1/pilot/update_job
390
+
391
+ Args:
392
+ req(PandaRequest): internally generated request object containing the env variables
393
+ job_id (int): PanDA job ID
394
+ job_status(str, optional): Job status
395
+ job_sub_status(str, optional): Job sub status. Optional, defaults to `None`
396
+ start_time(str, optional): Job start time in format `"%Y-%m-%d %H:%M:%S"`. Optional, defaults to `None`
397
+ end_time(str, optional): Job end time in format `"%Y-%m-%d %H:%M:%S"`. Optional, defaults to `None`
398
+ pilot_timing(str, optional): String with pilot timings. Optional, defaults to `None`
399
+ site_name(str, optional): PanDA queue name. Optional, defaults to `None`
400
+ node(str, optional): Identifier for worker node/slot. Optional, defaults to `None`
401
+ scheduler_id(str, optional): Scheduler ID, such as harvester instance. Optional, defaults to `None`
402
+ pilot_id(str, optional): Pilot ID. Optional, defaults to `None`
403
+ batch_id(str, optional): Batch ID. Optional, defaults to `None`
404
+ trans_exit_code(int, optional): Transformation exit code. Optional, defaults to `None`
405
+ pilot_error_code(int, optional): Pilot error code. Optional, defaults to `None`
406
+ pilot_error_diag(str, optional): Pilot error message. Optional, defaults to `None`
407
+ exe_error_code(int, optional): Execution error code. Optional, defaults to `None`
408
+ exe_error_diag(str, optional): Execution error message. Optional, defaults to `None`
409
+ n_events(int, optional): Number of events. Optional, defaults to `None`
410
+ n_input_files(int, optional): Number of input files. Optional, defaults to `None`
411
+ attempt_nr(int, optional): Job attempt number. Optional, defaults to `None`
412
+ cpu_consumption_time(int, optional): CPU consumption time. Optional, defaults to `None`
413
+ cpu_consumption_unit(str, optional): CPU consumption unit, being used for updating some CPU details. Optional, defaults to `None`
414
+ cpu_conversion_factor(float, optional): CPU conversion factor. Optional defaults to `None`
415
+ core_count(int, optional): Number of cores of the job. Optional, defaults to `None`
416
+ mean_core_count(int, optional): Mean core count. Optional, defaults to `None`
417
+ max_rss(int, optional): Measured max RSS memory. Optional, defaults to `None`
418
+ max_vmem(int, optional): Measured max Virtual memory. Optional, defaults to `None`
419
+ max_swap(int, optional): Measured max swap memory. Optional, defaults to `None`
420
+ max_pss(int, optional): Measured max PSS memory. Optional, defaults to `None`
421
+ avg_rss(int, optional): Measured average RSS. Optional, defaults to `None`
422
+ avg_vmem(int, optional): Measured average Virtual memory.Optional, defaults to `None`
423
+ avg_swap(int, optional): Measured average swap memory. Optional, defaults to `None`
424
+ avg_pss(int, optional): Measured average PSS. Optional, defaults to `None`
425
+ tot_rchar(int, optional): Measured total read characters. Optional, defaults to `None`
426
+ tot_wchar(int, optional): Measured total written characters. Optional, defaults to `None`
427
+ tot_rbytes(int, optional): Measured total read bytes. Optional, defaults to `None`
428
+ tot_wbytes(int, optional): Measured total written bytes. Optional, defaults to `None`
429
+ rate_rchar(int, optional): Measured rate for read characters. Optional, defaults to `None`
430
+ rate_wchar(int, optional): Measured rate for written characters. Optional, defaults to `None`
431
+ rate_rbytes(int, optional): Measured rate for read bytes. Optional, defaults to `None`
432
+ rate_wbytes(int, optional): Measured rate for written bytes. Optional, defaults to `None`
433
+ corrupted_files(str, optional): List of corrupted files in comma separated format. Optional, defaults to `None`
434
+ cpu_architecture_level(str, optional): CPU architecture level (e.g. `x86_64-v3`). Optional, defaults to `None`
435
+ grid(str, optional): Grid type. Optional, defaults to `None`
436
+ source_site(str, optional): Source site name. Optional, defaults to `None`
437
+ destination_site(str, optional): Destination site name. Optional, defaults to `None`
438
+ job_metrics(str, optional): Job metrics. Optional, defaults to `None`
439
+ job_output_report(str, optional): Job output report. Optional, defaults to `""`
440
+ pilot_log(str, optional): Pilot log excerpt. Optional, defaults to `""`
441
+ meta_data(str, optional): Job metadata. Optional, defaults to `""`
442
+ stdout(str, optional): Standard output. Optional, defaults to `""`
443
+ timeout(int, optional): Timeout for the operation in seconds. Optional, defaults to 60
444
+
445
+ Returns:
446
+ dict: The system response `{"success": success, "message": message, "data": data}`. Data will contain a dictionary with the pilot secrets and the command to the pilot.
447
+ ```
448
+ {"pilotSecrets": <pilot_secrets>, "command": <command>}
449
+ ```
450
+ """
451
+ tmp_logger = LogWrapper(_logger, f"update_job PandaID={job_id} PID={os.getpid()}")
452
+ tmp_logger.debug("Start")
453
+
454
+ _logger.debug(
455
+ f"update_job({job_id}, {job_status}, {trans_exit_code}, {pilot_error_code}, {pilot_error_diag}, {node},"
456
+ f"cpu_consumption_time={cpu_consumption_time}, {cpu_consumption_unit}, {cpu_architecture_level}, "
457
+ f"{scheduler_id}, {pilot_id}, {site_name}, {n_events}, {n_input_files}, {cpu_conversion_factor}, "
458
+ f"{exe_error_code}, {exe_error_diag}, {pilot_timing}, {start_time}, {end_time}, {batch_id}, "
459
+ f"attempt_nr:{attempt_nr}, job_sub_status:{job_sub_status}, core_count:{core_count}, "
460
+ f"max_rss={max_rss}, max_vmem={max_vmem}, max_swap={max_swap}, "
461
+ f"max_pss={max_pss}, avg_rss={avg_rss}, avg_vmem={avg_vmem}, avg_swap={avg_swap}, avg_pss={avg_pss}, "
462
+ f"tot_rchar={tot_rchar}, tot_wchar={tot_wchar}, tot_rbytes={tot_rbytes}, tot_wbytes={tot_wbytes}, rate_rchar={rate_rchar}, "
463
+ f"rate_wchar={rate_wchar}, rate_rbytes={rate_rbytes}, rate_wbytes={rate_wbytes}, mean_core_count={mean_core_count}, "
464
+ f"grid={grid}, source_site={source_site}, destination_site={destination_site}, "
465
+ f"corrupted_files={corrupted_files}\n==job_output_report==\n{job_output_report}\n==LOG==\n{pilot_log[:1024]}\n==Meta==\n{meta_data[:1024]}\n"
466
+ f"==Metrics==\n{job_metrics}\n==stdout==\n{stdout})"
467
+ )
468
+
469
+ pilot_logger.debug(f"method=updateJob,site={site_name},node={node},type=None")
470
+
471
+ # aborting message
472
+ if job_id == "NULL":
473
+ response = Protocol.Response(Protocol.SC_Invalid)
474
+ return generate_response(success=False, message="job_id is NULL", data=response.data)
475
+
476
+ # check the job status is valid
477
+ if job_status not in VALID_JOB_STATES:
478
+ message = f"Invalid job status: {job_status}"
479
+ tmp_logger.warning(message)
480
+ response = Protocol.Response(Protocol.SC_Invalid)
481
+ return generate_response(success=False, message=message, data=response.data)
482
+
483
+ # create the job parameter map
484
+ param = {}
485
+ fields = [
486
+ ("cpuConsumptionTime", cpu_consumption_time, int),
487
+ ("cpuConsumptionUnit", cpu_consumption_unit, str),
488
+ ("cpu_architecture_level", cpu_architecture_level, lambda x: str(x)[:20]),
489
+ ("modificationHost", node, lambda x: str(x)[:128]),
490
+ ("transExitCode", trans_exit_code, int),
491
+ ("pilotErrorCode", pilot_error_code, int),
492
+ ("pilotErrorDiag", pilot_error_diag, lambda x: str(x)[:500]),
493
+ ("jobMetrics", job_metrics, lambda x: str(x)[:500]),
494
+ ("schedulerID", scheduler_id, str),
495
+ ("pilotID", pilot_id, lambda x: str(x)[:200]),
496
+ ("batchID", batch_id, lambda x: str(x)[:80]),
497
+ ("exeErrorCode", exe_error_code, int),
498
+ ("exeErrorDiag", exe_error_diag, lambda x: str(x)[:500]),
499
+ ("cpuConversion", cpu_conversion_factor, float),
500
+ ("pilotTiming", pilot_timing, str),
501
+ ("nEvents", n_events, int),
502
+ ("nInputFiles", n_input_files, int),
503
+ ("jobSubStatus", job_sub_status, str),
504
+ ("actualCoreCount", core_count, int),
505
+ ("meanCoreCount", mean_core_count, float),
506
+ ("maxRSS", max_rss, int),
507
+ ("maxVMEM", max_vmem, int),
508
+ ("maxSWAP", max_swap, int),
509
+ ("maxPSS", max_pss, int),
510
+ ("avgRSS", avg_rss, lambda x: int(float(x))),
511
+ ("avgVMEM", avg_vmem, lambda x: int(float(x))),
512
+ ("avgSWAP", avg_swap, lambda x: int(float(x))),
513
+ ("avgPSS", avg_pss, lambda x: int(float(x))),
514
+ ("corruptedFiles", corrupted_files, str),
515
+ ("grid", grid, str),
516
+ ("sourceSite", source_site, str),
517
+ ("destinationSite", destination_site, str),
518
+ ]
519
+
520
+ # Iterate through fields, apply transformations and add to `param`
521
+ for key, value, cast in fields:
522
+ if value not in [None, ""]:
523
+ try:
524
+ param[key] = cast(value)
525
+ except Exception:
526
+ tmp_logger.error(f"Invalid {key}={value} for updateJob")
527
+
528
+ # Special handling for file size metrics
529
+ file_metrics = [
530
+ ("totRCHAR", tot_rchar),
531
+ ("totWCHAR", tot_wchar),
532
+ ("totRBYTES", tot_rbytes),
533
+ ("totWBYTES", tot_wbytes),
534
+ ("rateRCHAR", rate_rchar),
535
+ ("rateWCHAR", rate_wchar),
536
+ ("rateRBYTES", rate_rbytes),
537
+ ("rateWBYTES", rate_wbytes),
538
+ ]
539
+
540
+ for key, value in file_metrics:
541
+ if value is not None:
542
+ try:
543
+ value = int(value) / 1024 # Convert to kB
544
+ param[key] = min(10**10 - 1, value) # Limit to 10 digits
545
+ except Exception:
546
+ tmp_logger.error(f"Invalid {key}={value} for updateJob")
547
+
548
+ # Convert timestamps
549
+ for key, value in [("startTime", start_time), ("endTime", end_time)]:
550
+ if value is not None:
551
+ try:
552
+ param[key] = datetime.datetime(*time.strptime(value, "%Y-%m-%d %H:%M:%S")[:6])
553
+ except Exception:
554
+ tmp_logger.error(f"Invalid {key}={value} for updateJob")
555
+
556
+ # Handle attempt_nr separately
557
+ if attempt_nr is not None:
558
+ try:
559
+ attempt_nr = int(attempt_nr)
560
+ except Exception:
561
+ attempt_nr = None
562
+
563
+ tmp_logger.debug("executing")
564
+
565
+ # store the pilot log
566
+ if pilot_log != "":
567
+ tmp_logger.debug("Saving pilot log")
568
+ try:
569
+ global_task_buffer.storePilotLog(int(job_id), pilot_log)
570
+ tmp_logger.debug("Saving pilot log DONE")
571
+ except Exception:
572
+ tmp_logger.debug("Saving pilot log FAILED")
573
+
574
+ # add meta_data
575
+ if meta_data != "":
576
+ ret = global_task_buffer.addMetadata([job_id], [meta_data], [job_status])
577
+ if len(ret) > 0 and not ret[0]:
578
+ message = "Failed to add meta_data"
579
+ tmp_logger.debug(message)
580
+ return generate_response(True, message, data={"StatusCode": Protocol.SC_Failed, "ErrorDiag": message})
581
+
582
+ # add stdout
583
+ if stdout != "":
584
+ global_task_buffer.addStdOut(job_id, stdout)
585
+
586
+ # update the job
587
+ tmp_status = job_status
588
+ update_state_change = False
589
+ if job_status in ("failed", "finished"):
590
+ tmp_status = "holding"
591
+ update_state_change = True # update stateChangeTime to prevent Watcher from finding this job
592
+ param["jobDispatcherErrorDiag"] = None
593
+ elif job_status in ("holding", "transferring"):
594
+ param["jobDispatcherErrorDiag"] = f"set to {job_status} by the pilot at {naive_utcnow().strftime('%Y-%m-%d %H:%M:%S')}"
595
+
596
+ # update the job status in the database
597
+ timeout = None if job_status == "holding" else timeout
598
+ timed_method = TimedMethod(global_task_buffer.updateJobStatus, timeout)
599
+ timed_method.run(job_id, tmp_status, param, update_state_change, attempt_nr)
600
+
601
+ # time-out
602
+ if timed_method.result == Protocol.TimeOutToken:
603
+ message = "Timed out"
604
+ tmp_logger.error(message)
605
+ response = Protocol.Response(Protocol.SC_TimeOut)
606
+ return generate_response(True, message=message, data=response.data)
607
+
608
+ # no result
609
+ if not timed_method.result:
610
+ message = "Database error"
611
+ tmp_logger.error(message)
612
+ response = Protocol.Response(Protocol.SC_Failed)
613
+ return generate_response(True, message=message, data=response.data)
614
+
615
+ # generate the response with the result
616
+ data = {"StatusCode": Protocol.SC_Success}
617
+ result = timed_method.result
618
+
619
+ # set the secrets
620
+ secrets = result.get("secrets") if isinstance(result, dict) else None
621
+ if secrets:
622
+ data["pilotSecrets"] = secrets
623
+
624
+ # set the command to the pilot
625
+ command = result.get("command") if isinstance(result, dict) else result
626
+ data["command"] = command if isinstance(command, str) else None
627
+
628
+ # add output to dataset for failed/finished jobs with correct results
629
+ if job_status in ("failed", "finished") and result not in ("badattemptnr", "alreadydone"):
630
+ adder_gen = AdderGen(global_task_buffer, job_id, job_status, attempt_nr)
631
+ adder_gen.dump_file_report(job_output_report, attempt_nr)
632
+ del adder_gen
633
+
634
+ tmp_logger.debug(f"Done. data={data}")
635
+ return generate_response(True, data=data)
636
+
637
+
638
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
639
+ def update_jobs_bulk(req, job_list: List, harvester_id: str = None):
640
+ """
641
+ Update jobs in bulk
642
+
643
+ Bulk method to update the details for jobs, store the metadata and excerpt from the pilot log. Internally, this method loops over
644
+ the jobs and calls `update_job` for each job. Requires a secure connection and production role.
645
+
646
+ API details:
647
+ HTTP Method: POST
648
+ Path: /v1/pilot/update_jobs_bulk
649
+
650
+ Args:
651
+ req(PandaRequest): internally generated request object containing the env variables
652
+ job_list(list): list of job dictionaries to update. The mandatory and optional keys for each job dictionary are the same as the arguments for `update_job`.
653
+ harvester_id (str, optional): Harvester ID. Optional, defaults to `None`.
654
+
655
+ Returns:
656
+ dict: The system response `{"success": success, "message": message, "data": data}`. Data will contain a dictionary with the pilot secrets and the command to the pilot.
657
+ ```
658
+ {"pilotSecrets": <pilot_secrets>, "command": <command>}
659
+ ```
660
+ """
661
+ tmp_logger = LogWrapper(_logger, f"update_jobs_bulk harvester_id={harvester_id}")
662
+ tmp_logger.debug("Start")
663
+ t_start = naive_utcnow()
664
+
665
+ success = False
666
+ message = ""
667
+ data = []
668
+
669
+ try:
670
+ for job_dict in job_list:
671
+ job_id = job_dict["job_id"]
672
+ del job_dict["job_id"]
673
+
674
+ status = job_dict["job_status"]
675
+ del job_dict["job_status"]
676
+
677
+ if "meta_data" in job_dict:
678
+ job_dict["meta_data"] = str(job_dict["meta_data"])
679
+
680
+ tmp_ret = update_job(req, job_id, status, **job_dict)
681
+ data.append(tmp_ret)
682
+ success = True
683
+ except Exception:
684
+ err_type, err_value = sys.exc_info()[:2]
685
+ message = f"failed with {err_type.__name__} {err_value}"
686
+ data = []
687
+ tmp_logger.error(f"{message}\n{traceback.format_exc()}")
688
+
689
+ t_delta = naive_utcnow() - t_start
690
+ tmp_logger.debug(f"Done. Took {t_delta.seconds}.{t_delta.microseconds // 1000:03d} sec")
691
+ return generate_response(success, message, data)
692
+
693
+
694
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
695
+ def update_worker_status(req: PandaRequest, worker_id, harvester_id, status, timeout=60, node_id=None):
696
+ """
697
+ Update worker status
698
+
699
+ Updates the status of a worker with the information seen by the pilot. Requires a secure connection and production role.
700
+
701
+ API details:
702
+ HTTP Method: POST
703
+ Path: /v1/pilot/update_worker_status
704
+
705
+ Args:
706
+ req(PandaRequest): Internally generated request object containing the environment variables.
707
+ worker_id (str): The worker ID.
708
+ harvester_id (str): The harvester ID.
709
+ status (str): The status of the worker. Must be either 'started' or 'finished'.
710
+ timeout (int, optional): The timeout value. Defaults to 60.
711
+ node_id (str, optional): The node ID. Defaults to None.
712
+
713
+ Returns:
714
+ str: The result of the status update or an error message.
715
+ """
716
+ tmp_logger = LogWrapper(
717
+ _logger,
718
+ f"updateWorkerPilotStatus worker_id={worker_id} harvester_id={harvester_id} status={status} node_id={node_id} PID={os.getpid()}",
719
+ )
720
+ tmp_logger.debug("Start")
721
+
722
+ # validate the state passed by the pilot
723
+ valid_worker_states = ("started", "finished")
724
+ if status not in valid_worker_states:
725
+ message = f"Invalid worker state. The worker state has to be in {valid_worker_states}"
726
+ tmp_logger.debug(message)
727
+ return generate_response(False, message)
728
+
729
+ timed_method = TimedMethod(global_task_buffer.updateWorkerPilotStatus, timeout)
730
+ timed_method.run(worker_id, harvester_id, status, node_id)
731
+
732
+ # generate the response
733
+ if not timed_method.result: # failure
734
+ message = "Failed to update worker status"
735
+ tmp_logger.error(message)
736
+ return generate_response(False, message)
737
+
738
+ if timed_method.result == Protocol.TimeOutToken: # timeout
739
+ message = "Updating worker status timed out"
740
+ tmp_logger.error(message)
741
+ return generate_response(False, message)
742
+
743
+ tmp_logger.debug(f"Done")
744
+ return generate_response(True)
745
+
746
+
747
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
748
+ def update_worker_node(
749
+ req: PandaRequest,
750
+ site: str,
751
+ host_name: str,
752
+ cpu_model: str,
753
+ panda_queue: str = None,
754
+ n_logical_cpus: int = None,
755
+ n_sockets: int = None,
756
+ cores_per_socket: int = None,
757
+ threads_per_core: int = None,
758
+ cpu_architecture: str = None,
759
+ cpu_architecture_level: str = None,
760
+ clock_speed: float = None,
761
+ total_memory: int = None,
762
+ total_local_disk: int = None,
763
+ timeout: int = 60,
764
+ ):
765
+ """
766
+ Update worker node
767
+
768
+ Updates a worker node in the worker node map. When already found, it updates the `last_seen` time. When not found, it adds the worker node. Requires a secure connection and production role.
769
+
770
+ API details:
771
+ HTTP Method: POST
772
+ Path: /v1/pilot/update_worker_node
773
+
774
+ Args:
775
+ req(PandaRequest): Internally generated request object containing the environment variables.
776
+ site(str): Site name (e.g. ATLAS site name, not PanDA queue).
777
+ host_name(str): Host name. In the case of reporting in format `slot@worker_node.example.com`, the slot ID will be parsed out.
778
+ cpu_model(str): CPU model, e.g. `AMD EPYC 7351`.
779
+ panda_queue(str, optional): PanDA queue the worker node is associated to. Optional, defaults to `None`.
780
+ n_logical_cpus(int, optional): Number of logical CPUs: n_sockets * cores_per_socket * threads_per_core.
781
+ When SMT is enabled, this is the number of threads. Otherwise it is the number of cores. Optional, defaults to `None`.
782
+ n_sockets(int, optional): Number of sockets. Optional, defaults to `None`.
783
+ cores_per_socket(int, optional): Number of cores per socket. Optional, defaults to `None`.
784
+ threads_per_core(int, optional): Number of threads per core. When SMT is disabled, this is 1. Otherwise a number > 1. Optional, defaults to `None`.
785
+ cpu_architecture(str, optional): CPU architecture, e.g. `x86_64`. Optional, defaults to `None`.
786
+ cpu_architecture_level(str, optional): CPU architecture level, e.g. `x86-64-v3`. Optional, defaults to `None`.
787
+ clock_speed(float, optional): Clock speed in MHz. Optional, defaults to `None`.
788
+ total_memory(int, optional): Total memory in MB. Optional, defaults to `None`.
789
+ total_local_disk(int, optional): Total disk space in GB. Optional, defaults to `None`.
790
+ timeout(int, optional): The timeout value. Defaults to 60.
791
+
792
+ Returns:
793
+ dict: The system response `{"success": success, "message": message, "data": data}`. True for success, False for failure, and an error message.
794
+ """
795
+ tmp_logger = LogWrapper(_logger, f"update_worker_node site={site} panda_queue={panda_queue} host_name={host_name} cpu_model={cpu_model}")
796
+ tmp_logger.debug("Start")
797
+
798
+ cpu_model_normalized = normalize_cpu_model(cpu_model)
799
+
800
+ timed_method = TimedMethod(global_task_buffer.update_worker_node, timeout)
801
+ timed_method.run(
802
+ site,
803
+ panda_queue,
804
+ host_name,
805
+ cpu_model,
806
+ cpu_model_normalized,
807
+ n_logical_cpus,
808
+ n_sockets,
809
+ cores_per_socket,
810
+ threads_per_core,
811
+ cpu_architecture,
812
+ cpu_architecture_level,
813
+ clock_speed,
814
+ total_memory,
815
+ total_local_disk,
816
+ )
817
+
818
+ if timed_method.result == Protocol.TimeOutToken: # timeout
819
+ message = "Updating worker node timed out"
820
+ tmp_logger.error(message)
821
+ return generate_response(False, message)
822
+
823
+ success, message = timed_method.result
824
+
825
+ tmp_logger.debug(f"Done")
826
+ return generate_response(success, message)
827
+
828
+
829
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
830
+ def update_worker_node_gpu(
831
+ req: PandaRequest,
832
+ site: str,
833
+ host_name: str,
834
+ vendor: str,
835
+ model: str,
836
+ count: int,
837
+ vram: int = None,
838
+ architecture: str = None,
839
+ framework: str = None,
840
+ framework_version: str = None,
841
+ driver_version: str = None,
842
+ timeout: int = 60,
843
+ ):
844
+ """
845
+ Update GPUs for a worker node
846
+
847
+ Updates the GPUs associated to a worker node in the worker node map. When already found, it updates the `last_seen` time. Requires a secure connection and production role.
848
+
849
+ API details:
850
+ HTTP Method: POST
851
+ Path: /v1/pilot/update_worker_node_gpu
852
+
853
+ Args:
854
+ req(PandaRequest): Internally generated request object containing the environment variables.
855
+ site(str): Site name (e.g. ATLAS site name, not PanDA queue).
856
+ host_name(str): Host name. In the case of reporting in format `slot@worker_node.example.com`, the slot ID will be parsed out.
857
+ vendor(str): GPU vendor, e.g. `NVIDIA`.
858
+ model(str): GPU model, e.g. `A100 80GB`.
859
+ count(int): Number of GPUs of this type in the worker node.
860
+ vram(int, optional): VRAM memory in MB. Defaults to `None`.
861
+ architecture(str, optional): GPU architecture, e.g. `Tesla`, `Ampere`... Defaults to `None`.
862
+ framework(str, optional): Driver framework available, e.g. `CUDA`. Defaults to `None`.
863
+ framework_version(str, optional): Version of the driver framework, e.g. `12.2`. Defaults to `None`.
864
+ driver_version(str, optional): Version of the driver, e.g. `575.51.03`. Defaults to `None`
865
+ timeout(int, optional): The timeout value. Defaults to `60`.
866
+
867
+ Returns:
868
+ dict: The system response `{"success": success, "message": message, "data": data}`. True for success, False for failure, and an error message.
869
+ """
870
+ tmp_logger = LogWrapper(_logger, f"update_worker_node_gpu site={site} host_name={host_name} vendor={vendor} model={model}")
871
+ tmp_logger.debug("Start")
872
+
873
+ timed_method = TimedMethod(global_task_buffer.update_worker_node_gpu, timeout)
874
+ timed_method.run(
875
+ site,
876
+ host_name,
877
+ vendor,
878
+ model,
879
+ count,
880
+ vram,
881
+ architecture,
882
+ framework,
883
+ framework_version,
884
+ driver_version,
885
+ )
886
+
887
+ if timed_method.result == Protocol.TimeOutToken: # timeout
888
+ message = "Updating worker node GPU timed out"
889
+ tmp_logger.error(message)
890
+ return generate_response(False, message)
891
+
892
+ success, message = timed_method.result
893
+
894
+ tmp_logger.debug(f"Done")
895
+ return generate_response(success, message)