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,445 @@
1
+ import datetime
2
+ from typing import Any, Dict, List
3
+
4
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
5
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
6
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
7
+
8
+ from pandaserver.api.v1.common import (
9
+ MESSAGE_DATABASE,
10
+ TIME_OUT,
11
+ TimedMethod,
12
+ generate_response,
13
+ get_dn,
14
+ request_validation,
15
+ )
16
+ from pandaserver.srvcore.panda_request import PandaRequest
17
+ from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
18
+
19
+ _logger = PandaLogger().getLogger("api_harvester")
20
+
21
+ global_task_buffer = None
22
+
23
+
24
+ def init_task_buffer(task_buffer: TaskBuffer) -> None:
25
+ """
26
+ Initialize the task buffer. This method needs to be called before any other method in this module.
27
+ """
28
+ global global_task_buffer
29
+ global_task_buffer = task_buffer
30
+
31
+
32
+ @request_validation(_logger, secure=True, request_method="POST")
33
+ def update_workers(req: PandaRequest, harvester_id: str, workers: List) -> dict:
34
+ """
35
+ Update workers.
36
+
37
+ Update the details for a list of workers. Requires a secure connection.
38
+
39
+ API details:
40
+ HTTP Method: POST
41
+ Path: /v1/harvester/update_workers
42
+
43
+ Args:
44
+ req(PandaRequest): internally generated request object
45
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
46
+ workers(list): list of worker dictionaries that describe the fields of a pandaserver/taskbuffer/WorkerSpec object.
47
+ ```
48
+ [{"workerID": 1, "batchID": 1, "queueName": "queue1", "status": "running",
49
+ "computingSite": "site1", "nCore": 1, "nodeID": None,
50
+ "submitTime": "02-NOV-24 00:02:18", "startTime": "02-NOV-24 00:02:18", "endTime": None,
51
+ "jobType": "managed", "resourceType": "SCORE", "nativeExitCode": None, "nativeStatus": None,
52
+ "diagMessage": None, "nJobs": 1, "computingElement": "ce1", "syncLevel": 0,
53
+ "submissionHost": "submissionhost1", "harvesterHost": "harvesterhost1",
54
+ "errorCode": None, "minRamCount": 2000},...]
55
+ ```
56
+
57
+ Returns:
58
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
59
+
60
+ """
61
+ tmp_logger = LogWrapper(_logger, f"update_workers harvester_id={harvester_id}")
62
+ tmp_logger.debug("Start")
63
+ success, message, data = False, "", None
64
+ time_start = naive_utcnow()
65
+
66
+ ret = global_task_buffer.updateWorkers(harvester_id, workers)
67
+ if not ret:
68
+ tmp_logger.error(f"Error updating database for workers: {workers}")
69
+ success, message = False, MESSAGE_DATABASE
70
+ else:
71
+ success, data = True, ret
72
+
73
+ time_delta = naive_utcnow() - time_start
74
+ tmp_logger.debug(f"Done. Took {time_delta.seconds}.{time_delta.microseconds // 1000:03d} sec")
75
+
76
+ return generate_response(success, message, data)
77
+
78
+
79
+ @request_validation(_logger, secure=True, request_method="POST")
80
+ def update_service_metrics(req: PandaRequest, harvester_id: str, metrics: list) -> Dict[str, Any]:
81
+ """
82
+ Update harvester service metrics.
83
+
84
+ Update the service metrics for a harvester instance. Requires a secure connection.
85
+
86
+ API details:
87
+ HTTP Method: POST
88
+ Path: /v1/harvester/update_service_metrics
89
+
90
+ Args:
91
+ req(PandaRequest): internally generated request object
92
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
93
+ metrics(list): list of triplets `[[host, timestamp, metric_dict],[host, timestamp, metric_dict]...]`. The metric dictionary is json encoded, as it is stored in the database like that.
94
+ ```
95
+ harvester_host = "harvester_host.cern.ch"
96
+ creation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
97
+ metric = {
98
+ "rss_mib": 2737.36,
99
+ "memory_pc": 39.19,
100
+ "cpu_pc": 15.23,
101
+ "volume_data_pc": 20.0,
102
+ "cert_lifetime": {
103
+ "/data/atlpan/proxy/x509up_u25606_prod": 81,
104
+ "/data/atlpan/proxy/x509up_u25606_pilot": 81,
105
+ "/cephfs/atlpan/harvester/proxy/x509up_u25606_prod": 96,
106
+ "/cephfs/atlpan/harvester/proxy/x509up_u25606_pilot": 96,
107
+ },
108
+ }
109
+
110
+ # DBProxy expects the metrics in json format and stores them directly in the database
111
+ metrics = [[creation_time, harvester_host, json.dumps(metric)]]
112
+ ```
113
+
114
+ Returns:
115
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
116
+ """
117
+ tmp_logger = LogWrapper(_logger, f"update_service_metrics harvester_id={harvester_id}")
118
+ tmp_logger.debug("Start")
119
+ success, message, data = False, "", None
120
+
121
+ # update the metrics in the database
122
+ time_start = naive_utcnow()
123
+
124
+ ret = global_task_buffer.updateServiceMetrics(harvester_id, metrics)
125
+ if not ret:
126
+ tmp_logger.error(f"Error updating database for metrics: {metrics}")
127
+ success, message = False, MESSAGE_DATABASE
128
+ else:
129
+ success, data = True, ret
130
+
131
+ time_delta = naive_utcnow() - time_start
132
+ _logger.debug(f"Done. Took {time_delta.seconds}.{time_delta.microseconds // 1000:03d} sec")
133
+
134
+ return generate_response(success, message, data)
135
+
136
+
137
+ @request_validation(_logger, secure=True, request_method="POST")
138
+ def add_dialogs(req: PandaRequest, harvester_id: str, dialogs: list) -> Dict[str, Any]:
139
+ """
140
+ Add harvester dialog messages.
141
+
142
+ Add messages for a harvester instance. Requires a secure connection.
143
+
144
+ API details:
145
+ HTTP Method: POST
146
+ Path: /v1/harvester/add_dialogs
147
+
148
+ Args:
149
+ req(PandaRequest): internally generated request object
150
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
151
+ dialogs(list): list of dialog dictionaries, e.g.
152
+ ```
153
+ dialogs = [{
154
+ "diagID": 1,
155
+ "moduleName": "test_module",
156
+ "identifier": "test identifier",
157
+ "creationTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
158
+ "messageLevel": "INFO",
159
+ "diagMessage": "test message",
160
+ },...]
161
+ ```
162
+
163
+ Returns:
164
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
165
+ """
166
+ tmp_logger = LogWrapper(_logger, f"add_dialogs harvester_id={harvester_id}")
167
+ tmp_logger.debug("Start")
168
+
169
+ ret = global_task_buffer.addHarvesterDialogs(harvester_id, dialogs)
170
+ if not ret:
171
+ tmp_logger.error(f"Error updating database: {dialogs}")
172
+ return generate_response(False, message=MESSAGE_DATABASE)
173
+
174
+ tmp_logger.debug("Done")
175
+ return generate_response(True)
176
+
177
+
178
+ @request_validation(_logger, secure=True, request_method="POST")
179
+ def heartbeat(req: PandaRequest, harvester_id: str, data: dict = None) -> Dict[str, Any]:
180
+ """
181
+ Heartbeat for harvester.
182
+
183
+ Send a heartbeat for harvester and optionally update the instance data. User and host are retrieved from the request object and updated in the database. Requires a secure connection.
184
+
185
+ API details:
186
+ HTTP Method: POST
187
+ Path: /v1/harvester/heartbeat
188
+
189
+ Args:
190
+ req(PandaRequest): internally generated request object
191
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
192
+ data(dict): metadata dictionary to be updated in the PanDA database, e.g. `data = {"startTime": <start time>, "sw_version": <release version>, "commit_stamp": <commit timestamp>}`
193
+
194
+
195
+ Returns:
196
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
197
+ """
198
+ tmp_logger = LogWrapper(_logger, f"heartbeat harvester_id={harvester_id}")
199
+ tmp_logger.debug("Start")
200
+
201
+ # get user and hostname to record in harvester metadata
202
+ user = get_dn(req)
203
+ host = req.get_remote_host()
204
+
205
+ ret_message = global_task_buffer.harvesterIsAlive(user, host, harvester_id, data)
206
+ if not ret_message or ret_message != "succeeded":
207
+ tmp_logger.error(f"Error updating database: {data}")
208
+ return generate_response(False, message=MESSAGE_DATABASE)
209
+
210
+ tmp_logger.debug("Done")
211
+ return generate_response(True)
212
+
213
+
214
+ @request_validation(_logger, secure=True, request_method="GET")
215
+ def get_current_worker_id(req: PandaRequest, harvester_id: str) -> Dict[str, Any]:
216
+ """
217
+ Get the current worker ID.
218
+
219
+ Retrieve the current worker ID. Requires a secure connection.
220
+
221
+ API details:
222
+ HTTP Method: GET
223
+ Path: /v1/harvester/get_current_worker_id
224
+
225
+ Args:
226
+ req(PandaRequest): internally generated request object
227
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
228
+
229
+ Returns:
230
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
231
+ """
232
+ tmp_logger = LogWrapper(_logger, "get_current_worker_id")
233
+ tmp_logger.debug("Start")
234
+ current_worker_id = global_task_buffer.get_max_worker_id(harvester_id)
235
+ tmp_logger.debug("Done")
236
+
237
+ if current_worker_id is None:
238
+ return generate_response(False, message=MESSAGE_DATABASE)
239
+
240
+ return generate_response(True, data=current_worker_id)
241
+
242
+
243
+ @request_validation(_logger, secure=True, request_method="GET")
244
+ def get_worker_statistics(req: PandaRequest) -> Dict[str, Any]:
245
+ """
246
+ Get worker statistics.
247
+
248
+ Get statistics for all the workers managed across the Grid. Requires a secure connection.
249
+
250
+ API details:
251
+ HTTP Method: GET
252
+ Path: /v1/harvester/get_worker_statistics
253
+
254
+ Args:
255
+ req(PandaRequest): internally generated request object
256
+
257
+ Returns:
258
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
259
+ """
260
+ tmp_logger = LogWrapper(_logger, "get_worker_statistics")
261
+ tmp_logger.debug("Start")
262
+ worker_stats = global_task_buffer.getWorkerStats()
263
+ tmp_logger.debug("Done")
264
+ return generate_response(True, data=worker_stats)
265
+
266
+
267
+ @request_validation(_logger, secure=True, request_method="POST")
268
+ def report_worker_statistics(req: PandaRequest, harvester_id: str, panda_queue: str, statistics: str) -> Dict[str, Any]:
269
+ """
270
+ Report worker statistics.
271
+
272
+ Report statistics for the workers managed by a harvester instance at a PanDA queue. Requires a secure connection.
273
+
274
+ API details:
275
+ HTTP Method: POST
276
+ Path: /v1/harvester/report_worker_statistics
277
+
278
+ Args:
279
+ req (PandaRequest): Internally generated request object.
280
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
281
+ panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
282
+ statistics(str): JSON string containing a dictionary with the statistics to be reported. It will be stored as a json in the database. E.g.
283
+ ```
284
+ json.dumps({"user": {"SCORE": {"running": 1, "submitted": 1}}, "managed": {"MCORE": {"running": 1, "submitted": 1}}})
285
+ ```
286
+
287
+ Returns:
288
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
289
+ """
290
+ tmp_logger = LogWrapper(_logger, f"report_worker_statistics harvester_id={harvester_id}")
291
+ tmp_logger.debug("Start")
292
+ success, message = global_task_buffer.reportWorkerStats_jobtype(harvester_id, panda_queue, statistics)
293
+ tmp_logger.debug("Done")
294
+ return generate_response(success, message=message)
295
+
296
+
297
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
298
+ def acquire_commands(req: PandaRequest, harvester_id: str, n_commands: int, timeout: int = 30) -> Dict[str, Any]:
299
+ """
300
+ Get harvester commands.
301
+
302
+ Retrieves the commands for a specified harvester instance. Requires a secure connection and production role.
303
+
304
+ API details:
305
+ HTTP Method: POST
306
+ Path: /v1/harvester/acquire_commands
307
+
308
+ Args:
309
+ req(PandaRequest): The request object containing the environment variables.
310
+ harvester_id(str): harvester id, e.g. `harvester_central_A`
311
+ n_commands(int): The number of commands to retrieve, e.g. `10`.
312
+ timeout(int, optional): The timeout value. Defaults to `30`.
313
+
314
+ Returns:
315
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
316
+ """
317
+ tmp_logger = LogWrapper(_logger, "acquire_commands")
318
+ tmp_logger.debug("Start")
319
+
320
+ timed_method = TimedMethod(global_task_buffer.getCommands, timeout)
321
+ timed_method.run(harvester_id, n_commands)
322
+
323
+ tmp_logger.debug("Done")
324
+
325
+ # Getting the commands timed out
326
+ if timed_method.result == TIME_OUT:
327
+ return generate_response(False, message=TIME_OUT)
328
+
329
+ # Unpack the return code and the commands
330
+ return_code, commands = timed_method.result
331
+
332
+ # There was an error retrieving the commands from the database
333
+ if return_code == -1:
334
+ return generate_response(False, message=MESSAGE_DATABASE)
335
+
336
+ return generate_response(True, data=commands)
337
+
338
+
339
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
340
+ def acknowledge_commands(req: PandaRequest, command_ids: List, timeout: int = 30) -> Dict[str, Any]:
341
+ """
342
+ Acknowledge harvester commands.
343
+
344
+ Acknowledges the list of command IDs in the PanDA database. Requires a secure connection and production role.
345
+
346
+ API details:
347
+ HTTP Method: POST
348
+ Path: /v1/harvester/acknowledge_commands
349
+
350
+ Args:
351
+ req(PandaRequest): The request object containing the environment variables.
352
+ command_ids(list): A list of command IDs to acknowledge, e.g. `[1, 2, 3, 4,...]`.
353
+ timeout(int, optional): The timeout value. Defaults to `30`.
354
+
355
+ Returns:
356
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
357
+ """
358
+ tmp_logger = LogWrapper(_logger, "acknowledge_commands")
359
+ tmp_logger.debug("Start")
360
+
361
+ timed_method = TimedMethod(global_task_buffer.ackCommands, timeout)
362
+ timed_method.run(command_ids)
363
+
364
+ tmp_logger.debug("Done")
365
+
366
+ # Make response
367
+ if timed_method.result == TIME_OUT:
368
+ return generate_response(False, message=TIME_OUT)
369
+
370
+ # Unpack the return code and the commands
371
+ return_code = timed_method.result
372
+
373
+ # There was an error acknowledging the commands in the database
374
+ if return_code == -1:
375
+ return generate_response(False, message=MESSAGE_DATABASE)
376
+
377
+ return generate_response(True)
378
+
379
+
380
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
381
+ def add_sweep_command(req: PandaRequest, panda_queue: str, status_list: List[str], ce_list: List[str], submission_host_list: List[str]) -> Dict[str, Any]:
382
+ """
383
+ Add sweep command for harvester.
384
+
385
+ Send a command to harvester to kill the workers in a PanDA queue, with the possibility of specifying filters by status, CE or submission host. Requires a secure connection and production role.
386
+
387
+ API details:
388
+ HTTP Method: POST
389
+ Path: /v1/harvester/add_sweep_command
390
+
391
+ Args:
392
+ req(PandaRequest): internally generated request object
393
+ panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
394
+ status_list (list): list of worker statuses to be considered, e.g. `['submitted', 'running']`
395
+ ce_list (list): list of the Computing Elements to be considered, e.g. `['ce1.cern.ch', 'ce2.cern.ch']`
396
+ submission_host_list(list): list of the harvester submission hosts to be considered, e.g. `['submission_host1.cern.ch', 'submission_host2.cern.ch']`
397
+
398
+ Returns:
399
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
400
+ """
401
+
402
+ tmp_logger = LogWrapper(_logger, f"add_sweep_command panda_queue={panda_queue}")
403
+ tmp_logger.debug("Start")
404
+ return_message = global_task_buffer.sweepPQ(panda_queue, status_list, ce_list, submission_host_list)
405
+ if return_message == "OK":
406
+ success, message = True, ""
407
+ else:
408
+ success, message = False, return_message
409
+ tmp_logger.debug("Done")
410
+ return generate_response(success, message=message)
411
+
412
+
413
+ @request_validation(_logger, secure=True, production=True, request_method="POST")
414
+ def add_target_slots(req, panda_queue: str, slots: int, global_share: str = None, resource_type: str = None, expiration_date: str = None):
415
+ """
416
+ Set target slots.
417
+
418
+ Set the target number of slots for a PanDA queue, when you want to build up job pressure. Requires secure connection and production role.
419
+
420
+ API details:
421
+ HTTP Method: POST
422
+ Path: /v1/harvester/add_target_slots
423
+
424
+ Args:
425
+ req (PandaRequest): Internally generated request object.
426
+ panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
427
+ slots (int): Number of slots to set, e.g. `10000`.
428
+ global_share (str, optional): Global share the slots apply to. Optional - by default it applies to the whole queue. E.g. `User Analysis`
429
+ resource_type (str, optional): Resource type the slots apply to. Optional - by default it applies to the whole queue. E.g. `SCORE` or `MCORE`.
430
+ expiration_date (str, optional): The expiration date of the slots. Optional - by default it applies indefinitely.
431
+
432
+ Returns:
433
+ dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
434
+ """
435
+ tmp_logger = LogWrapper(_logger, f"add_target_slots panda_queue={panda_queue}")
436
+ tmp_logger.debug(f"Start with slots={slots}, global_share={global_share}, resource_type={resource_type}, expiration_date={expiration_date}")
437
+ return_code, return_message = global_task_buffer.setNumSlotsForWP(panda_queue, slots, global_share, resource_type, expiration_date)
438
+
439
+ if return_code == 0:
440
+ success, message = True, return_message
441
+ else:
442
+ success, message = False, return_message
443
+
444
+ tmp_logger.debug("Done")
445
+ return generate_response(success, message=message)
@@ -0,0 +1,166 @@
1
+ """
2
+ client methods
3
+ """
4
+
5
+ import os
6
+
7
+ import requests
8
+ from pandacommon.pandautils.net_utils import replace_hostname_in_url_randomly
9
+
10
+ # PanDA server configuration
11
+ base_url = os.environ.get("PANDA_URL", "http://pandaserver.cern.ch:25080/server/panda")
12
+ base_url_ssl = os.environ.get("PANDA_URL_SSL", "https://pandaserver.cern.ch:25443/server/panda")
13
+
14
+ api_url = os.environ.get("PANDA_API_URL", "http://pandaserver.cern.ch:25080/api/v1")
15
+ api_url_ssl = os.environ.get("PANDA_API_URL_SSL", "https://pandaserver.cern.ch:25443/api/v1")
16
+
17
+ # exit code
18
+ EC_Failed = 255
19
+
20
+
21
+ def is_https(url):
22
+ # check if https is used
23
+ return url.startswith("https://")
24
+
25
+
26
+ class HttpClient:
27
+ def __init__(self):
28
+ # verification of the host certificate
29
+ if "PANDA_VERIFY_HOST" in os.environ and os.environ["PANDA_VERIFY_HOST"] == "off":
30
+ self.verifyHost = False
31
+ else:
32
+ self.verifyHost = True
33
+
34
+ # request a compressed response
35
+ self.compress = True
36
+
37
+ # SSL cert/key
38
+ self.ssl_certificate = self._x509()
39
+ self.ssl_key = self._x509()
40
+
41
+ # OIDC
42
+ self.oidc = os.getenv("PANDA_AUTH") == "oidc"
43
+ self.auth_vo = os.getenv("PANDA_AUTH_VO") if self.oidc else None
44
+ self.id_token = os.getenv("PANDA_AUTH_ID_TOKEN") if self.oidc else None
45
+ if self.id_token and self.id_token.startswith("file:"):
46
+ with open(self.id_token[5:], "r") as f:
47
+ self.id_token = f.read().strip()
48
+
49
+ def _x509(self):
50
+ # retrieve the X509_USER_PROXY from the environment variables
51
+ try:
52
+ return os.environ["X509_USER_PROXY"]
53
+ except Exception:
54
+ pass
55
+
56
+ # look for the default place
57
+ x509 = f"/tmp/x509up_u{os.getuid()}"
58
+ if os.access(x509, os.R_OK):
59
+ return x509
60
+
61
+ # no valid proxy certificate
62
+ print("No valid grid proxy certificate found")
63
+ return ""
64
+
65
+ def _prepare_url(self, url):
66
+ """Modify URL with HTTPS check and hostname replacement."""
67
+ use_https = is_https(url)
68
+ if "PANDA_BEHIND_REAL_LB" in os.environ:
69
+ modified_url = url
70
+ else:
71
+ modified_url = replace_hostname_in_url_randomly(url)
72
+ return modified_url, use_https
73
+
74
+ def _prepare_headers(self, accept_json=True, content_type_json=True, encoding=None):
75
+ """Prepare headers based on authentication and JSON settings."""
76
+ headers = {}
77
+ if accept_json:
78
+ headers["Accept"] = "application/json"
79
+ if content_type_json:
80
+ headers["Content-Type"] = "application/json"
81
+
82
+ if encoding:
83
+ headers["Content-Encoding"] = encoding
84
+
85
+ if self.oidc:
86
+ headers["Authorization"] = f"Bearer {self.id_token}"
87
+ headers["Origin"] = self.auth_vo
88
+
89
+ return headers
90
+
91
+ def _prepare_ssl(self, use_https):
92
+ """Prepare SSL configuration based on HTTPS usage and verification settings."""
93
+ cert = None
94
+ verify = True
95
+ if use_https:
96
+ if not self.oidc:
97
+ cert = (self.ssl_certificate, self.ssl_key)
98
+
99
+ if not self.verifyHost:
100
+ verify = False
101
+ elif "X509_CERT_DIR" in os.environ:
102
+ verify = os.environ["X509_CERT_DIR"]
103
+ elif os.path.exists("/etc/grid-security/certificates"):
104
+ verify = "/etc/grid-security/certificates"
105
+
106
+ return cert, verify
107
+
108
+ def get(self, url, data):
109
+ url, use_https = self._prepare_url(url)
110
+ headers = self._prepare_headers()
111
+ cert, verify = self._prepare_ssl(use_https)
112
+
113
+ try:
114
+ response = requests.get(url, headers=headers, params=data, timeout=600, cert=cert, verify=verify)
115
+ response.raise_for_status()
116
+ return 0, response.json()
117
+ except requests.RequestException as e:
118
+ return 255, str(e)
119
+
120
+ def post(self, url, data):
121
+ url, use_https = self._prepare_url(url)
122
+ headers = self._prepare_headers()
123
+ cert, verify = self._prepare_ssl(use_https)
124
+
125
+ try:
126
+ response = requests.post(url, headers=headers, json=data, timeout=600, cert=cert, verify=verify)
127
+ response.raise_for_status()
128
+ return 0, response.json()
129
+ except requests.RequestException as e:
130
+ return 255, str(e)
131
+
132
+ def post_files(self, url, data, encoding=None):
133
+ url, use_https = self._prepare_url(url)
134
+ headers = self._prepare_headers(content_type_json=False, encoding=encoding)
135
+ cert, verify = self._prepare_ssl(use_https)
136
+
137
+ files = {}
138
+ try:
139
+ for key, value in data.items():
140
+ if isinstance(data[key], str):
141
+ # we got a file to upload without specifying the destination name
142
+ files[key] = open(data[key], "rb")
143
+ else:
144
+ # we got a file to upload which specifies the destination name
145
+ files[key] = (data[key][0], open(data[key][1], "rb"))
146
+ print(f"cert: {cert}, verify: {verify}")
147
+ response = requests.post(url, headers=headers, files=files, timeout=600, cert=cert, verify=verify)
148
+ response.raise_for_status()
149
+ return 0, response.json()
150
+ except requests.RequestException as e:
151
+ return 255, str(e)
152
+ finally:
153
+ for file in files.values():
154
+ if isinstance(file, tuple):
155
+ file_handler = file[1]
156
+ else:
157
+ file_handler = file
158
+ file_handler.close()
159
+
160
+ def override_oidc(self, oidc, id_token, auth_vo):
161
+ """
162
+ Override OIDC settings.
163
+ """
164
+ self.oidc = oidc
165
+ self.id_token = id_token
166
+ self.auth_vo = auth_vo