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,800 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
11
|
+
from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
|
|
12
|
+
|
|
13
|
+
from pandajedi.jedibrokerage import AtlasBrokerUtils
|
|
14
|
+
from pandajedi.jediconfig import jedi_config
|
|
15
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
16
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
17
|
+
from pandaserver.taskbuffer.ResourceSpec import ResourceSpecMapper
|
|
18
|
+
|
|
19
|
+
from .WatchDogBase import WatchDogBase
|
|
20
|
+
|
|
21
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
22
|
+
|
|
23
|
+
# dry run or not
|
|
24
|
+
DRY_RUN = False
|
|
25
|
+
|
|
26
|
+
# dedicated workqueue for preassigned task
|
|
27
|
+
magic_workqueue_id = 400
|
|
28
|
+
magic_workqueue_name = "wd_queuefiller"
|
|
29
|
+
|
|
30
|
+
# wait time before reassign jobs in seconds
|
|
31
|
+
reassign_jobs_wait_time = 300
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# queue filler watchdog for ATLAS
|
|
35
|
+
class AtlasQueueFillerWatchDog(WatchDogBase):
|
|
36
|
+
# constructor
|
|
37
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
38
|
+
WatchDogBase.__init__(self, taskBufferIF, ddmIF)
|
|
39
|
+
self.pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}-dog"
|
|
40
|
+
self.vo = "atlas"
|
|
41
|
+
self.prodSourceLabelList = ["managed"]
|
|
42
|
+
|
|
43
|
+
# keys for cache
|
|
44
|
+
self.dc_main_key = "AtlasQueueFillerWatchDog"
|
|
45
|
+
self.dc_sub_key_pt = "PreassignedTasks"
|
|
46
|
+
self.dc_sub_key_bt = "BlacklistedTasks"
|
|
47
|
+
self.dc_sub_key_attr = "OriginalTaskAttributes"
|
|
48
|
+
self.dc_sub_key_ses = "SiteEmptySince"
|
|
49
|
+
|
|
50
|
+
# initialize the resource_spec_mapper object
|
|
51
|
+
resource_types = taskBufferIF.load_resource_types()
|
|
52
|
+
self.resource_spec_mapper = ResourceSpecMapper(resource_types)
|
|
53
|
+
|
|
54
|
+
self.refresh()
|
|
55
|
+
|
|
56
|
+
# refresh information stored in the instance
|
|
57
|
+
def refresh(self):
|
|
58
|
+
# work queue mapper
|
|
59
|
+
self.workQueueMapper = self.taskBufferIF.getWorkQueueMap()
|
|
60
|
+
# site mapper
|
|
61
|
+
self.siteMapper = self.taskBufferIF.get_site_mapper()
|
|
62
|
+
# all sites
|
|
63
|
+
allSiteList = []
|
|
64
|
+
for siteName, tmpSiteSpec in self.siteMapper.siteSpecList.items():
|
|
65
|
+
# if tmpSiteSpec.type == 'analysis' or tmpSiteSpec.is_grandly_unified():
|
|
66
|
+
allSiteList.append(siteName)
|
|
67
|
+
self.allSiteList = allSiteList
|
|
68
|
+
# software map
|
|
69
|
+
try:
|
|
70
|
+
self.sw_map = self.taskBufferIF.load_sw_map()
|
|
71
|
+
if self.sw_map is None:
|
|
72
|
+
logger.warning("failed to load software map: got None from taskBufferIF.load_sw_map; using empty map")
|
|
73
|
+
self.sw_map = {}
|
|
74
|
+
except Exception:
|
|
75
|
+
logger.error("exception while loading software map in refresh:\n%s", traceback.format_exc())
|
|
76
|
+
self.sw_map = {}
|
|
77
|
+
|
|
78
|
+
# update preassigned task map to cache
|
|
79
|
+
def _update_to_pt_cache(self, ptmap):
|
|
80
|
+
data_json = json.dumps(ptmap)
|
|
81
|
+
self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_pt, data=data_json)
|
|
82
|
+
|
|
83
|
+
# get preassigned task map from cache
|
|
84
|
+
def _get_from_pt_cache(self):
|
|
85
|
+
cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_pt)
|
|
86
|
+
if cache_spec is not None:
|
|
87
|
+
ret_map = json.loads(cache_spec.data)
|
|
88
|
+
return ret_map
|
|
89
|
+
else:
|
|
90
|
+
return dict()
|
|
91
|
+
|
|
92
|
+
# update blacklisted task map to cache
|
|
93
|
+
def _update_to_bt_cache(self, btmap):
|
|
94
|
+
data_json = json.dumps(btmap)
|
|
95
|
+
self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_bt, data=data_json)
|
|
96
|
+
|
|
97
|
+
# get blacklisted task map from cache
|
|
98
|
+
def _get_from_bt_cache(self):
|
|
99
|
+
cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_bt)
|
|
100
|
+
if cache_spec is not None:
|
|
101
|
+
ret_map = json.loads(cache_spec.data)
|
|
102
|
+
return ret_map
|
|
103
|
+
else:
|
|
104
|
+
return dict()
|
|
105
|
+
|
|
106
|
+
# update task original attributes map to cache
|
|
107
|
+
def _update_to_attr_cache(self, attrmap):
|
|
108
|
+
data_json = json.dumps(attrmap)
|
|
109
|
+
self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_attr, data=data_json)
|
|
110
|
+
|
|
111
|
+
# get task original attributes map from cache
|
|
112
|
+
def _get_from_attr_cache(self):
|
|
113
|
+
cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_attr)
|
|
114
|
+
if cache_spec is not None:
|
|
115
|
+
ret_map = json.loads(cache_spec.data)
|
|
116
|
+
return ret_map
|
|
117
|
+
else:
|
|
118
|
+
return dict()
|
|
119
|
+
|
|
120
|
+
# update site empty-since map to cache
|
|
121
|
+
def _update_to_ses_cache(self, sesmap):
|
|
122
|
+
data_json = json.dumps(sesmap)
|
|
123
|
+
self.taskBufferIF.updateCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_ses, data=data_json)
|
|
124
|
+
|
|
125
|
+
# get site empty-since map from cache
|
|
126
|
+
def _get_from_ses_cache(self):
|
|
127
|
+
cache_spec = self.taskBufferIF.getCache_JEDI(main_key=self.dc_main_key, sub_key=self.dc_sub_key_ses)
|
|
128
|
+
if cache_spec is not None:
|
|
129
|
+
ret_map = json.loads(cache_spec.data)
|
|
130
|
+
return ret_map
|
|
131
|
+
else:
|
|
132
|
+
return dict()
|
|
133
|
+
|
|
134
|
+
# get process lock to preassign
|
|
135
|
+
def _get_lock(self):
|
|
136
|
+
return self.taskBufferIF.lockProcess_JEDI(
|
|
137
|
+
vo=self.vo,
|
|
138
|
+
prodSourceLabel="managed",
|
|
139
|
+
cloud=None,
|
|
140
|
+
workqueue_id=None,
|
|
141
|
+
resource_name=None,
|
|
142
|
+
component="AtlasQueueFillerWatchDog.preassign",
|
|
143
|
+
pid=self.pid,
|
|
144
|
+
timeLimit=5,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# get map of site to list of RSEs
|
|
148
|
+
def get_site_rse_map(self, prod_source_label):
|
|
149
|
+
site_rse_map = {}
|
|
150
|
+
for tmpPseudoSiteName in self.allSiteList:
|
|
151
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
152
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
153
|
+
scope_input, scope_output = DataServiceUtils.select_scope(tmpSiteSpec, prod_source_label, prod_source_label)
|
|
154
|
+
try:
|
|
155
|
+
endpoint_token_map = tmpSiteSpec.ddm_endpoints_input[scope_input].all
|
|
156
|
+
except KeyError:
|
|
157
|
+
continue
|
|
158
|
+
else:
|
|
159
|
+
# fill
|
|
160
|
+
site_rse_map[tmpSiteName] = [k for k, v in endpoint_token_map.items() if v.get("order_read") is not None]
|
|
161
|
+
# return
|
|
162
|
+
return site_rse_map
|
|
163
|
+
|
|
164
|
+
# get to-running rate of sites between 24 hours ago ~ 6 hours ago
|
|
165
|
+
def get_site_trr_map(self):
|
|
166
|
+
ret_val, ret_map = AtlasBrokerUtils.getSiteToRunRateStats(self.taskBufferIF, self.vo, time_window=86400, cutoff=0, cache_lifetime=600)
|
|
167
|
+
if ret_val:
|
|
168
|
+
return ret_map
|
|
169
|
+
else:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def is_arm_only_site(self, site) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Check if a site is ARM-only.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
site (str): The name of the site to check.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
bool: True if the site is ARM-only, False otherwise.
|
|
181
|
+
"""
|
|
182
|
+
ret = False
|
|
183
|
+
architecture_map = {}
|
|
184
|
+
if site in self.sw_map:
|
|
185
|
+
if architectures_list := self.sw_map[site].get("architectures"):
|
|
186
|
+
for architecture_dict in architectures_list:
|
|
187
|
+
if "type" in architecture_dict:
|
|
188
|
+
architecture_map[architecture_dict["type"]] = architecture_dict
|
|
189
|
+
# only tasks which can run with ARM if site architecture is ARM only
|
|
190
|
+
if cpu_architecture_dict := architecture_map.get("cpu"):
|
|
191
|
+
if arch_list := cpu_architecture_dict.get("arch"):
|
|
192
|
+
if "aarch64" in arch_list and ("excl" in arch_list or "x86_64" not in arch_list):
|
|
193
|
+
ret = True
|
|
194
|
+
return ret
|
|
195
|
+
|
|
196
|
+
def is_fat_container_site(self, site) -> bool:
|
|
197
|
+
"""
|
|
198
|
+
Check if a site is a fat container site.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
site (str): The name of the site to check.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
bool: True if the site is a fat container site, False otherwise.
|
|
205
|
+
"""
|
|
206
|
+
ret = False
|
|
207
|
+
if site in self.sw_map:
|
|
208
|
+
containers_list = self.sw_map[site].get("containers", [])
|
|
209
|
+
if not ("any" in containers_list or "/cvmfs" in containers_list):
|
|
210
|
+
ret = True
|
|
211
|
+
return ret
|
|
212
|
+
|
|
213
|
+
def get_list_of_fat_container_names(self, site) -> list | None:
|
|
214
|
+
"""
|
|
215
|
+
Get the list of fat container names for a site if it is a fat container site.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
site (str): The name of the site to check.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
list | None: The list of fat container names if the site is a fat container site, None otherwise.
|
|
222
|
+
"""
|
|
223
|
+
container_names_list = None
|
|
224
|
+
if self.is_fat_container_site(site):
|
|
225
|
+
container_names_list = []
|
|
226
|
+
if site in self.sw_map:
|
|
227
|
+
for tag_dict in self.sw_map[site].get("tags", []):
|
|
228
|
+
if container_name := tag_dict.get("container_name"):
|
|
229
|
+
container_names_list.append(container_name)
|
|
230
|
+
return container_names_list
|
|
231
|
+
|
|
232
|
+
# get available sites sorted list
|
|
233
|
+
def get_available_sites_list(self):
|
|
234
|
+
tmp_log = MsgWrapper(logger, "get_available_sites_list")
|
|
235
|
+
# initialize
|
|
236
|
+
available_sites_dict = {}
|
|
237
|
+
# get global share
|
|
238
|
+
tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(self.vo)
|
|
239
|
+
if not tmpSt:
|
|
240
|
+
# got nothing...
|
|
241
|
+
return available_sites_dict
|
|
242
|
+
# get to-running rate of sites
|
|
243
|
+
site_trr_map = self.get_site_trr_map()
|
|
244
|
+
if site_trr_map is None:
|
|
245
|
+
return available_sites_dict
|
|
246
|
+
# record for excluded site reasons
|
|
247
|
+
excluded_sites_dict = {
|
|
248
|
+
"not_online": set(),
|
|
249
|
+
"has_minrss": set(),
|
|
250
|
+
"es_jobseed": set(),
|
|
251
|
+
"low_trr": set(),
|
|
252
|
+
"enough_nq": set(),
|
|
253
|
+
}
|
|
254
|
+
# loop over sites
|
|
255
|
+
for tmpPseudoSiteName in self.allSiteList:
|
|
256
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
257
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
258
|
+
# skip site already added
|
|
259
|
+
if tmpSiteName in available_sites_dict:
|
|
260
|
+
continue
|
|
261
|
+
# skip site if not for production
|
|
262
|
+
if not tmpSiteSpec.runs_production():
|
|
263
|
+
continue
|
|
264
|
+
# skip site is not online
|
|
265
|
+
if tmpSiteSpec.status not in ("online"):
|
|
266
|
+
excluded_sites_dict["not_online"].add(tmpSiteName)
|
|
267
|
+
continue
|
|
268
|
+
# skip if site has memory limitations
|
|
269
|
+
if tmpSiteSpec.minrss not in (0, None):
|
|
270
|
+
excluded_sites_dict["has_minrss"].add(tmpPseudoSiteName)
|
|
271
|
+
continue
|
|
272
|
+
# skip if site has event service jobseed
|
|
273
|
+
if tmpSiteSpec.getJobSeed() in ["es"]:
|
|
274
|
+
excluded_sites_dict["es_jobseed"].add(tmpPseudoSiteName)
|
|
275
|
+
continue
|
|
276
|
+
# tmp_num_slots as num_slots in harvester_slots
|
|
277
|
+
tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
|
|
278
|
+
tmp_num_slots = 0 if tmp_num_slots is None else tmp_num_slots
|
|
279
|
+
# skip if site (except ARM and fat container sites) has no harvester_slots setup and has not enough activity in the past 24 hours
|
|
280
|
+
site_trr = site_trr_map.get(tmpSiteName)
|
|
281
|
+
if (
|
|
282
|
+
not self.is_arm_only_site(tmpSiteName)
|
|
283
|
+
and not self.is_fat_container_site(tmpSiteName)
|
|
284
|
+
and tmp_num_slots == 0
|
|
285
|
+
and (site_trr is None or site_trr < 0.6)
|
|
286
|
+
):
|
|
287
|
+
excluded_sites_dict["low_trr"].add(tmpSiteName)
|
|
288
|
+
continue
|
|
289
|
+
# get nQueue and nRunning
|
|
290
|
+
nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
|
|
291
|
+
nQueue = 0
|
|
292
|
+
for jobStatus in ["activated", "starting"]:
|
|
293
|
+
nQueue += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus)
|
|
294
|
+
# get nStandby; for queues that specify num_slots in harvester_slots
|
|
295
|
+
tmp_core_count = tmpSiteSpec.coreCount if tmpSiteSpec.coreCount else 8
|
|
296
|
+
nStandby = tmp_num_slots // tmp_core_count
|
|
297
|
+
# available sites: must be idle now
|
|
298
|
+
n_jobs_to_fill = max(20, max(nRunning, nStandby) * 2) * 0.25 - nQueue
|
|
299
|
+
if n_jobs_to_fill > 0:
|
|
300
|
+
available_sites_dict[tmpSiteName] = (tmpSiteName, tmpSiteSpec, n_jobs_to_fill)
|
|
301
|
+
else:
|
|
302
|
+
excluded_sites_dict["enough_nq"].add(tmpSiteName)
|
|
303
|
+
# list
|
|
304
|
+
available_sites_list = list(available_sites_dict.values())
|
|
305
|
+
# sort by n_jobs_to_fill
|
|
306
|
+
available_sites_list.sort(key=(lambda x: x[2]), reverse=True)
|
|
307
|
+
# log message
|
|
308
|
+
for reason, sites_set in excluded_sites_dict.items():
|
|
309
|
+
excluded_sites_str = ",".join(sorted(sites_set))
|
|
310
|
+
tmp_log.debug(f"excluded sites due to {reason} : {excluded_sites_str}")
|
|
311
|
+
included_sites_str = ",".join(sorted([x[0] for x in available_sites_list]))
|
|
312
|
+
tmp_log.debug("included sites : {sites}".format(reason=reason, sites=included_sites_str))
|
|
313
|
+
# return
|
|
314
|
+
return available_sites_list
|
|
315
|
+
|
|
316
|
+
# get busy sites
|
|
317
|
+
def get_busy_sites(self):
|
|
318
|
+
busy_sites_dict = {}
|
|
319
|
+
# get global share
|
|
320
|
+
tmpSt, jobStatPrioMap = self.taskBufferIF.getJobStatisticsByGlobalShare(self.vo)
|
|
321
|
+
if not tmpSt:
|
|
322
|
+
# got nothing...
|
|
323
|
+
return busy_sites_dict
|
|
324
|
+
# get to-running rate of sites
|
|
325
|
+
site_trr_map = self.get_site_trr_map()
|
|
326
|
+
if site_trr_map is None:
|
|
327
|
+
return busy_sites_dict
|
|
328
|
+
# loop over sites
|
|
329
|
+
for tmpPseudoSiteName in self.allSiteList:
|
|
330
|
+
tmpSiteSpec = self.siteMapper.getSite(tmpPseudoSiteName)
|
|
331
|
+
tmpSiteName = tmpSiteSpec.get_unified_name()
|
|
332
|
+
# skip site already added
|
|
333
|
+
if tmpSiteName in busy_sites_dict:
|
|
334
|
+
continue
|
|
335
|
+
# initialize
|
|
336
|
+
is_busy = False
|
|
337
|
+
# site is not online viewed as busy
|
|
338
|
+
if tmpSiteSpec.status not in ("online"):
|
|
339
|
+
is_busy = True
|
|
340
|
+
# tmp_num_slots as num_slots in harvester_slots
|
|
341
|
+
tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
|
|
342
|
+
tmp_num_slots = 0 if tmp_num_slots is None else tmp_num_slots
|
|
343
|
+
# get nStandby; for queues that specify num_slots in harvester_slots
|
|
344
|
+
tmp_core_count = tmpSiteSpec.coreCount if tmpSiteSpec.coreCount else 8
|
|
345
|
+
nStandby = tmp_num_slots // tmp_core_count
|
|
346
|
+
# get nQueue and nRunning
|
|
347
|
+
nRunning = AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, "running")
|
|
348
|
+
nQueue = 0
|
|
349
|
+
for jobStatus in ["activated", "starting"]:
|
|
350
|
+
nQueue += AtlasBrokerUtils.getNumJobs(jobStatPrioMap, tmpSiteName, jobStatus)
|
|
351
|
+
# busy sites
|
|
352
|
+
if nQueue > max(max(20, nRunning * 2) * 0.375, nStandby):
|
|
353
|
+
busy_sites_dict[tmpSiteName] = tmpSiteSpec
|
|
354
|
+
# return
|
|
355
|
+
return busy_sites_dict
|
|
356
|
+
|
|
357
|
+
# preassign tasks to site
|
|
358
|
+
def do_preassign(self):
|
|
359
|
+
tmp_log = MsgWrapper(logger, "do_preassign")
|
|
360
|
+
# refresh
|
|
361
|
+
self.refresh()
|
|
362
|
+
# list of resource type
|
|
363
|
+
resource_type_list = [rt.resource_name for rt in self.taskBufferIF.load_resource_types()]
|
|
364
|
+
# threshold of time duration in second that the queue keeps empty to trigger preassigning
|
|
365
|
+
empty_duration_threshold = 1800
|
|
366
|
+
# return map
|
|
367
|
+
ret_map = {
|
|
368
|
+
"to_reassign": {},
|
|
369
|
+
}
|
|
370
|
+
# loop
|
|
371
|
+
for prod_source_label in self.prodSourceLabelList:
|
|
372
|
+
# site-rse map
|
|
373
|
+
site_rse_map = self.get_site_rse_map(prod_source_label)
|
|
374
|
+
# parameters from GDP config
|
|
375
|
+
max_preassigned_tasks = self.taskBufferIF.getConfigValue("queue_filler", f"MAX_PREASSIGNED_TASKS_{prod_source_label}", "jedi", self.vo)
|
|
376
|
+
if max_preassigned_tasks is None:
|
|
377
|
+
max_preassigned_tasks = 3
|
|
378
|
+
min_files_ready = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}", "jedi", self.vo)
|
|
379
|
+
if min_files_ready is None:
|
|
380
|
+
min_files_ready = 50
|
|
381
|
+
min_files_ready_fc = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}_FC", "jedi", self.vo)
|
|
382
|
+
if min_files_ready_fc is None:
|
|
383
|
+
min_files_ready_fc = 10
|
|
384
|
+
min_files_remaining = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_REMAINING_{prod_source_label}", "jedi", self.vo)
|
|
385
|
+
if min_files_remaining is None:
|
|
386
|
+
min_files_remaining = 100
|
|
387
|
+
# load site empty-since map from cache
|
|
388
|
+
site_empty_since_map_orig = self._get_from_ses_cache()
|
|
389
|
+
# available sites
|
|
390
|
+
available_sites_list = self.get_available_sites_list()
|
|
391
|
+
# now timestamp
|
|
392
|
+
now_time = naive_utcnow()
|
|
393
|
+
now_time_ts = int(now_time.timestamp())
|
|
394
|
+
# update site empty-since map
|
|
395
|
+
site_empty_since_map = copy.deepcopy(site_empty_since_map_orig)
|
|
396
|
+
available_site_name_list = [x[0] for x in available_sites_list]
|
|
397
|
+
for site in site_empty_since_map_orig:
|
|
398
|
+
# remove sites that are no longer empty
|
|
399
|
+
if site not in available_site_name_list:
|
|
400
|
+
del site_empty_since_map[site]
|
|
401
|
+
for site in available_site_name_list:
|
|
402
|
+
# add newly found empty sites
|
|
403
|
+
if site not in site_empty_since_map_orig:
|
|
404
|
+
site_empty_since_map[site] = now_time_ts
|
|
405
|
+
self._update_to_ses_cache(site_empty_since_map)
|
|
406
|
+
# evaluate sites to preassign according to cache
|
|
407
|
+
# get blacklisted_tasks_map from cache
|
|
408
|
+
blacklisted_tasks_map = self._get_from_bt_cache()
|
|
409
|
+
blacklisted_tasks_set = set()
|
|
410
|
+
for bt_list in blacklisted_tasks_map.values():
|
|
411
|
+
blacklisted_tasks_set |= set(bt_list)
|
|
412
|
+
# loop over available sites to preassign
|
|
413
|
+
for site, tmpSiteSpec, n_jobs_to_fill in available_sites_list:
|
|
414
|
+
# rses of the available site
|
|
415
|
+
available_rses = set()
|
|
416
|
+
try:
|
|
417
|
+
available_rses.update(set(site_rse_map[site]))
|
|
418
|
+
except KeyError:
|
|
419
|
+
tmp_log.debug(f"skipped {site} since no good RSE")
|
|
420
|
+
continue
|
|
421
|
+
# do not consider TAPE rses
|
|
422
|
+
for rse in set(available_rses):
|
|
423
|
+
if "TAPE" in str(rse):
|
|
424
|
+
available_rses.remove(rse)
|
|
425
|
+
# skip if no rse for available site
|
|
426
|
+
if not available_rses:
|
|
427
|
+
tmp_log.debug(f"skipped {site} since no available RSE")
|
|
428
|
+
continue
|
|
429
|
+
# skip if no coreCount set
|
|
430
|
+
if not tmpSiteSpec.coreCount or tmpSiteSpec.coreCount <= 0:
|
|
431
|
+
tmp_log.debug(f"skipped {site} since coreCount is not set")
|
|
432
|
+
continue
|
|
433
|
+
# now timestamp
|
|
434
|
+
now_time = naive_utcnow()
|
|
435
|
+
now_time_ts = int(now_time.timestamp())
|
|
436
|
+
# skip if not empty for long enough
|
|
437
|
+
if site not in site_empty_since_map:
|
|
438
|
+
tmp_log.error(f"skipped {site} since not in empty-since map (should not happen)")
|
|
439
|
+
continue
|
|
440
|
+
empty_duration = now_time_ts - site_empty_since_map[site]
|
|
441
|
+
tmp_num_slots = tmpSiteSpec.getNumStandby(None, None)
|
|
442
|
+
if empty_duration < empty_duration_threshold and not tmp_num_slots:
|
|
443
|
+
tmp_log.debug(f"skipped {site} since not empty for enough time ({empty_duration}s < {empty_duration_threshold}s)")
|
|
444
|
+
continue
|
|
445
|
+
# only simul tasks if site has fairsharePolicy setup
|
|
446
|
+
processing_type_constraint = ""
|
|
447
|
+
if tmpSiteSpec.fairsharePolicy not in ("NULL", None):
|
|
448
|
+
if "type=simul:0%" in tmpSiteSpec.fairsharePolicy:
|
|
449
|
+
# skip if zero share of simul
|
|
450
|
+
tmp_log.debug(f"skipped {site} since with fairshare but zero for simul")
|
|
451
|
+
continue
|
|
452
|
+
else:
|
|
453
|
+
processing_type_constraint = "AND t.processingType='simul' "
|
|
454
|
+
# only tasks which can run with ARM if site architecture is ARM only
|
|
455
|
+
architecture_constraint = ""
|
|
456
|
+
if self.is_arm_only_site(site):
|
|
457
|
+
tmp_log.debug(f"{site} is ARM-only site")
|
|
458
|
+
architecture_constraint += "AND t.architecture LIKE '%aarch64%' "
|
|
459
|
+
# only tasks with fat container if site is fat container only
|
|
460
|
+
container_name_constraint = ""
|
|
461
|
+
container_name_var_map = {}
|
|
462
|
+
if container_names_list := self.get_list_of_fat_container_names(site):
|
|
463
|
+
tmp_log.debug(f"{site} is fat container site")
|
|
464
|
+
container_name_var_names_str, container_name_var_map = get_sql_IN_bind_variables(container_names_list, prefix=":container_name")
|
|
465
|
+
container_name_constraint += "AND t.container_name IS NOT NULL "
|
|
466
|
+
container_name_constraint += f"AND t.container_name IN ({container_name_var_names_str}) "
|
|
467
|
+
elif container_names_list == []:
|
|
468
|
+
# fat-container-only site with no specific container names configured; skipped the site
|
|
469
|
+
tmp_log.debug(f"skipped {site} since it is fat-container-only site but no specific container names configured")
|
|
470
|
+
continue
|
|
471
|
+
# site attributes
|
|
472
|
+
site_maxrss = tmpSiteSpec.maxrss if tmpSiteSpec.maxrss not in (0, None) else 999999
|
|
473
|
+
site_corecount = tmpSiteSpec.coreCount
|
|
474
|
+
site_capability = str(tmpSiteSpec.capability).lower()
|
|
475
|
+
# make sql parameters of rses
|
|
476
|
+
available_rses = list(available_rses)
|
|
477
|
+
rse_params_list = []
|
|
478
|
+
rse_params_map = {}
|
|
479
|
+
for j, rse in enumerate(available_rses):
|
|
480
|
+
rse_param = f":rse_{j + 1}"
|
|
481
|
+
rse_params_list.append(rse_param)
|
|
482
|
+
rse_params_map[rse_param] = rse
|
|
483
|
+
rse_params_str = ",".join(rse_params_list)
|
|
484
|
+
# sql
|
|
485
|
+
base_sql_query = (
|
|
486
|
+
"SELECT t.jediTaskID, t.workQueue_ID "
|
|
487
|
+
"FROM {jedi_schema}.JEDI_Tasks t "
|
|
488
|
+
"WHERE t.status IN ('ready','running') AND t.lockedBy IS NULL "
|
|
489
|
+
"AND t.prodSourceLabel=:prodSourceLabel "
|
|
490
|
+
"AND t.resource_type=:resource_type "
|
|
491
|
+
"AND site IS NULL "
|
|
492
|
+
"AND (COALESCE(t.baseRamCount, 0) + (CASE WHEN t.ramUnit IN ('MBPerCore','MBPerCoreFixed') THEN t.ramCount*:site_corecount ELSE t.ramCount END))*0.95 < :site_maxrss "
|
|
493
|
+
"AND t.eventService=0 "
|
|
494
|
+
"AND EXISTS ( "
|
|
495
|
+
"SELECT * FROM {jedi_schema}.JEDI_Dataset_Locality dl "
|
|
496
|
+
"WHERE dl.jediTaskID=t.jediTaskID "
|
|
497
|
+
"AND dl.rse IN ({rse_params_str}) "
|
|
498
|
+
") "
|
|
499
|
+
"{processing_type_constraint} "
|
|
500
|
+
"{architecture_constraint} "
|
|
501
|
+
"{container_name_constraint} "
|
|
502
|
+
"AND EXISTS ( "
|
|
503
|
+
"SELECT d.datasetID FROM {jedi_schema}.JEDI_Datasets d "
|
|
504
|
+
"WHERE t.jediTaskID=d.jediTaskID AND d.type='input' "
|
|
505
|
+
"AND d.nFilesToBeUsed-d.nFilesUsed>=:min_files_ready "
|
|
506
|
+
"AND d.nFiles-d.nFilesUsed>=:min_files_remaining "
|
|
507
|
+
") "
|
|
508
|
+
"ORDER BY t.currentPriority DESC "
|
|
509
|
+
).format(
|
|
510
|
+
jedi_schema=jedi_config.db.schemaJEDI,
|
|
511
|
+
rse_params_str=rse_params_str,
|
|
512
|
+
processing_type_constraint=processing_type_constraint,
|
|
513
|
+
architecture_constraint=architecture_constraint,
|
|
514
|
+
container_name_constraint=container_name_constraint,
|
|
515
|
+
)
|
|
516
|
+
# loop over resource type
|
|
517
|
+
for resource_type in resource_type_list:
|
|
518
|
+
# key name for preassigned_tasks_map = site + resource_type
|
|
519
|
+
key_name = f"{site}|{resource_type}"
|
|
520
|
+
|
|
521
|
+
# skip if no match: site is single core and resource_type is multi core
|
|
522
|
+
if site_capability == "score" and self.resource_spec_mapper.is_multi_core(resource_type):
|
|
523
|
+
continue
|
|
524
|
+
|
|
525
|
+
# skip if no match: site is multi core and resource_type is single core
|
|
526
|
+
elif site_capability == "mcore" and self.resource_spec_mapper.is_single_core(resource_type):
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
params_map = {
|
|
530
|
+
":prodSourceLabel": prod_source_label,
|
|
531
|
+
":resource_type": resource_type,
|
|
532
|
+
":site_maxrss": site_maxrss,
|
|
533
|
+
":site_corecount": site_corecount,
|
|
534
|
+
":min_files_ready": min_files_ready_fc if self.is_fat_container_site(site) else min_files_ready,
|
|
535
|
+
":min_files_remaining": min_files_remaining,
|
|
536
|
+
}
|
|
537
|
+
params_map.update(rse_params_map)
|
|
538
|
+
params_map.update(container_name_var_map)
|
|
539
|
+
# get preassigned_tasks_map from cache
|
|
540
|
+
preassigned_tasks_map = self._get_from_pt_cache()
|
|
541
|
+
preassigned_tasks_cached = preassigned_tasks_map.get(key_name, [])
|
|
542
|
+
# get task_orig_attr_map from cache
|
|
543
|
+
task_orig_attr_map = self._get_from_attr_cache()
|
|
544
|
+
# number of tasks already preassigned
|
|
545
|
+
n_preassigned_tasks = len(preassigned_tasks_cached)
|
|
546
|
+
# number of tasks to preassign
|
|
547
|
+
n_tasks_to_preassign = max(max_preassigned_tasks - n_preassigned_tasks, 0)
|
|
548
|
+
# preassign
|
|
549
|
+
if n_tasks_to_preassign <= 0:
|
|
550
|
+
tmp_log.debug(f"{key_name:<64} already has enough preassigned tasks ({n_preassigned_tasks:>3}) ; skipped ")
|
|
551
|
+
elif DRY_RUN:
|
|
552
|
+
# for dry run, just query and update cache without updating db
|
|
553
|
+
dry_sql_query = base_sql_query
|
|
554
|
+
# tmp_log.debug('[dry run] {} {}'.format(dry_sql_query, params_map))
|
|
555
|
+
res = self.taskBufferIF.querySQL(dry_sql_query, params_map)
|
|
556
|
+
n_tasks = 0 if res is None else len(res)
|
|
557
|
+
if n_tasks > 0:
|
|
558
|
+
result = [x[0] for x in res if x[0] not in preassigned_tasks_cached]
|
|
559
|
+
updated_tasks = result[:n_tasks_to_preassign]
|
|
560
|
+
tmp_log.debug(f"[dry run] {key_name:<64} {n_tasks_to_preassign:>3} tasks would be preassigned ")
|
|
561
|
+
# update preassigned_tasks_map into cache
|
|
562
|
+
preassigned_tasks_map[key_name] = list(set(updated_tasks) | set(preassigned_tasks_cached))
|
|
563
|
+
tmp_log.debug(f"{str(updated_tasks)} ; {str(preassigned_tasks_map[key_name])}")
|
|
564
|
+
self._update_to_pt_cache(preassigned_tasks_map)
|
|
565
|
+
else:
|
|
566
|
+
# for real run, query with FOR UPDATE and update db to preassign tasks
|
|
567
|
+
sql_query = base_sql_query + "FOR UPDATE "
|
|
568
|
+
updated_tasks_orig_attr = self.taskBufferIF.queryTasksToPreassign_JEDI(
|
|
569
|
+
sql_query, params_map, site, blacklist=blacklisted_tasks_set, limit=n_tasks_to_preassign
|
|
570
|
+
)
|
|
571
|
+
if updated_tasks_orig_attr is None:
|
|
572
|
+
# dbproxy method failed
|
|
573
|
+
tmp_log.error(f"{key_name:<64} failed to preassign tasks ")
|
|
574
|
+
else:
|
|
575
|
+
n_tasks = len(updated_tasks_orig_attr)
|
|
576
|
+
if n_tasks > 0:
|
|
577
|
+
updated_tasks = [x[0] for x in updated_tasks_orig_attr]
|
|
578
|
+
tmp_log.info(f"{key_name:<64} {str(n_tasks):>3} tasks preassigned : {updated_tasks}")
|
|
579
|
+
# update preassigned_tasks_map into cache
|
|
580
|
+
preassigned_tasks_map[key_name] = list(set(updated_tasks) | set(preassigned_tasks_cached))
|
|
581
|
+
self._update_to_pt_cache(preassigned_tasks_map)
|
|
582
|
+
# update task_orig_attr_map into cache and return map
|
|
583
|
+
for taskid, orig_attr in updated_tasks_orig_attr:
|
|
584
|
+
taskid_str = str(taskid)
|
|
585
|
+
task_orig_attr_map[taskid_str] = orig_attr
|
|
586
|
+
ret_map["to_reassign"][taskid] = {
|
|
587
|
+
"site": site,
|
|
588
|
+
"n_jobs_to_fill": n_jobs_to_fill,
|
|
589
|
+
}
|
|
590
|
+
self._update_to_attr_cache(task_orig_attr_map)
|
|
591
|
+
# Kibana log
|
|
592
|
+
for taskid in updated_tasks:
|
|
593
|
+
tmp_log.debug(f"#ATM #KV jediTaskID={taskid} action=do_preassign site={site} rtype={resource_type} preassigned ")
|
|
594
|
+
else:
|
|
595
|
+
tmp_log.debug(f"{key_name:<64} found no proper task to preassign")
|
|
596
|
+
# total preassigned tasks
|
|
597
|
+
preassigned_tasks_map = self._get_from_pt_cache()
|
|
598
|
+
n_pt_tot = sum([len(pt_list) for pt_list in preassigned_tasks_map.values()])
|
|
599
|
+
if DRY_RUN:
|
|
600
|
+
tmp_log.debug(f"[dry run] would have {n_pt_tot} tasks preassigned in total")
|
|
601
|
+
else:
|
|
602
|
+
tmp_log.debug(f"now {n_pt_tot} tasks preassigned in total")
|
|
603
|
+
# return
|
|
604
|
+
return ret_map
|
|
605
|
+
|
|
606
|
+
# undo preassign tasks
|
|
607
|
+
def undo_preassign(self):
|
|
608
|
+
tmp_log = MsgWrapper(logger, "undo_preassign")
|
|
609
|
+
# refresh
|
|
610
|
+
self.refresh()
|
|
611
|
+
# busy sites
|
|
612
|
+
busy_sites_dict = self.get_busy_sites()
|
|
613
|
+
# loop to undo preassigning
|
|
614
|
+
for prod_source_label in self.prodSourceLabelList:
|
|
615
|
+
# parameter from GDP config
|
|
616
|
+
max_preassigned_tasks = self.taskBufferIF.getConfigValue("queue_filler", f"MAX_PREASSIGNED_TASKS_{prod_source_label}", "jedi", self.vo)
|
|
617
|
+
if max_preassigned_tasks is None:
|
|
618
|
+
max_preassigned_tasks = 3
|
|
619
|
+
min_files_ready = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_READY_{prod_source_label}", "jedi", self.vo)
|
|
620
|
+
if min_files_ready is None:
|
|
621
|
+
min_files_ready = 50
|
|
622
|
+
min_files_remaining = self.taskBufferIF.getConfigValue("queue_filler", f"MIN_FILES_REMAINING_{prod_source_label}", "jedi", self.vo)
|
|
623
|
+
if min_files_remaining is None:
|
|
624
|
+
min_files_remaining = 100
|
|
625
|
+
# clean up outdated blacklist
|
|
626
|
+
blacklist_duration_hours = 12
|
|
627
|
+
blacklisted_tasks_map_orig = self._get_from_bt_cache()
|
|
628
|
+
blacklisted_tasks_map = copy.deepcopy(blacklisted_tasks_map_orig)
|
|
629
|
+
now_time = naive_utcnow()
|
|
630
|
+
min_allowed_time = now_time - datetime.timedelta(hours=blacklist_duration_hours)
|
|
631
|
+
min_allowed_ts = int(min_allowed_time.timestamp())
|
|
632
|
+
for ts_str in blacklisted_tasks_map_orig:
|
|
633
|
+
ts = int(ts_str)
|
|
634
|
+
if ts < min_allowed_ts:
|
|
635
|
+
del blacklisted_tasks_map[ts_str]
|
|
636
|
+
self._update_to_bt_cache(blacklisted_tasks_map)
|
|
637
|
+
n_bt_old = sum([len(bt_list) for bt_list in blacklisted_tasks_map_orig.values()])
|
|
638
|
+
n_bt = sum([len(bt_list) for bt_list in blacklisted_tasks_map.values()])
|
|
639
|
+
tmp_log.debug(f"done cleanup blacklist; before {n_bt_old} , now {n_bt} tasks in blacklist")
|
|
640
|
+
# get a copy of preassigned_tasks_map from cache
|
|
641
|
+
preassigned_tasks_map_orig = self._get_from_pt_cache()
|
|
642
|
+
preassigned_tasks_map = copy.deepcopy(preassigned_tasks_map_orig)
|
|
643
|
+
# clean up task_orig_attr_map in cache
|
|
644
|
+
task_orig_attr_map_orig = self._get_from_attr_cache()
|
|
645
|
+
task_orig_attr_map = copy.deepcopy(task_orig_attr_map_orig)
|
|
646
|
+
all_preassigned_taskids = set()
|
|
647
|
+
for taskid_list in preassigned_tasks_map_orig.values():
|
|
648
|
+
all_preassigned_taskids |= set(taskid_list)
|
|
649
|
+
for taskid_str in task_orig_attr_map_orig:
|
|
650
|
+
taskid = int(taskid_str)
|
|
651
|
+
if taskid not in all_preassigned_taskids:
|
|
652
|
+
del task_orig_attr_map[taskid_str]
|
|
653
|
+
self._update_to_attr_cache(task_orig_attr_map)
|
|
654
|
+
# loop on preassigned tasks in cache
|
|
655
|
+
for key_name in preassigned_tasks_map_orig:
|
|
656
|
+
# parse key name = site + resource_type
|
|
657
|
+
site, resource_type = key_name.split("|")
|
|
658
|
+
# preassigned tasks in cache
|
|
659
|
+
preassigned_tasks_cached = preassigned_tasks_map.get(key_name, [])
|
|
660
|
+
# force_undo=True for all tasks in busy sites, and force_undo=False for tasks not in status to generate jobs
|
|
661
|
+
force_undo = False
|
|
662
|
+
if site in busy_sites_dict or len(preassigned_tasks_cached) > max_preassigned_tasks:
|
|
663
|
+
force_undo = True
|
|
664
|
+
reason_str = (
|
|
665
|
+
"site busy or offline or with too many preassigned tasks" if force_undo else "task paused/terminated or without enough files to process"
|
|
666
|
+
)
|
|
667
|
+
# parameters for undo, kinda ugly
|
|
668
|
+
params_map = {
|
|
669
|
+
":min_files_ready": min_files_ready,
|
|
670
|
+
":min_files_remaining": min_files_remaining,
|
|
671
|
+
}
|
|
672
|
+
# undo preassign
|
|
673
|
+
had_undo = False
|
|
674
|
+
updated_tasks = []
|
|
675
|
+
if DRY_RUN:
|
|
676
|
+
if force_undo:
|
|
677
|
+
updated_tasks = list(preassigned_tasks_cached)
|
|
678
|
+
n_tasks = len(updated_tasks)
|
|
679
|
+
else:
|
|
680
|
+
preassigned_tasks_list = []
|
|
681
|
+
preassigned_tasks_params_map = {}
|
|
682
|
+
for j, taskid in enumerate(preassigned_tasks_cached):
|
|
683
|
+
pt_param = f":pt_{j + 1}"
|
|
684
|
+
preassigned_tasks_list.append(pt_param)
|
|
685
|
+
preassigned_tasks_params_map[pt_param] = taskid
|
|
686
|
+
if not preassigned_tasks_list:
|
|
687
|
+
continue
|
|
688
|
+
preassigned_tasks_params_str = ",".join(preassigned_tasks_list)
|
|
689
|
+
dry_sql_query = (
|
|
690
|
+
"SELECT t.jediTaskID "
|
|
691
|
+
"FROM {jedi_schema}.JEDI_Tasks t "
|
|
692
|
+
"WHERE t.jediTaskID IN ({preassigned_tasks_params_str}) "
|
|
693
|
+
"AND t.site IS NOT NULL "
|
|
694
|
+
"AND NOT ( "
|
|
695
|
+
"t.status IN ('ready','running') "
|
|
696
|
+
"AND EXISTS ( "
|
|
697
|
+
"SELECT d.datasetID FROM {0}.JEDI_Datasets d "
|
|
698
|
+
"WHERE t.jediTaskID=d.jediTaskID AND d.type='input' "
|
|
699
|
+
"AND d.nFilesToBeUsed-d.nFilesUsed>=:min_files_ready AND d.nFiles-d.nFilesUsed>=:min_files_remaining "
|
|
700
|
+
") "
|
|
701
|
+
") "
|
|
702
|
+
).format(jedi_schema=jedi_config.db.schemaJEDI, preassigned_tasks_params_str=preassigned_tasks_params_str)
|
|
703
|
+
res = self.taskBufferIF.querySQL(dry_sql_query, preassigned_tasks_params_map)
|
|
704
|
+
n_tasks = 0 if res is None else len(res)
|
|
705
|
+
if n_tasks > 0:
|
|
706
|
+
updated_tasks = [x[0] for x in res]
|
|
707
|
+
# tmp_log.debug('[dry run] {} {} force={}'.format(key_name, str(updated_tasks), force_undo))
|
|
708
|
+
had_undo = True
|
|
709
|
+
if n_tasks > 0:
|
|
710
|
+
tmp_log.debug(f"[dry run] {key_name:<64} {n_tasks:>3} preassigned tasks would be undone ({reason_str}) ")
|
|
711
|
+
else:
|
|
712
|
+
updated_tasks = self.taskBufferIF.undoPreassignedTasks_JEDI(
|
|
713
|
+
preassigned_tasks_cached, task_orig_attr_map=task_orig_attr_map, params_map=params_map, force=force_undo
|
|
714
|
+
)
|
|
715
|
+
if updated_tasks is None:
|
|
716
|
+
# dbproxy method failed
|
|
717
|
+
tmp_log.error(f"{key_name:<64} failed to undo preassigned tasks (force={force_undo})")
|
|
718
|
+
else:
|
|
719
|
+
had_undo = True
|
|
720
|
+
n_tasks = len(updated_tasks)
|
|
721
|
+
if n_tasks > 0:
|
|
722
|
+
tmp_log.info(f"{key_name:<64} {str(n_tasks):>3} preassigned tasks undone ({reason_str}) : {updated_tasks} ")
|
|
723
|
+
# Kibana log
|
|
724
|
+
for taskid in updated_tasks:
|
|
725
|
+
tmp_log.debug(
|
|
726
|
+
f"#ATM #KV jediTaskID={taskid} action=undo_preassign site={site} rtype={resource_type} un-preassigned since {reason_str}"
|
|
727
|
+
)
|
|
728
|
+
# update preassigned_tasks_map into cache
|
|
729
|
+
if had_undo:
|
|
730
|
+
if force_undo:
|
|
731
|
+
del preassigned_tasks_map[key_name]
|
|
732
|
+
else:
|
|
733
|
+
tmp_tasks_set = set(preassigned_tasks_cached) - set(updated_tasks)
|
|
734
|
+
if not tmp_tasks_set:
|
|
735
|
+
del preassigned_tasks_map[key_name]
|
|
736
|
+
else:
|
|
737
|
+
preassigned_tasks_map[key_name] = list(tmp_tasks_set)
|
|
738
|
+
self._update_to_pt_cache(preassigned_tasks_map)
|
|
739
|
+
# update blacklisted_tasks_map into cache
|
|
740
|
+
if had_undo and not force_undo:
|
|
741
|
+
blacklisted_tasks_map_orig = self._get_from_bt_cache()
|
|
742
|
+
blacklisted_tasks_map = copy.deepcopy(blacklisted_tasks_map_orig)
|
|
743
|
+
now_time = naive_utcnow()
|
|
744
|
+
now_rounded_ts = int(now_time.replace(minute=0, second=0, microsecond=0).timestamp())
|
|
745
|
+
ts_str = str(now_rounded_ts)
|
|
746
|
+
if ts_str in blacklisted_tasks_map_orig:
|
|
747
|
+
tmp_bt_list = blacklisted_tasks_map[ts_str]
|
|
748
|
+
blacklisted_tasks_map[ts_str] = list(set(tmp_bt_list) | set(updated_tasks))
|
|
749
|
+
else:
|
|
750
|
+
blacklisted_tasks_map[ts_str] = list(updated_tasks)
|
|
751
|
+
self._update_to_bt_cache(blacklisted_tasks_map)
|
|
752
|
+
|
|
753
|
+
# close and reassign jobs of preassigned tasks
|
|
754
|
+
def reassign_jobs(self, to_reassign_map):
|
|
755
|
+
tmp_log = MsgWrapper(logger, "reassign_jobs")
|
|
756
|
+
for jedi_taskid, value_map in to_reassign_map.items():
|
|
757
|
+
site = value_map["site"]
|
|
758
|
+
n_jobs_to_fill = value_map["n_jobs_to_fill"]
|
|
759
|
+
# compute n_jobs_to_close from n_jobs_to_fill
|
|
760
|
+
n_jobs_to_close = int(n_jobs_to_fill / 3)
|
|
761
|
+
# reassign
|
|
762
|
+
n_jobs_closed = self.taskBufferIF.reassignJobsInPreassignedTask_JEDI(jedi_taskid, site, n_jobs_to_close)
|
|
763
|
+
if n_jobs_closed is None:
|
|
764
|
+
tmp_log.debug(f"jediTaskID={jedi_taskid} no longer ready/running or not assigned to {site} , skipped")
|
|
765
|
+
else:
|
|
766
|
+
tmp_log.debug(f"jediTaskID={jedi_taskid} to {site} , closed {n_jobs_closed} jobs")
|
|
767
|
+
|
|
768
|
+
# main
|
|
769
|
+
def doAction(self):
|
|
770
|
+
try:
|
|
771
|
+
# get logger
|
|
772
|
+
origTmpLog = MsgWrapper(logger)
|
|
773
|
+
origTmpLog.debug("start")
|
|
774
|
+
# lock (also in DRY_RUN to avoid races on shared caches)
|
|
775
|
+
got_lock = self._get_lock()
|
|
776
|
+
if not got_lock:
|
|
777
|
+
origTmpLog.debug("locked by another process. Skipped")
|
|
778
|
+
return self.SC_SUCCEEDED
|
|
779
|
+
origTmpLog.debug("got lock")
|
|
780
|
+
# undo preassigned tasks
|
|
781
|
+
self.undo_preassign()
|
|
782
|
+
# preassign tasks to sites
|
|
783
|
+
ret_map = self.do_preassign()
|
|
784
|
+
# in non-dry-run mode, optionally wait and reassign jobs
|
|
785
|
+
if not DRY_RUN:
|
|
786
|
+
# to-reassign map
|
|
787
|
+
to_reassign_map = ret_map["to_reassign"]
|
|
788
|
+
if to_reassign_map:
|
|
789
|
+
# wait some minutes so that preassigned tasks can be brokered, before reassigning jobs
|
|
790
|
+
origTmpLog.debug(f"wait {reassign_jobs_wait_time}s before reassigning jobs")
|
|
791
|
+
time.sleep(reassign_jobs_wait_time)
|
|
792
|
+
# reassign jobs of preassigned tasks
|
|
793
|
+
self.reassign_jobs(to_reassign_map)
|
|
794
|
+
except Exception:
|
|
795
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
796
|
+
err_str = traceback.format_exc()
|
|
797
|
+
origTmpLog.error(f"failed with {errtype} {errvalue} ; {err_str}")
|
|
798
|
+
# return
|
|
799
|
+
origTmpLog.debug("done")
|
|
800
|
+
return self.SC_SUCCEEDED
|