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,95 @@
1
+ import argparse
2
+ import asyncio
3
+
4
+ from fastmcp import Client
5
+ from fastmcp.client.transports import SSETransport, StreamableHttpTransport
6
+
7
+ # argparse setup
8
+ parser = argparse.ArgumentParser(description="MCP test client")
9
+ parser.add_argument(
10
+ "--transport",
11
+ type=str,
12
+ choices=("streamable-http", "sse"),
13
+ default="streamable-http",
14
+ help="Transport to use: streamable-http or sse (default: streamable-http)",
15
+ )
16
+ parser.add_argument("--host", type=str, default="localhost", help="PanDA Server host (default: localhost)")
17
+ parser.add_argument("--port", type=int, default=25443, help="PanDA Server port (default: 25443)")
18
+ parser.add_argument("--use_http", action=argparse.BooleanOptionalAction, default=False, help="Use HTTP instead of HTTPS (default: False)")
19
+ parser.add_argument("--token", type=str, default=None, help="OIDC ID token for write-operations (default: None)")
20
+ parser.add_argument("--vo", type=str, default=None, help="Virtual organization with ID token is given (default: None)")
21
+ parser.add_argument("--tool", type=str, default="is_alive", help="A tool name to test (default: is_alive)")
22
+ parser.add_argument(
23
+ "--kv",
24
+ dest="kv",
25
+ metavar="KEY=VAL",
26
+ action="append",
27
+ help="Arguments to invoke the test function. Key=Value pairs repeat for multiple (e.g. --kv a=1 --kv b=2 --kv c=True)",
28
+ )
29
+ args = parser.parse_args()
30
+
31
+ # parse --kv KEY=VAL (repeatable) into a dict stored back on args.kv
32
+ raw_kv = args.kv or []
33
+ kv = {}
34
+ for item in raw_kv:
35
+ if "=" not in item:
36
+ parser.error(f"invalid --kv value {item!r}, expected KEY=VALUE")
37
+ k, v = item.split("=", 1)
38
+ v = v.strip()
39
+ if v == "True":
40
+ v = True
41
+ elif v == "False":
42
+ v = False
43
+ kv[k] = v
44
+ args.kv = kv
45
+
46
+ # construct base URL
47
+ if args.use_http:
48
+ base_url = f"http://{args.host}:{args.port}/mcp/"
49
+ else:
50
+ base_url = f"https://{args.host}:{args.port}/mcp/"
51
+
52
+ headers = {"Origin": args.vo} if args.token else None
53
+
54
+ # select transport
55
+ if args.transport == "streamable-http":
56
+ transport = StreamableHttpTransport(url=base_url, auth=args.token, headers=headers)
57
+ else:
58
+ transport = SSETransport(url=base_url, auth=args.token, headers=headers)
59
+
60
+ # create client
61
+ client = Client(transport)
62
+
63
+
64
+ # test function
65
+ async def cl():
66
+ # Connection is established here
67
+ async with client:
68
+ if client.is_connected():
69
+ print("Client connected")
70
+ else:
71
+ print("Client failed to connect")
72
+ return
73
+
74
+ # Make MCP calls within the context
75
+ tools = await client.list_tools()
76
+ print(f"\nAvailable tools:")
77
+ for tool in tools:
78
+ print(f"- {tool.name} -\n")
79
+ print(f" Description: {tool.description}")
80
+
81
+ print("\n" * 2)
82
+ print(f"Testing {args.tool}:")
83
+ result = await client.call_tool(args.tool, args.kv)
84
+ print(f"Result: {result}\n")
85
+
86
+ # Connection is closed automatically here
87
+ if not client.is_connected():
88
+ print("Client disconnected")
89
+ else:
90
+ print("Client still connected")
91
+ print("Done")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ asyncio.run(cl())
@@ -0,0 +1,74 @@
1
+ import inspect
2
+ import os.path
3
+ from collections.abc import Callable
4
+
5
+ from fastmcp.server.dependencies import get_http_headers
6
+ from fastmcp.tools.tool import Tool
7
+
8
+ from pandaserver.api.v1.http_client import HttpClient, api_url, api_url_ssl
9
+ from pandaserver.srvcore.panda_request import PandaRequest
10
+
11
+
12
+ def create_tool(func: Callable) -> Tool:
13
+ """
14
+ Create an MCP tool that wraps the API call.
15
+
16
+ :param func: The function to wrap. It should be a callable that takes PandaRequest as the first argument.
17
+ :return: An MCP tool.
18
+ """
19
+
20
+ # construct the URL based on the module and function name
21
+ mod_path = inspect.getfile(inspect.getmodule(func))
22
+ mod_name = os.path.basename(mod_path).split("_")[0]
23
+
24
+ # determine http method based on the docstring
25
+ http_method = None
26
+ for line in func.__doc__.splitlines():
27
+ line = line.strip()
28
+ if line.startswith("HTTP Method:"):
29
+ http_method = line.split(":")[1].strip().lower()
30
+ if http_method not in ["get", "post"]:
31
+ raise ValueError(f"Unsupported HTTP method: {http_method}")
32
+ break
33
+ if http_method is None:
34
+ raise ValueError("HTTP Method not specified in the function docstring")
35
+
36
+ if http_method == "get":
37
+ url = f"{api_url}/{mod_name}/{func.__name__}"
38
+ else:
39
+ url = f"{api_url_ssl}/{mod_name}/{func.__name__}"
40
+
41
+ # remove PandaRequest from the signature and annotations
42
+ sig = inspect.signature(func)
43
+ params = []
44
+ for p in sig.parameters.values():
45
+ if p.annotation != PandaRequest:
46
+ params.append(p)
47
+
48
+ annotations = {}
49
+ for k, v in func.__annotations__.items():
50
+ if v != PandaRequest:
51
+ annotations[k] = v
52
+
53
+ # create a new function that wraps the API call
54
+ def wrapped_func(**kwarg):
55
+ nonlocal url, http_method
56
+ # extract the id_token and auth_vo from the headers
57
+ original_headers = get_http_headers()
58
+ id_token = original_headers.get("authorization")
59
+ if id_token and id_token.startswith("Bearer "):
60
+ id_token = original_headers["authorization"].split(" ")[1]
61
+ auth_vo = original_headers.get("origin")
62
+ oidc = id_token is not None
63
+
64
+ # set the id_token and auth_vo in the HttpClient
65
+ http_client = HttpClient()
66
+ http_client.override_oidc(oidc, id_token, auth_vo)
67
+ status, output = getattr(http_client, http_method)(url, kwarg)
68
+ return output
69
+
70
+ # set the signature and annotations to the wrapped function to align with the original API call
71
+ wrapped_func.__signature__ = sig.replace(parameters=params)
72
+ wrapped_func.__annotations__ = annotations
73
+
74
+ return Tool.from_function(wrapped_func, name=func.__name__, description=func.__doc__)
File without changes
@@ -0,0 +1,240 @@
1
+ import datetime
2
+ import hashlib
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+
7
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
8
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
9
+
10
+ _logger = PandaLogger().getLogger("ProxyCache")
11
+
12
+
13
+ def execute(program, log_stream):
14
+ """Run a program on the command line. Return stderr, stdout and status."""
15
+ log_stream.info(f"executable: {program}")
16
+ pipe = subprocess.Popen(
17
+ program,
18
+ bufsize=-1,
19
+ shell=True,
20
+ close_fds=False,
21
+ cwd="/tmp",
22
+ stdout=subprocess.PIPE,
23
+ stderr=subprocess.PIPE,
24
+ )
25
+ stdout, stderr = pipe.communicate()
26
+ return stdout, stderr, pipe.wait()
27
+
28
+
29
+ def cat(filename):
30
+ """Given filename, print its text contents."""
31
+ with open(filename, "r") as f:
32
+ out = f.read()
33
+ return out
34
+
35
+
36
+ class MyProxyInterface(object):
37
+ """Class to store and retrieve proxies from my proxies."""
38
+
39
+ def __init__(self):
40
+ self.__target_path = "/tmp/proxies"
41
+ self.__cred_name = "panda"
42
+ if not os.path.exists(self.__target_path):
43
+ os.makedirs(self.__target_path)
44
+
45
+ def store(
46
+ self,
47
+ user_dn,
48
+ cred_name,
49
+ production=False,
50
+ server_name="myproxy.cern.ch",
51
+ role=None,
52
+ log_stream=None,
53
+ ):
54
+ log_stream.info("store proxy")
55
+
56
+ # Retrieve proxy from myproxy
57
+ proxy_path = os.path.join(
58
+ self.__target_path,
59
+ hashlib.sha1(f"{user_dn}.plain".encode("utf-8")).hexdigest(),
60
+ )
61
+
62
+ # check if empty dummy file
63
+ if os.path.exists(proxy_path) and os.stat(proxy_path).st_size == 0:
64
+ if datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromtimestamp(
65
+ os.path.getctime(proxy_path), datetime.timezone.utc
66
+ ) < datetime.timedelta(hours=1):
67
+ log_stream.info(f"skip too early to try again according to timestamp of {proxy_path}")
68
+ return 2
69
+ cmd = f"myproxy-logon -s {server_name} --no_passphrase --out {proxy_path} -l '{user_dn}' -k {cred_name} -t 0"
70
+
71
+ stdout, stderr, status = execute(cmd, log_stream)
72
+ if stdout:
73
+ log_stream.info(f"stdout is {stdout} ")
74
+ if stderr:
75
+ log_stream.info(f"stderr is {stderr} ")
76
+ # make a dummy to avoid too early reattempt
77
+ open(proxy_path, "w").close()
78
+ log_stream.info(f"test the status of plain... {status}")
79
+ if status == 1:
80
+ return status
81
+
82
+ if role is not None:
83
+ log_stream.info(f"proxy needs {role} - need to add voms attributes and store it in the cache")
84
+ tmpExtension = self.getExtension(role)
85
+ prodproxy_path = os.path.join(
86
+ self.__target_path,
87
+ str(hashlib.sha1((user_dn + tmpExtension).encode("utf-8")).hexdigest()),
88
+ )
89
+ prodcmd = f"voms-proxy-init -vomses /etc/vomses -valid 96:00 -rfc -cert {proxy_path} -key {proxy_path} -out {prodproxy_path} -n -voms {role}"
90
+ stdout, stderr, status = execute(prodcmd, log_stream)
91
+ if stdout:
92
+ log_stream.info(f"stdout is {stdout} ")
93
+ if stderr:
94
+ log_stream.info(f"stderr is {stderr} ")
95
+ log_stream.debug(f"test the status of production... {status}")
96
+ elif production:
97
+ log_stream.info("production proxy needed - need to add voms attributes and store it in the cache")
98
+ prodproxy_path = os.path.join(
99
+ self.__target_path,
100
+ str(hashlib.sha1(f"{user_dn}.prod".encode("utf-8")).hexdigest()),
101
+ )
102
+ log_stream.info(prodproxy_path)
103
+ prodcmd = f"voms-proxy-init -vomses /etc/vomses -valid 96:00 -rfc -cert {proxy_path} -key {proxy_path} -out {prodproxy_path} -n -voms atlas:/atlas/Role=production"
104
+ stdout, stderr, status = execute(prodcmd, log_stream)
105
+ if stdout:
106
+ log_stream.info(f"stdout is {stdout} ")
107
+ if stderr:
108
+ log_stream.info(f"stderr is {stderr} ")
109
+ log_stream.info(f"test the status of production... {status}")
110
+ else:
111
+ # Now we need to add atlas roles and store it
112
+ atlasproxy_path = os.path.join(self.__target_path, hashlib.sha1(user_dn.encode("utf-8")).hexdigest())
113
+ atlasrolescmd = f"voms-proxy-init -vomses /etc/vomses -valid 96:00 -rfc -cert {proxy_path} -key {proxy_path} -out {atlasproxy_path} -n -voms atlas"
114
+ stdout, stderr, status = execute(atlasrolescmd, log_stream)
115
+ if stdout:
116
+ log_stream.info(f"stdout is {stdout} ")
117
+ if stderr:
118
+ log_stream.info(f"stderr is {stderr} ")
119
+ log_stream.info(f"test the status of atlas... {status}")
120
+ # make dummy to avoid too early attempts
121
+ if status != 0 and not os.path.exists(proxy_path):
122
+ open(proxy_path, "w").close()
123
+ return status
124
+
125
+ def retrieve(self, user_dn, production=False, role=None):
126
+ """Retrieve proxy from proxy cache."""
127
+ if role is not None:
128
+ tmpExtension = self.getExtension(role)
129
+ proxy_path = os.path.join(
130
+ self.__target_path,
131
+ str(hashlib.sha1((user_dn + tmpExtension).encode("utf-8")).hexdigest()),
132
+ )
133
+ elif production:
134
+ proxy_path = os.path.join(
135
+ self.__target_path,
136
+ str(hashlib.sha1(f"{user_dn}.prod".encode("utf-8")).hexdigest()),
137
+ )
138
+ else:
139
+ proxy_path = os.path.join(self.__target_path, hashlib.sha1(user_dn.encode("utf-8")).hexdigest())
140
+ if os.path.isfile(proxy_path):
141
+ return cat(proxy_path)
142
+ else:
143
+ _logger.warning(f"proxy file does not exist : DN:{user_dn} role:{role} file:{proxy_path}")
144
+
145
+ # get proxy path
146
+ def get_proxy_path(self, user_dn, production, role):
147
+ if role is not None:
148
+ tmpExtension = self.getExtension(role)
149
+ return os.path.join(
150
+ self.__target_path,
151
+ str(hashlib.sha1((user_dn + tmpExtension).encode("utf-8")).hexdigest()),
152
+ )
153
+ elif production:
154
+ return os.path.join(
155
+ self.__target_path,
156
+ str(hashlib.sha1(f"{user_dn}.prod".encode("utf-8")).hexdigest()),
157
+ )
158
+ else:
159
+ return os.path.join(
160
+ self.__target_path,
161
+ hashlib.sha1(user_dn.encode("utf-8")).hexdigest(),
162
+ )
163
+
164
+ def checkProxy(self, user_dn, production=False, role=None, name=None):
165
+ log_stream = LogWrapper(_logger, f'< name="{name}" role={role} >')
166
+ log_stream.info(f"check proxy for {user_dn}")
167
+
168
+ # Check the validity of a proxy
169
+ proxy_path = self.get_proxy_path(user_dn, production, role)
170
+ is_ok = False
171
+ if os.path.isfile(proxy_path):
172
+ log_stream.info("proxy is there. Need to check validity")
173
+ cmd = f"voms-proxy-info -exists -hours 94 -file {proxy_path}"
174
+ stdout, stderr, status = execute(cmd, log_stream)
175
+ if stdout:
176
+ log_stream.info(f"stdout is {stdout} ")
177
+ if stderr:
178
+ log_stream.info(f"stderr is {stderr} ")
179
+ if status == 1:
180
+ log_stream.info("proxy expires in 94h or less. We need to renew proxy!")
181
+ ret = self.store(
182
+ user_dn,
183
+ self.__cred_name,
184
+ production,
185
+ role=role,
186
+ log_stream=log_stream,
187
+ )
188
+ if ret == 0:
189
+ log_stream.info("proxy retrieval successful")
190
+ # copy with compact name
191
+ alt_proxy_path = self.get_proxy_path(name, production, role)
192
+ shutil.copyfile(proxy_path, alt_proxy_path)
193
+ is_ok = True
194
+ elif ret == 2:
195
+ log_stream.info("proxy retrieval on hold")
196
+ else:
197
+ log_stream.error("proxy retrieval failed")
198
+ else:
199
+ log_stream.info("proxy is valid for more than 3 days")
200
+ is_ok = True
201
+ else:
202
+ log_stream.info("proxy is not in the cache repo. will try to get it from myproxy")
203
+ ret = self.store(user_dn, self.__cred_name, production, role=role, log_stream=log_stream)
204
+ if ret == 0:
205
+ log_stream.info("proxy stored successfully")
206
+ alt_proxy_path = self.get_proxy_path(name, production, role)
207
+ shutil.copyfile(proxy_path, alt_proxy_path)
208
+ is_ok = True
209
+ elif ret == 2:
210
+ log_stream.info("proxy retrieval on hold")
211
+ else:
212
+ log_stream.error("proxy retrieval failed")
213
+ if is_ok:
214
+ plain_path = os.path.join(
215
+ self.__target_path,
216
+ hashlib.sha1(f"{user_dn}.plain".encode("utf-8")).hexdigest(),
217
+ )
218
+ if os.path.isfile(plain_path):
219
+ return self.checkValidity(plain_path, log_stream)
220
+ else:
221
+ log_stream.error("plain proxy not there at the moment!")
222
+
223
+ def checkValidity(self, proxy_path, log_stream):
224
+ log_stream.info("Need to check validity and expiry!")
225
+ time_left_thresholds = [24, 94, 168]
226
+ status = 0
227
+ for threshold in time_left_thresholds:
228
+ cmd = f"voms-proxy-info -exists -hours {threshold} -file {proxy_path}"
229
+ stdout, stderr, status = execute(cmd, log_stream)
230
+ if status == 1:
231
+ log_stream.warning(f"proxy expires in {threshold} hours")
232
+ return threshold
233
+
234
+ return status
235
+
236
+ # get extension
237
+ def getExtension(self, role):
238
+ if role is not None:
239
+ return "." + role.split("=")[-1]
240
+ return None
@@ -0,0 +1,141 @@
1
+ """
2
+ download access tokens for OIDC token exchange flow
3
+ """
4
+
5
+ import datetime
6
+ import json
7
+ import os.path
8
+ import pathlib
9
+
10
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
11
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
12
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
13
+
14
+ from pandaserver.config import panda_config
15
+ from pandaserver.srvcore.oidc_utils import get_access_token
16
+
17
+ # logger
18
+ _logger = PandaLogger().getLogger("token_cache")
19
+
20
+
21
+ class TokenCache:
22
+ """
23
+ A class used to download and give access tokens for OIDC token exchange flow
24
+
25
+ """
26
+
27
+ # constructor
28
+ def __init__(self, target_path: str = None, file_prefix: str = None, refresh_interval: int = 60, task_buffer=None):
29
+ """
30
+ Constructs all the necessary attributes for the TokenCache object.
31
+
32
+ :param target_path: The base path to store the access tokens
33
+ :param file_prefix: The prefix of the access token files
34
+ :param refresh_interval: The interval to refresh the access tokens (default is 60 minutes)
35
+ :param task_buffer: TaskBuffer object
36
+ """
37
+ if target_path:
38
+ self.target_path = target_path
39
+ else:
40
+ self.target_path = "/tmp/proxies"
41
+ if not os.path.exists(self.target_path):
42
+ os.makedirs(self.target_path)
43
+ if file_prefix:
44
+ self.file_prefix = file_prefix
45
+ else:
46
+ self.file_prefix = "access_token_"
47
+ self.refresh_interval = refresh_interval
48
+ self.task_buffer = task_buffer
49
+ # cache for access tokens
50
+ self.cached_access_tokens = {}
51
+
52
+ # construct target path
53
+ def construct_target_path(self, client_name: str) -> str:
54
+ """
55
+ Constructs the target path to store an access token
56
+
57
+ :param client_name: client name
58
+ :return: the target path
59
+ """
60
+ return os.path.join(self.target_path, f"{self.file_prefix}{client_name}")
61
+
62
+ # main
63
+ def run(self):
64
+ """ "
65
+ Main function to download access tokens
66
+ """
67
+ tmp_log = LogWrapper(_logger)
68
+ tmp_log.debug("================= start ==================")
69
+ try:
70
+ # check config
71
+ if not hasattr(panda_config, "token_cache_config") or not panda_config.token_cache_config:
72
+ tmp_log.debug("token_cache_config is not set in panda_config")
73
+ # check config path
74
+ elif not os.path.exists(panda_config.token_cache_config):
75
+ tmp_log.debug(f"config file {panda_config.token_cache_config} not found")
76
+ # read config
77
+ else:
78
+ with open(panda_config.token_cache_config) as f:
79
+ token_cache_config = json.load(f)
80
+ for client_name, client_config in token_cache_config.items():
81
+ tmp_log.debug(f"client_name={client_name}")
82
+ # token file path
83
+ token_file_path = client_config.get("token_file_path")
84
+ if not token_file_path:
85
+ token_file_path = self.construct_target_path(client_name)
86
+ # check if fresh
87
+ is_fresh = False
88
+ if os.path.exists(token_file_path):
89
+ mod_time = datetime.datetime.fromtimestamp(os.stat(token_file_path).st_mtime, datetime.timezone.utc)
90
+ if datetime.datetime.now(datetime.timezone.utc) - mod_time < datetime.timedelta(minutes=self.refresh_interval):
91
+ tmp_log.debug(f"skip since {token_file_path} is fresh")
92
+ is_fresh = True
93
+ # get access token
94
+ if not is_fresh:
95
+ status_code, output = get_access_token(
96
+ client_config["endpoint"], client_config["client_id"], client_config["secret"], client_config.get("scope")
97
+ )
98
+ if status_code:
99
+ with open(token_file_path, "w") as f:
100
+ f.write(output)
101
+ tmp_log.debug(f"dump access token to {token_file_path}")
102
+ else:
103
+ tmp_log.error(output)
104
+ # touch file to avoid immediate reattempt
105
+ pathlib.Path(token_file_path).touch()
106
+ tmp_log.debug(f"touch {token_file_path} to avoid immediate reattempt")
107
+ # register token keys
108
+ if client_config.get("use_token_key") is True and self.task_buffer is not None:
109
+ token_key_lifetime = client_config.get("token_key_lifetime", 96)
110
+ tmp_log.debug(f"register token key for {client_name}")
111
+ tmp_stat = self.task_buffer.register_token_key(client_name, token_key_lifetime)
112
+ if not tmp_stat:
113
+ tmp_log.error("failed")
114
+ except Exception as e:
115
+ tmp_log.error(f"failed with {str(e)}")
116
+ tmp_log.debug("================= end ==================")
117
+ tmp_log.debug("done")
118
+ return
119
+
120
+ # get access token for a client
121
+ def get_access_token(self, client_name: str) -> str | None:
122
+ """
123
+ Get an access token string for a client. None is returned if the access token is not found
124
+
125
+ :param client_name : client name
126
+ :return: the access token
127
+ """
128
+ time_now = naive_utcnow()
129
+ if client_name in self.cached_access_tokens and self.cached_access_tokens[client_name]["last_update"] + datetime.timedelta(minutes=10) < time_now:
130
+ # use cached token since it is still fresh
131
+ pass
132
+ else:
133
+ target_path = self.construct_target_path(client_name)
134
+ token = None
135
+ if os.path.exists(target_path):
136
+ with open(target_path) as f:
137
+ token = f.read()
138
+ if not token:
139
+ token = None
140
+ self.cached_access_tokens[client_name] = {"token": token, "last_update": time_now}
141
+ return self.cached_access_tokens[client_name]["token"]
@@ -0,0 +1,6 @@
1
+ <gacl>
2
+ <entry>
3
+ <any-user/>
4
+ <allow><read/><list/></allow>
5
+ </entry>
6
+ </gacl>