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,753 @@
1
+ import copy
2
+ import datetime
3
+ import gc
4
+ import importlib
5
+ import json
6
+ import multiprocessing
7
+ import os
8
+ import queue
9
+ import signal
10
+ import socket
11
+ import sys
12
+ import threading
13
+ import time
14
+ import traceback
15
+
16
+ import psutil
17
+ from pandacommon.pandalogger import logger_utils
18
+ from pandacommon.pandautils.thread_utils import GenericThread, LockPool
19
+
20
+ from pandaserver.config import daemon_config, panda_config
21
+
22
+ # list of signals accepted to end the main process
23
+ END_SIGNALS = [
24
+ signal.SIGINT,
25
+ signal.SIGHUP,
26
+ signal.SIGTERM,
27
+ ]
28
+
29
+ # mandatory attributes and their type of daemon
30
+ MANDATORY_ATTRS = [
31
+ ("module", str),
32
+ ("period", int),
33
+ ("arguments", list),
34
+ ]
35
+
36
+ # command to send in pipe to stop daemon worker processes
37
+ CMD_STOP = "__STOP"
38
+
39
+ # epoch datetime
40
+ EPOCH = datetime.datetime.fromtimestamp(0)
41
+
42
+ # requester id for taskbuffer
43
+ requester_id = GenericThread().get_full_id(__name__, sys.modules[__name__].__file__)
44
+
45
+
46
+ def kill_proc_tree(pid, sig=signal.SIGKILL, include_parent=True, timeout=None, on_terminate=None):
47
+ """
48
+ Kill a process tree (including grandchildren) with signal "sig" and return a (gone, still_alive) tuple.
49
+ "on_terminate", if specified, is a callback function which is called as soon as a child terminates.
50
+ """
51
+ assert pid != os.getpid(), "will not kill myself"
52
+ parent = psutil.Process(pid)
53
+ children = parent.children(recursive=True)
54
+ if include_parent:
55
+ children.append(parent)
56
+ for p in children:
57
+ try:
58
+ p.send_signal(sig)
59
+ except psutil.NoSuchProcess:
60
+ pass
61
+ gone, alive = psutil.wait_procs(children, timeout=timeout, callback=on_terminate)
62
+ return (gone, alive)
63
+
64
+
65
+ def daemon_loop(dem_config, msg_queue, pipe_conn, worker_lifetime, tbuf=None, lock_pool=None):
66
+ """
67
+ Main loop of daemon worker process
68
+ """
69
+ # pid of the worker
70
+ my_pid = os.getpid()
71
+ my_full_pid = f"{socket.getfqdn().split('.')[0]}-{os.getpgrp()}-{my_pid}"
72
+ # logger to log in file
73
+ base_logger = logger_utils.setup_logger("daemons")
74
+ tmp_log = logger_utils.make_logger(base_logger, f"worker_pid={my_pid}")
75
+ tmp_log.info("daemon worker start")
76
+
77
+ # signal handler
78
+ def got_end_sig(sig, frame):
79
+ tmp_log.warning(f"(got signal {sig})")
80
+
81
+ for sig in END_SIGNALS:
82
+ signal.signal(sig, got_end_sig)
83
+
84
+ # dict of all daemons and their script module object
85
+ module_map = {}
86
+ # package of daemon scripts
87
+ mod_package = getattr(daemon_config, "package")
88
+ # start timestamp
89
+ start_ts = time.time()
90
+ # expiry time
91
+ expiry_ts = start_ts + worker_lifetime
92
+ # timestamp of getting last message
93
+ last_msg_ts = start_ts
94
+ # timestamp of last warning of no message
95
+ last_no_msg_warn_ts = start_ts
96
+ # interval in second for warning of no message
97
+ no_msg_warn_interval = 300
98
+ # create taskBuffer object if not given
99
+ if tbuf is None:
100
+ # initialize oracledb using dummy connection
101
+ try:
102
+ from pandaserver.taskbuffer.Initializer import initializer
103
+
104
+ initializer.init()
105
+ except Exception as e:
106
+ tmp_log.error(f"failed to launch initializer with {e.__class__.__name__}: {e} ; terminated")
107
+ return
108
+ # taskBuffer object
109
+ try:
110
+ from pandaserver.taskbuffer.TaskBuffer import taskBuffer as tbuf
111
+
112
+ tbuf.init(
113
+ panda_config.dbhost,
114
+ panda_config.dbpasswd,
115
+ nDBConnection=1,
116
+ useTimeout=True,
117
+ requester=requester_id,
118
+ )
119
+ tmp_log.debug("taskBuffer initialized")
120
+ except Exception as e:
121
+ tmp_log.error(f"failed to initialize taskBuffer with {e.__class__.__name__}: {e} ; terminated")
122
+ return
123
+ # import module of all daemons
124
+ for dem_name, attrs in dem_config.items():
125
+ mod_name = attrs["module"]
126
+ try:
127
+ the_module = importlib.import_module(f".{mod_name}", mod_package)
128
+ module_map[dem_name] = the_module
129
+ except Exception as e:
130
+ tmp_log.warning(f"for daemon {dem_name}, failed to import {mod_name} with {e.__class__.__name__}: {e} ; skipped it")
131
+ else:
132
+ module_map[dem_name] = the_module
133
+ tmp_log.debug("initialized, running")
134
+ # loop
135
+ while True:
136
+ # stop the worker since when reaches its lifetime
137
+ if time.time() > expiry_ts:
138
+ tmp_log.info("worker reached its lifetime, stop this worker")
139
+ break
140
+ # get command from pipe
141
+ if pipe_conn.poll():
142
+ cmd = pipe_conn.recv()
143
+ if cmd == CMD_STOP:
144
+ # got stop command, stop the process
145
+ tmp_log.info("got stop command, stop this worker")
146
+ break
147
+ else:
148
+ tmp_log.debug(f'got invalid command "{cmd}" ; skipped it')
149
+ # clean up memory
150
+ gc.collect()
151
+ # get a message from queue
152
+ tmp_log.debug("waiting for message...")
153
+ keep_going = True
154
+ one_msg = None
155
+ while True:
156
+ try:
157
+ one_msg = msg_queue.get(timeout=5)
158
+ last_msg_ts = time.time()
159
+ break
160
+ except queue.Empty:
161
+ now_ts = time.time()
162
+ # timeout warning if not getting messages for long time
163
+ no_msg_duration = now_ts - last_msg_ts
164
+ last_no_msg_warn_duration = now_ts - last_no_msg_warn_ts
165
+ if no_msg_duration >= no_msg_warn_interval and last_no_msg_warn_duration >= no_msg_warn_interval:
166
+ tmp_log.warning(f"no message gotten (qid={id(msg_queue)}) for {no_msg_duration:.3f} sec")
167
+ last_no_msg_warn_ts = now_ts
168
+ # timeout to get from queue, check whether to keep going
169
+ if now_ts > expiry_ts:
170
+ # worker expired, do not keep going
171
+ keep_going = False
172
+ break
173
+ # keep going
174
+ if not keep_going:
175
+ continue
176
+ # process message
177
+ if one_msg in module_map and one_msg is not None:
178
+ # got a daemon name, get the module object and corresponding attributes
179
+ dem_name = one_msg
180
+ tmp_log.debug(f"got message of {dem_name}")
181
+ the_module = module_map[dem_name]
182
+ attrs = dem_config[dem_name]
183
+ mod_args = attrs["arguments"]
184
+ mod_argv = tuple([__file__] + mod_args)
185
+ dem_period = attrs["period"]
186
+ dem_period_in_minute = dem_period / 60.0
187
+ is_sync = attrs["sync"]
188
+ is_loop = attrs["loop"]
189
+ # initialize variables
190
+ to_run_daemon = False
191
+ has_run = False
192
+ last_run_start_ts = 0
193
+ last_run_end_ts = 0
194
+ # component name in lock table
195
+ component = f"pandaD.{dem_name}"
196
+ # whether the daemon should be synchronized among nodes
197
+ if is_sync:
198
+ # synchronized daemon, check process lock in DB
199
+ ret_val, locked_time = tbuf.checkProcessLock_PANDA(
200
+ component=component,
201
+ pid=my_full_pid,
202
+ time_limit=dem_period_in_minute,
203
+ )
204
+ if ret_val:
205
+ # locked by some process on other nodes
206
+ last_run_start_ts = int((locked_time - EPOCH).total_seconds())
207
+ tmp_log.debug(f"found {dem_name} is locked by other process ; skipped it")
208
+ else:
209
+ # try to get the lock
210
+ got_lock = tbuf.lockProcess_PANDA(
211
+ component=component,
212
+ pid=my_full_pid,
213
+ time_limit=dem_period_in_minute,
214
+ )
215
+ if got_lock:
216
+ # got the lock
217
+ to_run_daemon = True
218
+ tmp_log.debug(f"got lock of {dem_name}")
219
+ else:
220
+ # did not get lock, skip
221
+ last_run_start_ts = int(time.time())
222
+ tmp_log.debug(f"did not get lock of {dem_name} ; skipped it")
223
+ else:
224
+ to_run_daemon = True
225
+ # run daemon
226
+ if to_run_daemon:
227
+ last_run_start_ts = int(time.time())
228
+ # send daemon status back to master
229
+ status_tuple = (dem_name, to_run_daemon, has_run, last_run_start_ts, last_run_end_ts)
230
+ pipe_conn.send(status_tuple)
231
+ try:
232
+ if is_loop:
233
+ # go looping the script until reaching daemon period
234
+ tmp_log.info(f"{dem_name} start looping")
235
+ start_ts = time.time()
236
+ while True:
237
+ ret_val = the_module.main(argv=mod_argv, tbuf=tbuf, lock_pool=lock_pool)
238
+ now_ts = time.time()
239
+ if not ret_val:
240
+ # daemon main function says stop the loop
241
+ break
242
+ if now_ts > start_ts + dem_period:
243
+ # longer than the period, stop the loop
244
+ break
245
+ tmp_log.info(f"{dem_name} finish looping")
246
+ else:
247
+ # execute the module script with arguments
248
+ tmp_log.info(f"{dem_name} start")
249
+ the_module.main(argv=mod_argv, tbuf=tbuf, lock_pool=lock_pool)
250
+ tmp_log.info(f"{dem_name} finish")
251
+ except Exception as e:
252
+ # with error
253
+ tb = traceback.format_exc()
254
+ tmp_log.error(f"failed to run daemon {dem_name} with {e.__class__.__name__}: {e}\n{tb}\n ; stop this worker")
255
+ # daemon has run but failed
256
+ last_run_end_ts = int(time.time())
257
+ has_run = True
258
+ # send daemon status back to master
259
+ status_tuple = (
260
+ dem_name,
261
+ to_run_daemon,
262
+ has_run,
263
+ last_run_start_ts,
264
+ last_run_end_ts,
265
+ )
266
+ pipe_conn.send(status_tuple)
267
+ # stop the worker
268
+ break
269
+ else:
270
+ # daemon has run
271
+ last_run_end_ts = int(time.time())
272
+ has_run = True
273
+ # send daemon status back to master
274
+ status_tuple = (dem_name, to_run_daemon, has_run, last_run_start_ts, last_run_end_ts)
275
+ pipe_conn.send(status_tuple)
276
+ else:
277
+ # got invalid message
278
+ tmp_log.warning(f'got invalid message "{one_msg}", skipped it')
279
+ # sleep
280
+ time.sleep(2**-5)
281
+
282
+
283
+ class DaemonWorker(object):
284
+ """
285
+ Class of worker process of PanDA daemon
286
+ """
287
+
288
+ __slots__ = (
289
+ "pid",
290
+ "parent_conn",
291
+ "child_conn",
292
+ "process",
293
+ "dem_name",
294
+ "dem_ts",
295
+ )
296
+
297
+ # class lock
298
+ _lock = threading.Lock()
299
+
300
+ # constructor
301
+ def __init__(self, dem_config, msg_queue, worker_lifetime, tbuf=None, lock_pool=None):
302
+ # synchronized with lock
303
+ with self._lock:
304
+ self._make_pipe()
305
+ self._make_process(
306
+ dem_config=dem_config,
307
+ msg_queue=msg_queue,
308
+ worker_lifetime=worker_lifetime,
309
+ tbuf=tbuf,
310
+ lock_pool=lock_pool,
311
+ )
312
+
313
+ def _make_pipe(self):
314
+ """
315
+ make pipe connection pairs between master and this worker
316
+ """
317
+ self.parent_conn, self.child_conn = multiprocessing.Pipe()
318
+
319
+ def _close_pipe(self):
320
+ """
321
+ close pipe connection pairs between master and this worker
322
+ """
323
+ self.parent_conn.close()
324
+ self.child_conn.close()
325
+
326
+ def _make_process(self, dem_config, msg_queue, worker_lifetime, tbuf, lock_pool):
327
+ """
328
+ make associate process of this worker
329
+ """
330
+ args = (
331
+ dem_config,
332
+ msg_queue,
333
+ self.child_conn,
334
+ worker_lifetime,
335
+ tbuf,
336
+ lock_pool,
337
+ )
338
+ self.process = multiprocessing.Process(target=daemon_loop, args=args)
339
+
340
+ def start(self):
341
+ """
342
+ start the worker process
343
+ """
344
+ self.unset_dem()
345
+ self.process.start()
346
+ self.pid = self.process.pid
347
+
348
+ def is_alive(self):
349
+ """
350
+ whether the worker process is alive
351
+ """
352
+ return self.process.is_alive()
353
+
354
+ def kill(self):
355
+ """
356
+ kill the worker process and all its subprocesses
357
+ """
358
+ self._close_pipe()
359
+ return kill_proc_tree(self.process.pid)
360
+
361
+ def is_running_dem(self):
362
+ """
363
+ whether the worker is still running a daemon script
364
+ """
365
+ return not (self.dem_name is None and self.dem_ts is None)
366
+
367
+ def set_dem(self, dem_name, dem_ts):
368
+ """
369
+ set current running daemon in this worker
370
+ """
371
+ if not self.is_running_dem() or dem_ts >= self.dem_ts:
372
+ self.dem_name = dem_name
373
+ self.dem_ts = dem_ts
374
+
375
+ def unset_dem(self):
376
+ """
377
+ unset current running daemon in this worker
378
+ """
379
+ self.dem_ts = None
380
+ self.dem_name = None
381
+
382
+
383
+ class DaemonMaster(object):
384
+ """
385
+ Class of master process of PanDA daemon
386
+ """
387
+
388
+ # constructor
389
+ def __init__(self, logger, n_workers=1, n_dbconn=1, worker_lifetime=28800, use_tbif=False):
390
+ # logger
391
+ self.logger = logger
392
+ # number of daemon worker processes
393
+ self.n_workers = n_workers
394
+ # number of db connections for common taskBuffer interface
395
+ self.n_dbconn = n_dbconn
396
+ # lifetime of daemon worker processes
397
+ self.worker_lifetime = worker_lifetime
398
+ # whether to use TaskBufferInterface to save DB sessions while prone to taskbuffer hanging
399
+ self.use_tbif = use_tbif
400
+ # locks
401
+ self._worker_lock = threading.Lock()
402
+ self._status_lock = threading.Lock()
403
+ # make message queue
404
+ self._reset_msg_queue()
405
+ # process pool
406
+ self.proc_pool = []
407
+ # worker pool
408
+ self.worker_pool = set()
409
+ # whether to stop scheduler
410
+ self.to_stop_scheduler = False
411
+ # make daemon config
412
+ self.dem_config = {}
413
+ self._parse_config()
414
+ # map of run status of daemons
415
+ self.dem_run_map = {}
416
+ self._make_dem_run_map()
417
+ # map to store global states
418
+ self.global_state_map = {}
419
+ # shared taskBufferIF
420
+ self.tbif = None
421
+ self._make_tbif()
422
+ # shared lock pool
423
+ self.lock_pool = LockPool()
424
+ # spawn workers
425
+ self._spawn_workers(self.n_workers)
426
+
427
+ def _reset_msg_queue(self):
428
+ """
429
+ reset the message queue for sending commands to workers
430
+ """
431
+ self.msg_queue = multiprocessing.Queue()
432
+ self.logger.info(f"reset message queue (qid={id(self.msg_queue)})")
433
+
434
+ def _make_tbif(self):
435
+ """
436
+ make common taskBuffer interface for daemon workers
437
+ """
438
+ try:
439
+ # import is always required to have reserveChangedState consistent in *Spec
440
+ from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
441
+ from pandaserver.taskbuffer.TaskBufferInterface import TaskBufferInterface
442
+
443
+ if not self.use_tbif:
444
+ return
445
+ # taskBuffer
446
+ _tbuf = TaskBuffer()
447
+ _tbuf.init(
448
+ panda_config.dbhost,
449
+ panda_config.dbpasswd,
450
+ nDBConnection=self.n_dbconn,
451
+ useTimeout=True,
452
+ requester=requester_id,
453
+ )
454
+ # taskBuffer interface for multiprocessing
455
+ taskBufferIF = TaskBufferInterface()
456
+ taskBufferIF.launch(_tbuf)
457
+ self.logger.debug("taskBuffer interface initialized")
458
+ self.tbif = taskBufferIF
459
+ except Exception as e:
460
+ self.logger.error(f"failed to initialize taskBuffer interface with {e.__class__.__name__}: {e} ; terminated")
461
+ raise e
462
+
463
+ def _spawn_workers(self, n_workers=1, auto_start=False):
464
+ """
465
+ spawn new workers and put them into worker pool
466
+ """
467
+ for j in range(n_workers):
468
+ with self._worker_lock:
469
+ if self.use_tbif:
470
+ tbuf = self.tbif.getInterface()
471
+ else:
472
+ tbuf = None
473
+ worker = DaemonWorker(
474
+ dem_config=self.dem_config,
475
+ msg_queue=self.msg_queue,
476
+ worker_lifetime=self.worker_lifetime,
477
+ tbuf=tbuf,
478
+ lock_pool=self.lock_pool,
479
+ )
480
+ self.worker_pool.add(worker)
481
+ if auto_start:
482
+ worker.start()
483
+ self.logger.debug(f"launched new worker_pid={worker.pid}")
484
+
485
+ def _remove_worker(self, worker):
486
+ """
487
+ remove a worker from pool
488
+ """
489
+ with self._worker_lock:
490
+ self.worker_pool.discard(worker)
491
+
492
+ def _parse_config(self):
493
+ """
494
+ parse configuration of PanDA daemon
495
+ """
496
+ try:
497
+ config_json = daemon_config.config
498
+ if isinstance(config_json, dict):
499
+ config_dict = config_json
500
+ else:
501
+ config_dict = json.loads(config_json)
502
+ self.dem_config = copy.deepcopy(config_dict)
503
+ # loop over daemons
504
+ for dem_name, attrs in config_dict.items():
505
+ # remove disabled daemons
506
+ if "enable" in attrs and attrs["enable"] is False:
507
+ del self.dem_config[dem_name]
508
+ continue
509
+ # handle option attributes
510
+ if "module" not in attrs:
511
+ self.dem_config[dem_name]["module"] = dem_name
512
+ if "arguments" not in attrs:
513
+ self.dem_config[dem_name]["arguments"] = []
514
+ if "sync" not in attrs:
515
+ self.dem_config[dem_name]["sync"] = False
516
+ if "loop" not in attrs:
517
+ self.dem_config[dem_name]["loop"] = False
518
+ if "timeout" not in attrs:
519
+ self.dem_config[dem_name]["timeout"] = min(attrs["period"] * 3, attrs["period"] + 3600)
520
+ # check mandatory attributes
521
+ the_attrs = copy.deepcopy(self.dem_config[dem_name])
522
+ for attr, attr_type in MANDATORY_ATTRS:
523
+ if attr not in the_attrs:
524
+ self.logger.warning(f'daemon config missing attribute "{attr}" for {dem_name} ; skipped')
525
+ del self.dem_config[dem_name]
526
+ break
527
+ elif not isinstance(the_attrs[attr], attr_type):
528
+ self.logger.warning(
529
+ f'daemon config has invalid type of attribute "{attr}" for {dem_name} (type must be {attr_type.__name__}) ; skipped'
530
+ )
531
+ del self.dem_config[dem_name]
532
+ break
533
+ except Exception as e:
534
+ tb = traceback.format_exc()
535
+ self.logger.error(f"failed to parse daemon config, {e.__class__.__name__}: {e}\n{tb}\n")
536
+
537
+ def _make_dem_run_map(self):
538
+ """
539
+ initialize daemon run status map
540
+ """
541
+ dem_run_map = {}
542
+ for dem in self.dem_config:
543
+ attrs = {}
544
+ attrs["last_run_start_ts"] = 0
545
+ attrs["last_warn_ts"] = 0
546
+ attrs["msg_ongoing"] = False
547
+ attrs["dem_running"] = False
548
+ dem_run_map[dem] = attrs
549
+ self.dem_run_map = dem_run_map
550
+
551
+ def _kill_one_worker(self, worker):
552
+ """
553
+ kill one (stuck) worker (and new worker will be re-spawned in scheduler cycle)
554
+ """
555
+ # kill worker process and remove it from pool
556
+ worker.kill()
557
+ self._remove_worker(worker)
558
+ # reset daemon run status map of the daemon run by the worker
559
+ self.dem_run_map[worker.dem_name]["msg_ongoing"] = False
560
+ self.dem_run_map[worker.dem_name]["dem_running"] = False
561
+
562
+ def _scheduler_cycle(self):
563
+ """
564
+ main scheduler cycle
565
+ """
566
+ # self.logger.debug(f"run scheduler cycle")
567
+ now_ts = int(time.time())
568
+ # check last run time from pipes
569
+ for worker in list(self.worker_pool):
570
+ # remove dead worker from worker pool
571
+ if not worker.is_alive():
572
+ self._remove_worker(worker)
573
+ # lock daemon run status
574
+ with self._status_lock:
575
+ # get message from the worker
576
+ while worker.parent_conn.poll():
577
+ (
578
+ dem_name,
579
+ to_run_daemon,
580
+ has_run,
581
+ last_run_start_ts,
582
+ last_run_end_ts,
583
+ ) = worker.parent_conn.recv()
584
+ # update run status map
585
+ dem_run_attrs = self.dem_run_map[dem_name]
586
+ old_last_run_start_ts = dem_run_attrs["last_run_start_ts"]
587
+ if last_run_start_ts > old_last_run_start_ts:
588
+ # take latest timestamp of run start
589
+ dem_run_attrs["last_run_start_ts"] = last_run_start_ts
590
+ if not has_run and last_run_start_ts > 0:
591
+ # worker not yet finishes running a daemon script
592
+ worker.set_dem(dem_name, last_run_start_ts)
593
+ if to_run_daemon:
594
+ dem_run_attrs["dem_running"] = True
595
+ if has_run and last_run_end_ts >= last_run_start_ts:
596
+ # worker already finishes running a daemon script
597
+ worker.unset_dem()
598
+ dem_run_attrs["dem_running"] = False
599
+ run_duration = last_run_end_ts - last_run_start_ts
600
+ run_period = self.dem_config[dem_name].get("period")
601
+ is_loop = self.dem_config[dem_name].get("loop")
602
+ if run_duration > run_period and not is_loop:
603
+ # warning since daemon run duration longer than daemon period (non-looping)
604
+ self.logger.warning(f"worker_pid={worker.pid} daemon {dem_name} took {run_duration} sec , exceeding its period {run_period} sec")
605
+ dem_run_attrs["msg_ongoing"] = False
606
+ # kill the worker due to daemon run timeout
607
+ if worker.is_running_dem():
608
+ run_till_now = now_ts - worker.dem_ts
609
+ run_timeout = self.dem_config[worker.dem_name].get("timeout")
610
+ if run_till_now > run_timeout:
611
+ self.logger.warning(
612
+ "worker_pid={pid} killing worker since daemon {dem} took {run} sec , exceeding its timeout {timeout} sec".format(
613
+ pid=worker.pid,
614
+ dem=worker.dem_name,
615
+ run=run_till_now,
616
+ timeout=run_timeout,
617
+ )
618
+ )
619
+ self._kill_one_worker(worker)
620
+ # counter for super delayed daemons
621
+ n_super_delayed_dems = 0
622
+ # send message to workers
623
+ for dem_name, attrs in self.dem_config.items():
624
+ with self._status_lock:
625
+ run_period = attrs.get("period")
626
+ dem_run_attrs = self.dem_run_map[dem_name]
627
+ last_run_start_ts = dem_run_attrs["last_run_start_ts"]
628
+ last_warn_ts = dem_run_attrs["last_warn_ts"]
629
+ if run_period is None or last_run_start_ts is None:
630
+ continue
631
+ if last_run_start_ts + run_period <= now_ts:
632
+ # time to send new message to run the daemon
633
+ msg_ongoing = dem_run_attrs["msg_ongoing"]
634
+ dem_running = dem_run_attrs["dem_running"]
635
+ if msg_ongoing or dem_running:
636
+ # old message not processed yet or daemon still running, skip
637
+ run_delay = now_ts - (last_run_start_ts + run_period)
638
+ warn_since_ago = now_ts - last_warn_ts
639
+ if last_run_start_ts > 0 and run_delay > max(300, run_period // 2):
640
+ # delayed
641
+ n_super_delayed_dems += 1
642
+ if warn_since_ago > 900:
643
+ # warning
644
+ self.logger.warning(f"{dem_name} delayed to run for {run_delay} sec ")
645
+ dem_run_attrs["last_warn_ts"] = now_ts
646
+ else:
647
+ # old message processed, send new message
648
+ self.msg_queue.put(dem_name)
649
+ self.logger.debug(f"scheduled to run {dem_name} ; qsize={self.msg_queue.qsize()}")
650
+ dem_run_attrs["msg_ongoing"] = True
651
+ # dem_run_attrs['last_run_start_ts'] = now_ts
652
+ # warning about delayed scripts
653
+ if n_super_delayed_dems > 0 and (
654
+ ((last_warn_super_delayed_ts := self.global_state_map.get("last_warn_super_delayed_ts")) is None or now_ts - last_warn_super_delayed_ts >= 300)
655
+ or n_super_delayed_dems != (last_n_super_delayed_dems := self.global_state_map.get("last_n_super_delayed_dems"))
656
+ ):
657
+ self.logger.warning(f"{n_super_delayed_dems} delayed scripts")
658
+ self.global_state_map["last_warn_super_delayed_ts"] = now_ts
659
+ self.global_state_map["last_n_super_delayed_dems"] = n_super_delayed_dems
660
+ # call revive if too many daemons are delayed too much (probably the queue is stuck)
661
+ if n_super_delayed_dems >= max(min(4, int(len(self.dem_config) * 0.667)), 1):
662
+ self.logger.warning(f"found {n_super_delayed_dems} daemons delayed too much; start to revive")
663
+ self.revive()
664
+ # spawn new workers if there are less than n_workers
665
+ now_n_workers = len(self.worker_pool)
666
+ if now_n_workers < self.n_workers:
667
+ n_up = self.n_workers - now_n_workers
668
+ self._spawn_workers(n_workers=n_up, auto_start=True)
669
+ # sleep
670
+ time.sleep(0.5)
671
+
672
+ def _stop_all_workers(self):
673
+ """
674
+ stop all workers gracefully by sending stop command to them
675
+ """
676
+ # send stop command
677
+ for worker in self.worker_pool:
678
+ worker.parent_conn.send(CMD_STOP)
679
+ self.logger.debug(f"sent stop command to worker_pid={worker.pid}")
680
+ # reset daemon run status map of all daemons
681
+ for dem_name in self.dem_config:
682
+ self.dem_run_map[dem_name]["msg_ongoing"] = False
683
+ self.dem_run_map[dem_name]["dem_running"] = False
684
+
685
+ def stop(self):
686
+ """
687
+ stop the master (and all workers)
688
+ """
689
+ self.logger.info("daemon master got stop")
690
+ # stop scheduler from sending more message
691
+ self.to_stop_scheduler = True
692
+ # stop all workers gracefully
693
+ self._stop_all_workers()
694
+ # wait a bit
695
+ time.sleep(1)
696
+ # close message queue
697
+ self.msg_queue.close()
698
+ # stop taskBuffer interface
699
+ if self.use_tbif:
700
+ self.tbif.stop()
701
+ # wait a bit
702
+ time.sleep(2)
703
+
704
+ def revive(self):
705
+ """
706
+ revive: kill all workers, reset a new message queue, and spawn new workers with new queue
707
+ """
708
+ self.logger.info("daemon master reviving")
709
+ # stop all workers gracefully
710
+ self._stop_all_workers()
711
+ # wait a bit
712
+ time.sleep(3)
713
+ # kill and remove workers
714
+ for worker in list(self.worker_pool):
715
+ worker.kill()
716
+ self._remove_worker(worker)
717
+ # close message queue
718
+ self.msg_queue.close()
719
+ # reset message queue
720
+ self._reset_msg_queue()
721
+ # reset map of run status of daemons
722
+ self._make_dem_run_map()
723
+ # spawn new workers with new queues
724
+ self._spawn_workers(self.n_workers, auto_start=True)
725
+ # done
726
+ self.logger.info("daemon master revived")
727
+
728
+ def run(self):
729
+ """
730
+ main function to run the master
731
+ """
732
+ # master pid
733
+ master_pid = os.getpid()
734
+ self.logger.info(f"daemon master started ; master_pid={master_pid}")
735
+ # start daemon workers
736
+ with self._worker_lock:
737
+ for worker in self.worker_pool:
738
+ worker.start()
739
+ self.logger.debug(f"launched worker_pid={worker.pid}")
740
+ self.logger.debug("daemon master launched all worker processes")
741
+ # initialize old worker pid set
742
+ worker_pid_list_old = []
743
+ # loop of scheduler
744
+ while not self.to_stop_scheduler:
745
+ with self._worker_lock:
746
+ worker_pid_list = [worker.pid for worker in self.worker_pool]
747
+ if worker_pid_list != worker_pid_list_old:
748
+ # log when worker pid list changes
749
+ self.logger.debug(f"master_pid: {master_pid} ; worker_pids: {str(worker_pid_list)} ")
750
+ worker_pid_list_old = worker_pid_list
751
+ self._scheduler_cycle()
752
+ # end
753
+ self.logger.info("daemon master ended")