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,648 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import socket
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
8
|
+
|
|
9
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
10
|
+
from pandaserver.dataservice.activator import Activator
|
|
11
|
+
|
|
12
|
+
from .TypicalWatchDogBase import TypicalWatchDogBase
|
|
13
|
+
|
|
14
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# watchdog for ATLAS analysis
|
|
18
|
+
class AtlasAnalWatchDog(TypicalWatchDogBase):
|
|
19
|
+
# constructor
|
|
20
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
21
|
+
TypicalWatchDogBase.__init__(self, taskBufferIF, ddmIF)
|
|
22
|
+
self.pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}-dog"
|
|
23
|
+
|
|
24
|
+
# main
|
|
25
|
+
def doAction(self):
|
|
26
|
+
# get logger
|
|
27
|
+
orig_tmp_log = MsgWrapper(logger)
|
|
28
|
+
try:
|
|
29
|
+
orig_tmp_log.debug("start")
|
|
30
|
+
# handle waiting jobs
|
|
31
|
+
self.doForWaitingJobs()
|
|
32
|
+
# throttle tasks if so many prestaging requests
|
|
33
|
+
self.doForPreStaging()
|
|
34
|
+
# priority massage
|
|
35
|
+
self.doForPriorityMassage()
|
|
36
|
+
# redo stalled analysis jobs
|
|
37
|
+
self.doForRedoStalledJobs()
|
|
38
|
+
# task share and priority boost
|
|
39
|
+
self.doForTaskBoost()
|
|
40
|
+
# action to set scout job data w/o scouts
|
|
41
|
+
self.doActionToSetScoutJobData(orig_tmp_log)
|
|
42
|
+
# periodic action
|
|
43
|
+
self.do_periodic_action()
|
|
44
|
+
except Exception as e:
|
|
45
|
+
orig_tmp_log.error(f"failed with {str(e)} {traceback.format_exc()}")
|
|
46
|
+
# return
|
|
47
|
+
orig_tmp_log.debug("done")
|
|
48
|
+
return self.SC_SUCCEEDED
|
|
49
|
+
|
|
50
|
+
# handle waiting jobs
|
|
51
|
+
def doForWaitingJobs(self):
|
|
52
|
+
try:
|
|
53
|
+
tmpLog = MsgWrapper(logger, "doForWaitingJobs label=user")
|
|
54
|
+
# lock
|
|
55
|
+
got_lock = self.taskBufferIF.lockProcess_JEDI(
|
|
56
|
+
vo=self.vo,
|
|
57
|
+
prodSourceLabel=self.prodSourceLabel,
|
|
58
|
+
cloud=None,
|
|
59
|
+
workqueue_id=None,
|
|
60
|
+
resource_name=None,
|
|
61
|
+
component="AtlasAnalWatchDog.doForWaitingJobs",
|
|
62
|
+
pid=self.pid,
|
|
63
|
+
timeLimit=0.5,
|
|
64
|
+
)
|
|
65
|
+
if not got_lock:
|
|
66
|
+
tmpLog.debug("locked by another process. Skipped")
|
|
67
|
+
return
|
|
68
|
+
# check every 60 min
|
|
69
|
+
checkInterval = 60
|
|
70
|
+
# get lib.tgz for waiting jobs
|
|
71
|
+
libList = self.taskBufferIF.getLibForWaitingRunJob_JEDI(self.vo, self.prodSourceLabel, checkInterval)
|
|
72
|
+
tmpLog.debug(f"got {len(libList)} lib.tgz files")
|
|
73
|
+
# activate or kill orphan jobs which were submitted to use lib.tgz when the lib.tgz was being produced
|
|
74
|
+
for prodUserName, datasetName, tmpFileSpec in libList:
|
|
75
|
+
tmpLog = MsgWrapper(logger, f"< #ATM #KV doForWaitingJobs jediTaskID={tmpFileSpec.jediTaskID} label=user >")
|
|
76
|
+
tmpLog.debug("start")
|
|
77
|
+
# check status of lib.tgz
|
|
78
|
+
if tmpFileSpec.status == "failed":
|
|
79
|
+
# get buildJob
|
|
80
|
+
pandaJobSpecs = self.taskBufferIF.peekJobs([tmpFileSpec.PandaID], fromDefined=False, fromActive=False, fromWaiting=False)
|
|
81
|
+
pandaJobSpec = pandaJobSpecs[0]
|
|
82
|
+
if pandaJobSpec is not None:
|
|
83
|
+
# kill
|
|
84
|
+
self.taskBufferIF.updateJobs([pandaJobSpec], False)
|
|
85
|
+
tmpLog.debug(f' action=killed_downstream_jobs for user="{prodUserName}" with libDS={datasetName}')
|
|
86
|
+
else:
|
|
87
|
+
# PandaJobSpec not found
|
|
88
|
+
tmpLog.error(f' cannot find PandaJobSpec for user="{prodUserName}" with PandaID={tmpFileSpec.PandaID}')
|
|
89
|
+
elif tmpFileSpec.status == "finished":
|
|
90
|
+
# set metadata
|
|
91
|
+
self.taskBufferIF.setGUIDs(
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"guid": tmpFileSpec.GUID,
|
|
95
|
+
"lfn": tmpFileSpec.lfn,
|
|
96
|
+
"checksum": tmpFileSpec.checksum,
|
|
97
|
+
"fsize": tmpFileSpec.fsize,
|
|
98
|
+
"scope": tmpFileSpec.scope,
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
# get lib dataset
|
|
103
|
+
dataset = self.taskBufferIF.queryDatasetWithMap({"name": datasetName})
|
|
104
|
+
if dataset is not None:
|
|
105
|
+
# activate jobs
|
|
106
|
+
aThr = Activator(self.taskBufferIF, dataset)
|
|
107
|
+
aThr.run()
|
|
108
|
+
tmpLog.debug(f' action=activated_downstream_jobs for user="{prodUserName}" with libDS={datasetName}')
|
|
109
|
+
else:
|
|
110
|
+
# datasetSpec not found
|
|
111
|
+
tmpLog.error(f' cannot find datasetSpec for user="{prodUserName}" with libDS={datasetName}')
|
|
112
|
+
else:
|
|
113
|
+
# lib.tgz is not ready
|
|
114
|
+
tmpLog.debug(f' keep waiting for user="{prodUserName}" libDS={datasetName}')
|
|
115
|
+
except Exception:
|
|
116
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
117
|
+
tmpLog.error(f"failed with {errtype} {errvalue} {traceback.format_exc()}")
|
|
118
|
+
|
|
119
|
+
# throttle tasks if so many prestaging requests
|
|
120
|
+
def doForPreStaging(self):
|
|
121
|
+
try:
|
|
122
|
+
tmpLog = MsgWrapper(logger, " #ATM #KV doForPreStaging label=user")
|
|
123
|
+
tmpLog.debug("start")
|
|
124
|
+
# lock
|
|
125
|
+
got_lock = self.taskBufferIF.lockProcess_JEDI(
|
|
126
|
+
vo=self.vo,
|
|
127
|
+
prodSourceLabel=self.prodSourceLabel,
|
|
128
|
+
cloud=None,
|
|
129
|
+
workqueue_id=None,
|
|
130
|
+
resource_name=None,
|
|
131
|
+
component="AtlasAnalWatchDog.doForPreStaging",
|
|
132
|
+
pid=self.pid,
|
|
133
|
+
timeLimit=0.5,
|
|
134
|
+
)
|
|
135
|
+
if not got_lock:
|
|
136
|
+
tmpLog.debug("locked by another process. Skipped")
|
|
137
|
+
return
|
|
138
|
+
# get throttled users
|
|
139
|
+
thrUserTasks = self.taskBufferIF.getThrottledUsersTasks_JEDI(self.vo, self.prodSourceLabel)
|
|
140
|
+
# get dispatch datasets
|
|
141
|
+
dispUserTasks = self.taskBufferIF.getDispatchDatasetsPerUser(self.vo, self.prodSourceLabel, True, True)
|
|
142
|
+
# max size of prestaging requests in GB
|
|
143
|
+
maxPrestaging = self.taskBufferIF.getConfigValue("anal_watchdog", "USER_PRESTAGE_LIMIT", "jedi", "atlas")
|
|
144
|
+
if maxPrestaging is None:
|
|
145
|
+
maxPrestaging = 1024
|
|
146
|
+
# max size of transfer requests in GB
|
|
147
|
+
maxTransfer = self.taskBufferIF.getConfigValue("anal_watchdog", "USER_TRANSFER_LIMIT", "jedi", "atlas")
|
|
148
|
+
if maxTransfer is None:
|
|
149
|
+
maxTransfer = 1024
|
|
150
|
+
# throttle interval
|
|
151
|
+
thrInterval = 120
|
|
152
|
+
# loop over all users
|
|
153
|
+
for userName, userDict in dispUserTasks.items():
|
|
154
|
+
# loop over all transfer types
|
|
155
|
+
for transferType, maxSize in [("prestaging", maxPrestaging), ("transfer", maxTransfer)]:
|
|
156
|
+
if transferType not in userDict:
|
|
157
|
+
continue
|
|
158
|
+
userTotal = int(userDict[transferType]["size"] / 1024)
|
|
159
|
+
tmpLog.debug(f"user={userName} {transferType} total={userTotal} GB")
|
|
160
|
+
# too large
|
|
161
|
+
if userTotal > maxSize:
|
|
162
|
+
tmpLog.debug(f"user={userName} has too large {transferType} total={userTotal} GB > limit={maxSize} GB")
|
|
163
|
+
# throttle tasks
|
|
164
|
+
for taskID in userDict[transferType]["tasks"]:
|
|
165
|
+
if userName not in thrUserTasks or transferType not in thrUserTasks[userName] or taskID not in thrUserTasks[userName][transferType]:
|
|
166
|
+
tmpLog.debug(f"action=throttle_{transferType} jediTaskID={taskID} for user={userName}")
|
|
167
|
+
errDiag = f"throttled since transferring large data volume in total={userTotal}GB > limit={maxSize}GB type={transferType}"
|
|
168
|
+
self.taskBufferIF.throttleTask_JEDI(taskID, thrInterval, errDiag)
|
|
169
|
+
# remove the user from the list
|
|
170
|
+
if userName in thrUserTasks and transferType in thrUserTasks[userName]:
|
|
171
|
+
del thrUserTasks[userName][transferType]
|
|
172
|
+
# release users
|
|
173
|
+
for userName, taskData in thrUserTasks.items():
|
|
174
|
+
for transferType, taskIDs in taskData.items():
|
|
175
|
+
tmpLog.debug(f"user={userName} release throttled tasks with {transferType}")
|
|
176
|
+
# unthrottle tasks
|
|
177
|
+
for taskID in taskIDs:
|
|
178
|
+
tmpLog.debug(f"action=release_{transferType} jediTaskID={taskID} for user={userName}")
|
|
179
|
+
self.taskBufferIF.release_task_on_hold(taskID, "throttled")
|
|
180
|
+
tmpLog.debug("done")
|
|
181
|
+
except Exception:
|
|
182
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
183
|
+
tmpLog.error(f"failed with {errtype} {errvalue} {traceback.format_exc()}")
|
|
184
|
+
|
|
185
|
+
# priority massage
|
|
186
|
+
def doForPriorityMassage(self):
|
|
187
|
+
tmpLog = MsgWrapper(logger, " #ATM #KV doForPriorityMassage label=user")
|
|
188
|
+
tmpLog.debug("start")
|
|
189
|
+
# lock
|
|
190
|
+
got_lock = self.taskBufferIF.lockProcess_JEDI(
|
|
191
|
+
vo=self.vo,
|
|
192
|
+
prodSourceLabel=self.prodSourceLabel,
|
|
193
|
+
cloud=None,
|
|
194
|
+
workqueue_id=None,
|
|
195
|
+
resource_name=None,
|
|
196
|
+
component="AtlasAnalWatchDog.doForPriorityMassage",
|
|
197
|
+
pid=self.pid,
|
|
198
|
+
timeLimit=6,
|
|
199
|
+
)
|
|
200
|
+
if not got_lock:
|
|
201
|
+
tmpLog.debug("locked by another process. Skipped")
|
|
202
|
+
return
|
|
203
|
+
try:
|
|
204
|
+
# get usage breakdown
|
|
205
|
+
usageBreakDownPerUser, usageBreakDownPerSite = self.taskBufferIF.getUsageBreakdown_JEDI(self.prodSourceLabel)
|
|
206
|
+
# get total number of users and running/done jobs
|
|
207
|
+
totalUsers = 0
|
|
208
|
+
totalRunDone = 0
|
|
209
|
+
usersTotalJobs = {}
|
|
210
|
+
usersTotalCores = {}
|
|
211
|
+
for prodUserName in usageBreakDownPerUser:
|
|
212
|
+
wgValMap = usageBreakDownPerUser[prodUserName]
|
|
213
|
+
for workingGroup in wgValMap:
|
|
214
|
+
siteValMap = wgValMap[workingGroup]
|
|
215
|
+
totalUsers += 1
|
|
216
|
+
for computingSite in siteValMap:
|
|
217
|
+
statValMap = siteValMap[computingSite]
|
|
218
|
+
totalRunDone += statValMap["rundone"]
|
|
219
|
+
usersTotalJobs.setdefault(prodUserName, {})
|
|
220
|
+
usersTotalJobs[prodUserName].setdefault(workingGroup, 0)
|
|
221
|
+
usersTotalJobs[prodUserName][workingGroup] += statValMap["running"]
|
|
222
|
+
usersTotalCores.setdefault(prodUserName, {})
|
|
223
|
+
usersTotalCores[prodUserName].setdefault(workingGroup, 0)
|
|
224
|
+
usersTotalCores[prodUserName][workingGroup] += statValMap["runcores"]
|
|
225
|
+
tmpLog.debug(f"total {totalUsers} users, {totalRunDone} RunDone jobs")
|
|
226
|
+
# skip if no user
|
|
227
|
+
if totalUsers == 0:
|
|
228
|
+
tmpLog.debug("no user. Skipped...")
|
|
229
|
+
return
|
|
230
|
+
# cap num of running jobs
|
|
231
|
+
tmpLog.debug("cap running jobs")
|
|
232
|
+
prodUserName = None
|
|
233
|
+
maxNumRunPerUser = self.taskBufferIF.getConfigValue("prio_mgr", "CAP_RUNNING_USER_JOBS")
|
|
234
|
+
maxNumRunPerGroup = self.taskBufferIF.getConfigValue("prio_mgr", "CAP_RUNNING_GROUP_JOBS")
|
|
235
|
+
maxNumCorePerUser = self.taskBufferIF.getConfigValue("prio_mgr", "CAP_RUNNING_USER_CORES")
|
|
236
|
+
maxNumCorePerGroup = self.taskBufferIF.getConfigValue("prio_mgr", "CAP_RUNNING_GROUP_CORES")
|
|
237
|
+
if maxNumRunPerUser is None:
|
|
238
|
+
maxNumRunPerUser = 10000
|
|
239
|
+
if maxNumRunPerGroup is None:
|
|
240
|
+
maxNumRunPerGroup = 10000
|
|
241
|
+
if maxNumCorePerUser is None:
|
|
242
|
+
maxNumCorePerUser = 10000
|
|
243
|
+
if maxNumCorePerGroup is None:
|
|
244
|
+
maxNumCorePerGroup = 10000
|
|
245
|
+
try:
|
|
246
|
+
throttledUsers = self.taskBufferIF.getThrottledUsers()
|
|
247
|
+
for prodUserName in usersTotalJobs:
|
|
248
|
+
for workingGroup in usersTotalJobs[prodUserName]:
|
|
249
|
+
tmpNumTotalJobs = usersTotalJobs[prodUserName][workingGroup]
|
|
250
|
+
tmpNumTotalCores = usersTotalCores[prodUserName][workingGroup]
|
|
251
|
+
if workingGroup is None:
|
|
252
|
+
maxNumRun = maxNumRunPerUser
|
|
253
|
+
maxNumCore = maxNumCorePerUser
|
|
254
|
+
else:
|
|
255
|
+
maxNumRun = maxNumRunPerGroup
|
|
256
|
+
maxNumCore = maxNumCorePerGroup
|
|
257
|
+
if tmpNumTotalJobs >= maxNumRun or tmpNumTotalCores >= maxNumCore:
|
|
258
|
+
# throttle user
|
|
259
|
+
tmpNumJobs = self.taskBufferIF.throttleUserJobs(prodUserName, workingGroup, get_dict=True)
|
|
260
|
+
if tmpNumJobs is not None:
|
|
261
|
+
for tmpJediTaskID, tmpNumJob in tmpNumJobs.items():
|
|
262
|
+
msg = (
|
|
263
|
+
'throttled {} jobs in jediTaskID={} for user="{}" group={} ' "since too many running jobs ({} > {}) or cores ({} > {}) "
|
|
264
|
+
).format(tmpNumJob, tmpJediTaskID, prodUserName, workingGroup, tmpNumTotalJobs, maxNumRun, tmpNumTotalCores, maxNumCore)
|
|
265
|
+
tmpLog.debug(msg)
|
|
266
|
+
tmpLog.sendMsg(msg, "userCap", msgLevel="warning")
|
|
267
|
+
elif tmpNumTotalJobs < maxNumRun * 0.9 and tmpNumTotalCores < maxNumCore * 0.9 and (prodUserName, workingGroup) in throttledUsers:
|
|
268
|
+
# unthrottle user
|
|
269
|
+
tmpNumJobs = self.taskBufferIF.unThrottleUserJobs(prodUserName, workingGroup, get_dict=True)
|
|
270
|
+
if tmpNumJobs is not None:
|
|
271
|
+
for tmpJediTaskID, tmpNumJob in tmpNumJobs.items():
|
|
272
|
+
msg = (
|
|
273
|
+
'released {} jobs in jediTaskID={} for user="{}" group={} '
|
|
274
|
+
"since number of running jobs and cores are less than {} and {}"
|
|
275
|
+
).format(tmpNumJob, tmpJediTaskID, prodUserName, workingGroup, maxNumRun, maxNumCore)
|
|
276
|
+
tmpLog.debug(msg)
|
|
277
|
+
tmpLog.sendMsg(msg, "userCap")
|
|
278
|
+
except Exception as e:
|
|
279
|
+
errStr = f"cap failed for {prodUserName} : {str(e)}"
|
|
280
|
+
errStr.strip()
|
|
281
|
+
errStr += traceback.format_exc()
|
|
282
|
+
tmpLog.error(errStr)
|
|
283
|
+
# to boost
|
|
284
|
+
tmpLog.debug("boost jobs")
|
|
285
|
+
# global average
|
|
286
|
+
globalAverageRunDone = float(totalRunDone) / float(totalUsers)
|
|
287
|
+
tmpLog.debug(f"global average: {globalAverageRunDone}")
|
|
288
|
+
# count the number of users and run/done jobs for each site
|
|
289
|
+
siteRunDone = {}
|
|
290
|
+
siteUsers = {}
|
|
291
|
+
for computingSite in usageBreakDownPerSite:
|
|
292
|
+
userValMap = usageBreakDownPerSite[computingSite]
|
|
293
|
+
for prodUserName in userValMap:
|
|
294
|
+
wgValMap = userValMap[prodUserName]
|
|
295
|
+
for workingGroup in wgValMap:
|
|
296
|
+
statValMap = wgValMap[workingGroup]
|
|
297
|
+
# count the number of users and running/done jobs
|
|
298
|
+
siteUsers.setdefault(computingSite, 0)
|
|
299
|
+
siteUsers[computingSite] += 1
|
|
300
|
+
siteRunDone.setdefault(computingSite, 0)
|
|
301
|
+
siteRunDone[computingSite] += statValMap["rundone"]
|
|
302
|
+
# get site average
|
|
303
|
+
tmpLog.debug("site average")
|
|
304
|
+
siteAverageRunDone = {}
|
|
305
|
+
for computingSite in siteRunDone:
|
|
306
|
+
nRunDone = siteRunDone[computingSite]
|
|
307
|
+
siteAverageRunDone[computingSite] = float(nRunDone) / float(siteUsers[computingSite])
|
|
308
|
+
tmpLog.debug(" %-25s : %s" % (computingSite, siteAverageRunDone[computingSite]))
|
|
309
|
+
# check if the number of user's jobs is lower than the average
|
|
310
|
+
for prodUserName in usageBreakDownPerUser:
|
|
311
|
+
wgValMap = usageBreakDownPerUser[prodUserName]
|
|
312
|
+
for workingGroup in wgValMap:
|
|
313
|
+
tmpLog.debug(f"---> {prodUserName} group={workingGroup}")
|
|
314
|
+
# count the number of running/done jobs
|
|
315
|
+
userTotalRunDone = 0
|
|
316
|
+
for computingSite in wgValMap[workingGroup]:
|
|
317
|
+
statValMap = wgValMap[workingGroup][computingSite]
|
|
318
|
+
userTotalRunDone += statValMap["rundone"]
|
|
319
|
+
# no priority boost when the number of jobs is higher than the average
|
|
320
|
+
if userTotalRunDone >= globalAverageRunDone:
|
|
321
|
+
tmpLog.debug(f"enough running {userTotalRunDone} > {globalAverageRunDone} (global average)")
|
|
322
|
+
continue
|
|
323
|
+
tmpLog.debug(f"user total:{userTotalRunDone} global average:{globalAverageRunDone}")
|
|
324
|
+
# check with site average
|
|
325
|
+
toBeBoostedSites = []
|
|
326
|
+
for computingSite in wgValMap[workingGroup]:
|
|
327
|
+
statValMap = wgValMap[workingGroup][computingSite]
|
|
328
|
+
# the number of running/done jobs is lower than the average and activated jobs are waiting
|
|
329
|
+
if statValMap["rundone"] >= siteAverageRunDone[computingSite]:
|
|
330
|
+
tmpLog.debug(f"enough running {statValMap['rundone']} > {siteAverageRunDone[computingSite]} (site average) at {computingSite}")
|
|
331
|
+
elif statValMap["activated"] == 0:
|
|
332
|
+
tmpLog.debug(f"no activated jobs at {computingSite}")
|
|
333
|
+
else:
|
|
334
|
+
toBeBoostedSites.append(computingSite)
|
|
335
|
+
# no boost is required
|
|
336
|
+
if toBeBoostedSites == []:
|
|
337
|
+
tmpLog.debug("no sites to be boosted")
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
# set weight
|
|
341
|
+
totalW = 0
|
|
342
|
+
defaultW = 100
|
|
343
|
+
for _ in toBeBoostedSites:
|
|
344
|
+
totalW += defaultW
|
|
345
|
+
|
|
346
|
+
totalW = float(totalW)
|
|
347
|
+
# the total number of jobs to be boosted
|
|
348
|
+
numBoostedJobs = globalAverageRunDone - float(userTotalRunDone)
|
|
349
|
+
# get quota
|
|
350
|
+
quotaFactor = 1.0 + self.taskBufferIF.checkQuota(prodUserName)
|
|
351
|
+
tmpLog.debug(f"quota factor:{quotaFactor}")
|
|
352
|
+
# make priority boost
|
|
353
|
+
nJobsPerPrioUnit = 5
|
|
354
|
+
highestPrio = 1000
|
|
355
|
+
for computingSite in toBeBoostedSites:
|
|
356
|
+
weight = float(defaultW)
|
|
357
|
+
weight /= totalW
|
|
358
|
+
# the number of boosted jobs at the site
|
|
359
|
+
numBoostedJobsSite = int(numBoostedJobs * weight / quotaFactor)
|
|
360
|
+
tmpLog.debug(f"nSite:{numBoostedJobsSite} nAll:{numBoostedJobs} W:{weight} Q:{quotaFactor} at {computingSite}")
|
|
361
|
+
if numBoostedJobsSite / nJobsPerPrioUnit == 0:
|
|
362
|
+
tmpLog.debug(f"too small number of jobs {numBoostedJobsSite} to be boosted at {computingSite}")
|
|
363
|
+
continue
|
|
364
|
+
# get the highest prio of activated jobs at the site
|
|
365
|
+
varMap = {}
|
|
366
|
+
varMap[":jobStatus"] = "activated"
|
|
367
|
+
varMap[":prodSourceLabel"] = self.prodSourceLabel
|
|
368
|
+
varMap[":pmerge"] = "pmerge"
|
|
369
|
+
varMap[":prodUserName"] = prodUserName
|
|
370
|
+
varMap[":computingSite"] = computingSite
|
|
371
|
+
sql = "SELECT MAX(currentPriority) FROM ATLAS_PANDA.jobsActive4 "
|
|
372
|
+
sql += "WHERE prodSourceLabel=:prodSourceLabel AND jobStatus=:jobStatus AND computingSite=:computingSite "
|
|
373
|
+
sql += "AND processingType<>:pmerge AND prodUserName=:prodUserName "
|
|
374
|
+
if workingGroup is not None:
|
|
375
|
+
varMap[":workingGroup"] = workingGroup
|
|
376
|
+
sql += "AND workingGroup=:workingGroup "
|
|
377
|
+
else:
|
|
378
|
+
sql += "AND workingGroup IS NULL "
|
|
379
|
+
res = self.taskBufferIF.querySQL(sql, varMap, arraySize=10)
|
|
380
|
+
maxPrio = None
|
|
381
|
+
if res is not None:
|
|
382
|
+
try:
|
|
383
|
+
maxPrio = res[0][0]
|
|
384
|
+
except Exception:
|
|
385
|
+
pass
|
|
386
|
+
if maxPrio is None:
|
|
387
|
+
tmpLog.debug(f"cannot get the highest prio at {computingSite}")
|
|
388
|
+
continue
|
|
389
|
+
# delta for priority boost
|
|
390
|
+
prioDelta = highestPrio - maxPrio
|
|
391
|
+
# already boosted
|
|
392
|
+
if prioDelta <= 0:
|
|
393
|
+
tmpLog.debug(f"already boosted (prio={maxPrio}) at {computingSite}")
|
|
394
|
+
continue
|
|
395
|
+
# lower limit
|
|
396
|
+
minPrio = maxPrio - numBoostedJobsSite / nJobsPerPrioUnit
|
|
397
|
+
# SQL for priority boost
|
|
398
|
+
varMap = {}
|
|
399
|
+
varMap[":jobStatus"] = "activated"
|
|
400
|
+
varMap[":prodSourceLabel"] = self.prodSourceLabel
|
|
401
|
+
varMap[":prodUserName"] = prodUserName
|
|
402
|
+
varMap[":computingSite"] = computingSite
|
|
403
|
+
varMap[":prioDelta"] = prioDelta
|
|
404
|
+
varMap[":maxPrio"] = maxPrio
|
|
405
|
+
varMap[":minPrio"] = minPrio
|
|
406
|
+
varMap[":rlimit"] = numBoostedJobsSite
|
|
407
|
+
sql = "UPDATE ATLAS_PANDA.jobsActive4 SET currentPriority=currentPriority+:prioDelta "
|
|
408
|
+
sql += "WHERE prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName "
|
|
409
|
+
if workingGroup is not None:
|
|
410
|
+
varMap[":workingGroup"] = workingGroup
|
|
411
|
+
sql += "AND workingGroup=:workingGroup "
|
|
412
|
+
else:
|
|
413
|
+
sql += "AND workingGroup IS NULL "
|
|
414
|
+
sql += "AND jobStatus=:jobStatus AND computingSite=:computingSite AND currentPriority>:minPrio "
|
|
415
|
+
sql += "AND currentPriority<=:maxPrio AND rownum<=:rlimit"
|
|
416
|
+
tmpLog.debug(f"boost {str(varMap)}")
|
|
417
|
+
res = self.taskBufferIF.querySQL(sql, varMap, arraySize=10)
|
|
418
|
+
tmpLog.debug(f" database return : {res}")
|
|
419
|
+
# done
|
|
420
|
+
tmpLog.debug("done")
|
|
421
|
+
except Exception:
|
|
422
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
423
|
+
tmpLog.error(f"failed with {errtype} {errvalue} {traceback.format_exc()}")
|
|
424
|
+
|
|
425
|
+
# redo stalled analysis jobs
|
|
426
|
+
def doForRedoStalledJobs(self):
|
|
427
|
+
tmpLog = MsgWrapper(logger, " #ATM #KV doForRedoStalledJobs label=user")
|
|
428
|
+
tmpLog.debug("start")
|
|
429
|
+
# lock
|
|
430
|
+
got_lock = self.taskBufferIF.lockProcess_JEDI(
|
|
431
|
+
vo=self.vo,
|
|
432
|
+
prodSourceLabel=self.prodSourceLabel,
|
|
433
|
+
cloud=None,
|
|
434
|
+
workqueue_id=None,
|
|
435
|
+
resource_name=None,
|
|
436
|
+
component="AtlasAnalWatchDog.doForRedoStalledJobs",
|
|
437
|
+
pid=self.pid,
|
|
438
|
+
timeLimit=6,
|
|
439
|
+
)
|
|
440
|
+
if not got_lock:
|
|
441
|
+
tmpLog.debug("locked by another process. Skipped")
|
|
442
|
+
return
|
|
443
|
+
# redo stalled analysis jobs
|
|
444
|
+
tmpLog.debug("redo stalled jobs")
|
|
445
|
+
try:
|
|
446
|
+
varMap = {":prodSourceLabel": self.prodSourceLabel}
|
|
447
|
+
|
|
448
|
+
sqlJ = "SELECT jobDefinitionID,prodUserName FROM ATLAS_PANDA.jobsDefined4 "
|
|
449
|
+
sqlJ += "WHERE prodSourceLabel=:prodSourceLabel AND modificationTime<CURRENT_DATE-2/24 "
|
|
450
|
+
sqlJ += "GROUP BY jobDefinitionID,prodUserName"
|
|
451
|
+
|
|
452
|
+
sqlP = "SELECT PandaID FROM ATLAS_PANDA.jobsDefined4 "
|
|
453
|
+
sqlP += "WHERE jobDefinitionID=:jobDefinitionID ANd prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName AND rownum <= 1"
|
|
454
|
+
|
|
455
|
+
sqlF = "SELECT lfn,type,destinationDBlock FROM ATLAS_PANDA.filesTable4 WHERE PandaID=:PandaID AND status=:status"
|
|
456
|
+
|
|
457
|
+
sqlL = "SELECT guid,status,PandaID,dataset FROM ATLAS_PANDA.filesTable4 WHERE lfn=:lfn AND type=:type"
|
|
458
|
+
|
|
459
|
+
sqlA = "SELECT PandaID FROM ATLAS_PANDA.jobsDefined4 "
|
|
460
|
+
sqlA += "WHERE jobDefinitionID=:jobDefinitionID ANd prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName"
|
|
461
|
+
|
|
462
|
+
sqlU = "UPDATE ATLAS_PANDA.jobsDefined4 SET modificationTime=CURRENT_DATE "
|
|
463
|
+
sqlU += "WHERE jobDefinitionID=:jobDefinitionID ANd prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName"
|
|
464
|
+
|
|
465
|
+
# get stalled jobs
|
|
466
|
+
resJ = self.taskBufferIF.querySQL(sqlJ, varMap)
|
|
467
|
+
if resJ is None or len(resJ) == 0:
|
|
468
|
+
pass
|
|
469
|
+
else:
|
|
470
|
+
# loop over all jobID/users
|
|
471
|
+
for jobDefinitionID, prodUserName in resJ:
|
|
472
|
+
tmpLog.debug(f" user:{prodUserName} jobID:{jobDefinitionID}")
|
|
473
|
+
# get stalled jobs
|
|
474
|
+
varMap = {":prodSourceLabel": self.prodSourceLabel, ":jobDefinitionID": jobDefinitionID, ":prodUserName": prodUserName}
|
|
475
|
+
resP = self.taskBufferIF.querySQL(sqlP, varMap)
|
|
476
|
+
if resP is None or len(resP) == 0:
|
|
477
|
+
tmpLog.debug(" no PandaID")
|
|
478
|
+
continue
|
|
479
|
+
useLib = False
|
|
480
|
+
libStatus = None
|
|
481
|
+
libGUID = None
|
|
482
|
+
libLFN = None
|
|
483
|
+
libDSName = None
|
|
484
|
+
destReady = False
|
|
485
|
+
# use the first PandaID
|
|
486
|
+
for (PandaID,) in resP:
|
|
487
|
+
tmpLog.debug(f" check PandaID:{PandaID}")
|
|
488
|
+
# get files
|
|
489
|
+
varMap = {}
|
|
490
|
+
varMap[":PandaID"] = PandaID
|
|
491
|
+
varMap[":status"] = "unknown"
|
|
492
|
+
resF = self.taskBufferIF.querySQL(sqlF, varMap)
|
|
493
|
+
if resF is None or len(resF) == 0:
|
|
494
|
+
tmpLog.debug(" no files")
|
|
495
|
+
else:
|
|
496
|
+
# get lib.tgz and destDBlock
|
|
497
|
+
for lfn, filetype, destinationDBlock in resF:
|
|
498
|
+
if filetype == "input" and lfn.endswith(".lib.tgz"):
|
|
499
|
+
useLib = True
|
|
500
|
+
libLFN = lfn
|
|
501
|
+
varMap = {}
|
|
502
|
+
varMap[":lfn"] = lfn
|
|
503
|
+
varMap[":type"] = "output"
|
|
504
|
+
resL = self.taskBufferIF.querySQL(sqlL, varMap)
|
|
505
|
+
# not found
|
|
506
|
+
if resL is None or len(resL) == 0:
|
|
507
|
+
tmpLog.error(f" cannot find status of {lfn}")
|
|
508
|
+
continue
|
|
509
|
+
# check status
|
|
510
|
+
guid, outFileStatus, pandaIDOutLibTgz, tmpLibDsName = resL[0]
|
|
511
|
+
tmpLog.debug(f" PandaID:{pandaIDOutLibTgz} produces {tmpLibDsName}:{lfn} GUID={guid} status={outFileStatus}")
|
|
512
|
+
libStatus = outFileStatus
|
|
513
|
+
libGUID = guid
|
|
514
|
+
libDSName = tmpLibDsName
|
|
515
|
+
elif filetype in ["log", "output"]:
|
|
516
|
+
if destinationDBlock is not None and re.search("_sub\d+$", destinationDBlock) is not None:
|
|
517
|
+
destReady = True
|
|
518
|
+
break
|
|
519
|
+
tmpLog.debug(f" useLib:{useLib} libStatus:{libStatus} libDsName:{libDSName} libLFN:{libLFN} libGUID:{libGUID} destReady:{destReady}")
|
|
520
|
+
if libStatus == "failed":
|
|
521
|
+
# delete downstream jobs
|
|
522
|
+
tmpLog.debug(" -> delete downstream jobs")
|
|
523
|
+
# FIXME
|
|
524
|
+
# self.taskBufferIF.deleteStalledJobs(libLFN)
|
|
525
|
+
else:
|
|
526
|
+
# activate
|
|
527
|
+
if useLib and libStatus == "ready" and (libGUID not in [None, ""]) and (libDSName not in [None, ""]):
|
|
528
|
+
# update GUID
|
|
529
|
+
tmpLog.debug(f" set GUID:{libGUID} for {libLFN}")
|
|
530
|
+
# retG = self.taskBufferIF.setGUIDs([{'lfn':libLFN,'guid':libGUID}])
|
|
531
|
+
# FIXME
|
|
532
|
+
retG = True
|
|
533
|
+
if not retG:
|
|
534
|
+
tmpLog.error(f" failed to update GUID for {libLFN}")
|
|
535
|
+
else:
|
|
536
|
+
# get PandaID with lib.tgz
|
|
537
|
+
# ids = self.taskBufferIF.updateInFilesReturnPandaIDs(libDSName,'ready')
|
|
538
|
+
ids = []
|
|
539
|
+
# get jobs
|
|
540
|
+
jobs = self.taskBufferIF.peekJobs(ids, fromActive=False, fromArchived=False, fromWaiting=False)
|
|
541
|
+
# remove None and unknown
|
|
542
|
+
acJobs = []
|
|
543
|
+
for job in jobs:
|
|
544
|
+
if job is None or job.jobStatus == "unknown":
|
|
545
|
+
continue
|
|
546
|
+
acJobs.append(job)
|
|
547
|
+
# activate
|
|
548
|
+
tmpLog.debug(" -> activate downstream jobs")
|
|
549
|
+
# self.taskBufferIF.activateJobs(acJobs)
|
|
550
|
+
else:
|
|
551
|
+
# wait
|
|
552
|
+
tmpLog.debug(" -> wait")
|
|
553
|
+
varMap = {":prodSourceLabel": self.prodSourceLabel, ":jobDefinitionID": jobDefinitionID, ":prodUserName": prodUserName}
|
|
554
|
+
# FIXME
|
|
555
|
+
# resU = self.taskBufferIF.querySQL(sqlU, varMap)
|
|
556
|
+
# done
|
|
557
|
+
tmpLog.debug("done")
|
|
558
|
+
except Exception:
|
|
559
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
560
|
+
tmpLog.error(f"failed to redo stalled jobs with {errtype} {errvalue} {traceback.format_exc()}")
|
|
561
|
+
|
|
562
|
+
# task share and priority boost
|
|
563
|
+
def doForTaskBoost(self):
|
|
564
|
+
tmpLog = MsgWrapper(logger, " #ATM #KV doForTaskBoost label=user")
|
|
565
|
+
tmpLog.debug("start")
|
|
566
|
+
# lock
|
|
567
|
+
got_lock = self.taskBufferIF.lockProcess_JEDI(
|
|
568
|
+
vo=self.vo,
|
|
569
|
+
prodSourceLabel=self.prodSourceLabel,
|
|
570
|
+
cloud=None,
|
|
571
|
+
workqueue_id=None,
|
|
572
|
+
resource_name=None,
|
|
573
|
+
component="AtlasAnalWatchDog.doForTaskBoost",
|
|
574
|
+
pid=self.pid,
|
|
575
|
+
timeLimit=5,
|
|
576
|
+
)
|
|
577
|
+
if not got_lock:
|
|
578
|
+
tmpLog.debug("locked by another process. Skipped")
|
|
579
|
+
return
|
|
580
|
+
try:
|
|
581
|
+
# get active tasks in S-class
|
|
582
|
+
sql_get_tasks = (
|
|
583
|
+
"""SELECT /* use_json_type */ tev.value_json.task_id, tev.value_json."user" """
|
|
584
|
+
"""FROM ATLAS_PANDA.Task_Evaluation tev, ATLAS_PANDA.JEDI_Tasks t """
|
|
585
|
+
"""WHERE tev.value_json.task_id=t.jediTaskID """
|
|
586
|
+
"""AND tev.metric='analy_task_eval' """
|
|
587
|
+
"""AND tev.value_json.class=:s_class """
|
|
588
|
+
"""AND tev.value_json.gshare=:gshare """
|
|
589
|
+
"""AND t.gshare=:gshare """
|
|
590
|
+
)
|
|
591
|
+
# varMap
|
|
592
|
+
varMap = {
|
|
593
|
+
":s_class": 2,
|
|
594
|
+
":gshare": "User Analysis",
|
|
595
|
+
}
|
|
596
|
+
# result
|
|
597
|
+
res = self.taskBufferIF.querySQL(sql_get_tasks, varMap)
|
|
598
|
+
# Assign to Express Analysis
|
|
599
|
+
new_share = "Express Analysis"
|
|
600
|
+
for task_id, user in res:
|
|
601
|
+
tmpLog.info(
|
|
602
|
+
f" >>> action=gshare_reassignment jediTaskID={task_id} from gshare_old={varMap[':gshare']} to gshare_new={new_share} #ATM #KV label=user"
|
|
603
|
+
)
|
|
604
|
+
self.taskBufferIF.reassignShare([task_id], new_share, True)
|
|
605
|
+
# tweak split rule
|
|
606
|
+
_, task_spec = self.taskBufferIF.getTaskWithID_JEDI(task_id)
|
|
607
|
+
# set max walltime to 1 hour
|
|
608
|
+
if task_spec.getNumFilesPerJob() is None and task_spec.getNumEventsPerJob() is None:
|
|
609
|
+
tmpLog.info(f" >>> set max walltime to 1 hr for jediTaskID={task_id}")
|
|
610
|
+
task_spec.set_max_walltime(1)
|
|
611
|
+
self.taskBufferIF.updateTask_JEDI(task_spec, {"jediTaskID": task_id})
|
|
612
|
+
tmpLog.info(f">>> done jediTaskID={task_id}")
|
|
613
|
+
# done
|
|
614
|
+
tmpLog.debug("done")
|
|
615
|
+
except Exception:
|
|
616
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
617
|
+
tmpLog.error(f"failed with {errtype} {errvalue} {traceback.format_exc()}")
|
|
618
|
+
|
|
619
|
+
# periodic task action
|
|
620
|
+
def do_periodic_action(self):
|
|
621
|
+
tmp_log = MsgWrapper(logger, " do_periodic_action label=user")
|
|
622
|
+
tmp_log.debug("start")
|
|
623
|
+
try:
|
|
624
|
+
# get lifetime for output container
|
|
625
|
+
lifetime = self.taskBufferIF.getConfigValue("user_output", "OUTPUT_CONTAINER_LIFETIME", "jedi")
|
|
626
|
+
if not lifetime:
|
|
627
|
+
lifetime = 14
|
|
628
|
+
lifetime *= 24 * 60 * 60
|
|
629
|
+
# get DDM interface
|
|
630
|
+
ddm_if = self.ddmIF.getInterface(self.vo)
|
|
631
|
+
# get tasks
|
|
632
|
+
task_list = self.taskBufferIF.get_tasks_for_periodic_action(self.vo, self.prodSourceLabel)
|
|
633
|
+
for task_id in task_list:
|
|
634
|
+
# get datasets
|
|
635
|
+
_, tmp_datasets = self.taskBufferIF.getDatasetsWithJediTaskID_JEDI(task_id, ["output"])
|
|
636
|
+
done_containers = set()
|
|
637
|
+
for dataset_spec in tmp_datasets:
|
|
638
|
+
# skip if already done for the container
|
|
639
|
+
if not dataset_spec.containerName or dataset_spec.containerName in done_containers:
|
|
640
|
+
continue
|
|
641
|
+
done_containers.add(dataset_spec.containerName)
|
|
642
|
+
tmp_log.debug(f"extend lifetime taskID={task_id} container={dataset_spec.containerName}")
|
|
643
|
+
ddm_if.updateReplicationRules(
|
|
644
|
+
dataset_spec.containerName,
|
|
645
|
+
{"type=.+": {"lifetime": lifetime}, "(SCRATCH|USER)DISK": {"lifetime": lifetime}},
|
|
646
|
+
)
|
|
647
|
+
except Exception as e:
|
|
648
|
+
tmp_log.error(f"failed with {str(e)}{traceback.format_exc()}")
|