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,2640 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import random
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
8
|
+
from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
|
|
9
|
+
|
|
10
|
+
from pandaserver.config import panda_config
|
|
11
|
+
from pandaserver.srvcore import CoreUtils, srv_msg_utils
|
|
12
|
+
from pandaserver.taskbuffer import EventServiceUtils, JobUtils
|
|
13
|
+
from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule
|
|
14
|
+
from pandaserver.taskbuffer.FileSpec import FileSpec
|
|
15
|
+
from pandaserver.taskbuffer.JobSpec import JobSpec
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Module class to define miscellaneous job-related methods that are independent of another module's methods
|
|
19
|
+
class JobStandaloneModule(BaseModule):
|
|
20
|
+
# constructor
|
|
21
|
+
def __init__(self, log_stream: LogWrapper):
|
|
22
|
+
super().__init__(log_stream)
|
|
23
|
+
|
|
24
|
+
# activate job. move job from jobsDefined to jobsActive
|
|
25
|
+
def activateJob(self, job):
|
|
26
|
+
comment = " /* DBProxy.activateJob */"
|
|
27
|
+
if job is None:
|
|
28
|
+
tmp_id = None
|
|
29
|
+
else:
|
|
30
|
+
tmp_id = job.PandaID
|
|
31
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={tmp_id}")
|
|
32
|
+
updatedFlag = False
|
|
33
|
+
if job is None:
|
|
34
|
+
tmp_log.debug("skip job=None")
|
|
35
|
+
return True
|
|
36
|
+
tmp_log.debug("start")
|
|
37
|
+
sql0 = "SELECT row_ID FROM ATLAS_PANDA.filesTable4 WHERE PandaID=:PandaID AND type=:type AND NOT status IN (:status1,:status2) "
|
|
38
|
+
sql1 = "DELETE FROM ATLAS_PANDA.jobsDefined4 "
|
|
39
|
+
sql1 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2) AND commandToPilot IS NULL"
|
|
40
|
+
sql2 = f"INSERT INTO ATLAS_PANDA.jobsActive4 ({JobSpec.columnNames()}) "
|
|
41
|
+
sql2 += JobSpec.bindValuesExpression()
|
|
42
|
+
# host and time information
|
|
43
|
+
job.modificationTime = naive_utcnow()
|
|
44
|
+
# set stateChangeTime for defined->activated but not for assigned->activated
|
|
45
|
+
if job.jobStatus in ["defined"]:
|
|
46
|
+
job.stateChangeTime = job.modificationTime
|
|
47
|
+
nTry = 3
|
|
48
|
+
to_push = False
|
|
49
|
+
for iTry in range(nTry):
|
|
50
|
+
try:
|
|
51
|
+
# check if all files are ready
|
|
52
|
+
allOK = True
|
|
53
|
+
for file in job.Files:
|
|
54
|
+
if file.type == "input" and file.status not in ["ready", "cached"]:
|
|
55
|
+
allOK = False
|
|
56
|
+
break
|
|
57
|
+
# begin transaction
|
|
58
|
+
self.conn.begin()
|
|
59
|
+
# check all inputs are ready
|
|
60
|
+
varMap = {}
|
|
61
|
+
varMap[":type"] = "input"
|
|
62
|
+
varMap[":status1"] = "ready"
|
|
63
|
+
varMap[":status2"] = "cached"
|
|
64
|
+
varMap[":PandaID"] = job.PandaID
|
|
65
|
+
self.cur.arraysize = 100
|
|
66
|
+
self.cur.execute(sql0 + comment, varMap)
|
|
67
|
+
res = self.cur.fetchall()
|
|
68
|
+
if len(res) == 0 or allOK:
|
|
69
|
+
# check resource share
|
|
70
|
+
job.jobStatus = "activated"
|
|
71
|
+
|
|
72
|
+
# delete
|
|
73
|
+
varMap = {}
|
|
74
|
+
varMap[":PandaID"] = job.PandaID
|
|
75
|
+
varMap[":oldJobStatus1"] = "assigned"
|
|
76
|
+
varMap[":oldJobStatus2"] = "defined"
|
|
77
|
+
self.cur.execute(sql1 + comment, varMap)
|
|
78
|
+
n = self.cur.rowcount
|
|
79
|
+
if n == 0:
|
|
80
|
+
# already killed or activated
|
|
81
|
+
tmp_log.debug("Job not found to activate")
|
|
82
|
+
else:
|
|
83
|
+
# insert
|
|
84
|
+
self.cur.execute(sql2 + comment, job.valuesMap())
|
|
85
|
+
# update files
|
|
86
|
+
for file in job.Files:
|
|
87
|
+
sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
|
|
88
|
+
varMap = file.valuesMap(onlyChanged=True)
|
|
89
|
+
if varMap != {}:
|
|
90
|
+
varMap[":row_ID"] = file.row_ID
|
|
91
|
+
tmp_log.debug(sqlF + comment + str(varMap))
|
|
92
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
93
|
+
# job parameters
|
|
94
|
+
sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
|
|
95
|
+
varMap = {}
|
|
96
|
+
varMap[":PandaID"] = job.PandaID
|
|
97
|
+
varMap[":param"] = job.jobParameters
|
|
98
|
+
self.cur.execute(sqlJob + comment, varMap)
|
|
99
|
+
updatedFlag = True
|
|
100
|
+
to_push = job.is_push_job()
|
|
101
|
+
else:
|
|
102
|
+
# update job
|
|
103
|
+
sqlJ = (
|
|
104
|
+
f"UPDATE ATLAS_PANDA.jobsDefined4 SET {job.bindUpdateChangesExpression()} "
|
|
105
|
+
) + "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2)"
|
|
106
|
+
varMap = job.valuesMap(onlyChanged=True)
|
|
107
|
+
varMap[":PandaID"] = job.PandaID
|
|
108
|
+
varMap[":oldJobStatus1"] = "assigned"
|
|
109
|
+
varMap[":oldJobStatus2"] = "defined"
|
|
110
|
+
tmp_log.debug(sqlJ + comment + str(varMap))
|
|
111
|
+
self.cur.execute(sqlJ + comment, varMap)
|
|
112
|
+
n = self.cur.rowcount
|
|
113
|
+
if n == 0:
|
|
114
|
+
# already killed or activated
|
|
115
|
+
tmp_log.debug("Job not found to update")
|
|
116
|
+
else:
|
|
117
|
+
# update files
|
|
118
|
+
for file in job.Files:
|
|
119
|
+
sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
|
|
120
|
+
varMap = file.valuesMap(onlyChanged=True)
|
|
121
|
+
if varMap != {}:
|
|
122
|
+
varMap[":row_ID"] = file.row_ID
|
|
123
|
+
tmp_log.debug(sqlF + comment + str(varMap))
|
|
124
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
125
|
+
# job parameters
|
|
126
|
+
sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
|
|
127
|
+
varMap = {}
|
|
128
|
+
varMap[":PandaID"] = job.PandaID
|
|
129
|
+
varMap[":param"] = job.jobParameters
|
|
130
|
+
self.cur.execute(sqlJob + comment, varMap)
|
|
131
|
+
updatedFlag = True
|
|
132
|
+
# commit
|
|
133
|
+
if not self._commit():
|
|
134
|
+
raise RuntimeError("Commit error")
|
|
135
|
+
# record status change
|
|
136
|
+
try:
|
|
137
|
+
if updatedFlag:
|
|
138
|
+
self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
|
|
139
|
+
except Exception:
|
|
140
|
+
tmp_log.error("recordStatusChange failed")
|
|
141
|
+
self.push_job_status_message(job, job.PandaID, job.jobStatus)
|
|
142
|
+
# push job
|
|
143
|
+
if to_push:
|
|
144
|
+
mb_proxy_queue = self.get_mb_proxy("panda_pilot_queue")
|
|
145
|
+
mb_proxy_topic = self.get_mb_proxy("panda_pilot_topic")
|
|
146
|
+
if mb_proxy_queue and mb_proxy_topic:
|
|
147
|
+
tmp_log.debug("push job")
|
|
148
|
+
srv_msg_utils.send_job_message(mb_proxy_queue, mb_proxy_topic, job.jediTaskID, job.PandaID)
|
|
149
|
+
else:
|
|
150
|
+
tmp_log.debug("message queue/topic not configured")
|
|
151
|
+
tmp_log.debug("done")
|
|
152
|
+
return True
|
|
153
|
+
except Exception as e:
|
|
154
|
+
# roll back
|
|
155
|
+
self._rollback()
|
|
156
|
+
if iTry + 1 < nTry:
|
|
157
|
+
tmp_log.debug(f"retry: {iTry}")
|
|
158
|
+
time.sleep(random.randint(10, 20))
|
|
159
|
+
continue
|
|
160
|
+
self.dump_error_message(tmp_log)
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
# send job to jobsWaiting
|
|
164
|
+
def keepJob(self, job):
|
|
165
|
+
comment = " /* DBProxy.keepJob */"
|
|
166
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={job.PandaID}")
|
|
167
|
+
tmp_log.debug("start")
|
|
168
|
+
# set status
|
|
169
|
+
job.jobStatus = "waiting"
|
|
170
|
+
sql1 = f"UPDATE ATLAS_PANDA.jobsDefined4 SET {job.bindUpdateChangesExpression()} "
|
|
171
|
+
sql1 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2) AND commandToPilot IS NULL"
|
|
172
|
+
# time information
|
|
173
|
+
job.modificationTime = naive_utcnow()
|
|
174
|
+
job.stateChangeTime = job.modificationTime
|
|
175
|
+
updatedFlag = False
|
|
176
|
+
nTry = 3
|
|
177
|
+
for iTry in range(nTry):
|
|
178
|
+
try:
|
|
179
|
+
# begin transaction
|
|
180
|
+
self.conn.begin()
|
|
181
|
+
# update
|
|
182
|
+
varMap = job.valuesMap(onlyChanged=True)
|
|
183
|
+
varMap[":PandaID"] = job.PandaID
|
|
184
|
+
varMap[":oldJobStatus1"] = "assigned"
|
|
185
|
+
varMap[":oldJobStatus2"] = "defined"
|
|
186
|
+
self.cur.execute(sql1 + comment, varMap)
|
|
187
|
+
n = self.cur.rowcount
|
|
188
|
+
if n == 0:
|
|
189
|
+
# already killed
|
|
190
|
+
tmp_log.debug(f"Not found")
|
|
191
|
+
else:
|
|
192
|
+
# update files
|
|
193
|
+
for file in job.Files:
|
|
194
|
+
sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
|
|
195
|
+
varMap = file.valuesMap(onlyChanged=True)
|
|
196
|
+
if varMap != {}:
|
|
197
|
+
varMap[":row_ID"] = file.row_ID
|
|
198
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
199
|
+
# update parameters
|
|
200
|
+
sqlJob = "UPDATE ATLAS_PANDA.jobParamsTable SET jobParameters=:param WHERE PandaID=:PandaID"
|
|
201
|
+
varMap = {}
|
|
202
|
+
varMap[":PandaID"] = job.PandaID
|
|
203
|
+
varMap[":param"] = job.jobParameters
|
|
204
|
+
self.cur.execute(sqlJob + comment, varMap)
|
|
205
|
+
updatedFlag = True
|
|
206
|
+
# commit
|
|
207
|
+
if not self._commit():
|
|
208
|
+
raise RuntimeError("Commit error")
|
|
209
|
+
# record status change
|
|
210
|
+
try:
|
|
211
|
+
if updatedFlag:
|
|
212
|
+
self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
|
|
213
|
+
self.push_job_status_message(job, job.PandaID, job.jobStatus)
|
|
214
|
+
except Exception:
|
|
215
|
+
tmp_log.error("recordStatusChange in keepJob")
|
|
216
|
+
return True
|
|
217
|
+
except Exception:
|
|
218
|
+
# roll back
|
|
219
|
+
self._rollback()
|
|
220
|
+
if iTry + 1 < nTry:
|
|
221
|
+
tmp_log.debug(f"retry : {iTry}")
|
|
222
|
+
time.sleep(random.randint(10, 20))
|
|
223
|
+
continue
|
|
224
|
+
# dump error
|
|
225
|
+
self.dump_error_message(tmp_log)
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
# reset job in jobsActive
|
|
229
|
+
def resetJob(
|
|
230
|
+
self,
|
|
231
|
+
pandaID,
|
|
232
|
+
activeTable=True,
|
|
233
|
+
keepSite=False,
|
|
234
|
+
getOldSubs=False,
|
|
235
|
+
forPending=True,
|
|
236
|
+
):
|
|
237
|
+
comment = " /* DBProxy.resetJob */"
|
|
238
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
239
|
+
tmp_log.debug(f"activeTable={activeTable}")
|
|
240
|
+
# select table
|
|
241
|
+
table = "ATLAS_PANDA.jobsActive4"
|
|
242
|
+
sql1 = f"SELECT {JobSpec.columnNames()} FROM {table} "
|
|
243
|
+
sql1 += "WHERE PandaID=:PandaID"
|
|
244
|
+
sql2 = f"DELETE FROM {table} "
|
|
245
|
+
sql2 += "WHERE PandaID=:PandaID AND (jobStatus=:oldJobStatus1 OR jobStatus=:oldJobStatus2)"
|
|
246
|
+
sql3 = f"INSERT INTO ATLAS_PANDA.jobsDefined4 ({JobSpec.columnNames()}) "
|
|
247
|
+
sql3 += JobSpec.bindValuesExpression()
|
|
248
|
+
try:
|
|
249
|
+
# transaction causes Request ndbd time-out in ATLAS_PANDA.jobsActive4
|
|
250
|
+
self.conn.begin()
|
|
251
|
+
# select
|
|
252
|
+
varMap = {}
|
|
253
|
+
varMap[":PandaID"] = pandaID
|
|
254
|
+
self.cur.arraysize = 10
|
|
255
|
+
self.cur.execute(sql1 + comment, varMap)
|
|
256
|
+
res = self.cur.fetchone()
|
|
257
|
+
# not found
|
|
258
|
+
if res is None:
|
|
259
|
+
# commit
|
|
260
|
+
if not self._commit():
|
|
261
|
+
raise RuntimeError("Commit error")
|
|
262
|
+
# return
|
|
263
|
+
return None
|
|
264
|
+
# instantiate Job
|
|
265
|
+
job = JobSpec()
|
|
266
|
+
job.pack(res)
|
|
267
|
+
# if already running
|
|
268
|
+
if job.jobStatus != "waiting" and job.jobStatus != "activated" and (forPending and job.jobStatus != "pending"):
|
|
269
|
+
# commit
|
|
270
|
+
if not self._commit():
|
|
271
|
+
raise RuntimeError("Commit error")
|
|
272
|
+
# return
|
|
273
|
+
return None
|
|
274
|
+
# do nothing for analysis jobs
|
|
275
|
+
if job.prodSourceLabel in ["user", "panda"] and not forPending and job.jobStatus != "pending":
|
|
276
|
+
# commit
|
|
277
|
+
if not self._commit():
|
|
278
|
+
raise RuntimeError("Commit error")
|
|
279
|
+
# return
|
|
280
|
+
return None
|
|
281
|
+
# delete
|
|
282
|
+
varMap = {}
|
|
283
|
+
varMap[":PandaID"] = pandaID
|
|
284
|
+
if not forPending:
|
|
285
|
+
varMap[":oldJobStatus1"] = "waiting"
|
|
286
|
+
else:
|
|
287
|
+
varMap[":oldJobStatus1"] = "pending"
|
|
288
|
+
varMap[":oldJobStatus2"] = "activated"
|
|
289
|
+
self.cur.execute(sql2 + comment, varMap)
|
|
290
|
+
retD = self.cur.rowcount
|
|
291
|
+
# delete failed
|
|
292
|
+
tmp_log.debug(f"retD = {retD}")
|
|
293
|
+
if retD != 1:
|
|
294
|
+
# commit
|
|
295
|
+
if not self._commit():
|
|
296
|
+
raise RuntimeError("Commit error")
|
|
297
|
+
return None
|
|
298
|
+
# delete from jobsDefined4 just in case
|
|
299
|
+
varMap = {}
|
|
300
|
+
varMap[":PandaID"] = pandaID
|
|
301
|
+
sqlD = "DELETE FROM ATLAS_PANDA.jobsDefined4 WHERE PandaID=:PandaID"
|
|
302
|
+
self.cur.execute(sqlD + comment, varMap)
|
|
303
|
+
# increase priority
|
|
304
|
+
if job.jobStatus == "activated" and job.currentPriority < 100:
|
|
305
|
+
job.currentPriority = 100
|
|
306
|
+
# reset computing site and dispatchDBlocks
|
|
307
|
+
job.jobStatus = "defined"
|
|
308
|
+
if job.prodSourceLabel not in ["user", "panda"]:
|
|
309
|
+
job.dispatchDBlock = None
|
|
310
|
+
# erase old assignment
|
|
311
|
+
if (not keepSite) and job.relocationFlag not in [1, 2]:
|
|
312
|
+
job.computingSite = None
|
|
313
|
+
job.computingElement = None
|
|
314
|
+
# host and time information
|
|
315
|
+
job.modificationHost = self.hostname
|
|
316
|
+
job.modificationTime = naive_utcnow()
|
|
317
|
+
job.stateChangeTime = job.modificationTime
|
|
318
|
+
# reset
|
|
319
|
+
job.brokerageErrorDiag = None
|
|
320
|
+
job.brokerageErrorCode = None
|
|
321
|
+
# insert
|
|
322
|
+
self.cur.execute(sql3 + comment, job.valuesMap())
|
|
323
|
+
# job parameters
|
|
324
|
+
sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
|
|
325
|
+
self.cur.execute(sqlJobP + comment, varMap)
|
|
326
|
+
for (clobJobP,) in self.cur:
|
|
327
|
+
try:
|
|
328
|
+
job.jobParameters = clobJobP.read()
|
|
329
|
+
except AttributeError:
|
|
330
|
+
job.jobParameters = str(clobJobP)
|
|
331
|
+
break
|
|
332
|
+
# Files
|
|
333
|
+
oldSubList = []
|
|
334
|
+
sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
|
|
335
|
+
sqlFile += "WHERE PandaID=:PandaID"
|
|
336
|
+
self.cur.arraysize = 10000
|
|
337
|
+
self.cur.execute(sqlFile + comment, varMap)
|
|
338
|
+
resFs = self.cur.fetchall()
|
|
339
|
+
for resF in resFs:
|
|
340
|
+
file = FileSpec()
|
|
341
|
+
file.pack(resF)
|
|
342
|
+
# reset GUID to trigger LRC/LFC scanning
|
|
343
|
+
if file.status == "missing":
|
|
344
|
+
file.GUID = None
|
|
345
|
+
# collect old subs
|
|
346
|
+
if job.prodSourceLabel in ["managed", "test"] and file.type in ["output", "log"] and re.search("_sub\d+$", file.destinationDBlock) is not None:
|
|
347
|
+
if file.destinationDBlock not in oldSubList:
|
|
348
|
+
oldSubList.append(file.destinationDBlock)
|
|
349
|
+
# reset status, destinationDBlock and dispatchDBlock
|
|
350
|
+
if job.lockedby != "jedi":
|
|
351
|
+
file.status = "unknown"
|
|
352
|
+
if job.prodSourceLabel not in ["user", "panda"]:
|
|
353
|
+
file.dispatchDBlock = None
|
|
354
|
+
file.destinationDBlock = re.sub("_sub\d+$", "", file.destinationDBlock)
|
|
355
|
+
# add file
|
|
356
|
+
job.addFile(file)
|
|
357
|
+
# update files
|
|
358
|
+
sqlF = f"UPDATE ATLAS_PANDA.filesTable4 SET {file.bindUpdateChangesExpression()}" + "WHERE row_ID=:row_ID"
|
|
359
|
+
varMap = file.valuesMap(onlyChanged=True)
|
|
360
|
+
if varMap != {}:
|
|
361
|
+
varMap[":row_ID"] = file.row_ID
|
|
362
|
+
tmp_log.debug(sqlF + comment + str(varMap))
|
|
363
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
364
|
+
# commit
|
|
365
|
+
if not self._commit():
|
|
366
|
+
raise RuntimeError("Commit error")
|
|
367
|
+
# record status change
|
|
368
|
+
try:
|
|
369
|
+
self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
|
|
370
|
+
except Exception:
|
|
371
|
+
tmp_log.error("recordStatusChange in resetJobs")
|
|
372
|
+
self.push_job_status_message(job, job.PandaID, job.jobStatus)
|
|
373
|
+
tmp_log.debug(f"done with {job is not None}")
|
|
374
|
+
if getOldSubs:
|
|
375
|
+
return job, oldSubList
|
|
376
|
+
return job
|
|
377
|
+
except Exception:
|
|
378
|
+
# roll back
|
|
379
|
+
self._rollback()
|
|
380
|
+
# error report
|
|
381
|
+
self.dump_error_message(tmp_log)
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
# reset jobs in jobsDefined
|
|
385
|
+
def resetDefinedJob(self, pandaID):
|
|
386
|
+
comment = " /* DBProxy.resetDefinedJob */"
|
|
387
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
388
|
+
tmp_log.debug("start")
|
|
389
|
+
sql1 = "UPDATE ATLAS_PANDA.jobsDefined4 SET "
|
|
390
|
+
sql1 += "jobStatus=:newJobStatus,"
|
|
391
|
+
sql1 += "modificationTime=CURRENT_DATE,"
|
|
392
|
+
sql1 += "modificationHost=:modificationHost"
|
|
393
|
+
sql1 += " WHERE PandaID=:PandaID AND jobStatus IN (:oldJobStatus1,:oldJobStatus2,:oldJobStatus3) "
|
|
394
|
+
sql2 = f"SELECT {JobSpec.columnNames()} FROM ATLAS_PANDA.jobsDefined4 "
|
|
395
|
+
sql2 += "WHERE PandaID=:PandaID"
|
|
396
|
+
try:
|
|
397
|
+
# begin transaction
|
|
398
|
+
self.conn.begin()
|
|
399
|
+
# update
|
|
400
|
+
varMap = {}
|
|
401
|
+
varMap[":PandaID"] = pandaID
|
|
402
|
+
varMap[":newJobStatus"] = "defined"
|
|
403
|
+
varMap[":oldJobStatus1"] = "assigned"
|
|
404
|
+
varMap[":oldJobStatus2"] = "defined"
|
|
405
|
+
varMap[":oldJobStatus3"] = "pending"
|
|
406
|
+
varMap[":modificationHost"] = self.hostname
|
|
407
|
+
self.cur.execute(sql1 + comment, varMap)
|
|
408
|
+
retU = self.cur.rowcount
|
|
409
|
+
# not found
|
|
410
|
+
updatedFlag = True
|
|
411
|
+
job = None
|
|
412
|
+
if retU == 0:
|
|
413
|
+
tmp_log.debug("Not found for UPDATE")
|
|
414
|
+
updatedFlag = False
|
|
415
|
+
else:
|
|
416
|
+
# select
|
|
417
|
+
varMap = {}
|
|
418
|
+
varMap[":PandaID"] = pandaID
|
|
419
|
+
self.cur.arraysize = 10
|
|
420
|
+
self.cur.execute(sql2 + comment, varMap)
|
|
421
|
+
res = self.cur.fetchone()
|
|
422
|
+
# not found
|
|
423
|
+
if res is None:
|
|
424
|
+
raise RuntimeError(f"Not found for SELECT")
|
|
425
|
+
# instantiate Job
|
|
426
|
+
job = JobSpec()
|
|
427
|
+
job.pack(res)
|
|
428
|
+
# job parameters
|
|
429
|
+
sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
|
|
430
|
+
self.cur.execute(sqlJobP + comment, varMap)
|
|
431
|
+
for (clobJobP,) in self.cur:
|
|
432
|
+
try:
|
|
433
|
+
job.jobParameters = clobJobP.read()
|
|
434
|
+
except AttributeError:
|
|
435
|
+
job.jobParameters = str(clobJobP)
|
|
436
|
+
break
|
|
437
|
+
# Files
|
|
438
|
+
sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
|
|
439
|
+
sqlFile += "WHERE PandaID=:PandaID"
|
|
440
|
+
self.cur.arraysize = 10000
|
|
441
|
+
self.cur.execute(sqlFile + comment, varMap)
|
|
442
|
+
resFs = self.cur.fetchall()
|
|
443
|
+
for resF in resFs:
|
|
444
|
+
file = FileSpec()
|
|
445
|
+
file.pack(resF)
|
|
446
|
+
# add file
|
|
447
|
+
job.addFile(file)
|
|
448
|
+
# commit
|
|
449
|
+
if not self._commit():
|
|
450
|
+
raise RuntimeError("Commit error")
|
|
451
|
+
# record status change
|
|
452
|
+
try:
|
|
453
|
+
if updatedFlag:
|
|
454
|
+
self.recordStatusChange(job.PandaID, job.jobStatus, jobInfo=job)
|
|
455
|
+
self.push_job_status_message(job, job.PandaID, job.jobStatus)
|
|
456
|
+
except Exception:
|
|
457
|
+
tmp_log.error("recordStatusChange in resetDefinedJobs")
|
|
458
|
+
tmp_log.debug(f"done with {job is not None}")
|
|
459
|
+
return job
|
|
460
|
+
except Exception:
|
|
461
|
+
self.dump_error_message(tmp_log)
|
|
462
|
+
# roll back
|
|
463
|
+
self._rollback()
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
# peek at job
|
|
467
|
+
def peekJob(self, pandaID, fromDefined, fromActive, fromArchived, fromWaiting, forAnal=False):
|
|
468
|
+
comment = " /* DBProxy.peekJob */"
|
|
469
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
470
|
+
# return None for NULL PandaID
|
|
471
|
+
if pandaID in ["NULL", "", "None", None]:
|
|
472
|
+
return None
|
|
473
|
+
# only int
|
|
474
|
+
try:
|
|
475
|
+
_ = int(pandaID)
|
|
476
|
+
except Exception:
|
|
477
|
+
tmp_log.debug(f"return None for {pandaID}:non-integer")
|
|
478
|
+
return None
|
|
479
|
+
sql1_0 = "SELECT %s FROM %s "
|
|
480
|
+
sql1_1 = "WHERE PandaID=:PandaID"
|
|
481
|
+
nTry = 3
|
|
482
|
+
for iTry in range(nTry):
|
|
483
|
+
try:
|
|
484
|
+
tables = []
|
|
485
|
+
if fromDefined or fromWaiting:
|
|
486
|
+
tables.append("ATLAS_PANDA.jobsDefined4")
|
|
487
|
+
if fromActive:
|
|
488
|
+
tables.append("ATLAS_PANDA.jobsActive4")
|
|
489
|
+
if fromArchived:
|
|
490
|
+
tables.append("ATLAS_PANDA.jobsArchived4")
|
|
491
|
+
if fromDefined:
|
|
492
|
+
# for jobs which are just reset
|
|
493
|
+
tables.append("ATLAS_PANDA.jobsDefined4")
|
|
494
|
+
# select
|
|
495
|
+
varMap = {}
|
|
496
|
+
varMap[":PandaID"] = pandaID
|
|
497
|
+
for table in tables:
|
|
498
|
+
# start transaction
|
|
499
|
+
self.conn.begin()
|
|
500
|
+
# select
|
|
501
|
+
sql = sql1_0 % (JobSpec.columnNames(), table) + sql1_1
|
|
502
|
+
self.cur.arraysize = 10
|
|
503
|
+
self.cur.execute(sql + comment, varMap)
|
|
504
|
+
res = self.cur.fetchall()
|
|
505
|
+
# commit
|
|
506
|
+
if not self._commit():
|
|
507
|
+
raise RuntimeError("Commit error")
|
|
508
|
+
if len(res) != 0:
|
|
509
|
+
# Job
|
|
510
|
+
job = JobSpec()
|
|
511
|
+
job.pack(res[0])
|
|
512
|
+
# Files
|
|
513
|
+
# start transaction
|
|
514
|
+
self.conn.begin()
|
|
515
|
+
# select
|
|
516
|
+
sqlFile = f"SELECT {FileSpec.columnNames()} FROM ATLAS_PANDA.filesTable4 "
|
|
517
|
+
sqlFile += "WHERE PandaID=:PandaID"
|
|
518
|
+
self.cur.arraysize = 10000
|
|
519
|
+
self.cur.execute(sqlFile + comment, varMap)
|
|
520
|
+
resFs = self.cur.fetchall()
|
|
521
|
+
# metadata
|
|
522
|
+
resMeta = None
|
|
523
|
+
if table == "ATLAS_PANDA.jobsArchived4" or forAnal:
|
|
524
|
+
# read metadata only for finished/failed production jobs
|
|
525
|
+
sqlMeta = "SELECT metaData FROM ATLAS_PANDA.metaTable WHERE PandaID=:PandaID"
|
|
526
|
+
self.cur.execute(sqlMeta + comment, varMap)
|
|
527
|
+
for (clobMeta,) in self.cur:
|
|
528
|
+
if clobMeta is not None:
|
|
529
|
+
try:
|
|
530
|
+
resMeta = clobMeta.read()
|
|
531
|
+
except AttributeError:
|
|
532
|
+
resMeta = str(clobMeta)
|
|
533
|
+
break
|
|
534
|
+
# job parameters
|
|
535
|
+
job.jobParameters = None
|
|
536
|
+
sqlJobP = "SELECT jobParameters FROM ATLAS_PANDA.jobParamsTable WHERE PandaID=:PandaID"
|
|
537
|
+
varMap = {}
|
|
538
|
+
varMap[":PandaID"] = job.PandaID
|
|
539
|
+
self.cur.execute(sqlJobP + comment, varMap)
|
|
540
|
+
for (clobJobP,) in self.cur:
|
|
541
|
+
if clobJobP is not None:
|
|
542
|
+
try:
|
|
543
|
+
job.jobParameters = clobJobP.read()
|
|
544
|
+
except AttributeError:
|
|
545
|
+
job.jobParameters = str(clobJobP)
|
|
546
|
+
break
|
|
547
|
+
# commit
|
|
548
|
+
if not self._commit():
|
|
549
|
+
raise RuntimeError("Commit error")
|
|
550
|
+
# set files
|
|
551
|
+
for resF in resFs:
|
|
552
|
+
file = FileSpec()
|
|
553
|
+
file.pack(resF)
|
|
554
|
+
job.addFile(file)
|
|
555
|
+
# set metadata
|
|
556
|
+
job.metadata = resMeta
|
|
557
|
+
return job
|
|
558
|
+
tmp_log.debug(f"not found")
|
|
559
|
+
return None
|
|
560
|
+
except Exception:
|
|
561
|
+
# roll back
|
|
562
|
+
self._rollback()
|
|
563
|
+
if iTry + 1 < nTry:
|
|
564
|
+
tmp_log.debug(f"retry : {iTry}")
|
|
565
|
+
time.sleep(random.randint(10, 20))
|
|
566
|
+
continue
|
|
567
|
+
self.dump_error_message(tmp_log)
|
|
568
|
+
# return None for analysis
|
|
569
|
+
if forAnal:
|
|
570
|
+
return None
|
|
571
|
+
# return 'unknown'
|
|
572
|
+
job = JobSpec()
|
|
573
|
+
job.PandaID = pandaID
|
|
574
|
+
job.jobStatus = "unknown"
|
|
575
|
+
return job
|
|
576
|
+
|
|
577
|
+
# get express jobs
|
|
578
|
+
def getExpressJobs(self, dn):
|
|
579
|
+
comment = " /* DBProxy.getExpressJobs */"
|
|
580
|
+
tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
|
|
581
|
+
tmp_log.debug(f"start")
|
|
582
|
+
sqlX = "SELECT specialHandling,COUNT(*) FROM %s "
|
|
583
|
+
sqlX += "WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel1 "
|
|
584
|
+
sqlX += "AND specialHandling IS NOT NULL "
|
|
585
|
+
sqlXJob = "SELECT PandaID,jobStatus,prodSourceLabel,modificationTime,jobDefinitionID,jobsetID,startTime,endTime FROM %s "
|
|
586
|
+
sqlXJob += "WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel1 "
|
|
587
|
+
sqlXJob += "AND specialHandling IS NOT NULL AND specialHandling=:specialHandling "
|
|
588
|
+
sqlQ = sqlX
|
|
589
|
+
sqlQ += "GROUP BY specialHandling "
|
|
590
|
+
sqlQJob = sqlXJob
|
|
591
|
+
sqlA = sqlX
|
|
592
|
+
sqlA += "AND modificationTime>:modificationTime GROUP BY specialHandling "
|
|
593
|
+
sqlAJob = sqlXJob
|
|
594
|
+
sqlAJob += "AND modificationTime>:modificationTime "
|
|
595
|
+
try:
|
|
596
|
+
# get compact DN
|
|
597
|
+
compactDN = CoreUtils.clean_user_id(dn)
|
|
598
|
+
if compactDN in ["", "NULL", None]:
|
|
599
|
+
compactDN = dn
|
|
600
|
+
expressStr = "express"
|
|
601
|
+
activeExpressU = []
|
|
602
|
+
timeUsageU = datetime.timedelta(0)
|
|
603
|
+
executionTimeU = datetime.timedelta(hours=1)
|
|
604
|
+
jobCreditU = 3
|
|
605
|
+
timeCreditU = executionTimeU * jobCreditU
|
|
606
|
+
timeNow = naive_utcnow()
|
|
607
|
+
timeLimit = timeNow - datetime.timedelta(hours=6)
|
|
608
|
+
# loop over tables
|
|
609
|
+
for table in [
|
|
610
|
+
"ATLAS_PANDA.jobsDefined4",
|
|
611
|
+
"ATLAS_PANDA.jobsActive4",
|
|
612
|
+
"ATLAS_PANDA.jobsArchived4",
|
|
613
|
+
]:
|
|
614
|
+
varMap = {}
|
|
615
|
+
varMap[":prodUserName"] = compactDN
|
|
616
|
+
varMap[":prodSourceLabel1"] = "user"
|
|
617
|
+
if table == "ATLAS_PANDA.jobsArchived4":
|
|
618
|
+
varMap[":modificationTime"] = timeLimit
|
|
619
|
+
sql = sqlA % table
|
|
620
|
+
sqlJob = sqlAJob % table
|
|
621
|
+
else:
|
|
622
|
+
sql = sqlQ % table
|
|
623
|
+
sqlJob = sqlQJob % table
|
|
624
|
+
# start transaction
|
|
625
|
+
self.conn.begin()
|
|
626
|
+
# get the number of jobs for each specialHandling
|
|
627
|
+
self.cur.arraysize = 10
|
|
628
|
+
tmp_log.debug(sql + comment + str(varMap))
|
|
629
|
+
self.cur.execute(sql + comment, varMap)
|
|
630
|
+
res = self.cur.fetchall()
|
|
631
|
+
tmp_log.debug(f"{str(res)}")
|
|
632
|
+
for specialHandling, countJobs in res:
|
|
633
|
+
if specialHandling is None:
|
|
634
|
+
continue
|
|
635
|
+
# look for express jobs
|
|
636
|
+
if expressStr in specialHandling:
|
|
637
|
+
varMap[":specialHandling"] = specialHandling
|
|
638
|
+
self.cur.arraysize = 1000
|
|
639
|
+
self.cur.execute(sqlJob + comment, varMap)
|
|
640
|
+
resJobs = self.cur.fetchall()
|
|
641
|
+
tmp_log.debug(f"{str(resJobs)}")
|
|
642
|
+
for (
|
|
643
|
+
tmp_PandaID,
|
|
644
|
+
tmp_jobStatus,
|
|
645
|
+
tmp_prodSourceLabel,
|
|
646
|
+
tmp_modificationTime,
|
|
647
|
+
tmp_jobDefinitionID,
|
|
648
|
+
tmp_jobsetID,
|
|
649
|
+
tmp_startTime,
|
|
650
|
+
tmp_endTime,
|
|
651
|
+
) in resJobs:
|
|
652
|
+
# collect active jobs
|
|
653
|
+
if tmp_jobStatus not in [
|
|
654
|
+
"finished",
|
|
655
|
+
"failed",
|
|
656
|
+
"cancelled",
|
|
657
|
+
"closed",
|
|
658
|
+
]:
|
|
659
|
+
activeExpressU.append((tmp_PandaID, tmp_jobsetID, tmp_jobDefinitionID))
|
|
660
|
+
# get time usage
|
|
661
|
+
if tmp_jobStatus not in ["defined", "activated"]:
|
|
662
|
+
# check only jobs which actually use or used CPU on WN
|
|
663
|
+
if tmp_startTime is not None:
|
|
664
|
+
# running or not
|
|
665
|
+
if tmp_endTime is None:
|
|
666
|
+
# job got started before/after the time limit
|
|
667
|
+
if timeLimit > tmp_startTime:
|
|
668
|
+
timeDelta = timeNow - timeLimit
|
|
669
|
+
else:
|
|
670
|
+
timeDelta = timeNow - tmp_startTime
|
|
671
|
+
else:
|
|
672
|
+
# job got started before/after the time limit
|
|
673
|
+
if timeLimit > tmp_startTime:
|
|
674
|
+
timeDelta = tmp_endTime - timeLimit
|
|
675
|
+
else:
|
|
676
|
+
timeDelta = tmp_endTime - tmp_startTime
|
|
677
|
+
# add
|
|
678
|
+
if timeDelta > datetime.timedelta(0):
|
|
679
|
+
timeUsageU += timeDelta
|
|
680
|
+
# commit
|
|
681
|
+
if not self._commit():
|
|
682
|
+
raise RuntimeError("Commit error")
|
|
683
|
+
# check quota
|
|
684
|
+
rRet = True
|
|
685
|
+
rRetStr = ""
|
|
686
|
+
rQuota = 0
|
|
687
|
+
if len(activeExpressU) >= jobCreditU:
|
|
688
|
+
rRetStr += f"The number of queued runXYZ exceeds the limit = {jobCreditU}. "
|
|
689
|
+
rRet = False
|
|
690
|
+
if timeUsageU >= timeCreditU:
|
|
691
|
+
rRetStr += f"The total execution time for runXYZ exceeds the limit = {timeCreditU.seconds / 60} min. "
|
|
692
|
+
rRet = False
|
|
693
|
+
# calculate available quota
|
|
694
|
+
if rRet:
|
|
695
|
+
tmpQuota = jobCreditU - len(activeExpressU) - timeUsageU.seconds / executionTimeU.seconds
|
|
696
|
+
if tmpQuota < 0:
|
|
697
|
+
rRetStr += "Quota for runXYZ exceeds. "
|
|
698
|
+
rRet = False
|
|
699
|
+
else:
|
|
700
|
+
rQuota = tmpQuota
|
|
701
|
+
# return
|
|
702
|
+
retVal = {
|
|
703
|
+
"status": rRet,
|
|
704
|
+
"quota": rQuota,
|
|
705
|
+
"output": rRetStr,
|
|
706
|
+
"usage": timeUsageU,
|
|
707
|
+
"jobs": activeExpressU,
|
|
708
|
+
}
|
|
709
|
+
tmp_log.debug(f"{str(retVal)}")
|
|
710
|
+
return retVal
|
|
711
|
+
except Exception:
|
|
712
|
+
# roll back
|
|
713
|
+
self._rollback()
|
|
714
|
+
self.dump_error_message(tmp_log)
|
|
715
|
+
return None
|
|
716
|
+
|
|
717
|
+
# get active debug jobs
|
|
718
|
+
def getActiveDebugJobs(self, dn=None, workingGroup=None, prodRole=False):
|
|
719
|
+
comment = " /* DBProxy.getActiveDebugJobs */"
|
|
720
|
+
tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
|
|
721
|
+
tmp_log.debug(f"wg={workingGroup} prodRole={prodRole}")
|
|
722
|
+
varMap = {}
|
|
723
|
+
sqlX = "SELECT PandaID,jobStatus,specialHandling FROM %s "
|
|
724
|
+
sqlX += "WHERE "
|
|
725
|
+
if prodRole:
|
|
726
|
+
pass
|
|
727
|
+
elif workingGroup is not None:
|
|
728
|
+
sqlX += "UPPER(workingGroup) IN (:wg1,:wg2) AND "
|
|
729
|
+
varMap[":wg1"] = f"AP_{workingGroup.upper()}"
|
|
730
|
+
varMap[":wg2"] = f"GP_{workingGroup.upper()}"
|
|
731
|
+
else:
|
|
732
|
+
sqlX += "prodUserName=:prodUserName AND "
|
|
733
|
+
# get compact DN
|
|
734
|
+
compactDN = CoreUtils.clean_user_id(dn)
|
|
735
|
+
if compactDN in ["", "NULL", None]:
|
|
736
|
+
compactDN = dn
|
|
737
|
+
varMap[":prodUserName"] = compactDN
|
|
738
|
+
sqlX += "specialHandling IS NOT NULL "
|
|
739
|
+
try:
|
|
740
|
+
debugStr = "debug"
|
|
741
|
+
activeDebugJobs = []
|
|
742
|
+
# loop over tables
|
|
743
|
+
for table in ["ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsActive4"]:
|
|
744
|
+
sql = sqlX % table
|
|
745
|
+
# start transaction
|
|
746
|
+
self.conn.begin()
|
|
747
|
+
# get jobs with specialHandling
|
|
748
|
+
self.cur.arraysize = 100000
|
|
749
|
+
self.cur.execute(sql + comment, varMap)
|
|
750
|
+
res = self.cur.fetchall()
|
|
751
|
+
# commit
|
|
752
|
+
if not self._commit():
|
|
753
|
+
raise RuntimeError("Commit error")
|
|
754
|
+
# loop over all PandaIDs
|
|
755
|
+
for pandaID, jobStatus, specialHandling in res:
|
|
756
|
+
if specialHandling is None:
|
|
757
|
+
continue
|
|
758
|
+
# only active jobs
|
|
759
|
+
if jobStatus not in [
|
|
760
|
+
"defined",
|
|
761
|
+
"activated",
|
|
762
|
+
"running",
|
|
763
|
+
"sent",
|
|
764
|
+
"starting",
|
|
765
|
+
]:
|
|
766
|
+
continue
|
|
767
|
+
# look for debug jobs
|
|
768
|
+
if debugStr in specialHandling and pandaID not in activeDebugJobs:
|
|
769
|
+
activeDebugJobs.append(pandaID)
|
|
770
|
+
# return
|
|
771
|
+
activeDebugJobs.sort()
|
|
772
|
+
tmp_log.debug(f"{str(activeDebugJobs)}")
|
|
773
|
+
return activeDebugJobs
|
|
774
|
+
except Exception:
|
|
775
|
+
# roll back
|
|
776
|
+
self._rollback()
|
|
777
|
+
self.dump_error_message(tmp_log)
|
|
778
|
+
return None
|
|
779
|
+
|
|
780
|
+
# set debug mode
|
|
781
|
+
def setDebugMode(self, dn, pandaID, prodManager, modeOn, workingGroup):
|
|
782
|
+
comment = " /* DBProxy.setDebugMode */"
|
|
783
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
784
|
+
tmp_log.debug(f"dn={dn} prod={prodManager} wg={workingGroup} mode={modeOn}")
|
|
785
|
+
sqlX = "SELECT prodUserName,jobStatus,specialHandling,workingGroup FROM %s "
|
|
786
|
+
sqlX += "WHERE PandaID=:PandaID "
|
|
787
|
+
sqlU = "UPDATE %s SET specialHandling=:specialHandling "
|
|
788
|
+
sqlU += "WHERE PandaID=:PandaID "
|
|
789
|
+
try:
|
|
790
|
+
# get compact DN
|
|
791
|
+
compactDN = CoreUtils.clean_user_id(dn)
|
|
792
|
+
if compactDN in ["", "NULL", None]:
|
|
793
|
+
compactDN = dn
|
|
794
|
+
debugStr = "debug"
|
|
795
|
+
retStr = ""
|
|
796
|
+
retCode = False
|
|
797
|
+
# loop over tables
|
|
798
|
+
for table in ["ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsActive4"]:
|
|
799
|
+
varMap = {}
|
|
800
|
+
varMap[":PandaID"] = pandaID
|
|
801
|
+
sql = sqlX % table
|
|
802
|
+
# start transaction
|
|
803
|
+
self.conn.begin()
|
|
804
|
+
# get jobs with specialHandling
|
|
805
|
+
self.cur.arraysize = 10
|
|
806
|
+
self.cur.execute(sql + comment, varMap)
|
|
807
|
+
res = self.cur.fetchone()
|
|
808
|
+
# not found
|
|
809
|
+
if res is None:
|
|
810
|
+
retStr = f"PandaID={pandaID} not found in active DB"
|
|
811
|
+
# commit
|
|
812
|
+
if not self._commit():
|
|
813
|
+
raise RuntimeError("Commit error")
|
|
814
|
+
continue
|
|
815
|
+
prodUserName, jobStatus, specialHandling, wGroup = res
|
|
816
|
+
# not active
|
|
817
|
+
changeableState = [
|
|
818
|
+
"defined",
|
|
819
|
+
"activated",
|
|
820
|
+
"running",
|
|
821
|
+
"sent",
|
|
822
|
+
"starting",
|
|
823
|
+
"assigned",
|
|
824
|
+
]
|
|
825
|
+
if jobStatus not in changeableState:
|
|
826
|
+
retStr = f"Cannot set debugMode since the job status is {jobStatus} which is not in one of {str(changeableState)}"
|
|
827
|
+
# commit
|
|
828
|
+
if not self._commit():
|
|
829
|
+
raise RuntimeError("Commit error")
|
|
830
|
+
break
|
|
831
|
+
# extract workingGroup
|
|
832
|
+
try:
|
|
833
|
+
wGroup = wGroup.split("_")[-1]
|
|
834
|
+
wGroup = wGroup.lower()
|
|
835
|
+
except Exception:
|
|
836
|
+
pass
|
|
837
|
+
# not owner
|
|
838
|
+
notOwner = False
|
|
839
|
+
if not prodManager:
|
|
840
|
+
if workingGroup is not None:
|
|
841
|
+
if workingGroup.lower() != wGroup:
|
|
842
|
+
retStr = f"Permission denied. Not the production manager for workingGroup={wGroup}"
|
|
843
|
+
notOwner = True
|
|
844
|
+
else:
|
|
845
|
+
if prodUserName != compactDN:
|
|
846
|
+
retStr = "Permission denied. Not the owner or production manager"
|
|
847
|
+
notOwner = True
|
|
848
|
+
if notOwner:
|
|
849
|
+
# commit
|
|
850
|
+
if not self._commit():
|
|
851
|
+
raise RuntimeError("Commit error")
|
|
852
|
+
break
|
|
853
|
+
# set specialHandling
|
|
854
|
+
updateSH = True
|
|
855
|
+
if specialHandling in [None, ""]:
|
|
856
|
+
if modeOn:
|
|
857
|
+
# set debug mode
|
|
858
|
+
specialHandling = debugStr
|
|
859
|
+
else:
|
|
860
|
+
# already disabled debug mode
|
|
861
|
+
updateSH = False
|
|
862
|
+
elif debugStr in specialHandling:
|
|
863
|
+
if modeOn:
|
|
864
|
+
# already in debug mode
|
|
865
|
+
updateSH = False
|
|
866
|
+
else:
|
|
867
|
+
# disable debug mode
|
|
868
|
+
specialHandling = re.sub(debugStr, "", specialHandling)
|
|
869
|
+
specialHandling = re.sub(",,", ",", specialHandling)
|
|
870
|
+
specialHandling = re.sub("^,", "", specialHandling)
|
|
871
|
+
specialHandling = re.sub(",$", "", specialHandling)
|
|
872
|
+
else:
|
|
873
|
+
if modeOn:
|
|
874
|
+
# set debug mode
|
|
875
|
+
specialHandling = debugStr
|
|
876
|
+
else:
|
|
877
|
+
# already disabled debug mode
|
|
878
|
+
updateSH = False
|
|
879
|
+
|
|
880
|
+
# no update
|
|
881
|
+
if not updateSH:
|
|
882
|
+
retStr = "Already set accordingly"
|
|
883
|
+
# commit
|
|
884
|
+
if not self._commit():
|
|
885
|
+
raise RuntimeError("Commit error")
|
|
886
|
+
break
|
|
887
|
+
# update
|
|
888
|
+
varMap = {}
|
|
889
|
+
varMap[":PandaID"] = pandaID
|
|
890
|
+
varMap[":specialHandling"] = specialHandling
|
|
891
|
+
self.cur.execute((sqlU + comment) % table, varMap)
|
|
892
|
+
retD = self.cur.rowcount
|
|
893
|
+
# commit
|
|
894
|
+
if not self._commit():
|
|
895
|
+
raise RuntimeError("Commit error")
|
|
896
|
+
if retD == 0:
|
|
897
|
+
retStr = "Failed to update DB"
|
|
898
|
+
else:
|
|
899
|
+
retStr = "Succeeded"
|
|
900
|
+
break
|
|
901
|
+
# return
|
|
902
|
+
tmp_log.debug(f"{retStr}")
|
|
903
|
+
return retStr
|
|
904
|
+
except Exception:
|
|
905
|
+
# roll back
|
|
906
|
+
self._rollback()
|
|
907
|
+
self.dump_error_message(tmp_log)
|
|
908
|
+
return None
|
|
909
|
+
|
|
910
|
+
# lock jobs for reassign
|
|
911
|
+
def lockJobsForReassign(
|
|
912
|
+
self,
|
|
913
|
+
tableName,
|
|
914
|
+
timeLimit,
|
|
915
|
+
statList,
|
|
916
|
+
labels,
|
|
917
|
+
processTypes,
|
|
918
|
+
sites,
|
|
919
|
+
clouds,
|
|
920
|
+
useJEDI=False,
|
|
921
|
+
onlyReassignable=False,
|
|
922
|
+
useStateChangeTime=False,
|
|
923
|
+
getEventService=False,
|
|
924
|
+
):
|
|
925
|
+
comment = " /* DBProxy.lockJobsForReassign */"
|
|
926
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
927
|
+
tmp_log.debug(f"{tableName} {timeLimit} {statList} {labels} {processTypes} {sites} {clouds} {useJEDI}")
|
|
928
|
+
try:
|
|
929
|
+
# make sql
|
|
930
|
+
if not useJEDI:
|
|
931
|
+
sql = f"SELECT PandaID FROM {tableName} "
|
|
932
|
+
elif getEventService:
|
|
933
|
+
sql = f"SELECT PandaID,lockedby,eventService,attemptNr,computingSite FROM {tableName} "
|
|
934
|
+
else:
|
|
935
|
+
sql = f"SELECT PandaID,lockedby FROM {tableName} "
|
|
936
|
+
if not useStateChangeTime:
|
|
937
|
+
sql += "WHERE modificationTime<:modificationTime "
|
|
938
|
+
else:
|
|
939
|
+
sql += "WHERE stateChangeTime<:modificationTime "
|
|
940
|
+
varMap = {}
|
|
941
|
+
varMap[":modificationTime"] = timeLimit
|
|
942
|
+
if statList != []:
|
|
943
|
+
stat_var_names_str, stat_var_map = get_sql_IN_bind_variables(statList, prefix=":stat")
|
|
944
|
+
sql += f"AND jobStatus IN ({stat_var_names_str}) "
|
|
945
|
+
varMap.update(stat_var_map)
|
|
946
|
+
if labels != []:
|
|
947
|
+
label_var_names_str, label_var_map = get_sql_IN_bind_variables(labels, prefix=":label")
|
|
948
|
+
sql += f"AND prodSourceLabel IN ({label_var_names_str}) "
|
|
949
|
+
varMap.update(label_var_map)
|
|
950
|
+
if processTypes != []:
|
|
951
|
+
ptype_var_names_str, ptype_var_map = get_sql_IN_bind_variables(processTypes, prefix=":processType")
|
|
952
|
+
sql += f"AND processingType IN ({ptype_var_names_str}) "
|
|
953
|
+
varMap.update(ptype_var_map)
|
|
954
|
+
if sites != []:
|
|
955
|
+
site_var_names_str, site_var_map = get_sql_IN_bind_variables(sites, prefix=":site")
|
|
956
|
+
sql += f"AND computingSite IN ({site_var_names_str}) "
|
|
957
|
+
varMap.update(site_var_map)
|
|
958
|
+
if clouds != []:
|
|
959
|
+
cloud_var_names_str, cloud_var_map = get_sql_IN_bind_variables(clouds, prefix=":cloud")
|
|
960
|
+
sql += f"AND cloud IN ({cloud_var_names_str}) "
|
|
961
|
+
varMap.update(cloud_var_map)
|
|
962
|
+
if onlyReassignable:
|
|
963
|
+
sql += "AND (relocationFlag IS NULL OR relocationFlag<>:relocationFlag) "
|
|
964
|
+
varMap[":relocationFlag"] = 2
|
|
965
|
+
# sql for lock
|
|
966
|
+
if not useStateChangeTime:
|
|
967
|
+
sqlLock = f"UPDATE {tableName} SET modificationTime=CURRENT_DATE WHERE PandaID=:PandaID"
|
|
968
|
+
else:
|
|
969
|
+
sqlLock = f"UPDATE {tableName} SET stateChangeTime=CURRENT_DATE WHERE PandaID=:PandaID"
|
|
970
|
+
# start transaction
|
|
971
|
+
self.conn.begin()
|
|
972
|
+
# select
|
|
973
|
+
self.cur.arraysize = 1000000
|
|
974
|
+
tmp_log.debug(sql + comment + str(varMap))
|
|
975
|
+
self.cur.execute(sql + comment, varMap)
|
|
976
|
+
resList = self.cur.fetchall()
|
|
977
|
+
tmp_log.debug(f"found {len(resList)}")
|
|
978
|
+
retList = []
|
|
979
|
+
# lock
|
|
980
|
+
for tmpItem in resList:
|
|
981
|
+
tmpID = tmpItem[0]
|
|
982
|
+
varLock = {":PandaID": tmpID}
|
|
983
|
+
self.cur.execute(sqlLock + comment, varLock)
|
|
984
|
+
retList.append(tmpItem)
|
|
985
|
+
# commit
|
|
986
|
+
if not self._commit():
|
|
987
|
+
raise RuntimeError("Commit error")
|
|
988
|
+
# sort
|
|
989
|
+
retList.sort()
|
|
990
|
+
tmp_log.debug(f"return {len(retList)}")
|
|
991
|
+
return True, retList
|
|
992
|
+
except Exception:
|
|
993
|
+
# roll back
|
|
994
|
+
self._rollback()
|
|
995
|
+
self.dump_error_message(tmp_log)
|
|
996
|
+
# return empty
|
|
997
|
+
return False, []
|
|
998
|
+
|
|
999
|
+
# lock jobs for finisher
|
|
1000
|
+
def lockJobsForFinisher(self, timeNow, rownum, highPrio):
|
|
1001
|
+
comment = " /* DBProxy.lockJobsForFinisher */"
|
|
1002
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1003
|
+
tmp_log.debug(f"{timeNow} {rownum} {highPrio}")
|
|
1004
|
+
try:
|
|
1005
|
+
varMap = {}
|
|
1006
|
+
varMap[":jobStatus"] = "transferring"
|
|
1007
|
+
varMap[":currentPriority"] = 800
|
|
1008
|
+
varMap[":pLabel1"] = "managed"
|
|
1009
|
+
varMap[":pLabel2"] = "test"
|
|
1010
|
+
varMap[":esJumbo"] = EventServiceUtils.jumboJobFlagNumber
|
|
1011
|
+
# make sql
|
|
1012
|
+
sql = "SELECT PandaID FROM ATLAS_PANDA.jobsActive4 "
|
|
1013
|
+
sql += "WHERE jobStatus=:jobStatus AND modificationTime<:modificationTime AND prodSourceLabel IN (:pLabel1,:pLabel2) "
|
|
1014
|
+
sql += "AND (eventService IS NULL OR eventService<>:esJumbo) "
|
|
1015
|
+
if highPrio:
|
|
1016
|
+
varMap[":modificationTime"] = timeNow - datetime.timedelta(hours=1)
|
|
1017
|
+
sql += f"AND currentPriority>=:currentPriority AND rownum<={rownum} "
|
|
1018
|
+
else:
|
|
1019
|
+
sql += f"AND currentPriority<:currentPriority AND rownum<={rownum} "
|
|
1020
|
+
varMap[":modificationTime"] = timeNow - datetime.timedelta(hours=2)
|
|
1021
|
+
sql += "FOR UPDATE "
|
|
1022
|
+
# sql for lock
|
|
1023
|
+
sqlLock = "UPDATE ATLAS_PANDA.jobsActive4 SET modificationTime=CURRENT_DATE WHERE PandaID=:PandaID"
|
|
1024
|
+
# start transaction
|
|
1025
|
+
self.conn.begin()
|
|
1026
|
+
# select
|
|
1027
|
+
self.cur.arraysize = 1000
|
|
1028
|
+
self.cur.execute(sql + comment, varMap)
|
|
1029
|
+
resList = self.cur.fetchall()
|
|
1030
|
+
retList = []
|
|
1031
|
+
# lock
|
|
1032
|
+
for (tmpID,) in resList:
|
|
1033
|
+
varLock = {":PandaID": tmpID}
|
|
1034
|
+
self.cur.execute(sqlLock + comment, varLock)
|
|
1035
|
+
retList.append(tmpID)
|
|
1036
|
+
# commit
|
|
1037
|
+
if not self._commit():
|
|
1038
|
+
raise RuntimeError("Commit error")
|
|
1039
|
+
# sort
|
|
1040
|
+
retList.sort()
|
|
1041
|
+
tmp_log.debug(f"{len(retList)}")
|
|
1042
|
+
return True, retList
|
|
1043
|
+
except Exception:
|
|
1044
|
+
# roll back
|
|
1045
|
+
self._rollback()
|
|
1046
|
+
self.dump_error_message(tmp_log)
|
|
1047
|
+
# return empty
|
|
1048
|
+
return False, []
|
|
1049
|
+
|
|
1050
|
+
# lock jobs for activator
|
|
1051
|
+
def lockJobsForActivator(self, timeLimit, rownum, prio):
|
|
1052
|
+
comment = " /* DBProxy.lockJobsForActivator */"
|
|
1053
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1054
|
+
tmp_log.debug("start")
|
|
1055
|
+
try:
|
|
1056
|
+
varMap = {}
|
|
1057
|
+
varMap[":jobStatus"] = "assigned"
|
|
1058
|
+
if prio > 0:
|
|
1059
|
+
varMap[":currentPriority"] = prio
|
|
1060
|
+
varMap[":timeLimit"] = timeLimit
|
|
1061
|
+
# make sql
|
|
1062
|
+
sql = "SELECT PandaID FROM ATLAS_PANDA.jobsDefined4 "
|
|
1063
|
+
sql += "WHERE jobStatus=:jobStatus AND (prodDBUpdateTime IS NULL OR prodDBUpdateTime<:timeLimit) "
|
|
1064
|
+
if prio > 0:
|
|
1065
|
+
sql += "AND currentPriority>=:currentPriority "
|
|
1066
|
+
sql += f"AND rownum<={rownum} "
|
|
1067
|
+
sql += "FOR UPDATE "
|
|
1068
|
+
# sql for lock
|
|
1069
|
+
sqlLock = "UPDATE ATLAS_PANDA.jobsDefined4 SET prodDBUpdateTime=CURRENT_DATE WHERE PandaID=:PandaID"
|
|
1070
|
+
# start transaction
|
|
1071
|
+
self.conn.begin()
|
|
1072
|
+
# select
|
|
1073
|
+
self.cur.arraysize = 1000
|
|
1074
|
+
self.cur.execute(sql + comment, varMap)
|
|
1075
|
+
resList = self.cur.fetchall()
|
|
1076
|
+
retList = []
|
|
1077
|
+
# lock
|
|
1078
|
+
for (tmpID,) in resList:
|
|
1079
|
+
varLock = {":PandaID": tmpID}
|
|
1080
|
+
self.cur.execute(sqlLock + comment, varLock)
|
|
1081
|
+
retList.append(tmpID)
|
|
1082
|
+
# commit
|
|
1083
|
+
if not self._commit():
|
|
1084
|
+
raise RuntimeError("Commit error")
|
|
1085
|
+
# sort
|
|
1086
|
+
retList.sort()
|
|
1087
|
+
tmp_log.debug(f"locked {len(retList)} jobs")
|
|
1088
|
+
return True, retList
|
|
1089
|
+
except Exception:
|
|
1090
|
+
# roll back
|
|
1091
|
+
self._rollback()
|
|
1092
|
+
# error
|
|
1093
|
+
self.dump_error_message(tmp_log)
|
|
1094
|
+
# return empty
|
|
1095
|
+
return False, []
|
|
1096
|
+
|
|
1097
|
+
# add metadata
|
|
1098
|
+
def addMetadata(self, pandaID, metadata, newStatus):
|
|
1099
|
+
comment = " /* DBProxy.addMetaData */"
|
|
1100
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
1101
|
+
tmp_log.debug(f"start")
|
|
1102
|
+
# discard metadata for failed jobs
|
|
1103
|
+
if newStatus == "failed":
|
|
1104
|
+
tmp_log.debug("skip")
|
|
1105
|
+
return True
|
|
1106
|
+
sqlJ = "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
|
|
1107
|
+
sqlJ += "UNION "
|
|
1108
|
+
sqlJ += "SELECT jobStatus FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
|
|
1109
|
+
sql0 = "SELECT PandaID FROM ATLAS_PANDA.metaTable WHERE PandaID=:PandaID"
|
|
1110
|
+
sql1 = "INSERT INTO ATLAS_PANDA.metaTable (PandaID,metaData) VALUES (:PandaID,:metaData)"
|
|
1111
|
+
nTry = 1
|
|
1112
|
+
regStart = naive_utcnow()
|
|
1113
|
+
for iTry in range(nTry):
|
|
1114
|
+
try:
|
|
1115
|
+
# autocommit on
|
|
1116
|
+
self.conn.begin()
|
|
1117
|
+
self.cur.arraysize = 10
|
|
1118
|
+
# check job status
|
|
1119
|
+
varMap = {}
|
|
1120
|
+
varMap[":PandaID"] = pandaID
|
|
1121
|
+
self.cur.execute(sqlJ + comment, varMap)
|
|
1122
|
+
resJ = self.cur.fetchone()
|
|
1123
|
+
if resJ is not None:
|
|
1124
|
+
(jobStatus,) = resJ
|
|
1125
|
+
else:
|
|
1126
|
+
jobStatus = "unknown"
|
|
1127
|
+
if jobStatus in ["unknown"]:
|
|
1128
|
+
tmp_log.debug(f"skip jobStatus={jobStatus}")
|
|
1129
|
+
if not self._commit():
|
|
1130
|
+
raise RuntimeError("Commit error")
|
|
1131
|
+
return False
|
|
1132
|
+
# skip if in final state
|
|
1133
|
+
if jobStatus in ["cancelled", "closed", "finished", "failed"]:
|
|
1134
|
+
tmp_log.debug(f"skip jobStatus={jobStatus}")
|
|
1135
|
+
if not self._commit():
|
|
1136
|
+
raise RuntimeError("Commit error")
|
|
1137
|
+
# return True so that subsequent procedure can keep going
|
|
1138
|
+
return True
|
|
1139
|
+
# select
|
|
1140
|
+
varMap = {}
|
|
1141
|
+
varMap[":PandaID"] = pandaID
|
|
1142
|
+
self.cur.arraysize = 10
|
|
1143
|
+
self.cur.execute(sql0 + comment, varMap)
|
|
1144
|
+
res = self.cur.fetchone()
|
|
1145
|
+
# already exist
|
|
1146
|
+
if res is not None:
|
|
1147
|
+
tmp_log.debug(f"skip duplicated during jobStatus={jobStatus}")
|
|
1148
|
+
if not self._commit():
|
|
1149
|
+
raise RuntimeError("Commit error")
|
|
1150
|
+
return True
|
|
1151
|
+
# truncate
|
|
1152
|
+
if metadata is not None:
|
|
1153
|
+
origSize = len(metadata)
|
|
1154
|
+
else:
|
|
1155
|
+
origSize = 0
|
|
1156
|
+
maxSize = 1024 * 1024
|
|
1157
|
+
if newStatus in ["failed"] and origSize > maxSize:
|
|
1158
|
+
metadata = metadata[:maxSize]
|
|
1159
|
+
# insert
|
|
1160
|
+
varMap = {}
|
|
1161
|
+
varMap[":PandaID"] = pandaID
|
|
1162
|
+
varMap[":metaData"] = metadata
|
|
1163
|
+
self.cur.execute(sql1 + comment, varMap)
|
|
1164
|
+
# commit
|
|
1165
|
+
if not self._commit():
|
|
1166
|
+
raise RuntimeError("Commit error")
|
|
1167
|
+
regTime = naive_utcnow() - regStart
|
|
1168
|
+
msgStr = f"done in jobStatus={jobStatus}->{newStatus} took {regTime.seconds} sec"
|
|
1169
|
+
if metadata is not None:
|
|
1170
|
+
msgStr += f" for {len(metadata)} (orig {origSize}) bytes"
|
|
1171
|
+
tmp_log.debug(msgStr)
|
|
1172
|
+
return True
|
|
1173
|
+
except Exception:
|
|
1174
|
+
# roll back
|
|
1175
|
+
self._rollback()
|
|
1176
|
+
if iTry + 1 < nTry:
|
|
1177
|
+
tmp_log.debug(f"retry : {iTry}")
|
|
1178
|
+
time.sleep(random.randint(10, 20))
|
|
1179
|
+
continue
|
|
1180
|
+
self.dump_error_message(tmp_log)
|
|
1181
|
+
return False
|
|
1182
|
+
|
|
1183
|
+
# add stdout
|
|
1184
|
+
def addStdOut(self, pandaID, stdOut):
|
|
1185
|
+
comment = " /* DBProxy.addStdOut */"
|
|
1186
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
1187
|
+
tmp_log.debug(f"start")
|
|
1188
|
+
sqlJ = "SELECT PandaID FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID FOR UPDATE "
|
|
1189
|
+
sqlC = "SELECT PandaID FROM ATLAS_PANDA.jobsDebug WHERE PandaID=:PandaID "
|
|
1190
|
+
sqlI = "INSERT INTO ATLAS_PANDA.jobsDebug (PandaID,stdOut) VALUES (:PandaID,:stdOut) "
|
|
1191
|
+
sqlU = "UPDATE ATLAS_PANDA.jobsDebug SET stdOut=:stdOut WHERE PandaID=:PandaID "
|
|
1192
|
+
try:
|
|
1193
|
+
# autocommit on
|
|
1194
|
+
self.conn.begin()
|
|
1195
|
+
# select
|
|
1196
|
+
varMap = {}
|
|
1197
|
+
varMap[":PandaID"] = pandaID
|
|
1198
|
+
self.cur.arraysize = 10
|
|
1199
|
+
# check job table
|
|
1200
|
+
self.cur.execute(sqlJ + comment, varMap)
|
|
1201
|
+
res = self.cur.fetchone()
|
|
1202
|
+
if res is None:
|
|
1203
|
+
tmp_log.debug(f"addStdOut : {pandaID} non active")
|
|
1204
|
+
else:
|
|
1205
|
+
# check debug table
|
|
1206
|
+
self.cur.execute(sqlC + comment, varMap)
|
|
1207
|
+
res = self.cur.fetchone()
|
|
1208
|
+
# already exist
|
|
1209
|
+
if res is not None:
|
|
1210
|
+
# update
|
|
1211
|
+
sql = sqlU
|
|
1212
|
+
else:
|
|
1213
|
+
# insert
|
|
1214
|
+
sql = sqlI
|
|
1215
|
+
# write stdout
|
|
1216
|
+
varMap = {}
|
|
1217
|
+
varMap[":PandaID"] = pandaID
|
|
1218
|
+
varMap[":stdOut"] = stdOut
|
|
1219
|
+
self.cur.execute(sql + comment, varMap)
|
|
1220
|
+
# commit
|
|
1221
|
+
if not self._commit():
|
|
1222
|
+
raise RuntimeError("Commit error")
|
|
1223
|
+
return True
|
|
1224
|
+
except Exception:
|
|
1225
|
+
# roll back
|
|
1226
|
+
self._rollback()
|
|
1227
|
+
self.dump_error_message(tmp_log)
|
|
1228
|
+
return False
|
|
1229
|
+
|
|
1230
|
+
# get job statistics
|
|
1231
|
+
def getJobStatistics(self):
|
|
1232
|
+
comment = " /* DBProxy.getJobStatistics */"
|
|
1233
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1234
|
+
tmp_log.debug("start")
|
|
1235
|
+
|
|
1236
|
+
# tables to query
|
|
1237
|
+
jobs_active_4_table = f"{panda_config.schemaPANDA}.jobsActive4"
|
|
1238
|
+
jobs_defined_4_table = f"{panda_config.schemaPANDA}.jobsDefined4"
|
|
1239
|
+
tables = [jobs_active_4_table, jobs_defined_4_table]
|
|
1240
|
+
|
|
1241
|
+
# states that are necessary and irrelevant states
|
|
1242
|
+
included_states = ["assigned", "activated", "running"]
|
|
1243
|
+
excluded_states = ["merging"]
|
|
1244
|
+
|
|
1245
|
+
# sql template for jobs table
|
|
1246
|
+
sql_template = f"SELECT computingSite, jobStatus, COUNT(*) FROM {{table_name}} GROUP BY computingSite, jobStatus"
|
|
1247
|
+
|
|
1248
|
+
# sql template for statistics table (materialized view)
|
|
1249
|
+
sql_mv_template = sql_template.replace("COUNT(*)", "SUM(num_of_jobs)")
|
|
1250
|
+
sql_mv_template = sql_mv_template.replace("SELECT ", "SELECT /*+ RESULT_CACHE */ ")
|
|
1251
|
+
ret = {}
|
|
1252
|
+
max_retries = 3
|
|
1253
|
+
|
|
1254
|
+
for retry in range(max_retries):
|
|
1255
|
+
try:
|
|
1256
|
+
for table in tables:
|
|
1257
|
+
# start transaction
|
|
1258
|
+
self.conn.begin()
|
|
1259
|
+
var_map = {}
|
|
1260
|
+
self.cur.arraysize = 10000
|
|
1261
|
+
|
|
1262
|
+
# for active jobs we will query the summarized materialized view
|
|
1263
|
+
if table == jobs_active_4_table:
|
|
1264
|
+
table_name = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
|
|
1265
|
+
sql = (sql_mv_template + comment).format(table_name=table_name)
|
|
1266
|
+
# for defined jobs we will query the actual table
|
|
1267
|
+
else:
|
|
1268
|
+
table_name = table
|
|
1269
|
+
sql = (sql_template + comment).format(table_name=table_name)
|
|
1270
|
+
tmp_log.debug(f"Will execute: {sql} {str(var_map)}")
|
|
1271
|
+
|
|
1272
|
+
self.cur.execute(sql, var_map)
|
|
1273
|
+
res = self.cur.fetchall()
|
|
1274
|
+
if not self._commit():
|
|
1275
|
+
raise RuntimeError("Commit error")
|
|
1276
|
+
|
|
1277
|
+
# create map
|
|
1278
|
+
for computing_site, job_status, n_jobs in res:
|
|
1279
|
+
if job_status in excluded_states: # ignore some job status since they break APF
|
|
1280
|
+
continue
|
|
1281
|
+
|
|
1282
|
+
ret.setdefault(computing_site, {}).setdefault(job_status, 0)
|
|
1283
|
+
ret[computing_site][job_status] += n_jobs
|
|
1284
|
+
|
|
1285
|
+
# fill in missing states with 0
|
|
1286
|
+
for site in ret:
|
|
1287
|
+
for state in included_states:
|
|
1288
|
+
ret[site].setdefault(state, 0)
|
|
1289
|
+
|
|
1290
|
+
tmp_log.debug(f"done")
|
|
1291
|
+
return ret
|
|
1292
|
+
|
|
1293
|
+
except Exception:
|
|
1294
|
+
self._rollback()
|
|
1295
|
+
|
|
1296
|
+
if retry + 1 < max_retries: # wait 2 seconds before the next retry
|
|
1297
|
+
tmp_log.debug(f"retry: {retry}")
|
|
1298
|
+
time.sleep(2)
|
|
1299
|
+
else: # reached max retries - leave
|
|
1300
|
+
self.dump_error_message(tmp_log)
|
|
1301
|
+
return {}
|
|
1302
|
+
|
|
1303
|
+
# get detailed job statistics with resource_type and prodsourcelabel
|
|
1304
|
+
def getDetailedJobStatistics(self):
|
|
1305
|
+
comment = " /* DBProxy.getDetailedJobStatistics */"
|
|
1306
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1307
|
+
tmp_log.debug("start")
|
|
1308
|
+
|
|
1309
|
+
# tables to query
|
|
1310
|
+
jobs_active_4_table = f"{panda_config.schemaPANDA}.jobsActive4"
|
|
1311
|
+
jobs_defined_4_table = f"{panda_config.schemaPANDA}.jobsDefined4"
|
|
1312
|
+
tables = [jobs_active_4_table, jobs_defined_4_table]
|
|
1313
|
+
|
|
1314
|
+
# states that are necessary and irrelevant states
|
|
1315
|
+
included_states = ["assigned", "activated", "running"]
|
|
1316
|
+
excluded_states = ["merging"]
|
|
1317
|
+
|
|
1318
|
+
# sql template for jobs table
|
|
1319
|
+
sql_template = f"SELECT computingSite, resource_type, prodSourceLabel, jobStatus, COUNT(*) FROM {{table_name}} GROUP BY computingSite, resource_type, prodSourceLabel, jobStatus"
|
|
1320
|
+
# sql template for statistics table (materialized view)
|
|
1321
|
+
sql_mv_template = sql_template.replace("COUNT(*)", "SUM(num_of_jobs)")
|
|
1322
|
+
sql_mv_template = sql_mv_template.replace("SELECT ", "SELECT /*+ RESULT_CACHE */ ")
|
|
1323
|
+
ret = {}
|
|
1324
|
+
max_retries = 3
|
|
1325
|
+
|
|
1326
|
+
for retry in range(max_retries):
|
|
1327
|
+
try:
|
|
1328
|
+
for table in tables:
|
|
1329
|
+
# start transaction
|
|
1330
|
+
self.conn.begin()
|
|
1331
|
+
var_map = {}
|
|
1332
|
+
self.cur.arraysize = 10000
|
|
1333
|
+
|
|
1334
|
+
# for active jobs we will query the summarized materialized view
|
|
1335
|
+
if table == jobs_active_4_table:
|
|
1336
|
+
table_name = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
|
|
1337
|
+
sql = (sql_mv_template + comment).format(table_name=table_name)
|
|
1338
|
+
# for defined jobs we will query the actual table
|
|
1339
|
+
else:
|
|
1340
|
+
table_name = table
|
|
1341
|
+
sql = (sql_template + comment).format(table_name=table_name)
|
|
1342
|
+
tmp_log.debug(f"Will execute: {sql} {str(var_map)}")
|
|
1343
|
+
|
|
1344
|
+
self.cur.execute(sql, var_map)
|
|
1345
|
+
res = self.cur.fetchall()
|
|
1346
|
+
if not self._commit():
|
|
1347
|
+
raise RuntimeError("Commit error")
|
|
1348
|
+
|
|
1349
|
+
# create map
|
|
1350
|
+
for computing_site, resource_type, prod_source_label, job_status, n_jobs in res:
|
|
1351
|
+
if job_status in excluded_states: # ignore some job status since they break APF
|
|
1352
|
+
continue
|
|
1353
|
+
|
|
1354
|
+
ret.setdefault(computing_site, {}).setdefault(resource_type, {}).setdefault(prod_source_label, {}).setdefault(job_status, 0)
|
|
1355
|
+
ret[computing_site][resource_type][prod_source_label][job_status] += n_jobs
|
|
1356
|
+
|
|
1357
|
+
# fill in missing states with 0
|
|
1358
|
+
for site in ret:
|
|
1359
|
+
for resource_type in ret[site]:
|
|
1360
|
+
for prod_source_label in ret[site][resource_type]:
|
|
1361
|
+
for state in included_states:
|
|
1362
|
+
ret[site][resource_type][prod_source_label].setdefault(state, 0)
|
|
1363
|
+
|
|
1364
|
+
tmp_log.debug(f"done")
|
|
1365
|
+
return ret
|
|
1366
|
+
|
|
1367
|
+
except Exception:
|
|
1368
|
+
self._rollback()
|
|
1369
|
+
|
|
1370
|
+
if retry + 1 < max_retries: # wait 2 seconds before the next retry
|
|
1371
|
+
tmp_log.debug(f"retry: {retry}")
|
|
1372
|
+
time.sleep(2)
|
|
1373
|
+
else: # reached max retries - leave
|
|
1374
|
+
self.dump_error_message(tmp_log)
|
|
1375
|
+
return {}
|
|
1376
|
+
|
|
1377
|
+
# get job statistics per site and resource type (SCORE, MCORE, ...)
|
|
1378
|
+
def getJobStatisticsPerSiteResource(self, time_window):
|
|
1379
|
+
comment = " /* DBProxy.getJobStatisticsPerSiteResource */"
|
|
1380
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1381
|
+
tmp_log.debug("start")
|
|
1382
|
+
|
|
1383
|
+
tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsDefined4", "ATLAS_PANDA.jobsArchived4"]
|
|
1384
|
+
|
|
1385
|
+
# basic SQL for active and defined jobs
|
|
1386
|
+
sql = "SELECT computingSite, jobStatus, resource_type, COUNT(*) FROM %s GROUP BY computingSite, jobStatus, resource_type "
|
|
1387
|
+
|
|
1388
|
+
# SQL for archived table including time window
|
|
1389
|
+
sql_archive = (
|
|
1390
|
+
"SELECT /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ computingSite, jobStatus, resource_type, COUNT(*) "
|
|
1391
|
+
"FROM ATLAS_PANDA.jobsArchived4 tab WHERE modificationTime > :modificationTime "
|
|
1392
|
+
"GROUP BY computingSite, jobStatus, resource_type "
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
# sql for materialized view
|
|
1396
|
+
sql_mv = re.sub("COUNT\(\*\)", "SUM(njobs)", sql)
|
|
1397
|
+
sql_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_mv)
|
|
1398
|
+
|
|
1399
|
+
ret = dict()
|
|
1400
|
+
try:
|
|
1401
|
+
# calculate the time floor based on the window specified by the caller
|
|
1402
|
+
if time_window is None:
|
|
1403
|
+
time_floor = naive_utcnow() - datetime.timedelta(hours=12)
|
|
1404
|
+
else:
|
|
1405
|
+
time_floor = naive_utcnow() - datetime.timedelta(minutes=int(time_window))
|
|
1406
|
+
|
|
1407
|
+
for table in tables:
|
|
1408
|
+
# start transaction
|
|
1409
|
+
self.conn.begin()
|
|
1410
|
+
self.cur.arraysize = 10000
|
|
1411
|
+
# select
|
|
1412
|
+
|
|
1413
|
+
var_map = {}
|
|
1414
|
+
if table == "ATLAS_PANDA.jobsArchived4":
|
|
1415
|
+
var_map[":modificationTime"] = time_floor
|
|
1416
|
+
sql_tmp = sql_archive + comment
|
|
1417
|
+
elif table == "ATLAS_PANDA.jobsActive4":
|
|
1418
|
+
sql_tmp = (sql_mv + comment) % "ATLAS_PANDA.JOBS_SHARE_STATS"
|
|
1419
|
+
else:
|
|
1420
|
+
sql_tmp = (sql + comment) % table
|
|
1421
|
+
|
|
1422
|
+
self.cur.execute(sql_tmp, var_map)
|
|
1423
|
+
res = self.cur.fetchall()
|
|
1424
|
+
|
|
1425
|
+
# commit
|
|
1426
|
+
if not self._commit():
|
|
1427
|
+
raise RuntimeError("Commit error")
|
|
1428
|
+
|
|
1429
|
+
# create map
|
|
1430
|
+
for computing_site, job_status, resource_type, n_jobs in res:
|
|
1431
|
+
ret.setdefault(computing_site, dict()).setdefault(resource_type, dict()).setdefault(job_status, 0)
|
|
1432
|
+
ret[computing_site][resource_type][job_status] += n_jobs
|
|
1433
|
+
|
|
1434
|
+
# fill in missing states with 0
|
|
1435
|
+
included_states = ["assigned", "activated", "running", "finished", "failed"]
|
|
1436
|
+
for computing_site in ret:
|
|
1437
|
+
for resource_type in ret[computing_site]:
|
|
1438
|
+
for job_status in included_states:
|
|
1439
|
+
ret[computing_site][resource_type].setdefault(job_status, 0)
|
|
1440
|
+
|
|
1441
|
+
tmp_log.debug("done")
|
|
1442
|
+
return ret
|
|
1443
|
+
except Exception:
|
|
1444
|
+
# roll back
|
|
1445
|
+
self._rollback()
|
|
1446
|
+
# error
|
|
1447
|
+
self.dump_error_message(tmp_log)
|
|
1448
|
+
return dict()
|
|
1449
|
+
|
|
1450
|
+
# get the number of job for a user
|
|
1451
|
+
def getNumberJobsUser(self, dn, workingGroup=None):
|
|
1452
|
+
comment = " /* DBProxy.getNumberJobsUser */"
|
|
1453
|
+
tmp_log = self.create_tagged_logger(comment, f"DN={dn}")
|
|
1454
|
+
tmp_log.debug(f"workingGroup={workingGroup})")
|
|
1455
|
+
|
|
1456
|
+
# get compact DN
|
|
1457
|
+
compact_dn = CoreUtils.clean_user_id(dn)
|
|
1458
|
+
if compact_dn in ["", "NULL", None]:
|
|
1459
|
+
compact_dn = dn
|
|
1460
|
+
|
|
1461
|
+
if workingGroup is not None:
|
|
1462
|
+
sql_count_jobs = "SELECT COUNT(*) FROM %s WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel AND workingGroup=:workingGroup "
|
|
1463
|
+
else:
|
|
1464
|
+
sql_count_jobs = "SELECT COUNT(*) FROM %s WHERE prodUserName=:prodUserName AND prodSourceLabel=:prodSourceLabel AND workingGroup IS NULL "
|
|
1465
|
+
sql_count_jobs += "AND NOT jobStatus IN (:failed,:merging) "
|
|
1466
|
+
|
|
1467
|
+
n_try = 1
|
|
1468
|
+
n_jobs = 0
|
|
1469
|
+
for i_try in range(n_try):
|
|
1470
|
+
try:
|
|
1471
|
+
for table in ("ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsDefined4"):
|
|
1472
|
+
# start transaction
|
|
1473
|
+
self.conn.begin()
|
|
1474
|
+
# select
|
|
1475
|
+
var_map = {":prodUserName": compact_dn, ":prodSourceLabel": "user", ":failed": "failed", ":merging": "merging"}
|
|
1476
|
+
if workingGroup is not None:
|
|
1477
|
+
var_map[":workingGroup"] = workingGroup
|
|
1478
|
+
self.cur.arraysize = 10
|
|
1479
|
+
self.cur.execute((sql_count_jobs + comment) % table, var_map)
|
|
1480
|
+
rows = self.cur.fetchall()
|
|
1481
|
+
# commit
|
|
1482
|
+
if not self._commit():
|
|
1483
|
+
raise RuntimeError("Commit error")
|
|
1484
|
+
if len(rows) != 0:
|
|
1485
|
+
n_jobs += rows[0][0]
|
|
1486
|
+
# return
|
|
1487
|
+
tmp_log.debug(f"{n_jobs}")
|
|
1488
|
+
return n_jobs
|
|
1489
|
+
except Exception:
|
|
1490
|
+
# roll back
|
|
1491
|
+
self._rollback()
|
|
1492
|
+
if i_try + 1 < n_try:
|
|
1493
|
+
time.sleep(2)
|
|
1494
|
+
continue
|
|
1495
|
+
self.dump_error_message(tmp_log)
|
|
1496
|
+
return 0
|
|
1497
|
+
|
|
1498
|
+
# get job statistics for ExtIF. Source type is analysis or production
|
|
1499
|
+
def getJobStatisticsForExtIF(self, source_type=None):
|
|
1500
|
+
comment = " /* DBProxy.getJobStatisticsForExtIF */"
|
|
1501
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1502
|
+
tmp_log.debug(f"start source_type={source_type}")
|
|
1503
|
+
|
|
1504
|
+
time_floor = naive_utcnow() - datetime.timedelta(hours=12)
|
|
1505
|
+
|
|
1506
|
+
# analysis
|
|
1507
|
+
if source_type == "analysis":
|
|
1508
|
+
sql = "SELECT jobStatus, COUNT(*), cloud FROM %s WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) GROUP BY jobStatus, cloud"
|
|
1509
|
+
|
|
1510
|
+
sql_archived = (
|
|
1511
|
+
"SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
|
|
1512
|
+
"jobStatus, COUNT(*), tabS.data.cloud "
|
|
1513
|
+
"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
|
|
1514
|
+
"WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) "
|
|
1515
|
+
"AND tab.computingSite = tabS.panda_queue "
|
|
1516
|
+
"AND modificationTime>:modificationTime GROUP BY tab.jobStatus,tabS.data.cloud"
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1519
|
+
# production
|
|
1520
|
+
else:
|
|
1521
|
+
prod_source_label_string = ":prodSourceLabel1, " + ", ".join(f":prodSourceLabel_{label}" for label in JobUtils.list_ptest_prod_sources)
|
|
1522
|
+
sql = (
|
|
1523
|
+
"SELECT /* use_json_type */ tab.jobStatus, COUNT(*), tabS.data.cloud "
|
|
1524
|
+
"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
|
|
1525
|
+
f"WHERE prodSourceLabel IN ({prod_source_label_string}) "
|
|
1526
|
+
"AND tab.computingSite = tabS.panda_queue "
|
|
1527
|
+
"GROUP BY tab.jobStatus, tabS.data.cloud"
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
sql_archived = (
|
|
1531
|
+
"SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
|
|
1532
|
+
"jobStatus, COUNT(*), tabS.data.cloud "
|
|
1533
|
+
"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
|
|
1534
|
+
f"WHERE prodSourceLabel IN ({prod_source_label_string}) "
|
|
1535
|
+
"AND tab.computingSite = tabS.panda_queue "
|
|
1536
|
+
"AND modificationTime>:modificationTime GROUP BY tab.jobStatus,tabS.data.cloud"
|
|
1537
|
+
)
|
|
1538
|
+
|
|
1539
|
+
# sql for materialized view
|
|
1540
|
+
sql_active_mv = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sql)
|
|
1541
|
+
sql_active_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_active_mv)
|
|
1542
|
+
|
|
1543
|
+
ret = {}
|
|
1544
|
+
|
|
1545
|
+
tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.jobsDefined4"]
|
|
1546
|
+
try:
|
|
1547
|
+
for table in tables:
|
|
1548
|
+
# start transaction
|
|
1549
|
+
self.conn.begin()
|
|
1550
|
+
|
|
1551
|
+
# select
|
|
1552
|
+
var_map = {}
|
|
1553
|
+
if source_type == "analysis":
|
|
1554
|
+
var_map[":prodSourceLabel1"] = "user"
|
|
1555
|
+
var_map[":prodSourceLabel2"] = "panda"
|
|
1556
|
+
else:
|
|
1557
|
+
var_map[":prodSourceLabel1"] = "managed"
|
|
1558
|
+
for tmp_label in JobUtils.list_ptest_prod_sources:
|
|
1559
|
+
tmp_key = f":prodSourceLabel_{tmp_label}"
|
|
1560
|
+
var_map[tmp_key] = tmp_label
|
|
1561
|
+
|
|
1562
|
+
if table != "ATLAS_PANDA.jobsArchived4":
|
|
1563
|
+
self.cur.arraysize = 10000
|
|
1564
|
+
# active uses materialized view
|
|
1565
|
+
if table == "ATLAS_PANDA.jobsActive4":
|
|
1566
|
+
self.cur.execute(
|
|
1567
|
+
(sql_active_mv + comment) % "ATLAS_PANDA.MV_JOBSACTIVE4_STATS",
|
|
1568
|
+
var_map,
|
|
1569
|
+
)
|
|
1570
|
+
# defined and waiting tables
|
|
1571
|
+
else:
|
|
1572
|
+
self.cur.execute((sql + comment) % table, var_map)
|
|
1573
|
+
else:
|
|
1574
|
+
var_map[":modificationTime"] = time_floor
|
|
1575
|
+
self.cur.arraysize = 10000
|
|
1576
|
+
self.cur.execute((sql_archived + comment) % table, var_map)
|
|
1577
|
+
res = self.cur.fetchall()
|
|
1578
|
+
|
|
1579
|
+
# commit
|
|
1580
|
+
if not self._commit():
|
|
1581
|
+
raise RuntimeError("Commit error")
|
|
1582
|
+
|
|
1583
|
+
# create map
|
|
1584
|
+
for job_status, count, cloud in res:
|
|
1585
|
+
ret.setdefault(cloud, dict())
|
|
1586
|
+
ret[cloud].setdefault(job_status, 0)
|
|
1587
|
+
ret[cloud][job_status] += count
|
|
1588
|
+
|
|
1589
|
+
# return
|
|
1590
|
+
tmp_log.debug(f"done")
|
|
1591
|
+
return ret
|
|
1592
|
+
except Exception:
|
|
1593
|
+
# roll back
|
|
1594
|
+
self._rollback()
|
|
1595
|
+
# error
|
|
1596
|
+
self.dump_error_message(tmp_log)
|
|
1597
|
+
return {}
|
|
1598
|
+
|
|
1599
|
+
# get statistics for production jobs per processingType
|
|
1600
|
+
def getJobStatisticsPerProcessingType(self):
|
|
1601
|
+
comment = " /* DBProxy.getJobStatisticsPerProcessingType */"
|
|
1602
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1603
|
+
tmp_log.debug("start")
|
|
1604
|
+
|
|
1605
|
+
time_floor = naive_utcnow() - datetime.timedelta(hours=12)
|
|
1606
|
+
|
|
1607
|
+
# Job tables we are going to query
|
|
1608
|
+
tables = ["ATLAS_PANDA.jobsActive4", "ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.jobsDefined4"]
|
|
1609
|
+
|
|
1610
|
+
# Define the prodSourceLabel list
|
|
1611
|
+
prod_source_labels = ", ".join([f":prodSourceLabel_{label}" for label in JobUtils.list_ptest_prod_sources])
|
|
1612
|
+
|
|
1613
|
+
# Construct the SQL query for active jobs
|
|
1614
|
+
sql_active = (
|
|
1615
|
+
f"SELECT /* use_json_type */ jobStatus, COUNT(*), tabS.data.cloud, processingType "
|
|
1616
|
+
f"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
|
|
1617
|
+
f"WHERE prodSourceLabel IN (:prodSourceLabelManaged, {prod_source_labels}) "
|
|
1618
|
+
f"AND computingSite=tabS.panda_queue "
|
|
1619
|
+
f"GROUP BY jobStatus, tabS.data.cloud, processingType"
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
# Construct the SQL query for archived jobs
|
|
1623
|
+
sql_archived = (
|
|
1624
|
+
f"SELECT /* use_json_type */ /*+ INDEX_RS_ASC(tab (MODIFICATIONTIME PRODSOURCELABEL)) */ "
|
|
1625
|
+
f"jobStatus, COUNT(*), tabS.data.cloud, processingType "
|
|
1626
|
+
f"FROM %s tab, ATLAS_PANDA.schedconfig_json tabS "
|
|
1627
|
+
f"WHERE prodSourceLabel IN (:prodSourceLabelManaged, {prod_source_labels}) "
|
|
1628
|
+
f"AND modificationTime > :modificationTime "
|
|
1629
|
+
f"AND computingSite = tabS.panda_queue "
|
|
1630
|
+
f"GROUP BY jobStatus, tabS.data.cloud, processingType"
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
# sql for materialized view
|
|
1634
|
+
sql_active_mv = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sql_active)
|
|
1635
|
+
sql_active_mv = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sql_active_mv)
|
|
1636
|
+
|
|
1637
|
+
ret = {}
|
|
1638
|
+
try:
|
|
1639
|
+
for table in tables:
|
|
1640
|
+
# start transaction
|
|
1641
|
+
self.conn.begin()
|
|
1642
|
+
|
|
1643
|
+
# select
|
|
1644
|
+
self.cur.arraysize = 10000
|
|
1645
|
+
var_map = {":prodSourceLabelManaged": "managed"}
|
|
1646
|
+
var_map.update({f":prodSourceLabel_{label}": label for label in JobUtils.list_ptest_prod_sources})
|
|
1647
|
+
|
|
1648
|
+
if table == "ATLAS_PANDA.jobsArchived4":
|
|
1649
|
+
var_map[":modificationTime"] = time_floor
|
|
1650
|
+
self.cur.execute((sql_archived + comment) % table, var_map)
|
|
1651
|
+
|
|
1652
|
+
elif table == "ATLAS_PANDA.jobsActive4":
|
|
1653
|
+
self.cur.execute(
|
|
1654
|
+
(sql_active_mv + comment) % "ATLAS_PANDA.MV_JOBSACTIVE4_STATS",
|
|
1655
|
+
var_map,
|
|
1656
|
+
)
|
|
1657
|
+
else:
|
|
1658
|
+
# use real table since coreCount is unavailable in MatView
|
|
1659
|
+
self.cur.execute((sql_active + comment) % table, var_map)
|
|
1660
|
+
|
|
1661
|
+
results = self.cur.fetchall()
|
|
1662
|
+
|
|
1663
|
+
if not self._commit():
|
|
1664
|
+
raise RuntimeError("Commit error")
|
|
1665
|
+
|
|
1666
|
+
# create map
|
|
1667
|
+
for row in results:
|
|
1668
|
+
job_status, count, cloud, processing_type = row
|
|
1669
|
+
ret.setdefault(cloud, {}).setdefault(processing_type, {}).setdefault(job_status, 0)
|
|
1670
|
+
ret[cloud][processing_type][job_status] += count
|
|
1671
|
+
|
|
1672
|
+
tmp_log.debug(f"done")
|
|
1673
|
+
return ret
|
|
1674
|
+
except Exception:
|
|
1675
|
+
# roll back
|
|
1676
|
+
self._rollback()
|
|
1677
|
+
# error
|
|
1678
|
+
self.dump_error_message(tmp_log)
|
|
1679
|
+
return {}
|
|
1680
|
+
|
|
1681
|
+
# peek at job
|
|
1682
|
+
def peekJobLog(self, pandaID, days=None):
|
|
1683
|
+
comment = " /* DBProxy.peekJobLog */"
|
|
1684
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
1685
|
+
tmp_log.debug(f"days={days}")
|
|
1686
|
+
|
|
1687
|
+
# return None for NULL PandaID
|
|
1688
|
+
if pandaID in ["NULL", "", "None", None]:
|
|
1689
|
+
return None
|
|
1690
|
+
|
|
1691
|
+
sql_select = "SELECT %s FROM %s "
|
|
1692
|
+
sql_where = "WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-:days) "
|
|
1693
|
+
|
|
1694
|
+
# select
|
|
1695
|
+
var_map = {":PandaID": pandaID}
|
|
1696
|
+
if days is None:
|
|
1697
|
+
days = 30
|
|
1698
|
+
var_map[":days"] = days
|
|
1699
|
+
n_try = 1
|
|
1700
|
+
|
|
1701
|
+
for i_try in range(n_try):
|
|
1702
|
+
try:
|
|
1703
|
+
# get list of archived tables
|
|
1704
|
+
tables = [f"{panda_config.schemaPANDAARCH}.jobsArchived"]
|
|
1705
|
+
# select
|
|
1706
|
+
for table in tables:
|
|
1707
|
+
# start transaction
|
|
1708
|
+
self.conn.begin()
|
|
1709
|
+
# select
|
|
1710
|
+
sql = sql_select % (JobSpec.columnNames(), table) + sql_where
|
|
1711
|
+
self.cur.arraysize = 10
|
|
1712
|
+
self.cur.execute(sql + comment, var_map)
|
|
1713
|
+
rows = self.cur.fetchall()
|
|
1714
|
+
# commit
|
|
1715
|
+
if not self._commit():
|
|
1716
|
+
raise RuntimeError("Commit error")
|
|
1717
|
+
if len(rows) != 0:
|
|
1718
|
+
# Job
|
|
1719
|
+
job = JobSpec()
|
|
1720
|
+
job.pack(rows[0])
|
|
1721
|
+
# Files
|
|
1722
|
+
# start transaction
|
|
1723
|
+
self.conn.begin()
|
|
1724
|
+
# select
|
|
1725
|
+
file_table_name = re.sub("jobsArchived", "filesTable_ARCH", table)
|
|
1726
|
+
sql_get_files = (
|
|
1727
|
+
f"SELECT /*+ INDEX(tab FILES_ARCH_PANDAID_IDX)*/ {FileSpec.columnNames()} "
|
|
1728
|
+
f"FROM {file_table_name} tab "
|
|
1729
|
+
"WHERE PandaID=:PandaID AND modificationTime>(CURRENT_DATE-:days)"
|
|
1730
|
+
)
|
|
1731
|
+
self.cur.arraysize = 10000
|
|
1732
|
+
self.cur.execute(sql_get_files + comment, var_map)
|
|
1733
|
+
file_rows = self.cur.fetchall()
|
|
1734
|
+
|
|
1735
|
+
# metadata
|
|
1736
|
+
var_map = {}
|
|
1737
|
+
var_map[":PandaID"] = job.PandaID
|
|
1738
|
+
job.metadata = None
|
|
1739
|
+
meta_table_name = re.sub("jobsArchived", "metaTable_ARCH", table)
|
|
1740
|
+
sql_get_meta = f"SELECT metaData FROM {meta_table_name} WHERE PandaID=:PandaID"
|
|
1741
|
+
self.cur.execute(sql_get_meta + comment, var_map)
|
|
1742
|
+
for (clob_meta,) in self.cur:
|
|
1743
|
+
if clob_meta is not None:
|
|
1744
|
+
try:
|
|
1745
|
+
job.metadata = clob_meta.read()
|
|
1746
|
+
except AttributeError:
|
|
1747
|
+
job.metadata = str(clob_meta)
|
|
1748
|
+
break
|
|
1749
|
+
|
|
1750
|
+
# job parameters
|
|
1751
|
+
job.jobParameters = None
|
|
1752
|
+
job_param_table_name = re.sub("jobsArchived", "jobParamsTable_ARCH", table)
|
|
1753
|
+
sql_get_job_params = f"SELECT jobParameters FROM {job_param_table_name} WHERE PandaID=:PandaID"
|
|
1754
|
+
var_map = {}
|
|
1755
|
+
var_map[":PandaID"] = job.PandaID
|
|
1756
|
+
self.cur.execute(sql_get_job_params + comment, var_map)
|
|
1757
|
+
for (clob_job_params,) in self.cur:
|
|
1758
|
+
if clob_job_params is not None:
|
|
1759
|
+
try:
|
|
1760
|
+
job.jobParameters = clob_job_params.read()
|
|
1761
|
+
except AttributeError:
|
|
1762
|
+
job.jobParameters = str(clob_job_params)
|
|
1763
|
+
break
|
|
1764
|
+
# commit
|
|
1765
|
+
if not self._commit():
|
|
1766
|
+
raise RuntimeError("Commit error")
|
|
1767
|
+
# set files
|
|
1768
|
+
for file_row in file_rows:
|
|
1769
|
+
file = FileSpec()
|
|
1770
|
+
file.pack(file_row)
|
|
1771
|
+
# remove redundant white spaces
|
|
1772
|
+
try:
|
|
1773
|
+
file.md5sum = file.md5sum.strip()
|
|
1774
|
+
except Exception:
|
|
1775
|
+
pass
|
|
1776
|
+
try:
|
|
1777
|
+
file.checksum = file.checksum.strip()
|
|
1778
|
+
except Exception:
|
|
1779
|
+
pass
|
|
1780
|
+
job.addFile(file)
|
|
1781
|
+
return job
|
|
1782
|
+
tmp_log.debug(f"not found")
|
|
1783
|
+
return None
|
|
1784
|
+
except Exception:
|
|
1785
|
+
# roll back
|
|
1786
|
+
self._rollback()
|
|
1787
|
+
if i_try + 1 < n_try:
|
|
1788
|
+
tmp_log.error(f"retry {i_try}")
|
|
1789
|
+
time.sleep(random.randint(10, 20))
|
|
1790
|
+
continue
|
|
1791
|
+
self.dump_error_message(tmp_log)
|
|
1792
|
+
# return None
|
|
1793
|
+
return None
|
|
1794
|
+
|
|
1795
|
+
# throttle user jobs
|
|
1796
|
+
def throttleUserJobs(self, prodUserName, workingGroup, get_dict):
|
|
1797
|
+
comment = " /* DBProxy.throttleUserJobs */"
|
|
1798
|
+
tmp_log = self.create_tagged_logger(comment, f"user={prodUserName} group={workingGroup}")
|
|
1799
|
+
tmp_log.debug("start")
|
|
1800
|
+
try:
|
|
1801
|
+
# sql to get tasks
|
|
1802
|
+
sql_get_tasks = (
|
|
1803
|
+
"SELECT /*+ INDEX_RS_ASC(tab JOBSACTIVE4_PRODUSERNAMEST_IDX) */ DISTINCT jediTaskID "
|
|
1804
|
+
"FROM ATLAS_PANDA.jobsActive4 tab "
|
|
1805
|
+
"WHERE prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName "
|
|
1806
|
+
"AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
|
|
1807
|
+
"AND maxCpuCount>:maxTime "
|
|
1808
|
+
)
|
|
1809
|
+
|
|
1810
|
+
if workingGroup is not None:
|
|
1811
|
+
sql_get_tasks += "AND workingGroup=:workingGroup "
|
|
1812
|
+
else:
|
|
1813
|
+
sql_get_tasks += "AND workingGroup IS NULL "
|
|
1814
|
+
|
|
1815
|
+
# sql to get jobs
|
|
1816
|
+
sql_get_jobs = (
|
|
1817
|
+
"SELECT "
|
|
1818
|
+
"PandaID, jediTaskID, cloud, computingSite, prodSourceLabel "
|
|
1819
|
+
"FROM ATLAS_PANDA.jobsActive4 "
|
|
1820
|
+
"WHERE jediTaskID=:jediTaskID "
|
|
1821
|
+
"AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
|
|
1822
|
+
"AND maxCpuCount>:maxTime "
|
|
1823
|
+
)
|
|
1824
|
+
|
|
1825
|
+
# sql to update jobs
|
|
1826
|
+
sql_update_jobs = (
|
|
1827
|
+
f"UPDATE {panda_config.schemaPANDA}.jobsActive4 SET jobStatus=:newJobStatus,relocationFlag=:newRelFlag "
|
|
1828
|
+
f"WHERE jediTaskID=:jediTaskID AND jobStatus=:oldJobStatus AND maxCpuCount>:maxTime"
|
|
1829
|
+
)
|
|
1830
|
+
|
|
1831
|
+
# start transaction
|
|
1832
|
+
self.conn.begin()
|
|
1833
|
+
# select
|
|
1834
|
+
self.cur.arraysize = 10
|
|
1835
|
+
var_map = {":prodSourceLabel": "user", ":oldRelFlag": 1, ":prodUserName": prodUserName, ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
|
|
1836
|
+
if workingGroup is not None:
|
|
1837
|
+
var_map[":workingGroup"] = workingGroup
|
|
1838
|
+
# get tasks
|
|
1839
|
+
self.cur.execute(sql_get_tasks + comment, var_map)
|
|
1840
|
+
task_rows = self.cur.fetchall()
|
|
1841
|
+
# commit
|
|
1842
|
+
if not self._commit():
|
|
1843
|
+
raise RuntimeError("Commit error")
|
|
1844
|
+
# loop over all tasks
|
|
1845
|
+
task_ids = [task_id for task_id, in task_rows]
|
|
1846
|
+
random.shuffle(task_ids)
|
|
1847
|
+
total_updated = 0
|
|
1848
|
+
updated_per_task = {}
|
|
1849
|
+
for task_id in task_ids:
|
|
1850
|
+
tmp_log.debug(f"reset jediTaskID={task_id}")
|
|
1851
|
+
# start transaction
|
|
1852
|
+
self.conn.begin()
|
|
1853
|
+
# get jobs
|
|
1854
|
+
var_map = {":jediTaskID": task_id, ":oldRelFlag": 1, ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
|
|
1855
|
+
self.cur.execute(sql_get_jobs + comment, var_map)
|
|
1856
|
+
job_rows = self.cur.fetchall()
|
|
1857
|
+
job_info_map = {
|
|
1858
|
+
panda_id: {
|
|
1859
|
+
"computingSite": computing_site,
|
|
1860
|
+
"cloud": cloud,
|
|
1861
|
+
"prodSourceLabel": prod_source_label,
|
|
1862
|
+
}
|
|
1863
|
+
for panda_id, _task_id, cloud, computing_site, prod_source_label in job_rows
|
|
1864
|
+
}
|
|
1865
|
+
# update jobs
|
|
1866
|
+
var_map = {":jediTaskID": task_id, ":newRelFlag": 3, ":newJobStatus": "throttled", ":oldJobStatus": "activated", ":maxTime": 6 * 60 * 60}
|
|
1867
|
+
self.cur.execute(sql_update_jobs + comment, var_map)
|
|
1868
|
+
n_updated = self.cur.rowcount
|
|
1869
|
+
tmp_log.debug(f"reset {n_updated} jobs")
|
|
1870
|
+
if n_updated > 0:
|
|
1871
|
+
total_updated += n_updated
|
|
1872
|
+
updated_per_task[task_id] = n_updated
|
|
1873
|
+
for panda_id, job_info in job_info_map.items():
|
|
1874
|
+
self.recordStatusChange(
|
|
1875
|
+
panda_id,
|
|
1876
|
+
var_map[":newJobStatus"],
|
|
1877
|
+
infoMap=job_info,
|
|
1878
|
+
useCommit=False,
|
|
1879
|
+
)
|
|
1880
|
+
# commit
|
|
1881
|
+
if not self._commit():
|
|
1882
|
+
raise RuntimeError("Commit error")
|
|
1883
|
+
if get_dict:
|
|
1884
|
+
tmp_log.debug(f"done with {updated_per_task}")
|
|
1885
|
+
return updated_per_task
|
|
1886
|
+
tmp_log.debug(f"done with {total_updated}")
|
|
1887
|
+
return total_updated
|
|
1888
|
+
except Exception:
|
|
1889
|
+
# roll back
|
|
1890
|
+
self._rollback()
|
|
1891
|
+
# error
|
|
1892
|
+
self.dump_error_message(tmp_log)
|
|
1893
|
+
return None
|
|
1894
|
+
|
|
1895
|
+
# unthrottle user jobs
|
|
1896
|
+
def unThrottleUserJobs(self, prodUserName, workingGroup, get_dict):
|
|
1897
|
+
comment = " /* DBProxy.unThrottleUserJobs */"
|
|
1898
|
+
tmp_log = self.create_tagged_logger(comment, f"user={prodUserName} group={workingGroup}")
|
|
1899
|
+
tmp_log.debug("start")
|
|
1900
|
+
try:
|
|
1901
|
+
# sql to get tasks
|
|
1902
|
+
sql_get_tasks = (
|
|
1903
|
+
"SELECT /*+ INDEX_RS_ASC(tab JOBSACTIVE4_PRODUSERNAMEST_IDX) */ DISTINCT jediTaskID "
|
|
1904
|
+
"FROM ATLAS_PANDA.jobsActive4 tab "
|
|
1905
|
+
"WHERE prodSourceLabel=:prodSourceLabel AND prodUserName=:prodUserName "
|
|
1906
|
+
"AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
if workingGroup is not None:
|
|
1910
|
+
sql_get_tasks += "AND workingGroup=:workingGroup "
|
|
1911
|
+
else:
|
|
1912
|
+
sql_get_tasks += "AND workingGroup IS NULL "
|
|
1913
|
+
|
|
1914
|
+
# sql to get jobs
|
|
1915
|
+
sql_get_jobs = (
|
|
1916
|
+
"SELECT "
|
|
1917
|
+
"PandaID, jediTaskID, cloud, computingSite, prodSourceLabel "
|
|
1918
|
+
"FROM ATLAS_PANDA.jobsActive4 "
|
|
1919
|
+
"WHERE jediTaskID=:jediTaskID "
|
|
1920
|
+
"AND jobStatus=:oldJobStatus AND relocationFlag=:oldRelFlag "
|
|
1921
|
+
)
|
|
1922
|
+
|
|
1923
|
+
# sql to update jobs
|
|
1924
|
+
sql_update_jobs = (
|
|
1925
|
+
f"UPDATE {panda_config.schemaPANDA}.jobsActive4 SET jobStatus=:newJobStatus,relocationFlag=:newRelFlag "
|
|
1926
|
+
"WHERE jediTaskID=:jediTaskID AND jobStatus=:oldJobStatus "
|
|
1927
|
+
)
|
|
1928
|
+
|
|
1929
|
+
# start transaction
|
|
1930
|
+
self.conn.begin()
|
|
1931
|
+
# select
|
|
1932
|
+
self.cur.arraysize = 10
|
|
1933
|
+
var_map = {":prodSourceLabel": "user", ":oldRelFlag": 3, ":prodUserName": prodUserName, ":oldJobStatus": "throttled"}
|
|
1934
|
+
if workingGroup is not None:
|
|
1935
|
+
var_map[":workingGroup"] = workingGroup
|
|
1936
|
+
# get tasks
|
|
1937
|
+
self.cur.execute(sql_get_tasks + comment, var_map)
|
|
1938
|
+
task_rows = self.cur.fetchall()
|
|
1939
|
+
# commit
|
|
1940
|
+
if not self._commit():
|
|
1941
|
+
raise RuntimeError("Commit error")
|
|
1942
|
+
# loop over all tasks
|
|
1943
|
+
task_ids = [task_id for task_id, in task_rows]
|
|
1944
|
+
random.shuffle(task_ids)
|
|
1945
|
+
total_updated = 0
|
|
1946
|
+
updated_per_task = {}
|
|
1947
|
+
for task_id in task_ids:
|
|
1948
|
+
tmp_log.debug(f"reset jediTaskID={task_id}")
|
|
1949
|
+
# start transaction
|
|
1950
|
+
self.conn.begin()
|
|
1951
|
+
# get jobs
|
|
1952
|
+
var_map = {":jediTaskID": task_id, ":oldRelFlag": 3, ":oldJobStatus": "throttled"}
|
|
1953
|
+
self.cur.execute(sql_get_jobs + comment, var_map)
|
|
1954
|
+
job_rows = self.cur.fetchall()
|
|
1955
|
+
job_info_map = {
|
|
1956
|
+
panda_id: {
|
|
1957
|
+
"computingSite": computing_site,
|
|
1958
|
+
"cloud": cloud,
|
|
1959
|
+
"prodSourceLabel": prod_source_label,
|
|
1960
|
+
}
|
|
1961
|
+
for panda_id, _task_id, cloud, computing_site, prod_source_label in job_rows
|
|
1962
|
+
}
|
|
1963
|
+
# update jobs
|
|
1964
|
+
var_map = {":jediTaskID": task_id, ":newRelFlag": 1, ":newJobStatus": "activated", ":oldJobStatus": "throttled"}
|
|
1965
|
+
self.cur.execute(sql_update_jobs + comment, var_map)
|
|
1966
|
+
n_updated = self.cur.rowcount
|
|
1967
|
+
tmp_log.debug(f"reset {n_updated} jobs")
|
|
1968
|
+
if n_updated > 0:
|
|
1969
|
+
total_updated += n_updated
|
|
1970
|
+
updated_per_task[task_id] = n_updated
|
|
1971
|
+
for panda_id, job_info in job_info_map.items():
|
|
1972
|
+
self.recordStatusChange(
|
|
1973
|
+
panda_id,
|
|
1974
|
+
var_map[":newJobStatus"],
|
|
1975
|
+
infoMap=job_info,
|
|
1976
|
+
useCommit=False,
|
|
1977
|
+
)
|
|
1978
|
+
# commit
|
|
1979
|
+
if not self._commit():
|
|
1980
|
+
raise RuntimeError("Commit error")
|
|
1981
|
+
if get_dict:
|
|
1982
|
+
tmp_log.debug(f"done with {updated_per_task}")
|
|
1983
|
+
return updated_per_task
|
|
1984
|
+
tmp_log.debug(f"done with {total_updated}")
|
|
1985
|
+
return total_updated
|
|
1986
|
+
except Exception:
|
|
1987
|
+
# roll back
|
|
1988
|
+
self._rollback()
|
|
1989
|
+
# error
|
|
1990
|
+
self.dump_error_message(tmp_log)
|
|
1991
|
+
return None
|
|
1992
|
+
|
|
1993
|
+
# get the list of jobdefIDs for failed jobs in a task
|
|
1994
|
+
def getJobdefIDsForFailedJob(self, jediTaskID):
|
|
1995
|
+
comment = " /* DBProxy.getJobdefIDsForFailedJob */"
|
|
1996
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
1997
|
+
tmp_log.debug(f"start")
|
|
1998
|
+
try:
|
|
1999
|
+
# begin transaction
|
|
2000
|
+
self.conn.begin()
|
|
2001
|
+
# dql to get jobDefIDs
|
|
2002
|
+
sqlGF = "SELECT distinct jobDefinitionID FROM ATLAS_PANDA.jobsActive4 "
|
|
2003
|
+
sqlGF += "WHERE jediTaskID=:jediTaskID AND jobStatus=:jobStatus "
|
|
2004
|
+
sqlGF += "AND attemptNr<maxAttempt "
|
|
2005
|
+
varMap = {}
|
|
2006
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
2007
|
+
varMap[":jobStatus"] = "failed"
|
|
2008
|
+
self.cur.execute(sqlGF + comment, varMap)
|
|
2009
|
+
resGF = self.cur.fetchall()
|
|
2010
|
+
retList = []
|
|
2011
|
+
for (jobDefinitionID,) in resGF:
|
|
2012
|
+
retList.append(jobDefinitionID)
|
|
2013
|
+
# commit
|
|
2014
|
+
if not self._commit():
|
|
2015
|
+
raise RuntimeError("Commit error")
|
|
2016
|
+
tmp_log.debug(f"{str(retList)}")
|
|
2017
|
+
return retList
|
|
2018
|
+
except Exception:
|
|
2019
|
+
# roll back
|
|
2020
|
+
self._rollback()
|
|
2021
|
+
# error
|
|
2022
|
+
self.dump_error_message(tmp_log)
|
|
2023
|
+
return []
|
|
2024
|
+
|
|
2025
|
+
# check validity of merge job
|
|
2026
|
+
def isValidMergeJob(self, pandaID, jediTaskID):
|
|
2027
|
+
comment = " /* DBProxy.isValidMergeJob */"
|
|
2028
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID} jediTaskID={jediTaskID}")
|
|
2029
|
+
tmp_log.debug("start")
|
|
2030
|
+
try:
|
|
2031
|
+
retVal = True
|
|
2032
|
+
retMsg = ""
|
|
2033
|
+
# sql to check if merge job is active
|
|
2034
|
+
sqlJ = "SELECT jobStatus FROM ATLAS_PANDA.jobsDefined4 WHERE PandaID=:PandaID "
|
|
2035
|
+
sqlJ += "UNION "
|
|
2036
|
+
sqlJ += "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
|
|
2037
|
+
# sql to get input files
|
|
2038
|
+
sqlF = "SELECT datasetID,fileID FROM ATLAS_PANDA.filesTable4 "
|
|
2039
|
+
sqlF += "WHERE PandaID=:PandaID AND type IN (:type1,:type2) "
|
|
2040
|
+
# sql to get PandaIDs for pre-merged jobs
|
|
2041
|
+
sqlP = "SELECT outPandaID FROM ATLAS_PANDA.JEDI_Dataset_Contents "
|
|
2042
|
+
sqlP += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID=:fileID AND type<>:type1"
|
|
2043
|
+
# sql to check if pre-merge job is active
|
|
2044
|
+
sqlC = "SELECT jobStatus FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
|
|
2045
|
+
# begin transaction
|
|
2046
|
+
self.conn.begin()
|
|
2047
|
+
# check if merge job is active
|
|
2048
|
+
varMap = {}
|
|
2049
|
+
varMap[":PandaID"] = pandaID
|
|
2050
|
+
self.cur.execute(sqlJ + comment, varMap)
|
|
2051
|
+
resJ = self.cur.fetchone()
|
|
2052
|
+
if resJ is None:
|
|
2053
|
+
tmp_log.debug("merge job not found")
|
|
2054
|
+
else:
|
|
2055
|
+
# get input files
|
|
2056
|
+
varMap = {}
|
|
2057
|
+
varMap[":PandaID"] = pandaID
|
|
2058
|
+
varMap[":type1"] = "input"
|
|
2059
|
+
varMap[":type2"] = "pseudo_input"
|
|
2060
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
2061
|
+
resF = self.cur.fetchall()
|
|
2062
|
+
firstDatasetID = None
|
|
2063
|
+
fileIDsMap = {}
|
|
2064
|
+
for datasetID, fileID in resF:
|
|
2065
|
+
if datasetID not in fileIDsMap:
|
|
2066
|
+
fileIDsMap[datasetID] = set()
|
|
2067
|
+
fileIDsMap[datasetID].add(fileID)
|
|
2068
|
+
# get PandaIDs for pre-merged jobs
|
|
2069
|
+
pandaIDs = set()
|
|
2070
|
+
for datasetID in fileIDsMap:
|
|
2071
|
+
fileIDs = fileIDsMap[datasetID]
|
|
2072
|
+
for fileID in fileIDs:
|
|
2073
|
+
varMap = {}
|
|
2074
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
2075
|
+
varMap[":datasetID"] = datasetID
|
|
2076
|
+
varMap[":fileID"] = fileID
|
|
2077
|
+
varMap[":type1"] = "lib"
|
|
2078
|
+
self.cur.execute(sqlP + comment, varMap)
|
|
2079
|
+
resP = self.cur.fetchone()
|
|
2080
|
+
if resP is not None and resP[0] is not None:
|
|
2081
|
+
pandaIDs.add(resP[0])
|
|
2082
|
+
# only files in the first dataset are enough
|
|
2083
|
+
if len(pandaIDs) > 0:
|
|
2084
|
+
break
|
|
2085
|
+
# check pre-merge job
|
|
2086
|
+
for tmpPandaID in pandaIDs:
|
|
2087
|
+
varMap = {}
|
|
2088
|
+
varMap[":PandaID"] = tmpPandaID
|
|
2089
|
+
self.cur.execute(sqlC + comment, varMap)
|
|
2090
|
+
resC = self.cur.fetchone()
|
|
2091
|
+
if resC is None:
|
|
2092
|
+
# not found
|
|
2093
|
+
tmp_log.debug(f"pre-merge job {tmpPandaID} not found")
|
|
2094
|
+
retVal = False
|
|
2095
|
+
retMsg = tmpPandaID
|
|
2096
|
+
break
|
|
2097
|
+
elif resC[0] != "merging":
|
|
2098
|
+
# not in merging
|
|
2099
|
+
tmp_log.debug("pre-merge job in {0} != merging".format(tmpPandaID, resC[0]))
|
|
2100
|
+
retVal = False
|
|
2101
|
+
retMsg = tmpPandaID
|
|
2102
|
+
break
|
|
2103
|
+
# commit
|
|
2104
|
+
if not self._commit():
|
|
2105
|
+
raise RuntimeError("Commit error")
|
|
2106
|
+
tmp_log.debug(f"ret={retVal}")
|
|
2107
|
+
return retVal, retMsg
|
|
2108
|
+
except Exception:
|
|
2109
|
+
# roll back
|
|
2110
|
+
self._rollback()
|
|
2111
|
+
# error
|
|
2112
|
+
self.dump_error_message(tmp_log)
|
|
2113
|
+
return None, ""
|
|
2114
|
+
|
|
2115
|
+
# check Job status
|
|
2116
|
+
def checkJobStatus(self, pandaID):
|
|
2117
|
+
comment = " /* DBProxy.checkJobStatus */"
|
|
2118
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
2119
|
+
tmp_log.debug("start")
|
|
2120
|
+
retVal = {"command": None, "status": None}
|
|
2121
|
+
try:
|
|
2122
|
+
sqlC = (
|
|
2123
|
+
"SELECT jobStatus,commandToPilot FROM ATLAS_PANDA.jobsActive4 "
|
|
2124
|
+
"WHERE PandaID=:pandaID "
|
|
2125
|
+
"UNION "
|
|
2126
|
+
"SELECT /*+ INDEX_RS_ASC(JOBSARCHIVED4 PART_JOBSARCHIVED4_PK) */ "
|
|
2127
|
+
"jobStatus,commandToPilot FROM ATLAS_PANDA.jobsArchived4 "
|
|
2128
|
+
"WHERE PandaID=:pandaID AND modificationTime>:timeLimit "
|
|
2129
|
+
)
|
|
2130
|
+
varMap = dict()
|
|
2131
|
+
varMap[":pandaID"] = int(pandaID)
|
|
2132
|
+
varMap[":timeLimit"] = naive_utcnow() - datetime.timedelta(hours=1)
|
|
2133
|
+
# begin transaction
|
|
2134
|
+
self.conn.begin()
|
|
2135
|
+
# select
|
|
2136
|
+
self.cur.arraysize = 10
|
|
2137
|
+
self.cur.execute(sqlC + comment, varMap)
|
|
2138
|
+
res = self.cur.fetchone()
|
|
2139
|
+
if res is not None:
|
|
2140
|
+
retVal["status"], retVal["command"] = res
|
|
2141
|
+
else:
|
|
2142
|
+
retVal["status"], retVal["command"] = "unknown", "tobekilled"
|
|
2143
|
+
# commit
|
|
2144
|
+
if not self._commit():
|
|
2145
|
+
raise RuntimeError("Commit error")
|
|
2146
|
+
tmp_log.debug(f"done with {str(retVal)}")
|
|
2147
|
+
return retVal
|
|
2148
|
+
except Exception:
|
|
2149
|
+
# roll back
|
|
2150
|
+
self._rollback()
|
|
2151
|
+
self.dump_error_message(tmp_log)
|
|
2152
|
+
return retVal
|
|
2153
|
+
|
|
2154
|
+
# get active job attribute
|
|
2155
|
+
def getActiveJobAttributes(self, pandaID, attrs):
|
|
2156
|
+
comment = " /* DBProxy.getActiveJobAttributes */"
|
|
2157
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
2158
|
+
tmp_log.debug("start")
|
|
2159
|
+
try:
|
|
2160
|
+
sqlS = f"SELECT {','.join(attrs)} FROM ATLAS_PANDA.jobsActive4 "
|
|
2161
|
+
sqlS += "WHERE PandaID=:PandaID "
|
|
2162
|
+
# start transaction
|
|
2163
|
+
self.conn.begin()
|
|
2164
|
+
varMap = {}
|
|
2165
|
+
varMap[":PandaID"] = pandaID
|
|
2166
|
+
self.cur.execute(sqlS + comment, varMap)
|
|
2167
|
+
res = self.cur.fetchone()
|
|
2168
|
+
if res is not None:
|
|
2169
|
+
retMap = dict()
|
|
2170
|
+
for idx, attr in enumerate(attrs):
|
|
2171
|
+
retMap[attr] = res[idx]
|
|
2172
|
+
else:
|
|
2173
|
+
retMap = None
|
|
2174
|
+
# commit
|
|
2175
|
+
if not self._commit():
|
|
2176
|
+
raise RuntimeError("Commit error")
|
|
2177
|
+
tmp_log.debug(f"got {str(retMap)}")
|
|
2178
|
+
return retMap
|
|
2179
|
+
except Exception:
|
|
2180
|
+
# roll back
|
|
2181
|
+
self._rollback()
|
|
2182
|
+
# error
|
|
2183
|
+
self.dump_error_message(tmp_log)
|
|
2184
|
+
return None
|
|
2185
|
+
|
|
2186
|
+
# get user job metadata
|
|
2187
|
+
def getUserJobMetadata(self, jediTaskID):
|
|
2188
|
+
comment = " /* DBProxy.getUserJobMetadata */"
|
|
2189
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
2190
|
+
tmp_log.debug("start")
|
|
2191
|
+
try:
|
|
2192
|
+
# sql to get workers
|
|
2193
|
+
sqlC = "SELECT j.PandaID,m.metaData FROM {0} j,{1} m "
|
|
2194
|
+
sqlC += "WHERE j.jediTaskID=:jediTaskID AND j.jobStatus=:jobStatus AND m.PandaID=j.PandaID AND j.prodSourceLabel=:label "
|
|
2195
|
+
retMap = dict()
|
|
2196
|
+
for a, m in [
|
|
2197
|
+
("ATLAS_PANDA.jobsArchived4", "ATLAS_PANDA.metaTable"),
|
|
2198
|
+
("ATLAS_PANDAARCH.jobsArchived", "ATLAS_PANDAARCH.metaTable_ARCH"),
|
|
2199
|
+
]:
|
|
2200
|
+
sql = sqlC.format(a, m)
|
|
2201
|
+
varMap = {}
|
|
2202
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
2203
|
+
varMap[":label"] = "user"
|
|
2204
|
+
varMap[":jobStatus"] = "finished"
|
|
2205
|
+
# start transaction
|
|
2206
|
+
self.conn.begin()
|
|
2207
|
+
self.cur.execute(sql + comment, varMap)
|
|
2208
|
+
resCs = self.cur.fetchall()
|
|
2209
|
+
for pandaID, clobMeta in resCs:
|
|
2210
|
+
try:
|
|
2211
|
+
metadata = clobMeta.read()
|
|
2212
|
+
except AttributeError:
|
|
2213
|
+
metadata = str(clobMeta)
|
|
2214
|
+
try:
|
|
2215
|
+
retMap[pandaID] = json.loads(metadata)
|
|
2216
|
+
except Exception:
|
|
2217
|
+
pass
|
|
2218
|
+
# commit
|
|
2219
|
+
if not self._commit():
|
|
2220
|
+
raise RuntimeError("Commit error")
|
|
2221
|
+
tmp_log.debug(f"got {len(retMap)} data blocks")
|
|
2222
|
+
return retMap
|
|
2223
|
+
except Exception:
|
|
2224
|
+
# roll back
|
|
2225
|
+
self._rollback()
|
|
2226
|
+
# error
|
|
2227
|
+
self.dump_error_message(tmp_log)
|
|
2228
|
+
return {}
|
|
2229
|
+
|
|
2230
|
+
# insert job output report
|
|
2231
|
+
def insertJobOutputReport(self, panda_id, prod_source_label, job_status, attempt_nr, data):
|
|
2232
|
+
comment = " /* DBProxy.insertJobOutputReport */"
|
|
2233
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2234
|
+
tmp_log.debug("start")
|
|
2235
|
+
# sql to insert
|
|
2236
|
+
sqlI = (
|
|
2237
|
+
"INSERT INTO {0}.Job_Output_Report "
|
|
2238
|
+
"(PandaID, prodSourceLabel, jobStatus, attemptNr, data, timeStamp) "
|
|
2239
|
+
"VALUES(:PandaID, :prodSourceLabel, :jobStatus, :attemptNr, :data, :timeStamp) "
|
|
2240
|
+
).format(panda_config.schemaPANDA)
|
|
2241
|
+
try:
|
|
2242
|
+
retVal = False
|
|
2243
|
+
# start transaction
|
|
2244
|
+
self.conn.begin()
|
|
2245
|
+
# insert
|
|
2246
|
+
varMap = {}
|
|
2247
|
+
varMap[":PandaID"] = panda_id
|
|
2248
|
+
varMap[":prodSourceLabel"] = prod_source_label
|
|
2249
|
+
varMap[":jobStatus"] = job_status
|
|
2250
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2251
|
+
varMap[":data"] = data
|
|
2252
|
+
varMap[":timeStamp"] = naive_utcnow()
|
|
2253
|
+
self.cur.execute(sqlI + comment, varMap)
|
|
2254
|
+
tmp_log.debug("successfully inserted")
|
|
2255
|
+
retVal = True
|
|
2256
|
+
# commit
|
|
2257
|
+
if not self._commit():
|
|
2258
|
+
raise RuntimeError("Commit error")
|
|
2259
|
+
tmp_log.debug("done")
|
|
2260
|
+
return retVal
|
|
2261
|
+
except Exception:
|
|
2262
|
+
# roll back
|
|
2263
|
+
self._rollback()
|
|
2264
|
+
# error
|
|
2265
|
+
self.dump_error_message(tmp_log)
|
|
2266
|
+
return retVal
|
|
2267
|
+
|
|
2268
|
+
# update data of job output report
|
|
2269
|
+
def updateJobOutputReport(self, panda_id, attempt_nr, data):
|
|
2270
|
+
comment = " /* DBProxy.updateJobOutputReport */"
|
|
2271
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2272
|
+
tmp_log.debug("start")
|
|
2273
|
+
# try to lock
|
|
2274
|
+
try:
|
|
2275
|
+
retVal = False
|
|
2276
|
+
# sql to update
|
|
2277
|
+
sqlU = f"UPDATE {panda_config.schemaPANDA}.Job_Output_Report SET data=:data, timeStamp=:timeStamp WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2278
|
+
# start transaction
|
|
2279
|
+
self.conn.begin()
|
|
2280
|
+
# update
|
|
2281
|
+
varMap = {}
|
|
2282
|
+
varMap[":PandaID"] = panda_id
|
|
2283
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2284
|
+
varMap[":data"] = data
|
|
2285
|
+
varMap[":timeStamp"] = naive_utcnow()
|
|
2286
|
+
self.cur.execute(sqlU + comment, varMap)
|
|
2287
|
+
nRow = self.cur.rowcount
|
|
2288
|
+
if nRow == 1:
|
|
2289
|
+
tmp_log.debug("successfully updated")
|
|
2290
|
+
retVal = True
|
|
2291
|
+
elif nRow == 0:
|
|
2292
|
+
tmp_log.debug("entry not found, not updated")
|
|
2293
|
+
else:
|
|
2294
|
+
tmp_log.warning(f"updated unspecific number of rows: {nRow}")
|
|
2295
|
+
# commit
|
|
2296
|
+
if not self._commit():
|
|
2297
|
+
raise RuntimeError("Commit error")
|
|
2298
|
+
tmp_log.debug("done")
|
|
2299
|
+
return retVal
|
|
2300
|
+
except Exception:
|
|
2301
|
+
# roll back
|
|
2302
|
+
self._rollback()
|
|
2303
|
+
# error
|
|
2304
|
+
self.dump_error_message(tmp_log)
|
|
2305
|
+
return retVal
|
|
2306
|
+
|
|
2307
|
+
# deleted job output report
|
|
2308
|
+
def deleteJobOutputReport(self, panda_id, attempt_nr):
|
|
2309
|
+
comment = " /* DBProxy.deleteJobOutputReport */"
|
|
2310
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2311
|
+
tmp_log.debug("start")
|
|
2312
|
+
# sql to delete
|
|
2313
|
+
sqlD = f"DELETE FROM {panda_config.schemaPANDA}.Job_Output_Report WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2314
|
+
try:
|
|
2315
|
+
retVal = False
|
|
2316
|
+
# start transaction
|
|
2317
|
+
self.conn.begin()
|
|
2318
|
+
# delete
|
|
2319
|
+
varMap = {}
|
|
2320
|
+
varMap[":PandaID"] = panda_id
|
|
2321
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2322
|
+
self.cur.execute(sqlD + comment, varMap)
|
|
2323
|
+
tmp_log.debug("successfully deleted")
|
|
2324
|
+
retVal = True
|
|
2325
|
+
# commit
|
|
2326
|
+
if not self._commit():
|
|
2327
|
+
raise RuntimeError("Commit error")
|
|
2328
|
+
tmp_log.debug("done")
|
|
2329
|
+
return retVal
|
|
2330
|
+
except Exception:
|
|
2331
|
+
# roll back
|
|
2332
|
+
self._rollback()
|
|
2333
|
+
# error
|
|
2334
|
+
self.dump_error_message(tmp_log)
|
|
2335
|
+
return retVal
|
|
2336
|
+
|
|
2337
|
+
# get record of a job output report
|
|
2338
|
+
def getJobOutputReport(self, panda_id, attempt_nr):
|
|
2339
|
+
comment = " /* DBProxy.getJobOutputReport */"
|
|
2340
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2341
|
+
tmp_log.debug("start")
|
|
2342
|
+
# try to lock
|
|
2343
|
+
try:
|
|
2344
|
+
retVal = {}
|
|
2345
|
+
# sql to get records
|
|
2346
|
+
sqlGR = (
|
|
2347
|
+
"SELECT PandaID,prodSourceLabel,jobStatus,attemptNr,data,timeStamp,lockedBy,lockedTime "
|
|
2348
|
+
"FROM {0}.Job_Output_Report "
|
|
2349
|
+
"WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2350
|
+
).format(panda_config.schemaPANDA)
|
|
2351
|
+
# start transaction
|
|
2352
|
+
self.conn.begin()
|
|
2353
|
+
# check
|
|
2354
|
+
varMap = {}
|
|
2355
|
+
varMap[":PandaID"] = panda_id
|
|
2356
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2357
|
+
self.cur.execute(sqlGR + comment, varMap)
|
|
2358
|
+
resGR = self.cur.fetchall()
|
|
2359
|
+
if not resGR:
|
|
2360
|
+
tmp_log.debug("record does not exist, skipped")
|
|
2361
|
+
for (
|
|
2362
|
+
PandaID,
|
|
2363
|
+
prodSourceLabel,
|
|
2364
|
+
jobStatus,
|
|
2365
|
+
attemptNr,
|
|
2366
|
+
data,
|
|
2367
|
+
timeStamp,
|
|
2368
|
+
lockedBy,
|
|
2369
|
+
lockedTime,
|
|
2370
|
+
) in resGR:
|
|
2371
|
+
# fill result
|
|
2372
|
+
retVal = {
|
|
2373
|
+
"PandaID": PandaID,
|
|
2374
|
+
"jobStatus": jobStatus,
|
|
2375
|
+
"attemptNr": attemptNr,
|
|
2376
|
+
"timeStamp": timeStamp,
|
|
2377
|
+
"data": data,
|
|
2378
|
+
"lockedBy": lockedBy,
|
|
2379
|
+
"lockedTime": lockedTime,
|
|
2380
|
+
}
|
|
2381
|
+
tmp_log.debug("got record")
|
|
2382
|
+
break
|
|
2383
|
+
# commit
|
|
2384
|
+
if not self._commit():
|
|
2385
|
+
raise RuntimeError("Commit error")
|
|
2386
|
+
tmp_log.debug("done")
|
|
2387
|
+
return retVal
|
|
2388
|
+
except Exception:
|
|
2389
|
+
# roll back
|
|
2390
|
+
self._rollback()
|
|
2391
|
+
# error
|
|
2392
|
+
self.dump_error_message(tmp_log)
|
|
2393
|
+
return retVal
|
|
2394
|
+
|
|
2395
|
+
# lock job output report
|
|
2396
|
+
def lockJobOutputReport(self, panda_id, attempt_nr, pid, time_limit, take_over_from=None):
|
|
2397
|
+
comment = " /* DBProxy.lockJobOutputReport */"
|
|
2398
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2399
|
+
tmp_log.debug("start")
|
|
2400
|
+
# try to lock
|
|
2401
|
+
try:
|
|
2402
|
+
retVal = []
|
|
2403
|
+
# sql to get lock
|
|
2404
|
+
sqlGL = (
|
|
2405
|
+
"SELECT PandaID,attemptNr "
|
|
2406
|
+
"FROM {0}.Job_Output_Report "
|
|
2407
|
+
"WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2408
|
+
"AND (lockedBy IS NULL OR lockedBy=:lockedBy OR lockedTime<:lockedTime) "
|
|
2409
|
+
"FOR UPDATE NOWAIT "
|
|
2410
|
+
).format(panda_config.schemaPANDA)
|
|
2411
|
+
# sql to update lock
|
|
2412
|
+
sqlUL = (
|
|
2413
|
+
"UPDATE {0}.Job_Output_Report " "SET lockedBy=:lockedBy, lockedTime=:lockedTime " "WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2414
|
+
).format(panda_config.schemaPANDA)
|
|
2415
|
+
# start transaction
|
|
2416
|
+
self.conn.begin()
|
|
2417
|
+
# check
|
|
2418
|
+
varMap = {}
|
|
2419
|
+
varMap[":PandaID"] = panda_id
|
|
2420
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2421
|
+
if take_over_from is None:
|
|
2422
|
+
varMap[":lockedBy"] = pid
|
|
2423
|
+
else:
|
|
2424
|
+
varMap[":lockedBy"] = take_over_from
|
|
2425
|
+
varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
|
|
2426
|
+
utc_now = naive_utcnow()
|
|
2427
|
+
try:
|
|
2428
|
+
self.cur.execute(sqlGL + comment, varMap)
|
|
2429
|
+
resGL = self.cur.fetchall()
|
|
2430
|
+
if not resGL:
|
|
2431
|
+
tmp_log.debug("record already locked by other thread, skipped")
|
|
2432
|
+
except Exception:
|
|
2433
|
+
resGL = None
|
|
2434
|
+
tmp_log.debug("record skipped due to NOWAIT")
|
|
2435
|
+
if resGL:
|
|
2436
|
+
for panda_id, attempt_nr in resGL:
|
|
2437
|
+
# lock
|
|
2438
|
+
varMap = {}
|
|
2439
|
+
varMap[":PandaID"] = panda_id
|
|
2440
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2441
|
+
varMap[":lockedBy"] = pid
|
|
2442
|
+
varMap[":lockedTime"] = utc_now
|
|
2443
|
+
self.cur.execute(sqlUL + comment, varMap)
|
|
2444
|
+
if take_over_from is None:
|
|
2445
|
+
tmp_log.debug(f"successfully locked record by {pid}")
|
|
2446
|
+
else:
|
|
2447
|
+
tmp_log.debug(f"successfully took over locked record from {take_over_from} by {pid}")
|
|
2448
|
+
retVal = True
|
|
2449
|
+
break
|
|
2450
|
+
# commit
|
|
2451
|
+
if not self._commit():
|
|
2452
|
+
raise RuntimeError("Commit error")
|
|
2453
|
+
tmp_log.debug("done")
|
|
2454
|
+
return retVal
|
|
2455
|
+
except Exception:
|
|
2456
|
+
# roll back
|
|
2457
|
+
self._rollback()
|
|
2458
|
+
# error
|
|
2459
|
+
self.dump_error_message(tmp_log)
|
|
2460
|
+
return retVal
|
|
2461
|
+
|
|
2462
|
+
# unlock job output report
|
|
2463
|
+
def unlockJobOutputReport(self, panda_id, attempt_nr, pid, lock_offset):
|
|
2464
|
+
comment = " /* DBProxy.unlockJobOutputReport */"
|
|
2465
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id} attemptNr={attempt_nr}")
|
|
2466
|
+
tmp_log.debug("start")
|
|
2467
|
+
# try to lock
|
|
2468
|
+
try:
|
|
2469
|
+
retVal = []
|
|
2470
|
+
# sql to get lock
|
|
2471
|
+
sqlGL = (
|
|
2472
|
+
"SELECT PandaID,attemptNr "
|
|
2473
|
+
"FROM {0}.Job_Output_Report "
|
|
2474
|
+
"WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2475
|
+
"AND lockedBy=:lockedBy "
|
|
2476
|
+
"FOR UPDATE"
|
|
2477
|
+
).format(panda_config.schemaPANDA)
|
|
2478
|
+
# sql to update lock
|
|
2479
|
+
sqlUL = f"UPDATE {panda_config.schemaPANDA}.Job_Output_Report SET lockedTime=:lockedTime WHERE PandaID=:PandaID AND attemptNr=:attemptNr "
|
|
2480
|
+
# start transaction
|
|
2481
|
+
self.conn.begin()
|
|
2482
|
+
# check
|
|
2483
|
+
varMap = {}
|
|
2484
|
+
varMap[":PandaID"] = panda_id
|
|
2485
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2486
|
+
varMap[":lockedBy"] = pid
|
|
2487
|
+
self.cur.execute(sqlGL + comment, varMap)
|
|
2488
|
+
resGL = self.cur.fetchall()
|
|
2489
|
+
if not resGL:
|
|
2490
|
+
tmp_log.debug("record not locked by this thread, skipped")
|
|
2491
|
+
else:
|
|
2492
|
+
for panda_id, attempt_nr in resGL:
|
|
2493
|
+
# lock
|
|
2494
|
+
varMap = {}
|
|
2495
|
+
varMap[":PandaID"] = panda_id
|
|
2496
|
+
varMap[":attemptNr"] = attempt_nr
|
|
2497
|
+
varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=lock_offset)
|
|
2498
|
+
self.cur.execute(sqlUL + comment, varMap)
|
|
2499
|
+
tmp_log.debug("successfully unlocked record")
|
|
2500
|
+
retVal = True
|
|
2501
|
+
break
|
|
2502
|
+
# commit
|
|
2503
|
+
if not self._commit():
|
|
2504
|
+
raise RuntimeError("Commit error")
|
|
2505
|
+
tmp_log.debug("done")
|
|
2506
|
+
return retVal
|
|
2507
|
+
except Exception:
|
|
2508
|
+
# roll back
|
|
2509
|
+
self._rollback()
|
|
2510
|
+
# error
|
|
2511
|
+
self.dump_error_message(tmp_log)
|
|
2512
|
+
return retVal
|
|
2513
|
+
|
|
2514
|
+
# list pandaID, jobStatus, attemptNr, timeStamp of job output report
|
|
2515
|
+
def listJobOutputReport(self, only_unlocked, time_limit, limit, grace_period, labels, anti_labels):
|
|
2516
|
+
comment = " /* DBProxy.listJobOutputReport */"
|
|
2517
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2518
|
+
tmp_log.debug(f"start label={str(labels)} limit={limit} anti_label={str(anti_labels)}")
|
|
2519
|
+
try:
|
|
2520
|
+
retVal = None
|
|
2521
|
+
if only_unlocked:
|
|
2522
|
+
# try to get only records unlocked or with expired lock
|
|
2523
|
+
varMap = {}
|
|
2524
|
+
varMap[":limit"] = limit * 10
|
|
2525
|
+
varMap[":lockedTime"] = naive_utcnow() - datetime.timedelta(minutes=time_limit)
|
|
2526
|
+
varMap[":timeStamp"] = naive_utcnow() - datetime.timedelta(seconds=grace_period)
|
|
2527
|
+
# sql to get record
|
|
2528
|
+
sqlGR = (
|
|
2529
|
+
"SELECT * "
|
|
2530
|
+
"FROM ( "
|
|
2531
|
+
"SELECT PandaID,jobStatus,attemptNr,timeStamp "
|
|
2532
|
+
"FROM {0}.Job_Output_Report "
|
|
2533
|
+
"WHERE (lockedBy IS NULL OR lockedTime<:lockedTime) "
|
|
2534
|
+
"AND timeStamp<:timeStamp ".format(panda_config.schemaPANDA)
|
|
2535
|
+
)
|
|
2536
|
+
if labels is not None:
|
|
2537
|
+
label_var_names_str, label_var_map = get_sql_IN_bind_variables(labels, prefix=":l_", value_as_suffix=True)
|
|
2538
|
+
sqlGR += f"AND prodSourceLabel IN ({label_var_names_str}) "
|
|
2539
|
+
varMap.update(label_var_map)
|
|
2540
|
+
if anti_labels is not None:
|
|
2541
|
+
anti_label_var_names_str, anti_label_var_map = get_sql_IN_bind_variables(anti_labels, prefix=":al_", value_as_suffix=True)
|
|
2542
|
+
sqlGR += f"AND prodSourceLabel NOT IN ({anti_label_var_names_str}) "
|
|
2543
|
+
varMap.update(anti_label_var_map)
|
|
2544
|
+
sqlGR += "ORDER BY timeStamp " ") " "WHERE rownum<=:limit "
|
|
2545
|
+
# start transaction
|
|
2546
|
+
self.conn.begin()
|
|
2547
|
+
# check
|
|
2548
|
+
self.cur.execute(sqlGR + comment, varMap)
|
|
2549
|
+
separator = limit // 10
|
|
2550
|
+
retVal = self.cur.fetchall()
|
|
2551
|
+
# shuffle tail to avoid conflict
|
|
2552
|
+
ret_head = retVal[:separator]
|
|
2553
|
+
ret_tail = retVal[separator:]
|
|
2554
|
+
random.shuffle(ret_tail)
|
|
2555
|
+
retVal = ret_head + ret_tail
|
|
2556
|
+
retVal = retVal[:limit]
|
|
2557
|
+
tmp_log.debug(f"listed {len(retVal)} unlocked records")
|
|
2558
|
+
else:
|
|
2559
|
+
# sql to select
|
|
2560
|
+
sqlS = (
|
|
2561
|
+
"SELECT * "
|
|
2562
|
+
"FROM ( "
|
|
2563
|
+
"SELECT PandaID,jobStatus,attemptNr,timeStamp "
|
|
2564
|
+
"FROM {0}.Job_Output_Report "
|
|
2565
|
+
"ORDER BY timeStamp "
|
|
2566
|
+
") "
|
|
2567
|
+
"WHERE rownum<=:limit "
|
|
2568
|
+
).format(panda_config.schemaPANDA)
|
|
2569
|
+
# start transaction
|
|
2570
|
+
self.conn.begin()
|
|
2571
|
+
varMap = {}
|
|
2572
|
+
varMap[":limit"] = limit
|
|
2573
|
+
# check
|
|
2574
|
+
self.cur.execute(sqlS + comment, varMap)
|
|
2575
|
+
retVal = self.cur.fetchall()
|
|
2576
|
+
tmp_log.debug(f"listed {len(retVal)} records")
|
|
2577
|
+
# commit
|
|
2578
|
+
if not self._commit():
|
|
2579
|
+
raise RuntimeError("Commit error")
|
|
2580
|
+
tmp_log.debug("done")
|
|
2581
|
+
return retVal
|
|
2582
|
+
except Exception:
|
|
2583
|
+
# roll back
|
|
2584
|
+
self._rollback()
|
|
2585
|
+
# error
|
|
2586
|
+
self.dump_error_message(tmp_log)
|
|
2587
|
+
return retVal
|
|
2588
|
+
|
|
2589
|
+
# send command to a job
|
|
2590
|
+
def send_command_to_job(self, panda_id, com):
|
|
2591
|
+
comment = " /* DBProxy.send_command_to_job */"
|
|
2592
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
|
|
2593
|
+
tmp_log.debug("start")
|
|
2594
|
+
retVal = None
|
|
2595
|
+
try:
|
|
2596
|
+
# check length
|
|
2597
|
+
new_com = JobSpec.truncateStringAttr("commandToPilot", com)
|
|
2598
|
+
if len(new_com) != len(com):
|
|
2599
|
+
retVal = (
|
|
2600
|
+
False,
|
|
2601
|
+
f"command string too long. must be less than {len(new_com)} chars",
|
|
2602
|
+
)
|
|
2603
|
+
else:
|
|
2604
|
+
sqlR = "SELECT commandToPilot FROM ATLAS_PANDA.{} WHERE PandaID=:PandaID FOR UPDATE "
|
|
2605
|
+
sqlU = "UPDATE ATLAS_PANDA.{} SET commandToPilot=:commandToPilot " "WHERE PandaID=:PandaID "
|
|
2606
|
+
for table in ["jobsDefined4", "jobsActive4"]:
|
|
2607
|
+
# start transaction
|
|
2608
|
+
self.conn.begin()
|
|
2609
|
+
# read
|
|
2610
|
+
varMap = {}
|
|
2611
|
+
varMap[":PandaID"] = panda_id
|
|
2612
|
+
self.cur.execute(sqlR.format(table) + comment, varMap)
|
|
2613
|
+
data = self.cur.fetchone()
|
|
2614
|
+
if data is not None:
|
|
2615
|
+
(commandToPilot,) = data
|
|
2616
|
+
if commandToPilot == "tobekilled":
|
|
2617
|
+
retVal = (False, "job is being killed")
|
|
2618
|
+
else:
|
|
2619
|
+
varMap = {}
|
|
2620
|
+
varMap[":PandaID"] = panda_id
|
|
2621
|
+
varMap[":commandToPilot"] = com
|
|
2622
|
+
self.cur.execute(sqlU.format(table) + comment, varMap)
|
|
2623
|
+
nRow = self.cur.rowcount
|
|
2624
|
+
if nRow:
|
|
2625
|
+
retVal = (True, "command received")
|
|
2626
|
+
# commit
|
|
2627
|
+
if not self._commit():
|
|
2628
|
+
raise RuntimeError("Commit error")
|
|
2629
|
+
if retVal is not None:
|
|
2630
|
+
break
|
|
2631
|
+
if retVal is None:
|
|
2632
|
+
retVal = (False, f"no active job with PandaID={panda_id}")
|
|
2633
|
+
tmp_log.debug(f"done with {str(retVal)}")
|
|
2634
|
+
return retVal
|
|
2635
|
+
except Exception:
|
|
2636
|
+
# roll back
|
|
2637
|
+
self._rollback()
|
|
2638
|
+
# error
|
|
2639
|
+
self.dump_error_message(tmp_log)
|
|
2640
|
+
return False, "database error"
|