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,572 @@
1
+ import copy
2
+ import datetime
3
+ import json
4
+ import math
5
+ import os
6
+ import re
7
+ import subprocess
8
+ from threading import Lock
9
+
10
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
11
+
12
+
13
+ # replacement for commands
14
+ def commands_get_status_output(com):
15
+ data = ""
16
+ try:
17
+ p = subprocess.Popen(
18
+ com,
19
+ shell=True,
20
+ universal_newlines=True,
21
+ stdout=subprocess.PIPE,
22
+ stderr=subprocess.STDOUT,
23
+ )
24
+ data, unused_err = p.communicate()
25
+ retcode = p.poll()
26
+ if retcode:
27
+ ex = subprocess.CalledProcessError(retcode, com)
28
+ raise ex
29
+ status = 0
30
+ except subprocess.CalledProcessError as ex:
31
+ status = ex.returncode
32
+ if data[-1:] == "\n":
33
+ data = data[:-1]
34
+ return status, data
35
+
36
+
37
+ # extract name from DN
38
+ def clean_user_id(id):
39
+ try:
40
+ up = re.compile("/(DC|O|OU|C|L)=[^\/]+")
41
+ username = up.sub("", id)
42
+ up2 = re.compile("/CN=[0-9]+")
43
+ username = up2.sub("", username)
44
+ up3 = re.compile(" [0-9]+")
45
+ username = up3.sub("", username)
46
+ up4 = re.compile("_[0-9]+")
47
+ username = up4.sub("", username)
48
+ username = username.replace("/CN=proxy", "")
49
+ username = username.replace("/CN=limited proxy", "")
50
+ username = username.replace("limited proxy", "")
51
+ username = re.sub("/CN=Robot:[^/]+", "", username)
52
+ username = re.sub("/CN=nickname:[^/]+", "", username)
53
+ pat = re.compile(".*/CN=([^\/]+)/CN=([^\/]+)")
54
+ mat = pat.match(username)
55
+ if mat:
56
+ username = mat.group(2)
57
+ else:
58
+ username = username.replace("/CN=", "")
59
+ if username.lower().find("/email") > 0:
60
+ username = username[: username.lower().find("/email")]
61
+ pat = re.compile(".*(limited.*proxy).*")
62
+ mat = pat.match(username)
63
+ if mat:
64
+ username = mat.group(1)
65
+ username = username.replace("(", "")
66
+ username = username.replace(")", "")
67
+ username = username.replace("'", "")
68
+ name_wo_email = re.sub(r" [a-z][\w\.-]+@[\w\.-]+(?:\.\w+)+", "", username).strip()
69
+ if " " in name_wo_email:
70
+ username = name_wo_email
71
+ return username
72
+ except Exception:
73
+ return id
74
+
75
+
76
+ # extract bare string from DN
77
+ def get_bare_dn(dn, keep_proxy=False, keep_digits=True):
78
+ dn = re.sub("/CN=limited proxy", "", dn)
79
+ if keep_proxy:
80
+ dn = re.sub("/CN=proxy(/CN=proxy)+", "/CN=proxy", dn)
81
+ else:
82
+ dn = re.sub("(/CN=proxy)+", "", dn)
83
+ if not keep_digits:
84
+ dn = re.sub(r"(/CN=\d+)+$", "", dn)
85
+ return dn
86
+
87
+
88
+ # extract id string from DN
89
+ def get_id_from_dn(dn, keep_proxy=False, keep_digits=True):
90
+ m = re.search("/CN=nickname:([^/]+)", dn)
91
+ if m:
92
+ return m.group(1)
93
+ return get_bare_dn(dn, keep_proxy, keep_digits)
94
+
95
+
96
+ def get_distinguished_name_list(distinguished_name: str) -> list:
97
+ """
98
+ Get a list of possible distinguished names from a string, including legacy and RFC formats.
99
+
100
+ Args:
101
+ distinguished_name (str): The distinguished name string.
102
+
103
+ Returns:
104
+ list: A list of possible distinguished names.
105
+ """
106
+ name_list = []
107
+ # characters to be escaped in RFC format
108
+ trans_table = str.maketrans({",": r"\,", "+": r"\+", '"': r"\"", "\\": r"\\", "<": r"\<", ">": r"\>", ";": r"\;"})
109
+ # loop over distinguished name and without-CN form
110
+ for tmp_name in [distinguished_name, get_bare_dn(distinguished_name, keep_digits=False)]:
111
+ name_list.append(tmp_name)
112
+ # replace backslashes with commas and reverse substrings to be converted to RFC format
113
+ tmp_list = [s.translate(trans_table) for s in tmp_name.split("/") if s]
114
+ name_list.append(",".join(tmp_list[::-1]))
115
+ return name_list
116
+
117
+
118
+ def normalize_cpu_model(cpu_model):
119
+ cpu_model = cpu_model.upper()
120
+ # Remove GHz, cache sizes, and redundant words
121
+ cpu_model = re.sub(r"@\s*\d+(\.\d+)?\s*GHZ", "", cpu_model)
122
+ cpu_model = re.sub(r"\d+\s*KB", "", cpu_model)
123
+ cpu_model = re.sub(r"CORE PROCESSOR\s*\([^)]*\)", "", cpu_model)
124
+ cpu_model = re.sub(r"\b\d+-CORE\b", "", cpu_model)
125
+ cpu_model = re.sub(r"CPU|CORE|PROCESSOR|SCALABLE|\(R\)|\(TM\)", "", cpu_model)
126
+ cpu_model = re.sub(r"\s+", " ", cpu_model)
127
+ cpu_model = cpu_model.strip()
128
+ return cpu_model
129
+
130
+
131
+ def clean_host_name(host_name):
132
+ # If the worker node comes in the slot1@worker1.example.com format, we remove the slot1@ part
133
+ match = re.search(r"@(.+)", host_name)
134
+ host_name = match.group(1) if match else host_name
135
+
136
+ # Special handling for ATLAS worker nodes to extract the third field of the hostname, since the first 2 fields are not unique
137
+ # e.g. atlprd55-xyz-<third_field>.cern.ch
138
+ match = re.match(r"^atlprd\d+-[^-]+-([^.]+\.cern\.ch)$", host_name)
139
+ host_name = match.group(1) if match else host_name
140
+
141
+ return host_name
142
+
143
+
144
+ # resolve string bool
145
+ def resolve_bool(param):
146
+ if isinstance(param, bool):
147
+ return param
148
+ if param == "True":
149
+ return True
150
+ if param == "False":
151
+ return False
152
+ return param
153
+
154
+
155
+ # cached object
156
+ class CachedObject:
157
+ # constructor
158
+ def __init__(self, name, time_interval, update_func, log_stream):
159
+ # name
160
+ self.name = name
161
+ # cached object
162
+ self.cachedObj = None
163
+ # datetime of last updated
164
+ self.lastUpdated = naive_utcnow()
165
+ # update frequency
166
+ self.timeInterval = datetime.timedelta(seconds=time_interval)
167
+ # lock
168
+ self.lock = Lock()
169
+ # function to update object
170
+ self.updateFunc = update_func
171
+ # log
172
+ self.log_stream = log_stream
173
+
174
+ # update obj
175
+ def update(self):
176
+ # lock
177
+ self.lock.acquire()
178
+ # get current datetime
179
+ current = naive_utcnow()
180
+ # update if old
181
+ if self.cachedObj is None or current - self.lastUpdated > self.timeInterval:
182
+ self.log_stream.debug(f"PID={os.getpid()} renewing {self.name} cache")
183
+ try:
184
+ tmp_stat, tmp_out = self.updateFunc()
185
+ self.log_stream.debug(f"PID={os.getpid()} got for {self.name} {tmp_stat} {str(tmp_out)}")
186
+ if tmp_stat:
187
+ self.cachedObj = tmp_out
188
+ except Exception as e:
189
+ self.log_stream.error(f"PID={os.getpid()} failed to renew {self.name} due to {str(e)}")
190
+ self.lastUpdated = current
191
+ # release
192
+ self.lock.release()
193
+ # return
194
+ return
195
+
196
+ # contains
197
+ def __contains__(self, item):
198
+ self.update()
199
+ contains = False
200
+ try:
201
+ contains = item in self.cachedObj
202
+ except TypeError:
203
+ pass
204
+ return contains
205
+
206
+ # get item
207
+ def __getitem__(self, name):
208
+ self.update()
209
+ return self.cachedObj[name]
210
+
211
+ # get method
212
+ def get(self, *var):
213
+ return self.cachedObj.get(*var)
214
+
215
+ # get object
216
+ def get_object(self):
217
+ self.lock.acquire()
218
+ return self.cachedObj
219
+
220
+ # release object
221
+ def release_object(self):
222
+ self.lock.release()
223
+
224
+
225
+ # dictionary of caches
226
+ class CacheDict:
227
+ """
228
+ Dictionary of caches with periodic cleanup
229
+ """
230
+
231
+ # constructor
232
+ def __init__(self, update_interval=10, cleanup_interval=60):
233
+ self.idx = 0
234
+ self.lock = Lock()
235
+ self.cache_dict = {}
236
+ self.update_interval = datetime.timedelta(minutes=update_interval)
237
+ self.cleanup_interval = datetime.timedelta(minutes=cleanup_interval)
238
+ self.last_cleanup = naive_utcnow()
239
+
240
+ def cleanup(self, tmp_log):
241
+ """
242
+ Cleanup caches
243
+ :param tmp_log: logger
244
+ """
245
+ with self.lock:
246
+ current = naive_utcnow()
247
+ if current - self.last_cleanup > self.cleanup_interval:
248
+ for name in list(self.cache_dict):
249
+ cache = self.cache_dict[name]
250
+ if current - cache["last_updated"] > self.cleanup_interval:
251
+ tmp_log.debug(f"""deleting cache #{self.cache_dict[name]["idx"]}""")
252
+ del self.cache_dict[name]
253
+ self.last_cleanup = current
254
+
255
+ def get(self, name, tmp_log, update_func, *update_args, **update_kwargs):
256
+ """
257
+ Get updated object
258
+ :param name: name of cache
259
+ :param tmp_log: logger
260
+ :param update_func: function to update object
261
+ :param update_args: arguments for update function
262
+ :param update_kwargs: keyword arguments for update function
263
+ :return: object or None
264
+ """
265
+ self.cleanup(tmp_log)
266
+ with self.lock:
267
+ obj = self.cache_dict.get(name)
268
+ current = naive_utcnow()
269
+ if not obj:
270
+ tmp_log.debug(f"creating new cache #{self.idx}")
271
+ # create new cache
272
+ obj = {
273
+ "obj": update_func(*update_args, **update_kwargs),
274
+ "last_updated": current,
275
+ "update_func": update_func,
276
+ "update_args": update_args,
277
+ "update_kwargs": update_kwargs,
278
+ "idx": self.idx,
279
+ }
280
+ self.cache_dict[name] = obj
281
+ self.idx += 1
282
+ else:
283
+ # update if old
284
+ if current - obj["last_updated"] > self.update_interval:
285
+ tmp_log.debug(f"""updating cache #{obj["idx"]}""")
286
+ obj["obj"] = obj["update_func"](*obj["update_args"], **obj["update_kwargs"])
287
+ obj["last_updated"] = current
288
+ else:
289
+ tmp_log.debug(f"""reusing cache #{obj["idx"]}""")
290
+ return obj["obj"]
291
+
292
+
293
+ # convert datetime to string
294
+ class NonJsonObjectEncoder(json.JSONEncoder):
295
+ def default(self, obj):
296
+ if isinstance(obj, datetime.datetime):
297
+ return {"_datetime_object": obj.strftime("%Y-%m-%d %H:%M:%S.%f")}
298
+ return json.JSONEncoder.default(self, obj)
299
+
300
+
301
+ # hook for json decoder
302
+ def as_python_object(dct):
303
+ if "_datetime_object" in dct:
304
+ return datetime.datetime.strptime(str(dct["_datetime_object"]), "%Y-%m-%d %H:%M:%S.%f")
305
+ return dct
306
+
307
+
308
+ # get effective file size
309
+ def getEffectiveFileSize(fsize, startEvent, endEvent, nEvents):
310
+ inMB = 1024 * 1024
311
+ if fsize in [None, 0]:
312
+ # use dummy size for pseudo input
313
+ effectiveFsize = inMB
314
+ elif nEvents is not None and startEvent is not None and endEvent is not None:
315
+ # take event range into account
316
+ effectiveFsize = int(float(fsize) * float(endEvent - startEvent + 1) / float(nEvents))
317
+ else:
318
+ effectiveFsize = fsize
319
+ # use dummy size if input is too small
320
+ if effectiveFsize == 0:
321
+ effectiveFsize = inMB
322
+ # in MB
323
+ effectiveFsize = float(effectiveFsize) / inMB
324
+ # return
325
+ return effectiveFsize
326
+
327
+
328
+ # get effective number of events
329
+ def getEffectiveNumEvents(startEvent, endEvent, nEvents):
330
+ if endEvent is not None and startEvent is not None:
331
+ evtCounts = endEvent - startEvent + 1
332
+ if evtCounts > 0:
333
+ return evtCounts
334
+ return 1
335
+ if nEvents is not None:
336
+ return nEvents
337
+ return 1
338
+
339
+
340
+ # get memory usage
341
+ def getMemoryUsage():
342
+ try:
343
+ t = open(f"/proc/{os.getpid()}/status")
344
+ v = t.read()
345
+ t.close()
346
+ value = 0
347
+ for line in v.split("\n"):
348
+ if line.startswith("VmRSS"):
349
+ items = line.split()
350
+ value = int(items[1])
351
+ if items[2] in ["kB", "KB"]:
352
+ value /= 1024
353
+ elif items[2] in ["mB", "MB"]:
354
+ pass
355
+ break
356
+ return int(value)
357
+ except Exception:
358
+ return None
359
+
360
+
361
+ # check process
362
+ def checkProcess(pid):
363
+ return os.path.exists(f"/proc/{pid}/status")
364
+
365
+
366
+ # offset for walltime
367
+ wallTimeOffset = 10 * 60
368
+
369
+
370
+ # convert config parameters
371
+ def convert_config_params(itemStr):
372
+ items = itemStr.split(":")
373
+ newItems = []
374
+ for item in items:
375
+ if item == "":
376
+ newItems.append(None)
377
+ elif "," in item:
378
+ newItems.append(item.split(","))
379
+ else:
380
+ try:
381
+ newItems.append(int(item))
382
+ except Exception:
383
+ newItems.append(item)
384
+ return newItems
385
+
386
+
387
+ # parse init params
388
+ def parse_init_params(par):
389
+ if isinstance(par, list):
390
+ return par
391
+ try:
392
+ return par.split("|")
393
+ except Exception:
394
+ return [par]
395
+
396
+
397
+ # get config param for vo and prodSourceLabel
398
+ def getConfigParam(configStr, vo, sourceLabel):
399
+ try:
400
+ for _ in configStr.split(","):
401
+ items = configStr.split(":")
402
+ vos = items[0].split("|")
403
+ sourceLabels = items[1].split("|")
404
+ if vo not in ["", "any"] and vo not in vos and None not in vos and "any" not in vos and "" not in vos:
405
+ continue
406
+ if (
407
+ sourceLabel not in ["", "any"]
408
+ and sourceLabel not in sourceLabels
409
+ and None not in sourceLabels
410
+ and "any" not in sourceLabels
411
+ and "" not in sourceLabels
412
+ ):
413
+ continue
414
+ return ",".join(items[2:])
415
+ except Exception:
416
+ pass
417
+ return None
418
+
419
+
420
+ # get percentile until numpy 1.5.X becomes available
421
+ def percentile(inList, percent, idMap):
422
+ inList = sorted(copy.copy(inList))
423
+ k = (len(inList) - 1) * float(percent) / 100
424
+ f = math.floor(k)
425
+ c = math.ceil(k)
426
+ if f == c:
427
+ retVal = inList[int(f)]
428
+ return retVal, [retVal]
429
+ val0 = inList[int(f)]
430
+ val1 = inList[int(c)]
431
+ d0 = val0 * (c - k)
432
+ d1 = val1 * (k - f)
433
+ retVal = d0 + d1
434
+ return retVal, [val0, val1]
435
+
436
+
437
+ # get max walltime and cpu count
438
+ def getJobMaxWalltime(taskSpec, inputChunk, totalMasterEvents, jobSpec, siteSpec):
439
+ try:
440
+ if taskSpec.getCpuTime() is None:
441
+ # use PQ maxtime when CPU time is not defined
442
+ jobSpec.maxWalltime = siteSpec.maxtime
443
+ jobSpec.maxCpuCount = siteSpec.maxtime
444
+ else:
445
+ jobSpec.maxWalltime = taskSpec.getCpuTime()
446
+ if jobSpec.maxWalltime is not None and jobSpec.maxWalltime > 0:
447
+ jobSpec.maxWalltime *= totalMasterEvents
448
+ if siteSpec.coreCount > 0:
449
+ jobSpec.maxWalltime /= float(siteSpec.coreCount)
450
+ if siteSpec.corepower not in [0, None]:
451
+ jobSpec.maxWalltime /= siteSpec.corepower
452
+ if taskSpec.cpuEfficiency not in [None, 0]:
453
+ jobSpec.maxWalltime /= float(taskSpec.cpuEfficiency) / 100.0
454
+ if taskSpec.baseWalltime is not None:
455
+ jobSpec.maxWalltime += taskSpec.baseWalltime
456
+ jobSpec.maxWalltime = int(jobSpec.maxWalltime)
457
+ if taskSpec.useHS06():
458
+ jobSpec.maxCpuCount = jobSpec.maxWalltime
459
+ except Exception:
460
+ pass
461
+
462
+
463
+ # use direct IO for job
464
+ def use_direct_io_for_job(task_spec, site_spec, input_chunk):
465
+ # not for merging
466
+ if input_chunk and input_chunk.isMerging:
467
+ return False
468
+ # always
469
+ if site_spec.always_use_direct_io():
470
+ return True
471
+ # force copy-to-scratch
472
+ if task_spec.useLocalIO():
473
+ return False
474
+ # depends on task and site specs
475
+ if task_spec.allowInputLAN() is not None and site_spec.isDirectIO():
476
+ return True
477
+ return False
478
+
479
+
480
+ # stopwatch
481
+ class StopWatch:
482
+ """Utility class to measure timing information."""
483
+
484
+ def __init__(self, identifier: str = None):
485
+ self.start_time = datetime.datetime.now()
486
+ self.checkpoint = self.start_time
487
+ self.step_name = None
488
+ self.identifier = identifier
489
+
490
+ def reset(self):
491
+ """Reset the stopwatch."""
492
+ self.start_time = datetime.datetime.now()
493
+ self.checkpoint = self.start_time
494
+ self.step_name = None
495
+
496
+ def get_elapsed_time(self, new_step_name: str) -> str:
497
+ """Get the elapsed time since the stopwatch was started and the duration since the last checkpoint.
498
+ :param new_step_name: The name of the next step.
499
+ Returns:
500
+ str: A string with the elapsed time and the duration since the last checkpoint.
501
+ """
502
+ now = datetime.datetime.now()
503
+ total_delta = now - self.start_time
504
+ duration_delta = now - self.checkpoint
505
+ return_str = ""
506
+ if self.identifier:
507
+ return_str += f"{self.identifier}: "
508
+ return_str += f"elapsed {total_delta.seconds}.{int(total_delta.microseconds / 1000):03d} sec. "
509
+ if self.step_name is not None:
510
+ return_str += f"{self.step_name} took {duration_delta.seconds}.{int(duration_delta.microseconds / 1000):03d} sec. "
511
+ if new_step_name:
512
+ return_str += f"{new_step_name} started."
513
+ else:
514
+ return_str += "done."
515
+ self.checkpoint = now
516
+ self.step_name = new_step_name
517
+ return return_str
518
+
519
+
520
+ # construct execution comment to reassign a task
521
+ def make_reassign_comment(site: str = None, cloud: str = None, nucleus: str = None, mode: str = None) -> str:
522
+ """
523
+ Construct execution comment to reassign a task to a site, cloud or nucleus with different modes.
524
+
525
+ :param site: target site for reassignment
526
+ :param cloud: target cloud for reassignment
527
+ :param nucleus: target nucleus for reassignment
528
+ :param mode: can be "nokill" to avoid killing running jobs and "soft" to trigger rebrokerage without killing running jobs
529
+
530
+ :return: execution comment for task reassignment
531
+ """
532
+ # reassign to site, nucleus or cloud. Note that Prodsys use blah="" to trigger rebrokerage
533
+ if site is not None:
534
+ comment = f"site:{site}:y" # set 'y' to go back to oldStatus bypassing rebrokerage
535
+ else:
536
+ if nucleus is not None:
537
+ comment = f"nucleus:{nucleus}"
538
+ else:
539
+ comment = f"cloud:{cloud}"
540
+ # set 'y' to trigger rebrokerage
541
+ comment += ":n"
542
+
543
+ # set additional modes
544
+ if mode == "nokill":
545
+ comment += ":nokill reassign"
546
+ elif mode == "soft":
547
+ comment += ":soft reassign"
548
+
549
+ return comment
550
+
551
+
552
+ # parse execution comment to get task reassignment instructions
553
+ def parse_reassign_comment(comment: str) -> dict:
554
+ """
555
+ Parse execution comment to get task reassignment instructions, including target site/cloud/nucleus, whether to go back to old status and additional modes.
556
+
557
+ :param comment: execution comment for task reassignment
558
+
559
+ :return: a dictionary with keys "target", "value", "back_to_old_status" and "mode" (if any)
560
+ """
561
+ info = {"target": "site", "value": None, "back_to_old_status": False, "mode": None}
562
+ try:
563
+ items = comment.split(":")
564
+ if len(items) >= 3:
565
+ info["target"] = items[0]
566
+ info["value"] = items[1]
567
+ info["back_to_old_status"] = items[2] == "y"
568
+ if len(items) > 3:
569
+ info["mode"] = items[3]
570
+ except Exception:
571
+ pass
572
+ return info
@@ -0,0 +1,106 @@
1
+ """
2
+ email utilities
3
+ """
4
+
5
+ import smtplib
6
+ import sys
7
+
8
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
9
+
10
+ from pandaserver.config import panda_config
11
+
12
+ # logger
13
+ _logger = PandaLogger().getLogger("MailUtils")
14
+
15
+
16
+ # wrapper to patch smtplib.stderr to send debug info to logger
17
+ class StderrLogger(object):
18
+ def __init__(self, tmpLog):
19
+ self.tmpLog = tmpLog
20
+
21
+ def write(self, message):
22
+ message = message.strip()
23
+ if message != "":
24
+ self.tmpLog.debug(message)
25
+
26
+
27
+ # wrapper of SMTP to redirect messages
28
+ class MySMTP(smtplib.SMTP):
29
+ def set_log(self, tmp_log):
30
+ self.tmpLog = tmp_log
31
+ try:
32
+ self.org_stderr = getattr(smtplib, "stderr")
33
+ setattr(smtplib, "stderr", tmp_log)
34
+ except Exception:
35
+ self.org_stderr = None
36
+
37
+ def _print_debug(self, *args):
38
+ self.tmpLog.write(" ".join(map(str, args)))
39
+
40
+ def reset_log(self):
41
+ if self.org_stderr is not None:
42
+ setattr(smtplib, "stderr", self.org_stderr)
43
+
44
+
45
+ class MailUtils:
46
+ # constructor
47
+ def __init__(self):
48
+ pass
49
+
50
+ # main
51
+ def send(self, toAddr, mailSubject, mailBody):
52
+ _logger.debug("start SEND session")
53
+ try:
54
+ # remove duplicated address
55
+ listToAddr = []
56
+ newToAddr = ""
57
+ for tmpToAddr in toAddr.split(","):
58
+ if tmpToAddr not in listToAddr:
59
+ listToAddr.append(tmpToAddr)
60
+ newToAddr += f"{tmpToAddr},"
61
+ toAddr = newToAddr[:-1]
62
+
63
+ # make message
64
+ fromAdd = panda_config.emailSender
65
+ message = f"Subject: {mailSubject}\n" f"From: {fromAdd}\n" f"To: {toAddr}\n\n" f"{mailBody}"
66
+ message = self.addTailer(message)
67
+
68
+ # send mail
69
+ _logger.debug(f"send to {toAddr}\n{message}")
70
+ stderrLog = StderrLogger(_logger)
71
+ server = MySMTP(panda_config.emailSMTPsrv)
72
+ server.set_debuglevel(1)
73
+ server.set_log(stderrLog)
74
+ server.ehlo()
75
+ server.starttls()
76
+ # server.login(panda_config.emailLogin,panda_config.emailPass)
77
+ out = server.sendmail(fromAdd, listToAddr, message)
78
+ _logger.debug(out)
79
+ server.quit()
80
+ retVal = True
81
+ except Exception:
82
+ type, value, traceBack = sys.exc_info()
83
+ _logger.error(f"{type} {value}")
84
+ retVal = False
85
+ try:
86
+ server.reset_log()
87
+ except Exception:
88
+ pass
89
+ _logger.debug("end SEND session")
90
+ return retVal
91
+
92
+ # add tailer
93
+ def addTailer(self, msg):
94
+ msg += """
95
+ Report Panda problems of any sort to
96
+
97
+ the eGroup for help request
98
+ hn-atlas-dist-analysis-help@cern.ch
99
+
100
+ the Discourse forum for distributed computing help
101
+ https://atlas-talk.web.cern.ch/c/distributed-computing-help
102
+
103
+ the Panda JIRA for software bug
104
+ https://its.cern.ch/jira/browse/ATLASPANDA
105
+ """
106
+ return msg
File without changes