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.
- panda_server_source-0.8.0.dist-info/METADATA +55 -0
- panda_server_source-0.8.0.dist-info/RECORD +375 -0
- panda_server_source-0.8.0.dist-info/WHEEL +4 -0
- panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
- pandajedi/__init__.py +0 -0
- pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
- pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
- pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
- pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
- pandajedi/jedibrokerage/GenJobBroker.py +381 -0
- pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
- pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
- pandajedi/jedibrokerage/__init__.py +0 -0
- pandajedi/jediconfig/__init__.py +0 -0
- pandajedi/jediconfig/jedi_config.py +40 -0
- pandajedi/jedicore/FactoryBase.py +182 -0
- pandajedi/jedicore/Interaction.py +433 -0
- pandajedi/jedicore/JediDBProxy.py +56 -0
- pandajedi/jedicore/JediDBProxyPool.py +37 -0
- pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
- pandajedi/jedicore/JediException.py +13 -0
- pandajedi/jedicore/JediTaskBuffer.py +984 -0
- pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
- pandajedi/jedicore/MsgWrapper.py +124 -0
- pandajedi/jedicore/ProcessUtils.py +34 -0
- pandajedi/jedicore/SiteCandidate.py +90 -0
- pandajedi/jedicore/ThreadUtils.py +205 -0
- pandajedi/jedicore/__init__.py +0 -0
- pandajedi/jediddm/AtlasDDMClient.py +1708 -0
- pandajedi/jediddm/DDMClientBase.py +16 -0
- pandajedi/jediddm/DDMInterface.py +83 -0
- pandajedi/jediddm/GenDDMClient.py +17 -0
- pandajedi/jediddm/__init__.py +0 -0
- pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
- pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
- pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
- pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
- pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
- pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
- pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
- pandajedi/jedidog/GenWatchDog.py +21 -0
- pandajedi/jedidog/JumboWatchDog.py +147 -0
- pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
- pandajedi/jedidog/WatchDogBase.py +53 -0
- pandajedi/jedidog/__init__.py +0 -0
- pandajedi/jediexec/reniceJEDI.py +25 -0
- pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
- pandajedi/jedigen/TaskGeneratorBase.py +15 -0
- pandajedi/jedigen/__init__.py +0 -0
- pandajedi/jedimsgprocessor/__init__.py +0 -0
- pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
- pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
- pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
- pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
- pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
- pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
- pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
- pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
- pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
- pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
- pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
- pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
- pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
- pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
- pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
- pandajedi/jediorder/ContentsFeeder.py +785 -0
- pandajedi/jediorder/JediDaemon.py +27 -0
- pandajedi/jediorder/JediKnight.py +55 -0
- pandajedi/jediorder/JediMaster.py +238 -0
- pandajedi/jediorder/JediMsgProcessor.py +32 -0
- pandajedi/jediorder/JobBroker.py +33 -0
- pandajedi/jediorder/JobGenerator.py +2763 -0
- pandajedi/jediorder/JobSplitter.py +308 -0
- pandajedi/jediorder/JobThrottler.py +28 -0
- pandajedi/jediorder/PostProcessor.py +166 -0
- pandajedi/jediorder/TaskBroker.py +268 -0
- pandajedi/jediorder/TaskCommando.py +334 -0
- pandajedi/jediorder/TaskGenerator.py +17 -0
- pandajedi/jediorder/TaskRefiner.py +611 -0
- pandajedi/jediorder/TaskSetupper.py +17 -0
- pandajedi/jediorder/WatchDog.py +79 -0
- pandajedi/jediorder/__init__.py +0 -0
- pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
- pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
- pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
- pandajedi/jedipprocess/GenPostProcessor.py +35 -0
- pandajedi/jedipprocess/MailTemplates.py +185 -0
- pandajedi/jedipprocess/PostProcessorBase.py +269 -0
- pandajedi/jedipprocess/__init__.py +0 -0
- pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
- pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
- pandajedi/jedirefine/GenTaskRefiner.py +61 -0
- pandajedi/jedirefine/RefinerUtils.py +154 -0
- pandajedi/jedirefine/TaskRefinerBase.py +996 -0
- pandajedi/jedirefine/__init__.py +0 -0
- pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
- pandajedi/jedisetup/GenTaskSetupper.py +16 -0
- pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
- pandajedi/jedisetup/TaskSetupperBase.py +15 -0
- pandajedi/jedisetup/__init__.py +0 -0
- pandajedi/jeditest/SchemaChecker.py +41 -0
- pandajedi/jeditest/addContWoSoTask.py +54 -0
- pandajedi/jeditest/addDataCarouselRequest.py +47 -0
- pandajedi/jeditest/addEventServiceTask.py +101 -0
- pandajedi/jeditest/addHpoTask.py +81 -0
- pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
- pandajedi/jeditest/addNonAtlasTask.py +71 -0
- pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
- pandajedi/jeditest/addTestTask.py +124 -0
- pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
- pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
- pandajedi/jeditest/brokerTest.py +78 -0
- pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
- pandajedi/jeditest/contentsFeederTest.py +28 -0
- pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
- pandajedi/jeditest/ddmTest.py +4 -0
- pandajedi/jeditest/jobGenForOneTask.py +38 -0
- pandajedi/jeditest/jobGeneratorTest.py +43 -0
- pandajedi/jeditest/jobSplitterTest.py +104 -0
- pandajedi/jeditest/jobThrottlerTest.py +22 -0
- pandajedi/jeditest/msgProcessorTest.py +9 -0
- pandajedi/jeditest/postProcessorTest.py +18 -0
- pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
- pandajedi/jeditest/resubmitTaskParam.py +20 -0
- pandajedi/jeditest/taskBrokerTest.py +22 -0
- pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
- pandajedi/jeditest/taskBufferTest.py +5 -0
- pandajedi/jeditest/taskCommandoTest.py +28 -0
- pandajedi/jeditest/taskRefinerTest.py +30 -0
- pandajedi/jeditest/testOneTaskToRefine.py +49 -0
- pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
- pandajedi/jeditest/testPandaDDMMap.py +62 -0
- pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
- pandajedi/jeditest/watchDogTest.py +25 -0
- pandajedi/jeditest/workQueueTest.py +22 -0
- pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
- pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
- pandajedi/jedithrottle/GenJobThrottler.py +26 -0
- pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
- pandajedi/jedithrottle/__init__.py +0 -0
- pandaserver/__init__.py +0 -0
- pandaserver/api/__init__.py +0 -0
- pandaserver/api/v1/__init__.py +0 -0
- pandaserver/api/v1/common.py +339 -0
- pandaserver/api/v1/credential_management_api.py +372 -0
- pandaserver/api/v1/data_carousel_api.py +264 -0
- pandaserver/api/v1/event_api.py +269 -0
- pandaserver/api/v1/file_server_api.py +942 -0
- pandaserver/api/v1/harvester_api.py +445 -0
- pandaserver/api/v1/http_client.py +166 -0
- pandaserver/api/v1/idds_api.py +180 -0
- pandaserver/api/v1/job_api.py +463 -0
- pandaserver/api/v1/metaconfig_api.py +122 -0
- pandaserver/api/v1/pilot_api.py +895 -0
- pandaserver/api/v1/statistics_api.py +207 -0
- pandaserver/api/v1/system_api.py +186 -0
- pandaserver/api/v1/task_api.py +1520 -0
- pandaserver/api/v1/tests/__init__.py +0 -0
- pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
- pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
- pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
- pandaserver/api/v1/tests/idds_api_tests.py +45 -0
- pandaserver/api/v1/tests/job_api_tests.py +112 -0
- pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
- pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
- pandaserver/api/v1/tests/system_api_tests.py +58 -0
- pandaserver/api/v1/tests/task_api_tests.py +590 -0
- pandaserver/api/v1/version.py +2 -0
- pandaserver/api/v1/workflow_api.py +136 -0
- pandaserver/brokerage/SiteMapper.py +406 -0
- pandaserver/brokerage/__init__.py +0 -0
- pandaserver/brokerage/broker.py +272 -0
- pandaserver/config/__init__.py +0 -0
- pandaserver/config/config_utils.py +15 -0
- pandaserver/config/daemon_config.py +29 -0
- pandaserver/config/mcp_config.py +38 -0
- pandaserver/config/panda_config.py +233 -0
- pandaserver/configurator/Carbon.py +65 -0
- pandaserver/configurator/Configurator.py +908 -0
- pandaserver/configurator/__init__.py +0 -0
- pandaserver/configurator/aux.py +73 -0
- pandaserver/daemons/__init__.py +0 -0
- pandaserver/daemons/master.py +110 -0
- pandaserver/daemons/master_systemd.py +79 -0
- pandaserver/daemons/scripts/__init__.py +0 -0
- pandaserver/daemons/scripts/add_main.py +264 -0
- pandaserver/daemons/scripts/add_sub.py +221 -0
- pandaserver/daemons/scripts/cache_pilots.py +47 -0
- pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
- pandaserver/daemons/scripts/carbon.py +50 -0
- pandaserver/daemons/scripts/configurator.py +94 -0
- pandaserver/daemons/scripts/copyArchive.py +1387 -0
- pandaserver/daemons/scripts/datasetManager.py +1154 -0
- pandaserver/daemons/scripts/dummy_test.py +25 -0
- pandaserver/daemons/scripts/evpPD2P.py +144 -0
- pandaserver/daemons/scripts/hs_scrapers.py +264 -0
- pandaserver/daemons/scripts/metric_collector.py +927 -0
- pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
- pandaserver/daemons/scripts/pilotStreaming.py +109 -0
- pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
- pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
- pandaserver/daemons/scripts/task_evaluator.py +405 -0
- pandaserver/daemons/scripts/tmpwatch.py +70 -0
- pandaserver/daemons/scripts/worker_synchronization.py +97 -0
- pandaserver/daemons/utils.py +753 -0
- pandaserver/dataservice/DataServiceUtils.py +323 -0
- pandaserver/dataservice/ErrorCode.py +25 -0
- pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
- pandaserver/dataservice/__init__.py +0 -0
- pandaserver/dataservice/activator.py +79 -0
- pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
- pandaserver/dataservice/adder_dummy_plugin.py +31 -0
- pandaserver/dataservice/adder_gen.py +954 -0
- pandaserver/dataservice/adder_plugin_base.py +27 -0
- pandaserver/dataservice/adder_result.py +75 -0
- pandaserver/dataservice/adder_simple_plugin.py +160 -0
- pandaserver/dataservice/closer.py +247 -0
- pandaserver/dataservice/closer_atlas_plugin.py +90 -0
- pandaserver/dataservice/ddm.py +1381 -0
- pandaserver/dataservice/ddm_handler.py +95 -0
- pandaserver/dataservice/dyn_data_distributer.py +824 -0
- pandaserver/dataservice/event_lookup_client_ei.py +77 -0
- pandaserver/dataservice/event_picker.py +268 -0
- pandaserver/dataservice/finisher.py +194 -0
- pandaserver/dataservice/setupper.py +225 -0
- pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
- pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
- pandaserver/dataservice/setupper_plugin_base.py +67 -0
- pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
- pandaserver/jobdispatcher/ErrorCode.py +19 -0
- pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
- pandaserver/jobdispatcher/Protocol.py +443 -0
- pandaserver/jobdispatcher/Watcher.py +186 -0
- pandaserver/jobdispatcher/__init__.py +0 -0
- pandaserver/pandamcp/__init__.py +0 -0
- pandaserver/pandamcp/mcp_main.py +56 -0
- pandaserver/pandamcp/mcp_test_client.py +95 -0
- pandaserver/pandamcp/mcp_utils.py +74 -0
- pandaserver/proxycache/__init__.py +0 -0
- pandaserver/proxycache/panda_proxy_cache.py +240 -0
- pandaserver/proxycache/token_cache.py +141 -0
- pandaserver/server/.gacl +6 -0
- pandaserver/server/panda.py +539 -0
- pandaserver/srvcore/CoreUtils.py +572 -0
- pandaserver/srvcore/MailUtils.py +106 -0
- pandaserver/srvcore/__init__.py +0 -0
- pandaserver/srvcore/allowed_methods.py +100 -0
- pandaserver/srvcore/oidc_utils.py +152 -0
- pandaserver/srvcore/panda_request.py +196 -0
- pandaserver/srvcore/srv_msg_utils.py +42 -0
- pandaserver/taskbuffer/ConBridge.py +513 -0
- pandaserver/taskbuffer/DBProxyPool.py +95 -0
- pandaserver/taskbuffer/DataCarousel.py +3053 -0
- pandaserver/taskbuffer/DatasetSpec.py +137 -0
- pandaserver/taskbuffer/DdmSpec.py +201 -0
- pandaserver/taskbuffer/ErrorCode.py +87 -0
- pandaserver/taskbuffer/EventServiceUtils.py +396 -0
- pandaserver/taskbuffer/FileSpec.py +290 -0
- pandaserver/taskbuffer/GlobalShares.py +244 -0
- pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
- pandaserver/taskbuffer/Initializer.py +82 -0
- pandaserver/taskbuffer/InputChunk.py +1080 -0
- pandaserver/taskbuffer/JediCacheSpec.py +57 -0
- pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
- pandaserver/taskbuffer/JediFileSpec.py +242 -0
- pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
- pandaserver/taskbuffer/JobSpec.py +1006 -0
- pandaserver/taskbuffer/JobUtils.py +232 -0
- pandaserver/taskbuffer/NucleusSpec.py +116 -0
- pandaserver/taskbuffer/OraDBProxy.py +174 -0
- pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
- pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
- pandaserver/taskbuffer/ParseJobXML.py +350 -0
- pandaserver/taskbuffer/PickleFileSpec.py +18 -0
- pandaserver/taskbuffer/PickleJobSpec.py +25 -0
- pandaserver/taskbuffer/PrioUtil.py +40 -0
- pandaserver/taskbuffer/ProcessGroups.py +111 -0
- pandaserver/taskbuffer/ResourceSpec.py +175 -0
- pandaserver/taskbuffer/SQLDumper.py +37 -0
- pandaserver/taskbuffer/SQLManager.py +60 -0
- pandaserver/taskbuffer/SiteSpec.py +304 -0
- pandaserver/taskbuffer/SupErrors.py +10 -0
- pandaserver/taskbuffer/TaskBuffer.py +2882 -0
- pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
- pandaserver/taskbuffer/Utils.py +848 -0
- pandaserver/taskbuffer/WorkQueue.py +227 -0
- pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
- pandaserver/taskbuffer/WorkerSpec.py +143 -0
- pandaserver/taskbuffer/WrappedCursor.py +487 -0
- pandaserver/taskbuffer/WrappedPickle.py +88 -0
- pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
- pandaserver/taskbuffer/__init__.py +0 -0
- pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
- pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
- pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
- pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
- pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
- pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
- pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
- pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
- pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
- pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
- pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
- pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
- pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
- pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
- pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
- pandaserver/taskbuffer/retryModule.py +552 -0
- pandaserver/taskbuffer/task_split_rules.py +308 -0
- pandaserver/taskbuffer/workflow_processor.py +249 -0
- pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
- pandaserver/test/SchemaChecker.py +40 -0
- pandaserver/test/__init__.py +0 -0
- pandaserver/test/banUser.py +43 -0
- pandaserver/test/boostPrio.py +19 -0
- pandaserver/test/boostUser.py +35 -0
- pandaserver/test/callbackDDM.py +11 -0
- pandaserver/test/daod_on_demand.py +95 -0
- pandaserver/test/finishJob.py +184 -0
- pandaserver/test/finishTaskJEDI.py +11 -0
- pandaserver/test/getJobs.py +53 -0
- pandaserver/test/killJob.py +79 -0
- pandaserver/test/killJobLowPrio.py +99 -0
- pandaserver/test/killJobsInTask.py +56 -0
- pandaserver/test/killTask.py +80 -0
- pandaserver/test/killTaskJEDI.py +11 -0
- pandaserver/test/killUser.py +83 -0
- pandaserver/test/lsst/README.txt +7 -0
- pandaserver/test/lsst/lsstSubmit.py +139 -0
- pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
- pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
- pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
- pandaserver/test/reassignJobs.py +17 -0
- pandaserver/test/reassignSite.py +82 -0
- pandaserver/test/reassignTask.py +117 -0
- pandaserver/test/reassignWaiting.py +39 -0
- pandaserver/test/recoverLostFiles.py +7 -0
- pandaserver/test/reloadInputDS.py +51 -0
- pandaserver/test/sendCommandToJob.py +26 -0
- pandaserver/test/setDebugMode.py +38 -0
- pandaserver/test/setPriority.py +28 -0
- pandaserver/test/testEvgen.py +60 -0
- pandaserver/test/testEvgen17.py +58 -0
- pandaserver/test/testG4sim.py +81 -0
- pandaserver/test/testG4sim17.py +90 -0
- pandaserver/test/testGlobalShares.py +131 -0
- pandaserver/test/testReco.py +103 -0
- pandaserver/test/testSimulReco14.py +98 -0
- pandaserver/test/testSiteMap.py +26 -0
- pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
- pandaserver/userinterface/Client.py +1686 -0
- pandaserver/userinterface/UserIF.py +1799 -0
- pandaserver/userinterface/__init__.py +0 -0
- pandaserver/workflow/__init__.py +0 -0
- pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
- pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
- pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
- pandaserver/workflow/pcwl_test.py +122 -0
- pandaserver/workflow/pcwl_utils.py +384 -0
- pandaserver/workflow/psnakemake_container.json +68 -0
- pandaserver/workflow/psnakemake_task.json +62 -0
- pandaserver/workflow/psnakemake_test.py +60 -0
- pandaserver/workflow/snakeparser/__init__.py +3 -0
- pandaserver/workflow/snakeparser/extensions.py +74 -0
- pandaserver/workflow/snakeparser/log.py +53 -0
- pandaserver/workflow/snakeparser/names.py +3 -0
- pandaserver/workflow/snakeparser/parser.py +332 -0
- pandaserver/workflow/snakeparser/utils.py +29 -0
- pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
- pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
- pandaserver/workflow/workflow_base.py +531 -0
- pandaserver/workflow/workflow_core.py +1974 -0
- pandaserver/workflow/workflow_core_smoketest.py +388 -0
- pandaserver/workflow/workflow_parser.py +222 -0
- 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")
|