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,1625 @@
1
+ """
2
+ dispatch jobs
3
+
4
+ """
5
+
6
+ import datetime
7
+ import json
8
+ import os
9
+ import re
10
+ import socket
11
+ import sys
12
+ import threading
13
+ import time
14
+ import traceback
15
+ from threading import Lock
16
+
17
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
18
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
19
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
20
+
21
+ from pandaserver.brokerage.SiteMapper import SiteMapper
22
+ from pandaserver.config import panda_config
23
+ from pandaserver.dataservice.adder_gen import AdderGen
24
+ from pandaserver.jobdispatcher import Protocol
25
+ from pandaserver.proxycache import panda_proxy_cache, token_cache
26
+ from pandaserver.srvcore import CoreUtils
27
+
28
+ # logger
29
+ _logger = PandaLogger().getLogger("JobDispatcher")
30
+ _pilotReqLogger = PandaLogger().getLogger("PilotRequests")
31
+
32
+
33
+ # a wrapper to install timeout into a method
34
+ class _TimedMethod:
35
+ def __init__(self, method, timeout):
36
+ self.method = method
37
+ self.timeout = timeout
38
+ self.result = Protocol.TimeOutToken
39
+
40
+ # method emulation
41
+ def __call__(self, *var):
42
+ self.result = self.method(*var)
43
+
44
+ # run
45
+ def run(self, *var):
46
+ thr = threading.Thread(target=self, args=var)
47
+ thr.start()
48
+ thr.join()
49
+
50
+
51
+ # job dispatcher
52
+ class JobDispatcher:
53
+ # constructor
54
+ def __init__(self):
55
+ # taskbuffer
56
+ self.taskBuffer = None
57
+ # datetime of last updated
58
+ self.lastUpdated = naive_utcnow()
59
+ # how frequently update DN/token map
60
+ self.timeInterval = datetime.timedelta(seconds=180)
61
+ # special dispatcher parameters
62
+ self.specialDispatchParams = None
63
+ # site mapper cache
64
+ self.siteMapperCache = None
65
+ # lock
66
+ self.lock = Lock()
67
+ # proxy cacher
68
+ self.proxy_cacher = panda_proxy_cache.MyProxyInterface()
69
+ # token cacher
70
+ self.token_cacher = token_cache.TokenCache()
71
+ # config of token cacher
72
+ try:
73
+ with open(panda_config.token_cache_config) as f:
74
+ self.token_cache_config = json.load(f)
75
+ except Exception:
76
+ self.token_cache_config = {}
77
+
78
+ # set task buffer
79
+ def init(self, taskBuffer):
80
+ # lock
81
+ self.lock.acquire()
82
+ # set TB
83
+ if self.taskBuffer is None:
84
+ self.taskBuffer = taskBuffer
85
+ # special dispatcher parameters
86
+ if self.specialDispatchParams is None:
87
+ self.specialDispatchParams = CoreUtils.CachedObject("dispatcher_params", 60 * 10, self.get_special_dispatch_params, _logger)
88
+ # site mapper cache
89
+ if self.siteMapperCache is None:
90
+ self.siteMapperCache = CoreUtils.CachedObject("site_mapper", 60 * 10, self.getSiteMapper, _logger)
91
+ # release
92
+ self.lock.release()
93
+
94
+ # get special parameters for dispatcher
95
+ def get_special_dispatch_params(self):
96
+ """
97
+ Wrapper function around taskBuffer.get_special_dispatch_params to convert list to set since task buffer cannot return set
98
+ """
99
+ param = self.taskBuffer.get_special_dispatch_params()
100
+ for client_name in param["tokenKeys"]:
101
+ param["tokenKeys"][client_name]["fullList"] = set(param["tokenKeys"][client_name]["fullList"])
102
+ return True, param
103
+
104
+ # set user proxy
105
+ def set_user_proxy(self, response, distinguished_name=None, role=None, tokenized=False) -> tuple[bool, str]:
106
+ """
107
+ Set user proxy to the response
108
+
109
+ :param response: response object
110
+ :param distinguished_name: the distinguished name of the user
111
+ :param role: the role of the user
112
+ :param tokenized: whether the response should contain a token instead of a proxy
113
+
114
+ :return: a tuple containing a boolean indicating success and a message
115
+ """
116
+ try:
117
+ if distinguished_name is None:
118
+ distinguished_name = response.data["prodUserID"]
119
+ # remove redundant extensions
120
+ distinguished_name = CoreUtils.get_bare_dn(distinguished_name, keep_digits=False)
121
+ if not tokenized:
122
+ # get proxy
123
+ output = self.proxy_cacher.retrieve(distinguished_name, role=role)
124
+ else:
125
+ # get token
126
+ output = self.token_cacher.get_access_token(distinguished_name)
127
+ # not found
128
+ if output is None:
129
+ tmp_msg = f"""{"token" if tokenized else "proxy"} not found for {distinguished_name}"""
130
+ response.appendNode("errorDialog", tmp_msg)
131
+ return False, tmp_msg
132
+ # set
133
+ response.appendNode("userProxy", output)
134
+ return True, ""
135
+ except Exception as e:
136
+ tmp_msg = f"""{"token" if tokenized else "proxy"} retrieval failed with {str(e)}"""
137
+ response.appendNode("errorDialog", tmp_msg)
138
+ return False, tmp_msg
139
+
140
+ # get job
141
+ def getJob(
142
+ self,
143
+ siteName,
144
+ prodSourceLabel,
145
+ mem,
146
+ diskSpace,
147
+ node,
148
+ timeout,
149
+ computingElement,
150
+ prodUserID,
151
+ getProxyKey,
152
+ realDN,
153
+ taskID,
154
+ nJobs,
155
+ acceptJson,
156
+ background,
157
+ resourceType,
158
+ harvester_id,
159
+ worker_id,
160
+ schedulerID,
161
+ jobType,
162
+ via_topic,
163
+ remaining_time,
164
+ tmpLog,
165
+ ):
166
+ t_getJob_start = time.time()
167
+ jobs = []
168
+ try:
169
+ tmpNumJobs = int(nJobs)
170
+ except Exception:
171
+ tmpNumJobs = None
172
+ if tmpNumJobs is None:
173
+ tmpNumJobs = 1
174
+
175
+ self.siteMapperCache.update()
176
+ is_gu = self.siteMapperCache.cachedObj.getSite(siteName).is_grandly_unified()
177
+ in_test = self.siteMapperCache.cachedObj.getSite(siteName).status == "test"
178
+
179
+ # change label
180
+ if in_test and prodSourceLabel in ["user", "managed", "unified"]:
181
+ new_label = "test"
182
+ tmpLog.debug(f"prodSourceLabel changed {prodSourceLabel} -> {new_label}")
183
+ prodSourceLabel = new_label
184
+
185
+ # wrapper function for timeout
186
+ tmpWrapper = _TimedMethod(self.taskBuffer.getJobs, timeout)
187
+ tmpWrapper.run(
188
+ tmpNumJobs,
189
+ siteName,
190
+ prodSourceLabel,
191
+ mem,
192
+ diskSpace,
193
+ node,
194
+ timeout,
195
+ computingElement,
196
+ prodUserID,
197
+ taskID,
198
+ background,
199
+ resourceType,
200
+ harvester_id,
201
+ worker_id,
202
+ schedulerID,
203
+ jobType,
204
+ is_gu,
205
+ via_topic,
206
+ remaining_time,
207
+ )
208
+
209
+ if isinstance(tmpWrapper.result, list):
210
+ jobs = jobs + tmpWrapper.result
211
+ # make response
212
+ secrets_map = {}
213
+ if len(jobs) > 0:
214
+ secrets_map = jobs.pop()
215
+ proxyKey = jobs[-1]
216
+ nSent = jobs[-2]
217
+ jobs = jobs[:-2]
218
+ if len(jobs) != 0:
219
+ # succeed
220
+ responseList = []
221
+ # append Jobs
222
+ for tmpJob in jobs:
223
+ try:
224
+ response = Protocol.Response(Protocol.SC_Success)
225
+ response.appendJob(tmpJob, self.siteMapperCache)
226
+ except Exception as e:
227
+ tmpMsg = f"failed to get jobs with {str(e)}"
228
+ tmpLog.error(tmpMsg + "\n" + traceback.format_exc())
229
+ raise
230
+
231
+ # append nSent
232
+ response.appendNode("nSent", nSent)
233
+ # set proxy key
234
+ if getProxyKey:
235
+ response.setProxyKey(proxyKey)
236
+ # set secrets
237
+ if tmpJob.use_secrets() and tmpJob.prodUserName in secrets_map and secrets_map[tmpJob.prodUserName]:
238
+ response.appendNode("secrets", secrets_map[tmpJob.prodUserName])
239
+ if panda_config.pilot_secrets in secrets_map and secrets_map[panda_config.pilot_secrets]:
240
+ response.appendNode("pilotSecrets", secrets_map[panda_config.pilot_secrets])
241
+ # add
242
+ responseList.append(response.data)
243
+ # make response for bulk
244
+ if nJobs is not None:
245
+ try:
246
+ response = Protocol.Response(Protocol.SC_Success)
247
+ if not acceptJson:
248
+ response.appendNode("jobs", json.dumps(responseList))
249
+ else:
250
+ response.appendNode("jobs", responseList)
251
+ except Exception as e:
252
+ tmpMsg = f"failed to make response with {str(e)}"
253
+ tmpLog.error(tmpMsg + "\n" + traceback.format_exc())
254
+ raise
255
+
256
+ else:
257
+ if tmpWrapper.result == Protocol.TimeOutToken:
258
+ # timeout
259
+ if acceptJson:
260
+ response = Protocol.Response(Protocol.SC_TimeOut, "database timeout")
261
+ else:
262
+ response = Protocol.Response(Protocol.SC_TimeOut)
263
+ else:
264
+ # no available jobs
265
+ if acceptJson:
266
+ response = Protocol.Response(Protocol.SC_NoJobs, "no jobs in PanDA")
267
+ else:
268
+ response = Protocol.Response(Protocol.SC_NoJobs)
269
+ _pilotReqLogger.info(f"method=noJob,site={siteName},node={node},type={prodSourceLabel}")
270
+ # return
271
+ tmpLog.debug(f"{siteName} {node} ret -> {response.encode(acceptJson)}")
272
+
273
+ t_getJob_end = time.time()
274
+ t_getJob_spent = t_getJob_end - t_getJob_start
275
+ tmpLog.info(f"siteName={siteName} took timing={t_getJob_spent}s in_test={in_test}")
276
+ return response.encode(acceptJson)
277
+
278
+ # update job status
279
+ def updateJob(
280
+ self,
281
+ jobID,
282
+ jobStatus,
283
+ timeout,
284
+ xml,
285
+ siteName,
286
+ param,
287
+ metadata,
288
+ pilotLog,
289
+ attemptNr=None,
290
+ stdout="",
291
+ acceptJson=False,
292
+ ):
293
+ tmp_logger = LogWrapper(_logger, f"updateJob {jobID}")
294
+
295
+ # store the pilot log
296
+ if pilotLog != "":
297
+ tmp_logger.debug("saving pilot log")
298
+ try:
299
+ self.taskBuffer.storePilotLog(int(jobID), pilotLog)
300
+ tmp_logger.debug("saving pilot log DONE")
301
+ except Exception:
302
+ tmp_logger.debug("saving pilot log FAILED")
303
+
304
+ # add metadata
305
+ if metadata != "":
306
+ ret = self.taskBuffer.addMetadata([jobID], [metadata], [jobStatus])
307
+ if len(ret) > 0 and not ret[0]:
308
+ tmp_logger.debug(f"failed to add metadata")
309
+ # return succeed
310
+ response = Protocol.Response(Protocol.SC_Success)
311
+ return response.encode(acceptJson)
312
+
313
+ # add stdout
314
+ if stdout != "":
315
+ self.taskBuffer.addStdOut(jobID, stdout)
316
+
317
+ # update the job
318
+ tmpStatus = jobStatus
319
+ updateStateChange = False
320
+ if jobStatus == "failed" or jobStatus == "finished":
321
+ tmpStatus = "holding"
322
+ # update stateChangeTime to prevent Watcher from finding this job
323
+ updateStateChange = True
324
+ param["jobDispatcherErrorDiag"] = None
325
+ elif jobStatus in ["holding", "transferring"]:
326
+ param["jobDispatcherErrorDiag"] = f"set to {jobStatus} by the pilot at {naive_utcnow().strftime('%Y-%m-%d %H:%M:%S')}"
327
+ if tmpStatus == "holding":
328
+ tmpWrapper = _TimedMethod(self.taskBuffer.updateJobStatus, None)
329
+ else:
330
+ tmpWrapper = _TimedMethod(self.taskBuffer.updateJobStatus, timeout)
331
+ tmpWrapper.run(jobID, tmpStatus, param, updateStateChange, attemptNr)
332
+
333
+ # make response
334
+ if tmpWrapper.result == Protocol.TimeOutToken:
335
+ # timeout
336
+ response = Protocol.Response(Protocol.SC_TimeOut)
337
+ else:
338
+ if tmpWrapper.result:
339
+ # succeed
340
+ response = Protocol.Response(Protocol.SC_Success)
341
+ result = tmpWrapper.result
342
+ secrets = None
343
+ if isinstance(result, dict):
344
+ if "secrets" in result:
345
+ secrets = result["secrets"]
346
+ result = result["command"]
347
+ # set command
348
+ if isinstance(result, str):
349
+ response.appendNode("command", result)
350
+ if secrets:
351
+ response.appendNode("pilotSecrets", secrets)
352
+ else:
353
+ response.appendNode("command", "NULL")
354
+
355
+ # add output to dataset
356
+ if result not in ["badattemptnr", "alreadydone"] and (jobStatus == "failed" or jobStatus == "finished"):
357
+ adder_gen = AdderGen(self.taskBuffer, jobID, jobStatus, attemptNr)
358
+ adder_gen.dump_file_report(xml, attemptNr)
359
+ del adder_gen
360
+ else:
361
+ response = Protocol.Response(Protocol.SC_Failed)
362
+
363
+ tmp_logger.debug(f"ret -> {response.encode(acceptJson)}")
364
+ return response.encode(acceptJson)
365
+
366
+ # get job status
367
+ def getStatus(self, strIDs, timeout):
368
+ # convert str to list
369
+ ids = strIDs.split()
370
+ # peek jobs
371
+ tmpWrapper = _TimedMethod(self.taskBuffer.peekJobs, timeout)
372
+ tmpWrapper.run(ids, False, True, True, False)
373
+ # make response
374
+ if tmpWrapper.result == Protocol.TimeOutToken:
375
+ # timeout
376
+ response = Protocol.Response(Protocol.SC_TimeOut)
377
+ else:
378
+ if isinstance(tmpWrapper.result, list):
379
+ # succeed
380
+ response = Protocol.Response(Protocol.SC_Success)
381
+ # make return
382
+ retStr = ""
383
+ attStr = ""
384
+ for job in tmpWrapper.result:
385
+ if job is None:
386
+ retStr += f"notfound+"
387
+ attStr += "0+"
388
+ else:
389
+ retStr += f"{job.jobStatus}+"
390
+ attStr += f"{job.attemptNr}+"
391
+ response.appendNode("status", retStr[:-1])
392
+ response.appendNode("attemptNr", attStr[:-1])
393
+ else:
394
+ # failed
395
+ response = Protocol.Response(Protocol.SC_Failed)
396
+ _logger.debug(f"getStatus : {strIDs} ret -> {response.encode()}")
397
+ return response.encode()
398
+
399
+ # check job status
400
+ def checkJobStatus(self, pandaIDs, timeout):
401
+ tmpWrapper = _TimedMethod(self.taskBuffer.checkJobStatus, timeout)
402
+ tmpWrapper.run(pandaIDs)
403
+ # make response
404
+ if tmpWrapper.result == Protocol.TimeOutToken:
405
+ # timeout
406
+ response = Protocol.Response(Protocol.SC_TimeOut)
407
+ else:
408
+ if isinstance(tmpWrapper.result, list):
409
+ # succeed
410
+ response = Protocol.Response(Protocol.SC_Success)
411
+ response.appendNode("data", tmpWrapper.result)
412
+ else:
413
+ # failed
414
+ response = Protocol.Response(Protocol.SC_Failed)
415
+ _logger.debug(f"checkJobStatus : {pandaIDs} ret -> {response.encode(True)}")
416
+ return response.encode(True)
417
+
418
+ # get a list of event ranges for a PandaID
419
+ def getEventRanges(
420
+ self,
421
+ pandaID,
422
+ jobsetID,
423
+ jediTaskID,
424
+ nRanges,
425
+ timeout,
426
+ acceptJson,
427
+ scattered,
428
+ segment_id,
429
+ ):
430
+ tmpWrapper = _TimedMethod(self.taskBuffer.getEventRanges, timeout)
431
+ tmpWrapper.run(pandaID, jobsetID, jediTaskID, nRanges, acceptJson, scattered, segment_id)
432
+ # make response
433
+ if tmpWrapper.result == Protocol.TimeOutToken:
434
+ # timeout
435
+ response = Protocol.Response(Protocol.SC_TimeOut)
436
+ else:
437
+ if tmpWrapper.result is not None:
438
+ # succeed
439
+ response = Protocol.Response(Protocol.SC_Success)
440
+ # make return
441
+ response.appendNode("eventRanges", tmpWrapper.result)
442
+ else:
443
+ # failed
444
+ response = Protocol.Response(Protocol.SC_Failed)
445
+ _logger.debug(f"getEventRanges : {pandaID} ret -> {response.encode(acceptJson)}")
446
+ return response.encode(acceptJson)
447
+
448
+ # update an event range
449
+ def updateEventRange(
450
+ self,
451
+ eventRangeID,
452
+ eventStatus,
453
+ coreCount,
454
+ cpuConsumptionTime,
455
+ objstoreID,
456
+ timeout,
457
+ ):
458
+ tmpWrapper = _TimedMethod(self.taskBuffer.updateEventRange, timeout)
459
+ tmpWrapper.run(eventRangeID, eventStatus, coreCount, cpuConsumptionTime, objstoreID)
460
+ # make response
461
+ _logger.debug(str(tmpWrapper.result))
462
+ if tmpWrapper.result == Protocol.TimeOutToken:
463
+ # timeout
464
+ response = Protocol.Response(Protocol.SC_TimeOut)
465
+ else:
466
+ if tmpWrapper.result[0] is True:
467
+ # succeed
468
+ response = Protocol.Response(Protocol.SC_Success)
469
+ response.appendNode("Command", tmpWrapper.result[1])
470
+ else:
471
+ # failed
472
+ response = Protocol.Response(Protocol.SC_Failed)
473
+ _logger.debug(f"updateEventRange : {eventRangeID} ret -> {response.encode()}")
474
+ return response.encode()
475
+
476
+ # update event ranges
477
+ def updateEventRanges(self, eventRanges, timeout, acceptJson, version):
478
+ tmpWrapper = _TimedMethod(self.taskBuffer.updateEventRanges, timeout)
479
+ tmpWrapper.run(eventRanges, version)
480
+ # make response
481
+ if tmpWrapper.result == Protocol.TimeOutToken:
482
+ # timeout
483
+ response = Protocol.Response(Protocol.SC_TimeOut)
484
+ else:
485
+ # succeed
486
+ response = Protocol.Response(Protocol.SC_Success)
487
+ # make return
488
+ response.appendNode("Returns", tmpWrapper.result[0])
489
+ response.appendNode("Command", tmpWrapper.result[1])
490
+ _logger.debug(f"updateEventRanges : ret -> {response.encode(acceptJson)}")
491
+ return response.encode(acceptJson)
492
+
493
+ # check event availability
494
+ def checkEventsAvailability(self, pandaID, jobsetID, jediTaskID, timeout):
495
+ tmpWrapper = _TimedMethod(self.taskBuffer.checkEventsAvailability, timeout)
496
+ tmpWrapper.run(pandaID, jobsetID, jediTaskID)
497
+ # make response
498
+ if tmpWrapper.result == Protocol.TimeOutToken:
499
+ # timeout
500
+ response = Protocol.Response(Protocol.SC_TimeOut)
501
+ else:
502
+ if tmpWrapper.result is not None:
503
+ # succeed
504
+ response = Protocol.Response(Protocol.SC_Success)
505
+ # make return
506
+ response.appendNode("nEventRanges", tmpWrapper.result)
507
+ else:
508
+ # failed
509
+ response = Protocol.Response(Protocol.SC_Failed)
510
+ _logger.debug(f"checkEventsAvailability : {pandaID} ret -> {response.encode(True)}")
511
+ return response.encode(True)
512
+
513
+ # get key pair
514
+ def getKeyPair(self, realDN, publicKeyName, privateKeyName, acceptJson):
515
+ tmpMsg = f"getKeyPair {publicKeyName}/{privateKeyName} json={acceptJson}: "
516
+ if realDN is None:
517
+ # cannot extract DN
518
+ tmpMsg += "failed since DN cannot be extracted"
519
+ _logger.debug(tmpMsg)
520
+ response = Protocol.Response(Protocol.SC_Perms, "Cannot extract DN from proxy. not HTTPS?")
521
+ else:
522
+ # get compact DN
523
+ compactDN = CoreUtils.clean_user_id(realDN)
524
+ # check permission
525
+ self.specialDispatchParams.update()
526
+ allowKey = self.specialDispatchParams.get("allowKeyPair", [])
527
+ if compactDN not in allowKey:
528
+ # permission denied
529
+ tmpMsg += f"failed since '{compactDN}' not authorized with 'k' in {panda_config.schemaMETA}.USERS.GRIDPREF"
530
+ _logger.debug(tmpMsg)
531
+ response = Protocol.Response(Protocol.SC_Perms, tmpMsg)
532
+ else:
533
+ # look for key pair
534
+ if "keyPair" not in self.specialDispatchParams:
535
+ keyPair = {}
536
+ else:
537
+ keyPair = self.specialDispatchParams["keyPair"]
538
+ notFound = False
539
+ if publicKeyName not in keyPair:
540
+ # public key is missing
541
+ notFound = True
542
+ tmpMsg += f"failed for '{compactDN}' since {publicKeyName} is missing on {socket.getfqdn()}"
543
+ elif privateKeyName not in keyPair:
544
+ # private key is missing
545
+ notFound = True
546
+ tmpMsg += f"failed for '{compactDN}' since {privateKeyName} is missing on {socket.getfqdn()}"
547
+ if notFound:
548
+ # private or public key is missing
549
+ _logger.debug(tmpMsg)
550
+ response = Protocol.Response(Protocol.SC_MissKey, tmpMsg)
551
+ else:
552
+ # key pair is available
553
+ response = Protocol.Response(Protocol.SC_Success)
554
+ response.appendNode("publicKey", keyPair[publicKeyName])
555
+ response.appendNode("privateKey", keyPair[privateKeyName])
556
+ tmpMsg += f"sent key-pair to '{compactDN}'"
557
+ _logger.debug(tmpMsg)
558
+ # return
559
+ return response.encode(acceptJson)
560
+
561
+ # get a token key
562
+ def get_token_key(self, distinguished_name, client_name, accept_json):
563
+ tmp_log = LogWrapper(_logger, f"get_token_key client={client_name} PID={os.getpid()}")
564
+ if distinguished_name is None:
565
+ # cannot extract DN
566
+ tmp_msg = "failed since DN cannot be extracted. non-HTTPS?"
567
+ tmp_log.debug(tmp_msg)
568
+ response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
569
+ else:
570
+ # get compact DN
571
+ compact_name = CoreUtils.clean_user_id(distinguished_name)
572
+ # check permission
573
+ self.specialDispatchParams.update()
574
+ allowed_users = self.specialDispatchParams.get("allowTokenKey", [])
575
+ if compact_name not in allowed_users:
576
+ # permission denied
577
+ tmp_msg = f"denied since '{compact_name}' not authorized with 't' in {panda_config.schemaMETA}.USERS.GRIDPREF"
578
+ tmp_log.debug(tmp_msg)
579
+ response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
580
+ else:
581
+ # get a token key
582
+ if client_name not in self.specialDispatchParams["tokenKeys"]:
583
+ # token key is missing
584
+ tmp_msg = f"token key is missing for '{client_name}'"
585
+ tmp_log.debug(tmp_msg)
586
+ response = Protocol.Response(Protocol.SC_MissKey, tmp_msg)
587
+ else:
588
+ # token key is available
589
+ response = Protocol.Response(Protocol.SC_Success)
590
+ response.appendNode("tokenKey", self.specialDispatchParams["tokenKeys"][client_name]["latest"])
591
+ tmp_msg = f"sent token key to '{compact_name}'"
592
+ tmp_log.debug(tmp_msg)
593
+ # return
594
+ return response.encode(accept_json)
595
+
596
+ # get site mapper
597
+ def getSiteMapper(self):
598
+ return True, SiteMapper(self.taskBuffer)
599
+
600
+ def getCommands(self, harvester_id, n_commands, timeout, accept_json):
601
+ """
602
+ Get commands for a particular harvester instance
603
+ """
604
+ tmp_wrapper = _TimedMethod(self.taskBuffer.getCommands, timeout)
605
+ tmp_wrapper.run(harvester_id, n_commands)
606
+
607
+ # Make response
608
+ if tmp_wrapper.result == Protocol.TimeOutToken:
609
+ # timeout
610
+ response = Protocol.Response(Protocol.SC_TimeOut)
611
+ else:
612
+ # success
613
+ response = Protocol.Response(Protocol.SC_Success)
614
+ response.appendNode("Returns", tmp_wrapper.result[0])
615
+ response.appendNode("Commands", tmp_wrapper.result[1])
616
+
617
+ _logger.debug(f"getCommands : ret -> {response.encode(accept_json)}")
618
+ return response.encode(accept_json)
619
+
620
+ def ackCommands(self, command_ids, timeout, accept_json):
621
+ """
622
+ Acknowledge the commands from a list of IDs
623
+ """
624
+ _logger.debug(f"command_ids : {command_ids}")
625
+ tmp_wrapper = _TimedMethod(self.taskBuffer.ackCommands, timeout)
626
+ tmp_wrapper.run(command_ids)
627
+
628
+ # Make response
629
+ if tmp_wrapper.result == Protocol.TimeOutToken:
630
+ # timeout
631
+ response = Protocol.Response(Protocol.SC_TimeOut)
632
+ else:
633
+ # success
634
+ response = Protocol.Response(Protocol.SC_Success)
635
+ response.appendNode("Returns", tmp_wrapper.result)
636
+
637
+ _logger.debug(f"ackCommands : ret -> {response.encode(accept_json)}")
638
+ return response.encode(accept_json)
639
+
640
+ def getResourceTypes(self, timeout, accept_json):
641
+ """
642
+ Get resource types (SCORE, MCORE, SCORE_HIMEM, MCORE_HIMEM) and their definitions
643
+ """
644
+ tmp_wrapper = _TimedMethod(self.taskBuffer.getResourceTypes, timeout)
645
+ tmp_wrapper.run()
646
+
647
+ # Make response
648
+ if tmp_wrapper.result == Protocol.TimeOutToken:
649
+ # timeout
650
+ response = Protocol.Response(Protocol.SC_TimeOut)
651
+ else:
652
+ # success
653
+ response = Protocol.Response(Protocol.SC_Success)
654
+ response.appendNode("Returns", 0)
655
+ response.appendNode("ResourceTypes", tmp_wrapper.result)
656
+
657
+ _logger.debug(f"getResourceTypes : ret -> {response.encode(accept_json)}")
658
+ return response.encode(accept_json)
659
+
660
+ # get proxy
661
+ def get_proxy(self, real_distinguished_name: str, role: str | None, target_distinguished_name: str | None, tokenized: bool, token_key: str | None) -> dict:
662
+ """
663
+ Get proxy for a user with a role
664
+
665
+ :param real_distinguished_name: actual distinguished name of the user
666
+ :param role: role of the user
667
+ :param target_distinguished_name: target distinguished name if the user wants to get proxy for someone else.
668
+ This is one of client_name defined in token_cache_config when getting a token
669
+ :param tokenized: whether the response should contain a token instead of a proxy
670
+ :param token_key: key to get the token from the token cache
671
+
672
+ :return: response in dictionary
673
+ """
674
+ if target_distinguished_name is None:
675
+ target_distinguished_name = real_distinguished_name
676
+ tmp_log = LogWrapper(_logger, f"get_proxy PID={os.getpid()}")
677
+ tmp_msg = f"""start DN="{real_distinguished_name}" role={role} target="{target_distinguished_name}" tokenized={tokenized} token_key={token_key}"""
678
+ tmp_log.debug(tmp_msg)
679
+ if real_distinguished_name is None:
680
+ # cannot extract DN
681
+ tmp_msg = "failed since DN cannot be extracted"
682
+ tmp_log.debug(tmp_msg)
683
+ response = Protocol.Response(Protocol.SC_Perms, "Cannot extract DN from proxy. not HTTPS?")
684
+ else:
685
+ # get compact DN
686
+ compact_name = CoreUtils.clean_user_id(real_distinguished_name)
687
+ # check permission
688
+ self.specialDispatchParams.update()
689
+ if "allowProxy" not in self.specialDispatchParams:
690
+ allowed_names = []
691
+ else:
692
+ allowed_names = self.specialDispatchParams["allowProxy"]
693
+ if compact_name not in allowed_names:
694
+ # permission denied
695
+ tmp_msg = f"failed since '{compact_name}' not in the authorized user list who have 'p' in {panda_config.schemaMETA}.USERS.GRIDPREF "
696
+ if not tokenized:
697
+ tmp_msg += "to get proxy"
698
+ else:
699
+ tmp_msg += "to get access token"
700
+ tmp_log.debug(tmp_msg)
701
+ response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
702
+ elif (
703
+ tokenized
704
+ and target_distinguished_name in self.token_cache_config
705
+ and self.token_cache_config[target_distinguished_name].get("use_token_key") is True
706
+ and (
707
+ target_distinguished_name not in self.specialDispatchParams["tokenKeys"]
708
+ or token_key not in self.specialDispatchParams["tokenKeys"][target_distinguished_name]["fullList"]
709
+ )
710
+ ):
711
+ # invalid token key
712
+ tmp_msg = f"failed since token key is invalid for {target_distinguished_name}"
713
+ tmp_log.debug(tmp_msg)
714
+ response = Protocol.Response(Protocol.SC_Invalid, tmp_msg)
715
+ else:
716
+ # get proxy
717
+ response = Protocol.Response(Protocol.SC_Success, "")
718
+ tmp_status, tmp_msg = self.set_user_proxy(response, target_distinguished_name, role, tokenized)
719
+ if not tmp_status:
720
+ tmp_log.debug(tmp_msg)
721
+ response.appendNode("StatusCode", Protocol.SC_ProxyError)
722
+ else:
723
+ tmp_msg = "successful sent proxy"
724
+ tmp_log.debug(tmp_msg)
725
+ # return
726
+ return response.encode(True)
727
+
728
+ # get active job attribute
729
+ def getActiveJobAttributes(self, pandaID, attrs):
730
+ return self.taskBuffer.getActiveJobAttributes(pandaID, attrs)
731
+
732
+ # update job status
733
+ def updateWorkerPilotStatus(self, workerID, harvesterID, status, timeout, accept_json, node_id):
734
+ tmp_wrapper = _TimedMethod(self.taskBuffer.updateWorkerPilotStatus, timeout)
735
+ tmp_wrapper.run(workerID, harvesterID, status, node_id)
736
+
737
+ # make response
738
+ if tmp_wrapper.result == Protocol.TimeOutToken:
739
+ response = Protocol.Response(Protocol.SC_TimeOut)
740
+ else:
741
+ if tmp_wrapper.result: # success
742
+ response = Protocol.Response(Protocol.SC_Success)
743
+ else: # error
744
+ response = Protocol.Response(Protocol.SC_Failed)
745
+ _logger.debug(f"updateWorkerPilotStatus : {workerID} {harvesterID} {status} ret -> {response.encode(accept_json)}")
746
+ return response.encode(accept_json)
747
+
748
+ # get max workerID
749
+ def get_max_worker_id(self, harvester_id):
750
+ id = self.taskBuffer.get_max_worker_id(harvester_id)
751
+ return json.dumps(id)
752
+
753
+ def get_events_status(self, ids):
754
+ ret = self.taskBuffer.get_events_status(ids)
755
+ return json.dumps(ret)
756
+
757
+
758
+ # Singleton
759
+ jobDispatcher = JobDispatcher()
760
+ del JobDispatcher
761
+
762
+
763
+ # get FQANs
764
+ def _getFQAN(req):
765
+ fqans = []
766
+ for tmp_key in req.subprocess_env:
767
+ tmp_value = req.subprocess_env[tmp_key]
768
+ # Scan VOMS attributes
769
+ # compact style
770
+ if tmp_key.startswith("GRST_CRED_") and tmp_value.startswith("VOMS"):
771
+ fqan = tmp_value.split()[-1]
772
+ fqans.append(fqan)
773
+
774
+ # old style
775
+ elif tmp_key.startswith("GRST_CONN_"):
776
+ tmp_items = tmp_value.split(":")
777
+ if len(tmp_items) == 2 and tmp_items[0] == "fqan":
778
+ fqans.append(tmp_items[-1])
779
+
780
+ return fqans
781
+
782
+
783
+ # check role
784
+ def _checkRole(fqans, dn, withVomsPatch=True):
785
+ production_manager = False
786
+ try:
787
+ # VOMS attributes of production and pilot roles
788
+ production_attributes = [
789
+ "/atlas/usatlas/Role=production",
790
+ "/atlas/usatlas/Role=pilot",
791
+ "/atlas/Role=production",
792
+ "/atlas/Role=pilot",
793
+ "/osg/Role=pilot",
794
+ "^/[^/]+/Role=production$",
795
+ "/ams/Role=pilot",
796
+ "/Engage/LBNE/Role=pilot",
797
+ ]
798
+ if withVomsPatch:
799
+ # FIXME once http://savannah.cern.ch/bugs/?47136 is solved
800
+ production_attributes += ["/atlas/", "/osg/", "/cms/", "/ams/", "/Engage/LBNE/"]
801
+
802
+ for fqan in fqans:
803
+ # check atlas/usatlas production role
804
+ if any(fqan.startswith(role_pattern) or re.search(role_pattern, fqan) for role_pattern in production_attributes):
805
+ production_manager = True
806
+ break
807
+
808
+ # escape
809
+ if production_manager:
810
+ break
811
+
812
+ # check DN with pilot owners
813
+ if not production_manager and dn not in [None]:
814
+ for owner in set(panda_config.production_dns).union(panda_config.pilot_owners):
815
+ if owner and re.search(owner, dn) is not None:
816
+ production_manager = True
817
+ break
818
+
819
+ except Exception:
820
+ pass
821
+
822
+ return production_manager
823
+
824
+
825
+ # get DN
826
+ def _getDN(req):
827
+ realDN = None
828
+ if "SSL_CLIENT_S_DN" in req.subprocess_env:
829
+ realDN = req.subprocess_env["SSL_CLIENT_S_DN"]
830
+ # remove redundant CN
831
+ realDN = CoreUtils.get_bare_dn(realDN, keep_proxy=True)
832
+ # return
833
+ return realDN
834
+
835
+
836
+ """
837
+ web service interface
838
+
839
+ """
840
+
841
+
842
+ # get job
843
+ def getJob(
844
+ req,
845
+ siteName,
846
+ token=None,
847
+ timeout=60,
848
+ cpu=None,
849
+ mem=None,
850
+ diskSpace=None,
851
+ prodSourceLabel=None,
852
+ node=None,
853
+ computingElement=None,
854
+ AtlasRelease=None,
855
+ prodUserID=None,
856
+ getProxyKey=None,
857
+ countryGroup=None,
858
+ workingGroup=None,
859
+ allowOtherCountry=None,
860
+ taskID=None,
861
+ nJobs=None,
862
+ background=None,
863
+ resourceType=None,
864
+ harvester_id=None,
865
+ worker_id=None,
866
+ schedulerID=None,
867
+ jobType=None,
868
+ viaTopic=None,
869
+ remaining_time=None,
870
+ ):
871
+ tmpLog = LogWrapper(_logger, f"getJob {naive_utcnow().isoformat('/')}")
872
+ tmpLog.debug(siteName)
873
+ # get DN
874
+ realDN = _getDN(req)
875
+ # get FQANs
876
+ fqans = _getFQAN(req)
877
+ # check production role
878
+ if getProxyKey == "True":
879
+ # don't use /atlas to prevent normal proxy getting credname
880
+ prodManager = _checkRole(fqans, realDN, False)
881
+ else:
882
+ prodManager = _checkRole(fqans, realDN)
883
+ # set DN for non-production user
884
+ if not prodManager:
885
+ prodUserID = realDN
886
+ # allow getProxyKey for production role
887
+ if getProxyKey == "True" and prodManager:
888
+ getProxyKey = True
889
+ else:
890
+ getProxyKey = False
891
+ # convert mem, diskSpace, remaining_time
892
+ try:
893
+ mem = int(float(mem))
894
+ if mem < 0:
895
+ mem = 0
896
+ except Exception:
897
+ mem = 0
898
+ try:
899
+ diskSpace = int(float(diskSpace))
900
+ if diskSpace < 0:
901
+ diskSpace = 0
902
+ except Exception:
903
+ diskSpace = 0
904
+ try:
905
+ remaining_time = max(0, int(remaining_time))
906
+ except Exception:
907
+ remaining_time = 0
908
+ if background == "True":
909
+ background = True
910
+ else:
911
+ background = False
912
+ if viaTopic == "True":
913
+ viaTopic = True
914
+ else:
915
+ viaTopic = False
916
+ tmpLog.debug(
917
+ f"{siteName},nJobs={nJobs},cpu={cpu},mem={mem},disk={diskSpace},source_label={prodSourceLabel},"
918
+ f"node={node},ce={computingElement},rel={AtlasRelease},user={prodUserID},proxy={getProxyKey},"
919
+ f"c_group={countryGroup},w_group={workingGroup},{allowOtherCountry},taskID={taskID},DN={realDN},"
920
+ f"role={prodManager},FQAN={str(fqans)},json={req.acceptJson()},"
921
+ f"bg={background},rt={resourceType},harvester_id={harvester_id},worker_id={worker_id},"
922
+ f"schedulerID={schedulerID},jobType={jobType},viaTopic={viaTopic} remaining_time={remaining_time}"
923
+ )
924
+ try:
925
+ dummyNumSlots = int(nJobs)
926
+ except Exception:
927
+ dummyNumSlots = 1
928
+ if dummyNumSlots > 1:
929
+ for iSlots in range(dummyNumSlots):
930
+ _pilotReqLogger.info(f"method=getJob,site={siteName},node={node}_{iSlots},type={prodSourceLabel}")
931
+ else:
932
+ _pilotReqLogger.info(f"method=getJob,site={siteName},node={node},type={prodSourceLabel}")
933
+ # invalid role
934
+ if (not prodManager) and (prodSourceLabel not in ["user"]):
935
+ tmpLog.warning("invalid role")
936
+ if req.acceptJson():
937
+ tmpMsg = "no production/pilot role in VOMS FQANs or non pilot owner"
938
+ else:
939
+ tmpMsg = None
940
+ return Protocol.Response(Protocol.SC_Role, tmpMsg).encode(req.acceptJson())
941
+ # invoke job dispatcher
942
+ return jobDispatcher.getJob(
943
+ siteName,
944
+ prodSourceLabel,
945
+ mem,
946
+ diskSpace,
947
+ node,
948
+ int(timeout),
949
+ computingElement,
950
+ prodUserID,
951
+ getProxyKey,
952
+ realDN,
953
+ taskID,
954
+ nJobs,
955
+ req.acceptJson(),
956
+ background,
957
+ resourceType,
958
+ harvester_id,
959
+ worker_id,
960
+ schedulerID,
961
+ jobType,
962
+ viaTopic,
963
+ remaining_time,
964
+ tmpLog,
965
+ )
966
+
967
+
968
+ # update job status
969
+ def updateJob(
970
+ req,
971
+ jobId,
972
+ state,
973
+ token=None,
974
+ transExitCode=None,
975
+ pilotErrorCode=None,
976
+ pilotErrorDiag=None,
977
+ timestamp=None,
978
+ timeout=60,
979
+ xml="",
980
+ node=None,
981
+ workdir=None,
982
+ cpuConsumptionTime=None,
983
+ cpuConsumptionUnit=None,
984
+ remainingSpace=None,
985
+ schedulerID=None,
986
+ pilotID=None,
987
+ siteName=None,
988
+ messageLevel=None,
989
+ pilotLog="",
990
+ metaData="",
991
+ cpuConversionFactor=None,
992
+ exeErrorCode=None,
993
+ exeErrorDiag=None,
994
+ pilotTiming=None,
995
+ computingElement=None,
996
+ startTime=None,
997
+ endTime=None,
998
+ nEvents=None,
999
+ nInputFiles=None,
1000
+ batchID=None,
1001
+ attemptNr=None,
1002
+ jobMetrics=None,
1003
+ stdout="",
1004
+ jobSubStatus=None,
1005
+ coreCount=None,
1006
+ maxRSS=None,
1007
+ maxVMEM=None,
1008
+ maxSWAP=None,
1009
+ maxPSS=None,
1010
+ avgRSS=None,
1011
+ avgVMEM=None,
1012
+ avgSWAP=None,
1013
+ avgPSS=None,
1014
+ totRCHAR=None,
1015
+ totWCHAR=None,
1016
+ totRBYTES=None,
1017
+ totWBYTES=None,
1018
+ rateRCHAR=None,
1019
+ rateWCHAR=None,
1020
+ rateRBYTES=None,
1021
+ rateWBYTES=None,
1022
+ corruptedFiles=None,
1023
+ meanCoreCount=None,
1024
+ cpu_architecture_level=None,
1025
+ grid=None,
1026
+ source_site=None,
1027
+ destination_site=None,
1028
+ ):
1029
+ tmp_log = LogWrapper(_logger, f"updateJob PandaID={jobId} PID={os.getpid()}")
1030
+ tmp_log.debug("start")
1031
+
1032
+ # get DN, FQANs and roles
1033
+ realDN = _getDN(req)
1034
+ fqans = _getFQAN(req)
1035
+ prodManager = _checkRole(fqans, realDN)
1036
+ acceptJson = req.acceptJson()
1037
+
1038
+ _logger.debug(
1039
+ f"updateJob({jobId},status={state},transExitCode={transExitCode},pilotErrorCode={pilotErrorCode},pilotErrorDiag={pilotErrorDiag},node={node},workdir={workdir},"
1040
+ f"cpuConsumptionTime={cpuConsumptionTime},cpuConsumptionUnit={cpuConsumptionUnit},cpu_architecture_level={cpu_architecture_level},remainingSpace={remainingSpace},"
1041
+ f"schedulerID={schedulerID},pilotID={pilotID},siteName={siteName},messageLevel={messageLevel},nEvents={nEvents},nInputFiles={nInputFiles},cpuConversionFactor={cpuConversionFactor},"
1042
+ f"exeErrorCode={exeErrorCode},exeErrorDiag={exeErrorDiag},pilotTiming={pilotTiming},computingElement={computingElement},startTime={startTime},endTime={endTime},batchID={batchID},"
1043
+ f"attemptNr:{attemptNr},jobSubStatus:{jobSubStatus},core:{coreCount},DN:{realDN},role:{prodManager},"
1044
+ f"FQAN:{fqans},maxRSS={maxRSS},maxVMEM={maxVMEM},maxSWAP={maxSWAP},"
1045
+ f"maxPSS={maxPSS},avgRSS={avgRSS},avgVMEM={avgVMEM},avgSWAP={avgSWAP},avgPSS={avgPSS},"
1046
+ f"totRCHAR={totRCHAR},totWCHAR={totWCHAR},totRBYTES={totRBYTES},totWBYTES={totWBYTES},rateRCHAR={rateRCHAR},"
1047
+ f"rateWCHAR={rateWCHAR},rateRBYTES={rateRBYTES},rateWBYTES={rateWBYTES},meanCoreCount={meanCoreCount},"
1048
+ f"grid={grid},source_site={source_site},destination_site={destination_site},"
1049
+ f"corruptedFiles={corruptedFiles}\n==XML==\n{xml}\n==LOG==\n{pilotLog[:1024]}\n==Meta==\n{metaData[:1024]}\n"
1050
+ f"==Metrics==\n{jobMetrics}\n==stdout==\n{stdout})"
1051
+ )
1052
+
1053
+ _pilotReqLogger.debug(f"method=updateJob,site={siteName},node={node},type=None")
1054
+
1055
+ # invalid role
1056
+ if not prodManager:
1057
+ tmp_log.warning(f"invalid role")
1058
+ tmpMsg = None
1059
+ if acceptJson:
1060
+ tmpMsg = "no production/pilot role in VOMS FQANs or non pilot owner"
1061
+ return Protocol.Response(Protocol.SC_Role, tmpMsg).encode(acceptJson)
1062
+
1063
+ # aborting message
1064
+ if jobId == "NULL":
1065
+ return Protocol.Response(Protocol.SC_Success).encode(acceptJson)
1066
+
1067
+ # check status
1068
+ if state not in [
1069
+ "running",
1070
+ "failed",
1071
+ "finished",
1072
+ "holding",
1073
+ "starting",
1074
+ "transferring",
1075
+ ]:
1076
+ tmp_log.warning(f"invalid state={state} for updateJob")
1077
+ return Protocol.Response(Protocol.SC_Success).encode(acceptJson)
1078
+
1079
+ # create parameter map
1080
+ param = {}
1081
+ if cpuConsumptionTime not in [None, ""]:
1082
+ param["cpuConsumptionTime"] = cpuConsumptionTime
1083
+ if cpuConsumptionUnit is not None:
1084
+ param["cpuConsumptionUnit"] = cpuConsumptionUnit
1085
+ if cpu_architecture_level:
1086
+ try:
1087
+ param["cpu_architecture_level"] = cpu_architecture_level[:20]
1088
+ except Exception:
1089
+ tmp_log.error(f"invalid cpu_architecture_level={cpu_architecture_level} for updateJob")
1090
+ pass
1091
+ if node is not None:
1092
+ param["modificationHost"] = node[:128]
1093
+ if transExitCode is not None:
1094
+ try:
1095
+ int(transExitCode)
1096
+ param["transExitCode"] = transExitCode
1097
+ except Exception:
1098
+ pass
1099
+ if pilotErrorCode is not None:
1100
+ try:
1101
+ int(pilotErrorCode)
1102
+ param["pilotErrorCode"] = pilotErrorCode
1103
+ except Exception:
1104
+ pass
1105
+ if pilotErrorDiag is not None:
1106
+ param["pilotErrorDiag"] = pilotErrorDiag[:500]
1107
+ if jobMetrics is not None:
1108
+ param["jobMetrics"] = jobMetrics[:500]
1109
+ if schedulerID is not None:
1110
+ param["schedulerID"] = schedulerID
1111
+ if pilotID is not None:
1112
+ param["pilotID"] = pilotID[:200]
1113
+ if batchID is not None:
1114
+ param["batchID"] = batchID[:80]
1115
+ if exeErrorCode is not None:
1116
+ param["exeErrorCode"] = exeErrorCode
1117
+ if exeErrorDiag is not None:
1118
+ param["exeErrorDiag"] = exeErrorDiag[:500]
1119
+ if cpuConversionFactor is not None:
1120
+ param["cpuConversion"] = cpuConversionFactor
1121
+ if pilotTiming is not None:
1122
+ param["pilotTiming"] = pilotTiming
1123
+ if nEvents is not None:
1124
+ param["nEvents"] = nEvents
1125
+ if nInputFiles is not None:
1126
+ param["nInputFiles"] = nInputFiles
1127
+ if jobSubStatus not in [None, ""]:
1128
+ param["jobSubStatus"] = jobSubStatus
1129
+ if coreCount not in [None, ""]:
1130
+ param["actualCoreCount"] = coreCount
1131
+ if meanCoreCount:
1132
+ try:
1133
+ param["meanCoreCount"] = float(meanCoreCount)
1134
+ except Exception:
1135
+ pass
1136
+ if grid is not None:
1137
+ param["grid"] = grid
1138
+ if source_site is not None:
1139
+ param["sourceSite"] = source_site
1140
+ if destination_site is not None:
1141
+ param["destinationSite"] = destination_site
1142
+ if maxRSS is not None:
1143
+ param["maxRSS"] = maxRSS
1144
+ if maxVMEM is not None:
1145
+ param["maxVMEM"] = maxVMEM
1146
+ if maxSWAP is not None:
1147
+ param["maxSWAP"] = maxSWAP
1148
+ if maxPSS is not None:
1149
+ param["maxPSS"] = maxPSS
1150
+ if avgRSS is not None:
1151
+ param["avgRSS"] = int(float(avgRSS))
1152
+ if avgVMEM is not None:
1153
+ param["avgVMEM"] = int(float(avgVMEM))
1154
+ if avgSWAP is not None:
1155
+ param["avgSWAP"] = int(float(avgSWAP))
1156
+ if avgPSS is not None:
1157
+ param["avgPSS"] = int(float(avgPSS))
1158
+ if totRCHAR is not None:
1159
+ totRCHAR = int(totRCHAR) / 1024 # convert to kByte
1160
+ totRCHAR = min(10**10 - 1, totRCHAR) # limit to 10 digit
1161
+ param["totRCHAR"] = totRCHAR
1162
+ if totWCHAR is not None:
1163
+ totWCHAR = int(totWCHAR) / 1024 # convert to kByte
1164
+ totWCHAR = min(10**10 - 1, totWCHAR) # limit to 10 digit
1165
+ param["totWCHAR"] = totWCHAR
1166
+ if totRBYTES is not None:
1167
+ totRBYTES = int(totRBYTES) / 1024 # convert to kByte
1168
+ totRBYTES = min(10**10 - 1, totRBYTES) # limit to 10 digit
1169
+ param["totRBYTES"] = totRBYTES
1170
+ if totWBYTES is not None:
1171
+ totWBYTES = int(totWBYTES) / 1024 # convert to kByte
1172
+ totWBYTES = min(10**10 - 1, totWBYTES) # limit to 10 digit
1173
+ param["totWBYTES"] = totWBYTES
1174
+ if rateRCHAR is not None:
1175
+ rateRCHAR = min(10**10 - 1, int(float(rateRCHAR))) # limit to 10 digit
1176
+ param["rateRCHAR"] = rateRCHAR
1177
+ if rateWCHAR is not None:
1178
+ rateWCHAR = min(10**10 - 1, int(float(rateWCHAR))) # limit to 10 digit
1179
+ param["rateWCHAR"] = rateWCHAR
1180
+ if rateRBYTES is not None:
1181
+ rateRBYTES = min(10**10 - 1, int(float(rateRBYTES))) # limit to 10 digit
1182
+ param["rateRBYTES"] = rateRBYTES
1183
+ if rateWBYTES is not None:
1184
+ rateWBYTES = min(10**10 - 1, int(float(rateWBYTES))) # limit to 10 digit
1185
+ param["rateWBYTES"] = rateWBYTES
1186
+ if startTime is not None:
1187
+ try:
1188
+ param["startTime"] = datetime.datetime(*time.strptime(startTime, "%Y-%m-%d %H:%M:%S")[:6])
1189
+ except Exception:
1190
+ pass
1191
+ if endTime is not None:
1192
+ try:
1193
+ param["endTime"] = datetime.datetime(*time.strptime(endTime, "%Y-%m-%d %H:%M:%S")[:6])
1194
+ except Exception:
1195
+ pass
1196
+ if attemptNr is not None:
1197
+ try:
1198
+ attemptNr = int(attemptNr)
1199
+ except Exception:
1200
+ attemptNr = None
1201
+ if stdout != "":
1202
+ stdout = stdout[:2048]
1203
+ if corruptedFiles is not None:
1204
+ param["corruptedFiles"] = corruptedFiles
1205
+ # invoke JD
1206
+ tmp_log.debug("executing")
1207
+ return jobDispatcher.updateJob(
1208
+ int(jobId),
1209
+ state,
1210
+ int(timeout),
1211
+ xml,
1212
+ siteName,
1213
+ param,
1214
+ metaData,
1215
+ pilotLog,
1216
+ attemptNr,
1217
+ stdout,
1218
+ acceptJson,
1219
+ )
1220
+
1221
+
1222
+ # bulk update jobs
1223
+ def updateJobsInBulk(req, jobList, harvester_id=None):
1224
+ ret_list = []
1225
+ ret_val = False
1226
+ prefix = f"updateJobsInBulk {harvester_id}"
1227
+ tmp_logger = LogWrapper(_logger, prefix)
1228
+ tmp_logger.debug("start")
1229
+ t_start = naive_utcnow()
1230
+
1231
+ try:
1232
+ job_list = json.loads(jobList)
1233
+ for job_dict in job_list:
1234
+ job_id = job_dict["jobId"]
1235
+ del job_dict["jobId"]
1236
+ state = job_dict["state"]
1237
+ del job_dict["state"]
1238
+ if "metaData" in job_dict:
1239
+ job_dict["metaData"] = str(job_dict["metaData"])
1240
+ tmp_ret = updateJob(req, job_id, state, **job_dict)
1241
+ ret_list.append(tmp_ret)
1242
+ ret_val = True
1243
+ except Exception:
1244
+ err_type, err_value = sys.exc_info()[:2]
1245
+ tmp_msg = f"failed with {err_type.__name__} {err_value}"
1246
+ ret_list = f"{prefix} {tmp_msg}"
1247
+ tmp_logger.error(f"{tmp_msg}\n{traceback.format_exc()}")
1248
+
1249
+ t_delta = naive_utcnow() - t_start
1250
+ tmp_logger.debug(f"took {t_delta.seconds}.{t_delta.microseconds // 1000:03d} sec")
1251
+ return json.dumps((ret_val, ret_list))
1252
+
1253
+
1254
+ # get job status
1255
+ def getStatus(req, ids, timeout=60):
1256
+ _logger.debug(f"getStatus({ids})")
1257
+ return jobDispatcher.getStatus(ids, int(timeout))
1258
+
1259
+
1260
+ # check job status
1261
+ def checkJobStatus(req, ids, timeout=60):
1262
+ return jobDispatcher.checkJobStatus(ids, int(timeout))
1263
+
1264
+
1265
+ # get a list of even ranges for a PandaID
1266
+ def getEventRanges(
1267
+ req,
1268
+ pandaID,
1269
+ jobsetID,
1270
+ taskID=None,
1271
+ nRanges=10,
1272
+ timeout=60,
1273
+ scattered=None,
1274
+ segment_id=None,
1275
+ ):
1276
+ """
1277
+ Check the permissions and retrieve a list of event ranges for a given PandaID.
1278
+
1279
+ Args:
1280
+ req: The request object containing the environment variables.
1281
+ pandaID (str): The ID of the Panda job.
1282
+ jobsetID (str): The ID of the job set.
1283
+ taskID (str, optional): The ID of the task. Defaults to None.
1284
+ nRanges (int, optional): The number of event ranges to retrieve. Defaults to 10.
1285
+ timeout (int, optional): The timeout value. Defaults to 60.
1286
+ scattered (str, optional): Whether the event ranges are scattered. Defaults to None.
1287
+ segment_id (int, optional): The segment ID. Defaults to None.
1288
+ Returns:
1289
+ dict: The response from the job dispatcher.
1290
+ """
1291
+ tmp_log = LogWrapper(_logger, f"getEventRanges(PandaID={pandaID} jobsetID={jobsetID} taskID={taskID},nRanges={nRanges},segment={segment_id})")
1292
+ tmp_log.debug("start")
1293
+
1294
+ tmp_stat, tmp_out = checkPilotPermission(req)
1295
+ if not tmp_stat:
1296
+ tmp_log.error(f"failed with {tmp_out}")
1297
+ return tmp_out
1298
+
1299
+ if scattered == "True":
1300
+ scattered = True
1301
+ else:
1302
+ scattered = False
1303
+
1304
+ if segment_id is not None:
1305
+ segment_id = int(segment_id)
1306
+
1307
+ return jobDispatcher.getEventRanges(
1308
+ pandaID,
1309
+ jobsetID,
1310
+ taskID,
1311
+ nRanges,
1312
+ int(timeout),
1313
+ req.acceptJson(),
1314
+ scattered,
1315
+ segment_id,
1316
+ )
1317
+
1318
+
1319
+ def updateEventRange(
1320
+ req,
1321
+ eventRangeID,
1322
+ eventStatus,
1323
+ coreCount=None,
1324
+ cpuConsumptionTime=None,
1325
+ objstoreID=None,
1326
+ timeout=60,
1327
+ pandaID=None,
1328
+ ):
1329
+ """
1330
+ Check the permissions and update the status of a specific event range.
1331
+
1332
+ Args:
1333
+ req: The request object containing the environment variables.
1334
+ eventRangeID (str): The ID of the event range to update.
1335
+ eventStatus (str): The new status of the event range.
1336
+ coreCount (int, optional): The number of cores used. Defaults to None.
1337
+ cpuConsumptionTime (float, optional): The CPU consumption time. Defaults to None.
1338
+ objstoreID (int, optional): The object store ID. Defaults to None.
1339
+ timeout (int, optional): The timeout value. Defaults to 60.
1340
+ pandaID (str, optional): The PandaID. Defaults to None.
1341
+
1342
+ Returns:
1343
+ dict: The response from the job dispatcher.
1344
+ """
1345
+ tmp_log = LogWrapper(
1346
+ _logger, f"updateEventRange({eventRangeID} status={eventStatus} coreCount={coreCount} cpuConsumptionTime={cpuConsumptionTime} osID={objstoreID})"
1347
+ )
1348
+ tmp_log.debug("start")
1349
+
1350
+ tmp_stat, tmp_out = checkPilotPermission(req)
1351
+ if not tmp_stat:
1352
+ tmp_log.error(f"failed with {tmp_out}")
1353
+ return tmp_out
1354
+
1355
+ return jobDispatcher.updateEventRange(
1356
+ eventRangeID,
1357
+ eventStatus,
1358
+ coreCount,
1359
+ cpuConsumptionTime,
1360
+ objstoreID,
1361
+ int(timeout),
1362
+ )
1363
+
1364
+
1365
+ def updateEventRanges(req, eventRanges, timeout=120, version=0, pandaID=None):
1366
+ """
1367
+ This function checks the permissions, converts the version to an integer, and updates the event ranges.
1368
+
1369
+ Args:
1370
+ req: The request object containing the environment variables.
1371
+ eventRanges (str): A JSON string containing the list of event ranges to update.
1372
+ timeout (int, optional): The timeout value. Defaults to 120.
1373
+ version (int, optional): The version of the event ranges. Defaults to 0.
1374
+ pandaID (str, optional): The PandaID. Defaults to None.
1375
+ Returns:
1376
+ dict: The response from the job dispatcher.
1377
+ """
1378
+ tmp_log = LogWrapper(_logger, f"updateEventRanges({eventRanges})")
1379
+ tmp_log.debug("start")
1380
+
1381
+ tmp_stat, tmp_out = checkPilotPermission(req)
1382
+ if not tmp_stat:
1383
+ tmp_log.error(f"failed with {tmp_out}")
1384
+ return tmp_out
1385
+
1386
+ try:
1387
+ version = int(version)
1388
+ except Exception:
1389
+ version = 0
1390
+
1391
+ return jobDispatcher.updateEventRanges(eventRanges, int(timeout), req.acceptJson(), version)
1392
+
1393
+
1394
+ def checkEventsAvailability(req, pandaID, jobsetID, taskID, timeout=60):
1395
+ """
1396
+ This function checks the availability of events for a given PandaID, jobsetID, and taskID.
1397
+
1398
+ Args:
1399
+ req: The request object containing the environment variables.
1400
+ pandaID (str): The PandaID.
1401
+ jobset_id (str): The jobsetID.
1402
+ task_id (str): The taskID.
1403
+ timeout (int, optional): The timeout value. Defaults to 60.
1404
+ Returns:
1405
+ bool: The availability status of the events.
1406
+ """
1407
+ tmp_log = LogWrapper(_logger, f"check_events_availability(panda_id={pandaID} jobset_id={jobsetID} task_id={taskID})")
1408
+ tmp_log.debug("start")
1409
+ tmp_stat, tmp_out = checkPilotPermission(req)
1410
+ if not tmp_stat:
1411
+ tmp_log.error(f"failed with {tmp_out}")
1412
+
1413
+ return jobDispatcher.checkEventsAvailability(pandaID, jobsetID, taskID, timeout)
1414
+
1415
+
1416
+ def getKeyPair(req, publicKeyName, privateKeyName):
1417
+ """
1418
+ This function retrieves the distinguished name (DN) from the request and uses it to get a key pair.
1419
+
1420
+ Args:
1421
+ req: The request object containing the environment variables.
1422
+ publicKeyName (str): The name of the public key.
1423
+ privateKeyName (str): The name of the private key.
1424
+
1425
+ Returns:
1426
+ dict: The key pair for the user.
1427
+ """
1428
+ real_dn = _getDN(req)
1429
+ return jobDispatcher.getKeyPair(real_dn, publicKeyName, privateKeyName, req.acceptJson())
1430
+
1431
+
1432
+ def getProxy(req, role=None, dn=None):
1433
+ """
1434
+ Get proxy for a user with a role.
1435
+
1436
+ Args:
1437
+ req: The request object containing the environment variables.
1438
+ role (str, optional): The role of the user. Defaults to None.
1439
+ dn (str, optional): The distinguished name of the user. Defaults to None.
1440
+ Returns:
1441
+ dict: The proxy for the user.
1442
+ """
1443
+ real_dn = _getDN(req)
1444
+ if role == "":
1445
+ role = None
1446
+ return jobDispatcher.get_proxy(real_dn, role, dn, False, None)
1447
+
1448
+
1449
+ def get_access_token(req, client_name, token_key=None):
1450
+ """
1451
+ This function retrieves the distinguished name (DN) from the request and uses it to get an access token for the specified client.
1452
+
1453
+ Args:
1454
+ req: The request object containing the environment variables.
1455
+ client_name (str): The name of the client requesting the access token.
1456
+ token_key (str, optional): The key to get the token from the token cache. Defaults to None.
1457
+ Returns:
1458
+ dict: The access token for the client.
1459
+ """
1460
+ # get DN
1461
+ real_dn = _getDN(req)
1462
+ return jobDispatcher.get_proxy(real_dn, None, client_name, True, token_key)
1463
+
1464
+
1465
+ def get_token_key(req, client_name):
1466
+ """
1467
+ This function retrieves the distinguished name (DN) from the request and uses it to get a token key for the specified client.
1468
+
1469
+ Args:
1470
+ req: The request object containing the environment variables.
1471
+ client_name (str): The name of the client requesting the token key.
1472
+ Returns:
1473
+ str: The token key for the client.
1474
+ """
1475
+ # get DN
1476
+ real_dn = _getDN(req)
1477
+ return jobDispatcher.get_token_key(real_dn, client_name, req.acceptJson())
1478
+
1479
+
1480
+ def checkPilotPermission(req):
1481
+ """
1482
+ This function retrieves the distinguished name (DN) and Fully Qualified Attribute Names (FQANs) from the request,
1483
+ checks if the user has a production role, and verifies the DN.
1484
+
1485
+ Args:
1486
+ req: The request object containing the environment variables.
1487
+ Returns:
1488
+ tuple: A tuple containing a boolean indicating success and a message.
1489
+ """
1490
+ # get DN
1491
+ real_dn = _getDN(req)
1492
+ if real_dn is None:
1493
+ return False, "failed to retrieve DN"
1494
+
1495
+ # get FQANs and check production role
1496
+ fqans = _getFQAN(req)
1497
+ prod_manager = _checkRole(fqans, real_dn, True)
1498
+ if not prod_manager:
1499
+ return False, "production or pilot role is required"
1500
+
1501
+ return True, None
1502
+
1503
+
1504
+ def getCommands(req, harvester_id, n_commands, timeout=30):
1505
+ """
1506
+ This function checks the permissions and retrieves the commands for a specified harvester instance.
1507
+
1508
+ Args:
1509
+ req: The request object containing the environment variables.
1510
+ harvester_id (str): The ID of the harvester instance.
1511
+ n_commands (int): The number of commands to retrieve.
1512
+ timeout (int, optional): The timeout value. Defaults to 30.
1513
+ Returns:
1514
+ dict: The response from the job dispatcher.
1515
+ """
1516
+ tmp_str = "getCommands"
1517
+
1518
+ # check permissions
1519
+ tmp_stat, tmp_out = checkPilotPermission(req)
1520
+ if not tmp_stat:
1521
+ _logger.error(f"{tmp_str} failed with {tmp_out}")
1522
+
1523
+ accept_json = req.acceptJson()
1524
+ # retrieve the commands
1525
+ return jobDispatcher.getCommands(harvester_id, n_commands, timeout, accept_json)
1526
+
1527
+
1528
+ def ackCommands(req, command_ids, timeout=30):
1529
+ """
1530
+ This function checks the permissions, parses the command IDs from JSON, and acknowledges the list of commands.
1531
+
1532
+ Args:
1533
+ req: The request object containing the environment variables.
1534
+ command_ids (str): A JSON string containing the list of command IDs to acknowledge.
1535
+ timeout (int, optional): The timeout value. Defaults to 30.
1536
+ Returns:
1537
+ dict: The response from the job dispatcher.
1538
+ """
1539
+ tmp_str = "ackCommands"
1540
+
1541
+ # check permissions
1542
+ tmp_stat, tmp_out = checkPilotPermission(req)
1543
+ if not tmp_stat:
1544
+ _logger.error(f"{tmp_str} failed with {tmp_out}")
1545
+
1546
+ command_ids = json.loads(command_ids)
1547
+ accept_json = req.acceptJson()
1548
+
1549
+ # retrieve the commands
1550
+ return jobDispatcher.ackCommands(command_ids, timeout, accept_json)
1551
+
1552
+
1553
+ def getResourceTypes(req, timeout=30):
1554
+ """
1555
+ This function retrieves the resource types (MCORE, SCORE, etc.) and their definitions.
1556
+
1557
+ Args:
1558
+ req: The request object containing the environment variables.
1559
+ timeout (int, optional): The timeout value. Defaults to 30.
1560
+ Returns:
1561
+ dict: The resource types and their definitions.
1562
+ """
1563
+ tmp_str = "getResourceTypes"
1564
+
1565
+ # check permissions
1566
+ tmp_stat, tmp_out = checkPilotPermission(req)
1567
+ if not tmp_stat:
1568
+ _logger.error(f"{tmp_str} failed with {tmp_out}")
1569
+
1570
+ accept_json = req.acceptJson()
1571
+
1572
+ # retrieve the resource types
1573
+ return jobDispatcher.getResourceTypes(timeout, accept_json)
1574
+
1575
+
1576
+ def updateWorkerPilotStatus(req, site, workerID, harvesterID, status, timeout=60, node_id=None):
1577
+ """
1578
+ Update the status of a worker according to the pilot.
1579
+
1580
+ This function validates the pilot permissions and the state passed by the pilot, then updates the worker status.
1581
+
1582
+ Args:
1583
+ req: The request object containing the environment variables.
1584
+ site (str): The site name.
1585
+ workerID (str): The worker ID.
1586
+ harvesterID (str): The harvester ID.
1587
+ status (str): The status of the worker. Must be either 'started' or 'finished'.
1588
+ timeout (int, optional): The timeout value. Defaults to 60.
1589
+ node_id (str, optional): The node ID. Defaults to None.
1590
+
1591
+ Returns:
1592
+ str: The result of the status update or an error message.
1593
+ """
1594
+ tmp_log = LogWrapper(
1595
+ _logger,
1596
+ f"updateWorkerPilotStatus workerID={workerID} harvesterID={harvesterID} status={status} nodeID={node_id} PID={os.getpid()}",
1597
+ )
1598
+ tmp_log.debug("start")
1599
+
1600
+ # validate the pilot permissions
1601
+ tmp_stat, tmp_out = checkPilotPermission(req)
1602
+ if not tmp_stat:
1603
+ tmp_log.error(f"failed with {tmp_out}")
1604
+ return tmp_out
1605
+
1606
+ # validate the state passed by the pilot
1607
+ valid_states = ("started", "finished")
1608
+ if status not in valid_states:
1609
+ message = f"Invalid worker state. The worker state has to be in {valid_states}"
1610
+ tmp_log.debug(message)
1611
+ return message
1612
+
1613
+ accept_json = req.acceptJson()
1614
+
1615
+ return jobDispatcher.updateWorkerPilotStatus(workerID, harvesterID, status, timeout, accept_json, node_id)
1616
+
1617
+
1618
+ # get max workerID
1619
+ def get_max_worker_id(req, harvester_id):
1620
+ return jobDispatcher.get_max_worker_id(harvester_id)
1621
+
1622
+
1623
+ # get events status
1624
+ def get_events_status(req, ids):
1625
+ return jobDispatcher.get_events_status(ids)