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,443 @@
1
+ import base64
2
+ import json
3
+ import re
4
+ from urllib.parse import urlencode
5
+
6
+ from pandaserver.dataservice import DataServiceUtils
7
+ from pandaserver.taskbuffer import EventServiceUtils
8
+
9
+ # constants
10
+ TimeOutToken = "TimeOut"
11
+
12
+ # status codes
13
+ # succeeded
14
+ SC_Success = 0
15
+ # timeout
16
+ SC_TimeOut = 10
17
+ # no available jobs
18
+ SC_NoJobs = 20
19
+ # failed
20
+ SC_Failed = 30
21
+ # invalid token
22
+ SC_Invalid = 50
23
+ # invalid role
24
+ SC_Role = 60
25
+ # permission denied
26
+ SC_Perms = 70
27
+ # key missing
28
+ SC_MissKey = 80
29
+ # failure of proxy retrieval
30
+ SC_ProxyError = 90
31
+
32
+
33
+ # response
34
+ class Response:
35
+ # constructor
36
+ def __init__(self, statusCode, errorDialog=None):
37
+ # create data object
38
+ self.data = {"StatusCode": statusCode}
39
+ if errorDialog is not None:
40
+ self.data["errorDialog"] = errorDialog
41
+
42
+ # URL encode
43
+ def encode(self, acceptJson=False):
44
+ if not acceptJson:
45
+ return urlencode(self.data)
46
+ else:
47
+ return {"type": "json", "content": json.dumps(self.data)}
48
+
49
+ # append Node
50
+ def appendNode(self, name, value):
51
+ self.data[name] = value
52
+
53
+ # append job
54
+ def appendJob(self, job, siteMapperCache=None):
55
+ # event service merge
56
+ if EventServiceUtils.isEventServiceMerge(job):
57
+ isEventServiceMerge = True
58
+ else:
59
+ isEventServiceMerge = False
60
+ # PandaID
61
+ self.data["PandaID"] = job.PandaID
62
+ # prodSourceLabel
63
+ self.data["prodSourceLabel"] = job.prodSourceLabel
64
+ # swRelease
65
+ self.data["swRelease"] = job.AtlasRelease
66
+ # homepackage
67
+ self.data["homepackage"] = job.homepackage
68
+ # transformation
69
+ self.data["transformation"] = job.transformation
70
+ # job name
71
+ self.data["jobName"] = job.jobName
72
+ # job definition ID
73
+ self.data["jobDefinitionID"] = job.jobDefinitionID
74
+ # cloud
75
+ self.data["cloud"] = job.cloud
76
+ # files
77
+ strIFiles = ""
78
+ strOFiles = ""
79
+ strDispatch = ""
80
+ strDisToken = ""
81
+ strDisTokenForOutput = ""
82
+ strDestination = ""
83
+ strRealDataset = ""
84
+ strRealDatasetIn = ""
85
+ strProdDBlock = ""
86
+ strDestToken = ""
87
+ strProdToken = ""
88
+ strProdTokenForOutput = ""
89
+ strGUID = ""
90
+ strFSize = ""
91
+ strCheckSum = ""
92
+ strFileDestinationSE = ""
93
+ strScopeIn = ""
94
+ strScopeOut = ""
95
+ strScopeLog = ""
96
+ logFile = ""
97
+ logGUID = ""
98
+ ddmEndPointIn = []
99
+ ddmEndPointOut = []
100
+ noOutput = []
101
+ siteSpec = None
102
+ inDsLfnMap = {}
103
+ inLFNset = set()
104
+ if siteMapperCache is not None:
105
+ siteMapper = siteMapperCache.get_object()
106
+ siteSpec = siteMapper.getSite(job.computingSite)
107
+ # resolve destSE
108
+ try:
109
+ job.destinationSE = siteMapper.resolveNucleus(job.destinationSE)
110
+ for tmpFile in job.Files:
111
+ tmpFile.destinationSE = siteMapper.resolveNucleus(tmpFile.destinationSE)
112
+ except Exception:
113
+ pass
114
+ siteMapperCache.release_object()
115
+ for file in job.Files:
116
+ if file.type == "input":
117
+ if file.lfn in inLFNset:
118
+ pass
119
+ else:
120
+ inLFNset.add(file.lfn)
121
+ if strIFiles != "":
122
+ strIFiles += ","
123
+ strIFiles += file.lfn
124
+ if strDispatch != "":
125
+ strDispatch += ","
126
+ strDispatch += file.dispatchDBlock
127
+ if strDisToken != "":
128
+ strDisToken += ","
129
+ strDisToken += file.dispatchDBlockToken
130
+ strProdDBlock += f"{file.prodDBlock},"
131
+ if not isEventServiceMerge:
132
+ strProdToken += f"{file.prodDBlockToken},"
133
+ else:
134
+ strProdToken += f"{job.metadata[1][file.lfn]},"
135
+ if strGUID != "":
136
+ strGUID += ","
137
+ strGUID += file.GUID
138
+ strRealDatasetIn += f"{file.dataset},"
139
+ strFSize += f"{file.fsize},"
140
+ if file.checksum not in ["", "NULL", None]:
141
+ strCheckSum += f"{file.checksum},"
142
+ else:
143
+ strCheckSum += f"{file.md5sum},"
144
+ strScopeIn += f"{file.scope},"
145
+ ddmEndPointIn.append(
146
+ self.getDdmEndpoint(
147
+ siteSpec,
148
+ file.dispatchDBlockToken,
149
+ "input",
150
+ job.prodSourceLabel,
151
+ job.job_label,
152
+ )
153
+ )
154
+ if file.dataset not in inDsLfnMap:
155
+ inDsLfnMap[file.dataset] = []
156
+ inDsLfnMap[file.dataset].append(file.lfn)
157
+ if file.type == "output" or file.type == "log":
158
+ if strOFiles != "":
159
+ strOFiles += ","
160
+ strOFiles += file.lfn
161
+ if strDestination != "":
162
+ strDestination += ","
163
+ strDestination += file.destinationDBlock
164
+ if strRealDataset != "":
165
+ strRealDataset += ","
166
+ strRealDataset += file.dataset
167
+ strFileDestinationSE += f"{file.destinationSE},"
168
+ if file.type == "log":
169
+ logFile = file.lfn
170
+ logGUID = file.GUID
171
+ strScopeLog = file.scope
172
+ else:
173
+ strScopeOut += f"{file.scope},"
174
+ if strDestToken != "":
175
+ strDestToken += ","
176
+ strDestToken += re.sub("^ddd:", "dst:", file.destinationDBlockToken.split(",")[0])
177
+ strDisTokenForOutput += f"{file.dispatchDBlockToken},"
178
+ strProdTokenForOutput += f"{file.prodDBlockToken},"
179
+ ddmEndPointOut.append(
180
+ self.getDdmEndpoint(
181
+ siteSpec,
182
+ file.destinationDBlockToken.split(",")[0],
183
+ "output",
184
+ job.prodSourceLabel,
185
+ job.job_label,
186
+ )
187
+ )
188
+ if file.isAllowedNoOutput():
189
+ noOutput.append(file.lfn)
190
+ # inFiles
191
+ self.data["inFiles"] = strIFiles
192
+ # dispatch DBlock
193
+ self.data["dispatchDblock"] = strDispatch
194
+ # dispatch DBlock space token
195
+ self.data["dispatchDBlockToken"] = strDisToken
196
+ # dispatch DBlock space token for output
197
+ self.data["dispatchDBlockTokenForOut"] = strDisTokenForOutput[:-1]
198
+ # outFiles
199
+ self.data["outFiles"] = strOFiles
200
+ # destination DBlock
201
+ self.data["destinationDblock"] = strDestination
202
+ # destination DBlock space token
203
+ self.data["destinationDBlockToken"] = strDestToken
204
+ # prod DBlocks
205
+ self.data["prodDBlocks"] = strProdDBlock[:-1]
206
+ # prod DBlock space token
207
+ self.data["prodDBlockToken"] = strProdToken[:-1]
208
+ # real output datasets
209
+ self.data["realDatasets"] = strRealDataset
210
+ # real output datasets
211
+ self.data["realDatasetsIn"] = strRealDatasetIn[:-1]
212
+ # file's destinationSE
213
+ self.data["fileDestinationSE"] = strFileDestinationSE[:-1]
214
+ # log filename
215
+ self.data["logFile"] = logFile
216
+ # log GUID
217
+ self.data["logGUID"] = logGUID
218
+ # jobPars
219
+ self.data["jobPars"], ppSteps = job.extractMultiStepExec()
220
+ if ppSteps is not None:
221
+ self.data.update(ppSteps)
222
+ if job.to_encode_job_params():
223
+ self.data["jobPars"] = base64.b64encode(self.data["jobPars"].encode()).decode()
224
+ # attempt number
225
+ self.data["attemptNr"] = job.attemptNr
226
+ # GUIDs
227
+ self.data["GUID"] = strGUID
228
+ # checksum
229
+ self.data["checksum"] = strCheckSum[:-1]
230
+ # fsize
231
+ self.data["fsize"] = strFSize[:-1]
232
+ # scope
233
+ self.data["scopeIn"] = strScopeIn[:-1]
234
+ self.data["scopeOut"] = strScopeOut[:-1]
235
+ self.data["scopeLog"] = strScopeLog
236
+ # DDM endpoints
237
+ try:
238
+ self.data["ddmEndPointIn"] = ",".join(ddmEndPointIn)
239
+ except TypeError:
240
+ self.data["ddmEndPointIn"] = ""
241
+ try:
242
+ self.data["ddmEndPointOut"] = ",".join(ddmEndPointOut)
243
+ except TypeError:
244
+ self.data["ddmEndPointOut"] = ""
245
+ # destinationSE
246
+ self.data["destinationSE"] = job.destinationSE
247
+ # user ID
248
+ self.data["prodUserID"] = job.prodUserID
249
+ # CPU count
250
+ self.data["maxCpuCount"] = job.maxCpuCount
251
+ # RAM count
252
+ self.data["minRamCount"] = job.minRamCount
253
+ # disk count
254
+ self.data["maxDiskCount"] = job.maxDiskCount
255
+ # cmtconfig
256
+ if ppSteps is None or job.cmtConfig not in ["NULL", None]:
257
+ self.data["cmtConfig"] = job.cmtConfig
258
+ else:
259
+ self.data["cmtConfig"] = ""
260
+ # processingType
261
+ self.data["processingType"] = job.processingType
262
+ # transferType
263
+ self.data["transferType"] = job.transferType
264
+ # sourceSite
265
+ self.data["sourceSite"] = job.sourceSite
266
+ # current priority
267
+ self.data["currentPriority"] = job.currentPriority
268
+ # taskID
269
+ if job.lockedby == "jedi":
270
+ self.data["taskID"] = job.jediTaskID
271
+ else:
272
+ self.data["taskID"] = job.taskID
273
+ # core count
274
+ if job.coreCount in ["NULL", None]:
275
+ self.data["coreCount"] = 1
276
+ else:
277
+ self.data["coreCount"] = job.coreCount
278
+ # jobsetID
279
+ self.data["jobsetID"] = job.jobsetID
280
+ # requestID
281
+ self.data["reqID"] = job.reqID
282
+ # nucleus
283
+ self.data["nucleus"] = job.nucleus
284
+ # walltime
285
+ self.data["maxWalltime"] = job.maxWalltime
286
+ # resource type
287
+ self.data["resource_type"] = job.resource_type
288
+ # looping check
289
+ if job.is_no_looping_check():
290
+ self.data["loopingCheck"] = False
291
+ # debug mode
292
+ if job.is_debug_mode():
293
+ self.data["debug"] = "True"
294
+ # event service or job cloning or fine-grained
295
+ if EventServiceUtils.isJobCloningJob(job):
296
+ self.data["cloneJob"] = EventServiceUtils.getJobCloningType(job)
297
+ elif EventServiceUtils.isEventServiceJob(job) or EventServiceUtils.isJumboJob(job):
298
+ self.data["eventService"] = "True"
299
+ # prod DBlock space token for pre-merging output
300
+ self.data["prodDBlockTokenForOutput"] = strProdTokenForOutput[:-1]
301
+ elif EventServiceUtils.is_fine_grained_job(job):
302
+ self.data["eventService"] = "True"
303
+ # event service merge
304
+ if isEventServiceMerge:
305
+ self.data["eventServiceMerge"] = "True"
306
+ # write to file for ES merge
307
+ writeToFileStr = ""
308
+ try:
309
+ for outputName in job.metadata[0]:
310
+ inputList = job.metadata[0][outputName]
311
+ writeToFileStr += f"inputFor_{outputName}:"
312
+ for tmpInput in inputList:
313
+ writeToFileStr += f"{tmpInput},"
314
+ writeToFileStr = writeToFileStr[:-1]
315
+ writeToFileStr += "^"
316
+ writeToFileStr = writeToFileStr[:-1]
317
+ except Exception:
318
+ pass
319
+ self.data["writeToFile"] = writeToFileStr
320
+ elif job.writeInputToFile():
321
+ try:
322
+ # write input to file
323
+ writeToFileStr = ""
324
+ for inDS in inDsLfnMap:
325
+ inputList = inDsLfnMap[inDS]
326
+ inDS = re.sub("/$", "", inDS)
327
+ inDS = inDS.split(":")[-1]
328
+ writeToFileStr += f"tmpin_{inDS}:"
329
+ writeToFileStr += ",".join(inputList)
330
+ writeToFileStr += "^"
331
+ writeToFileStr = writeToFileStr[:-1]
332
+ self.data["writeToFile"] = writeToFileStr
333
+ except Exception:
334
+ pass
335
+ # replace placeholder
336
+ if EventServiceUtils.isJumboJob(job) or EventServiceUtils.isCoJumboJob(job):
337
+ try:
338
+ for inDS in inDsLfnMap:
339
+ inputList = inDsLfnMap[inDS]
340
+ inDS = re.sub("/$", "", inDS)
341
+ inDS = inDS.split(":")[-1]
342
+ srcStr = f"tmpin__cnt_{inDS}"
343
+ dstStr = ",".join(inputList)
344
+ self.data["jobPars"] = self.data["jobPars"].replace(srcStr, dstStr)
345
+ except Exception:
346
+ pass
347
+ # no output
348
+ if noOutput != []:
349
+ self.data["allowNoOutput"] = ",".join(noOutput)
350
+ # alternative stage-out
351
+ if job.getAltStgOut() is not None:
352
+ self.data["altStageOut"] = job.getAltStgOut()
353
+ # log to OS
354
+ if job.putLogToOS():
355
+ self.data["putLogToOS"] = "True"
356
+ # suppress execute string conversion
357
+ if job.noExecStrCnv():
358
+ self.data["noExecStrCnv"] = "True"
359
+ # in-file positional event number
360
+ if job.inFilePosEvtNum():
361
+ self.data["inFilePosEvtNum"] = "True"
362
+ # use prefetcher
363
+ if job.usePrefetcher():
364
+ self.data["usePrefetcher"] = "True"
365
+ # image name
366
+ if job.container_name not in ["NULL", None]:
367
+ self.data["container_name"] = job.container_name
368
+ # IO
369
+ self.data["ioIntensity"] = job.get_task_attribute("ioIntensity")
370
+ self.data["ioIntensityUnit"] = job.get_task_attribute("ioIntensityUnit")
371
+ # HPO
372
+ if job.is_hpo_workflow():
373
+ self.data["isHPO"] = "True"
374
+ # VP
375
+ if siteSpec is not None:
376
+ scope_input, scope_output = DataServiceUtils.select_scope(siteSpec, job.prodSourceLabel, job.job_label)
377
+ if siteSpec.use_vp(scope_input):
378
+ self.data["useVP"] = "True"
379
+ # on-site merging
380
+ if job.is_on_site_merging():
381
+ self.data["onSiteMerging"] = "True"
382
+
383
+ # set proxy key
384
+ def setProxyKey(self, proxyKey):
385
+ names = ["credname", "myproxy"]
386
+ for name in names:
387
+ if name in proxyKey:
388
+ self.data[name] = proxyKey[name]
389
+ else:
390
+ self.data[name] = ""
391
+
392
+ # set secret key for panda proxy
393
+ def setPandaProxySecretKey(self, secretKey):
394
+ self.data["pandaProxySecretKey"] = secretKey
395
+
396
+ # get ddm endpoint
397
+ def getDdmEndpoint(self, siteSpec, spaceToken, mode, prodSourceLabel, job_label):
398
+ scope_input, scope_output = DataServiceUtils.select_scope(siteSpec, prodSourceLabel, job_label)
399
+ if siteSpec is None or mode not in ["input", "output"]:
400
+ return ""
401
+
402
+ if mode == "input":
403
+ connected_endpoints = siteSpec.ddm_endpoints_input.get(scope_input)
404
+ elif mode == "output":
405
+ connected_endpoints = siteSpec.ddm_endpoints_output.get(scope_output)
406
+
407
+ endPoint = DataServiceUtils.getDestinationSE(spaceToken)
408
+ if endPoint and connected_endpoints and connected_endpoints.isAssociated(endPoint):
409
+ return endPoint
410
+
411
+ endPoint = DataServiceUtils.getDistributedDestination(spaceToken)
412
+ if endPoint and connected_endpoints and connected_endpoints.isAssociated(endPoint):
413
+ return endPoint
414
+
415
+ if mode == "input":
416
+ setokens = siteSpec.setokens_input.get(scope_input, [])
417
+ ddm = siteSpec.ddm_input.get(scope_input)
418
+ elif mode == "output":
419
+ setokens = siteSpec.setokens_output.get(scope_output, [])
420
+ ddm = siteSpec.ddm_output.get(scope_output)
421
+ if spaceToken in setokens:
422
+ return setokens[spaceToken]
423
+
424
+ # Protection against misconfigured sites
425
+ if not ddm:
426
+ ddm = ""
427
+
428
+ return ddm
429
+
430
+
431
+ # check if secure connection
432
+ def isSecure(req):
433
+ if "SSL_CLIENT_S_DN" not in req.subprocess_env:
434
+ return False
435
+ return True
436
+
437
+
438
+ # get user DN
439
+ def getUserDN(req):
440
+ try:
441
+ return req.subprocess_env["SSL_CLIENT_S_DN"]
442
+ except Exception:
443
+ return "None"
@@ -0,0 +1,186 @@
1
+ """
2
+ watch job
3
+
4
+ """
5
+
6
+ import datetime
7
+ import threading
8
+ import time
9
+ import traceback
10
+
11
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
12
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
13
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
14
+
15
+ from pandaserver.dataservice.closer import Closer
16
+ from pandaserver.jobdispatcher import ErrorCode
17
+ from pandaserver.taskbuffer import EventServiceUtils, retryModule
18
+ from pandaserver.taskbuffer.JobSpec import JobSpec
19
+ from pandaserver.taskbuffer.SupErrors import SupErrors
20
+
21
+ # logger
22
+ _logger = PandaLogger().getLogger("Watcher")
23
+
24
+
25
+ class Watcher(threading.Thread):
26
+ # constructor
27
+ def __init__(self, taskBuffer, pandaID, single=False, sleepTime=360, sitemapper=None):
28
+ threading.Thread.__init__(self)
29
+ self.pandaID = pandaID
30
+ self.taskBuffer = taskBuffer
31
+ self.sleepTime = sleepTime
32
+ self.single = single
33
+ self.siteMapper = sitemapper
34
+ self.logger = LogWrapper(_logger, str(pandaID))
35
+
36
+ # main
37
+ def run(self):
38
+ try:
39
+ while True:
40
+ self.logger.debug("start")
41
+ # query job
42
+ job = self.taskBuffer.peekJobs(
43
+ [self.pandaID],
44
+ fromDefined=False,
45
+ fromArchived=False,
46
+ fromWaiting=False,
47
+ )[0]
48
+ # check job status
49
+ if job is None:
50
+ self.logger.debug("escape : not found")
51
+ return
52
+ self.logger.debug(f"in {job.jobStatus}")
53
+ if job.jobStatus not in [
54
+ "running",
55
+ "sent",
56
+ "starting",
57
+ "holding",
58
+ "stagein",
59
+ "stageout",
60
+ ]:
61
+ if job.jobStatus == "transferring" and (job.prodSourceLabel in ["user", "panda"] or job.jobSubStatus not in [None, "NULL", ""]):
62
+ pass
63
+ else:
64
+ self.logger.debug(f"escape : wrong status {job.jobStatus}")
65
+ return
66
+ # time limit
67
+ timeLimit = naive_utcnow() - datetime.timedelta(minutes=self.sleepTime)
68
+ if job.modificationTime < timeLimit or (job.endTime != "NULL" and job.endTime < timeLimit):
69
+ self.logger.debug(f"{job.jobStatus} lastmod:{str(job.modificationTime)} endtime:{str(job.endTime)}")
70
+ destDBList = []
71
+ if job.jobStatus == "sent":
72
+ # sent job didn't receive reply from pilot within 30 min
73
+ job.jobDispatcherErrorCode = ErrorCode.EC_SendError
74
+ job.jobDispatcherErrorDiag = "Sent job didn't receive reply from pilot within 30 min"
75
+ elif job.exeErrorDiag == "NULL" and job.pilotErrorDiag == "NULL":
76
+ # lost heartbeat
77
+ if job.jobDispatcherErrorDiag == "NULL":
78
+ if job.endTime == "NULL":
79
+ # normal lost heartbeat
80
+ job.jobDispatcherErrorCode = ErrorCode.EC_Watcher
81
+ job.jobDispatcherErrorDiag = f"lost heartbeat : {str(job.modificationTime)}"
82
+ else:
83
+ if job.jobStatus == "holding":
84
+ job.jobDispatcherErrorCode = ErrorCode.EC_Holding
85
+ elif job.jobStatus == "transferring":
86
+ job.jobDispatcherErrorCode = ErrorCode.EC_Transferring
87
+ else:
88
+ job.jobDispatcherErrorCode = ErrorCode.EC_Timeout
89
+ job.jobDispatcherErrorDiag = f"timeout in {job.jobStatus} : last heartbeat at {str(job.endTime)}"
90
+ # get worker
91
+ workerSpecs = self.taskBuffer.getWorkersForJob(job.PandaID)
92
+ if len(workerSpecs) > 0:
93
+ workerSpec = workerSpecs[0]
94
+ if workerSpec.status in [
95
+ "finished",
96
+ "failed",
97
+ "cancelled",
98
+ "missed",
99
+ ]:
100
+ job.supErrorCode = SupErrors.error_codes["WORKER_ALREADY_DONE"]
101
+ job.supErrorDiag = f"worker already {workerSpec.status} at {str(workerSpec.endTime)} with {workerSpec.diagMessage}"
102
+ job.supErrorDiag = JobSpec.truncateStringAttr("supErrorDiag", job.supErrorDiag)
103
+ else:
104
+ # job recovery failed
105
+ job.jobDispatcherErrorCode = ErrorCode.EC_Recovery
106
+ job.jobDispatcherErrorDiag = f"job recovery failed for {self.sleepTime / 60} hours"
107
+ # set job status
108
+ job.jobStatus = "failed"
109
+ # set endTime for lost heartbeat
110
+ if job.endTime == "NULL":
111
+ # normal lost heartbeat
112
+ job.endTime = job.modificationTime
113
+ # set files status
114
+ for file in job.Files:
115
+ if file.type == "output" or file.type == "log":
116
+ file.status = "failed"
117
+ if file.destinationDBlock not in destDBList:
118
+ destDBList.append(file.destinationDBlock)
119
+ # event service
120
+ if EventServiceUtils.isEventServiceJob(job) and not EventServiceUtils.isJobCloningJob(job):
121
+ eventStat = self.taskBuffer.getEventStat(job.jediTaskID, job.PandaID)
122
+ # set sub status when no successful events
123
+ if EventServiceUtils.ST_finished not in eventStat:
124
+ job.jobSubStatus = "es_heartbeat"
125
+ # update job
126
+ self.taskBuffer.updateJobs([job], False)
127
+ # start closer
128
+ if job.jobStatus == "failed":
129
+ source = "jobDispatcherErrorCode"
130
+ error_code = job.jobDispatcherErrorCode
131
+ error_diag = job.jobDispatcherErrorDiag
132
+ errors = [
133
+ {
134
+ "source": source,
135
+ "error_code": error_code,
136
+ "error_diag": error_diag,
137
+ }
138
+ ]
139
+
140
+ try:
141
+ self.logger.debug("Watcher will call job_failure_postprocessing")
142
+ retryModule.job_failure_postprocessing(self.taskBuffer, job.PandaID, errors, job.attemptNr)
143
+ self.logger.debug("job_failure_postprocessing is back")
144
+ except Exception as e:
145
+ self.logger.debug(f"job_failure_postprocessing excepted and needs to be investigated ({e}): {traceback.format_exc()}")
146
+
147
+ # updateJobs was successful and it failed a job with taskBufferErrorCode
148
+ try:
149
+ self.logger.debug("Watcher.run will peek the job")
150
+ job_tmp = self.taskBuffer.peekJobs(
151
+ [job.PandaID],
152
+ fromDefined=False,
153
+ fromArchived=True,
154
+ fromWaiting=False,
155
+ )[0]
156
+ if job_tmp.taskBufferErrorCode:
157
+ source = "taskBufferErrorCode"
158
+ error_code = job_tmp.taskBufferErrorCode
159
+ error_diag = job_tmp.taskBufferErrorDiag
160
+ self.logger.debug("Watcher.run 2 will call job_failure_postprocessing")
161
+ retryModule.job_failure_postprocessing(
162
+ self.taskBuffer,
163
+ job_tmp.PandaID,
164
+ source,
165
+ error_code,
166
+ error_diag,
167
+ job_tmp.attemptNr,
168
+ )
169
+ self.logger.debug("job_failure_postprocessing 2 is back")
170
+ except IndexError:
171
+ pass
172
+ except Exception as e:
173
+ self.logger.error(f"job_failure_postprocessing 2 excepted and needs to be investigated ({e}): {traceback.format_exc()}")
174
+
175
+ cThr = Closer(self.taskBuffer, destDBList, job)
176
+ cThr.run()
177
+ self.logger.debug("done")
178
+ return
179
+ # single action
180
+ if self.single:
181
+ return
182
+ # sleep
183
+ time.sleep(60 * self.sleepTime)
184
+ except Exception as e:
185
+ self.logger.error(f"run() : {str(e)} {traceback.format_exc()}")
186
+ return
File without changes
File without changes
@@ -0,0 +1,56 @@
1
+ import json
2
+ import os
3
+ from importlib import import_module
4
+ from urllib.parse import urlparse
5
+
6
+ import uvicorn
7
+ from fastmcp import FastMCP
8
+
9
+ from pandaserver.api.v1.http_client import api_url
10
+ from pandaserver.config import mcp_config
11
+ from pandaserver.pandamcp.mcp_utils import create_tool
12
+
13
+ # remove or overwrite environment variables to avoid server's credential being used
14
+ for var in ["PANDA_AUTH_ID_TOKEN", "PANDA_AUTH_VO", "X509_USER_PROXY"]:
15
+ if var in os.environ:
16
+ del os.environ[var]
17
+ os.environ["X509_USER_PROXY"] = "/dev/null"
18
+
19
+ # extract API's module path
20
+ p = urlparse(api_url)
21
+ api_module_path = ".".join([seg for seg in p.path.strip("/").split("/") if seg])
22
+
23
+ # get a list of API endpoints to expose
24
+ endpoints_to_expose = {}
25
+
26
+ if os.path.exists(mcp_config.endpoint_list_file):
27
+ with open(mcp_config.endpoint_list_file, "r") as f:
28
+ endpoints_to_expose = json.load(f)
29
+
30
+ # add an example endpoint if the file does not exist
31
+ endpoints_to_expose.setdefault("system", [])
32
+ if "is_alive" not in endpoints_to_expose["system"]:
33
+ endpoints_to_expose["system"].append("is_alive")
34
+
35
+ # create FastMCP instance
36
+ main_mcp = FastMCP(name="Main")
37
+
38
+ # add tools
39
+ for mod, func_list in endpoints_to_expose.items():
40
+ api_module = import_module(f"pandaserver.{api_module_path}.{mod}_api")
41
+ for func_name in func_list:
42
+ func = getattr(api_module, func_name)
43
+ tool = create_tool(func)
44
+ main_mcp.add_tool(tool)
45
+
46
+ # get HTTP app
47
+ http_app = main_mcp.http_app(transport=mcp_config.transport)
48
+
49
+ if __name__ == "__main__":
50
+ uvicorn.run(
51
+ http_app,
52
+ host="0.0.0.0",
53
+ port=int(os.getenv("PANDA_SERVER_CONF_PORT_MCP", 25888)),
54
+ ssl_keyfile=mcp_config.ssl_keyfile,
55
+ ssl_certfile=mcp_config.ssl_certfile,
56
+ )